5. Desarrollando con Odoo (3). Relaciones entre modelos , atributos de campos y campos calculados. Menu sequence. No confundir date con datetime

 1. Añadir información a los campos del modelo.

Al campo name le hemos añadido estos atributos:

  1. string : Etiqueta del campo
  2. readonly: Si es de solo lectura (no modificable)
  3. required: Campo obligatorio
  4. help: Información "tool tip"
  5. default: Valor por omisión

class task(models.Model):
    _name = 'ximoapp01.task'
    _description = 'ximoapp01.task'

    name = fields.Char(string= "Nombre", readonly=False, required=True, help="Nombre de la Tarea", default="Crear índices" )

Hay diferentes tipos de campos:

  1. fields.Char() : tira de caràcteres
  2. fields.Text(): Texto más grande
  3. fields.Date(): Fecha
  4. fields.Boolean(): Booleano
  5. fields.Integer(): Entero
  6. fields:Binary(): Un fichero, por ejemplo un documento
  7. fields.Image(): Imagen (subtipo de Binary)
  8. fields.Monetary(): Moneda
  9. fields.Html(): HTML
  10. fields.Selection(): Conjunto de valores posibles

2. Campos calculados

Veamos dos ejemplos, el primero no se guarda en la BD y el segundo si.

from odoo import models, fields, api
import datetime

class task(models.Model):
    _name = 'ximoapp01.task'
    _description = 'ximoapp01.task'

    name = fields.Char(string= "Nombre", readonly=False, required=True, help="Nombre de la Tarea" )
    description = fields.Text()
    creation_date = fields.Datetime()
    start_date = fields.Datetime()
    duration= fields.Integer()
    end_date =fields.Datetime(compute="_get_end_date", store=True )
    is_paused = fields.Boolean(default=False)
    code = fields.Char(compute="_get_code")

    def _get_code(self):
        for record in self:
            record.code=record.name + str(record.id)

    @api.depends('start_date', 'duration')
    def _get_end_date(self):
        for record in self:
            if isinstance(record.start_date, datetime.datetime): and record.duration > 0 :
record.end_date = record.start_date + datetime.timedelta(days=record.duration) else: record.end_date=record.start_date

El primer ejemplo crea un campo "code" que toma el valor del campo "name" y le añadimos el valo del campo "id" convertido en tira. No se guarda en la BD pues si se guardara se indicaría con el atributo store = True
En este primer caso se ha definido una función "_get_code" dentro del modelo que genera el valor calculado del campo

En el segundo ejemplo, el campo calculado (end_date) se guarda en la BD (store = True) y además depende de dos campos (start_date  y duration), por eso la función que calcula su valor (_get_end_date), tiene el decorador @api.depends('start_date','duration')

Hay que destacar en la función "_det_end_date" 2 aspectos:
  1. Si el campo de tipo datetime no tiene valor, entonces toma el valor "False" y hay que comprobr que el valor que tiene es tipo datetime.datetime.
  2. Para poder añadir dias a una fecha se debe utilizar datetime.timedelta(days=dias_a_sumar)
OJO: Si creamos los campos como fields.Date, estos seran del tipo datetime.date, y si los creamos como fileds.Datetime estos serán del tipo datetime.datetime

3. Relación One2Many y Many2One

La primera relación se indica de un padre a sus hijos y la segunda de los hijos al padre. Vamos a seguir las indicaciones de Inma Gijón y crearemos un nuevo modelo llamado "Sprint" que representa una macrotarea que engloba varias tareas  "Task".

Cada vez que creemos un modelo, hay que crear 5 elementos, 4 dentro del fichero views.xml y uno dentro de ir.model.access.csv :
  1. Crear las vistas tipo "tree" (list)(1) y opcionalmente la de tipo "form"(2), así como la acción(3) y el menú (4) dentro de views/views.xml
  2. Añadir una línea de permisos (5) al nuevo modelo dentro de security/ir.model.access.csv
Veamos el fichero models.py que contiene a Task y Sprint

from odoo import models, fields, api
import datetime

class task(models.Model):
    _name = 'ximoapp01.task'
    _description = 'ximoapp01.task'

    name = fields.Char(string= "Nombre", readonly=False, required=True, help="Nombre de la Tarea" )
    description = fields.Text()
    creation_date = fields.Datetime()
    start_date = fields.Datetime()
