8. Desarrollando con Odoo (6). Herencia de clase en modelos. Herencia de vistas. Operaciones numeradas en Odoo ??

1. Introducción 

En odoo tenemos 3 mecanismos de herencia de modelos. Para una clase padre llamada "padre" (_name="padre") veamos los 3 mecanismos de hernacia

  1.  El primero és una herencia tradicional añade atributos al modelo padre y se almacena en la misma tabla. La nueva clase es compatible con las vistas existentes de la clase padre. Para esta nueva clase se utiliza el mismo valor del atributo _name ("padre")  del padre (no hace falta volverlo a definir), pero se añade un atributo "_inherit" que toma el nombre del "_name" de la clase padre. Por tanto definiremos _inherit="padre". Se llama herencia de clase que crea una clase compatible con la tabla anterior
  2. El segundo es también herencia tradicional y se llama herencia de prototipo, pero el nuevo objeto utiliza una tabla nueva. Las vistas del padre no le sirven. Para ello, hay que proporcionar el nombre de la clase al atributo "_name" e indicar que se hereda ("_inherit") del padre se dan estos atributos: _name ="hijo" y _inherit="padre" .
  3. Un caso particular es cuando _name hace referencia a una clase que existe y _inherit hace referencia a una clase abstracta existente. En este caso es un "mixin". Lo que hace es añadir las características de la clase referenciada por _inherit a la clase referenciada por _name.
  4. El tercer mecanismo es herencia por delegación. Aquí se guardan en tablas diferentes y las vistas del padre no le sirven. En la tabla se guarda un atributo que es el objeto padre y además los atributos que adjuntamos. Hay que darle un atributo "_name" y el atributo (en plural!!!) "_inherits" que puede toma una lista de clases padres (herencia múltiple): _name="hijo_herencia_multiple" _inherits="padre1", "padre2",...."padreN". Parece ser según el vídeo de Cybrosys que los atributos del hijo son accesibles desde el padre
En esta imagen se parecian los 3 mecanismos de herencia de los modelos de Odoo


Imagen obtenida de la documentación de Odoo


En Odoo la que más se utiliza es la primera (herencia de clases)


2. El modelo "Contactos" que correspode a "res.partner"

Es conveniente que cualquier tipo de persona herede de "res.partner" que proporciona Odoo.

Creamos esta clase que sencillamente toma el nombre de la clase padre. No hace falta indicar el atributo _name (aunque aquí se se ha hecho) y se añade al campo technology. A todos los fectos hemos añadido un campo a la clase "res.parter", y se pueden aprovechar todas las vistas.

class developer(models.Model):
    _name = 'res.partner' # no hace falta poner este campo
    _inherit = 'res.partner'

    technologies = fields.Many2many(comodel_name="ximoapp01.technology", relation="technology_developer", column1="developer_id", column2="technology_id", help="Tecnologías que usa")

Para el fichero views.xml solo hace falta aladir la acción y añadirla al menú y ya está. en este caso se ha añadido un menu primario "Human Resources" del que colgará nuestr nuevo "res.partner"

    <!-- D E V E L O P E R  --> 
    <record model="ir.actions.act_window" id="ximoapp01.action_developer_window">
      <field name="name">ximoapp01 developer window</field>
      <field name="res_model">res.partner</field>
      <field name="view_mode">tree,form</field>
    </record>

    <!-- Top menu item -->

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

    <!-- menu categories -->

    <menuitem name="Projects &amp; Histories" id="ximoapp01.menu_1" parent="ximoapp01.menu_root"/>
    <menuitem name="Tasks &amp; Sprints"      id="ximoapp01.menu_2" parent="ximoapp01.menu_root"/>
    <menuitem name="Human resources"          id="ximoapp01.menu_3" parent="ximoapp01.menu_root"/>
    
    <!-- actions -->

    <menuitem name="Projects" id="ximoapp01.menu_1_project_list"            parent="ximoapp01.menu_1"
              action="ximoapp01.action_project_window"/>
    <menuitem name="Histories" id="ximoapp01.menu_1_history_list"           parent="ximoapp01.menu_1"
              action="ximoapp01.action_history_window"/> 
    

    <menuitem name="Tasks" id="ximoapp01.menu_2_task_list"              parent="ximoapp01.menu_2"
              action="ximoapp01.action_task_window"/>
    <menuitem name="Sprints" id="ximoapp01.menu_2_sprint_list"          parent="ximoapp01.menu_2"
              action="ximoapp01.action_sprint_window"/> 

    <menuitem name="Technologies" id="ximoapp01.menu_2_technology_list" parent="ximoapp01.menu_2"
              action="ximoapp01.action_technology_window"/>

    <menuitem name="Developers" id="ximoapp01.menu_3_developer_list"    parent="ximoapp01.menu_3"
              action="ximoapp01.action_developer_window"/>          
