Fino a questo punto abbiamo utilizzato l’ORM di Odoo senza scendere nel dettaglio su come funziona. Ora andiamo a vedere quali sono i suoi principali componenti
Abbiamo notato che a molti metodi dei modelli viene applicato un decoratore come @api.multi. Questi decorati hanno lo scopo di istruire il backend su come gestire i metodi rispetto alle API esposte.
Il decoratore @api.model invece si utilizza per decorare metodi statici in cui il self non fa rifermento a nessuna entità in particolare. Per coerenza self farà sempre riferimento a un oggetto di tipo recordset ma il suo contenuto diventa irrilevante. Questo tipo di metodi non possono essere richiamati dalle API e quindi non possono essere usati sui bottoni nell’interfaccia utente.
Altri decoratori con scopi più specifici:
Lanciando il seguente comando, odoo ci presenta un linea di comando interattiva dove possiamo accedere a tutto l’ambiente di odoo. E' molto comoda per effettuare dei test:
$ docker compose run odoo shell
In [1]: self
Out[1]: res.users(1,)
In [2]: self._name
Out[2]: 'res.users'
In [3]: self.name
Out[3]: 'Administrator'
I metodi principali utilizzabili nei modelli sono quelli disponibili sulla documentazione ufficiale
Attraverso la variabile self possiamo accedere solamente ai metodi del modello che stiamo attualmente utilizzando. Ma ogni modello ha un variabile env, accesssibile tramite self.env che ci permette di avere un riferimento a qualsiasi modello installato sul sistema. Per esempio self.env[‘res.parner’] restituisce un riferimento al modello dei Partner permettendoci quindi di utilizzare metodi come search o browse su quel determinato set di dati.
Il metodo search() accetta come paramentro un domain e restituisce un recordset contenente le righe che rispettano le condizioni del dominio. Passando un domain vuoto ([]) si ottengono tutte le righe presenti. Gli altri paramentri accettati da search() sono:
A volte serve solo contare gli elemneti in un determinato domain, in quei casi è possibile utilizzare la funzione search_count() che accetta gli stessi parametri della search() ma restituisce un intero (e ha un impatto infinitesimale in termini di performance rispetto alla classica search())
Il metodo browse() prende una lista di ID oppure un singolo ID e ritorna un recordset contenente i record trovati. E' molto più performante della search ma richiede la conoscenza degli ID interessati.
Alcuni Esempi:
In [2]: self.env['res.partner'].search([('name','ilike','ad')])
Out[2]: res.partner(3,)
In [4]: p = self.env['res.partner'].browse(3)
In [5]: p.name
Out[5]: 'Administrator'
I Recordset supportano diverse operazioni. Possiamo per esempio controllare se un elemento è contenuto in un recordset oppure no.
Considerando x un singleton e test_recordset un insieme di elementi possiamo scrivere
Sono inoltre dissponibili le seguenti proprietà:
Per aggiungere, togliere o sostituire elementi dai recorset ci sono una serie di operatori che ci possono aiutare. I recordset di per sè sono immutabili ma attraverso questi operatori è possibile generare nuovi recordset modificati partendo da quelli esistenti
Gli operatori di manipolazione sono:
È inoltre possibile accedere agli elementi dei recordset attverso gli operatori di list Python, queste sono quindi espressioni valide:
Altri operatori:
E' sempre possibile accedere al databse direttametne eseguendo query SQL personalizzate. Nella varibile self.env.cr è disponibile un cursore legato all’attuale connessione al db che possiamo utilizzare proprio a questo scopo.
Per effetturare una query utilizziamo il metodo execute successitamente dobbiamo invocare un’altra funzione per ottenerne gli eventuali risultati:
In [1]: self.env.cr.execute("SELECT id, login FROM res_users WHERE login='%s'" % 'admin')
In [2]: self.env.cr.dictfetchall()
Out[2]: [{'id': 1, 'login': 'admin'}]
I campi relazionali possono essere utilizzati con la notazione puntata, esempio:
In [14]: self
Out[14]: res.users(1,)
In [15]: self.company_id
Out[15]: res.company(1,)
In [16]: self.company_id.name
Out[16]: 'My Company'
In [17]: self.company_id.currency_id
Out[17]: res.currency(1,)
In [18]: self.company_id.currency_id.name
Out[18]: 'EUR'
Quando dobbiamo scrivere un campo Many2one dobbiamo ricordarci di passare solo l’id dell’oggetto e non il singleton corrispondente, quindi e' corretto
# CORRETTO
In [17]: self.write({'user_id': self.env.user.id})
ma darebbe invece errore
# SBAGLIATO
In [17]: self.write({'user_id': self.env.user})
Quando invece dobbiamo scrivere i campi Many2many oppure One2many esiste una sintassi specifica per farlo. Il valore che dobbiamo passare nella write è una tupla contenente tre valori i cui possibili valori sono: