Skip to content

2.3. Creació de mòduls per a Odoo

Fher edited this page Dec 5, 2024 · 34 revisions

Preparació de l'entorn

Aquest exemple parteix del supòsit que es disposa d'una màquina virtual o contenidor amb una instància d'Odoo v16 instal·lada i operativa.

Connexió remota amb Visual Studio Code

Afegir un host virtual

Per a facilitar les tasques de desenvolupament es recomana configurar una connexió remota que simplifiqui el procés, per aquest motiu el primer que es farà serà generar una nova entrada al fitxer de hosts /etc/hosts:

image

Generar les claus privada/pública

Després es procedirà a generar un parell de claus pública/provada i instal·lar la pública a la màquina remota, de tal manera que es podran fer connexions SSH sense haver d'introduir la contrasenya (procés més aviat farragós i repetitiu quan es treballa amb Visual Studio Code):

ssh-keygen -t rsa
ssh-copy-id -i $HOME/.ssh/id_rsa.pub <username>@ims-devel

Configuració de Visual Studio Code

Primerament, caldrà instal·lar el plugin que permet connectar-se remotament a les màquines:

image

Un cop instal·lat, s'afegirà el host al llistat del Visual Studio Code:

  1. Prémer la combinació de tecles Ctrl+Shift+P.
  2. Escriure host i escollir l'opció Add new SSH Host:
    image
  3. Escriure la comanda que permet una connexió via terminal:
    ssh <username>@ims-devel
  4. Guardar la configuració per a l'usuari actual:
    image

Connexió a la màquina remota

Finalment cal connectar el Visual Studio Code a la màquina remota:

  1. Prémer la combinació de tecles Ctrl+Shift+P.
  2. Escriure host i escollir l'opció Connect to Host:
    image
  3. Escollir ims-devel:
    image
  4. Això obrirà una nova finestra del Visual Studio Code, cal acceptar els missatges que puguin sortir; la primera connexió pot trigar una mica.
  5. Un cop acaba el procés, cal obrir un directori amb el Visual Studio Code:
    image
  6. Es recomana obrir /home/<username> donat que és el directori en el qual es tenen permisos i, per tant, on es configurarà l'Odoo per anar a buscar el mòdul que s'està desenvolupant:
    image
  7. Finalment, després d'acceptar els missatges que hagin pogut sortir, cal obrir un terminal (remot):
    image

A partir d'aquest punt, tot el desenvolupament es pot fer sense haver de sortir del Visual Studio Code, el qual pot resultar molt còmode.

Configuració d'Odoo

Cal indicar-li a Odoo en quin directori es desenvoluparan les mòduls propis, això resulta útil per a evitar problemes amb els permisos i per a no barrejar els mòduls de producció amb els de desenvolupament. Cal:

  1. Crear la carpeta on es vol treballar amb mkdir myModules, /root/myModules (si es fa servir l'usuari root) o /home/<username>/myModules (si es fa servir un altre usuari).
  2. Editar el fitxer de configuració d'Odoo amb sudo nano /etc/odoo/odoo.conf i afegir la ruta a myModules:
[options]
db_host = False
db_port = False
db_user = odoo
db_password = False
default_productivity_apps = True
admin_passwd = $pbkdf2-sha512$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
addons_path = /usr/lib/python3/dist-packages/odoo/addons, <path>/myModules
  1. Un cop editat el fitxer de configuració d'Odoo, cal reiniciar el servei amb sudo service odoo restart.

Permisos

Cal donar permisos a l'usuari odoo perque pugui accedir a la carpeta de desenvolupament del mòdul.

Per a l'usuari root

Si es fa servir una màquina amb l'usuari root (per exemple, l'entorn de desenvolupament sota IsardVDI que fa servir contenidors LXC/LXD), caldrà donar els següents permisos:

sudo adduser root odoo

sudo chown root:odoo /root
sudo chmod 750 /root

cd /root
sudo chown -R root:odoo myModules
sudo chmod g+s myModules

Per a un altre usuari

Si es fa servir una màquina amb un usuari que no sigui root (per exemple, l'entorn de desenvolupament sota IsardVDI que fa servir l'usuari usuario), caldrà donar els següents permisos:

sudo adduser usuario odoo

cd /home
sudo chown usuario:odoo usuario
sudo chmod 750 usuario

cd usuario
sudo chown -R usuario:odoo myModules
sudo chmod g+s myModules	

Creació de l'esquelet

