Segundo Capitulo - Creando un Modulo Nuevo

Construyendo un módulo de Odoo.-

Iniciar/Detener el servidor Odoo

Mosca: Este tutorial requiere tener instalado Odoo

Odoo utiliza una arquitectura de cliente/servidor en la cual los clientes son navegadores web accediendo al servidor de Odoo vía RPC.

La lógica del negocio y sus extensiones son, generalmente, ejecutadas del lado del servidor, a pesar de que soporta características del cliente (Ej. Representación de nuevos datos, tales como mapas interactivos) que pueden ser agregadas al cliente.

Para iniciar el servidor, simplemente invocamos el comando odoo.py en el intérprete de comandos, agregando la ruta absoluta del archivo si es necesario:

$ odoo.py

El servidor pudiera ser detenido presionando la combinación de teclas Ctrl+C simultáneamente desde el terminal, o matando el correspondiente proceso del sistema operativo.

Construir un Módulo de Odoo

Un módulo de Odoo puede contener varios elementos:

  • Objetos del Negocio

    Declarados como clases Python, esos recursos son automáticamente persistidos por Odoo basado en su configuración.

  • Archivos de Datos

    Los archivos XML y CSV declarando metadatos (vistas o flujos de trabajo), datos de configuración (módulos de parametrización), datos de demostración y mas.

  • Controladores Web

    Maneja peticiones de navegadores web

  • Datos Estáticos Web

    Archivos de Imágenes, CSS o JavaScript utilizados por la interfaz web o sitio web

Estructura del Módulo

Cada módulo es un directorio con un directorio módulo. Los directorios del módulo son especificados usando la opción --addons-path.

     Agarra Dato:
        la mayoría de los comandos del shell pueden ser 
        utilizados a través de un archivo de configuración

Un módulo de Odoo es declarado por su manifiest. Ver la documentación del manifiest (En inglés)

Un módulo es también un paquete Python con un archivo __init.py__, conteniendo importantes instrucciones para varios archivos Python en el módulo.

Para cada instancia, si el módulo tiene un solo archivo mymodule.py, el archivo __init.py__ podría contener:

from . import mymodule

Odoo provee un mecanismo de creación de ayuda para configurar un nuevo módulo odoo.py tiene un subcomando scaffold para crear un módulo vacío:

$ odoo.py scaffold <nombre del modulo> <donde lo vas a poner>

El comando crea un subdirectorio para tu módulo, y automáticamente crea un grupo de archivos estándar para un módulo. La mayoría de ellos sólo contiene código comentado o XML. El uso de la mayor parte de esos archivos los revisaremos mas adelante.


Ejercicio

    Creación de un módulo
    Usar la línea de comandos para crear un módulo vacío llamado "La Escuelita", y lo vamos a instalar en Odoo
  1. Ejecutamos el comando odoo.py scaffold escuelita addons.
  2. Adaptamos el archivo a nuestro módulo
  3. No jurungar los otros archivos
escuelita/__openerp__.py
# -*- coding: utf-8 -*-
{
    'name': "La Escuelita",

    'summary': """Gestionar Entrenamientos""",

    'description': """
        Módulos de La Escuelita para gestión de entrenamientos:
            - cursos
            - sesiones de entrenamiento
            - registro de asistentes
    """,

    'autor': "Avaloon",
    'sitio web': "http://avalon.com.ve/",

    # Las categorías pueden ser usadas para filtrar módulos a través de listados
    'category': Prueba',
    'version': '0.1',

    # cualquier módulo necesario para que trabaje correctamente
    'depends': ['base'],

    # siempre cargado
    'data': [
        # 'security/ir.model.accesos.csv',
        'templates.xml',
    ],
    # cargado solamente en modo de demostración
    'demo': [
        'demo.xml',
    ],
}
escuelita/__init__.py
    # -*- coding: utf-8 -*-
    from . import controllers
    from . import models
escuelita/controllers.py
    # -*- coding: utf-8 -*-
    from openerp import http

    # class escuelita(http.Controller):
    #     @http.route('/escuelita/escuelita/', auth='public')
    #     def index(self, **kw):
    #         return "Epa, panita"

    #     @http.route('/escuelita/escuelita/objects/', auth='public')
    #     def list(self, **kw):
    #         return http.request.render('escuelita.listing', {
    #             'root': '/escuelita/escuelita',
    #             'objects': http.request.env['escuelita.escuelita'].search([]),
    #         })

    #     @http.route('/escuelita/escuelita/objects/<model("escuelita.escuelita"):obj>/', auth='public')
    #     def object(self, obj, **kw):
    #         return http.request.render('escuelita.object', {
    #             'object': obj
    #         })
