Herencia - Extendiendo la Funcionalidad de las Aplicaciones Existentes

Una de las características más poderosas de Odoo es la capacidad para agregar características sin modificar directamente los objetos subyacentes. Esto se logra a través de mecanismos de herencia, que funcionan como capas para la modificación por encima de los objetos existentes.

Estas modificaciones puede suceder en todos los niveles: modelos, vistas, y lógica de negocio. En lugar de modificar directamente un módulo existente, creara un módulo nuevo para agregar las modificaciones previstas.

Aquí, aprenderá como escribir sus propios módulos de extensión, confiriéndole facultades para aprovechar las aplicaciones base o comunitarias. Como ejemplo, aprenderá como agregar las características de mensajería y redes sociales de Odoo a sus propios módulos.

Agregar la capacidad de compartir con otros a la aplicación To-Do

La aplicación To-Do actualmente permite a los usuarios y gestionar de forma privada sus tareas por hacer. ¿No sería grandioso llevar su aplicación a otro nivel agregando características colaborativas y de redes sociales? Sera capaz de compartir las tareas y discutirlas con otras personas.

Hará esto con un módulo nuevo para ampliar la funcionalidad de la aplicación To-Do creada anteriormente y agregar estas características nuevas. Esto es lo que esperara lograr al final de este capítulo:

Gráfico 3.1 - Nuevo módulo para la aplicación To-Do

Gráfico 3.1 - Nuevo módulo para la aplicación To-Do

Camino a seguir para las características colaborativas

Aquí esta su plan de trabajo para implementar la extensión de funcionalidades:

  • Agregar campos al modelo Task, como el usuario quien posee la tarea.
  • Modificar la lógica de negocio para operar solo en la tarea actual del usuario, en vez de todas las tareas disponibles para ser vistas por el usuario.
  • Agregar los campos necesarios a las vistas.
  • Agregar características de redes sociales: el muro de mensajes y los seguidores.

Comience creando la estructura básica para el módulo junto al módulo todo_app. Siguiendo el ejemplo de instalación del Capítulo 1, sus módulos estarán alojados en ~/odoo-dev/custom-addons/:

$ mkdir ~/odoo-dev/custom-addons/todo_user
$ touch ~/odoo-dev/custom-addons/todo_user/__init__.py

Ahora cree el archivo __openerp__.py, con el siguiente código:

{
    'name': 'Multiuser To-Do',
    'description': 'Extend the To-Do app to multiuser.',
    'author': 'Daniel Reis',
    'depends': ['todo_app'],
}

No ha hecho esto, pero incluir las claves "summary" y "category" puede ser importante cuando se publican módulos en la tienda de aplicaciones en línea de Odoo.

Ahora, podrá instalarlo. Debe ser suficiente con solo actualizar el Lista de módulos desde el menú Configuración, encuentre el módulo nuevo en la lista de Módulos locales y haga clic en el botón Instalar. Para instrucciones más detalladas sobre como encontrar e instalar un módulo puede volver al Capítulo 1.

Ahora, comience a agregar las nuevas características.

Ampliando el modelo de tareas por hacer

Los modelos nuevos son definidos a través de las clases Python. Ampliarlos también es hecho a través de las clases Python, pero usando un mecanismo específico de Odoo.

Para aplicar un modelo use una clase Python con un atributo __inherit. Este identifica el modelo que será ampliado. La clase nueva hereda todas las características del modelo padre, y solo necesite declarar las modificaciones que querrá introducir.

De hecho, los modelos de Odoo existen fuera de su módulo particular, en un registro central. Podrá referirse a este registro como la piscina, y puede ser accedido desde los métodos del modelo usando self.env[<model name>]. Por ejemplo, para referirse al modelo res.partner escribirá self.env['res.partner'].

Para modificar un modelo de Odoo obtiene una referencia a la clase de registro y luego ejecuta los cambios en ella. Esto significa que esas modificaciones también estarán disponibles en cualquier otro lado donde el modelo sea usado.

