Akcje, dekoratory, self i env
ORM Odoo implementuje typowe funkcje spotykane w tego typu bibliotekach jak odczyt (read), zapis (write), utworzenie (create). Akcje w obrębie modelu wykorzystują kursor (cr), identyfikator użytkownika (uid), oraz kontekst (context). Należałoby więc zawsze gdy korzystamy z odczytu pisać coś w rodzaju:
read(cr, uid, lista_identyfikatorow, lista_pol, context=context)
Aby uniknąć przekazywania w parametrach danych nie związanych z logiką programu, API definiuje dekoratory, które przekazują te dane wprost do metody z użyciem zmiennej env. Env nie zawiera zatem jedynie listy modeli.
Atrybuty i metody env:
env.cr
kursor bazy danychenv.uid
ID użytkownika dla aktualnej sesjienv.user
rekord aktualnego (zalogowanego) użytkownikaenv.context
słownik wartości opisujących kontekst obliczeń (ustawiany programowo)env.with_context(key=value,...)
zmiana kontekstenv.sudo(user)
wykonanie operacji z uprawnieniami innego użytkownika.env.ref()
funkcja zwracająca rekord dla podanych parametrów
Użycie dekoratora zdefiniowanego przez api:
@api.dekorator
def metoda(self, parametry):
....
jest równoważne do:
def metoda(self, cr, uid, parametry, context=context):
....
jeśli metoda działa na zbiorze rekordów - zamiast przekazywać listę ich identyfikatorów za pośrednictwem parametru (ids), zmienna self staje się zbiorem rekordów (recordset - zob. https://www.odoo.com/documentation/online/reference/orm.html). Udekorowana funkcja jest w takim wypadku równoważna do:
def metoda(self, cr, uid, ids, parametry, context=context):
....
Więcej szczegółów: https://www.slideshare.net/openobject/odoo-from-v7-to-v8-the-new-api
Model zbudowany z użyciem api Odoo jest bardzo prosty w użyciu:
env = Env(cr, uid, context) # cr, uid, context wbudowane w env
model = env[MODEL] # instancja modelu MODEL
recs = model.search(DOMAIN) # wyszukanie rekordów (search zwraca recordset
# dla warunków z DOMAIN)
for rec in recs: # iterate over the records
print rec.name
recs.write(VALUES) # zapis wartości VALUES.
MODEL oznacza tu nazwę modelu, DOMAIN - listę warunków (krotek w rodzaju ('pole', '=', wartosc)), a VALUES zbiór wartości (na przykład {'pole1': wartosc1, 'pole2':wartosc2}).
Poza pojęciem domeny (DOMAIN - w przybliżeniu odpowiednik where w SQL) spotykamy pojęcie filtru - czyli definicji wyboru rekordów opartej na domenie.
Pierwsza z powyższych operacji (env = ...) nie występuje w aplikacjach, gdyż o zawartość env dba framework. Jeśli w programie chcemy zmienić środowisko (env), posługujemy się metodami withenv oraz with_context (zob. https://www.odoo.com/documentation/8.0/reference/orm.html).
Do zapisu nie musimy używać funkcji write - wystarczy pod zmieną reprezentującą pole podstawić nową wartość. Na przykład:
self.name = "nowa nazwa"
Przykład praktyczny: jak odczytać listę osób zapisanych na wydarzenie (moduł event)?
lista = self.env['event.registration'].search([
('state', '!=', 'cancel'),
('partner_id', '>', 0),
('event_ticket_id', '>', 0),
('event_id', '=', event_id),
])
Odpowiada to zapytaniu do bazy danych:
select *
from event_registration
where partner_id>0 and state<>'cancel' and event_ticket_id>0 and event_id=:id;
Lista dekoratorów
- @api.multi
- @api.one
- @api.model
- @api.depends
- @api.returns
- @api.onchange
- @api.constrains
- @api.noguess
Zobacz: http://odoo-new-api-guide-line.readthedocs.org/en/latest/decorator.html
@api.multi
Metoda api.multi pozwala na przetwarzane informacji z modelu bez ich zmiany. Przykład:
@api.multi
def get_names(self):
names = []
for rec in self:
names.append(rec.name)
return ', '.join(names)
@api.one
Jeśli model obejmuje tylko jeden rekord, zamiast @api.multi można - choć nie jest to zalecane - stosować @api.one.
@api.one
def get_name(self):
return self.name # self w tym miejscu znaczy, że mamy tylko jeden rekord!
Wywołanie:
partner.get_name()
Jeśli model zawiera więcej rekordów - pojawia się wyjątek.
Zamiast @api.one zaleca się stosowanie @api.multi oraz metodę self.ensure_one().
@api.multi
def get_name(self):
self.ensure_one()
return self.name # self w
Testujemy:
class Partner2(models.Model):
_inherit = 'res.partner'
_name = 'Partner'
def __init__(self, name):
self.name = name
@api.one
def get_name1(self):
return self.name
@api.multi
def get_name2(self):
self.ensure_one()
return self.name
@api.multi
def get_names(self):
names = []
for rec in self:
names.append(rec.name)
return ', '.join(names)
@api.model
Dekorator używany zawsze gdy chcemy wykorzystać mechanizmy self/env zamiast przekazywać dane przez parametry (a nie ma innego dekoratora). Na przykład dla funkcji ustawiającej wartość domyślną (aktualnego użytkownika):
created_id = fields.Many2one('res.users', default=_default_created_user, string='Created by')
@api.model
def _default_created_user(self):
uid = self._uid
return uid
@api.returns
Tak udekorowana funkcja zwraca zbiór rekordów (RecordSet, w starym API automatycznie zamieni na listę identyfikatorów).
Przykład:
@api.returns('self')
def _get_return(self):
return self
@api.onchange
Wiąże funkcję z obsługą zdarzenia onchange na formularzu (XML). Bez dekoratora taka funkcja może wyglądać następująco:
def on_change_cena0(self,cena,upust):
total = cena - upust
res = {
'value': {
'wartosc': total
}
}
return res
Aby ją wywołać - na formularzu musimy zdefiniować pola:
<field name="wartosc" />
<field name="cena" on_change="on_change_cena0(cena,upust)"/>
<field name="upust" on_change="on_change_cena0(cena,upust)"/>
Użycie dekoratora pokazuje niesamowite możliwości w ukrywaniu szczegółów implementacyjnych (dla programisty zostaje czysta logika biznesowa). Funkcja z dekoratorem:
@api.onchange('cena','upust')
def on_change_cena(self):
self.wartosc = self.cena - self.upust
Nie jest w tym przypadku potrzebne definiowanie własności on_change na formularzu!!! Muszą tylko zgadzać się identyfikatory. Oczywiście dekorator może być deklarowany dla jednego lub więcej.
Jeśli coś nam nie pasuje - możemy po staremu zgłosić komunikat (podkreślenie to wywołanie translatora):
@api.onchange('cena','upust')
def on_change_cena(self):
if self.cena<self.upust:
return {
'warning': {
'title': _('Warning!'),
'message': _('Price must be > discount'),
},
}
self.wartosc = self.cena - self.upust
W przypadku pól powiązanych (Many2one) można też w taki sposób sterować filtrami wyboru (domain). Zobacz przykład: https://stackoverflow.com/questions/45690736/how-to-return-domain-on-onchange
@api.depends
Dekorator używany w polach obliczeniowych - przekazuje wartości pól na podstawie których wylicza się wartość. Więcej informacji na ten temat znajdziesz w następnym rozdziale.
@api.constrains
Odoo umożliwia korzystanie z więzów integralności bazy danych. Pokazuje to poniższy przykład (zaczerpnięty z podręcznika "Odoo 10 Developement Essentials").
class TodoTask(models.Model):
_sql_constraints = [
('todo_task_name_uniq',
'UNIQUE (name, active)',
'Task title must be unique!')]
Jednak możemy definiować dodatkowe ograniczenia programowo
Na przykład:
@api.multi
@api.constrains('cena')
def _on_cena_nieujemna(self):
# self.ensure_one()
for item in self:
if item.cena<0:
# _logger.warning('Cena = (%s).' % self.cena)
raise Warning(_('Error! Price must be >0.'))
Zamiast Warning można użyć ValidationError, Warning (from odoo.exceptions import ValidationError, Warning).
Uwaga! Te ograniczenia są sprawdzane dopiero w momencie zapisu! Nie tak jak w przypadku depends, czy onchange - gdzie odbywa się sprawdzanie w momencie edycji.