escuelita/demo.xml
<openerp>
    <data>
        <!--  -->
        <!--   <record id="object0" model="escuelita.escuelita"> -->
        <!--     <field nombre="nombre">Objeto 0</field> -->
        <!--   </record> -->
        <!--  -->
        <!--   <record id="objeto1" model="escuelita.escuelita"> -->
        <!--     <field nombre="nombre">Objeto 1</field> -->
        <!--   </record> -->
        <!--  -->
        <!--   <record id="objeto2" model="escuelita.escuelita"> -->
        <!--     <field nombre="nombre">Objeto 2</field> -->
        <!--   </record> -->
        <!--  -->
        <!--   <record id="object3" model="escuelita.escuelita"> -->
        <!--     <field nombre="nombre">Objeto 3</field> -->
        <!--   </record> -->
        <!--  -->
        <!--   <record id="objeto4" model="escuelita.escuelita"> -->
        <!--     <field nombre="nombre">Objeto 4</field> -->
        <!--   </record> -->
        <!--  -->
    </data>
</openerp>
escuelita/models.py
# -*- coding: utf-8 -*-

from openerp import models, fields, api

# class escuelita(models.Model):
#     _name = 'escuelita.escuelita'

#     name = fields.Char()
escuelita/security/ir.model.accesos.csv
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_escuelita_escuelita,escuelita.escuelita,model_escuelita_escuelita,,1,0,0,0
escuelita/templates.xml
<openerp>
    <data>
        <!-- <template id="listing"> -->
        <!--   <ul> -->
        <!--     <li t-foreach="objetos" t-as="objeto"> -->
        <!--       <a t-attf-href="{{ root }}/objetos/{{ object.id }}"> -->
        <!--         <t t-esc="objeto.mostrar_nombre"/> -->
        <!--       </a> -->
        <!--     </li> -->
        <!--   </ul> -->
        <!-- </template> -->
        <!-- <template id="objeto"> -->
        <!--   <h1><t t-esc="objeto.mostrar_nombre"/></h1> -->
        <!--   <dl> -->
        <!--     <t t-foreach="object._fields" t-as="field"> -->
        <!--       <dt><t t-esc="campo"/></dt> -->
        <!--       <dd><t t-esc="objeto[campo]"/></dd> -->
        <!--     </t> -->  
        <!--   </dl> -->
        <!-- </template> -->
    </data>
</openerp>

Mapeo Objeto-Relacional

Un componente clave de Odoo es la capa ORM. Esta capa evitar tener que escribir los SQL a mano, además de proveer extensibilidad y servicios de seguridad.

Los objetos del negocio son declarados como clases Python extendiéndose Model que se integra al sistema de persistencia automatizado.

Los modelos pueden ser configurados configurando un número de atributos en su definición. El atributo mas importante es _name el cual es requerido y define el nombre para el modelo en el sistema Odoo. Aquí tenemos una mínima definición completa de un modelo:

from openerp import models
class MinimalModel(models.Model):
    _name = 'test.model'

Campos del Modelo

Los campos son usados para definir que puede almacenar el modelo y dónde. Los campos son definidos como atributos en la clase model:

from openerp import models, fields

class LessMinimalModel(models.Model):
    _name = 'test.model2'

    name = fields.Char()

Atributos Comunes

Al igual que el modelo en si, sus campos se pueden configurar, pasando atributos de configuración como parámetros:

name = field.Char(required=True)

Algunos atributos están disponibles en todos los campos, aquí los mas comunes:

  • string (unicode, valor por defecto: nombre del campo)

    La etiqueta del campo en la Interfaz de Usuario (visible por los usuarios)

  • required (bool, valor por defecto: False)

    Si el valor es True, el campo no puede estar vacío, este debe tener un valor por defecto o siempre se le debe asignar un valor al momento de la creación del registro

  • help (unicode, valor por defecto: '')

    Provee al usuario un globo informativo en la interfaz gráfica

  • index (bool, valor por defecto: False)

    Solicitud que realiza Odoo para la creación de un índice sobre la columna

