-
Notifications
You must be signed in to change notification settings - Fork 1
2.4. Desenvolupar l'IMS
A continuació s'ofereix un compendi de tota la informació que ofereix aquesta wiki, estructurada i guiada per a facilitar la preparació d'un entorn de desenvolupament per a l'IMS:
-
Plataforma de desenvolupament: Cal configurar una plataforma de desenvolupament per a poder instal·lar Odoo.
-
Preparació de l'entorn: Cal seguir la guia de preparació de l'entorn per a deixar el sistema preparat i poder treballar amb Odoo.
-
Accés remot: Després cal configurar l'accés remot a la màquina per a poder treballar amb comoditat.
-
Directori de proves: Tot seguit, és necessari configurar un directori de proves on es desenvoluparà el mòdul.
-
Permisos: El següent pas consisteix en donar els permisos necessaris perquè Odoo pugui accedir al mòdul.
-
Prerequisits: Cal instal·lar alguns paquets per a que l'IMS funcioni correctament, cal executar les següents comandes: 6.1.
apt install git pip -y
6.2.pip3 install phonenumbers
-
Instal·lació del mòdul: Finalment, només cal situar-se dins el directori
myModules
i fer:
7.1. Clonar el repositori:git clone https://github.com/ElPuig/IMS.git ims
7.2. Accedir a la carpeta:cd ims
7.3. Escollir la base de dades (opcional): modificar el paràmetre-i
de l'scriptinstall.sh
per a escollir la base de dades.
7.4. Instal·lar el mòdul:./install.sh
-
Accedir a Odoo: Ja es pot accedir a Odoo fent servir un navegador web, la IP o el hostname i el port 8069 (http://ims-devel:8069). Les credencials predeterminades són:
Usuari: admin
Password: admin
Es pot instal·lar o actualitzar l'IMS afegint dades de demostració per a fer proves, amb la comanda:
./demo.sh
Es pot actualitzar l'IMS mentre es fan canvis al codi font, per a veure'ls reflectits en la instal·lació i fer-ne proves, amb la comanda:
./update.sh
Només cal fer servir el cercador i copiar l'exemple. Odoo fa servir els que acaben en "-o" quan n'hi ha alguna variant (per exemple: "fa fa-trash-o").
https://fontawesome.com/v4/icons/
Exemple: el següent bloc de codi permet afegir un botó per esborrar un ítem
<a title="Delete" type="delete" href="#" class="we-button.o_delete_btn" role="button">
<i class="fa fa-trash-o" aria-hidden="true" />
</a>
Cal cercar el menú dins de "Settings / Techincal / User Interface / Menu Items", es recomana cercar pel nom del menú. Per exemple: "Employees":
Una forma de saber si és el menú correcte, és revisar els seus submenús:
Un cop trobat el menú, cal accedir al seu ID consultant les metadades:
Es mostrarà l'ID:
Editant el camp "name" amb XML i aportant el ID correcte, es pot modificar el nom:
<record model="ir.ui.menu" id="hr.menu_hr_employee_user">
<field name="name">All</field>
</record>
Afegir el següent codi dins un XML de vista per a sobreescriure'l:
<record model="ir.ui.menu" id="hr.menu_view_employee_category_form">
<field name="active">false</field>
</record>
Consultar els detalls sobre com trobar els IDs a l'apartat "Canviar el nom d'un menú existent (nadiu d'Odoo)".
https://www.cybrosys.com/blog/add-colors-to-tree-view-odoo-13
Pot resultar útil per a debugar amb Python o simplement per a mostrar alertes als usuaris. https://www.cybrosys.com/blog/raising-exceptions-in-the-odoo-15
Si el desplegable depèn d'un camp Many2one, cal indicar l'ordre dins del propi model:
class ims_attendance_schedule(models.Model):
_name = "ims.attendance_schedule"
_description = "Attendance schedule: concretes the weekdays data."
_order = 'attendance_template_id asc, weekday asc, start_time asc'
A continuació es detallen els passos per a editar una vista existent, sense alterar el codi font original:
- Primer cal heretar la vista que es vol modificar:
<record model="ir.ui.view" id="view_contact_form">
<field name="name">ims.contact.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
- A continuació, es pot escollir un element fent servir XPath o directament pel nom d'un atribut del model: 2.1. Exemple a partir del nom d'un atribut del model:
<field name="vat" position="after">
<field name="contact_type"/>
<field name="company_type" attrs="{'invisible': [('contact_type', '!=', 'provider')]}"/>
</field>
2.2. Exemple a partir d'una consulta XPath:
<xpath expr="//page[@name='sales_purchases']" position="attributes">
<attribute name="attrs">{'invisible': [('contact_type', '!=', 'provider')]}</attribute>
</xpath>
2.3. Els detalls sobre els diferents valors de "position" o d'altres elements de modificació, es poden consultar a la documentació oficial: https://www.odoo.com/documentation/16.0/es/developer/reference/backend/views.html#inheritance-specs
Per a simular vistes cal crear un nou model de dades, que no estarà orientat a la lògica de negoci sinó a proveir d'una estructura per a recollir la informació que ha de mostrar una vista, normalment un llistat o "treeview". El primer que cal decidir és si el model de dades ha de ser persistent (llistat d'elements, com per exemple el de "subject") o si pot ser temporal o "transient" (com el llistat d'alumnes matriculat a assignatures concretes dins un grup, que es mostra dins el formulari de "group"):
- Model: funciona com un model de dades normal, amb persistència dins la base de dades, però cal determinar-ne el moment de creació d'aquest model en funció del model de dades original. Com a exemple es pot trobar el model "subject" ja que es permet assignar una assignatura a més d'un nivell d'estudis, però el llistat d'assignatures (que mostra les assignatures agrupades per nivell d'estudis) necessita d'un llistat amb relació 1-1 i no pas 1-N. És per aquest motiu que, quan es crea un nou "subject" o se'n modifiquen els seus estudis, es crea un nou model de vista. Per a fer-ho, s'ha fet de la següent manera:
1.1. Es crea el model de vista (models.Model
) dins el mateix fitxer que el model de dades original (ims_subject_view
iims_subject
es troben dins el fitxersubject.py
.
1.2. Es crea un nou atribut cap a la vista, de tipusOne2many
, amb un mètodecompute
istore=True
. Per exemple:subject_view_ids = fields.One2many(comodel_name="ims.subject_view", inverse_name="subject_id", compute="_compute_subject_views", store=True)
.
1.3. Es crea el mètodecompute
tenint en compte que necessita un@api.depends
que indicarà que, quan es modifiquin altres atributs, caldrà tornar a recalcular aquest mètode. Per exemple:
@api.depends("study_ids")
def _compute_subject_views(self):
for rec in self:
self.env['ims.subject_view'].search([('subject_id', '=', rec.id)]).unlink()
for study in rec.study_ids:
rec.subject_view_ids.create({
"level": rec.level,
"code": rec.code,
"acronym": rec.acronym,
"name": rec.name,
"study_id": study.id,
"subject_id": rec.id,
})
- TransientModel: funciona com un model de dades normal, amb persistència temporal dins la base de dades (s'esborra de tant en tant), però cal determinar-ne el moment de creació d'aquest model en funció del model de dades original. Com a exemple es pot trobar el model "group" perquè es volen mostrar quines matèries cursen els alumnes dins aquest grup (ignorant la resta de matèries que un alumne pugui cursar a altres grups), però el treeview no permet filtrar els resultats d'un camp que és una relació cap a N (existeix l'opció, amb l'atribut
domain
, però l'ignora). És per aquest motiu que, quan s'obre el formulari d'un "group", es crea un nou model de vista. Per a fer-ho, s'ha fet de la següent manera:
2.1. Es crea el model de vista (models.TransientModel
) dins el mateix fitxer que el model de dades original (ims_enrollment_view
iims_group
es troben dins el fitxergroup.py
.
2.2. Es crea un nou atribut cap a la vista, de tipusOne2many
, amb un mètodecompute
. Per exemple:enrollment_view_ids = fields.One2many(string="Enrollment", comodel_name="ims.enrollment_view", inverse_name="group_id", compute="_compute_enrollment_ids")
.
2.3. Es crea el mètodecompute
sense cap@api.depends
, de tal manera que s'activarà només en obrir el formulari. Per exemple:
def _compute_enrollment_ids(self):
for rec in self:
self.env['ims.enrollment_view'].search([('group_id', '=', rec.id)]).unlink()
for student in self.env['ims.enrollment'].read_group(domain=[('group_id', '=', rec.id)], fields=['student_id'], groupby=['student_id']):
sid = student['student_id'][0]
subs = self.env["ims.enrollment"].search([("group_id", "=", rec.id), ('student_id', '=', sid)]).mapped("subject_id")
rec.enrollment_view_ids.create({
"group_id": rec.id,
"student_id": sid,
"subject_ids": subs,
})
Mes informació sobre com obtenir dades agrupades o com crear-ne nous registres de forma manual:
- https://www.odoo.com/documentation/16.0/developer/reference/backend/orm.html?highlight=read_group#search-read
- https://www.cybrosys.com/odoo/odoo-books/odoo-15-development/ch15/grouped-data/
- https://www.odoo.com/fi_FI/forum/apua-1/how-to-insert-value-to-a-one2many-field-in-table-with-create-method-28714
En aquest cas, cal capturar l'event de click sobre un "ListRenderer" i modificar-ne el comportament. Cal fer el següent:
- Crear un nou fitxer javascript dins de
/static/src/js
(el nom del fitxer és indiferent). - Carregar el fitxer dins el
__manifest__.py
:
'assets': {
'web.assets_common': [
'ims/static/src/js/**/*',
],
},
- Afegir codi propi al
ListRendeded
, a continuació s'exposa un exemple per a dos models diferents (es pot veure un exemple complet a/static/src/js/list_renderer_customs.js
):
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { ListRenderer } from "@web/views/list/list_renderer";
import { actionService } from "@web/webclient/actions/action_service";
var ActionService = actionService;
patch(ListRenderer.prototype, "list_renderer_customs", {
setup() {
this._super.apply(this, arguments);
},
onClickCapture(record, ev){
switch(record.resModel){
case "ims.enrollment_view":
ev.preventDefault();
ev.stopPropagation();
var am = ActionService.start(this.env);
am.doAction({
name: 'Open: Students', //to fit with the other regular student's tab
type: 'ir.actions.act_window',
res_model: 'res.partner',
res_id: record.data.student_id[0],
views: [[false, "form"]],
target: 'new', //with 'current' the form opens in fullscreen (not modal).
context: {},
});
break;
case "ims.subject_view":
ev.preventDefault();
ev.stopPropagation();
var am = ActionService.start(this.env);
am.doAction({
//name: 'Open: Students', //to fit with the other regular student's tab
type: 'ir.actions.act_window',
res_model: 'ims.subject',
res_id: record.data.subject_id[0],
views: [[false, "form"]],
target: 'current', //with 'new' the form opens as a modal window.
context: {},
});
break;
}
},
});
En aquest cas, cal fer el següent:
- Crear un nou fitxer javascript dins de
/static/src/css
(el nom del fitxer és indiferent). - Carregar el fitxer dins el
__manifest__.py
:
'assets': {
'web.assets_backend': [
'ims/static/src/css/**/*',
],
},
- Crear un fitxer css nou o afegir-ne codi a algun fitxer existent, dins la ruta que indica el manifest (es pot veure un exemple complet a
/static/src/css/ims_customs.css
):
Cal tenir en compte que Odoo te els seus propis cicles i que el codi JavaScript del client podria afectar-ne l'execució de codi propi. Per aquest motiu, no es poden fer servir les tècniques clàssices (esperar a la càrrega del DOM o del Window) i cal esperar a que l'objecte que es vol capturar o "atacar" estigui carregat.
A continuació s'exposa un exemple que permet que les cel·les d'una llista que contenen radiobuttons, facin un focus automàtic (es pot veure un exemple complet a /static/src/js/list_renderer_customs.js
):
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { ListRenderer } from "@web/views/list/list_renderer";
import { actionService } from "@web/webclient/actions/action_service";
var ActionService = actionService;
patch(ListRenderer.prototype, "list_renderer_customs", {
setup() {
this._super.apply(this, arguments);
owl.onMounted(this.autofocusForRadioCells);
},
autofocusForRadioCells(){
var self = this;
var intervalID = setInterval(function(){
if($(".o_field_cell.o_radio_cell").length != 0){
clearInterval(intervalID);
$(".o_form_button_save").off('click').on("click", self.autofocusForRadioCells);
$(".o_field_cell.o_radio_cell").off('mouseover').on("mouseover", function(){
$(this).mouseover(function() {
$(this).click();
});
$(this).mouseout(function() {
$(".o_horizontal_separator").first().click();
});
});
}
}, 100);
},
});
En aquest cas, cal fer el següent:
- Crear un nou fitxer XML dins de
/static/src/xml
(el nom del fitxer és indiferent). - A continuació es mostra un exemple de bloc XML (es pot veure un exemple complet a
/static/src/xml/subject_list_create_button.xml
):
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="subjectview_create_button.ListView.Buttons" t-inherit="web.ListView.Buttons">
<xpath expr="//*[@class='btn btn-primary o_list_button_add']" position="attributes">
<attribute name="style">display: none</attribute>
</xpath>
<xpath expr="//*[@class='btn btn-primary o_list_button_add']" position="after">
<button type="button" class="btn btn-primary" t-on-click="onClick">
<!-- TODO: localize -->
<!-- TODO: permissions (this is a custom button, so the create permission should be used in order to display it or not...) -->
NEW
</button>
</xpath>
</t>
</templates>
- A continuació es mostra un exemple de bloc JavaScript (es pot veure un exemple complet a
/static/src/js/subject_list_create_button.xml
):
/** @odoo-module */
import { ListController } from "@web/views/list/list_controller";
import { registry } from '@web/core/registry';
import { listView } from '@web/views/list/list_view';
export class SubjectViewListController extends ListController {
setup() {
super.setup();
}
onClick() {
this.actionService.doAction({
//name: 'Open: Students', //to fit with the other regular student's tab
type: 'ir.actions.act_window',
res_model: 'ims.subject',
res_id: false,
views: [[false, "form"]],
target: 'current', //with 'new' the form opens as a modal window.
context: {},
});
}
}
registry.category("views").add("subjectview_create_button", {
...listView,
Controller: SubjectViewListController,
buttonTemplate: "subjectview_create_button.ListView.Buttons",
});
- Carregar els fitxers dins el
__manifest__.py
:
'assets': {
'web.assets_backend': [
'ims/static/src/xml/**/*',
],
'web.assets_common': [
'ims/static/src/js/**/*',
],
},