En la secuencia de carga del módulo, durante un reinicio del servidor, las modificaciones solo serán visibles en los modelos cargados después. Así que, la secuencia de carga es importante y debe asegurarse que las dependencias del módulo están fijadas correctamente.

Agregar campos a un modelo

Ampliara el modelo todo.task para agregar un par de campos: el usuario responsable de la tarea, y la fecha de vencimiento.

Cree un archivo todo_task.py nuevo y declare una clase que extienda al modelo original:

#-*- coding: utf-8 -*-
from openerp import models, fields, api
class TodoTask(models.Model):
    _inherit = 'todo.task'
    user_id = fields.Many2one('res.users', 'Responsible')
    date_deadline = fields.Date('Deadline')

El nombre de la clase es local para este archivo Python, y en general es irrelevante para los otros módulos. El atributo _inherit de la clase es la clave aquí: esta le dice a Odoo que esta clase hereda el modelo todo.task. Note la ausencia del atributo _name. Este no es necesario porque ya es heredado desde el modelo padre.

Las siguientes dos líneas son declaraciones de campos comunes. El user_id representa un usuario desde el modelo Users, res.users. Es un campo de Many2one equivalente a una clave foránea en el argot de base de datos. El date_deadline es un simple campo de fecha. En el Capítulo 5, se explica con más detalle los tipos de campos disponibles en Odoo.

Aun le falta agregar al archivo __init__.py la declaración import para incluirlo en el módulo:

from . import todo_task

Para tener los campos nuevos agregados a la tabla de la base de datos soportada por el modelo, necesita ejecutar una actualización al módulo. Si todo sale como es esperado, debería poder ver los campos nuevos cuando revise el modelo todo.task, en el menú Técnico, Estructura de base de datos > Modelos.

Modificar los campos existentes

Como puede ver, agregar campos nuevos a un modelo existente es bastante directo. Desde Odoo 8, es posible modificar atributos en campos existentes. Esto es hecho agregando un campo con el mismo nombre, y configurando los valores solo para los atributos que serán modificados.

Por ejemplo, para agregar un comentario de ayuda a un campo name, podrá agregar esta línea en el archivo todo_task.py:

name = fields.Char(help="What needs to be done?")

Si actualiza el módulo, va a un formulario de tareas por hacer, y posicione el ratón sobre el campo Descripción, aparecerá el mensaje de texto escrito en el código anterior.

Modificar los métodos del modelo

La herencia también funciona en la lógica de negocio. Agregar métodos nuevos es simple: solo declare las funciones dentro de la clase heredada.

Para ampliar la lógica existente, un método puede ser sobrescrito declarando otro método con el mismo nombre, y el método nuevo reemplazará al anterior. Pero este puede extender el código de la clase heredada, usando la palabra clave de Python super() para llamar al método padre.

Es mejor evitar cambiar la función distintiva del método (esto es, mantener los mismos argumentos) para asegurarse que las llamadas a este sigan funcionando adecuadamente. En caso que necesite agregar parámetros adicionales, hágalos opcionales (con un valor predeterminado).

La acción original de Clear All Done ya no es apropiada para su módulos de tareas compartidas, ya que borra todas las tareas sin importar a quien le pertenecen. Necesita modificarla para que borre solo las tareas del usuario actual.

Para esto, se sobrescribirá el método original con una nueva versión que primero encuentre las tareas completadas del usuario actual, y luego las desactive:

@api.multi
def do_clear_done(self):
    domain = [('is_done', '=', True), '|', ('user_id', '=', self.env.uid), ('user_id', '=', False)]
    done_recs = self.search(domain)
    done_recs.write({'active': False})
    return True

Primero se listan los registros finalizados sobre los cuales se usa el método search con un filtro de búsqueda. El filtro de búsqueda sigue una sintaxis especial de Odoo referida como domain.

