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&#x142;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.