OpenERP

De castillowiki
Saltar a: navegación, buscar

OpenERP és un producte de codi obert que es distribueix sota dos tipus de desplegament: On-premise, sota els SO Linux i Windows, amb dues versions (Community, gratuïta, i Enterprise, de pagament); i el SaaS.

La llicència de la versió 6 i 7 és és AGPLv3 o AGPL+Private User.

L’OpenERP és desenvolupat sota una arquitectura web client-servidor de tres capes:

  • La base de dades en un servidor PostgreSQL
  • Un servidor OpenERP que conté tota la lògica de negoci
  • Un servidor web, que a partir de la versió 6.x està ubicat conjuntament amb el servidor OpenERP
  • La capa client que té diverses possibilitats: web, GTK i possibles clients desenvolupats amb els protocols XML-RPC i Net-RPC


Instal·lació

Podem seguir aquest manual https://doc.openerp.com/7.0/es/install/#installation-link

Explotació i adequació

L’explotació i adequació de l’ERP d’una organització és una tasca imprescindible, ja que garanteix que el programari es mantingui en condicions de ser utilitzat per l’organització per tal de donar sortida a les seves necessitats. Per poder-ho dur a terme, per una banda cal identificar les necessitats (tasca pròpia de consultors) i, per una altra, tenir un coneixement profund de l’ERP, tant en les funcionalitats que facilita (tasca de consultors i implantadors) com en les qüestions tècniques vinculades a l’ERP (tasca d’analistes i programadors).

L’OpenERP és un programari de gestió empresarial desenvolupat sobre el framework OpenObject de tipus RAD (Rapid Application Development).

La facilitat dels entorns RAD rau en el fet que el desenvolupament d’aplicacions és molt simple pel programador, de manera que amb poc esforç es pot obtenir aplicacions d’altes prestacions.

El poc esfoç és relatiu. Per a utilitzar correctament OpenObject és necessari amplis coneixement de python, XML, HTML, javascript i altres tecnologíes com QWeb, JQuery, Mako Templates, DIA, UML... Amés, la documentació correcta i actualitzada és escasa. Per tant, OpenObject o qualsevol RAD és molt cómode si ja es controla, mentres tant, pot resultar difícil i frustrant.

Arquitectura

L’OpenObject facilita diversos components que permeten construir l’aplicació:

Arquitectura MVC
Cliente Servidor OpenERP
  • La capa ORM (Object Relational Mapping) entre els objectes Python i la base de dades PostgreSQL. El dissenyador-programador no efectua el disseny de la base de dades; únicament dissenya classes, per les quals la capa ORM d’OpenObject n’efectuarà el mapat sobre el SGBD PostgreSQL.
  • Una arquitectura MVC (model-vista-controlador) en la qual el model resideix en les dades de les classes dissenyades amb Python, la vista resideix en els formularis, llistes, calendaris, gràfics… definits en fitxers XML i el controlador resideix en els mètodes definits en les classes que proporcionen la lògica de negoci.
  • OpenERP és un ERP amb una arquitectura de Tenencia Múltiple. És A dir, té una base de dades i un servidor comú per a tots els clients. El contrari sería tindre un servidor o base de dades per client o virtualitzar.
  • Un sistema de fluxos de treball o workflows.
  • Dissenyadors d’informes.
  • Facilitats de traducció de l’aplicació a diversos idiomes.

El servidor OpenERP proporciona un accés a la base de dades emb ORM. El servidor necessita tindre instal·lats mòduls, ja que comença buit.

Per altra banda, el client es comunica amb el servidor en XML-RPC, els clients web per JSON-RPC. El client sols té que mostrar el que dona el servidor o demanar correctament les dades. Per tant, un client pot ser molt simple i fer-se en qualsevol llenguatge de programació. OpenERP proporciona un client GTK i un client web.

Les dades estan guardades en una base de dades relacional. Gràcies a l'ORM, no cal fer consultes SQL dirèctament. ORM proporciona una serie de mètodes per a treballar de manera més ràpida i segura. En compte de parlar de taules es parla de models. Aquest són mapejats per l'ORM en taules. No obstant, un model té més que dades en una taula. Un model es comporta con un objecte al tindre camps funcionals, restriccions i camps relacionals que deixen la normalització de la base de dades en mans d'OpenERP.

L'accés del client a les dades es fa fent ús d'un servici. Aquest pot ser WSGI. WSGI és una solució estàndar per a fer servidors i clients HTTP en Python. En el cas d'OpenERP, aquest té el OpenERP Web Project, que és el servidor web.

Un altre concepte dins d'OpenERP són els Business Objects quasi tot en OpenERP és un Business Object, és persistent gràcies a ORM i es troba estructurat en el directori /modules.

OpenERP proporciona els anomenats Wizards, que es comporten com un assistent per introduir dades d'una manera més fàcil per a l'usuari.

El client web és fàcil de desenvolupar gràcies al Widgets o Window GaDGETS. Aquests proporcionen un comportament i visualització correctes per a cada tipus de dades. Per exemple: si el camp és per definir una data, mostrarà un calendari. Alguns tenen diferents visualitzacions en funció del tipus de vista i se'n poden definir Widgets personalitzats.

Tal com es pot observar, són molts els components d’OpenObject a conèixer per poder adequar l’OpenERP a les necessitats de l’organització, en cas que les funcionalitats que aporta l’OpenERP, tot i ser moltes, no siguin suficients.

La base de dades d'OpenERP

En l’OpenERP no hi ha un disseny explícit de la base de dades, sinó que la base de dades d’una empresa d’OpenERP és el resultat del mapatge del disseny de classes de l’ERP cap el SGBD PostgreSQL que és el que proporciona la persistència necessària per als objectes.

En conseqüència, l’OpenERP no facilita cap disseny entitat-relació sobre la base de dades d’una empresa ni tampoc cap diagrama del model relacional.

Si sorgeix la necessitat de detectar la taula o les taules on resideix determinada informació, és perquè es coneix l’existència d’aquesta informació gestionada des de l’ERP i, per tant, es coneix algun formulari de l’ERP a través del qual s’introdueix la informació.

L’OpenERP possibilita, mitjançant els clients web i GTK, recuperar el nom de la classe Python que defineix la informació que s’introdueix a través d’un formulari i el nom de la dada membre de la classe corresponent a cada camp del formulari. Aquesta informació permet arribar a la taula i columna afectades, tenint en compte dues qüestions:

  • El nom de les classes Python d’OpenERP sempre són en minúscula (s’utilitza el guió baix per fer llegible els mots compostos) i segueix la nomenclatura nom_del_modul.nom1.nom2.nom3… en la qual s’utilitza el punt per indicar un cert nivell de jerarquia. Cada classe Python d’OpenERP és mapada en una taula de PostgreSQL amb moltes possibilitats que el seu nom coincideixi amb el nom de la classe, tot substituint els punts per guions baixos.
  • Els noms dels atributs d’una classe Python sempre són en minúscula (s’utilitza el guió baix per fer llegible els mots compostos). Cada dada membre d’una classe Python d’OpenERP que sigui persistent (una classe pot tenir dades membres calculades no persistents) és mapat com un atribut en la corresponent taula de PostgreSQL amb el mateix nom.
Per exemple: La classe Python sale.order d’OpenERP està pensada per descriure la capçalera de les comandes de venda i la corresponent taula a PostgreSQL és sale_order.

D’aquesta manera, coneixent el nom de la classe i el nom de la dada membre, és molt possible conèixer el nom de la taula i de la columna corresponents. Es pot configurar els clients web i GTK per tal que informin del nom de la classe i de la dada membre en situar el ratolí damunt les etiquetes dels camps dels formularis.

Figura 1.1 Activar el debug mode


Si observem la figura 1.2, podem observar com:

Dalt apareix un desplegable que diu depurar#574, és la vista per defecte per a analitzar els elements dels formularis.

El camp Referencia cliente es diu client_order_ref de l'objecte sale.order. Per tant, la columna client_order_ref de la taula sale_order.

Figura 1.2 Dades de depuració

Respecte al desplegable de dalt, la resta d'opcions són:

  • View Fields que permet obtenir una llista dels camps de la vista actual, amb els paràmetres corresponents.
  • Fields View Get que mostra l’XML generat per la vista actual. Cal tenir en compte que si la vista s’ha generat a partir de diverses vistes XML heretant les unes de les altres, aquí se’n mostra el resultat final.
  • Manage Views que mostra un llistat de les vistes relacionades amb la vista actual. Des d’aquest punt es poden crear noves vistes, eliminar-les o editar-les, encara que és recomanable utilitzar aquesta funcionalitat només per consultar. Es recomana realitzar les modificacions creant nous mòduls, per no perdre les modificacions davant actualitzacions de l’ERP.
  • Edit TreeView, Edit SearchView, Edit Action i Edit Workflow que serveixen per accedir a l’edició de les vistes relacionades amb la vista actual.
  • Si estem editant un registre (mode formulari) o consultant-lo (mode pàgina), apareix una nova opció View Log (perm_read) que mostra informació relativa al registre actual.


Accés de només lectura a les dades

Les empreses acostumen a tenir, entre els seus responsables, usuaris finals que poden efectuar consultes no previstes a la base de dades i que, per aconseguir-ho, poden utilitzar eines gràfiques per elaborar consultes o fins i tot, si són prou espavilats, executar consultes SQL des d’una consola d’accés.

En aquesta situació, cal facilitar als usuaris que correspongui, un usuari per accedir al SGBD PostgreSQL amb els privilegis d’accés, en mode consulta, als objectes de la base de dades que correspongui. S’aconsella seguir els següents passos:

1. Crear els usuaris de SGBD amb les contrasenyes que corresponguin.

Amb pgAdmin és fàcil d'afegir un nou rol de login.

2. Donar els privilegis d’accés adequats als usuaris que corresponguin.

Instal·lació de mòduls

Per a instal·lar mòduls que fem nosaltres, en el cas de Ubuntu o Debian, cal guardar-los en:

/usr/lib/pymodules/python2.7/openerp/addons/
Podem canviar el lloc on estan els mòduls o afegir rutes modificant el fitxer openerp-server.conf.

Poden estar en .zip o descomprimits.

En OpenERP, fem que el nostre usuari tinga permís d'accés i característiques tècniques:

Usuarimodulerp.png

Eixim de sessió i entrem de nou. Ara tenim accés a un menú molt més complet on podem actualitzar la llista de mòduls, trobar el nostre i instal·lar-lo.

Depuració en Openerp

Podem iniciar el servidor manualment desde la terminal. Pe a fer-ho hem de ser usuari openerp. En ubuntu, cal fer que l'usuari openerp tinga /bin/bash per poder entrar en terminal.

# su openerp
openerp$ /usr/bin/python /usr/bin/openerp-server --config=/etc/openerp/openerp-server.conf

Podem fer que actualitze un mòdul a l'arranc, sense tindre que anar a actualitzar-lo manualment:

openerp$ /usr/bin/python /usr/bin/openerp-server --config=/etc/openerp/openerp-server.conf -u mòdul -d empresa

S'ha observat que en Ubuntu 14.04 i l'últim Debian, escrivint /usr/bin/python davant no funciona.

https://doc.odoo.com/6.0/developer/1_1_Introduction/4_command_line/

Desenvolupament de mòduls en OpenERP

Estructura bàsica dels mòduls

Un mòdul pot contenir els següents elements:

  • Objecte de negoci: declara com classes de Python qual hereta de la classe osv.Model, la persistència d'aquests recursos és manejada completament per l'ORM d'OpenERP.
  • Dades: Arxius XML/CSV amb les metadades (vistes i la declaració de fluxos de treball), les dades de configuració (parametrització de mòduls) i les dades de demostració.
  • Informes: RML (format XML). HTML/MAKO o plantilles d'informes d'OpenOffice que es combinaran amb qualsevol tipus de dades de negocis i generar HTML, ODT o informes en PDF.


03 module gen view.png

Cada mòdul està contingut en el seu propi directori al servidor al directori de addons, configurat a la instal·lació del servidor. Per crear un nou mòdul, són necessaris els següents passos:

  • Crear un subdirectori en el directori de addons
  • Crear el __ init__.py per a l'importació d'arxius.
  • Crear el __ openerp__.py que és el que defineix el mòdul.
  • Crear arxius de Python que continguen objectes
  • Crear. xml que contenen dades de mòduls com ara vistes, entrades de menú o dades de demostració
  • Opcionalment crear informes i fluxos de treball
En ubuntu 13.04, si instal·lem amb apt-get, els mòduls estan per defecte en /usr/lib/pymodules/python2.7/openerp/addons/, encara que es pot modificar

__init__.py

És l'arxiu d'importació de Python, perquè un mòdul d'OpenERP és també un mòdul ordinari de Python. L'arxiu ha d'importar tots els altres arxius python o submòduls.

Per exemple, si un mòdul conté un sol arxiu de python anomenat openacademy.py, l'arxiu ha de ser similar a:

    import openacademy

__openerp__.py

Aquest arxiu, que ha de ser un diccionari Python literal, és responsable de:

  • Determinar els arxius XML que es va a analitzar durant la inicialització del servidor
  • Determinar les dependències del mòdul creat.
  • Declarar metadades addicionals


Aquest fitxer ha de contenir un diccionari de Python amb els següents valors:

  • name: el nom del mòdul, en anglès.
  • version: la versió del mòdul.
  • description: la descripció del mòdul (text).
  • author: l’autor del mòdul.
  • website: el lloc web de l’autor del mòdul.
  • license: la llicència del mòdul (per defecte, la que correspon a la versió de servidor OpenERP on s’instal·larà el mòdul).
  • category: categoria a la qual pertany el mòdul. Text separat per barres / amb la ruta jeràrquica de les categories.
  • depends: llista Python de mòduls dels quals depèn aquest mòdul. El mòdul base s’acostuma a afegir perquè conté dades utilitzades en vistes, informes…
  • init_xml: llista Python de noms de fitxers XML que es carregaran en iniciar el servidor OpenERP amb l’opció -init=module. Les rutes dels fitxers han de ser relatives a la carpeta on és el mòdul. En aquesta llista s’acostuma a posar els fitxers relatius a definició de workflows i les dades a carregar en el procés d’instal·lació del mòdul.
  • update_xml o data : llista Python de noms de fitxers XML que es carregaran en iniciar el servidor OpenERP amb l’opció -update=module. Les rutes dels fitxers han de ser relatives al directori on és el mòdul. En aquesta llista s’inclouen els fitxers relatius a vistes, informes i assistents.
  • demo_xml: llista Python de noms de fitxers XML que es carregaran si s’ha instal·lat el servidor OpenERP amb dades de demostració o exemple. Les rutes dels fitxers han de ser relatives al directori on és el mòdul.
  • installable: els valors permesos són True o False i determinen si el mòdul és o no és instal·lable.
  • active: els valors permesos són True o False i determinen si el mòdul serà instal·lat automàticament en el procés de creació de l’empresa. Per defecte, False.
En Python, els diccionaris es defineixen entre {} amb parelles clau:valor separades per comes; les llistes entre [] amb valors separats per comes.

En el cas de del nostre exemple de openacademy tindrà un aspecte similar a:

{
    'name' : "OpenAcademy",
    'version' : "1.0",
    'author' : "OpenERP SA",
    'category' : "Tools",
    'depends' : ['mail'],
    'data' : [
        'openacademy_view.xml',
        'openacademy_data.xml',
        'report/module_report.xml',
        'wizard/module_wizard.xml',
    ],
    'demo' : [
        'openacademy_demo.xml'
    ],
    'installable': True,
}

Objectes

Tots els recursos OpenERP són objectes: factures, socis. Les metadades també són objecte també: menús, accions, informes ... Els noms d'objectes són jeràrquics, com en els exemples següents:

        account.transfer: una transferència de diners
        account.invoice: una factura
        account.invoice.line: una línia de factura

En general, la primera paraula és el nom del mòdul.

Els objectes es declaren en python com a subclasse de osv.Model

El ORM d'OpenERP es construeix sobre PostgreSQL. Per tant, és possible consultar l'objecte utilitzat per OpenERP utilitzant la interfície d'objecte (ORM) o mitjançant l'ús d'instruccions SQL directament.

Però és perillós per escriure o llegir directament a la base de dades PostgreSQL, com es pot escurçar passos importants com la comprovació de restriccions o la modificació de flux de treball.

Cada objecte de python NO és un registre en la taula de la base de dades. En OpenERP, els objectes es comporten com les classes tradicionals. És a dir, un objecte fà referència a una taula, la qual té instàncies d'eixe objecte. Per tant, les funcions de cada classe no han de ser sobre un element de la taula, sino sobre tots els elements.

Arxius XML

Arxius XML ubicats al directori dels mòduls s'usen per inicialitzar o actualitzar la base de dades quan s'instal·la o actualitza el mòdul. S'utilitzen per a molts fins, entre els quals podem citar:

  • Inicialització i declaració de dades de demostració,
  • Declaració de vistes,
  • Declaració dels informes,
  • Declaració dels fluxos de treball

Les dades poden ser inserides o actualitzades en les taules de PostgreSQL corresponents als objectes OpenERP utilitzant fitxers XML. L'estructura general d'un fitxer XML OpenERP és el següent:

<?xml version="1.0"?>
<openerp>
  <data>
    <record model="model.name_1" id="id_name_1">
      <field name="field1"> "field1 content" </field>
      <field name="field2"> "field2 content" </field>
      (...)
    </record>
    <record model="model.name_2" id="id_name_2">
        (...)
    </record>
    (...)
  </data>
</openerp>

Com podem observar, cada element que declarem té la etiqueta <record>. Quant s'actualitza o s'instal·la un mòdul, els records seràn registres d'una taula de la base de dades. És a dir, els .py (model) defineixen les taules de la base de dades i els XML defineixen el contingut d'algunes d'eixes taules. Com que algunes taules contenen les vistes o els menus, aquestes queden guardades en una taula de la base de dades. Però els XML poden definir dades normals, d'exemple o necessaries per al funcionament d'un mòdul.

<record model="ir.ui.view" id="view_school_professor_form">

En aquest exemple el model="ir.ui.view" és el model sobre el que se desa el registre. És a dir, la taula ir_ui_view.

Diseny de mòduls amb DIA

Dia és una aplicació gràfica de propòsit general per a la creació de diagrames, desenvolupada com a part del projecte GNOME. Està construïda de forma modular, amb diferents paquets de formes per a diferents necessitats.

A nosaltres ens interessa aquesta eina per a dissenyar els diagrames UML dels mòduls OpenERP que volem desenvolupar i generar-ne posteriorment el mòdul per a OpenERP, motiu pel qual haurem d’instal·lar Dia amb Python amb l’opció de generació de mòduls OpenERP. El mòdul està en la branca extra-addons [1]

Per a fer-ho anar, seguim aquest manual: [2]

El procés d’instal·lació de Dia amb Python amb l’opció de generació de mòduls OpenERP, incorpora un exemple de diagrama UML desenvolupat amb Dia (arxiu uml_test.dia facilitat per OpenERP) corresponent a un senzill mòdul de gestió escolar per a OpenERP. Utilitzarem aquest diagrama per iniciar-nos en el disseny de mòduls OpenERP mitjançant l’eina de diagramació Dia.