duration= fields.Integer() end_date =fields.Datetime(compute="_get_end_date", store=True )
is_paused = fields.Boolean(default=False) code = fields.Char(compute="_get_code") sprint = fields.Many2one("ximoapp01.sprint", ondelete="set null", help="Macro tarea padre Sprint"))

    def _get_code(self):
        for record in self:
            record.code=record.name + str(record.id)

    @api.depends('start_date', 'duration')
    def _get_end_date(self):
        for record in self:
            #if isinstance(record.start_date, datetime.datetime) and record.duration > 0 :
            #    record.end_date = record.start_date + datetime.timedelta(days=record.duration)
            #if isinstance(record.start_date, datetime.datetime) and record.duration > 0:
            if  not record.start_date==False:    
                record.end_date = record.start_date + datetime.timedelta(days=record.duration)
            else:
                record.end_date=record.start_date    


class sprint(models.Model):
    _name = 'ximoapp01.sprint'
    _description = 'ximoapp01.sprint'

    name = fields.Char(string= "Nombre", readonly=False, required=True, help="Nombre de la macro tarea Sprint" )
    description = fields.Text()
    creation_date = fields.Datetime()
start_date
= fields.Datetime()
duration= fields.Integer() end_date =fields.Datetime(compute="_get_end_date", store=True )
#tasks = fields.One2many("ximoapp01.task", 'sprint') # >Le pasamos modelo y campo de la tabla hija que hace referencia al padre tasks = fields.One2many(string="Tareas", comodel_name="ximoapp01.task", inverse_name='sprint') # >Le pasamos modelo y campo de la tabla hija que hace referencia al padre
        
    @api.depends('start_date', 'duration')
    def _get_end_date(self):
        for record in self:
            #if isinstance(record.start_date, datetime.datetime) and record.duration > 0 :
            #    record.end_date = record.start_date + datetime.timedelta(days=record.duration)
            #if isinstance(record.start_date, datetime.datetime) and record.duration > 0:
            if  not record.start_date==False:    
                record.end_date = record.start_date + datetime.timedelta(days=record.duration)
            else:
                record.end_date=record.start_date    

En la relación Many2one solo le indicamos el modelo padre mientras que en la relación One2many hay que indicarle el modelo hijo y el campo del modelo hijo que hace referencia al padre.

Como vimos, en el fichero views.xml hay que añadirle los formularios, acciones y menús

<odoo>
  <data>
    <!-- view T A S K -->
    <!-- explicit list view definition -->
    <record model="ir.ui.view" id="ximoapp01.task_list">
      <field name="name">ximoapp01 task list</field>
      <field name="model">ximoapp01.task</field>
      <field name="arch" type="xml">
        <tree>
          <field name="name"/>
          <field name="description"/>
          <field name="creation_date"/>
          <field name="start_date"/>
          <field name="duration"/>
          <field name="end_date"/>
          <field name="is_paused"/>
          <field name="code"/>
          <field name="sprint"/>
        </tree>
      </field>
    </record>

    <!-- explicit form view definition -->
    <record model="ir.ui.view" id="ximoapp01.task_form">
      <field name="name">ximoapp01 task form</field>
      <field name="model">ximoapp01.task</field>
      <field name="arch" type="xml">
        <form>
          <group>
            <field name="name"/>
            <field name="description"/>
            <field name="creation_date"/>
            <field name="start_date"/>
            <field name="duration"/>
            <field name="end_date"/>
            <field name="is_paused"/>
            <field name="code"/>
            <field name="sprint"/>
          </group>
        </form>  
      </field>
    </record>

<!-- view S P R I N T -->
<!-- explicit list view definition -->
    <record model="ir.ui.view" id="ximoapp01.sprint_list">
      <field name="name">ximoapp01 sprint list</field>
      <field name="model">ximoapp01.sprint</field>
      <field name="arch" type="xml">
        <tree>
          <field name="name"/>
          <field name="description"/>
          <field name="creation_date"/>
          <field name="start_date"/>
          <field name="duration"/>
          <field name="end_date"/>
        </tree>
      </field>
    </record>

    <!-- explicit form view definition -->
    <record model="ir.ui.view" id="ximoapp01.sprint_form">
      <field name="name">ximoapp01 sprint form</field>
      <field name="model">ximoapp01.sprint</field>
      <field name="arch" type="xml">
        <form>
          <group>
            <field name="name"/>
            <field name="description"/>
            <field name="creation_date"/>
            <field name="start_date"/>
            <field name="duration"/>
            <field name="end_date"/>
            <field name="tasks"/>
          </group>
        </form>  
      </field>
    </record>