</data> </odoo>

Pero se veran todos los contactos. Sería bueno discriminar solo los que sean desarrolladores! Esto se realiza mediante herencia de vistas.

3. Herencia de vistas. Parte 1

Veamos lo que hay que hacer para la vista "tree".
  1. Mantener la acción creada en el punto anterior y crear una nueva acción para la vista "tree" que:
  2. El "model" sea "ir.actions.act_window.view" (en vez de "ir.actions.act_window.view")
  3. Indicar la secuencia de carga de vistas, en este caso comenzamos con "1" con esta línea :    <field name='sequence' eval="1"></field>
  4. Le indicamos el view_mode : <field name='view_mode'>tree</field>
  5. Y la vista a cargar, que se obiene de la cucaracha del modo desarrollador "Vista de edición List" y seleccionamos el ID externo que es "base.view_partner_tree": <field name="view_id" ref='base.view_partner_tree'></field>
  6. Indicar que la acción original: <field name=act_window_id" ref='ximoapp01.action_developer_window'></field>
Para la vista "form" se hará:
  1. Se creará una vista tipo form con los campos que queramos del modelo "res.partner" ampliado
  2. Se creará una nueva acción igual que la anterior, pero cambiando la secuencia "seq" a "2" y el "view_mode" a "form" y "ref" que sea el id de la vista tipo form que hemos creado.
Veamos como queda la vista particular para developer:

    <!-- D E V E L O P E R  --> 
    <record model="ir.ui.view" id="ximoapp01.developer_form">
      <field name="name">ximoapp01 developer form</field>
      <field name="model">res.partner</field>
      <field name="arch" type="xml">
        <form>
          <group>
            <field name="technologies"/>
          </group>
        </form>
      </field>
    </record>

Y las acciones, tanto la original como las 2 adicionales para tree y form:

    <!-- D E V E L O P E R  -->
    <!-- Accion primaria--> 
    <record model="ir.actions.act_window" id="ximoapp01.action_developer_window">
      <field name="name">ximoapp01 developer window</field>
      <field name="res_model">res.partner</field>
      <field name="view_mode">tree,form</field>
    </record>
    <!-- Accion secundaria tree--> 
    <record model="ir.actions.act_window.view" id="ximoapp01.action_developer_tree">
      <field name="sequence" eval="1"></field>
      <field name="view_mode">tree</field>
      <field name="view_id" ref='base.view_partner_tree'></field>
      <field name="act_window_id" ref='ximoapp01.action_developer_window'></field>
    </record>
    <!-- Accion secundaria form--> 
    <record model="ir.actions.act_window.view" id="ximoapp01.action_developer_form">
      <field name="sequence" eval="2"></field>
      <field name="view_mode">form</field>
      <field name="view_id" ref='ximoapp01.developer_form'></field>
      <field name="act_window_id" ref='ximoapp01.action_developer_window'></field>
    </record>

Pero queremos heredar la vista y no hacer una vista cutre con un solo campo (technologies).


4. Herencia de vistas. Parte 2

Vamos a la vista form de contactos y en la cucaracha del modo desarrollador seleccionamaos "Vista de edición Form" y vemos como se llama la vista de formulario de "res.partner" que se llama "res.partner.form" y el ID externo que es el que nos interesa que es "base.view_partner_form"