Umlexempledia.png


L’eina Dia permet desenvolupar diversos tipus de diagrama i, com que ens interessa els diagrames UML, caldrà tenir seleccionada la fulla UML a la caixa d’eines de la part esquerra de la pantalla de Dia.

L’exemple de la figura anterior correspon a un model per gestionar, en una escola que facilita cursos (en diverses edicions) i disposa de professors, els estudiants assistents als diversos cursos.

En una situació com la presentada, a l’hora de desenvolupar un diagrama UML, és molt lògic pensar en un disseny que contingui classes per modelar els conceptes curs, professor, edicions dels cursos i estudiants, de forma similar al diagrama uml_test, el qual contempla:

  • Classe school.course, per modelar els cursos que facilita l’escola.
  • Classe school.event, per modelar les edicions dels cursos.
  • Classe abstracta school.professor, per modelar els professors.
  • Plantilla de classe school.student, per modelar els estudiants.

Una primera ullada del diagrama permet introduir els següents conceptes per mòduls OpenERP:

  • Tots els elements d’un diagrama cal escriure’ls en anglès (fins i tot les etiquetes i els textos informatius). Si ens interessa tenir-lo en un altre idioma, utilitzarem posteriorment les eines de traducció que facilita OpenERP.
  • Totes les classes s’anomenen en minúscules i es recomana incorporar, com a prefix, el nom del mòdul al què pertanyen.
  • Els noms de mòduls, classes i membres de les classes han de ser escrits en minúscula amb guions baixos per a fer llegibles els noms compostos.
  • La nomenclatura indicada és la que marca OpenERP i, com possiblement sabreu, és diferent de les nomenclatures recomanades en altres entorns de POO, com és el cas del llenguatge Java.

Com que els noms de les classes tenen el prefix school, deduïm que es tracta del diagrama d’un mòdul anomenat school. S’aconsella batejar el diagrama Dia amb el nom del corresponent mòdul, ja que en el procés de generació del mòdul a partir del diagrama, Dia proposa el nom del diagrama com a nom del mòdul, tot i que es pot canviar.

Continuem observant el diagrama. Per a la classe school.event del mòdul school, en tractar-se del model per les edicions dels cursos, potser hagués estat més encertat el nom school.course.event, de forma similar a la nomenclatura utilitzada per OpenERP per les comandes de venda i les seves línies (sale.order i sale.order.line).

En el diagrama UML, school.professor una classe abstracta i school.studentés una plantilla de classe. A l’hora de generar els mòduls per OpenERP, això no té cap efecte i les haguéssim pogut dissenyar com a classes normals.

Fixem-nos que cada classe del diagrama incorpora un estereotip (nom encerclat entre els símbols « i » per sobre del nom de la classe). El procés de generació de mòduls OpenERP a partir de diagrames Dia, interpreta el contingut del camp estereotip com la jerarquia de menús, dins OpenERP, on ubicar els formularis generats a partir del diagrama i s’utilitza el símbol / per separar els diversos nivells. És a dir, segons el diagrama de la figura 6, el formulari corresponent al manteniment dels cursos estarà en una opció de menú anomenada Courses, dins el submenú Configuration del menú principal Courses (doncs l’estereotip de la classe school.course és «Courses/Configuration/Courses»).

És aconsellable instal·lar el mòdul uml_test original facilitat per OpenERP, per poder comparar el disseny efectuat mitjançant l’eina Dia amb el resultat final dins OpenERP.

( Els addons en ubuntu si fem una instal·lació amb apt-get estan en /usr/lib/pymodules/python2.7/openerp/addons cal copiar el .zip del mòdul i actualitzar la llista. També pot estar en /usr/share/pyshared/openerp/addons)

El plugin de DIA està desactualitzat (en 2013) i genera mòduls per a versions anteriors a la 7. Per tant, cal fer algunes modificacions:
  • El fitxer __terp__.py cal renombrar-lo a __openerp__.py
  • Pot ser que __terp__.py tinga mal tancades algunes comilles.
  • Cal solucionar el problema de res.partner.address, ja que no existeix ja en la versió 7.
  • El menú en l'XML està obsolet, cal modernitzar-lo.


En el diagrama observem que entre les classes hi ha uns connectors (associacions entre classes) que són únicament de caire informatiu i no tenen cap incidència en la generació del mòdul OpenERP. Les relacions entre les classes es defineix a través d’atributs relacionals dins cada classe; la facilitat de diagramació d’associacions entre classes que facilita l’eina Dia no és aprofitada pel mòdul uml_dia en generar els mòduls OpenERP; en conseqüència, la utilització dels connectors associatius de Dia serveixen, únicament, com a documentació gràfica.

Cal interpretar les associacions que observem com:

  • Un professor (fletxa de school.professor cap a school.course) pot impartir diversos cursos.
  • Un curs (fletxa de school.course cap a school.event) pot tenir diverses edicions.
  • Un curs pot tenir diversos estudiants i un estudiant pot cursar diversos cursos (fletxa amb doble sentit entre school.course i school.student).

Si seleccionem qualsevol de les associacions del diagrama i n’editem les propietats, observem que l’eina Dia, per a documentar encara més el diagrama, permet indicar els rols i la multiplicitat. Així, podríem retocar el diagrama per aconseguir un diagrama més documentat:


A1 continguts image 7.png

Figura Diagrama UML amb multiplicitats a les associacions


El diagrama de classes de les figures anteriors mostra únicament la capçalera de les classes (nom i estereotip) i aquestes classes poden contenir membres (dades i mètodes). Per a visualitzar dades i/o mètodes (atributs i/o operacions, segons nomenclatura de l’eina Dia) d’una classe, cal seleccionar la classe i editar les seves propietats (boto secundari del ratolí), i activar les caselles de verificació Atributs visibles i Operacions visibles. En activar-les per a totes les classes, obtenim la visualització següent:

A1 continguts image 8.png

Una ullada ràpida al diagrama ens permet afirmar que les seves classes no incorporen cap operació i únicament contenen atributs. Aprofitarem les classes d’aquest diagrama per exemplificar els diversos tipus d’atributs de les classes d’un mòdul OpenERP i com es defineixen en un diagrama dissenyat amb l’eina Dia.

Diseny d'atributs

Els atributs de les classes d’un mòdul OpenERP són bàsicament de tres categories: bàsics o simples, relacionals i funcionals.

Els atributs simples o bàsics són aquells destinats a contenir un valor concret (enter, real, booleà, cadena,…). Els atributs relacionals són aquells destinats a representar les relacions entre classes. Els atributs funcionals són uns camps especials perquè no s’emmagatzemen a la base de dades i es calculen en temps reals a partir d’altres atributs.

Fixem-nos, per exemple, en els vuit atributs de la classe school.course: set són simples o bàsics ( name, subject, hours_total, requirements, website, date i note) i un és relacional (prof_id).

Atributs simples o bàsics