El filtro "domain" usado es definido en la primera instrucción: es una lista de condiciones, donde cada condición es una tupla.

Estas condiciones son unidas implícitamente con un operador AND (& en la sintaxis de dominio). Para agregar una operación OR se usa una "tubería" (|) en el lugar de la tupla, y afectara las siguientes dos condiciones. Ahondara más sobre este tema en el Capítulo 6.

El dominio usado aquí filtra todas las tareas con su etapa finalizadas ('is_done', '=', True) que también tengan al usuario actual como responsable ('user_id','=',self.env.uid) o no tengan fijado un usuario ('user_id', '=', False).

Lo que acaba de hacer fue sobrescribir completamente el método padre, reemplazándolo con una implementación nueva.

Pero esto no es lo que usualmente querrá hacer. En vez de esto, ampliara la lógica actual y agregara operaciones adicionales. De lo contrario podrá dañar operaciones existentes. La lógica existente es insertada dentro de un método sobrescrito usando el comando super() de Python para llamar a la versión padre del método.

Vea un ejemplo de esto: podrá escribir una versión mejor de do_toggle_done() que solo ejecute la acción sobre las Tareas asignadas a su usuario:

@api.one
def do_toggle_done(self):
    if self.user_id != self.env.user:
        raise Exception('Only the responsible can do this!')
    else:
        return super(TodoTask, self).do_toggle_done()

Estas son las técnicas básicas para sobrescribir y ampliar la lógica de negocio definida en las clases del modelo. Vera ahora como extender las vistas de la interfaz con los usuarios.

Ampliar las vistas

Vistas de formulario, listas y búsqueda son definidas usando las estructuras de arco de XML. Para ampliar las vistas necesita una manera de modificar este XML. Esto significa localizar los elementos XML y luego introducir modificaciones en esos puntos. Las vistas heredadas permiten esto.

Una vista heredada se ve así:

<record id="view_form_todo_task_inherited" model="ir.ui.view">
    <field name="name">Todo Task form – User extension</field>
    <field name="model">todo.task</field>
    <field name="inherit_id" ref="todo_app.view_form_todo_task"/>
    <field name="arch" type="xml">
        <!-- ...match and extend elements here! ... -->
    </field>
</record>

El campo inherit_id identifica la vista que será ampliada, a través de la referencia de su identificador externo usando el atributo especial ref. Los identificadores externos serán discutidos con mayor detalle en el Capítulo 4.

La forma natural de localizar los elementos XML es usando expresiones XPath. Por ejemplo, tomando la vista que fue definida en el capítulo anterior, la expresión XPath para localizar el elemento <field name="is_done"> es //field[@name]='is_done'. Esta expresión encuentra un elemento field con un atributo name igual a is_done. Puede encontrar mayor información sobre XPath en: https://docs.python.org/2/library/xml.etree.elementtree.html#xpath-support.

Tener atributos "name" en los elementos es importante porque los hace mucho más fácil de seleccionar como puntos de extensión. Una vez que el punto de extensión es localizado, puede ser modificado o puede tener elementos XML agregados cerca de él.

Como un ejemplo práctico, para agregar el campo date_deadline antes del campo is_done, debe escribir en arch:

<xpath expr="//field[@name]='is_done'" position="before">
    <field name="date_deadline" />
</xpath>

Afortunadamente Odoo proporciona una notación simplificada para eso, así que la mayoría de las veces podrá omitir la sintaxis XPath. En vez del elemento xpath anterior podrá usar el tipo de elementos que querrá localizar y su atributo distintivo.

Lo anterior también puede ser escrito como:

<field name="is_done" position="before">
    <field name="date_deadline" />