<!-- actions opening views on models -->
    <!-- action T A S K -->
<record model="ir.actions.act_window" id="ximoapp01.action_task_window"> <field name="name">ximoapp01 task window</field> <field name="res_model">ximoapp01.task</field> <field name="view_mode">tree,form</field> </record>
    <!-- action S P R I N T -->
<record model="ir.actions.act_window" id="ximoapp01.action_sprint_window"> <field name="name">ximoapp01 sprint window</field> <field name="res_model">ximoapp01.sprint</field> <field name="view_mode">tree,form</field> </record> <!-- server action to the one above --> <!-- <record model="ir.actions.server" id="ximoapp01.action_server"> <field name="name">ximoapp01 server</field> <field name="model_id" ref="model_ximoapp01_ximoapp01"/> <field name="state">code</field> <field name="code"> action = { "type": "ir.actions.act_window", "view_mode": "tree,form", "res_model": model._name, } </field> </record> --> <!-- Top menu item --> <menuitem name="ximoapp01" id="ximoapp01.menu_root"/> <!-- menu categories --> <menuitem name="Tasks &amp; Sprints" id="ximoapp01.menu_1" parent="ximoapp01.menu_root"/> <!-- <menuitem name="Menu_2" id="ximoapp01.menu_2" parent="ximoapp01.menu_root"/> --> <!-- actions -->

    <!-- 1. T A S K S -->
<menuitem name="Tasks" id="ximoapp01.menu_1_task_list" parent="ximoapp01.menu_1" action="ximoapp01.action_task_window"/>

    <!-- 2. S P R I N T S -->    
    <menuitem name="Sprints" id="ximoapp01.menu_1_sprint_list"   parent="ximoapp01.menu_1"
              action="ximoapp01.action_sprint_window"/>

  </data>
</odoo>

Y el fichero  security/ir.model.access.csv queda:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ximoapp01_task,ximoapp01.task,model_ximoapp01_task,base.group_user,1,1,1,1
access_ximoapp01_sprint,ximoapp01.sprint,model_ximoapp01_sprint,base.group_user,1,1,1,1



4. Relación Many2many

Vamos a crear un tercer modelo llamado "technology", que indica las tecnologías utilizadas para realizar una tarea (por ejemplo Python, Postgres, Linux, Windows, shell, cron, .. )

Veamos la línea a añadir en el modelo task para que tenga el campo tecnologies del model technology

class task(models.Model):
    - - - - - - - -
    technologies = fields.Many2many(comodel_name="ximoapp01.technology", 
                                    relation="technology_task", 
                                    column1="task_id", 
                                    column2="technology_id", 
                                    help="Tecnologías empleadas")

Y para el modelo techology:


class technology(models.Model):
    - - - - - - - -
    tasks = fields.Many2many(comodel_name="ximoapp01.task", 
                             relation="technology_task", 
                             column1="technology_id", 
                             column2="task_id", 
                             help="Se emplea en estas tareas")

Donde:
  1. comodel_name es el modelo con el que se relaciona
  2. relation: nombre de la tabla que guardara dicha relación (debe ser la misma)
  3. column1: Nombre del campo que hace referencia al modelo actual
  4. column2: Nombre del campo que hace referencia al modelo relacionado. Observar que esos campos (column1 y column2) se intercambian en las relaciones.
Veamos pues como quedan los ficheros afectados:

4.1 Models.py


from odoo import models, fields, api
import datetime

class task(models.Model):
    _name = 'ximoapp01.task'
    _description = 'ximoapp01.task'

    name = fields.Char(string= "Nombre", readonly=False, required=True, help="Nombre de la Tarea" )
    description = fields.Text()
    creation_date = fields.Datetime()