Campos Sencillos

Hay dos categorías de campos: "sencillos" los cuales almacenan valores atómicos directamente en las tablas del modelo y campos "relacionales", vinculando registros (del mismo modelo o de diferentes modelos)

Ejemplos de campos sencillos son: Boolean, Date, Char.

Campos Reservados

Odoo crea unos pocos campos en todos los modelos. Esos campos están administrados por el sistema y no deben ser escritos. Pueden ser leídos si ello es necesario.

  • id (Id)

    El identificador único para un registro en el modelo

  • create_date (Datetime)

    Fecha de creación de un registro

  • create_uid (Many2one)

    Usuario quien ha creado el registro

  • write_date (Datetime)

    Última fecha de modificación del registro

  • write_uid (Many2one)

    Usuario quien ha realizado la última modificación

Campos Especiales

Por defecto, Odoo requiere un campo name en todos los modelos para varias formas de presentación y comportamientos de búsqueda. El campo usado para esos propósitos puede ser sustituido configurando _rec_name


Ejercicio

Definir un modelo

Definir un nuevo modelo de datos Curso en el modulo escuelita. Un curso tiene un título y una descripción. Los cursos deben tener un título.

Editamos el archivo escuelita/models.py para incluir un Curso

escuelita/models.py
from openerp import models, fields, api

class Curso(models.Model):
    _name = 'escuelita.curso'

    nombre = fields.Char(string="Title", required=True)
    descripcion = fields.Text()

Archivos de Datos

Odoo está altamente orientado a los datos. Aunque el comportamiento se controla utilizando código Python, parte de los valores de los módulos se configuran al momento de la carga.

Agarra Dato
Algunos módulos existen únicamente para agregar datos en Odoo

Un módulo de datos es declarado vía archivos de datos, archivos XML con elementos tipo <record>. Cada elemento <record> crea o actualiza un registro de la base de datos.

<openerp>
    <data>
        <record model="{nombre del modelo}" id="{identificador del registro}">
            <field name="{un nombre para el campo}">{un valor}</field>
        </record>
    </data>
<openerp>
  • model es el nombre del modelo Odoo para el registro
  • id es un identificador externo, esto permite referir al registro (sin tener que conocer su identificador en la base de datos)
  • <field> elementos que tienen un name que es el nombre del campo en el modelo (Ej. Descripción). Su cuerpo es el valor del campo.

Los archivos de datos tienen que ser declarados en el archivo manifiest para ser cargados, pueden ser declarados en la lista data (siempre cargado) o en la lista demo (sólo cargado en modo de demostración).


Ejercicio

Definir datos de demostración

Crear datos de demostración llenando el modelo Cursos con algunos datos de demostración.

Editar el archivo *escuelita/demo.xml* para incluir algunos datos

escuelita/demo.xml
<openerp>
    <data>
        <record model="escuelita.curso" id="curso0">
            <field name="nombre">Curso 0</field>
            <field name="descripción">Descripción del curso 0
            que puede tener
            múltiples líneas
            </field>
        </record>
        <record model="escuelita.curso" id="curso1">
            <field name="nombre">Curso 1</field>
            <!-- Sin descripción por el momento -->
        </record>
        <record model="escuelita.curso" id="curso2">
            <field name="nombre">Curso 2</field>
            <field name="descripción">Descripcion del Curso 2</field>
        </record>
    </data>
</openerp>

Acciones y Menús

Acciones y menús son registros regulares en la base de datos, usualmente declarados mediante archivos de datos. Las acciones pueden ser disparadas de tres maneras:

  • Haciendo clic en el menú de items (vinculado a acciones específicas)
  • Haciendo clic en los botones de las vistas (si ellos están conectados a acciones)
  • Como acciones contextuales sobre objetos

Los menús son algo complejos de declarar, hay un atajo <menuiten> para declarar un ir.ui.menu y conectarlo a la correspondiente acción mas fácilmente.

<record model="ir.actions.act_window" id="action_list_ideas">
    <field name="nombre">Ideas</field>
    <field name="modelo">idea.idea</field>
    <field name="modo_vista">tree,form</field>
</record>
<menuitem id="menu_ideas" parent="menu_root" name="Ideas" sequence="10"
    action="action_list_ideas"/>