A la vista anterior le añadiremos las 2 líneas resaltadas en amarillo que significan:
  1. La primera indica que hereda ("inherit_id") de la vista "base.view_partner_form"
  2. La segunda indica que los cambios producidos se realicen sobre una vista nueva y que no afecten o modifiquen la vista original, pues si metemos un error, ya no funcionaría la vista original de contactos. Para ello se indica <field name="mode">primary</field>
  3. Para este caso concreto, queremos meter el campo tecnologias dentro de una pestaña (tab), y seleccionamos donde vamos a meter este nuevo elemento. Para ello, en el modo desarrollador podemos ver el código de la vista (Vista edicion Form). Buscamos una "page" cuyo atributo "name" sea "internal_notes" que este anidada dentro de "notebook" y este dentro de "sheet" con "xpath"="//sheet/notebook/page" y creamos una nueva página depues de esta (position="after" ) 
  4. A continuación se definen los elementos (campo technologies) que vamos a meter dentro de esa pestaña (page)

    <!-- D E V E L O P E R  --> 
    <record model="ir.ui.view" id="ximoapp01.developer_form">
      <field name="name">ximoapp01 developer form</field>
      <field name="model">res.partner</field>
      <field name="inherit_id" ref="base.view_partner_form"></field>
      <field name="mode">primary</field>
      <field name="arch" type="xml">
      
        <xpath expr="//sheet/notebook/page[@name='internal_notes']" position = 'after'>
          <page name="devs" string="Devs">
            <group>
              <group>
                <field name="technologies"/>
              </group>  
            </group>
          </page>
        </xpath>

      </field>
</record>


Pero si abrimos el form, aparecen todos los contactos, pero solo nos interesan aquellos que son desarrolladores. Para ello en el modelo creamos un campo booleano is_dev

En la primera action del elemento developer (fichero de vistas) metemos una condición (o filtro) para que solo muestre desarrolladores,  mediante la creación de "domain"
<field name="domain">[('is_dev','=',True)]</field>

Tambien en la primera action anterior se añade "context" que es un diccionario de datos en formato json que se pasan al cliente web para que se apliquen estos valores. En este caso se da un valor por defecto al campo is_dev
<field name="context">{'default_is_dev':True}</field>

Tambien añadimos a la vista el campo is_dev a la vista (form) pero solo se mostrará el "page" si is_dev es True. Para ello el atributo "invisible" lo pondremos a false si is_dev es True
<page name="devs" string="Devs" attrs="{'invisible':[('is_dev','=',False)]}">

Veamos pues como queda la vista del form:

    <!-- D E V E L O P E R  --> 
    <record model="ir.ui.view" id="ximoapp01.developer_form">
      <field name="name">ximoapp01 developer form</field>
      <field name="model">res.partner</field>
      <field name="inherit_id" ref="base.view_partner_form"></field>
      <field name="mode">primary</field>
      <field name="arch" type="xml">
        <xpath expr="//sheet/notebook/page[@name='internal_notes']" position = 'after'>
          <page name="devs" string="Devs" attrs="{'invisible':[('is_dev','=',False)]}">
            <group>
              <group>
                <field name="is_dev"/>
                <field name="technologies"/>
              </group>  
            </group>
          </page>
        </xpath>
      </field>  
    </record>


Veamos como queda la primera acción de developers

    <!-- D E V E L O P E R  -->
    <!-- Accion primaria--> 
    <record model="ir.actions.act_window" id="ximoapp01.action_developer_window">
      <field name="name">ximoapp01 developer window</field>
      <field name="res_model">res.partner</field>
      <field name="view_mode">tree,form</field>
      
      <field name="domain">[('is_dev','=',True)]</field>
      <field name="context">{'default_is_dev':True}</field>
      
    </record>

5. Herencia de vistas. Parte 3. Relaciones