Un diagrama Dia amb la visualització d’atributs activada, mostra per a cada atribut simple, la següent seqüència d’informació:

  1. atribut de visibilitat (+ per públic, - per privat i # per protegit)
  2. nom de l’atribut, seguit del símbol :
  3. tipus de l’atribut (boolean, integer, float, char, text, date, datetime, binary, selection, reference)
  4. símbol =
  5. etiqueta pel camp, entre cometes simples, d’obligada existència
  6. cap o més paràmetres, seguint la sintaxis nom=valor i separats per coma.

Així, la classe school.course, té un atribut public (símbol +), destinat a emmagatzemar el nom (name) del curs, amb etiqueta Course, de tipus caràcter (char) de 32 caràcters com a molt (paràmetre size) i amb obligatorietat de valor (required=‘True’ ). Fixem-nos que la definició del camp en el diagrama és:

+name: char = 'Course', size=32, required = 'True'
  • boolean Valors lògics Valors vàlids: True i False
  • integer Valors enters
  • float Valors reals Pot anar acompanyat d’un paràmetre digits destinat a indicar la precisió i l’escala de l’atribut. L’escala és el nombre de dígits després del punt decimal i la precisió és el nombre total de dígits significatius (abans i després del punt decimal). Si el paràmetre digits no és present, serà considerat com un nombre en punt flotant amb doble precisió.
  • date Valor data
  • datetime Valor data i hora en un mateix camp
  • char Cadena de longitud limitada És obligatori indicar el paràmetre size=valor, per indicar la màxima longitud permesa.
  • text Cadena de longitud il·limitada
  • binary Cadena binària
  • selection Un valor d’una llista de valors possibles predefinits En aquest cas, abans del valor de l’etiqueta del camp, cal indicar la llista de valors possibles, com a seqüència de parelles (valor, etiqueta):

[(valor1, etiqueta1), (valor2, etiqueta2),…]

  • reference Punter cap un objecte existent en OpenERP Els objectes d’OpenERP que es poden seleccionar per una referència es defineixen en el menú Settings|Personalització|Objectes de baix nivell|Sol·licituds|Tipus de referències en sol·licitudsd’OpenERP.

Com a exemple d’atribut selection podem observar l’atribut contract de la classe school.professor. Fixem-nos que la llista conté dos valors (trainee i named) pels que la visualització des del programa serà Trainee i Named, a no ser que apliquem un procés de traducció de l’aplicació (però a nivell de la base de dades, els valors emmagatzemats sempre seran trainee i named.

Paràmetres que poden acompanyar els atributs bàsics:

Nom Obli-gatori Atribut al què acompanya Significat
size char Llargada màxima de la cadena
digits No float Indicar la precisió i escala en format (p,s). Veure explicació del camp float a la taula.
required No Qualsevol Indicar l’obligatorietat de l’atribut amb el valor True. Per defecte, el seu valor és False.
readonly No Qualsevol Indicar que l’atribut és de només lectura, amb el valor True. Per defecte, el seu valor és False.
select No Qualsevol Exigir que a nivell de la base de dades es generi un índex per aquest camp per tal d’optimitzar els processos de recerca, amb el valor 1. Per defecte el seu valor és 0.
help No Qualsevol Indicar el missatge d’ajuda que OpenERP visualitza en situar el ratolí damunt l’etiqueta del camp. Provoca que l’etiqueta vagi acompanyada per un petit interrogant a l’extrem superior esquerre.
translate No char o text Indicar que el valor del camp pot ser traduït pels usuaris, amb el valor True. Per defecte, el seu valor és False.

En el client web, els camps amb l’atribut translate a True presenten, en mode edició, una banderola al final de la caixa de text que conté el valor del camp, per indicar que el camp és traduïble. Prement el ratolí damunt la banderola, s’obre una pantalla que recull tots els camps traduïbles del formulari i podem procedir a la seva traducció. Podeu comprovar-ho en el camp Nom del formulari de productes d’OpenERP.

La introducció dels diversos valors per definir un atribut simple des de l’eina Dia, s’efectua des de la pestanya Atributs de les propietats de la classe, seguint els criteris següents:

  • La visibilitat de l’atribut (public, private o protegit) s’escull en el camp Visibilitat.
  • El nom de l’atribut s’introdueix a l’apartat Nom.
  • El tipus de l’atribut s’introdueix a l’apartat Tipus.
  • El nom de l’etiqueta (entre cometes simples i obligatori), seguit de la seqüència de parelles paràmetre=valor que correspongui, separades per coma, s’introdueix a l’apartat Valor.
  • L’ajuda que acompanya al camp (paràmetre help), s’introdueix a l’apartat Comentari.

Podeu comprovar l’aplicació dels criteris anteriors tot editant les propietats de qualsevol dels atributs bàsics de les classes del diagrama uml_test.

Us heu adonat que, en els paràmetres presentats, no n’hi ha cap que faci referència a la definició d’un camp identificador de la classe?

En OpenObject, cada classe té un atribut identificador, de nom id, generat automàticament per la capa Object Relational Mapping (ORM) d’OpenObject, de manera que no s’ha d’indicar en la definició dels atributs de la classe. El valor de l’atribut id per a cada objecte és generat automàticament per OpenObject i identificarà l’objecte (el registre a nivell de la base de dades) per sempre més. Aquest identificador també s’utilitza per relacionar objectes (integritat referencial a nivell de base de dades).

Llavors, si l’atribut identificador dels objectes d’una classe el genera OpenObject, com podem definir un camp que identifiqui un objecte tal i com estem acostumats en multitud d’aplicacions informàtiques? Per exemple, fixem-nos en l’atribut IDNum de la classe school.student. L’ajuda introduïda diu, textualment: “This is the uniq ID of the student”. Com podem aconseguir que IDNum sigui, veritablement identificador? Doncs per això haurem d’utilitzar la restricció d’unicitat que facilita OpenObject, però que no podem indicar a través de l’eina Dia i ho haurem de fer, més endavant, via el llenguatge Python.

L’atribut id és,doncs, un atribut especial però no és l’únic. La següent taula presenta amb detall els atributs de classe bàsics especials a tenir en compte en desenvolupar en OpenObject.

Nom Tipus Explicació
id integer Atribut identificador dins la classe, utilitzat per identificar cada objecte (registre a nivell de base de dades) per sempre més i per establir les relacions entre objectes de diverses classes (integritat referencial a nivell de base de dades). El crea l’eina ORM d’OpenObject i, per tant, no s’ha d’indicar en dissenyar una classe.
create_date datetime Emmagatzema el moment temporal en què es crea l’objecte. El crea l’eina ORM.
write_date datetime Emmagatzema el moment temporal de la darrera modificació de l’objecte. El crea l’eina ORM.
create_uid integer Emmagatzema l’identificador (id) de l’usuari que va crear l’objecte. Aquest usuari és un objecte de la classe res.user (o registre de la taula res_user). El crea l’eina ORM.
write_uid integer Emmagatzema l’identificador (id) de l’usuari que ha modificat per darrera vegada l’objecte. Aquest usuari és un objedcte de la classe res.user (o registre de la taula res_user). El crea l’eina ORM.
name char És l’atribut que utilitza OpenObject quan ha de mostrar objectes en una llista desplegable. En conseqüència, aquest atribut és obligatori en qualsevol classe C susceptible de ser referenciada des d’altres classes, doncs en aquestes caldrà facilitar una llista desplegable per facilitar la selecció dels objectes de C.
active boolean És un atribut que afegirem a aquelles classes per les que interessi poder amagar objectes en algun moment de la seva vida. Podria servir, per exemple, per amagar objectes històrics sense eliminar-los. Un objecte inactiu ja no és accessible ni apareix per enlloc, fins que es torni a activar.
state selection És un atribut que afegirem a aquelles classes per les que interessi poder definir diversos estats del cicle de vida d’un objecte.
parent_id integer És un atribut que permet definir estructures jeràrquiques entre objectes de la mateixa classe. Acostuma a sorgir com a conseqüència d’una associació reflexiva en una classe.

Com a exemple de la utilització que fa OpenObject de l’atribut name, podem anar a consultar la llista de clients d’una empresa, on observarem la columna País, que mostra el contingut de l’atribut name del país al que pertany el client. Si anem a la base de dades a consultar el contingut de la taula res_partner_address (que conté les adreces dels clients), hi veurem el camp country_id, com a clau forana de l’atribut id de la taula res.country. A la llista de clients, però, no veiem el contingut de l’atribut country_id (que és el què relaciona el client amb el país) sinó el contingut de l’atribut name del país referenciat.

OpenERP té moltes classes que contenen l’atribut active, com per exemple la classe res.users (taula res_users). Així, per exemple, podeu anar a Settings|Usuaris, editar el registre Demo User, desmarcar la casella Actiu per deixar-lo no actiu i enregistrar el canvi. Podreu observar com el registre Demo User ha desaparegut de la llista d’usuaris i ja no sortirà mai més fins que el fem actiu. Per a fer actiu un registre inactiu, hem d’aconseguir accedir-hi per a canviar el valor de la casella Actiu i per aconseguir-ho ens hem de valer de la recerca avançada dels formularis, seleccionant el camp Actiu i indicant que volem veure els registres que el tenen a valor fals.

Com a exemple d’utilització de l’atribut state, podem fixar-nos en la classe sale.order corresponent a les comandes de venda. OpenERP hi defineix l’atributstatede tipus selection amb els següents valors que defineixen els diferents estats pels que pot passar una comanda de venda:

   [('draft', 'Quotation'),
   ('waiting_date', 'Waiting Schedule'),
   ('manual', 'To Invoice'),
   ('progress', 'In Progress'),
   ('shipping_except', 'Shipping Exception'),
   ('invoice_except', 'Invoice Exception'),
   ('done', 'Done'),
   ('cancel', 'Cancelled')
   ]

Un exemple d’utilització de l’atribut parent_id el trobem, en OpenERP, en la definició dels tercers (clients i proveïdors). Si editem un client/proveïdor, veurem com a l’apartat Informació General de la pestanya Vendes & Compres, es pot indicar quina és l’empresa pare que, en cas de tenir valor, és un altre tercer.

Atributs relacionals

Entre les classes d’OpenObject es pot establir tres tipus de relacions, de manera similar a les relacions entre les entitats en un model entitat-relació per a bases de dades. Es defineixen via atributs relacionals dins les classes. Tenim, en conseqüència, tres tipus d’atributs relacionals: molts a un, un a molts, molts a molts.

Atribut relacional “molts a un” (many2one)

L’atribut relacional “molts a un” (many2one), s’utilitza per representar una relació cap una classe pare, de molts a un, és a dir, molts objectes de la classe que conté l’atribut poden estar relacionats amb un mateix objecte de la classe pare.

En el diagrama uml_test observem molts atributs relacionals many2one.Un exemple el tenim a la classe school.event, on l’atribut course_id està destinat a contenir, per a cada objecte de la classe school.event, l’identificador de l’objecte pare de la classe school.course.

OpenERP mostra els camps many2one acompanyats per una llista per a seleccionar l’objecte de la classe pare. És a dir, en el formulari per gestionar els cursos (objectes de la classe school.event), el camp course_id, amb etiqueta Course, estarà acompanyat d’un desplegable per seleccionar el curs que correspongui d’entre els objectes de la classe school.course). Això obliga a que la classe referenciada pel camp many2one contingui el camp name.

A nivell de la base de dades, els atributs many2one es mapen en atributs dins la taula corresponent a la classe que els conté, amb restriccions d’integritat referencial cap l’atribut identificador de la taula referenciada. Continuant amb l’exemple de l’atribut course_id de la classe school.event, la taula school_event contindrà el camp course_id amb una restricció d’integritat referencial cap l’atribut id de la taula school_course(automàticament creat per OpenERP).

Per definir un atribut many2one en l’eina Dia, cal crear l’atribut indicant que és del tipus many2one i a l’espai valor introduir:

'classeReferenciada','etiquetaDelCamp',...

Els punts suspensius del final indiquen que es pot utilitzar alguns paràmetres opcionals. Concretament:

  • ondelete: Com actuar quan l’objecte referenciat per l’atribut sigui eliminat. Valors possibles: cascade,set null. Valor per defecte: set null.
  • required, readonly, select: Amb funcionament idèntic als camps no relacionals.

Podem observar que l’atribut course_id de la classe school.event té com a valor:

'school.course','Course',required=True

És conveni, entre els desenvolupadors en OpenERP, anomenar els atributs many2one, amb el nom de la classe a la que referencien (o darrer nivell) acompanyat de _id, com és el cas de l’atribut course_id.


Atribut relacional “un a molts” (one2many)

Un atribut relacional “un a molts” (one2many) s’utilitza per representar una relació cap una classe filla, d’un a molts, és a dir, un objecte de la classe que conté l’atribut pot estar relacionat amb molts objectes de la classe filla.

Tot atribut one2manyés complementari d’un atribut many2one que ha d’existir obligatòriament a la classe filla. Per definir un atribut one2many en l’eina Dia, cal crear l’atribut indicant que és del tipus one2many i a l’espai valor introduir:

'classeReferenciada','nomAtributClasseReferenciada','etiquetaDelCamp',... 

La classe filla (classeReferenciada) ha de contenir l’atribut nomAtributClasseReferenciada de tipus many2one apuntant a la classe on hi ha l’atribut one2many.

Els punts suspensius del final indiquen que es pot utilitzar alguns paràmetres opcionals. Concretament:

  • invisible: Per impedir que el seu contingut sigui visible en els formularis corresponents a la classe a la que pertany el camp. Valors possibles: True,False. Valor per defecte: False.
  • readonly: Amb funcionament idèntic als camps no relacionals.

És conveni, entre els desenvolupadors en OpenERP, anomenar els atributs one2many, amb el nom de la classe a la que referencien (o darrer nivell) acompanyat de _ids.

En el diagrama uml_dia observem l’atribut course_ids del tipusone2many a la classe school.professor, amb el següent contingut a l’espai valor:

'school.course', 'prof_id', 'Courses'

L’atribut course_ids de la classe school.professor relaciona un professor amb el conjunt de cursos que imparteix. En conseqüència, la classe school.course ha de contenir l’atribut prof_id de tipus ser many2one cap a la classe school.professor.


L’existència d’un atribut one2many implica l’existència d’un atribut many2one, però l’existència un atribut many2one no implica l’existència d’un atribut one2many. La definició dels atributs one2many té dos objectius:

  • Aconseguir que en les vistes (formularis) que continguin el camp one2many, aparegui una llista dels objectes referenciats (com mostra la zona apunta per la fletxa de la figura.11).
  • Facilitar l’accés, en desenvolupar mètodes (lògica de negoci) de la classe que conté el camp one2many, cap els objectes referenciats per l’objecte sobre el que s’executi el mètode.

El paràmetre invisible=True en un atribut one2many provoca que cap formulari on aparegui l’atribut incorpori la llista dels objectes referenciats. D’aquesta manera, l’atribut únicament pot servir per facilitar l’accés als objectes referenciats en els mètodes desenvolupats sobre la classe.

Si observem el diagrama uml_diaoriginal facilitat per OpenERP, podem observar que l’atributprof_id de la classe school.courseconté, a l’espai valor:

'res.partner','Professor',required=True

La definició de l’atribut prof_id a la classe school.course és errònia, ja que no apunta a la classe school.professor, sinó a la classe res.partner. El mòdul generat mantenint aquesta definició té un mal funcionament.

Atribut relacional “molts a molts” (many2many)

L’atribut relacional “molts a molts” (many2many) s’utilitza per representar una relació de molts a molts entre dos objectes, és a dir, quan cada objecte d’una classe A pot estar relacionat amb molts objectes d’una classe B i cada objecte d’una classe B pot estar relacionat amb molts objectes d’una classe A.

Un exemple de relació many2many el trobem entre les classes school.student i school.course, ja que un estudiant es pot inscriure a molts cursos i en un curs hi pot haver molts estudiants inscrits.

Per definir un atribut many2many en l’eina Dia cal crear l’atribut en alguna de les dues classes que relaciona, o en ambdues, indicant el tipus many2many i, a l’espai valor, introduir:

'classeReferenciada','nomTaulaRelacional','nomCampFKPropi','nomCampFKAltraClasse','etiquetaDelCamp'

Està acordat, entre els desenvolupadors en OpenERP, anomenar els atributs many2many, amb un nom (adequat a les dues classes que relaciona) acompanyat de _ids. Fixem-nos que la definició d’un atribut many2many implica la creació d’una taula relacional a la base de dades.

En el diagrama uml_dia observem l’atribut subscriptions del tipus many2many a la classe school.student, amb el següent contingut a l’espai valor:

'school.course','course_student_subscription_rel','course_id','student_id','Subscriptions'

L’atribut subscriptions relaciona la classe school.student (en la qual és definit) amb la classe school.course, fet que provocarà l’aparició d’una nova taula a la base de dades, de nom course_student_subscription_rel, que tindrà l’atribut course_id amb restricció d’integritat referencial cap a la classe school.student i l’atribut student_id amb restricció d’integritat referencial cap a la classe school.course.

L’atribut subscriptions presenta algunes anomalies de notació:

  • El nom no segueix el conveni, ja que seria bo que el seu nom finalitzés en _ids.
  • El nom de la taula relacional que es generarà a la base de dades seria bo que comencés amb el prefix school_, de la mateixa manera que la resta de taules vinculades a aquest mòdul.
  • Els noms assignats als tercer i quart paràmetres del contingut de l’espai valor haurien d’estar a l’inrevés, de manera que course_id fos el nom per fer referència a la classe school.course i student_id fos el nom per fer referència a la classe school.student.

Així doncs, seria millor redefinir l’atribut subscriptions, canviant-li el nom per course_ids i amb el següent contingut a l’espai valor:

'school.course','school_student_course_rel','student_id','course_id','Subscriptions to courses'

Un atribut many2many provoca que l’eina Dia, en generar el formulari per al manteniment dels objectes de la classe, hi incrusti una zona de tipus llista per accedir als registres de la classe referenciada per l’atribut many2many.

Disseny del model

L’eina de diagramació Dia, amb l’extensió per generar mòduls per a OpenERP, facilita el disseny de mòduls per OpenERP. Les possibilitats que permet l’eina Dia són, però, limitades i, en conseqüència, ens cal aprendre com escriure directament els mòduls utilitzant els llenguatges Python i XML.

Un mòdul d’OpenERP incorpora, en el seu interior, tots els elements del patró model-vista-controlador en el qual es basa el desenvolupament d’OpenObject. L’eina Dia permet definir el model via diagrama UML i l’extensió per generar mòduls per a OpenERP ens facilita el mòdul que conté:

  • El model, definit a través de llenguatge Python, corresponent al diagrama UML dissenyat.
  • Una vista, definida a través del llenguatge XML, generada de forma automàtica.
  • Un controlador buit, ja que l’eina Dia no permet dissenyar els mètodes.
El nostre objectiu final és dissenyar mòduls utilitzant directament el llenguatge Python pel model i el controlador, i el llenguatge XML per la vista.
Nomenclatura d'OpenObject versus nomenclatura POO OpenObject no utilitza la nomenclatura habitual de POO i no parla de classes i d’objectes. Així, les instàncies que gestiona (productes, clients, proveïdors…) s’anomenen recursos (en lloc d’objectes) i l’abstracció o definició de les instàncies s’anomena objecte (en lloc de classe).

Definició d'objectes OpenERP amb Python

El model en OpenERP es descriu a través de classes escrites en Python i OpenObject s’encarrega de fer-les persistents en el SGBD PostgreSQL.

Per definir un objecte (classe) d’OpenERP cal definir una classe de Python i posteriorment instanciar-la, fet que provoca la creació de l’objecte (classe) d’OpenERP. La classe ha d’heretar obligatòriament de la classe osv del mòdul osv.

L’esquelet de la classe Python que permet definir l’objecte OpenERP té la forma:

class name_of_the_object (osv.osv): # <- El nom de la classe. Observeu que hereta de la classe osv.osv
 _name = 'name.of.the.object'       
 _columns = {...}
 ...
name_of_the_object()                # <- Instància de la classe per a crear la taula en l'ORM

Les classes Python que permeten definir objectes (classes) d’OpenERP tenen dos atributs obligatoris i altres opcionals.

Els atributs obligatoris són:

  • _name: nom de l’objecte (classe) d’OpenERP. Aquest nom s’utilitza de forma automàtica per generar el nom de la taula de PostgreSQL, i canvia els punts per guions baixos.
  • _columns: diccionari Python amb la declaració de les dades de l’objecte (classe) d’OpenERP, que passaran a ser les columnes de la taula de PostgreSQL.

Els atributs opcionals són:

  • _auto: els valors possibles són True i False. Per defecte True. Determina si s’ha de generar la taula PostgreSQL a partir de la definició de l’objecte OpenERP. El valor False s’utilitza per objectes OpenERP no persistents que es basen en vistes definides en PostgreSQL.
  • _constraints: definició de restriccions sobre l’objecte, definides amb codi Python.
  • _sql_constraints: definició de restriccions SQL sobre l’objecte, definides a la corresponent taula de PostgreSQL.
  • _defaults: diccionari Python que conté els valors per defecte per les dades (definides a _columns) de l’objecte (classe) d’OpenERP que ho precisin.
  • _inherit: objecte (classe) d’OpenERP del qual hereta l’objecte (classe) que estem definint.
  • _inherits: llista d’objectes (classes) d’OpenERP dels quals hereta l’objecte (classe) que estem definint. La llista ha de seguir la sintaxi d’un diccionari Python de la forma:
{'nom_objecte_pare':'nom_de_camp',...}
  • _log_access: valors possibles True i False. Per defecte, True. Indica si els accessos d’escriptura als recursos (objectes) han de quedar enregistrats. En cas afirmatiu, de forma automàtica es creen a la corresponent taula de PostgreSQL les columnes create_uid, create_date, write_uid i write_date per enregistrar els accessos d’escriptura.
  • _order: nom dels atributs utilitzats per ordenar els resultats dels mètodes de cerca i lectura. Per defecte, s’utilitza el camp _id generat automàticament en totes les taules de PostgreSQL corresponents a objectes (classes) d’OpenERP. Exemples:
_order = 'name'
_order = 'date_order desc'
  • _rec_name: nom de l’atribut de l’objecte (classe) d’OpenERP que conté el nom informatiu de cada recurs (objecte) concret. Per defecte name.
  • _sequence: nom de la seqüència SQL que gestiona els identificadors (contingut del camp _id) pels recursos d’aquest objecte (classe) d’OpenERP. Per defecte, nomDeLaTaula_id_seq.
  • _sql: codi SQL a executar en la creació de l’objecte, en cas que _auto sigui True.
  • _table: nom de la taula SQL si es vol que sigui diferent del nom automàtic a partir del nom de l’objecte substituint els punts per guions baixos.

Una vegada presentats els possibles atributs de les classes Python dissenyades per generar objectes (classes) d’OpenERP, ens cal endinsar-nos en els possibles continguts d’alguns d’aquests atributs.

Tipus de Camps

L’atribut _columns de la classe Python que defineix l’objecte (classe) d’OpenERP ha de contenir un diccionari Python amb la definició dels atributs de l’objecte d’OpenERP, a partir dels camps (fields) predefinits a la classe osv.

La sintaxi general per definir una columna és la següent:

'nom_camp':fields.tipus_de_camp(paràmetres)

on tipus_de_camp ha de ser un dels tipus facilitats per la classe osv i el contingut de paràmetres depèn del tipus del camp.

Hi ha diverses categories de camps:

  • Camps simples: boolean, integer, float, char, text, date, datetime, binary i selection.
  • Camps relacionals: many2one, one2many, many2many i related.
  • Camps function.
  • Camps property.

Els tipus de camps simples i relacionals habituals (many2one, one2many i many2many) ja ens són familiars a causa de la seva utilització en el disseny de mòduls amb l’eina de diagramació Dia. Per a tots aquests camps, el contingut de paràmetres en la crida fields.tipus_de_camp(paràmetres) es correspon amb el contingut de l’espai valor en la definició de cada atribut de classe mitjançant l’eina Dia.

Tipus relacional related

Els camps related permeten accedir a un camp d’un altre objecte referenciat des del propi objecte; és a dir, com si es tractés de camps encadenats.

Com a exemple, suposem que en un mòdul d’OpenERP tenim les classes exemplificades en el diagrama UML de la figura. En un mòdul per a OpenERP, a la classe City hi haurà un camp relacional many2one cap a la classe State, i a la classe State hi haurà un camp relacional many2one cap a la classe Country. Un camp related ens permet accedir des de la classe City a la classe Country via la classe State.

A1 continguts image 13.png

Figura Disseny UML amb dues relacions many2one

La definició d’una columna related ha de seguir la sintaxi:

'nom': fields.related('campPontPropi','campOnAccedir',
                     type='tipusDelCamp', string='etiquetaNouCamp'
                     [,store=True/False][,...]),

Els punts suspensius del final indiquen que hi pot haver més paràmetres segons el tipus del camp. Així, en els camps de tipus many2one, caldrà afegir un paràmetre relation per indicar el nom de la classe relacionada. El paràmetre store (per defecte False) permet indicar si el valor accedit ha de quedar enregistrat a la base de dades (cas clar de redundància) per tal de facilitar una major eficiència d’OpenERP en els processos de recerca i de lectura. En cas d’activar aquesta redundància, els valors sempre són mantinguts per OpenERP i mai pels usuaris.

Així, per aconseguir que la classe City de la figura pugui accedir a l’identificador de la classe Country via la classe State, hauríem de tenir:

# Dins la classe module.state:
_columns: {
 ...
 'country_id': fields.many2one('module.country','Country'),
 ...
}
# Dins la classe module.city:
_columns: {
 ...
 'state_id': fields.many2one('module.state','State'),
 'country_id': fields.related('state_id,'country_id',
                              type='many2one',
                              relation='module.country',
                              string='Country',store=False),
  ...
}

Un cas real d’utilització el podem observar en el disseny de l’objecte (classe) res.partner d’OpenERP, on veiem les columnes city, function, subname, phone, mobile, country i email com a camps related a través del camp address de la mateixa classe:

 _columns = {
  ...
  'address': fields.one2many('res.partner.address', 'partner_id','Contacts'),
  ...
  'city': fields.related('address', 'city', type='char', string='City'),
  'function': fields.related('address', 'function', type='char', 
                             string='function'),
  'subname': fields.related('address', 'name', type='char', 
                            string='Contact Name'),
  'phone': fields.related('address', 'phone', type='char', string='Phone'),
  'mobile': fields.related('address', 'mobile', type='char', string='Mobile'),
  'country': fields.related('address', 'country_id', type='many2one', 
                            relation='res.country',string='Country'),
  'email': fields.related('address', 'email', type='char', size=240,
                          string='E-mail'),
  ...
 }

Exemple d'utilització dels camps related:

L’objecte (classe) school.professor del mòdul school que estem millorant incorpora l’atribut partner_id que permet accedir a l’adreça del professor (objecte res.partner).

Imaginem-nos que és important, per a nosaltres, que el telèfon de cada professor es vegi directament a la fitxa del professor, sense haver d’accedir a la fitxa del partner.

Per aconseguir-ho, com que el telèfon del professor resideix a res.partner i ja accedim a aquest objecte a través del camp partner_id, podem definir el següent camp _related:

 'phone':fields.related('partner_id','phone',type='char',size=64,string='Phone',store=False,readonly=True),
Tipus function

Els camps function simulen camps reals però es calculen mitjançant una funció Python enlloc d’emmagatzemar-se a la base de dades PostgreSQL. En casos especials, per augmentar la velocitat de consulta d’OpenERP i facilitar les consultes, es fan redundants a la base de dades, és a dir, s’emmagatzemen a la base de dades, però sempre són calculats i actualitzats a través de funcions i mai pels usuaris.

La definició d’una columna function ha de seguir la sintaxi:

 'nom': fields.function(fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, 
                       type='Float', fnct_search=None, 
                       string='etiquetaNouCamp', store=False, 
                       multi=False [,...]),

en la qual:

  • Els paràmetres que van acompanyats d’un símbol =Valor no són obligatoris i el valor indicat és el que es considera en cas de no explicitar el paràmetre.
  • type: és el tipus de camp retornat per la funció. Pot ser qualsevol tipus excepte function. Els punts suspensius del final indiquen que hi pot haver més paràmetres segons el tipus del camp. Així, en els camps de tipus 'many2one' caldrà afegir un paràmetre obj per indicar el nom de la classe relacionada.
  • store: per indicar si el camp ha de residir a la taula de la base de dades (redundància).
  • multi: per indicar que el càlcul de la funció s’efectuï per a diversos camps, a causa que la mateixa funció és invocada en diferents camps (veure, com exemple, el mòdul auction d’OpenERP).
  • fnct: és el mètode que calcula el valor del camp. És un paràmetre obligatori i ha d’existir abans de declarar el camp funcional. Ha de retornar, obligatòriament, un diccionari de parelles de la forma {id1:valor1, id2:valor2,…} en el qual id1, id2… han de ser identificadors d’objectes i valor1, valor2… els corresponents valors que han de correspondre amb el tipus indicat a type. La sintaxi ha de ser:
def fnct(self, cr, uid, ids, field_name, arg, context)
  • fnct_inv: és un mètode utilitzat per escriure un valor en lloc del camp. La sintaxi ha de ser:
def fnct_inv(obj, cr, uid, id, name, value, fnct_inv_arg, context)
  • fnct_search: és el mètode utilitzat per fer recerques per aquest camp. Ha de retornar la llista de tuples que especifiquin el criteri de cerca a utilitzar pel mètode search facilitat per OpenObject. La sintaxi ha de ser:
def fnct_search(obj, cr, uid, obj, name, args)


Els mòduls d’OpenERP estan plens d’exemples de camps funcionals, però no tots són senzills d’entendre en una primera aproximació al tema. Així, per iniciar-nos en els camps funcionals, podem centrar-nos en l’atribut number_of_days del mòdul hr_holidays. Seria interessant que, si no el teniu instal·lat, procedíssiu a instal·lar aquest mòdul, per poder comprovar el que comentarem a continuació.

Fixeu-vos en la part de la definició de la classe que ens interessa comentar:

 class hr_holidays(osv.osv):
  ...
  def _compute_number_of_days(self, cr, uid, ids, name, args, context=None):
    result = {}
    for h in self.browse(cr, uid, ids, context=context):
    if h.type=='remove':
      result[h.id] = -h.number_of_days_temp
    else:
      result[h.id] = h.number_of_days_temp
    return result
 
  _columns = {
    ...
    'type': fields.selection([('remove','Leave Request'),
                              ('add','Allocation Request')],
                             'Request Type', required=True,readonly=True, ...),
    'number_of_days_temp': fields.float('Number of Days', readonly=True, 
                                        states={'draft':[('readonly',False)]}),
    'number_of_days': fields.function(_compute_number_of_days,
                                      string='Number of Days',store=True),
    ...
  }
  ...

El mètode _compute_number_of_days crida el mètode browse facilitat per OpenObject. La llista dels mètodes facilitats per OpenObject es troba a l’apartat “OpenERP: la vista i el controlador”.

Fixem-nos que number_of_days és un atribut funcional que, malgrat ser calculat mitjançant una funció, el seu resultat resta emmagatzemat a la base de dades, a causa de store=True. Podem comprovar, mirant la descripció de la taula hr_holidays a la taula de PostgreSQL, l’existència de la columna number_of_days.

Si utilitzeu el mòdul, observareu que permet gestionar, per cada empleat, els dies d’absència i els dies treballats fora del calendari laboral. Cada recurs (objecte) de l’objecte (classe) hr.holidays d’OpenERP correspon a un dels dos tipus possibles (add o remove) segons la definició de l’atribut type. L’atribut number_of_days_temp conté el nombre de dies afectats, sempre en positiu. En canvi, l’atribut number_of_days vol contenir el nombre de dies afectats, en positiu si es tracta de dies de treball addicional, i en negatiu si es tracta de dies d’absència.

En conseqüència, el camp number_of_days es pot calcular a partir del camp number_of_days_temp i per això es defineix com un camp funcional, que executa el mètode _compute_number_of_days definit prèviament.

Observem també que el mètode invocat retorna el diccionari anomenat result format per una única parella on la clau és l’identificador del recurs sobre el qual s’executa i el valor és el nombre de dies calculats.

Tipus property

Els camps property són camps dinàmics amb drets d’accés específics, que permeten contenir diferents valors en funció de la companyia.

 class res_partner(osv.osv):
  _name = 'res.partner'
  _inherit = 'res.partner'
  _description = 'Partner'
  ...
  _columns = {
    ...
    'property_account_payable': 
      fields.property('account.account', type='many2one', 
                      relation='account.account', string="Account Payable", 
                      view_load=True, domain="[('type', '=', 'payable')]",
                      help="This account will be used instead of the default one as the payable account for the current partner", required=True),
    'property_account_receivable': 
      fields.property('account.account', type='many2one', 
                      relation='account.account', string="Account Receivable",             
                      view_load=True, domain="[('type', '=', 'receivable')]",  
                      help="This account will be used instead of the default one as the receivable account for the current partner",   required=True),
    ...
  }

Valors per defecte

La classe Python per definir els objectes d’OpenERP incorpora l’atribut opcional _defaults que permet la definició dels valors per defecte per un o diversos tipus de dades simples de l’objecte d’OpenERP.

Els valors per defecte dels camps d’un objecte d’OpenERP es defineixen en un diccionari de la forma:

 _defaults = {
  'nom_del_camp1': funció1 o valor1,
  'nom_del_camp2': funció2 o valor2,
  ...
  }

Observem que els valors per defecte (sempre corresponents a tipus de dades simples) poden ser valors estàtics o valors dinàmics resultants de la crida de funcions que han d’estar declarades amb anterioritat a la definició del diccionari _defaults.

Les funcions per ser invocades en un valor per defecte dinàmic, han de contenir obligatòriament 4 paràmetres:

  • obj: objecte osv corresponent al recurs que s’està creant.
  • cr: cursor de base de dades.
  • uid: ID de l’usuari que està donant d’alta el recurs.
  • context: el context actual (facilitat pel client).

Els mòduls d’OpenERP estan plens d’exemples d’utilització de valors per defecte. Així, per exemple, observem els valors per defecte de l’objecte res.users (definit en el fitxer res_users.py dins el mòdul base) ideat per gestionar els usuaris de l’empresa gestionada per OpenERP:

 _defaults = {
  'password' : '', 'context_lang': 'en_US', 
  'active' : True, 'menu_id': _get_menu, 
  'company_id': _get_company, 'company_ids': _get_companies,
  'groups_id': _get_group, 'menu_tips': False
 }

En aquesta definició observem la coexistència de valors estàtics (password, llenguatge, active i menu_tips) i valors dinàmics (menu_id, company_id, company_ids i groups_id). Si observeu la definició completa de la classe res.users podreu comprovar com abans de la definició del diccionari _defaults es troba la definició de diverses funcions, entre les quals hi ha les quatre funcions invocades des de _defaults (_get_menu, _get_company, _get_companies i _get_group). Veureu, també, que les quatre funcions tenen els paràmetres self, cr, uid i context.

En algunes ocasions, la definició d’un valor per defecte estàtic s’efectua amb la crida a una funció lambda de Python sense paràmetres. La definició dels valors per defecte estàtics a l’anterior exemple s’hagués pogut efectuar com:

 _defaults = {
  'password': lambda *a: '', 'context_lang': lambda *a: 'en_US',
  'active': lambda *a: True, 'menu_id': _get_menu,
  'company_id': _get_company, 'company_ids': _get_companies,
  'groups_id': _get_group, 'menu_tips': lambda *a: False
 }

Per últim, per no veure’s obligats a crear funcions que només siguin invocades en un valor _defaults, es pot utilitzar les funcions lambda de Python en el mateix moment d’efectuar la crida, estalviant-nos la definició de la funció amb nom. En el següent codi corresponent al diccionari _defaults de la classe document.directory (fitxer document_directory.py del mòdul document) observem la definició de dues funcions lambda:

 _defaults = {
  'company_id': lambda s,cr,uid,c: 
    s.pool.get('res.company')._company_default_get(cr, uid, 'document.directory', context=c),
  'user_id': lambda self,cr,uid,ctx: uid,
  'domain': '[]',
  'type': 'directory',
  'ressource_id': 0,
  'storage_id': _get_def_storage,
  'resource_find_all': True,
 }

http://stackoverflow.com/questions/19293630/what-is-the-reason-of-using-defaults-and-lambda-in-python-for-openerp-developme

Exemple d'utilització de l'atribut _defaults

L’objecte (classe) school.professor del mòdul school que estem millorant, incorpora l’atribut contract que consisteix en una selecció entre dos possibles valors. En el procés de creació d’un nou professor podem observar que el camp contract apareix sense valor per defecte. Volem definir que el valor per defecte d’aquest camp sigui trainee.

Així mateix, observem que l’objecte (classe) school.professor no incorpora el camp especial active que permet ocultar els recursos (objectes) de la classe quan ja no interessi, sense eliminar-los. Aprofitarem aquest exemple per afegir aquest camp i posar-li el valor 1 (cert) com a valor per defecte.

El codi de la classe school.professor modificada és:

 class school_professor(osv.osv):
  """Professors"""
  _name = 'school.professor'
  _columns = {
    'name': fields.char('Professor Name', size=64,required=True),
    'contract': fields.selection([('trainee','Trainee'), 
                                  ('named','Named')],'Contract'),
    'partner_id': fields.many2one('res.partner', 
                                  'Associated Partner'),
    'address_id': fields.many2one('res.partner.address',
                                  'Private Address'),
    'phone': fields.related('address_id','phone', type='char',
                            string='Phone', store=False, 
                            readonly=True),
    'hours_available': fields.integer('Hours Per Week'),
    'course_ids': fields.one2many('school.course', 'prof_id',
                                  'Courses'),
    'active': fields.boolean('Active'),
  }
  _defaults = {
    'contract': lambda *a: 'trainee',
    'active': lambda *a: 1,
  }
 school_professor()

Observem que en la definició dels valors per defecte, hem utilitzat una funció lambda de Python, però no hagués estat necessari.

Una vegada actualitzat el mòdul school amb les noves millores, si obrim el formulari de manteniment de professors observarem que en donar d’alta un professor, en el camp Contract apareix per defecte el valor Trainee. En canvi, no apareix el nou camp Active, a causa que no hem modificat encara el formulari XML, però sí que podem veure la seva existència a la taula school_professor de la base de dades, amb valor True per tots els professors que ja existien a la taula i també pels nous professors.

Restriccions

Els objectes (classes) d’OpenERP poden incorporar, de forma opcional, restriccions d’integritat, addicionals a les que es puguin establir sobre la pròpia base de dades. OpenERP valida aquestes restriccions en les modificacions de dades i, en cas de violació, OpenERP mostra una pantalla d’error.

Les restriccions d’un objecte d’OpenERP es declaren a la classe Python que defineix l’objecte amb un atribut _constraints consistent en una llista de tuples, on cada tuple correspon a una restricció:

 _constraints = [
  (nomMètode,'missatgeError','llistaNomsCamps')
  (nomMètode,'missatgeError','llistaNomsCamps'),
  ...
 ]

Els tuples corresponents a una restricció venen definits per tres camps:

  • nomMètode: és el nom del mètode de l’objecte utilitzat per validar la restricció. El seu prototipus ha de tenir la forma def _nomMètode (self, cr, uid, ids) i ha de retornar un valor booleà.
  • missatgeError: és el missatge d’error que es mostrarà a l’usuari si la comprovació falla.
  • llistaNomsCamps: és la llista dels noms dels camps que s’afegeixen al missatge d’error amb l’objectiu d’ajudar a l’usuari a entendre el motiu pel qual ha fallat la validació de la restricció.

Els mòduls d’OpenERP incorporen validacions _constraints en nombrosos llocs. Un cas el trobem en la validació dels comptes bancaris segons la normativa IBAN.

Codis IBAN i BIC

El codi IBAN (International Bank Account Number) s’utilitza per a identificar a nivell internacional un compte bancari. Sempre comença per dos caràcters que identifiquen el país i va seguit d’una seqüència de dígits. En el cas dels comptes bancaris espanyols, comença per ES i va seguit de 22 dígits, dels quals, els 2 primers són dígits de control i els altres 20 corresponen al CCC (Codi Compte Client) utilitzat des de molt de temps a Espanya.

El codi BIC (Bank Identifier Code) serveix per identificar una entitat bancària.

A la xarxa trobareu gran quantitat de documentació relativa als codis IBAN i BIC.

Fixem-nos en el següent fragment de codi de la classe Python res_partner_bank definida en el mòdul base_iban (fitxer base_iban.py):

 _constraints = [
  (check_iban, _construct_constraint_msg, ["iban"]),
  (_check_bank, '\nPlease define BIC/Swift code on bank for bank type IBAN Account to make valid payments', ['bic'])
  ]

L’anterior atribut _constraints inclou dues restriccions:

  • check_iban: aquest és el nom del mètode declarat a la mateixa classe, que s’encarrega de validar el codi IBAN introduït per l’usuari. En cas que sigui erroni, la restricció informa que l’error es produeix en el camp iban i afegeix el missatge que retorna la crida al mètode _construct_constraint_msg (mètode que construeix el missatge ja que és força complex perquè difereix en cada país).
  • _check_bank: aquest és el nom del mètode declarat a la mateixa classe que s’encarrega de validar el codi BIC introduït per l’usuari (obligatori en cas que l’usuari hagi indicat que està introduint un codi IBAN). En cas que sigui erroni, la restricció informa que l’error es produeix en el camp bic i mostra el missatge d’error que indica la restricció.

La vista

OpenERP és un programari de gestió empresarial desenvolupat sobre el framework OpenObject de tipus RAD (Rapid Application Development) que facilita una arquitectura MVC (model-vista-controlador) per als desenvolupaments.

En les aplicacions desenvolupades sobre el framework OpenObject, el concepte vista engloba les pantalles que permeten exposar la informació a l’usuari (anomenades vistes o views) per a visualitzar-la i/o editar-la, i els menús, que faciliten un accés organitzat a les vistes. En conseqüència, ens cal aprendre a dissenyar les vistes (views) i els menús.

Els menús i vistes dissenyats en arxius XML, en instal·lar-se en una empresa d’OpenERP, obtenen un identificador numèric dins l’empresa i que OpenERP utilitza per a la gestió de l’aplicació. Aquest identificador acostuma a ser diferent a cada empresa, ja que no totes les empreses tenen els mateixos mòduls instal·lats ni han estat instal·lats en el mateix ordre. A causa que és molt possible que en la fase de disseny el dissenyador hagi de fer referència a menús i vistes es fa necessari un identificador XML per a cada vista/menú que el dissenyador pugui utilitzar. Per això, en la definició XML de menús i vistes s’inclou un identificador (text) que ha de ser únic dins el mòdul i al qual es pot fer referència des del propi mòdul a través del seu nom o des de qualsevol altre mòdul m a través de la sintaxi m.identificador.

Els menús

Dins la vista s’inclouen les diverses pantalles que permeten gestionar la informació i els menús que permeten un accés organitzat a les vistes.

Els menús d’un mòdul OpenObject es dissenyen en arxius XML i han d’estar referenciats des de l’apartat update_xml del fitxer descriptor del mòdul __openerp__.py. Els arxius XML acostumen a incorporar les pantalles (views) i els menús que permeten accedir a les pantalles. Els dos tipus d’elements, però, podrien residir en fitxers XML específics (un per menús i un per views).

Com a exemple, podem analitzar el mòdul school generat per l’eina Dia, en el qual observem la presència del fitxer anomenat school_view.xml:

"update_xml" : ['school_view.xml'],

Els menús d’OpenObject i les seves opcions han de tenir una declaració similar a:

    <menuitem id="menuitem_id"
    name="títol_del_menú"
    action="action_id"
    icon="nom_de_icona"
    web_icon="nom_icona_web"
    web_icon_hover="nom_icona_web_flotant"
    parent="menuitem_id"
    groups="grups_usuaris"
    sequence="<integer>"
    />

en la qual els valors específics de cada menú són:

  • id: conté l’identificador XML del menú, únic dins el mòdul.
  • name: conté el títol del menú. Aquest camp és opcional i, si no s’indica, el menú agafarà el nom de l’acció que executi el menú.
  • action: especifica l’identificador de l’acció que executa el menú i que ha d’existir en el mòdul. Quan no s’especifica l’acció s’entén que es tracta de l’arrel d’un menú que conté altres menús i/o opcions. El disseny de la corresponent acció depèn del tipus d’execució associada (obrir una finestra, executar un informe, posar en marxa un assistent…).
  • icon: especifica la icona que acompanya al menú en el client GTK. La icona per defecte és una carpeta. La llista d’icones possibles es pot consultar en el desplegable Icona del manteniment de menús accessible a través de Settings | Personalització | Intefície d’usuari | Elements menú.
  • web_icon: especifica la icona que es mostra en el client web, a la pàgina inicial (Home) .
  • web_icon_hover: especifica la icona que es mostra en el client web, a la pàgina inicial, en passar el ratolí pel damunt. Acostuma a ser la versió acolorida de la icona especificada a l’atribut web_icon.
  • parent: especifica l’identificador del menú pare del qual depèn. Si no es defineix, indica que es tracta d’un menú arrel (menú principal).
  • groups: especifica quin grup d’usuaris pot veure el menú. Si es desitja introduir més d’un grup, cal separar-lo per comes (per exemple, groups="admin,user").
  • sequence: és un enter que s’utilitza per ordenar el menú dins l’arbre de menús, de manera que a major valor, més avall apareix el menú. Aquest valor no és obligatori i per defecte pren el valor 10. Els menús que tinguin el mateix valor s’ordenen per moment temporal de creació.

Les icones dels atributs web_icon o web_icon_hover han de residir a la carpeta del mòdul i el seu nom ha d’anar acompanyat del camí que indiqui la subcarpeta (normalment images o icons) en la qual resideixen (o sense camí si no es troben a cap subcarpeta).

Accions en OpenObject

Les accions defineixen el comportament del sistema en resposta als esdeveniments generats per l’usuari, ja sigui en seleccionar una opció de menú, en prémer un botó, en fer clic damunt un registre…

Hi ha diferents tipus d’accions:

  • Window: per obrir una finestra.
  • Report: per imprimir un informe.
  • Wizard: per iniciar un assistent vinculat a un treball o procés.
  • Execute: per executar un mètode en el servidor.
  • Group: per reunir un conjunt d’accions en un grup.
En el Technical Memento d’OpenERP disponible a doc.openerp.com/memento hi trobareu el recordatori sobre el disseny de menús.

Exemple de menús en el mòdul school

Els menús originals del menú school generats per l’eina Dia per a la versió 5.0 d’OpenERP no són vàlids per a la versió 6.1 d’OpenERP, de manera que ens veiem obligats a refer-los segons les normes anteriors. Així, una possibilitat és la següent:

    <menuitem name="Courses" id="menu_school"/>
    <menuitem name="Calendar of Courses" id="menu_school_event"
    action="action_school_course_event" parent="menu_school"/>
    <menuitem name="Students" id="menu_school_student"
    action="action_school_student" parent="menu_school"/>
    <menuitem name="Configuration" id="menu_school_configuration"
    parent="menu_school"/>
    <menuitem name="Courses" id="menu_school_course"
    action="action_school_course"
    parent="menu_school_configuration"/>
    <menuitem name="Professors" id="menu_school_professor"
    action="action_school_professor"
    parent="menu_school_configuration"/>

En els elements menuitem anteriors detectem dues definicions de menús i quatre definicions d’opcions que executen accions. Cap dels dos menús incorpora l’atribut icon i, en conseqüència, en el client GTK la icona que els acompanya és una carpeta. El menú principal menu_school tampoc incorpora els atributs web_icon i/o web_icon_hover i, per tant, el mòdul Courses no va acompanyat de cap icona a la pàgina inicial del client web.

Vistes

Les vistes d’OpenERP són les pantalles que faciliten l’accés de l’usuari a la informació, tant per consultar-la com per modificar-la (altes, baixes i modificacions).

OpenObject facilita diversos tipus d’interfícies per facilitar l’accés de l’usuari a la informació (formularis, llistes, diagrames, gràfics, calendaris, arbres i targetes kanban) i totes elles són dinàmiques.

Les vistes d’OpenERP són dinàmiques i es construeixen en temps d’execució a partir de descripcions XML accessibles des del client, fet que en possibilita, fins i tot, la modificació en temps d’execució.

En cas que una vista es modifiqui en temps d’execució (des del client web activant el mode de desenvolupament, o des del client GTK, pel menú Settings | Personalització | Interfície d’usuari | Vistes), simplement cal tancar-la i reobrir-la per observar els canvis.

La descripció XML per les vistes associades a un objecte (classe) d’OpenERP, resideix en fitxers XML dins el mòdul corresponent a l’objecte. En cas de modificació en temps d’execució, el fitxer XML no es modifica i això cal tenir-ho molt present, ja que qualsevol procés d’actualització del mòdul provocarà la pèrdua dels canvis efectuats.

Per evitar la pèrdua de les modificacions efectuades a les vistes corresponents a mòduls susceptibles de ser actualitzats periòdicament (mòduls oficials i extres de la comunitat), el camí és crear un nou mòdul i definir-hi la nova vista com a herència de la vista a modificar, tot introduint els canvis en la vista heretada.


La descripció de les vistes ha de residir en un fitxer XML que ha d’estar referenciat des de l’apartat update_xml del fitxer descriptor del mòdul __openerp__.py. Així, en el mòdul school hi observem la presència del fitxer anomenat school_view.xml i en el fitxer __openerp__.py hi trobem la línia:

"update_xml" : ['school_view.xml'],

El nom del fitxer XML en el qual resideixen les vistes pot ser qualsevol, però és altament aconsellable utilitzar alguna combinació en la qual aparegui el nom del mòdul i quelcom que indiqui el seu contingut (com per exemple el mot view).

La declaració d’un vista ha de ser similar a:

    <record model="ir.ui.view" id="view_id">
    <field name="name">view.name</field>
    <field name="model">object_name</field>
    <!-Valors possibles: tree,form,calendar,search,graph,gantt,kanban-->
    <field name="priority" eval="16"/>
    <field name="arch" type="xml">
    <!-- view content: <form>, <tree>, <graph>, ... -->
    </field>
    </record>

en el qual els valors específics de cada vista són:

  • Element record que conté l’atribut model amb valor ir.ui.view obligatori per a les vistes, i l’atribut id amb l’identificador XML de la vista dins el mòdul.
  • Element field name="name" amb el nom de la vista.
  • Element field name="model" amb el nom de l’objecte (classe) d’OpenERP sobre el qual es defineix la vista.
  • Element field name="priority" amb la prioritat de la vista. Com més petita, més prioritat. Per defecte: 16.
  • Element field name="arch" type="xml" amb l’arquitectura o estructura de la vista que es defineix mitjançant diverses etiquetes XML. Aquesta estructura és diferent segons el tipus de vista (tree, form, calendar, search, graph, gantt, kanban).

Així doncs, cal endinsar-nos en l’estructura dels diversos tipus de vista i en els mecanismes (accions) que possibiliten a l’usuari la seva execució.

En el Technical Memento d’OpenERP disponible a doc.openerp.com/memento hi trobareu el recordatori sobre el disseny de vistes i accions.
Accions Window

Una vista d’OpenObject entra en execució a partir d’un esdeveniment d’usuari (seleccionar una opció de menú, prémer un botó…) que està vinculat a una acció window, responsable de la posada en marxa de la vista.

L’element field name="type" de la declaració d’una vista obliga a definir la vista amb un dels tipus següents: tree, form, calendar, search, graph, gantt, kanban. Cal tenir en compte que sota el tipus tree s’aixopluguen dos tipus diferents: llista jerarquitzada (normalment anomenada arbre -tree-) i llista no jerarquitzada (normalment anomenada llista -list-).

De la mateixa manera que els menús i les vistes, les accions (no només les window) es declaren en arxius XML que han d’estar referenciats des del fitxer descriptor del mòdul. A causa que les vistes es posen en marxa a través d’accions i que hi ha menús que porten associades aquestes accions, és molt comú (però no obligatori) introduir en el fitxer XML les declaracions en el següent ordre:

  • Vistes vinculades a un objecte (acostuma a haver-n’hi diverses).
  • Accions que executen vistes (pot ser única i que accioni diverses vistes).
  • Menús per facilitar a l’usuari l’execució d’accions.

Exemple de vistes, accions i menús relacionats

Fixem-nos en els següents fragments de vistes, accions i menús, en el mòdul school (versió school_04) corresponents a l’objecte professor:

    <record model="ir.ui.view" id="view_school_professor_form">
    <field name="name">school.professor.form</field>
    <field name="model">school.professor</field>
    <field name="type">form</field>
    <field name="arch" type="xml">
    ...
 
    <record model="ir.ui.view" id="view_school_professor_tree">
    <field name="name">school.professor.tree</field>
    <field name="model">school.professor</field>
    <field name="type">tree</field>
    <field name="arch" type="xml">
    ...
 
    <record model="ir.actions.act_window"
    id="action_school_professor">
    <field name="name">Professors</field>
    <field name="res_model">school.professor</field>
    <field name="view_type">form</field>
    <field name="view_mode">tree,form</field>
    </record>
 
    <menuitem name="Professors"
    id="menu_school_professor" action="action_school_professor"
    parent="menu_school_configuration"/>

Hi observem:

  • Dues vistes: view_school_professor_form (de tipus form) i view_school_professor_tree (de tipus tree).
  • Una acció: action_school_professor, que no està associada a cap de les dues vistes. Quina s’executa, doncs, en activar l’acció?
  • Un menú: menu_school_professor, associat a l’acció anterior.

En executar, des d’OpenERP, l’opció de menú Professors, observem com apareixen els professors en format llista (tree) i que la vista formulari (form) també es pot seleccionar.

Els elements field name="view_type" i field name="view_mode" defineixen, quan no s’explicita la vista a executar (com és el cas), el comportament d’OpenObject per escollir la vista.


L’exemple anterior serveix per il·lustrar que en OpenObject hi ha certs comportaments per defecte que s’activen quan el programador no els ha explicitat, com és el cas de l’elecció de la vista a executar quan el programador no la indica.

Abans de presentar com és la declaració d’una acció window és molt convenient entendre el comportament d’OpenObject en l’elecció i visualització de les possibles vistes sobre un objecte.

OpenObject categoritza totes les vistes (tree, form, calendar, search, graph, gantt i kanban) en dos grans grups:

  • tree per les vistes jeràrquiques, que permeten la visualització de dos tipus de vistes:
    • tree: visualització arbre
    • form: visualització formulari
  • form per les vistes no jeràrquiques, que permeten la visualització de diversos tipus de vistes:
    • tree: visualització llista
    • form: visualització formulari
    • calendar: visualització calendari
    • graph: visualització gràfic
    • gantt: visualització diagrama de gantt
    • search: visualització d’una zona de filtres
    • kanban: visualització dels recursos com a targetes agrupades sota un criteri, que poden ser editables i/o arrossegables

En la definició d’una acció window s’ha d’indicar si correspon a una vista jeràrquica (categoria tree) o una vista no jeràrquica (categoria form) i, una vegada definida la categoria, cal indicar –en l’ordre que interessi– els tipus de vista (adequats a la categoria) que permetrà visualitzar. Això s’aconsegueix amb dos elements que formen part de la definició XML d’una acció window:

  • Element field name="view_type" amb una de les dues categories possibles: form o tree.
  • Element field name="view_mode" amb una seqüència ordenada dels tipus de vista que facilita l’acció.

Els valors de view_mode, separats per comes, han de ser coherents amb el valor de view_type i, a més, el mòdul ha d’incorporar alguna vista per cadascun dels tipus indicats a la seqüència de view_mode. En cas que per algun tipus de vista dels indicats a view_mode, hi hagi diverses vistes definides en el mòdul, OpenObject escollirà la vista més prioritària (element priority de la definició de les vistes).

Exemple de view_type i view_mode en la definició d'accions window

En el mòdul school (versió school_04) observem el següent fragment d’acció window:

    <record model="ir.actions.act_window"
    id="action_school_professor">
    <field name="name">Professors</field>
    <field name="res_model">school.professor</field>
    <field name="view_type">form</field>
    <field name="view_mode">tree,form</field>
    </record>

En aquest cas, a causa que view_type té el valor form, l’element view_mode podria contenir, en principi, qualsevol seqüència ordenada dels valors tree, form, calendar, search, graph, gantt i kanban. Fixem-nos que la seqüència és "tree,form" i, en conseqüència, en executar l’acció (manteniment de professors), les dues vistes possibles són llista i formulari i el manteniment s’obre en vista llista. Si el contingut de view_mode hagués estat "form, tree" les vistes possibles haguessin estat les mateixes, però el manteniment s’obriria en mode formulari buit. Per tal que tot funcioni, el mòdul facilita per a l’objecte school.professor, com a mínim, una vista de tipus form i una vista de tipus tree no jeràrquica:

  • view_school_professor_form per a la vista form
  • view_school_professor_tree per a la vista tree no jeràrquica

En el mòdul school (versió school_04) observem el següent fragment d’acció window:

    <record model="ir.actions.act_window"
    id="action_school_course">
    <field name="name">Courses</field>
    <field name="res_model">school.course</field>
    <field name="view_type">form</field>
    <field name="view_mode">tree,form,calendar</field>
    </record>

Aquest és un altre cas en el qual view_type té el valor form, però, en canvi, l’element view_mode conté la seqüència "tree,form,calendar" i, en conseqüència, en executar l’acció (manteniment de cursos), tenim tres vistes possibles (llista, formulari i calendari) i el manteniment s’obre en vista llista. Ja que l’acció indica tres vistes possibles, el mòdul hauria de contenir, com a mínim, una vista de cada tipus, i en aquest cas, l’eina Dia, que ha generat l’element view_mode amb els tres tipus de vista, només facilita dues vistes:

  • view_school_course_form per a la vista form
  • view_school_course_tree per a la vista tree no jeràrquica

Al no crear una vista calendar, el tercer element del view_mode no funciona. Caldría fer una vista també per al calendar.


La declaració d’un acció window ha de ser similar a:

    <record model="ir.actions.act_window" id="action_id">
    <field name="name">action.name</field>
    <field name="view_type">form|tree</field>
    <field name="view_mode">...</field>
    <!-- combinació ordenada dels tipus de vista
    possible, coherent amb el contingut de
    l'element view_type -->
    <field name="view_id" ref="nomVista"/>
    <field name="search_view_id" ref="nomVistaDeCerca"/>
    <field name="domain">
    ["list of 3-tuples (max 250 characters)"]
    </field>
    <field name="context">
    {"context dictionary (max 250 characters)"}
    </field>
    <field name="res_model">Open.object</field>
    <field name="target">new</field>
    </record>

en la qual els valors específics per a cada acció són:

  • Element record que conté l’atribut model amb valor ir.actions.act_window obligatori per a les accions window i l’atribut id amb l’identificador XML de l’acció dins el mòdul.
  • Element field name="name" amb el nom de l’acció. És obligatori. Els menús sense name que invoquin l’acció, prenen el name de l’acció com a name propi.
  • Elements view_type i view_mode, estudiats prèviament.
  • Element field name="view_id" amb l’atribut ref que ha de contenir el nom de la vista per mostrar quan s’activa l’acció. En cas que aquest camp no estigui definit, OpenObject decideix en funció del contingut de l’element view_mode.
  • Element field name="search_view_id" amb l’atribut ref que ha de contenir el nom de la vista de cerca (capçalera de les pantalles que mostren una llista de registres, per facilitar-ne el filtrat) per mostrar quan s’activa l’acció.
  • Element res_model amb el nom de l’objecte sobre el qual opera l’acció.
  • Element domain amb una llista Python de condicions utilitzada per refinar els resultats d’una selecció i, en conseqüència, mostrar menys recursos a la vista. Les condicions de la llista s’enllacen amb una clàusula AND i són tuples Python de tres valors ('camp','condició','valor'). Per exemple: <field name="domain">[('code','=','0')]</field>
  • Element context amb un diccionari contextual que s’utilitzarà a la vista que s’obri en executar l’acció. Els diccionaris contextuals es declaren com un diccionari Python. Aquests diccionaris contenen informació de context a utilitzar, com per exemple l’idioma, la moneda, la tarifa, el magatzem…
  • Element target per indicar on s’ha d’obrir la finestra. Valors possibles: new (en una nova finestra), current (substituint la finestra actual) i inline (dins la finestra actual).
Vistes Form

La vista formulari per a un objecte d’OpenObject consisteix en la correcta distribució en la pantalla dels camps de l’objecte amb l’objectiu de facilitar-ne la visualització i/o l’edició d’un recurs. L’element arrel de l’XML que defineix l’arquitectura de la vista és form.

Els camps en una vista formulari d’OpenObject sempre estan distribuïts en la pantalla segons les següents normes:

  • Per defecte cada camp va precedit d’una etiqueta que és el seu nom.
  • Els camps se situen a la pantalla d’esquerra a dreta i de dalt a baix, segons l’ordre en què estan declarats en el fitxer XML que descriu la vista.
  • Cada pantalla es troba dividida en 4 columnes, cadascuna de les quals pot contenir una etiqueta o un camp. Ja que cada camp és precedit (per defecte) per una etiqueta amb el seu nom hi haurà dos camps amb les seves respectives etiquetes a cada línia de la pantalla.

OpenObject permet:

  • Que un camp pugui ocupar més d’una columna, tot utilitzant l’atribut colspan.
  • Agafar un grup de columnes i dividir-les en les columnes que es desitgi, tot utilitzant l’etiqueta group i els atributs colspan i col.
  • Distribuir els camps d’un objecte en diverses pestanyes, tot utilitzant les etiquetes notebook i page.

Exemple:

    <record model="ir.ui.view" id="view_school_professor_form">
    <field name="name">school.professor.form</field>
    <field name="model">school.professor</field>
    <field name="type">form</field>
    <field name="arch" type="xml">
    <form string="school.professor">
    <field name="name" select="1"/>
    <field name="contract" select="2"/>
    <field name="partner_id" select="0"/>
    <field name="address_id" select="0"/>
    <field name="phone" select="0"/>
    <field name="hours_available" select="0"/>
    <field name="course_ids" colspan="4" select="0"/>
    </form>
    </field>
    </record>

En aquest cas, detectar dins el fitxer school_view.xml el registre corresponent a la vista no ha estat difícil, però en altres casos en els quals hi ha moltes vistes pot ser més dificultós. En canvi, des del client web, tenint obert el formulari i el mode de desenvolupament activat, podem demanar d’editar la vista, obtenint el formulari de la figura.3, en la qual observem el nom de la vista i, a més, podem modificar-la i enregistrar els canvis en el servidor OpenERP.

Recordem que els canvis no queden enregistrats en el fitxer XML del mòdul.

Fixem-nos que el contingut XML de l’element arch no és altre que el conjunt de camps a visualitzar, de l’objecte (classe) d’OpenERP en el qual es basa la vista. Recordem la definició de l’objecte school.professor (versió school_04):

    class school_professor(osv.osv):
    _name = 'school.professor'
    _columns = {
    'name': fields.char(...),
    'contract': fields.selection(...),
    'partner_id': fields.many2one(...),
    'address_id': fields.many2one(...),
    'phone': fields.related(...),
    'hours_available': fields.integer(...),
    'course_ids': fields.one2many(...),
    'active': fields.boolean('Active'),
    }
    ...

La definició de la classe Python school_professor incorpora el camp active que no apareix a la vista school.professor.form. Això és a causa que la vista school.professor.form que estem analitzant va ser generada per l’eina Dia i el camp active el vam incorporar posteriorment en el codi Python sense modificar la corresponent vista.

Suposem que volem situar el camp active, que correspon a una casella de verificació, a la 4a fila del formulari. Simplement l’haurem de situar en 7è lloc dins l’XML de l’element arch, entre els camps hours_available i course_ids. Si efectuem la modificació en el fitxer school_view.xml caldrà actualitzar el mòdul; en canvi, si efectuem la modificació des dels clients web o GTK només cal recarregar el formulari. Sigui quin sigui el camí, observarem el camp active amb etiqueta Active a la 4a línia de la pantalla.

En el formulari school.professor.form anterior també podem observar com el camp one2many de nom course_ids va acompanyat de l’atribut colspan=4 que indica que el camp (conjuntament amb la seva etiqueta Courses) ha d’ocupar les 4 columnes de la pantalla.

Dins l’estructura del formulari (contingut de l’element field name="arch") hi pot haver diversos tipus d’elements i aquests elements poden tenir diversos atributs. La taula.1 mostra els atributs comuns als diversos tipus d’elements possibles i la taula mostra els diversos tipus d’elements possibles acompanyats dels atributs específics de cada tipus d’element.

Taula Relació d’atributs comuns pels elements de la definició d’una vista form

Atribut Utilització
string Etiqueta de l’element que substitueix l’etiqueta definida a la classe.

També s’utilitza en els processos de recerca.

nolabel Permet amagar (valor="1") l’etiqueta del camp.
colspan Permet indicar el nombre de columnes que ha de tenir el camp.
rowspan Permet indicar el nombre de files que ha de tenir el camp.
col Permet indicar el nombre de columnes en què es divideix l’espai assignat al camp.
invisible Permet ocultar (valor="1") el camp i la seva etiqueta.
eval Cadena avaluada com a codi Python per obtenir el valor del camp.
attrs Permet indicar sota quines condicions dinàmiques, basades en els valors d’altres camps del formulari, l’element ha de ser readonly i/o invisible i/o required. Ha de seguir el format:
{'atribut':[('nomCamp','operador','valor'), ('nomCamp','operador','valor')...],...}

on atribut ha de ser readonly o invisible o required.


Exemples d'utilització de l'atribut attrs en la ubicació dels camps en vistes

En els mòduls d’OpenERP es pot veure la utilització de l’atribut attrs en múltiples vistes. Per exemple, en la vista Leave Request del mòdul hr_holidays (fitxer hr_holidays_view.xml):

   <field name="name"
   attrs="{'readonly':[('state','!=','draft'),
   ('state','!=','confirm')]}"/>

