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.