Modele - powiązania
Dla zaprezentowania powiązań między modułami stwórzmy dwie klasy (moduł example_rel):
class Demo1Model(models.Model):
_name = 'demo.demo1ex'
name = fields.Char('Nazwa z demo1ex', required=True)
link_dem2ex = fields.Many2one(comodel_name='demo.demo2ex',
string='Dla relacji one2many')
class Demo1Model(models.Model):
_name = 'demo.demo2ex'
name = fields.Char('Nazwa 2', required=True)
pole_one2many = fields.One2many(comodel_name='demo.demo1ex',
inverse_name = 'link_dem2ex',
string='Pole one2many (zbior rekordow z demo1ex)')
pole_many2one = fields.Many2one(comodel_name='demo.demo1ex',
string='Many2one - moze wskazywac na 1 rekord z demo1ex')
pole_many2many = fields.Many2many(comodel_name='demo.demo1ex',
string='many2many - relacje miedzy tabelami')
pole_many2many_r = fields.Many2many(comodel_name='demo.demo1ex',
relation='deno1demo2ex',
string='many2many nazwane')
Uwaga!
Nazwa pola podana w inverse_name musi (pole One2many) się odnosić do pola istniejącego i zadeklarowanego jako Many2one. Zła deklaracja może powodować trudne w zlokalizowaniu błędy (niestety Odoo tego nie kontroluje).
Ekrany do edycji danych:
<record model="ir.ui.view" id="example_rel.demo1ex">
<field name="name">Demo1ex</field>
<field name="model">demo.demo1ex</field>
<field name="arch" type="xml">
<form string="Przykład - powiazania">
<sheet>
<group>
<field name="name"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="example_rel.demo2ex">
<field name="name">Demo2ex</field>
<field name="model">demo.demo2ex</field>
<field name="arch" type="xml">
<form string="Przykład - powiazania">
<sheet>
<group>
<field name="name"/>
</group>
<group>
<field name="pole_one2many"/>
</group>
<group>
<field name="pole_many2one"/>
</group>
<group>
<field name="pole_many2many"/>
</group>
</sheet>
</form>
</field>
</record>
Wpisując dane, zauważamy na czym polega różnica między zachowaniami różnego rodzaju pól.Pole one2many pozwala tworzyć powiązanie z wieloma rekordami z tabeli podrzędnej (demo1ex). Pole Many2one pozwala wybrać jeden rekord z tabeli podrzędnej. Many2many zaś dowolną ilość rekordów.
Zobaczmy jak te dane zostały zapisane w bazie danych:
edu=# select * from demo_demo1ex;
id | create_uid | create_date | name | write_uid | link_dem2ex | write_date
----+------------+----------------------------+----------+-----------+-------------+----------------------------
1 | 1 | 2017-12-26 04:05:25.383331 | trzeci | 1 | | 2017-12-26 04:05:25.383331
2 | 1 | 2017-12-26 04:05:32.894388 | pierwszy | 1 | 1 | 2017-12-26 04:05:32.894388
3 | 1 | 2017-12-26 04:05:32.894388 | drugi | 1 | 1 | 2017-12-26 04:05:32.894388
(3 rows)
edu=# select * from demo_demo2ex;
id | create_uid | name | write_uid | write_date | pole_many2one | create_date
----+------------+------+-----------+----------------------------+---------------+----------------------------
1 | 1 | test | 1 | 2017-12-26 04:06:12.791172 | 1 | 2017-12-26 04:05:32.894388
(1 row)
edu=# select * from demo_demo1ex_demo_demo2ex_rel;
demo_demo2ex_id | demo_demo1ex_id
-----------------+-----------------
1 | 3
1 | 2
1 | 1
(3 rows)
Przede wszystkim trzeba zwrócić uwagę na różnice między tym co zostało zadeklarowane w programie, a ty co zapisał Odoo w bazie danych. Dodany zostały klucze główne (Id) oraz dane o zmianach (kto i kiedy). Pojawia się też dodatkowa tabela na zapamiętywanie relacji many2many (demo_demo1ex_demo_demo2ex_rel).
Uwaga!
Standardowo możliwość zmiany zawartości pola z powiązaniami wymaga uprawnień dostępu do modyfikacji także tabeli z której je wybieramy. Aby uzyskać, trzeba użyć kontrolek (widget - zob. https://odooforbeginnersblog.wordpress.com/2017/03/09/widgets-in-odoo) na przykład many2many_tags.
W modelu podrzędnym możemy zdefiniować własność _rec_name
- ona będzie wyświetlana jako opis wybranej wartości. Na przykład:
class SlownikModel(models.Model):
_name = 'demo.slownik'
_rec_name = 'opis'
kod = fields.Char('Identikator')
opis = fields.Char('Opis')
Operacje na listach w polach relacyjnych
Zbiór rekordów wskazywany w polu Many2many może być zmieniany programowo przy pomocy "komend" oznaczanych cyfrowo (https://www.odoo.com/documentation/8.0/reference/orm.html#fields\:
(0, _, values) - dodaje do zbioru rekord
(1, id, values) - zmienia rekord o identyfikatorze id
(2, id, _) - usuwa rekord id ze zbioru i z bazy danych
(3, id, _) - usuwa tylko ze zbioru rekordów
(4, id, _) - dodaje istniejący w bazie rekord
(5, _, _) - usuwa wszystkie rekordy
(6, _, ids) zamnienia wszystkie rekordy na nowy zbiów
Przykład:
def _default_user_ids(self):
return [(6, 0, [self._uid])] # domyślnie lista z aktualnym użytkownikiem
user_ids = fields.Many2many(
comodel_name='res.users',
relation='tab_relacji',
column1='wybrani_id',
column2='user_id',
string='Użytkownicy',
default=_default_user_ids)
Przykład (inny) w XML:
<field eval="[(6, 0, [ref('res_partner_category_13'),
ref('res_partner_category_12')])]" name="category_id"/>
Dostęp do pól modelu podrzędnego
Zadeklarowanie pola jako klucza obcego (Many2one) powoduje automatyczne odczytywanie danych z modelu podrzędnego.
Trzeba zatem zwrócić uwagę, że często pole klucza obcego nie reprezentuje wartości tego klucza (liczby) ale obiekt (model podrzędny). Gdyby na na przykład pole modelu partner_id
zadeklarować jako liczbę (integer) reprezentującą klucz do tabeli respartner, to wartość zmiennej będzie wartością klucza (self.partner_id
). Jeśli użyjemy Many2one, to oczywiście pole to nie jest w modelu liczbą (wartością klucza) - choć tak pamiętane jest w bazie danych! Jest to obiekt - rekord modelu podrzędnego. Wartość klucza (liczbę) uzyskujemy pisząc: self.partner_id.id
.
Pola obliczeniowe
Wartości pól obliczeiowych nie są związane wprost z zawartoscią bazy danych, ale mogą być liczone programowo.
Pokazuje to poniższy przykład:
wartosc = fields.Float(string="wartosc...")
tysiace = fields.Float(string="w tysiacach",compute='compute_tys')
@api.depends('wartosc')
def compute_tys(self):
self.tysiace = float(self.wartosc) / 1000
Możliwe jest stworzenie jednej funkcji obliczającej wiele wartości. Służy do tego dekorator api.multi.
Przykład:
@api.multi
@api.depends('field.relation', 'an_otherfield.relation')
def _amount(self):
for x in self:
x.total = an_algo
x.untaxed = an_algo
Powiązania dynamiczne
Czasem chcemy w obrębie modelu mieć dostęp do danych, które wprost nie wynikają z zapamiętanych powiązań. Na przykład na ekranie edycji możemy chcieć wyszukać dane z tabeli partnerów. Stwórzmy pole "sel" w którym będziemy wpisywać klucz wyszukiwań oraz pole typu Many2many - powiązane z tabelą res.partners.:
class DemoModel(models.Model):
_name = 'demo.mdemo'
sel = fields.Char('Wzór', default='')
partners = fields.Many2many( comodel_name = 'res.partner',
compute='_compute_ids',
string='Wybrane konta',
)
@api.one
@api.depends('sel')
def _compute_ids(self):
values = self.env['res.partner'].search([('name','like',self.sel)])
self.partners=values.ids
Jak widać w tym przykładzie - pole wypełniane jest listą wyszukanych identyfikatorów (kluczy) z tabeli res.partner.
Odpowiedni ekran:
<record model="ir.ui.view" id="mdemo1v">
<field name="name">Partnerzy</field>
<field name="model">demo.mdemo</field>
<field name="arch" type="xml">
<form>
<group col="2" name="Forum">
<field name="sel" />
<field name="partners" />
</group>
</form>
</field>
</record>
Po wypełnieniu pola wzór (i naciśnięciu Enter) - wyświetlają się zgodne z tym wzorem kontakty.