El camp name serà de només lectura quan el camp state no tingui els valors draft ni confirm.

   <field name="category_id"
   attrs="{'required':[('holiday_type','=','category')],
   'readonly':[('state','!=','draft')]}"/>

El camp category_id serà obligatori quan el camp holiday_type valgui category i de només lectura quan el camp state no valgui draft.

Taula Relació dels elements possibles en la definició d’una vista form

Element Utilització
field Camp per visualitzar, d’entre les columnes definides a l’objecte (classe) OpenERP en la qual es basa la vista.

Cada camp porta un giny (widget) associat segons el tipus de dada del camp. Així, si el camp és de tipus booleà, el giny és una casella de verificació; si el camp és de tipus date el giny és un camp formatat per a data acompanyat d’un calendari per poder seleccionar la data amb comoditat, com mostra la .
La taula següent mostra els atributs específics d’aquest element.

button Giny (widget) en forma de botó per ser premut i que està associat a determinades accions.

La taula següent mostra els atributs específics d’aquest element.

separator Línia de separació horitzontal per estructurar vistes, amb etiqueta opcional.
newline Permet afegir espai en blanc per completar la línia actual de la vista i saltar a la següent.
label Títol de text lliure en el formulari.
group Permet organitzar els camps en grups amb etiquetes opcionals. Aporta un requadre al voltant del grup.
notebook
page
Un element notebook és un contenidor de pestanyes, definides cadascuna per un element page.