start_date = fields.Datetime()
duration= fields.Integer() end_date =fields.Datetime(compute="_get_end_date", store=True )
is_paused = fields.Boolean(default=False) code = fields.Char(compute="_get_code") sprint = fields.Many2one("ximoapp01.sprint", ondelete="set null",help="Macro tarea padre Sprint") technologies = fields.Many2many(comodel_name="ximoapp01.technology", relation="technology_task", column1="task_id", column2="technology_id", help="Tecnologías empleadas") def _get_code(self): for record in self: record.code=record.name + str(record.id) @api.depends('start_date', 'duration') def _get_end_date(self): for record in self:
            if isinstance(sprint.end_date, datetime.datetime) and record.duration > 0 ::
record.end_date = record.start_date + datetime.timedelta(days=record.duration) else: record.end_date=record.start_date class sprint(models.Model): _name = 'ximoapp01.sprint' _description = 'ximoapp01.sprint' name = fields.Char(string= "Nombre", readonly=False, required=True, help="Nombre de la macro tarea Sprint" ) description = fields.Text() creation_date = fields.Datetime() start_date = fields.Datetime()
duration= fields.Integer() end_date =fields.Datetime(compute="_get_end_date", store=True )
tasks = fields.One2many(string="Tareas", comodel_name="ximoapp01.task", inverse_name='sprint') # >Le pasamos modelo y campo de la tabla hija que hace referencia al padre @api.depends('start_date', 'duration') def _get_end_date(self): for record in self: for record in self:
            if isinstance(sprint.end_date, datetime.datetime) and record.duration > 0 ::
record.end_date = record.start_date + datetime.timedelta(days=record.duration) else: record.end_date=record.start_date
class technology(models.Model): _name = 'ximoapp01.technology' _description = 'ximoapp01.technology' name = fields.Char(string= "Nombre", readonly=False, required=True, help="Nombre de la Tecnología" ) description = fields.Text() photo = fields.Image(max_width=200, max_heigh=200) tasks = fields.Many2many(comodel_name="ximoapp01.task", relation="technology_task", column1="technology_id", column2="task_id", help="Tareas donde se emplea")

4.2 Views.xml

<odoo>
  <data>

    <!-- T A S K -->
    <!-- explicit list view definition -->

    <record model="ir.ui.view" id="ximoapp01.task_list">
      <field name="name">ximoapp01 task list</field>
      <field name="model">ximoapp01.task</field>
      <field name="arch" type="xml">
        <tree>
          <field name="name"/>
          <field name="description"/>
          <field name="creation_date"/>
          <field name="start_date"/>
          <field name="duration"/>
          <field name="end_date"/>
          <field name="is_paused"/>
          <field name="sprint"/>
          <field name="technologies"/>
        </tree>
      </field>
    </record>

    <!-- explicit form view definition -->

    <record model="ir.ui.view" id="ximoapp01.task_form">
      <field name="name">ximoapp01 task form</field>
      <field name="model">ximoapp01.task</field>
      <field name="arch" type="xml">
        <form>
          <group>
            <field name="name"/>
            <field name="description"/>
            <field name="creation_date"/>
            <field name="start_date"/>
            <field name="duration"/>
            <field name="end_date"/>
            <field name="is_paused"/>
            <field name="sprint"/>
            <field name="technologies"/>
          </group>
        </form>
      </field>
    </record>

    <!-- S P R I N T -->
    <!-- explicit list view definition -->

    <record model="ir.ui.view" id="ximoapp01.sprint_list">
      <field name="name">ximoapp01 sprint list</field>
      <field name="model">ximoapp01.sprint</field>
      <field name="arch" type="xml">
        <tree>
          <field name="name"/>
          <field name="description"/>
          <field name="creation_date"/>
          <field name="start_date"/>
          <field name="duration"/>
          <field name="end_date"/>
          <field name="tasks"/>
        </tree>
      </field>
    </record>

    <!-- explicit form view definition -->

    <record model="ir.ui.view" id="ximoapp01.sprint_form">
      <field name="name">ximoapp01 sprint form</field>
      <field name="model">ximoapp01.sprint</field>
      <field name="arch" type="xml">
        <form>
          <group>
            <field name="name"/>
            <field name="description"/>
            <field name="creation_date"/>
            <field name="start_date"/>
            <field name="duration"/>
            <field name="end_date"/>
            <field name="tasks"/>
          </group>
        </form>
      </field>
    </record>

    <!-- T E C H N O L O G Y -->
    <!-- explicit list view definition -->

    <record model="ir.ui.view" id="ximoapp01.sprint_list">
      <field name="name">ximoapp01 technology list</field>
      <field name="model">ximoapp01.technology</field>
      <field name="arch" type="xml">
        <tree>
          <field name="name"/>
          <field name="description"/>
          <field name="photo"/>
          <field name="tasks"/>
        </tree>
      </field>
    </record>

    <!-- explicit form view definition -->

    <record model="ir.ui.view" id="ximoapp01.sprint_form">
      <field name="name">ximoapp01 sprint form</field>
      <field name="model">ximoapp01.sprint</field>
      <field name="arch" type="xml">
        <form>
          <group>
            <field name="name"/>
            <field name="description"/>
            <field name="creation_date"/>
            <field name="start_date"/>
            <field name="duration"/>
            <field name="end_date"/>
            <field name="tasks"/>
          </group>
        </form>
      </field>
    </record>


    <!-- actions opening views on models -->

    <!-- T A S K  --> 
    <record model="ir.actions.act_window" id="ximoapp01.action_task_window">
      <field name="name">ximoapp01 task window</field>
      <field name="res_model">ximoapp01.task</field>
      <field name="view_mode">tree,form</field>
    </record>

    <!-- S P R I N T  --> 
    <record model="ir.actions.act_window" id="ximoapp01.action_sprint_window">
      <field name="name">ximoapp01 sprint window</field>
      <field name="res_model">ximoapp01.sprint</field>
      <field name="view_mode">tree,form</field>
    </record>

    <!-- T E C H N O L O G Y  --> 
    <record model="ir.actions.act_window" id="ximoapp01.action_technology_window">
      <field name="name">ximoapp01 technology window</field>
      <field name="res_model">ximoapp01.technology</field>
      <field name="view_mode">tree,form</field>
    </record>


    <!-- server action to the one above -->