Si creamos el campo "developer" en el modelo "task" de tipo Many2one que relaciona el modelo "res.partner", a la hora de elegir un developer aparecen todos los contactos. Por tanto debemos reducir la busqueda a solo los que sean "is_dev" True

Para ello en la vista "form" de "task" tenemos que indicar:
  1. Utilizando  "domain" con la condición is_dev igual True
  2. Utilizando "context" para pasar valores al form, pero aquí le decimos el id del formulario que queremos que se cargue que es "ximoapp01.developer_form"
<field
    name= "developer"
    domain="[('is_dev','=',True)]"
    context="{'form_view_ref':'ximoapp01.developer_form'}">
</field>

con lo que la vista for de task queda

    <!-- T A S K  -->
    <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="definition_date"/>
            <field name="code"/>
            <field name="start_date"/>
            <field name="duration"/>
            <field name="end_date"/>
            <field name="is_paused"/>
            <field name="sprint"/>
            <field name="technologies"/>
            <field name="history"/>
            <field
              name= "developer"
              domain="[('is_dev','=',True)]"
              context="{'form_view_ref':'ximoapp01.developer_form'}">
            </field>
          </group>
        </form>
      </field>
    </record>


6. Herencia de vistas. Parte 4. Añadir un tag "dev" al developer

El modelo "res.partner" tiene una campo cuya etiqueta es Tags y queremos que cuando se cree un developer, se asigne "dev" a este campo.

Queremos saber como se llama este campo. Vamos a la "?" que hay al lado de la etiqueta y vemos el nombre del campo. que es "category_id", pero pertenece al modelo "res.partner.category" con una relación "Many2many"


Cuando vayamos a asignarles este "category_id" ("dev") a un desarrollador (solo al crearlo), primero veremos si existe "dev" y si no existe la creamos.

Vamos a crear un método en el modelo cuando el campo is_dev cambia de valor, que en principio será solamente al crear el desarrollador (si dejamos el campo a solo lectura en el form)

<field name="is_dev" readonly="True"/>


A la hora de buscar la categoría utilizamos self.env, y también a la hora de crearla.

Pero para asignar la categoria al campo tenemos que utilizar esta "argucia" (pues tenemos una relación many2many) donde asignamos al campo del modelo una lista de tuplas donde el 4 indica que el valor se va a modificar pero añadiendo a los valores que ya tenga un nuevo valor "category.id"

Veamos como queda el modelo

class developer(models.Model):
    _name = 'res.partner' # no hace falta poner este campo
    _inherit = 'res.partner'

    is_dev= fields.Boolean()

    technologies = fields.Many2many(comodel_name="ximoapp01.technology", relation="technology_developer", column1="developer_id", column2="technology_id", help="Tecnologías que usa")

    @api.onchange('is_dev')
    def _onchange_is_dev(self):
        categories = self.env['res.partner.category'].search([('name','=','Devs')])
        if len(categories)>0:
            category= categories[0]
        else:
            category = self.env['res.partner.category'].create({'name':'Devs'})    
        self.category_id=[4,category.id]

Respecto a las operaciones, en un blog posterior y otro blog posterior se ve una pincelada y son:
  • Se le indica el tipo de operación:  
    •     (0,_ ,{'field':value}) : Crea un nuevo registro y lo vincula
    •     (1,id,{'field':value}) : Actualiza los valores en un registro ya vinculado
    •     (2,id,_              ) : Desvincula y elimina el registro
    •     (3,id,_              ) : Desvincula pero no elimina el registro de la relación
    •     (4,id,_              ) : Vincula un registro ya existente
    •    (5,_ ,_              ) : Desvincula pero no elimina los registros vinculados
    •    (6,_ ,[ids]          ): Reemplaza la lista de registros vinculados




Comentarios

Entradas populares de este blog

4. Desarrollando con Odoo (2). Introducción a modulos, vistas, modelos .. Añadir dependencias externas de Python a Odoo

26. Desarrollando con Odoo (21). IMPORTANTE: Entorno de desarrollo. Recapitulaciones del capítulo 8 (Development, Test and Debug).