Skip to content

2.4. Desenvolupar l'IMS

Fher edited this page Dec 4, 2024 · 21 revisions

Descàrrega i configuració

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:

  1. Plataforma de desenvolupament: Cal configurar una plataforma de desenvolupament per a poder instal·lar Odoo.

  2. Preparació de l'entorn: Cal seguir la guia de preparació de l'entorn per a deixar el sistema preparat i poder treballar amb Odoo.

  3. Accés remot: Després cal configurar l'accés remot a la màquina per a poder treballar amb comoditat.

  4. Directori de proves: Tot seguit, és necessari configurar un directori de proves on es desenvoluparà el mòdul.

  5. Permisos: El següent pas consisteix en donar els permisos necessaris perquè Odoo pugui accedir al mòdul.

  6. 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

  7. 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'script install.sh per a escollir la base de dades.
    7.4. Instal·lar el mòdul: ./install.sh

  8. 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

Instal·lar les dades de demo:

Es pot instal·lar o actualitzar l'IMS afegint dades de demostració per a fer proves, amb la comanda:
./demo.sh

Reinstal·lar l'IMS durant el desenvolupament:

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

Icones que fa servir Odoo

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>

Canviar el nom d'un menú existent (nadiu d'Odoo)

Cal cercar el menú dins de "Settings / Techincal / User Interface / Menu Items", es recomana cercar pel nom del menú. Per exemple: "Employees":
image

Una forma de saber si és el menú correcte, és revisar els seus submenús:
image

Un cop trobat el menú, cal accedir al seu ID consultant les metadades:
image

Es mostrarà l'ID:
image

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>

Amagar un menú existent (nadiu d'Odoo)

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)".

Pintar les cel·les dels llistats

https://www.cybrosys.com/blog/add-colors-to-tree-view-odoo-13

Mostrar alertes als usuaris

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

Endreçar un desplegable dins un formulari

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'

Editar les vistes predeterminades

A continuació es detallen els passos per a editar una vista existent, sense alterar el codi font original:

  1. 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"> 
  1. 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

Models orientats a les vistes (com simular vistes SQL)

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"):

  1. 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 i ims_subject es troben dins el fitxer subject.py.
    1.2. Es crea un nou atribut cap a la vista, de tipus One2many, amb un mètode compute i store=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ètode compute 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,
         })	 
  1. 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 i ims_group es troben dins el fitxer group.py.
    2.2. Es crea un nou atribut cap a la vista, de tipus One2many, amb un mètode compute. 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ètode compute 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:

Obrir un model de dades diferent en fer click sobre un element

En aquest cas, cal capturar l'event de click sobre un "ListRenderer" i modificar-ne el comportament. Cal fer el següent:

  1. Crear un nou fitxer javascript dins de /static/src/js (el nom del fitxer és indiferent).
  2. Carregar el fitxer dins el __manifest__.py:
'assets': {         
   'web.assets_common': [
      'ims/static/src/js/**/*',            
   ],
},
  1. 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;   
      }    
   },       
});

CSS personalitzat

En aquest cas, cal fer el següent:

  1. Crear un nou fitxer javascript dins de /static/src/css (el nom del fitxer és indiferent).
  2. Carregar el fitxer dins el __manifest__.py:
'assets': {         
   'web.assets_backend': [
      'ims/static/src/css/**/*',            
   ],
},
  1. 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):

JavaScript personalitzat

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);                               
   },       
});

Canviar el model que es crea des del botó "NEW" de la capçalera d'un llistat:

En aquest cas, cal fer el següent:

  1. Crear un nou fitxer XML dins de /static/src/xml (el nom del fitxer és indiferent).
  2. 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>
  1. 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",
});
  1. Carregar els fitxers dins el __manifest__.py:
'assets': {         
   'web.assets_backend': [
      'ims/static/src/xml/**/*',            
   ],
   'web.assets_common': [
      'ims/static/src/js/**/*',            
   ],
},