<!--
    <record model="ir.actions.server" id="ximoapp01.action_server">
      <field name="name">ximoapp01 server</field>
      <field name="model_id" ref="model_ximoapp01_ximoapp01"/>
      <field name="state">code</field>
      <field name="code">
        action = {
          "type": "ir.actions.act_window",
          "view_mode": "tree,form",
          "res_model": model._name,
        }
      </field>
    </record>
-->

    <!-- Top menu item -->

    <menuitem name="ximoapp01" id="ximoapp01.menu_root"/>

    <!-- menu categories -->

    <menuitem name="Tasks &amp; Sprints" id="ximoapp01.menu_1" parent="ximoapp01.menu_root"/>
<!--
    <menuitem name="Menu 2" id="ximoapp01.menu_2" parent="ximoapp01.menu_root"/>
-->
    <!-- actions -->

    <menuitem name="Tasks" id="ximoapp01.menu_1_task_list"              parent="ximoapp01.menu_1"
              action="ximoapp01.action_task_window"     sequence="10"/>
    <menuitem name="Sprints" id="ximoapp01.menu_1_sprint_list"          parent="ximoapp01.menu_1"
              action="ximoapp01.action_sprint_window"   sequence="15"/> 
    <menuitem name="Technologies" id="ximoapp01.menu_1_technology_list" parent="ximoapp01.menu_1"
              action="ximoapp01.action_technology_window"/>
<!--
    <menuitem name="Server to list" id="ximoapp01" parent="ximoapp01.menu_2"
              action="ximoapp01.action_server"/>
-->
  </data>
</odoo>

Si queremos establecer la secuencia de aparición de los menús y submenús, utilizamos sequence


4.3 ir.model.access.csv

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ximoapp01_task,ximoapp01.task,model_ximoapp01_task,base.group_user,1,1,1,1
access_ximoapp01_sprint,ximoapp01.sprint,model_ximoapp01_sprint,base.group_user,1,1,1,1
access_ximoapp01_technology,ximoapp01.technology,model_ximoapp01_technology,base.group_user,1,1,1,1











Comentarios

Entradas populares de este blog

20. Desarrollando con Odoo (15). Permisos y grupos. Crear usuarios de la aplicación. Restringir permisos a usuarios

2. El Modo desarrollador

10. Crear clave de la API