Comencemos con un pequeño modelo de ejemplo, el cual vamos a visualizar en diferentes variantes de formato (Vistas).
Paso 1
Teniendo en cuenta la estructura sugerida para una app en Odoo, dentro de la carpeta models, crea un fichero book.py para definir la clase TBook:
from odoo import fields, models, api
# Heredamos de models.Model para crear clases persistentes (tema para otra ocasión).
class TBook(models.Model):
_name = 'tutorial.book'
_description = 'Book Description'
_inherit = ['mail.thread', 'mail.activity.mixin']
name = fields.Char('Name')
is_classic = fields.Boolean('Is classic?', default=False)
description = fields.Text('Description')
publish_date = fields.Date('Publish date')
pages_count = fields.Integer('Pages Count', default=1)
state = fields.Selection([('available', 'Available'), ('borrowed', 'Borrowed')], 'State', default='available')
author = fields.Char('Author(s) name')
Paso 2
En la carpeta views, crea un fichero book.xml. Comenzaremos por la vista form.
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="book_form_view" model="ir.ui.view">
<field name="name">tutorial.book.form</field>
<field name="model">tutorial.book</field>
<field name="arch" type="xml">
<form string="Books">
<header>
<button name="borrow" type="object" string="Borrow book" class="btn btn-primary" states="available"/>
<button name="return_book" type="object" string="Return book" class="btn btn-primary" states="borrowed"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="e.g. Utopia" required="1"/>
</h1>
</div>
<group colspan="4">
<group col="2">
<field name="publish_date"/>
<field name="author" required="1"/>
</group>
<group col="2">
<field name="pages_count"/>
<field name="is_classic"/>
</group>
</group>
<notebook>
<page name="description" string="Description">
<group colspan="4">
<field name="description" nolabel="1"/>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"
help="Follow this book to automatically track its state."
groups="base.group_user"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
</odoo>
Puedes ver en la siguiente imagen a qué sección corresponde cada parte del código anterior separado por colores.
Para organizar y diseñar mejor los elementos de la vista puedes auxiliarte de widgets, acá te dejo un enlace para que veas algunos en acción WIDGETS EN ODOO
A continuación ‘dibujamos’ nuestra vista tree para el modelo ‘tutorial.book’, en este caso lo incluiremos luego del <record> que contiene la vista form que hicimos anteriormente
<record id="book_tree_view" model="ir.ui.view">
<field name="name">tutorial.book.tree</field>
<field name="model">tutorial.book</field>
<field name="arch" type="xml">
<tree string="Tutorial Book"
decoration-muted="state=='borrowed'" decoration-info="state=='available'">
<field name="name"/>
<field name="publish_date"/>
<field name="author"/>
<field name="pages_count"/>
<field name="is_classic"/>
<field name="state"/>
</tree>
</field>
</record>
Dentro de la etiqueta <tree></tree>, vamos listando los campos (que se mostrarán en forma de columnas) para diseñar nuestro listado. En ocasiones es muy útil que nuestro listado nos ayude a ‘hacer muy obvia’ la información que nos está mostrando. Para ello podemos hacer uso de alguna escala de colores asignada según algún criterio. Por ejemplo, en este ejemplo que les muestro, he asignado el color Azul a los libros Disponibles, y el color Gris a los libros Prestados. Es importante que el campo que se use como condicional se incluya entre los campos listados dentro de la etiqueta tree, si no es un campo que quieres mostrar, inclúyelo pero con invisible=’1’, de esta forma lograrás ambos objetivos.
Para ver con más detalle cómo aplicar colores a las líneas de la vista tree, visita el siguiente enlace Cómo aplicar colores en la vista tree de odoo
Si tenemos una vista tree o de Listado, lo más seguro es que eventualmente necesitemos aplicar búsquedas o agrupadores sobre dicho listado. Por ello vamos a generar una vista search o de Búsqueda.
A continuación de nuestro <record> para la vista tree en el archivo book.xml, vamos a escribir el siguiente código:
<record id="book_search_view" model="ir.ui.view">
<field name="name">tutorial.book.search</field>
<field name="model">tutorial.book</field>
<field name="arch" type="xml">
<search string="Tutorial Book Search">
<field name="name" string="Book name"/>
<field name="author" string="Book author name"/>
<field name="publish_date" string="Book publish date"/>
<filter domain="[('state','=','borrowed')]" string="Borrowed" name="borrowed"/>
<filter domain="[('state','=','available')]" string="Available" name="available"/>
<group expand="1" string="Group By">
<filter string="Classics" name="is_classic" domain="[]"
context="{'group_by':'is_classic'}"/>
<filter string="Publish date" name="publish_date" domain="[]"
context="{'group_by':'publish_date'}"/>
<filter string="State" name="state" domain="[]"
context="{'group_by':'state'}"/>
</group>
</search>
</field>
</record>
Esta vista nos permite generar filtros y agrupadores de una vez. Dentro de las etiquetas <search></search>, aquellas que son <field name="x_field_name"/> me van a permitir digitar un criterio de búsqueda, y por cada caracter que digite actualizará el resultado. Por eso en esta sección sugiero incluir campos como nombres, descripciones o algún tipo de texto dentro del formulario. También permite un tipo de búsqueda más ‘atómica’, donde el usuario solo presiona un enlace y de una vez aparecen los registros que cumplan con esa condición, por ejemplo:
<filter domain="[('state','=','borrowed')]" string="Borrowed" name="borrowed"/>
Al presionar sobre el filtro Borrowed, automáticamente se mostrarán sólo aquellos libros que están en estado Borrowed, pues el filtro se aplica sobre el valor del campo state.
Y por último tenemos los agrupadores, que se mostrarán en un menú diferente de los filtros.
<group expand="1" string="Group By">
<filter string="Classics" name="is_classic" domain="[]"
context="{'group_by':'is_classic'}"/>
</group
Acá debemos usar campos que sabemos nos aportarán información valiosa. Por ejemplo, categorías, estados, etc. Para que de forma rápida el sistema pueda mostrarme cuántos registros pertenecen a una misma categoría o tienen el mismo estado. Pero no nos arrojaría mucha información incluir un agrupador que use un campo único, como una secuencia, o tal vez un nombre o identificador (por razones obvias).
Paso 3
Definiremos entonces el action y el menú. Vamos a utilizar un action para especificar cuáles de esas vistas se quiere mostrar. Es posible que yo quiera mostrar los registros de mi modelo desde lugares diferentes, desde un menú quiero que se muestren ciertas vistas y desde otro quiero mostrar vistas diferentes o en un orden diferente. Desde el action podemos controlar esta situación.
<record id="book_act_window" model="ir.actions.act_window">
<field name="name">Books</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">tutorial.book</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
There is no book added yet. Click here to add a new Book.
</p>
</field>
</record>
En este segmento de código: <field name="view_mode"></field> podemos especificar qué vistas mostrar y en qué orden. En este caso solo tenemos vista form y tree, pero si necesitamos vistas kanban, tree, form y calendar se escrbiría así:
<field name="view_mode"> kanban,tree,form,calendar</field>.
Para controlar qué mensaje quiero que se muestre al usuario cuando la búsqueda no arroje resultados (o cuando aún no se ha registrado información), se utiliza el atributo help:
<field name="help" type="html">
<p class="oe_view_nocontent_create">
There is no book added yet. Click here to add a new Book.
</p>
</field>El caso de la vista form se mostrará al presionar un registro y la vista search será visible en la esquina superior derecha de la pantalla.
Menuitems
Creamos nuestra entrada de menú, para que al presionarla, el sistema nos muestre estas vistas que acabamos de crear.
<menuitem name="Library" id="library_root_menu" sequence="1" web_icon="tutorial_create_views,/static/description/icon.jpg"/>
Creamos un menú global que nos permita el acceso a una nueva app o funcionalidad (Que se muestre en el menú general). Si estoy usando un tema donde visualice los íconos de cada app, este ícono se debe especificar en el atributo web_icon. A este tipo de menú, como práctica personal me gusta ponerle la palabra root en el nombre para localizarlo con facilidad en tiempos de soporte y/o estrés ;)
<menuitem name="Books" id="books_menu" parent="library_root_menu" action="book_act_window"/>
Creamos entonces un submenú que debe tener como parent, el menú externo anterior para que se muestre ‘dentro’ del mismo.Si quiero que estas vistas se incluyan dentro de otro módulo, pues debo especificar el id del menú correspondiente del otro módulo en el atributo parent. En este punto también ponemos la referencia al action que hemos creado para ‘llamar’ a nuestras vistas.
Paso 4
Ahora debemos conceder los permisos apropiados a los usuarios. Creamos un archivo ir.model.access.csv, con la siguiente estructura.
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_book","access_books","model_tutorial_book","base.group_user",1,1,1,1
En este caso hemos concedido full access al usuario group_user para acceder al modelo tutorial.book.
Paso 5
Ordenamos nuestro __manifest__.py
{
'name': "Custom Library App",
'summary': """
Books description
""",
'description': """
Books description
""",
'author': "Konodoo",
'website': "https://www.konodoo.com",
'version': "12.0.1.0.0",
'depends': ['mail'],
'data': ['security/ir.model.access.csv',
'views/book.xml'],
'license': "AGPL-3",
'installable': True,
'application': True,
}
Para ver con más detalles las características del fichero __manifest__.py, visita el siguiente enlace Estructura del __manifest__.py en Odoo
Solo te falta poner tu módulo (carpeta) en el addons_path de tu proyecto. Recuerda actualizar la lista de aplicaciones para que aparezca el nuevo módulo y puedas instalarlo.
(Continuará….)