Odoo incorpora una eina que prepara l'esquelet d'un mòdul perquè sigui fàcil de crear-ne un des de zero, basta fer servir la comanda odoo scaffold <modul> <ruta> on "modul" és el nom del mòdul que es vol crear i "ruta" el path on es vol crear el mòduo. Per exemple, es podria crear un mòdul de proves amb la comanda `odoo scaffold myTestModule $HOME/myModules/".

Un cop executada la comanda, es pot comprovar com ha creat una carpeta (normalitzant el nom) i a dins es troben tots els fitxers necessaris per a treballar: image

A partir d'aquest punt, es pot fer servir la connexió remota de Visual Studio Code per obrir la carpeta d'aquest mòdul i facilitar-ne la modificació:

image

Estructura d'un mòdul

L'estructura completa d'un mòdul segueix el patró de "Model / Vista / Controlador" i, un cop generat l'esquelet, és la següent: image

Manifest

El document __manifest__.py inclou informació sobre el mòdul entre d'altres:

# -*- coding: utf-8 -*-
{
    'name': "myTestModule",

    'summary': """
        Short (1 phrase/line) summary of the module's purpose, used as
        subtitle on modules listing or apps.openerp.com""",

    'description': """
        Long description of module's purpose
    """,

    'author': "My Company",
    'website': "https://www.yourcompany.com",

    # Categories can be used to filter modules in modules listing
    # Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml
    # for the full list
    'category': 'Uncategorized',
    'version': '0.1',

    # any module necessary for this one to work correctly
    'depends': ['base'],

    # always loaded
    'data': [
        # 'security/ir.model.access.csv',
        'views/views.xml',
        'views/templates.xml',
    ],
    # only loaded in demonstration mode
    'demo': [
        'demo/demo.xml',
    ],
}

Respecte del contingut, caldria destacar el següent:
* name: És el nom del mòdul, es mostrarà al llistat d'aplicacions d'Odoo a l'hora d'instal·lar-lo.
* summary: És un resum del que fa el mòdul, es mostrarà al llistat d'aplicacions d'Odoo a l'hora d'instal·lar-lo.
* description: És la versió detallada del resum anterior.
* author: Quí és l'autor del mòdul, es mostrarà al llistat d'aplicacions d'Odoo a l'hora d'instal·lar-lo.
* website: Quina és la pàgina oficial del mòdul, es mostrarà al llistat d'aplicacions d'Odoo a l'hora d'instal·lar-lo.
* category: En quina categoria encaixa el mòdul, es fa servir per a cercar mòduls.
* version: Quina és la versió actual del mòdul.
* depends: Permet establir dependències entre el mòdul actual i altres mòduls.
* data: Les dades que carregarà el mòdul en el moment de la instal·lació.
* demo: Les dades de demostració que es carregaràn i s'ha activat l'opció en generar la BBDD d'Odoo.

Controllers

Els controladors permeten fer gestions d'una forma similar a la que fan els models, però cal tenir en compte que podrien activar-se en un moment en el qual encara no es disposa dels prerequisits necessaris (com per exemple, la connexió a la base de dades). Usualment, un controlador es fa servir per a definir les accions que cal executar en rebre una petició via HTTP, com per exemple servir una web completa, una porció d'aquesta (un bloc HTML que s'injecta dinàmicament via AJAX) o realitzar una acció sobre la base de dades.

Exemple del controlador comentat que genera l'esquelet, és a dir, no es fa servir cap controlador propi de forma predeterminada: image

Font: Web Controllers

Demo

Es tracta d'un fitxer XML que serveix per a definir les dades de demostració que cal inserir a la base de dades quan s'instal·la el mòdul que s'està desenvolupant. Aquestes dades només s'insereixen si, durant la instal·lació de la base de dades d'Odoo, s'ha activat l'opció de "dades de demostració":

image

Com es pot observar, el fitxer de demostració que ha generat l'esquelet té comentat el codi que permet fer les insercions: image

Per cada registre de tipus "record" cal dir:

  • id: quin és el ID de l'objecte que s'ha de crear.
  • model: quin és el model de dades que es farà servir, en format <nom_del_modul>.<nom_del_model>
  • tants camps "field" com atributs tingui el registre.
    • name="name": el nom de l'atribut del registre com a valor del node.
    • name="value": el valor de l'atribut del registre com a valor del node.

Font: Data Files

Models

De forma predeterminada, l'esquelet es genera amb un únic fitxer de models: image

Tanmateix, es recomana que cada model de dades ocupi un fitxer diferent donat que la lògica i complexitat inherents a un model en concret pot esdevenir en un codi prou extens que dificulti la comprensió si es fa servir un únic fitxer per a diversos models.

Per exemple, per a generar un model de dades anomenat "UF" i un altre anomenat "MP" caldria crear dos fitxers uf.py i mp.py (es poden generar fent una còpia del fitxer models.py). Igualment, caldrà establir un nom per la classe (que ha de ser únic) així com el contingut del model en si:

mp.py:

# -*- coding: utf-8 -*-

from odoo import models, fields, api

class my_test_module_mp(models.Model):
	_name = 'my_test_module.mp'
	_description = 'Model de dades per a un Mòdul Professional'

	code = fields.Char('Codi')
	name = fields.Char('Nom')
	teacher = fields.Char('Professor')
	startDate = fields.Date('Data inici')
	started = fields.Boolean('Començat')
	students = fields.Integer('Estudiants')
	image = fields.Binary('Image')
	notes = fields.Text('Anotacions')

	ufs = fields.One2many(comodel_name="my_test_module.uf", inverse_name="mp", string="Unitats Formatives")

uf.py:

# -*- coding: utf-8 -*-

from odoo import models, fields, api

class my_test_module_uf(models.Model):
	_name = 'gestio_academica.uf'
        _description = 'Model de dades per a una Unitat Formativa'

	name = fields.Char('Nom')
	code = fields.Char('Codi')
	teacher = fields.Char('Professor')
	startDate = fields.Date('Data inici')
	endDate = fields.Date('Data final')

	mp = fields.Many2one(comodel_name="my_test_module.mp", string="Modul Professional")

Respecte de les propietats:

  • name: és el nom que tindrà el model, en forma de <nom_del_modul>.<nom_del_model> i, per tant, Odoo crearà una taula nova dins la BBDD amb el nom de <nom_del_modul><nom_del_model>.
  • _description: és la descripció del model de dades, útil per als desenvolupadors que treballen amb el backoffice d'Odoo.

La resta de propietats del model seran les dades que es volen guardar. Més informació aquí.

Cal tenir en compte, però, que els nous fitxers de models de dades no seran visibles per Odoo a menys que s'incloguin dins del fitxer d'importació de models: init.py:

# -*- coding: utf-8 -*-

from . import mp
from . import uf

Font: ORM API
Font: Model fields

Relacions 1-N entre models

Els models de dades es poden relacionar uns amb altres, en un tipus de relació anomenat "1 a N" o "OneToMany". Per a Odoo, cal que els dos models de dades es relacionen entre si de forma bidireccional per a funcionar correctament, i un bon exemple són els modles uf i mp que s'han mostrat anteriorment:

mp.py Aquest model inclou una propietat anomenada "ufs", com que un MP pot tenir moltes UFs, cal fer la relació cap al les seves UFs en forma de One2Many:
ufs = fields.One2many(comodel_name="my_test_module.uf", inverse_name="mp", string="Unitats Formatives")

uf.py Aquest model inclou una propietat anomenada "mp", com que una UF pertany només a un MP, cal fer la relació cap a la UF en forma de ManyToOne:
mp = fields.Many2one(comodel_name="my_test_module.mp", string="Modul Professional")

Font: Relational Fields

Relacions N-N entre models

Similar a l'exemple anterior, es poden relacionar models entre si de la forma "N-N" o "Many2Many"; per exemple: un alumne té molts professors i un professor té molts alumnes.

No es disposa d'un exemple funcional d'una relació "Many2Many" però el funcionament hauria de ser similar al de les relaciones "Many2One".

Font: Relational Fields

Camps calculats

En ocasions resulta interessant calcular de forma automàtica els camps, però el mecanisme funciona diferent si es vol fer visible per l'usuari o no. Cal tenir sempre present que, quan s'obre un formulari per a crear un nou model, aquestes dades no es troben a la base de dades i, per tant, el que es pot fer de cara a l'usuari queda limitat (o requereix de dades addicionals).

Valors predeterminats

Es poden establir valors predeterminats que no requereixen de càlculs, fent servir el camp apropiat quan es defineix l'atribut dins el model. Per exemple, amb level = fields.Integer(string="Level", default=1) el valor per a level que veurà l'usuari en obrir un formulari nou serà 1.

També es poden realitzar càlculs per a mostrar valors, però cal tenir en compte que un formulari nou no disposa de dades guardades a la BBDD tot i que podria fer-ne servir d'altres que si que es trobin prèviament emmagatzemades. A continuació un exemple: _display_warning = fields.Boolean(default=lambda self: self._default_display_warning(), store=False). El mètode _default_display_warning retorna True o False en funció dels valors d'altres dades que es troben guardades a la BBDD.

També es pot passar un valor predeterminat d'un formulari cap a un altre, com quan es vol crear una assignatura que ha de ser filla d'una altra, fent servir l'atribut context del field. Per exemple <field name="subject_ids" widget="one2many" context="{'default_subject_id': id, 'default_study_ids': study_ids, 'default_code': code}">, tots quests valors s'estableixen de forma automàtica al formulari i l'usuari els veu establerts (i els pot canviar si vol).

Modificar valors automàticament (onchange)

Si es vol que l'usuari vegi canvis instantanis mentre treballa, es pot fer servir una capçalera onchange a un mètode, que l'invocarà en funció de l'atribut modificat. Per exemple:

 @api.onchange("subject_id")
    def _onchange_subject_id(self):
        for rec in self:
            rec.study_ids = rec.subject_id.study_ids 

Cada cop que l'usuari modifica el valor del desplegable assignat a subject_id, veu com es carreguen automàticament els valors de study_ids.

Important: aquests canvis només apliquen quan l'usuari modifica el formulari, no s'activa quan es carreguen dades de demo o via CSV.

Valors calculats (compute i depends)

Aquests càlculs es realitzen cada cop que hi ha un canvi a la base de dades i, per tant, si que s'activen en carregar dades de demo o un document CSV, així com quan es guarden les dades d'un formulari, però és transparent a l'usuari.

Per una banda, cal indicar que un camp es calcula mitjançant el paràmetre _compute de l'atribut, per exemple: level = fields.Integer(string="Level", default=1, compute="_compute_level", store=True). A continuació cal definir el mètode _compute_level:

@api.depends("subject_id")
    def _compute_level(self):	        
        for rec in self:
            if rec.subject_id.id != False: rec.level = rec.subject_id.level + 1 

Cada cop que l'atribut subject_id es vegi alterat a la BBDD, s'invocarà aquest mètode i actualitzarà el valor de l'atribut level (tal com defineix el mètode que ha de fer). En aquest cas es recomana que el camp level sigui ocult, perquè l'usuari no veurà el canvi fins que no es guardin.

Security

Dins el fitxer csv que hi ha dins la carpeta security es poden definir els permisos predeterminats que tindrà el mòdul:

image

Cal tenir en compte que els permisos predeterminats són tan estrictes que només l'usuari "root" d'Odoo serà capaç de veure'l i fer-lo servir. El següent exemple mostra un permís que fa visible el mòdul als usuaris un cop instal·lat sense necessitat de ser "root":

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_my_test_module_my_test_module,my_test_module.my_test_module,model_my_test_module_my_test_module,,1,0,0,0

Views

L'esquelet que s'ha generat crea totes les vistes necessàries dins un únic document anomenat views.xml:
image

Aquesta forma de treballar fa que la complexitat dels fitxers augmenti i gestionar-los es converteixi en quelcom tediós; per aquest motiu, es recomana generar fitxers separats per a cada vista que es vulgui crear i afegir aquestes vistes dins el fitxer __manifest__.py dins la secció data. Per exemple:

'data': [
        'security/ir.model.access.csv',
        'views/menu.xml',
	'views/uf_list.xml',
	'views/uf_form.xml',
	'views/mp_list.xml',
	'views/mp_form.xml',
	'reports/mp.xml',
    ],

Menu

Perquè es pugui fer servir un mòdul cal generar un menú que inclogui les seves opcions disponibles. El següent seria un exemple:

menu.xml

<odoo>
  <data>
	<act_window id="action_my_test_module_uf_tree" name="Gestio Academica" res_model="my_test_module.uf" view_mode="tree,form"  />    
	<act_window id="action_my_test_module_mp_tree" name="Gestio Academica" res_model="my_test_module.mp" view_mode="tree,form"  />

	<menuitem name="Gestio Academica" id="my_test_module.menu_root"/>
	<menuitem name="Gestio de MPs" id="my_test_module.mps" parent="my_test_module.menu_root" action="action_my_test_module_mp_tree" />
	<menuitem name="Gestio de UFs" id="my_test_module.ufs" parent="my_test_module.menu_root" action="action_my_test_module_uf_tree" />   
  </data>
</odoo>

Per una banda es disposen de les act_window i per l'altra dels menuitem:

  • menuitem: Una opció del menú ha d'incloure altres opcions o bé apuntar cap a una acció, que serà el que es farà en clicar.
  • act_window: Les dues accions que s'han posat d'exemple fan el mateix per a mòduls diferents, és a dir, mostres el llistat de registres (ufs o mps) que s'han creat (per la opció tree, la opció form no arriba a entrar en joc).

Llistat

Per les llistes, cal definir quin model de dades cal fer servir i quines columnes (propietats del model) es volen mostrar. Exemples: mp_list.xml

<odoo>
  <data>
      <record model="ir.ui.view" id="my_test_module.mp_list">
	  <field name="name">my_test_module mp list</field>
	  <field name="model">my_test_module.mp</field>
	  <field name="arch" type="xml">
		<tree>
		     <field name="code"/>
		     <field name="name"/>
		     <field name="teacher"/>
		     <field name="startDate"/>
		</tree>
	  </field>
      </record>   
  </data>
</odoo>

uf_list.xml

<odoo>
  <data>
      <record model="ir.ui.view" id="my_test_module.uf_list">
          <field name="name">my_test_module uf list</field>
          <field name="model">my_test_module.uf</field>
          <field name="arch" type="xml">
	      <tree>
		 <field name="code"/>
		 <field name="name"/>
	      </tree>
          </field>
      </record>   
  </data>
</odoo>

Formulari

Pels formularis, cal definir quin model de dades cal fer servir i quines propietats del model es volen mostrar i com (es pot fer servir HTML CSS, etc). Exemple: mp_form.xml

<odoo>
  <data>
     <record id="view_my_test_module_mp_form" model="ir.ui.view">
        <field name="name">MP form</field>
	<field name="model">my_test_module.mp</field>
	<field name="type">form</field>
	<field name="arch" type="xml">				
	   <form string="MP">
	      <style>
		.preview {
		   float: left;
		}
						
		.preview img {
		   width: 100px; 							
		} 
						
		h1 {
		   float: left;
		   width: 84%;
		}
	      </style>

	      <sheet>
	         <field name="image" widget="image" class="preview" />
		    <h1>
			<field name="name"/>
		    </h1>

		    <group col="2">
			<group string="Dades acadèmiques">							
				<field name="code"/>
				<field name="teacher"/>
			</group>
		        <group string="Dates i alumnat">
				<field name="startDate"/>
				<field name="started"/>
				<field name="students"/>
			</group>
		    </group>
						
		    <notebook>
			<page string="Unitats Formatives">
				<field name="ufs" widget="one2many_list" context="{'show_attribute': False}">
					<tree>
						<field name="code"/>
						<field name="name"/>
					</tree>
				</field>
			</page>
							
			<page string="Anotacions">
				<field name="notes" placeholder="Espai reservat per a anotacions..."/>
			</page>
		    </notebook>
	      </sheet>	
	   </form>
        </field>			
     </record>
  </data>
</odoo>

L'exemple amb el formulari del model de dades UF seria molt similar a l'anterior.

Informes

Tot i que l'esquelet no ho fa de forma automàtica, es recomana crear un nou directori reports per a emmagatzemar a dins els informes. A continuació es mostra un informe que, donat un MP, mostra la seva informació junt amb la de les seves UFs.

mp_report.xml

<odoo>
   <data>
      <report
	id="report_mp"
	model="my_test_module.mp"
	string="Report de MP"
	name="my_test_module.report_mp_view"
	file="my_test_module.report_mp"
	menu="True"
	report_type="qweb-pdf" 
     />
	   
      <template id="report_mp_view">
         <t t-call="report.html_container">
	    <t t-foreach="docs" t-as="doc">				
	       <t t-call="report.external_layout">
		  <div class="page">
		     <span t-field="doc.image" style="float:right" t-field-options="{&quot;widget&quot;: &quot;image&quot;, &quot;class&quot;: &quot;img-rounded&quot;}"/>
		     <h1 t-field="doc.code"/>
		     <h2 t-field="doc.name"/>

		     <p>Professor: <span t-field="doc.teacher"/></p>
		     <p>Data d'inici: <span t-field="doc.startDate"/></p>
		     <p>Començat: <span t-field="doc.started"/></p>
		     <p>Alumnes matriculats: <span t-field="doc.students"/></p>
		     <br/>
		     
		     <h2>Unitats Formatives</h2>
		     <t t-foreach="doc.ufs" t-as="uf">		
			<h3 t-field="uf.code"/>
			<h4 t-field="uf.name"/>
			
		        <p>Professor: <span t-field="uf.teacher"/></p>
			<p>Data d'inici: <span t-field="uf.startDate"/></p>
			<p>Data de final: <span t-field="uf.endDate"/></p>
		     </t>
		  </div>
	       </t>
	    </t>
         </t>
      </template>
   </data>
</odoo>

Templates

Les plantilles permeten generar blocs de vistes (formulatris, informes, llistats, etc) que poden invocar-se des les mateixes vistes per tal de reaprofitar el codi.

Font: QWeb Templates

Documentació i material de referència