Creando consultas en Odoo
De por si Odoo brinda muy buenas herramientas para el análisis de datos. Las vistas tipo lista (con la posibilidad de agrupar registros) junto con las vistas tipo pivot, brindan al usuario funcionalidades para consultar datos out-of-the-box
Igual estas vistas tienen sus limitaciones. Por lo pronto, sus columnas están limitadas a las columnas que definió el desarrollador en la vista. Pero por sobre todo, no permite relacionar la información de dos modelos (por ejemplo clientes con el modelo res.partner junto con pedidos de venta con el modelo sale.order). Otra de las limitaciones es relativa a la performance. Las vistas pivot son muy lindas, ahora sus cubos se resuelven todos en la memoria del navegador de internet. Lo que significa que si uno realiza una consulta contra un modelo que tiene miles de lineas (como tranquilamente puede ser un año de ventas); la misma no se va a poder resolver debido a los límites de procesamiento que tiene el navegador de internet.
Creando una vista en Odoo que refleja una vista SQL
Como resolver estas limitaciones? Es desarrollando módulos que implementen modelos con dichas consultas. Dichos modelos se mapearán a vistas de SQL. Lo cual no es muy dificil, solo hay que saber utilizar bien el SQL. Esta técnica es muy popular en Odoo, ya que muchos informes que se brindan con el core crean vistas de SQL que luego se muestran mediante vistas tipo lista o pivot. Otro buen ejemplo lo van a encontrar en el módulo account_debt_management, donde se mapea los asientos contables a una vista que permite navegar la cuenta corriente de un cliente/proveedor.
Ahora vamos a crear una consulta a modo de ejemplo. Supongamos que queremos saber que productos se venden por provincia y ciudad. Esa es una consulta no provista por default por Odoo; entonces como hacemos?
El primer paso es crear un nuevo módulo, supongamos que se va a llamar a2_query_sales
mkdir a2_query_sales
cd a2_query_sales
Luego con nuestro editor favorito, vamos a crear el archivo de manifiesto con los siguientes contenidos:
{
"name": "a2 query sales",
"version": "15.0.1.0.0",
"license": "AGPL-3",
"depends": ["sale","sales_team"],
"category": "Sale",
"data": [
'a2_sales.xml', 'security/ir.model.access.csv',
]
}
Como verán, no tiene grandes secretos. Solo depende de dos módulos (sale y sale_team), y declara el uso de un solo archivo donde declararemos las vistas, a2_sales.xml. Tambien declara el archivo desde donde importamos las definiciones de seguridad (ir.model.access.csv). Paso siguiente, creamos un archivo para inicializar el módulo (__init__.py)
from . import models
En donde indicamos que Odoo debe cargar el archivo de Python models. En ese archivo crearemos el nuevo modelo, el cual se va a llamar a2.sales y por lo pronto vamos a definir los siguientes contenidos:
from odoo import tools, models, fields, api, _
class A2Sales(models.Model):
_name = "a2.sales"
_description = "A2 Sales"
_auto = False
partner_id = fields.Many2one('res.partner','Cliente')
city = fields.Char('Ciudad')
state_id = fields.Many2one('res.country.state','Provincia')
product_id = fields.Many2one('product.product','Producto')
product_uom_qty = fields.Float('Cantidad')
Lo que hacen estas líneas es; primero crean una nueva clase e inmediatamente setean su atributo _auto a False. De esta manera al instalarse el módulo, no se creará en la base de datos la tabla con las columnas.
Seguidamente, tenemos que hacer la consulta SQL con la que crearemos la vista. La idea era consultar por cliente, provincia y ciudad que productos se vendían. La misma se puede resolver con el siguiente query de SQL
select sol.id,so.partner_id,pa.city,pa.state_id,sol.product_id,sol.product_uom_qty from sale_order_line sol inner join sale_order so on so.id = sol.order_id inner join res_partner pa on pa.id = so.partner_id
Como podemos ver, por cada columna del query se mapea un atributo del modelo. Se respetan los tipos de campo de los modelos originales, así en las vistas tienen el comportamiento correcto. Hay que indicar que hay una columna extra al principio con un ID, en donde indicamos una columna única de ID de la consulta. Si no lo hacemos, Odoo no va a poder mostrar los resultados de las consultas en sus vistas.
Seguidamente, tenemos que definir el método init en el modelo. El método init es el método que se invocará cada vez que el modelo es inicializado en memoria. En este método, crearemos la vista SQL donde tendremos los resultados de la consulta
def init(self):
tools.drop_view_if_exists(self._cr, self._table)
query = """
select sol.id,so.partner_id,pa.city,pa.state_id,sol.product_id,sol.product_uom_qty
from sale_order_line sol
inner join sale_order so on so.id = sol.order_id
inner join res_partner pa on pa.id = so.partner_id
"""
self.env.cr.execute("""CREATE or REPLACE VIEW %s as (%s)""" % (self._table, query))
En estas líneas, se puede ver que cada vez que el modelo se inicializa (o sea cada vez que Odoo se reinicia) la vista SQL es recreada en la base de datos. Automaticamente Odoo mapea cada una de las columnas de la vista al modelo definido previamente.
Ya finalizamos con la definición del modelo, ahora el paso siguiente es declarar sus permisos de seguridad así lo pueden usar los usuarios. Para ello crearemos el archivo security/ir.model.access.csv con el siguiente contenido
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_a2_sales,a2.sales,model_a2_sales,sales_team.group_sale_salesman,1,1,1,1
En el cual se indica que el modelo recientemente creado, va a poder ser accedido por todos los vendedores (no hay necesidad de complicarlo más con las reglas de seguridad, eso se puede dejar para otro momento).
Bien, seguidamente hay que definir un menú, una acción y dos vistas (una de tipo tree y otra del tipo pivot) para mostrar los resultados del modelo. Para ello creamos un nuevo archivo a2_sales.xml donde crearemos el menuitem, el action y las dos views.
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="a2_sales_view_pivot" model="ir.ui.view">
<field name="name">a2.sales.view.pivot</field>
<field name="model">a2.sales</field>
<field name="arch" type="xml">
<tree string="A2 Sales">
<field name="partner_id" type="row"/>
<field name="state_id" type="row"/>
<field name="city" type="row"/>
<field name="product_id" type="col"/>
<field name="product_uom_qty" type="measure"/>
</tree>
</field>
</record>
<record id="a2_sales_view_tree" model="ir.ui.view">
<field name="name">a2.sales.view.tree</field>
<field name="model">a2.sales</field>
<field name="arch" type="xml">
<tree string="A2 Sales">
<field name="partner_id" />
<field name="state_id" />
<field name="city" />
<field name="product_id" />
<field name="product_uom_qty" />
</tree>
</field>
</record>
<record id="action_a2_sales" model="ir.actions.act_window">
<field name="name">A2 Sales</field>
<field name="res_model">a2.sales</field>
<field name="view_mode">tree,pivot</field>
</record>
<menuitem id="menu_a2_sales"
name="A2 Sales"
parent="sale.menu_sale_report"
sequence="10" action="action_a2_sales"
groups="sales_team.group_sale_salesman"/>
</odoo>
Como podemos ver; primero definimos el menuitem que va a pender del menu "Reporting" en el modulo sale. Luego declaramos la acción en donde indicamos que va a poder ver las vistas en dos modos: tree y pivot. Y seguidamente declaramos las dos vistas.
Bueno, finalmente instalamos el módulo y refrescamos el navegador. Podremos apreciar que en el menu Reporting de Ventas hay un nuevo menu A2 Sales.
Si lo seleccionamos podremos apreciar la vista tipo lista con los contenidos de la consulta
Y la vista tipo pivot.
Esos son los pasos para crear una consulta que muestre los resultados de una vista de SQL. Si quieren ver el código, lo pueden ver en el repositorio a2_query_sales de ctmil.
Por último, a partir de la versión 14 (a partir de este PR) ya no es necesario definir una vista para hacer las consultas. Solo se necesita reemplazar el método init por un método llamado _table_query en el cual se define el SQL a ejecutar (sin necesidad de crear una vista). Lo que haría que el método init en el ejemplo, sea reemplazado por:
@property
def _table_query(self):
query = """
select sol.id,so.partner_id,pa.city,pa.state_id,sol.product_id,sol.product_uom_qty
from sale_order_line sol
inner join sale_order so on so.id = sol.order_id
inner join res_partner pa on pa.id = so.partner_id
"""
return query
Acerca de:
Gustavo Orrillo
Passionate about programming, he has implemented Odoo for different types of businesses since 2010. In Moldeo Interactive he is a founding Partner and Programmer; In addition to writing on the Blog about different topics related to the developments he makes.