</field>`

Agregar campos nuevos, cerca de campos existentes es hecho frecuentemente, por lo tanto la etiqueta <field> es usada frecuentemente como el localizador. Pero cualquier otra etiqueta puede ser usada: <sheet>, <group>, <div>, entre otras. El atributo name es generalmente la mejor opción para hacer coincidir elementos, pero a veces, podrá necesitar usar string (el texto mostrado en un "label") o la clase CSS del elemento.

El atributo de posición usado con el elemento localizador es opcional, y puede tener los siguientes valores:

  • after: Este es agregado al elemento padre, después del nodo de coincidencia.
  • before: Este es agregado al elemento padre, antes del nodo de coincidencia.
  • inside (el valor predeterminado): Este es anexado al contenido del nodo de coincidencia.
  • replace: Este reemplaza el nodo de coincidencia. Si es usado con un contenido vacío, borra un elemento.
  • attributes: Este modifica los atributos XML del elemento de coincidencia (más detalles luego de esta lista).

La posición del atributo le permite modificar los atributos del elemento de coincidencia. Esto es hecho usando los elementos <attribute name="attr-name"> con los valores del atributo nuevo.

En el formulario de Tareas, tendrá el campo Active, pero tenerlo visible no es muy útil. Quizás podrá esconderlo al usuario. Esto puede ser realizado configurando su atributo invisible:

<field name="active" position="attributes">
    <attribute name="invisible">1</attribute>
</field>

Configurar el atributo invisible para esconder un elemento es una buena alternativa para usar el localizador de reemplazo para eliminar nodos. Debería evitarse la eliminación, ya que puede dañar las extensiones de modelos que pueden depender del nodo eliminado.

Finalmente, podrá poner todo junto, agregar los campos nuevos, y obtener la siguiente vista heredada completa para ampliar el formulario de tareas por hacer:

<record id="view_form_todo_task_inherited" model="ir.ui.view">
    <field name="name">Todo Task form – User extension</field>
    <field name="model">todo.task</field>
    <field name="inherit_id" ref="todo_app.view_form_todo_task"/>
    <field name="arch" type="xml">
        <field name="name" position="after">
            <field name="user_id" />
        </field>
        <field name="is_done" position="before">
            <field name="date_deadline" />
        </field>
        <field name="name" position="attributes">
            <attribute name="string">I have to…</attribute>
        </field>
    </field>
</record>

Esto debe ser agregado al archivo todo_view.xml en su módulo, dentro de las etiquetas <openerp> y <data>, como fue mostrado en el capítulo anterior.

No podrá olvidar agregar el atributo datos al archivo descriptor __openerp__.py:

'data': ['todo_view.xml'],

Ampliando más vistas de árbol y búsqueda

Las extensiones de las vistas de árbol y búsqueda son también definidas usando la estructura XML arch, y pueden ser ampliadas de la misma manera que las vistas de formulario. Seguidamente se muestra un ejemplo de la ampliación de vistas de lista y búsqueda.

Para la vista de lista, querrá agregar el campo usuario:

<record id="view_tree_todo_task_inherited" model="ir.ui.view">
    <field name="name">Todo Task tree – User extension</field>
    <field name="model">todo.task</field>
    <field name="inherit_id" ref="todo_app.view_tree_todo_task"/>
    <field name="arch" type="xml">
        <field name="name" position="after">
            <field name="user_id" />
        </field>
    </field>
</record>

Para la vista de búsqueda, agregara una búsqueda por usuario, y filtros predefinidos para las tareas propias del usuario y tareas no asignadas a alguien.

<record id="view_filter_todo_task_inherited" model="ir.ui.view">
    <field name="name">Todo Task tree – User extension</field>
    <field name="model">todo.task</field>
    <field name="inherit_id" ref="todo_app.view_filter_todo_task"/>
    <field name="arch" type="xml">
        <field name="name" position="after">
            <field name="user_id" />
            <filter name="filter_my_tasks" string="My Tasks"
                    domain="[('user_id','in',[uid,False])]" />
            <filter name="filter_not_assigned" string="Not Assigned"
                    domain="[('user_id','=',False)]" />
        </field>
    </field>
</record>

No se preocupe demasiado por la sintaxis específica de las vistas. Se describirá esto con más detalle en el Capítulo 6.

Más sobre el uso de la herencia para ampliar los modelos

Ha visto lo básico en lo que se refiere a la ampliación de modelos "in place", lo cual es la forma más frecuente de uso de la herencia. Pero la herencia usando el atributo _inherit tiene mayores capacidades, como la mezcla de clases.

También tiene disponible el método de herencia delegada, usando el atributo _inherits. Esto permite a un modelo contener otros modelos de forma transparente a la vista, mientras por detrás de escena cada modelo gestiona sus propios datos.

Explore esas posibilidades en más detalle.

Copiar características usando herencia por prototipo

El método que use anteriormente para ampliar el modelo solo usa el atributo _inherit. Defina una clase que hereda el modelo todo.task, y le agrega algunas características. La clase _name no fue fijada explícitamente; implícitamente fue también todo.task.

Pero usando el atributo _name le permitió crear una mezcla de clases (mixin), incorporándolo al modelo que querrá ampliar. Aquí se muestre un ejemplo:

from openerp import models
class TodoTask(models.Model):
    _name = 'todo.task'
    _inherit = 'mail.thread'

Esto amplia el modelo todo.task copiando las características del modelo mail.thread. El modelo mail.thread implementa la mensajería de Odoo y la función de seguidores, y es reusable, por lo tanto es fácil agregar esas características a cualquier modelo.

Copiar significa que los métodos y los campos heredados estarán disponibles en el modelo heredero. Para los campos significa que estos serán creados y almacenados en las tablas de la base de datos del modelo objetivo. Los registros de datos del modelo original (heredado) y el nuevo modelo (heredero) son conservados sin relación entre ellos. Solo son compartidas las definiciones.

Estas mezclas son usadas frecuentemente como modelos abstractos, como el mail.thread usado en el ejemplo. Los modelos abstractos son como los modelos regulares excepto que no es creada ninguna representación de ellos en la base de datos. Actúan como plantillas, describen campos y la lógica para ser reusadas en modelos regulares.

Los campos que definen solo serán creados en aquellos modelos regulares que hereden de ellos. En un momento se discutirá en detalle como usar eso para agregar mail.thread y sus características de redes sociales a su módulo. En la práctica cuando se usan las mezclas rara vez hereda de modelos regulares, porque esto puede causar duplicación de las mismas estructuras de datos.

Odoo proporciona un mecanismo de herencia delegada, el cual impide la duplicación de estructuras de datos, por lo que es usualmente usada cuando se hereda de modelos regulares. Vea esto con mayor detalle.

Integrar Modelos usando herencia delegada

La herencia delegada es el método de extensión de modelos usado con menos frecuencia, pero puede proporcionar soluciones muy convenientes. Es usada a través del atributo _inherits (note la 's' adicional) con un mapeo de diccionario de modelos heredados con campos relacionados a él.

Un buen ejemplo de esto es el modelo estándar Users, res.users, que tiene un modelo Partner res.partner anidado:

from openerp import models, fields

class User(models.Model):
    _name = 'res.users'
    _inherits = {'res.partner': 'partner_id'}
    partner_id = fields.Many2one('res.partner')

Con la herencia delegada el modelos res.users integra el modelo heredado res.partner, por lo tanto cuando un usuario (User) nuevo es creado, un socio (Partner) también es creado y se mantiene una referencia a este a través del campo partner_id de User. Es similar al concepto de polimorfismo en la programación orientada a objetos.

Todos los campos del modelo heredado, Partner, están disponibles como si fueran campos de User, a través del mecanismo de delegación. Por ejemplo, el nombre del socio y los campos de dirección son expuestos como campos de User, pero de hecho son almacenados en el modelo Partner enlazado, y no ocurre ninguna duplicación de la estructura de datos.

La ventaja de esto, comparada a la herencia por prototipo, es que no hay necesidad de repetir la estructura de datos en muchas tablas, como las direcciones. Cualquier modelo que necesite incluir un dirección puede delegar esto a un modelo Partner vinculado. Y si son introducidas algunas modificaciones en los campos de dirección del socio o validaciones, estas estarán disponibles inmediatamente para todos los modelos que vinculen con él!

Usar la herencia para agregar características redes sociales

El módulo de red social (nombre técnico mail) proporciona la pizarra de mensajes que se encuentra en la parte inferior de muchos formularios, también llamado Charla Abierta (Open Chatter), los seguidores se presentan junto a la lógica relativa a mensajes y notificaciones. Esto es algo que va a querer agregar con frecuencia a sus modelos, así que aprenda como hacerlo.

Las características de mensajería de red social son proporcionadas por el modelo mail.thread del modelo mail. Para agregarlo a un módulo personalizado necesita:

  1. Que el módulo dependa de mail.
  2. Que la clase herede de mail.thread.
  3. Tener agregados a la vista de formulario los widgets Followers (seguidores) y Threads (hilos).
  4. Opcionalmente, configurar las reglas de registro para seguidores.

Siga esta lista de verificación:

En relación a #1, debido a que su módulo ampliado depende de todo_app, el cual a su vez depende de mail, la dependencia de mail esta implícita, por lo tanto no se requiere ninguna acción.

En relación a #2, la herencia a mail.thread es hecha usando el atributo _inherit. Pero su clase ampliada de tareas por hacer ya está usando el atributo _inherit.

Afortunadamente, también puede aceptar una lista de modelos desde los cuales heredar, así que podrá usar esto para hacer que incluya la herencia a mail.thread:

_name = 'todo.task'
_inherit = ['todo.task', 'mail.thread']

El modelo mail.thread es un modelo abstracto. Los modelos abstractos son como los modelos regulares excepto que no tienen una representación en la base de datos; no se crean tablas para ellos. Los modelos abstractos no están destinados a ser usados directamente. Pero se espera que sean usados en la mezcla de clases, como acaba de hacer.

Podrá pensar en los modelos abstractos como plantillas con características listas para usar. Para crear una clase abstracta solo necesita usar modelos abstractos. AbstractModel en vez de models.Model.

Para la número #3, querrá agregar el widget de red social en la parte inferior del formulario. Podrá reusar la vista heredada que recién creada, view_form_todo_task_inherited, y agregar esto dentro de arch:

<sheet position="after">
    <div class="oe_chatter">
        <field name="message_follower_ids" widget="mail_followers" />
        <field name="message_ids" widget="mail_thread" />
    </div>
</sheet>

Los dos campos que ha agregado aquí no han sido declarados explícitamente, pero son provistos por el modelo mail.thread.

El paso final es fijar las reglas de los registros de seguidores, esto solo es necesario si su modelo tiene implementadas reglas de registro que limitan el acceso a otros usuarios. En este caso, necesita asegurarse que los seguidores para cada registro tengan al menos acceso de lectura.

Tendrá reglas de registro en su modelo de tareas por hacer así que necesita abordar esto, y es lo que hará en la siguiente sección.

Modificar datos

A diferencia de las vistas, los registros de datos no tienen una estructura de arco XML y no pueden ser ampliados usando expresiones XPath. Pero aún pueden ser modificados reemplazando valores en sus campos.

El elemento <record id="x" model="y"> está realizando una operación de inserción o actualización en un modelo: si x no existe, es creada; de otra forma, es actualizada / escrita.

Debido a que los registros en otros módulos pueden ser accedidos usando un identificador <model>.<identifier>, es perfectamente legal para su módulo sobrescribir algo que fue escrito antes por otro módulo.

Como ejemplo, cambie la opción de menú creada por el módulo todo_app en "My To Do". Para esto agregar lo siguiente al archivo todo_user/todo_view.xml:

<!-- Modify menu item -->
<record id="todo_app.menu_todo_task" model="ir.ui.menu">
    <field name="name">My To-Do</field>
</record>
<!-- Action to open To-Do Task list -->
<record model="ir.actions.act_window" id="todo_app.action_todo_task">
    <field name="context">
        {'search_default_filter_my_tasks': True}
    </field>
</record>

Ampliando las reglas de registro

La aplicación Tareas-por-Hacer incluye una regla de registro para asegurar que cada tarea sea solo visible para el usuario que la ha creado. Pero ahora, con la adición de las características sociales, necesita que los seguidores de la tarea también tengan acceso. El modelo de red social no maneja esto por si solo.

Ahora las tareas también pueden tener usuarios asignados a ellas, por lo tanto tiene más sentido tener reglas de acceso que funcionen para el usuario responsable en vez del usuario que creo la tarea.

El plan será el mismo que para la opción de menú: sobrescribir todo_app.todo_task_user_rule para modificar el campo domain_force a un valor nuevo.

Desafortunadamente, esto no funcionará esta vez. Recuerde que el <data no_update="1"> que use anteriormente en el archivo XML de las reglas de seguridad: previene las operaciones posteriores de escritura.

Debido a que las actualizaciones del registro no están permitidas, necesita una solución alterna. Este será borrar el registro y agregar un reemplazo para este en su módulo.

Para mantener las cosas organizadas, creara un archivo security/todo_access_rules.xml y agregara lo siguiente:

<?xml version="1.0" encoding="utf-8"?>
    <openerp>
        <data noupdate="1">
            <delete model="ir.rule" search="[('id''=',ref('todo_app.todo_task_user_rule'))]" />
            <record id="todo_task_per_user_rule" model="ir.rule">
                <field name="name">ToDo Tasks only for owner</field>
                <field name="model_id" ref="model_todo_task"/>
                <field name="groups" eval="[(4, ref('base.group_user'))]"/>
                <field name="domain_force">
                    ['|', ('user_id','in', [user.id,False]), ('message_follower_ids','in',[user.partner_id.id])]
                </field>
            </record>
        </data>
    </openerp>

Esto encuentra y elimina la regla de registro todo_task_user_rule del módulo todo_app, y crea una nueva regla de registro todo_task_per_user. El filtro de dominio que usa ahora hace la tarea visible para el usuario responsable user_id, para todo el mundo si el usuario responsable no ha sido definido (igual a False), y para todos los seguidores. La regla se ejecutará en un contexto donde el usuario este disponible y represente la sesión del usuario actual. Los seguidores son socios, no objetos User, así que en vez de user_id, necesita usar user.partner_id.id.

Como de costumbre, no debe olvidar agregar el archivo nuevo al archivo descriptor __openerp__.py en el atributo "data":

'data': [
    'todo_view.xml',
    'security/todo_access_rules.xml'
],

Note que en la actualización de módulos, el elemento <delete> arrojará un mensaje de advertencia, porque el registro que será eliminado no existe más. Esto no es un error y la actualización se realizará con éxito, así que no es necesario preocuparse por esto.

Resumen

Ahora debe ser capaz de crear módulos nuevos para ampliar los módulos existentes. Vio como ampliar el módulo To-Do creado en los capítulos anteriores.

Se agregaron nuevas características en las diferentes capas que forman la aplicación. Amplio el modelo Odoo para agregar campos nuevos, y amplié los métodos con su lógica de negocio. Luego, modifique las vistas para hacer disponibles los campos nuevos. Finalmente, aprendió como ampliar un modelo heredando de otros modelos, y use esto para agregar características de red social a su aplicación.

Con estos tres capítulos, tiene una vista general de las actividades mas comunes dentro del desarrollo en Odoo, desde la instalación de Odoo y configuración a la creación de módulos y extensiones.

Los siguientes capítulos se enfocarán en áreas específicas, la mayoría de las cuales ha tocado en estos primeros capítulos. En el siguiente capítulo, abordara la serialización de datos y el uso de archivos XML y CSV con más detalle.