Els atributs específics d’aquest element són:

  • name: etiqueta per la pestanya
  • position: posició de la pestanya dins el notebook, amb valors possibles: inside, top, bottom, left i right.


Taula Relació d’atributs específics per l’element field en la definició d’un form

Atribut Significat
select 1 per mostrar el camp en cerques normals i 2 per mostrar el camp només en cerques avançades.

A partir de la versió 6 d’OpenERP, les cerques avançades han deixat d’existir i, en canvi, els clients faciliten l’opció de filtres avançats en els quals es pot escollir qualsevol dels camps del model. Per tant, el valor 2 és obsolet a partir de la versió 6 d’OpenERP.
La utilització d’aquest atribut queda obsoleta en el moment en què la versió 6 d’OpenERP facilita les vistes search.

required Substitueix l’atribut required definit en el model.

1 per tal que el camp sigui obligatori.

readonly Substitueix l’atribut readonly definit en el model.

1 per tal que el camp sigui de només lectura.

default_focus Valor 1 per indicar que aquest camp ha de tenir el focus en obrir el formulari. Només hi pot haver un element amb aquest atribut a 1.
password True per ocultar els caràcters que s’introdueixen en el camp.
context Codi Python per definir un diccionari contextual.
domain Codi Python per definir una llista de tuples amb condicions per restringir valors, en camps relacionals.
on_change Mètode Python que s’executa en canviar el valor del camp. S’utilitza per calcular automàticament el valor d’altres camps quan el camp actual canvia.
groups Llista Python d’identificadors de grups d’usuaris autoritzats a visualitzar el camp.
widget Giny alternatiu al proporcionat de forma automàtica segons el tipus del camp. Valors possibles:
  • date
  • float_time
  • datetime
  • selection
  • number
  • many2one_list
  • one2many_list
  • many2many
  • url
  • email
  • image
  • reference
  • text_wiki
  • text_html
  • progressbar


Taula Relació d’atributs específics per l’element button en la definició d’un form

Atribut Significat
type Tipus de botó. Valors possibles:
  • workflow (flux de treball, valor per defecte): envia el senyal (indicat a l’atribut name) al flux de treball del mòdul.
  • object: crida la funció o mètode indicada a l’atribut name.
  • action: executa l’acció indicada a l’atribut name.
name Segons el valor de l’atribut type:
  • Senyal del flux de treball
  • Nom de la funció o mètode a executar
  • Nom de l’acció a executar
special Únicament és operatiu en una finestra emergent (pop-up) i en cas d’utilitzar-lo en una finestra no emergent, no és operatiu.

Només té un possible valor: cancel.
És incompatible amb l’atribut type.

confirm Text de missatge de confirmació per quan es prem el botó.
states Llista d’estats, separats per comes, en els quals el botó es mostra. Si aquest atribut no apareix, el botó és sempre visible.
icon Nom d’icona. Per defecte, el botó és textual.

Es pot utilitzar qualsevol de les icones existents a la subcarpeta web/static/src/img/icons de la carpeta addons on es troben els mòduls o qualsevol altra icona indicant la seva ubicació (normalment en una subcarpeta images o icons dins el mòdul).

default_focus Valor a 1 per indicar que aquest botó ha de tenir el focus en obrir el formulari. Només hi pot haver un element amb aquest atribut a 1.

Valors per defecte en un one2many

Quant creem un one2many en el mode form (o tree editable) ens permet crear elements d'aquesta relació. Per a aconseguir que, al crear-los, el camp many2one corresponga al pare des del que es crida, es pot fer amb el context: Dins del field one2many que estem fent fiquem aquest codi:

context="{'default_<camp many2one>':active_id}"
Vistes Tree

Les vistes arbre/llista per a un objecte d’OpenObject consisteixen en la distribució de la pantalla en línies amb l’objectiu de facilitar la visualització i/o l’edició d’un conjunt de recursos de l’objecte. La majoria de les vistes arbre/llista són de només visualització, però OpenObject permet fer-les editables.

La vista arbre mostra els recursos de l’objecte seguint una estructura jeràrquica, mentre que la vista llista mostra els recursos en seqüència, sense cap jerarquia.

En OpenERP tenim una gran quantitat de vista llista com, per exemple, en accedir a l’accés directe Clients, en el qual se’ns presenta els clients en vista llista. OpenERP també inclou exemples de vista arbre; un exemple el trobem en accedir a l’opció de menú Magatzem | Productes | Productes per categoria.

Les vistes arbre/llista es distingeixen perquè en la seva declaració incorporen:

   <field name="type">tree</field>

i l’element arrel de l’XML que defineix l’arquitectura de la vista és tree.

La diferència entre les vistes arbre i vistes llista en la declaració radica en l’existència, en les vistes arbre, del següent element per indicar el camp one2many que determina la jerarquia:

   <field name="field_parent">camp_one2many</field>

Les vistes arbre/llista són més simples que les vistes formulari i tenen menys opcions. De manera similar a les vistes formulari, incorporen en la seva estructura elements field. La taula.5 recull els atributs que poden acompanyar l’element arrel tree.

Taula Relació d’atributs per l’element tree en la definició d’un tree

Atribut Significat
colors Llistes de colors definides en un diccionari Python per tal que les línies es puguin acolorir segons diferents condicions.
editable En cas d’estar definit, els valors possibles són top i bottom i permeten editar els registres directament a la llista, sense necessitat de passar a la vista formulari. Si el valor és top, els nous registres s’afegeixen al capdamunt de la llista i si el valor és bottom, els nous registres s’afegeixen al final.
toolbar Només per a les vistes arbre.

Valor a True per mostrar el nivell superior de la jerarquia d’objectes amb una barra d’eines lateral.


Exemple d'anàlisi del codi de la vista arbre Productes per categoria

El codi de la vista arbre Productes per categoria és:

    <record id="product_category_tree_view" model="ir.ui.view">
    <field name="name">product.category.tree</field>
    <field name="model">product.category</field>
    <field name="type">tree</field>
    <field name="field_parent">child_id</field>
    <field name="arch" type="xml">
    <tree toolbar="True" string="Product Categories">
    <field name="name"/>
    </tree>
    </field>
    </record>

Veiem que l’element field_parent informa que l’arbre es munta a partir de la jerarquia indicada pel camp child_id. Fixem-nos com és la definició de la classe product.category:

    class product_category(osv.osv):
    ...
    _name = "product.category"
    _description = "Product Category"
    _columns = {
    ...
    'parent_id':
    fields.many2one('product.category', 'Parent Category',
    select=True, ondelete='cascade'),
    'child_id':
    fields.one2many('product.category', 'parent_id',
    string='Child Categories'),
    ...

Si modifiquem la vista product_category_tree_view eliminant l’atribut toolbar, cosa que podem aconseguir ràpidament des del client web, i tornem a demanar l’opció Productes per categoria, veurem que el desplegable per escollir el recurs de primer nivell de la jerarquia ha desaparegut i directament ens facilita la llista de tots els recursos de primer nivell.

Exemple d'anàlisi de la vista llista Productes

Quan accedim a Productes (ja sigui amb la drecera o des de l’opció de menú), apareixen els productes en vista llista. La majoria dels productes apareixen de color negre, però n’hi ha alguns en vermell i alguns en blau. Això és a causa que s’està utilitzant l’atribut colors en la definició de l’element tree.

El codi de la vista llista és:

    <record id="product_product_tree_view"
    model="ir.ui.view">
    <field name="name">product.product.tree</field>
    <field name="model">product.product</field>
    <field name="type">tree</field>
    <field eval="7" name="priority"/>
    <field name="arch" type="xml">
    <tree
    colors="red:virtual_available&lt;0;
    blue:virtual_available&gt;=0 and
    state in ('draft','end','obsolete');
    black:virtual_available&gt;=0 and
    state not in ('draft','end','obsolete')"
    string="Products">
    <field name="default_code"/>
    <field name="name"/>
    <field name="categ_id" invisible="1"/>
    <field name="variants"
    groups="product.group_product_variant"/>
    <field name="uom_id" string="UoM"/>
    <field name="type"/>
    <field name="qty_available"/>
    <field name="virtual_available"/>
    <field name="lst_price"/>
    <field name="price"
    invisible="not context.get('pricelist',False)"/>
    <field name="standard_price" groups="base.group_extended"/>
    <field name="state" groups="base.group_extended"/>
    <field name="company_id"
    groups="base.group_multi_company"
    invisible="1"/>
    </tree>
    </field>
    </record>

Observem-hi el contingut de l’atribut colors a l’element tree: es defineix el color que ha de mostrar el registre en funció dels valors dels camps virtual_available i state.

Si voleu que la llista sigui editable només heu d’afegir l’atribut editable a l’element tree, amb el valor top o bottom segons vulgueu que els nous registres s’afegeixin per dalt o per baix. Podeu comprovar-ho, de manera ràpida, modificant la vista des del client web.

Els ginys one2many i many2many emprats en les vistes form mostren la informació de la vista llista associada als objectes referenciats pels camps one2many i many2many.

L’element tree també s’utilitza per modificar l’estructura dels ginys one2many i many2many en una vista form. Per aconseguir-ho, a l’element field corresponent al camp one2many o many2many se li ha d’afegir un element fill tree, que haurà de contenir els elements fields que es vulguin visualitzar en el giny. És a dir, mentre que el funcionament automàtic dels ginys one2many i many2many mostra la vista llista associada a l’objecte referit en el camp one2many i many2many, amb l’element tree podem alterar-la i mostrar aquells camps que interessi i amb les característiques adequades (ordre, color…).

Cal tenir en compte, també, que si en un element tree explicita una columna one2many o many2many, OpenERP hi visualitzarà el nombre de recursos (registres) que en aquell moment estan referenciats.

De la mateixa manera que en un camp one2many o many2many d’una vista form, la utilització de l’element tree permet explicitar les columnes a visualitzar (exemples anteriors), ja que si no s’utilitza OpenERP mostra tots els camps dels objectes referenciats, pot interessar que l’edició en vista form de qualsevol dels recursos mostrats en un giny one2many rebi algun camp ja emplenat i no sigui editable.


Vistes Calendar

Les vistes calendari per a un objecte d’OpenObject són possibles quan l’objecte conté camps date o datetime que permeten situar cada recurs en un interval de temps (un dia, un dia-hora o un interval de dies) i faciliten la visualització i/o l’edició dels recursos dins el seu interval de temps.

Pot contenir els següents atributs:

  • string, per al títol de la vista
  • date_start, que ha de contenir el nom d’un camp datetime o date del model.
  • date_delay, que ha de contenir la llargada en hores de l’interval. Aquest atribut té preferència sobre l’atribut date_stop.
  • date_stop, que ha de contenir el nom d’un camp datetime o date del model. Aquest atribut és ignorat si existeix l’atribut date_delay.
  • day_length, per indicar la durada en hores d’un dia. OpenObject utilitza aquest valor per calcular la data final a partir del valor de date_delay. Per defecte, el seu valor és 8 hores.
  • color, per indicar el camp del model utilitzat per distingir, amb colors, els recursos mostrats a la vista.
  • mode, per mostrar l’enfoc (dia/setmana/mes) amb el què s’obre la vista. Valors possibles: day, week, month. Per defecte, month.
 <record model="ir.ui.view"
 id="view_school_course_event_calendar">
 <field name="name">school.course.event.calendar
 </field>
 <field name="model">school.course.event</field>
 <field name="type">calendar</field>
 <field name="arch" type="xml">
 <calendar color="full_name" date_start="date"
 date_stop="date_end">
 <field name="id"/>
 <field name="course_id"/>
 <field name="name"/>
 </calendar>
 </field>
 </record>
Vistes Graph

Les vistes gràfic són un tipus de vista que permet la visualització de gràfics construïts a partir de les dades contingudes en els recursos.

pot contenir els següents atributs:

  • string, per al títol de la vista
  • type, per al tipus de gràfic. Per defecte és un gràfic de sectors. Cal indicar el valor bar per un gràfic de barres verticals o line per a una linea.
  • orientation, per indicar el valor horizontal quan type sigui bar i es desitgi un gràfic de barres horitzontal.

La definició dels elements fills de l’element arrel graph determina el contingut del gràfic:

  • El primer camp indica el contingut de l’eix X (horitzontal). És obligatori.
  • El segon camp indica el contingut de l’eix Y (vertical). És obligatori.
  • El tercer camp indica el contingut de l’eix Z en gràfics tridimensionals. És optatiu.

A cadascun dels camps que determinen els eixos, se’ls pot aplicar els atributs següents:

  • group=“True”, a utilitzar en el segon o tercer camp, per indicar que cal agrupar tots els recursos d’igual valor per aquest camp. El primer camp sempre actua amb group=“True”. Per la resta de camps, s’aplica l’operador indicat a l’atribut operator.
  • operator, per indicar l’operador a utilitzar per calcular el valor en la resta dels camps, com a conseqüència de l’agrupació indicada a l’atribut group. Els operadors permesos són: + (suma), *(producte), **(exponent), min i max (menor i major valors de la llista de valors de l’agrupació). Per defecte, actua l’operador +.
 <record model="ir.ui.view" id="view_school_course_graph">
   <field name="name">school.course.graph</field>
   <field name="model">school.course</field>
   <field name="arch" type="xml">
      <graph string="Percentage of hours">
        <field name="name"/>
        <field name="hours_total"/>
      </graph>
   </field>
 </record>
Les vistes graph en Openerp 7.0 són molt limitades. Sols accepten un camp en l'eix vertical i no poden tindre camps function. https://services.openerp.com/es_ES/forum/help-1/question/graph-view-use-fields-function-53549

Si volem ficar-lo dins d'un form comun camp one2many, cal especificar un domain:

   <field name="id"/> <!-- És necessari el camp id, pot ser invisible -->
   <field name="rounds" mode="tree,graph" domain="[('team','=', id)]" >
      <graph string="Points" type="line">
          <field name="round"/>
          <field name="point_v" />
      </graph>
Vistes Search

Les vistes search són per a afegir filtres als trees o altres vistes. Els filtres poden ser d'agrupació o de filtrat.

L’element arrel de l’XML que defineix l’arquitectura de la vista és search i pot tenir els següents elements fills:

  • group: per agrupar un conjunt de filtres i/o condicions d’agrupament sota un mateix títol, que s’indica a l’atribut string. No és obligatòria l’existència del títol. També admet l’atribut expand (valors possibles: 0/1 – per defecte: 1) per indicar si el grup ha d’aparèixer expandit (1) o no (0).
  • separator: per situar un separador, amb atribut orientation, que per defecte és vertical. En una vista search no té sentit el separador horitzontal. El separador no es visualitza en el client web d’OpenERP 6.1.
  • label: per situar una etiqueta.
  • field: per situar un camp textual de filtre en el qual l’usuari ha d’escriure un valor.
  • filter: per situar un botó que permet filtrar i/o agrupar sota unes determinades condicions definides en el botó.
  • newline: per provocar un salt de línia.

Pel que fa a l’element field, cal saber que:

  • En els camps many2one, quan l’usuari comença a escriure un valor, OpenObject mostra els valors possibles que comencen per aquell valor; si es vol que el camp incorpori un desplegable, cal utilitzar l’atribut widget="selection".
  • Pot anar acompanyat de l’atribut context.
  • OpenObject realment construeix un domini [('camp','=','valor')] amb el valor introduït en el camp. Es pot alterar l’operador = de la condició utilitzant l’atribut operator. Així mateix, es pot alterar el domini anterior per un domini personalitzat, amb l’atribut filter_domain i, en tal cas, indicar el domini com una llista Python de condicions, en la qual les condicions són tuples Python de tres elements: ('camp','condició','valor').

La sintaxi de l’element filter és:

    <filter string="Etiqueta" icon="nomIcona"
    domain="llistaCondicions"
    help="missatgeAjuda"
    context="{'group_by':'llistaCamps'}/>

en la qual:

  • L’atribut icon ha de contenir una de les icones d’OpenERP, de manera anàloga a l’atribut icon dels elements menuitem.
  • L’atribut domain serveix per indicar el filtre i és, com en les accions, una llista Python de condicions que s’enllacen amb una clàusula AND; les condicions són tuples Python de tres elements: ('camp','condició','valor').
  • L’atribut context serveix per indicar un context i, amb el valor clau group_by, per aconseguir agrupar els registres pels camps indicats a llistaCamps, separats per comes.

L’element filter es pot ubicar com a fill d’un element field per indicar que el seu filtre s’aplica específicament sobre el camp. En aquest cas, el botó és més petit i s’adequa a l’altura d’un camp field.

Per finalitzar amb les vistes search recordeu que el nom de la vista search que es vol utilitzar s’ha d’indicar a l’element search_view_id de l’acció window que invoca la vista.

Per exemple, en el mòdul del campionat:

 <record model="ir.ui.view" id="view_championship_round_search">
   <field name="name">championship.round.search</field>
   <field name="model">championship.round</field>
 
   <field name="arch" type="xml">
	<search string="Search Round">			
	        <filter string="Team" icon="terp-gtk-jump-to-rtl" domain="[]" context="{'group_by':'team'}" help="Team"/>
		<field name="team" string="team" domain="[]"/>
		<field name="championship" string="championship" domain="[]"/>
                <filter string="Valencia" icon="terp-gtk-jump-to-rtl" domain="[('team','=','VALENCIA')]" context="" help="Team"/>
 
       </search>
 
   </field>
 </record>

El primer filter agrupa les rondes per equip. El segon, permet buscar més ràpidament sobre un camp concret. En Openerp 7.0 no apareix el camp fins que no escrivim alguna cosa dins que es parega a alguns resultats de la cerca. El quart, filtra específicament per les rondes del Valencia.

El Controlador

Part del controlador l'hem mencionat al parlar dels camps function. No obstant, cal comentar les facilitats que proporciona OpenObject per a no tindre que accedir dirèctament a la base de dades.

Per encarar amb garanties el disseny de mètodes en OpenObject es pressuposa uns coneixements mínims de disseny de mètodes en Python.

La capa ORM d’OpenObject facilita un seguit de mètodes que s’encarreguen del mapatge entre els objectes Python i les taules de PostgreSQL. Així, disposem de mètodes per crear, modificar, eliminar i cercar registres a la base de dades. Aquests mètodes són utilitzats de manera automàtica per OpenObject en l’execució dels diversos tipus de vista que OpenObject ens permet dissenyar.

En ocasions, però, pot ser necessari alterar l’acció automàtica de cerca – creació – modificació – eliminació facilitada per OpenObject i, llavors, haurem de sobreescriure els corresponents mètodes en les nostres classes.

Com exemple d’aquesta necessitat, podem considerar el cas de la gestió de comandes de venda (classe sale_order) dins el fitxer sale.py del mòdul sale d’OpenERP. Si hi fem una ullada, al final de la classe hi trobem el disseny dels mètodes unlink (eliminar), create (crear) i write(modificar). Cadascun d’ells executa un seguit de comprovacions i/o accions i, si tot és correcte, invoca la crida dels corresponents mètodes unlink, create i write de la capa ORM. Així, el mètode unlink (eliminació de comandes) comprova si les comandes a eliminar tenen l’estat draft o cancel (estats en els quals l’eliminació és permesa, segons la lògica de negoci) i si alguna de les comandes no és eliminable genera una excepció tot avortant l’eliminació; en canvi, si totes són eliminables, procedeix a l’eliminació tot invocant el mètode unlink subministrat per la capa ORM.

Els programadors en el framework OpenObject hem de conèixer els mètodes subministrats per la capa ORM i hem de dominar el disseny de mètodes per:

  • Poder definir camps funcionals en el disseny del model.
  • Poder definir l’acció que cal executar en modificar el contingut d’un field d’una vista form (atribut on_change del field)
  • Poder alterar les accions automàtiques de cerca, creació, modificació i eliminació de recursos.

Una darrera consideració a tenir en compte en l’escriptura de mètodes i funcions en OpenObject és que els textos de missatges inclosos en mètodes i funcions, per poder ser traduïbles, han de ser introduïts amb la sintaxi _('text') i el fitxer .py ha de contenir from tools.translate import _ a la capçalera.

MVC vs SQL

Imaginem que volem guardar en la variable sale el contingut de un registre del model sale.order que correspon a la taula sale_order. El ORM ens proporciona aquesta ferramenta:

sale = self.browse(cr, uid, ID)

On cr és la fila actual de la base de dades, uid és l'usuari i ID és el identificador de la fila que volem. (Poden ser varios ids en un diccionari python).

Imaginem que volem extraure el contry name del primer contacte del partner del sale anterior.

country_name = sale.partner_id.address[0].country_id.name

Si volem fer la mateixa operació amb SQL:

 cr.execute('select partner_id from sale_order where id=%d', (ID,))
 partner_id = cr.fetchone()[0]
 cr.execute('select country_id from res_partner_address where partner_id=%d', (partner_id,))
 country_id = cr.fetchone()[0]
 cr.execute('select name from res_country where id=%d', (country_id,))
 del partner_id
 del country_id
 country_name = cr.fetchone()[0]

Pot ser que sigues més eficient programant amb SQL, però has de tindre en compte que ORM mapeja la base de dades i crea les seues propies taules per a les relacions mols a molts. Amés, imagina que, després, vols accedir a altres dades, no faria falta tornar a fer un altre SQL:

partner_name = sale.partner_id.name

Imaginem que volem calcular el preu total per paisos de varies vendes:

def get_totals(self, cr, uid, ids):
   countries = {}
   for sale in self.browse(cr, uid, ids):
      country = sale.partner_invoice_id.country
      countries.setdefault(country, 0.0)
      countries[country] += sale.amount_untaxed
   return countries

I per a imprimir-les:

def print_totals(self, cr, uid, ids):
   result = self.get_totals(cr, uid, ids)
   for country in result.keys():
      print '[%s] %s: %.2f' (country.code, country.name, result[country])

En total s'han fet 4 consultes SQL. Si es fa sobre 1000 vendes, sols necessitem 4 SQL. Per tant, pot ser molt més eficient que si els fem manualment.

Mètodes ORM

La majoria de mètodes ORM que proporciona OpenObject tenen els paràmetres següents:

  • cr: cursor de la base de dades
  • uid: identificador de l’usuari que executa el mètode
  • ids: llista d’enters amb els identificadors dels recursos als quals s’aplica el mètode
  • context: diccionari Python amb un seguit de paràmetres que poden ser necessaris en l’execució del mètode, com per exemple: idioma, zona horària, companyia…
Recordem que OpenObject té, per cada classe Python, un únic objecte en memòria per gestionar tots els recursos del model associat a la classe. Per això, un mètode s’invoca sempre sobre l’únic objecte en memòria i rep, a través del paràmetre ids, la llista dels identificadors dels recursos sobre els quals actuar.

Mètode per obervar el valor dels paràmetres.

	def unlink(self, cr, uid, ids, context=None):
		print "Contingut de uid : " + str(uid)
		print "Llista ids amb '%d' valors: " % len(ids)
		for id in ids:
			print "\t'%d'" % id
		print "Diccionari context amb '%d' tuples: " % len(context)
		for val in context.items():
			print "\t'%s'" % (val,)
		return osv.osv.unlink(self, cr, uid, ids, context=context)

El per què del paràmetre ids en la definició d'un mètode d'OpenObject

Per ajudar a entendre la necessitat del paràmetre ids, obrim la vista tree de professors. Suposem que tenim diversos professors i n’efectuem una selecció (un o més d’un) per eliminar-los. En el moment que premem el botó Delete i confirmem l’eliminació, OpenObject invoca el mètode unlink sobre l’únic objecte school_professor existent en memòria i li passa la llista dels identificadors dels professors seleccionats, de manera que el mètode unlink pot eliminar-los.

El paràmetre context és un calaix de sastre ideat per OpenObject per ficar-hi tots aquells paràmetres que el mètode pot necessitar i que en canvi no s’ha previst en el prototipus del mètode.

Alguns dels mètodes ORM més utilitzats són:

  • Mètode read
  • Mètode name_get
  • Mètode browse
  • Mètode self.pool.get
  • Mètode search
  • Mètode create
  • Mètode write
  • Mètode unlink

Mètode read

read (self, cr, uid, ids, fields=None, context=None)

fields : llista de camps dels quals es vol obtenir el contingut. Per defecte, tots els camps.

Per cada diccionari d’ids, obté un diccionari de parelles camp:valor corresponent als camps indicats en el paràmetre fields i retorna una llista amb tots els diccionaris. Cada diccionari incorpora, encara que no s’hagi explicitat a fields, la parella id:valor.

Mètode name_get

name_get (self, cr, uid, ids, context=None)

Retorna una llista de tuples (id, valor) amb la representació textual dels recursos d’ids. Recordem que la representació textual d’un recurs és, en principi, el contingut del camp name (camp especial de l’atribut _columns de la classe Python) o, si està definit, el camp indicat a l’atribut _rec_name de la classe Python. Quan OpenObject necessita la representació textual d’un recurs, invoca automàticament el mètode name_get.

En ocasions, pot interessar que la representació textual d’un recurs sigui el resultat d’un determinat càlcul i llavors caldrà sobreescriure aquest mètode.

Exemple d'utilització dels mètodes read i name_get en el mòdul school

La classe school.student té les columnes idnum, name i surname. Per tant, la representació textual d’un alumne és el seu nom i això és força adequat, però preferim que sigui la concatenació de cognom i nom, separats per una coma i un espai. Per aconseguir-ho cal sobreescriure el mètode name_get a la classe school_student:

    def name_get (self, cr, uid, ids, context=None):
     res = []
     records = self.read(cr, uid, ids, ['name','surname'])
     for r in records:
      res.append((r['id'], r['surname']+", "+r['name']))
     return res

Fixem-nos que per poder generar la concatenació indicada ens cal per a cada recurs conèixer el cognom i el nom, i per aconseguir-ho hem utilitzat el mètode read. Observem, també, que name_get ha de retornar una llista de tuples i, per això, en invocar la funció append de Python, en el seu interior construïm el tuple (r['id'],...).

La modificació efectuada a la classe school_student no és visible ja que no hi ha cap vista en la qual hagi d’aparèixer la representació textual d’un estudiant. Per forçar la utilització del nou mètode name_get ens plantegem que a la vista tree de l’objecte school.student, en comptes d’aparèixer les columnes Name i Surname per separat, aparegui una única columna Full Name. Per aconseguir-ho, cal afegir al model school.student el camp funcional full_name. Segons la definició dels camps funcionals, la funció que ha d’invocar un camp funcional ha de tenir un determinat nombre d’arguments, que no són els del mètode name_get. En conseqüència, cal crear una nova funció _name_get_fnc per utilitzar en la definició del camp funcional.

    def _name_get_fnc(self, cr, uid, ids, prop, unknow_none,context):
     res = self.name_get(cr, uid, ids, context)
     return dict(res)

A la zona _columns de school.student hi afegim el camp:

    'full_name': fields.function(_name_get_fnc, type='char', size='66',string='Full Name',readonly='True'),

A causa que la funció invocada per un camp funcional ha de retornar un diccionari de parelles id:valor i que la funció name_get retorna una llista de tuples (id, valor), ens va molt bé utilitzar la funció Python dict que, donada una llista de tuples de parelles, ens retorna el corresponent diccionari.

Per finalitzar l’exemple, comentar que sembla recomanable afegir l’atribut _order a la classe school_student per visualitzar els estudiants ordenats per cognom.

Per comprovar la visualització de la columna Full Name en la vista tree de school.student, cal retocar la vista.

Un altra manera de provar el que fa la funció name_get és creant un camp many2one en courses anomenat delegate:

 'delegate': fields.many2one('school.student','Delegate'),

Mètode browse

browse (self, cr, uid, ids, context = None)

Recupera els recursos de la llista ids com a llista d’objectes sobre els quals és possible utilitzar la notació de punt per accedir a qualsevol dels seus camps i, en el cas de camps relacionals, navegar cap als objectes apuntats. Si ids és un únic recurs (no llista), facilita el corresponent objecte (no llista d’objectes).

Aquest és un mètode fantàstic, a causa de la potència que facilita per poder navegar entre els objectes mitjançant els camps relacionals.

Observem l'exemple anterior fet amb browse.

    def name_get (self, cr, uid, ids, context=None):
     res = []
     records = self.browse(cr,uid,ids)
     for r in records:
       res.append((r.id, r.surname+", "+r.name))
     return res

Mètode self.pool.get

self.pool.get ('nom_objecte')

Mètode especial que permet obtenir l’objecte Python que gestiona els recursos del model associat a qualsevol classe.

Aquest mètode és imprescindible quan en el disseny d’un mètode d’una classe, cal invocar un mètode d’una altra classe. Recordem que OpenObject té un únic objecte que gestiona tots els recursos del model associat a cada classe. Per tant, en el contingut d’un mètode s’utilitza self per fer referència a l’objecte que gestiona els recursos de la classe, però és molt possible que calgui accedir a l’objecte que gestioni els recursos d’una altra classe i, en tal situació, el mètode self.pool.get hi facilita l’accés.

Mètode search

search (self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False)
  • args: llista de tuples especificant criteris de cerca.
  • offset: nombre de registres a saltar (opcional).
  • limit: màxim nombre de registres a recuperar (opcional).
  • order: columnes sobre les quals ordenar el resultat. Per defecte, utilitza les columnes indicades a l’atribut _order de la definició de la classe Python i, en el seu defecte, la columna id de tota classe Python.
  • count: si té valor True indica que el mètode només ha de retornar el nombre de registres que coincideixen amb els criteris indicats, en comptes de recuperar els seus identificadors.

Retorna una llista dels identificadors dels recursos que verifiquen els criteris de cerca o, si count=True, el nombre de registres.

Les condicions que cal introduir en els criteris de cerca han de verificar:

  • Cada condició és un tuple del tipus ('nom_camp', 'op', valor) en el qual op és qualsevol dels operadors binaris següents: =, !=, >, >=, <, ⇐, like, ilike, in, not in, child_of. L’operador like diferencia majúscules de minúscules, mentre que ilike no ho fa.
  • La negació d’una condició s’efectua amb l’operador ! davant el tuple.
  • La combinació de condicions amb els operadors & (conjunció) i | (disjunció) s’efectua utilitzant la notació polonesa, també coneguda per notació prefix.

Exemple d'utilització de mètode self.pool.get i mètode search amb condició complexa

Suposem que en algun lloc (fora de cap mètode de la classe school.professor) necessitem efectuar una cerca de professors el nom dels quals conté el text Rodríguez i que tenen contracte named. Escriuríem:

   prof = self.pool.get('school.professor')
   ids = prof.search(cr,uid,['&',('contract','=','named'),('name','ilike','Rodriguez')])

Si la cerca s’hagués d’efectuar dins un mètode de la classe school_professor no caldria la crida a self.pool.get i escriuríem directament:

   ids = self.search(cr,uid,['&',('contract','=','named'),('name','ilike','Rodriguez')])

Per obtenir els professors el nom dels quals no contingui el text Rodríguez i que tenen contracte named, escriuríem:

   ids = self.search(cr,uid,['&',('contract','=','named'),'!',('name','ilike','Rodriguez')])

Mètode create

create (self, cr, uid, values, context=None)

Crea un nou registre amb els valors especificats en el paràmetre values, de tipus diccionari. Retorna l’identificador del registre creat.

Mètode write

write (self, cr, uid, ids, values, context=None)

Modifica els registres especificats a la llista ids, amb els valors indicats en el paràmetre values, de tipus diccionari. Retorna True.

Mètode unlink

unlink (self, cr, uid, ids, context=None)

Elimina els registres especificats a la llista ids. Retorna True.

Els mètodes create, write i unlink són candidats a ser sobreescrits quan interessi alterar el seu funcionament habitual. En tal situació, la seva sobreescriptura ha d’incloure, en algun punt, la crida als mètodes create, write i unlink de la classe osv.osv.

Mètodes on_change

Les vistes form faciliten, en els camps editables, l’atribut on_change per indicar una acció a executar quan l’usuari canvia el valor del camp. El contingut d’aquest atribut ha de ser la crida a un mètode de la classe, amb els paràmetres que corresponguin (normalment el nou valor que ha pres el camp i, eventualment, valors d’altres camps).

El mètode invocat per l’atribut on_change té obligatòriament la sintaxi:

   def metode (self, cr, uid, ids, paràmetres...)

i ha de retornar, sempre:

   return {'value': resultat}

en què resultat ha de ser un diccionari de parelles 'camp':valor amb els nous valors dels camps que s’han de veure afectats per l’execució del mètode.


<field name="amount" on_change = "onchange_price(unit_price, amount)" />
<field name="unit_price" on_change = "onchange_price(unit_price,amount)" />
<field name="price" />

def onchange_price(self, cr, uid, ids, unit_price, amount, context=None):
""" change the price when the unit price or the amount is changed """
return { ’value’ : {’price’ : unit_price * amount}}

Domain En el model i en la vista es poden establir filtres. El domain utilitza la mateixa notació que el mètode search.Pot ser útil fer un filtre en un many2one, per exemple:

domain="[('groupd_ids','=', group_id)]"

Combinant el mètode on_change i el domain en la vista, podem fer un assistent per simplificar l’elecció en many2one.

Imaginem que sols podem asignar a cursos professors que tenen les mateixes hores que les hores totals dels cursos. Per simplificar la cerca de professors:

<field name="prof_id" />
<field name="hours_total" on_change="onchange_prof(hours_total)"/>
...
def onchange_prof(self, cr, uid, ids, hours_total):
return {'domain':{'prof_id':[('hours_available','>=',hours_total)]}}


https://doc.openerp.com/trunk/server/06_misc_on_change_tips/ http://help.openerp.com/question/23694/how-to-use-on-change-function/

https://doc.openerp.com/6.1/developer/03_modules_2/

Herència

El framework OpenObject facilita el mecanisme de l’herència per tal que els programadors puguin adaptar mòduls existents i garantir a la vegada que les actualitzacions dels mòduls no destrossin les adequacions desenvolupades.

L’herència es pot aplicar en els tres components del patró MVC:

  • En el model: possibilita ampliar les classes existents o dissenyar noves classes a partir de les existents.
  • En la vista: possibilita modificar el comportament de vistes existents o dissenyar noves vistes.
  • En el controlador: possibilita sobreescriure els mètodes existents o dissenyar-ne de nous.


OpenObject proporciona tres mecanismes d’herència: l’herència de classe, l’herència per prototip i l’herència per delegació.

Mecanisme Característiques Com es defineix
De classe - Herència simple.

- La classe original queda substituïda per la nova classe.
- Afegeix noves funcionalitats (atributs i/o mètodes) a la classe original.
- Les vistes definides sobre la classe original continuen funcionant.
- Permet sobreescriure mètodes de la classe original.
- En PostgreSQL, continua mapada en la mateixa taula que la classe original, ampliada amb els nous atributs que pugui incorporar.

- S’utilitza l’atribut _inherit en la definició de la nova classe Python: _inherit = obj

- El nom de la nova classe ha de continuar sent el mateix que el de la classe original: _name = obj

Per prototip - Herència simple.

- Aprofita la definició de la classe original (com si fos un «prototipus»).
- La classe original continua existint.
- Afegeix noves funcionalitats (atributs i/o mètodes) a les aportades per la classe original.
- Les vistes definides sobre la classe original no existeixen (cal dissenyar-les de nou).
- Permet sobreescriure mètodes de la classe original.
- En PostgreSQL, queda mapada en una nova taula.

- S’utilitza l’atribut _inherit en la definició de la nova classe Python: _inherit = obj

- Cal indicar el nom de la nova classe: _name = nou_nom

Per delegació - Herència simple o múltiple.

- La nova classe «delega» certs funcionaments a altres classes que incorpora a l’interior.
- Els recursos de la nova classe contenen un recurs de cada classe de la que deriven.
- Les classes base continuen existint.
- Afegeix les funcionalitats pròpies (atributs i/o mètodes) que correspongui.
- Les vistes definides sobre les classes bases no existeixen a la nova classe.
- En PostgreSQL, queda mapada en diferents taules: una taula per als atributs propis, mentre que els recursos de les classes derivades resideixen en les taules corresponents a les dites classes.

- S’utilitza l’atribut _inherits en la definició de la nova classe Python: _inherits = …

- Cal indicar el nom de la nova classe: _name = nou_nom

El fitxer __openerp__.py ha de contindre les dependències de la clase heretada.

Herència en el Model

El disseny d’un objecte d’OpenObject heretat és paregut al disseny d’un objecte d’OpenObject no heretat; únicament hi ha dues diferències:

  • Apareix l’atribut _inherit o _inherits per indicar l’objecte (herència simple) o els objectes (herència múltiple) dels quals deriva el nou objecte. La sintaxi a seguir és:
_inherit = 'nom.objecte.del.que.es.deriva'
_inherits = {'nom.objecte1':'nom_camp_FK1', ...}
  • En cas d’herència simple, el nom (atribut _name) de l’objecte derivat pot coincidir o no amb el nom de l’objecte pare. També és possible no indicar l’atribut _name, fet que indica que el nou objecte manté el nom de l’objecte pare.

L’herència simple (_inherit) amb atribut _name idèntic al de l’objecte pare, s’anomena herència de classe i en ella el nou objecte substitueix l’objecte pare, tot i que les vistes sobre l’objecte pare continuen funcionant. Aquest tipus d’herència, la més habitual, s’utilitza quan es vol afegir dades (_columns) i/o modificar propietats de dades existents i/o modificar el funcionament d’alguns mètodes. En cas d’afegir dades, aquestes s’afegeixen a la taula de la base de dades en la qual estava mapat l’objecte pare.

Exemple d'herència de classe L’herència de classe la trobem en molts mòduls que afegeixen dades i mètodes a objectes ja existents, com per exemple, el mòdul comptabilitat (account) que afegix dades i mètodes a l’objecte res.partner. Fixem-nos en el contingut del mòdul account:

   class res_partner(osv.osv):
   _name = 'res.partner'
   _inherit = 'res.partner'
   _columns = {
   ...
   'debit_limit': fields.float('Payable limit'),
   ...

Podeu comprovar que la taula res_partner d’una empresa sense el mòdul account instal·lat no conté el camp debit_limit, que en canvi sí que hi apareix una vegada instal·lat el mòdul.

OpenERP té molts mòduls que deriven de l’objecte res.partner per afegir-hi característiques i funcionalitats.

L’herència simple (_inherit) amb atribut _name diferent al de l’objecte pare, s’anomena herència per prototip i en ella es crea un nou objecte que aglutina les dades (_columns) i mètodes que tenia l’objecte del qual deriva, juntament amb les noves dades i mètodes que pugui incorporar el nou objecte. En aquest cas, sempre es crea una nova taula a la base de dades per mapar el nou objecte.

Exemple d'herència per prototip L’herència per prototip és difícil de trobar en els mòduls que incorpora OpenERP. Un exemple el tenim en el mòdul base_calendar en el qual podem observar el mòdul comptabilitat (account) que afegix dades i mètodes a l’objecte res.partner. Fixem-nos en el contingut del mòdul account:

   class res_alarm(osv.osv):
   _name = 'res.alarm'
   ...
   class calendar_alarm(osv.osv):
   _name = 'calendar.alarm'
   _inherit = 'res.alarm'
   ...

En una empresa que tingui el mòdul base_calendar instal·lat podeu comprovar l’existència de la taula res_alarm amb els camps definits a l’apartat _atributs de la classe res_alarm i la taula calendar_alarm amb camps idèntics als de la taula res_alarm més els camps definits a l’apartat _atributs de la classe calendar_alarm.

L’herència múltiple (_inherits) s’anomena herència per delegació i sempre provoca la creació d’una nova taula a la base de dades. L’objecte derivat ha d’incloure, per cada derivació, un camp many2one apuntant l’objecte del qual deriva, amb la propietat ondelete='cascade'. L’herència per delegació obliga que cada recurs de l’objecte derivat apunti a un recurs de cadascun dels objectes dels quals deriva i es pot donar el cas que hi hagi diversos recursos de l’objecte derivat que apuntin a un mateix recurs per algun dels objectes dels quals deriva.

Herència en la vista

L’herència de classe possibilita continuar utilitzant les vistes definides sobre l’objecte pare, però en moltes ocasions interessa disposar d’una versió retocada. En aquest cas, és molt millor heretar de les vistes existents (per afegir, modificar o eliminar camps) que no pas reemplaçar-les completament.

<field name="inherit_id" ref="id_xml_vista_pare"/>

En cas que la vista id_xml_vista_pare resideixi en un mòdul diferent del que estem dissenyant, cal afegir el nom del mòdul al davant:

<field name="inherit_id" ref="modul.id_xml_vista_pare"/>

El motor d’herència d’OpenObject, en trobar una vista heretada, processa el contingut de l’element arch. Per cada fill d’aquest element que tingui algun atribut, OpenObject cerca a la vista pare una etiqueta amb atributs coincidents (excepte el de la posició) i, a continuació, combina els camps de la vista pare amb els de la vista heretada i estableix la posició de les noves etiquetes a partir dels següents valors:

  • inside (per defecte): els valors s’afegeixen “dins” de l’etiqueta.
  • after: afegeix el contingut després de l’etiqueta.
  • before: afegeix el contingut abans de l’etiqueta.
  • replace: reemplaça el contingut de l’etiqueta.

Reemplaçar

 <field name="arch" type="xml">
   <field name="camp" position="replace">
     <field name="nou_camp" ... />
   </field>
 </field>

Esborrar

 <field name="arch" type="xml">
   <field name="camp" position="replace"/>
 </field>

Inserir nous camps

 <field name="arch" type="xml">
    <field name="camp" position="before">
       <field name="nou_camp" .../>
    </field>
 </field>
 
 <field name="arch" type="xml">
    <field name="camp" position="after">
       <field name="nou_camp" .../>
    </field>
 </field>

Fer combinacions

 <field name="arch"type="xml">
   <data>
     <field name="camp1" position="after">
       <field name="nou_camp1"/>
     </field>
     <field name="camp2" position="replace"/>
     <field name="camp3" position="before">
        <field name="nou_camp3"/>
     </field>
   </data>
 </field>

És posssible que necessitem una vista totalment nova de l'objecte heredat. Si fem un action normal en l'XML es veuran els que més prioritat tenen. Si volem especificar quina vista volem en concret hem d'utilitzar view_id:

<field name="view_id" ref="view_school_parent_form2"/>

Tal vegada cal especificar totes les vistes. En eixe cas, s'ha de guardar per separat en ir.actions.act_window.view:

<record model="ir.actions.act_window" id="action_my_hr_employee_seq">
    <field name="name">Angajati</field>
    <field name="res_model">hr.employee</field>
    <field name="view_type">form</field>
    <field name="view_mode">tree,form</field>
</record>
 
<record model="ir.actions.act_window.view" id="act_hr_employee_tree_view">
    <field eval="1" name="sequence"/>
    <field name="view_mode">tree</field>
    <field name="view_id" ref="your_tree_view_id"/>
    <field name="act_window_id" ref="action_my_hr_employee_seq"/>
</record>
 
<record model="ir.actions.act_window.view" id="act_hr_employee_form_view">
    <field eval="2" name="sequence"/>
    <field name="view_mode">form</field>
    <field name="view_id" ref="your_form_view_id"/>
    <field name="act_window_id" ref="action_my_hr_employee_seq"/>
</record>

Herència en el controlador

L’herència en el controlador és un mecanisme conegut, ja que l’apliquem de forma inconscient quan ens veiem obligats a sobreescriure els mètodes de la capa ORM d’OpenObject en el disseny de molts mòduls.

Funció super() El llenguatge Python recomana utilitzar la funció super() per invocar el mètode de la classe base quan s’està sobreescrivint en una classe derivada, en lloc d’utilitzar la sintaxi nomClasseBase.metode(self…).

L’efecte de l’herència en el controlador es manifesta únicament quan cal sobreescriure algun dels mètodes de l’objecte del qual es deriva i per a fer-ho adequadament cal tenir en compte que el mètode sobreescrit en l’objecte derivat:

  • A vegades vol substituir el mètode de l’objecte base sense aprofitar-ne cap funcionalitat: el mètode de l’objecte derivat no invoca el mètode sobreescrit.
  • A vegades vol aprofitar la funcionalitat del mètode de l’objecte base: el mètode de l’objecte derivat invoca el mètode sobreescrit.

Wizards

Un assistent informàtic (wizard en anglès) és un programa pensat per abreujar o canalitzar els passos a seguir per dur a terme una tasca o, com a mínim, explicar els passos detalladament. En moltes ocasions s’utilitza per guiar als usuaris inexperts.

En OpenERP, un assistent és una successió de passos. Cada pas es composa de diverses accions:

  • Mostra un formulari a l’usuari amb alguns botons.
  • Recupera la informació introduïda per l’usuari i el botó seleccionat.
  • Executa les accions que corresponguin.
  • Envia una nova acció al client (formulari, informe…).

Per crear un assistent, cal definir un objecte OpenERP que no es fa persistent en el SGBD PostgreSQL, fet que s’aconsegueix fent que la classe Python que crea l’objecte OpenERP derivi de la classe osv.osv_memory en lloc de derivar de la classe osv.osv. La classe Python es defineix en un fitxer .py i la vista formulari es defineix en un fitxer XML, de forma similar a les classes que defineixen els objectes persistents d’OpenERP.

Els fitxers .py i XML corresponents als assistents d’un mòdul s’acostumen a ubicar en una carpeta anomenada wizard situada dins la carpeta del mòdul. El fitxer __init__.py del mòdul ha de contenir una sentència import wizard i la carpeta wizard ha de contenir un fitxer __init__.py amb la sentència import per al fitxer .py que conté les classes necessàries per a l’assistent. Els fitxers XML de l’assistent, ubicats normalment dins la carpeta wizard, han de ser referenciats des de l’apartat update_xml del fitxer __openerp__.py del mòdul.

Ja que els assistents defineixen una seqüència de passos, la classe que en defineix el model acostuma a tenir el camp especial state, de tipus selection, que permet definir els diversos estats pels quals passa l’assistent. El contingut d’aquest camp s’acostuma a utilitzar en la vista formulari per fer visibles i/o invisibles altres camps en funció del seu valor, de manera que l’usuari té la percepció que hi ha una successió de pantalles quan, en molts casos, és la mateixa però amb camps que canvien el seu estat de visibilitat.

Exemple de disseny d'un assistent en el mòdul school

Interessa incorporar uns assistents, en el vigent mòdul school, que permeti omplir el camp First Date d’alguns cursos existents (o de tots), amb una data introduïda per l’usuari.

En el menú School | Configuration es vol habilitar una nova opció de menú, de nom Wizards, que contingui dues opcions:

  • Assignació d’una First Date comuna per tots els cursos, que ha de permetre que l’usuari introdueixi la data a assignar a tots els cursos existents a l’empresa, tinguin o no data introduïda.
  • Assignació d’una First Date comuna per cursos filtrant per nom, que ha de permetre que l’usuari introdueixi la data a assignar a tots els cursos que el seu nom contingui un determinat text introduït per l’usuari.

En qualsevol cas, el procés ha de facilitar dos botons: Cancel, per cancel·lar el procés i Assign, per procedir a l’assignació. En cas d’executar l’assignació, l’assistent ha de mostrar una pantalla en la qual s’informi del nombre de cursos actualitzats amb un únic botó Close per tancar l’assistent.

Es crea el fitxer school_manage_course_date.py que conté la definició de la classe no persistent school_manage_course_date que incorpora quatre camps: new_date per recollir la data que l’usuari vol assignar als cursos; info_updates per mostrar el nombre de cursos actualitzats; course_name per recollir el text que ha de contenir el nom en el segon assistent demanat; i el camp state amb dos possibles valors (Init i Done) per distingir l’estat en el qual es troba el procés. Tot i que es demana dos assistents, com que són molt similars, ens serveix la mateixa classe school_manage_course_date per ambdós.

from osv import osv, fields
 
class school_manage_course_date(osv.osv_memory):
  _name = 'school.manage_course_date'
  _columns = {
    'info_updates': fields.integer('Number of updated courses',readonly=True),
    'course_name': fields.char('Part of the course name to search',size=30),
    'new_date': fields.date('New date', required=True),
    'state':fields.selection([ ('init','Init'), ('done','Done'), ],'State',readonly=True)
  } 
  _defaults = { 
    'state': lambda *a: 'init'
  }
 
  def assign_course_date_all(self, cr, uid, ids, data, context=None):
    form = self.browse(cr, uid, ids[0])
    new_date = form.new_date
    course_obj = self.pool.get('school.course')
    course_ids = course_obj.search(cr, uid, [])
    values = { 'date': new_date }
    course_obj.write(cr, uid, course_ids, values)
    values = { 'state':'done', 'info_updates':len(course_ids), }
    self.write(cr, uid, ids, values)
    return True    
 
  def assign_course_date_name(self, cr, uid, ids, data, context=None):
    form = self.browse(cr, uid, ids[0])
    course_name=form.course_name
    new_date = form.new_date
    course_obj = self.pool.get('school.course')
    course_ids = course_obj.search(cr, uid, [('name','ilike',course_name)])
    values = { 'date': new_date }
    course_obj.write(cr, uid, course_ids, values)
    values = { 'state':'done', 'info_updates':len(course_ids), }
    self.write(cr, uid, ids, values)
    return True    
 
school_manage_course_date()

La classe school_manage_course_date incorpora dos mètodes: assign_course_date_all, que actualitza tots els cursos a partir de la data existent en el camp new_date del formulari, i assign_course_date_name, que actualitza els cursos el nom dels quals conté el contingut del camp course_name del formulari, amb la data existent en el camp new_date del formulari.

El fitxer school_manage_course_date_view.xml conté la definició dels menús indicats, amb les accions corresponents i les dues vistes (una per cada assistent). Les dues vistes es basen en el model school.manage_course_date i es diferencien en els camps que mostren (la corresponent al segon assistent, a diferència de la del primer assistent, mostra el camp course_name) i en el mètode que executa el botó Assign. Fixeu-vos que, per cadascuna de les vistes, s’aconsegueix l’aparença de dues pantalles diferents, a partir de la visibilitat i invisibilitat dels diferents camps i botons. La visibilitat es modifica a partir de l’estat en el qual es troba l’assistent.

<?xml version="1.0"?>
<openerp>
<data>
  <record model="ir.ui.view" id="view_school_manage_course_date_all">
    <field name="name">school.manage.course.date.all</field>
    <field name="model">school.manage_course_date</field>
    <field name="type">form</field>
    <field name="arch" type="xml">
      <form string="Assignment first date to all courses">
        <group col="4" colspan="4" attrs="{'invisible':[('state','=','done')]}">
          <label string="Enter the date to be assigned to all courses:" colspan="4"/>
          <newline />
          <field name="new_date"/>
        </group>
        <group col="4" colspan="4" attrs="{'invisible':[('state','=','init')]}">
          <field name="info_updates"/>
        </group>     
        <separator colspan="4"/>
        <group col="2" colspan="2">
          <field name="state" widget="statusbar" />
        </group>
        <group col="2" colspan="2">
          <button string="Cancel" special="cancel" icon="gtk-cancel" states="init" />
          <button string="Assign" name="assign_course_date_all" type="object" icon="gtk-ok" states="init" />
          <button string="Close" special="cancel" icon="gtk-ok" states="done" />       
        </group>
      </form>
    </field>
  </record>
  <record model="ir.actions.act_window" id="action_school_course_assign_date_all">
    <field name="name">Assign first date to all courses</field>
    <field name="res_model">school.manage_course_date</field>
    <field name="view_type">form</field>
    <field name="view_mode">form</field>
    <field name="view_id" ref="view_school_manage_course_date_all"/>
    <field name="target">new</field>
  </record>
 
  <record model="ir.ui.view" id="view_school_manage_course_date_name">
    <field name="name">school.manage.course.date.name</field>
    <field name="model">school.manage_course_date</field>
    <field name="type">form</field>
    <field name="arch" type="xml">
      <form string="Assignment first date to courses whose name contains...">
        <group col="4" colspan="4" attrs="{'invisible':[('state','=','done')]}">
          <label string="Enter the date to be assigned to courses whose name contains...:" colspan="4"/>
          <newline />
          <field name="course_name"/>
          <field name="new_date"/>
        </group>
        <group col="4" colspan="4" attrs="{'invisible':[('state','=','init')]}">
          <field name="info_updates"/>
        </group>        
        <separator colspan="4"/>
        <group col="2" colspan="2">
          <field name="state" widget="statusbar" />
        </group>
        <group col="2" colspan="2">
          <button string="Cancel" special="cancel" icon="gtk-cancel" states="init" />
          <button string="Assign" name="assign_course_date_name" type="object" icon="gtk-ok" states="init" />
          <button string="Close" special="cancel" icon="gtk-ok" states="done" />       
        </group>
      </form>
    </field>
  </record>
  <record model="ir.actions.act_window" id="action_school_course_assign_date_name">
    <field name="name">Assign first date to courses whose name contains...</field>
    <field name="res_model">school.manage_course_date</field>
    <field name="view_type">form</field>
    <field name="view_mode">form</field>
    <field name="view_id" ref="view_school_manage_course_date_name"/>
    <field name="target">new</field>
  </record>
 
  <menuitem name="Wizards" id="menu_school_configuration_wizards" parent="menu_school_configuration" groups="group_school_manager"/>
  <menuitem name="Assign first date to all courses" id="menu_school_course_assign_date_all" action="action_school_course_assign_date_all" parent="menu_school_configuration_wizards"/>
  <menuitem name="Assign first date to courses whose name contains..." id="menu_school_course_assign_date_name" action="action_school_course_assign_date_name" parent="menu_school_configuration_wizards"/>
 
</data>
</openerp>

Incorpora, en les vistes, a la part inferior esquerra, el camp state per tal que puguem veure com canvia el seu valor segons el moment en el qual es troba l’assistent. En la situació presentada, no té gaire sentit que el camp state sigui visible, però com que en altres casos sí que en pot tenir, l’hem incorporat per a veure’n la seva utilització, amb widget="statusbar".

Per últim, una observació important sobre el contingut dels mètodes de la classe school_manage_course_date invocats pel botó Assign de les dues vistes. Fixeu-vos que aquests mètodes criden en dues ocasions el mètode write:

  • Una, sobre l’objecte course_obj que gestiona els recursos del model school.course, indicant-li el conjunt de cursos a actualitzar (course_ids) i els camps a modificar (values):
values = {'date':new_date}
course_obj.write(cr, uid, course_ids, values)
  • L’altra, sobre l’objecte self, és a dir, sobre l’objecte vinculat al formulari actiu, per actualitzar el contingut dels camps info_udpates i state del formulari i així aconseguir, en el formulari, la transició de la pantalla en què es demana la nova data (state='init') a la pantalla en què s’informa del nombre de cursos actualitzats (state='done'):
   values = {'state':'done','info_updates':len(course_ids),}
   self.write(cr, uid, ids, values)

Misc.

Relació many2many amb atributs

Els many2many generen una taula automàtica. Però no es poden afegir camps directament a aquesta taula. Si volem atributs en aquesta relació, cal crear un model enmig amb camps many2one que apunten a les dues taules. En aquest model es poden ficar els camps de la relació.

Què és el context

El context és un diccionari python i s'utilitza per passar certes dades a un mètode. Es pot utilitzar el context per passar les dades a través de diversos nivells dels mètodes de python.

context.get('active_id',False)

Retorna el valor de la clau 'active_id'. Si la clau no està en el context, es retorna el valor 'false'.

El valor de 'active_id' es passa des del client web i conté l'ID del registre actual seleccionat.


En el cas de la vista, context, s'utilitza per passar informació contextual arbitraria al servidor.

Per exemple, en un camp en l'objecte one2many soci, pots posar context="{'partner_id': active_id}". Aquesta informació està disponible al servidor quan es generen les formes que apareixen en el camp one2many, per exemple en funcions de camp '_defaults.

No obstant això, l'objecte no és (necessàriament) filtrat per qualsevol cosa que es posa en el context, només si el dissenyador d'objectes ha decidit que ha de ser.

Un petit truc en realitat és utilitzar el context per passar dels valors predeterminats per als camps. Per exemple:

context="('default_name': 'Fred')"

O la manera de passar el id del pare en un one2many per a que el formulari que crea el model fill tinga com a id del pare el que l'ha invocat, en la vista:

context="{'default_pare_id': active_id}"

Exemples

El parking

Fer un mòdul per a la gestió d’un parking públic. Cal guardar matrícula, data i hora d’entrada i d’eixida, plaça ocupada, preu i forma de pagament de cada cotxe que entra. Amés, un cotxe pot entrar varies vegades. La forma de pagament serà un camp desplegable amb 3 opcions. El preu es calcula amb el temps consumit per un preu per minut. Ens interessa guardar de cada plaça de aparcament el pis, número i una llista de cotxes que l’han utilitzat. Per qüestions estadístiques, s’ha de calcular quines places tenen més èxit.

Python:

from osv import osv, fields
from datetime import datetime
from datetime import timedelta
import openerp.tools as tools
 
class parking_car(osv.osv):
        _name = 'parking.car'
        _columns = {
                'name': fields.char('ID', size=10, required=True),
                'tickets': fields.one2many('parking.ticket','car','Tickets'),
        }
parking_car()
 
class parking_place(osv.osv):
        def _get_total(self, cr, uid, ids, name, arg, context=None):
                res = {}
                p = self.browse(cr, uid, ids, context=context)
                for h in p:
                        total=0
                        for t in h.tickets:
                                total=total+t.price
                        res[h.id]=total
                return res
 
        _name = 'parking.place'
        _columns = {
                'level' : fields.integer('Level', required=True),
                'num' : fields.integer('Number', required=True),
                'tickets': fields.one2many('parking.ticket','place','Tickets'),
                'total' :fields.function(_get_total,type='float',string='Total',store=False),
        }
 
parking_place()
 
class parking_ticket(osv.osv):
        def _get_price (self, cr, uid, ids, name, arg, context=None):
                res = {}
                t = self.browse(cr, uid, ids, context=context)
                for h in t:
                        print h.date_in
                        print tools.DEFAULT_SERVER_DATETIME_FORMAT # guarda el format de OpenERP per a dates
                        a=datetime.strptime(h.date_in,"%Y-%m-%d %H:%M:%S") # també es pot ficar manualment
                        b=datetime.strptime(h.date_out,tools.DEFAULT_SERVER_DATETIME_FORMAT)
                        timedelta = b - a
                        print timedelta.seconds
                        res[h.id]=(timedelta.seconds/60)*0.2
                return res
 
        _name = 'parking.ticket'
        _columns = {
                'car': fields.many2one('parking.car','Car ID', required=True),
                'place': fields.many2one('parking.place','Place',required=True),
                'date_in': fields.datetime('Date In',required=True),
                'date_out': fields.datetime('Date Out',required=False),
                'price': fields.function(_get_price,type='float',string='Price',store=False),
                'payment': fields.selection((('c','Credit Card'), ('m','Money'),('p','Partner')),'Payment Mode'),
        }
parking_ticket()


Enllaços

Diapossitives