Struktura systemu Odoo
ORM
Zmodyfikujmy nieco nasz przykład z poprzedniego rozdziału:
from contextlib import closing
# UWAGA! Przykład niepoprawny!!!!!
connection=odoo.sql_db.db_connect('e14p')
with closing(connection.cursor()) as cr:
cr.execute('select login name from res_users')
for (login,name) in cr.fetchall():
print('login=%s, name=%s' % (login, name))
Dlaczego to nie zadziała?
Przeglądając model res.users widzimy, że pole name jest dostępne (/web?&debug=#id=91&view_type=form&model=ir.model&menu_id=29&action=14).
W tabeli bazy danych res_users nie ma jednak takiej kolumny.
Definicję modelu odnajdujemy w pliku addons/base/res/res_users.py. Widzimy w nim:
name = fields.Char(related='partner_id.name', inherited=True)
Parametry funkcji Char mówią nam o tym, że tak naprawdę model udostępnia nazwę (name) z tabeli powiązanej kluczem partner_id.
Model Odoo nie jest więc prostym pośrednikiem ORM między tabelą a strukturą obiektową. Potrafi obsługiwać pola z tabel powiązanych (na przykład takich jak wyżej) oraz pola "wirtualne", których wartość jest obliczana programowo ("compute").
Poprawiony przykład powyżej:
users=env['res.users'].search([])
for user in users:
print('login=%s, name=%s' % (user.login, user.name))
Dziedziczenie
Dla przedstawienia mechanizmu dziedziczenia w Odoo posłużymy się prostym przykładem stworzonym w bazie danych o nazwie "edu". Zbudujmy moduł struktura składający się z 4 plików:
__manifest__.py
# -*- coding: utf-8 -*-
{
'name': 'demo1-struktura',
'data': [ 'views.xml' ],
'installable': True,
'auto_install': False,
'application': True,
}
__init__.py
import modele
modele.py
from odoo import fields, models
class DemoModel1(models.Model):
_name = 'demo1.model1'
pole1 = fields.Char(string="Pierwsze pole")
class DemoModel2(models.Model):
_inherit = 'demo1.model1'
_name = 'demo1.model2'
pole2 = fields.Char(string="Pole dodane")
views.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record model="ir.ui.view" id="struktura.widok1">
<field name="name">Demo - widok1</field>
<field name="model">demo1.model1</field>
<field name="arch" type="xml">
<form string="Przykład">
<sheet>
<group>
<field name="pole1"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="struktura.widok2">
<field name="name"></field>
<field name="model">demo1.model2</field>
<field eval="10" name="priority"/>
<field name="inherit_id" ref="struktura.widok1"/>
<field name="arch" type="xml">
<field name="pole1" position="after">
<field name="pole2"/></field>
</field>
</record>
<record model="ir.actions.act_window" id="struktura.otworz1">
<field name="name">demo1.otworz1 - 1 pole</field>
<field name="res_model">demo1.model1</field>
<field name="view_type">form</field>
</record>
<record model="ir.actions.act_window" id="struktura.otworz2">
<field name="name">demo1.otworz2 - 2 pola</field>
<field name="res_model">demo1.model2</field>
<field name="view_type">form</field>
</record>
<menuitem name="Demo" id="menu_demo1" sequence="100" action='struktura.otworz1' parent="" />
<menuitem name="Demo - model1" id="menu_demo1_pole1" sequence="100"
action='struktura.otworz1' parent="menu_demo1" />
<menuitem name="Demo - model2" id="menu_demo1_pole2" sequence="200"
action='struktura.otworz2' parent="menu_demo1" />
</data>
</odoo>
Plik XML zawiera rekordy danych zapisywane do tabel określonych w znaczniku record atrybutem model. Czyli odpowiednio: ir.ui.view (widoki) ir.actions.act_window (akcje okna) i ir.ui.menu (menu).
Sprawdźmy co zapisało się w bazie danych:
$ sudo -u odoo psql edu
psql (9.6.6)
Type "help" for help.
edu=# \pset format wrapped
Output format is wrapped.
edu=# select id,arch_db from ir_ui_view where model='demo1.model1';
id | arch_db
-----+---------------------------------------------------
908 | <?xml version="1.0"?> +
| <form string="Przykład"> +
| <sheet> +
| <group> +
| <field name="pole1"/>+
| </group> +
| </sheet> +
| </form> +
|
(1 row)
edu=# select id,arch_db from ir_ui_view where model='demo1.model2';
id | arch_db
-----+---------------------------------------------------
909 | <?xml version="1.0"?> +
| <field name="pole1" position="after"> +
| <field name="pole2"/></field>+
|
(1 row)
edu=# select id,view_id,name from ir_act_window where res_model='demo1.model1';
id | view_id | name
-----+---------+------------------------
331 | | demo1.otworz1 - 1 pole
(1 row)
edu=# select id,view_id,name,domain,context from ir_act_window where res_model='demo1.model1';
id | view_id | name | domain | context
-----+---------+------------------------+--------+---------
331 | | demo1.otworz1 - 1 pole | | {}
(1 row)
edu=# select id,view_id,name,domain,context from ir_act_window where res_model='demo1.model2';
id | view_id | name | domain | context
-----+---------+------------------------+--------+---------
332 | | demo1.otworz2 - 2 pola | | {}
(1 row)
edu=# select id,parent_id,sequence,action,name from ir_ui_menu where name like 'Demo%';
id | parent_id | sequence | action | name
-----+-----------+----------+---------------------------+---------------
232 | 231 | 100 | ir.actions.act_window,331 | Demo - model1
233 | 231 | 200 | ir.actions.act_window,332 | Demo - model2
231 | | 100 | ir.actions.act_window,331 | Demo
(3 rows)
To samo możemy podglądnąć w trybie deweloperskim ze środowiska Odoo (w module Ustawienia, menu "Techniczne").
Widzimy więc, że struktury z plików XML są zapisywane do tabel bazy danych i z tych danych Odoo buduje odpowiednie widoki (przechowywane w cache). Wykonanie odświeżenia (update) modułu umożliwia naniesienie zmian w tych strukturach.
Każdej strukturze odpowiada jeden wiersz w tabeli (o nazwie rozpoczynającej się od ir_).
Natomiast zmiany w kodzie (Python) wymagają dodatkowo (przed update) restartu serwera.
Dziedziczenie widoków (<field name="inherit_id" ref=......) umozliwia dodanie do widoku / formularzy pól, W naszym przykładzie są odpowiednio widoczne dwa lub jedno pole.
W bazie danych uzyskamy dwie tabele!
edu=# select * from demo1_model2;
id | create_uid | create_date | write_date | write_uid | pole2 | pole1
----+------------+----------------------------+----------------------------+-----------+--------+--------
1 | 1 | 2017-12-13 04:04:27.886973 | 2017-12-13 04:04:27.886973 | 1 | pole1 | poe2
2 | 1 | 2017-12-13 04:05:16.800152 | 2017-12-13 04:05:16.800152 | 1 | drugi1 | drugi2
(2 rows)
edu=# select * from demo1_model1;
id | create_uid | create_date | write_date | write_uid | pole1
----+------------+----------------------------+----------------------------+-----------+---------
1 | 1 | 2017-12-13 04:04:09.800131 | 2017-12-13 04:04:09.800131 | 1 | pole1
2 | 1 | 2017-12-13 04:06:06.826608 | 2017-12-13 04:06:06.826608 | 1 | tylko 1
(2 rows)
Wystarczy jednak usunąć pole _name z drugiego modelu, aby w miejsce dwóch tabel uzyskać jedną tabelę z dwoma polami:
modele.py
from odoo import fields, models
class DemoModel1(models.Model):
_name = 'demo1.model'
pole1 = fields.Char(string="Pierwsze pole")
class DemoModel2(models.Model):
_inherit = 'demo1.model'
pole2 = fields.Char(string="Pole dodane")
Oczywiście trzeba przy tym odpowiednio zmodyfikować nasze widoki. Dodamy przy okazji widok tabeli - by móc przeglądać wprowadzane wiersze:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record model="ir.ui.view" id="struktura.widok1">
<field name="name">Demo - widok1</field>
<field name="model">demo1.model</field>
<field name="arch" type="xml">
<form string="Przykład">
<sheet>
<group>
<field name="pole1"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="struktura.widok2">
<field name="name">Demo - widok2</field>
<field name="model">demo1.model</field>
<field eval="10" name="priority"/>
<field name="inherit_id" ref="struktura.widok1"/>
<field name="arch" type="xml">
<field name="pole1" position="after">
<field name="pole2"/></field>
</field>
</record>
<record model="ir.ui.view" id="struktura.widok_tab">
<field name="name">Demo - tabela</field>
<field name="model">demo1.model</field>
<field name="arch" type="xml">
<tree string="Tabela">
<field name="pole1"/>
<field name="pole2"/>
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="struktura.otworz1">
<field name="name">demo1.otworz1</field>
<field name="view_id" ref="struktura.widok1" />
<field name="res_model">demo1.model</field>
<field name="view_type">form</field>
</record>
<record model="ir.actions.act_window" id="struktura.otworz2">
<field name="name">demo1.otworz2</field>
<field name="view_id" ref="struktura.widok2" />
<field name="res_model">demo1.model</field>
<field name="view_type">form</field>
</record>
<record model="ir.actions.act_window" id="struktura.otworz3">
<field name="name">demo1.otworz3</field>
<field name="view_id" ref="struktura.widok_tab" />
<field name="res_model">demo1.model</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem name="Demo" id="menu_demo1" sequence="100" action='struktura.otworz3' parent="" />
<menuitem name="Demo - 1" id="menu_demo1_1" sequence="100" action='struktura.otworz1' parent="menu_demo1" />
<menuitem name="Demo - 2" id="menu_demo1_2" sequence="200" action='struktura.otworz2' parent="menu_demo1" />
<menuitem name="Demo - 3" id="menu_demo1_3" sequence="300" action='struktura.otworz3' parent="menu_demo1" />
</data>
</odoo>
Pewnym zaskoczeniem może być to, że zarówno akcja otworz1 jak i otworz2 otwiera widok z dwoma polami! Widok wiąże się bowiem z modelem. Jeśli otworzymy model do edycji - zostaną przetworzone wszystkie związane z nim widoki (w odpowiedniej formie). Mamy zatem jeden obiekt w bazie (tabelę) opisywany przez modele (powiązane przez dziedziczenie) i widoki składane także na drodze dziedziczenia. Widzimy więc, że wprowadzenie własnego mechanizmu dziedziczenia pozwala rozwijać aplikację drogą kolejnych ulepszeń - co jest jednym z największych atutów Odoo. Natomiast tradycyjne dziedziczenie Pythona pozwala na odnoszenie się do funkcjonalności zdefiniowanej w klasie przodka.