48
Le framework Odoo Sébastien Namèche <[email protected]> Odoo (ex-OpenERP, ex-TinyERP) est l'étoile montante dans le domaine des PGI (Progiciel de Gestion Intégré) ou ERP (Enterprise Resource Planning) open- sources. Ces progiciels complexes sont souvent considérés comme rébarbatifs et déchaînent en général peu les passions. Cependant Odoo est aussi un framework par-dessus lequel sont construites les briques du PGI (CRM, gestion des ventes, des achats, de la production, de la RH, etc.). Le but de cet article est bien de présenter ce framework particulière- ment mature et complet avec lequel il est possible de développer des applica- tions rapidement. Le framework Odoo libère le développeur des tâches ingrates et permet de mettre en place des interfaces Web modernes te réactives selon une méthodo- logie de développement RAD. Licence : Ce document peut être librement lu, stocké, reproduit, diffusé, traduit et cité par tous moyens et sur tous supports aux conditions suivantes : - tout lecteur ou utilisateur de ce document reconnaît avoir pris connaissance de ce qu'aucune garantie n'est donnée quant à son contenu, à tous points de vue, notamment véracité, précision et adéquation pour toute utilisation ; - il n'est procédé à aucune modification autre que cosmétique, changement de format de représentation, traduction, correction d'une erreur de syntaxe évidente ou en accord avec les clauses ci-dessous ; - le nom, le logo et les coordonnées de l'auteur devront être préservés sur toutes les versions dérivées du document à tous les endroits où ils apparaissent dans l'original, les noms et logos d'autres contributeurs ne pourront pas appa- raître dans une taille supérieure à celle des auteurs précédents, des commentaires ou additions peuvent êtres insérés à condition d'apparaître clairement comme tels ; - les traductions ou fragments doivent faire clairement référence à une copie originale complète, si possible à une co- pie facilement accessible ; - les traductions et les commentaires ou ajouts insérés doivent être datés et leur(s) auteur(s) doi(ven)t être identi- fiable(s) (éventuellement au travers d'un alias) ; - cette licence est préservée et s'applique à l'ensemble du document et des modifications et ajouts éventuels (sauf en cas de citation courte), quelqu'en soit le format de représentation ; - quel que soit le mode de stockage, reproduction ou diffusion, toute version imprimée doit contenir une référence à une version numérique librement accessible au moment de la première diffusion de la version imprimée, toute per- sonne ayant accès à une version numérisée de ce document doit pouvoir en faire une copie numérisée dans un format directement utilisable et si possible éditable, suivant les standards publics, et publiquement documentés en usage ; - la transmission de ce document à un tiers se fait avec transmission de cette licence, sans modification, et en parti- culier sans addition de clause ou contrainte nouvelle, explicite ou implicite, liée ou non à cette transmission. En par- ticulier, en cas d'inclusion dans une base de données ou une collection, le propriétaire ou l'exploitant de la base ou de la collection s'interdit tout droit de regard lié à ce stockage et concernant l'utilisation qui pourrait être faite du do- cument après extraction de la base ou de la collection, seul ou en relation avec d'autres documents. Toute incompatibilité des clauses ci-dessus avec des dispositions ou contraintes légales, contractuelles ou judiciaires implique une limitation correspondante : droit de lecture, utilisation ou redistribution verbatim ou modifiée du docu- ment. Adapté de la licence Licence LLDD v1, octobre 1997, Libre reproduction © Copyright Bernard Lang [F1450324322014]. URL : http://pauillac.inria.fr/~lang/licence/lldd.html L'original de ce document est disponible à cette URL : http://sebastien.nameche.fr/cours Dédicace de la version v0.1 du 25 novembre 2015 à Gee.

Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

  • Upload
    lenhi

  • View
    220

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework OdooSébastien Namèche <[email protected]>

Odoo (ex-OpenERP, ex-TinyERP) est l'étoile montante dans le domaine des PGI(Progiciel de Gestion Intégré) ou ERP (Enterprise Resource Planning) open-sources. Ces progiciels complexes sont souvent considérés comme rébarbatifset déchaînent en général peu les passions.Cependant Odoo est aussi un framework par-dessus lequel sont construites lesbriques du PGI (CRM, gestion des ventes, des achats, de la production, de laRH, etc.). Le but de cet article est bien de présenter ce framework particulière-ment mature et complet avec lequel il est possible de développer des applica-tions rapidement.Le framework Odoo libère le développeur des tâches ingrates et permet demettre en place des interfaces Web modernes te réactives selon une méthodo-logie de développement RAD.

Licence :

Ce document peut être librement lu, stocké, reproduit, diffusé, traduit et cité par tous moyens et sur tous supports auxconditions suivantes :

- tout lecteur ou utilisateur de ce document reconnaît avoir pris connaissance de ce qu'aucune garantie n'est donnéequant à son contenu, à tous points de vue, notamment véracité, précision et adéquation pour toute utilisation ;

- il n'est procédé à aucune modification autre que cosmétique, changement de format de représentation, traduction,correction d'une erreur de syntaxe évidente ou en accord avec les clauses ci-dessous ;

- le nom, le logo et les coordonnées de l'auteur devront être préservés sur toutes les versions dérivées du document àtous les endroits où ils apparaissent dans l'original, les noms et logos d'autres contributeurs ne pourront pas appa-raître dans une taille supérieure à celle des auteurs précédents, des commentaires ou additions peuvent êtres insérésà condition d'apparaître clairement comme tels ;

- les traductions ou fragments doivent faire clairement référence à une copie originale complète, si possible à une co-pie facilement accessible ;

- les traductions et les commentaires ou ajouts insérés doivent être datés et leur(s) auteur(s) doi(ven)t être identi-fiable(s) (éventuellement au travers d'un alias) ;

- cette licence est préservée et s'applique à l'ensemble du document et des modifications et ajouts éventuels (sauf encas de citation courte), quelqu'en soit le format de représentation ;

- quel que soit le mode de stockage, reproduction ou diffusion, toute version imprimée doit contenir une référence àune version numérique librement accessible au moment de la première diffusion de la version imprimée, toute per-sonne ayant accès à une version numérisée de ce document doit pouvoir en faire une copie numérisée dans un formatdirectement utilisable et si possible éditable, suivant les standards publics, et publiquement documentés en usage ;

- la transmission de ce document à un tiers se fait avec transmission de cette licence, sans modification, et en parti-culier sans addition de clause ou contrainte nouvelle, explicite ou implicite, liée ou non à cette transmission. En par-ticulier, en cas d'inclusion dans une base de données ou une collection, le propriétaire ou l'exploitant de la base oude la collection s'interdit tout droit de regard lié à ce stockage et concernant l'utilisation qui pourrait être faite du do-cument après extraction de la base ou de la collection, seul ou en relation avec d'autres documents.

Toute incompatibilité des clauses ci-dessus avec des dispositions ou contraintes légales, contractuelles ou judiciairesimplique une limitation correspondante : droit de lecture, utilisation ou redistribution verbatim ou modifiée du docu-ment.

Adapté de la licence Licence LLDD v1, octobre 1997, Libre reproduction © Copyright Bernard Lang[F1450324322014]. URL : http://pauillac.inria.fr/~lang/licence/lldd.html

L'original de ce document est disponible à cette URL : http://sebastien.nameche.fr/cours

Dédicace de la version v0.1 du 25 novembre 2015 à Gee.

Page 2: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

Table des matières1. Introduction......................................................................................................................................41.1 Fonctionnalités................................................................................................................................41.2 Architecture....................................................................................................................................42. Installation........................................................................................................................................52.1 Code source et Git..........................................................................................................................52.2 Compilation de la documentation...................................................................................................62.3 Créer votre première base de données............................................................................................62.4 Astuces............................................................................................................................................72.5 Fichier de configuration..................................................................................................................93. Modules..........................................................................................................................................103.1 Le squelette...................................................................................................................................103.2 Le modèle de données..................................................................................................................113.3 Champs calculés et contraintes.....................................................................................................143.4 Importer des données....................................................................................................................173.5 Puis-je avoir le menu s'il-vous-plaît ?..........................................................................................203.6 Vue imprenable.............................................................................................................................233.7 Filtres............................................................................................................................................263.8 Droits d'accès................................................................................................................................284. Traduction.......................................................................................................................................315. Publication......................................................................................................................................345.1 Recettes publiques........................................................................................................................345.2 Contrôleur.....................................................................................................................................345.3 Modèles QWeb.............................................................................................................................365.4 Sous-modèles................................................................................................................................406. Héritage..........................................................................................................................................407. Fonctionnalités étendues.................................................................................................................447.1 Rapports........................................................................................................................................447.2 Wizards.........................................................................................................................................457.3 Tests..............................................................................................................................................467.4 API................................................................................................................................................478. Sujets à aborder..............................................................................................................................48

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 2

Page 3: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

Index des illustrationsIllustration 1: L'écran de création d'une base de données.....................................................................7Illustration 2: Obtenir la liste des modules installés.............................................................................8Illustration 3: Mettre à jour le module Cookbook..............................................................................13Illustration 4: Identifiants externes de notre module..........................................................................20Illustration 5: La vue formulaire par défaut pour une recette.............................................................22Illustration 6: Notre vue formulaire pour une recette.........................................................................25Illustration 7: Nos filtres prédéfinis pour la liste des recettes............................................................28Illustration 8: La liste des groupes associés au module Coobook......................................................30Illustration 9: Export d'un nouveau modèle de traduction pour le module Cookbook.......................32Illustration 10: Accès à la traduction d'un champ...............................................................................33Illustration 11: Traduction du terme Appetizer...................................................................................33Illustration 12: La page d'accueil de notre livre de cuisine public.....................................................38

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 3

Page 4: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

1. Introduction

Cet article présente donc uniquement le framework Odoo. Les applications qui composent le PGI etqui sont développées sur ce framework feront éventuellement l'objet d'une autre série d'articles.Le lecteur gagnera à avoir des connaissances en Python, PostgreSQL et XML (notamment XPath)afin de comprendre tous les exemples mais cela n'est pas absolument nécessaire pour tirer la sub-stantifique moelle de ces pages.Cet article se focalise uniquement sur l'API de la version 8 d'Odoo. Cette API a été fortement rema-niée lors du passage de la version 7 à la version 8. La majorité des modules d'Odoo utilisent encorel'ancienne API, ne soyez donc pas étonnés des différences importantes que vous constaterez si votrecuriosité vous amène à la lecture du code source d'autres modules. L'API ne devrait pas évolueroutre mesure lors de la sortie de la version 9 d'Odoo qui ne devrait plus tarder.1

1.1 Fonctionnalités

Le framework Odoo propose toutes les fonctionnalités qu'on attend d'un environnement de dévelop-pement moderne, sans : un ORM (Object-Relational Mapping), une gestion des utilisateurs, desgroupes et des droits d'accès, différents types d'écrans (listes, arbres, formulaires, diagrammes deGantt, Kanban, calendriers), une gestion des workflows, une édition de rapports, une API XML-RPC, la gestion des traductions, etc.Le framework Odoo a clairement été créé pour développer des applications de gestion. Si vous avezbesoin d'un environnement pour créer une application fortement graphique, choisissez un autre fra-mework.

1.2 Architecture

Le framework Odoo est écrit en Python 2.7 et s'appuie sur le gestionnaire de bases de données rela-tionnelles PostgreSQL. Ces deux fleurons majeurs de l'open-source lui confèrent stabilité, perfor-mance et ouverture (il existe un grand nombre de modules développés par une communauté active).La partie cliente Web est développée en Javascript avec l'aide des librairies jQuery et Underscore.js.Il y a quelques années, OpenERP 6 disposait d'un client lourd développé en PyGTK. Ce client a dis-paru en version 7. La version 8 apporte un lot de changements conséquents : un nouveau nom,Odoo, une interface Web entièrement retravaillée (abandon de TurboGears), une nouvelle API, plussimple et plus « Python ».Odoo est basé sur une architecture trois-tiers : base de données (PostgreSQL), traitement (le serveurOdoo), présentation (client Web) et utilise, assez naturellement, le paradigme MVC (Modèle-Vue-Contrôleur).Enfin, et c'est un point important, les applications Odoo s'articulent en modules. Un module apporteune fonctionnalité spécifique, une application Odoo est souvent constituée d'un ensemble de mo-dules. Certains apportent des fonctionnalités basiques et d'autres constituent des ensembles fonc-tionnels bien plus vastes. Un module peut venir surcharger le comportement d'un autre module. Ain-si, il est relativement aisé de d'adapter des modules existants sans pour autant modifier leur codesource. Cette particularité est bien plus important qu'il n'y paraît, elle a sans doute fortement contri-bué au succès d'Odoo en tant que PGI.

1. Entre le moment où j'ai commencé cet article et celui où je l'ai fini, la version 9.0 d'Odoo a été officiellement publiée.Cependant, l'API des versions 8.0 et 9.0 est identique. Notre cher module Cookbook fonctionnera donc sur ces deuxversions.

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 4

Page 5: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

2. Installation

Il existe plusieurs solutions pour installer Odoo. Notamment des paquets sont construits toutes lesnuits pour Windows et les distributions basées sur les systèmes de paquetages Apt et Rpm. Ils sontdisponibles à cette adresse : https://nightly.odoo.com.Mais ce n'est pas la méthode recommandée. En effet, même la version stable du code (branche 8.0)évolue rapidement et de nombreuses corrections lui sont régulièrement apportées. Ces correctionsne sont pas disruptives, il est possible de les appliquées régulièrement sur un système en productionavec les précautions d'usage bien évidemment.

2.1 Code source et Git

Pour cette installation, nous partirons d'une Ubuntu Server 14.04 LTS. Ubuntu car il s'agit, avec De-bian, de la plateforme de référence pour le développement d'Odoo et pour son support LTS (LongTerm Support). Avec un peu plus d'effort, il est bien évidemment possible d'installer Odoo sur n'im-porte quel système d'exploitation courant (Linux, Windows, MacOS X).Donc, à partir d'une Ubuntu Server 14.04 LTS fraichement installée avec uniquement un serveurOpenSSH, nous commençons par la mise-à-jour traditionnelle du système :$ sudo apt-get update$ sudo apt-get upgrade

Nous avons ensuite besoin de Git :$ sudo apt-get install git

Le serveur Odoo ayant le bon goût de ne pas vouloir démarrer lorsqu'il est est exécuté par l'utilisa-teur root, nous devons ensuite créer un utilisateur qui lui sera dédié :$ sudo adduser --home /opt/odoo --gecos 'Serveur Odoo' --disabled-password odoo

Pour les distributions Debian et Ubuntu, les développeurs d'Odoo ont eu la délicatesse de mettre ànotre disposition un script qui automatise la configuration et le clonage du dépôt Git adéquat ainsique l'installation des dépendances nécessaires à l'exécution du serveur Odoo. Même si cela fait unpeu peur, il faut être root pour l'utiliser1 :$ sudo bash# cd /opt/odoo# wget -O- https://raw.githubusercontent.com/odoo/odoo/master/odoo.py | python# chown -R odoo:odoo odoo# exit

Il nous faut ensuite créer un utilisateur PostgreSQL pour notre serveur Odoo. Cet utilisateur doitdisposer des droits pour créer des bases de données (option --createdb) et, pour simplifier la confi-guration, il doit porter le même nom que l'utilisateur système qui exécutera le serveur Odoo (odoodans notre cas) :$ sudo -u postgres createuser --createdb odoo

Enfin, le seul paquetage qu'il nous faudra installer à la main est wkhtmltox. En effet, la version in-cluse dans Ubuntu 14.04 est trop ancienne et présente des problèmes notamment en ce qui concerneles en-têtes et pieds de page. Odoo utilise cet outil afin de transformer les rapports du format HTMLau format PDF pour impression ou envoie par émail et requiert au moins la version 12.2 dewkhtmltox. L'installation du paquetage et de ses dépendances se fait ainsi :

1. Pour celles et ceux d'entre vous qui souhaiteraient installer Odoo sans passer par ce script (typiquement dans le casd'une installation sur un autre système d'exploitation que Debian ou Ubuntu), il suffira de cloner la branche 8.0 du dépôtGit d'Odoo puis d'installer les dépendances contenus dans le fichier requirements.txt :$ git clone https://www.github.com/odoo/odoo --depth 1 --branch 8.0 --single-branch .$ sudo pip install -r odoo/requirements.txtIl est bien évidemment possible d'installer les dépendances en utilisant le système de paquetages de votre distribution.

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 5

Page 6: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

$ sudo apt-get install xfonts-base xfonts-75dpi$ wget http://download.gna.org/wkhtmltopdf/0.12/0.12.2.1/wkhtmltox-0.12.2.1_linux-trusty-amd64.deb$ sudo dpkg -i wkhtmltox-0.12.2.1_linux-trusty-amd64.deb

Voilà qui est fait, notre serveur Odoo est prêt à fonctionner. Démarrage dans quelques lignes…

2.2 Compilation de la documentation

Si vous souhaitez disposer de la documentation hors-ligne (j'aime beaucoup travailler dans le train),vous devrez la compiler. Pour cela, il suffit d'installer l'outil de documentation Sphinx ainsi que l'ex-tension sphinx-patchqueue avant de lancer la compilation avec make. Cependant l'outil de mise enforme de la syntaxe des langages de programmation (paquetage pygments) distribué avec Ubuntu esttrop ancien, il faut donc installer une version plus récente avec pip. Et, ce dernier a besoin de Mer-curial pour récupérer le code source de pygments. Les commandes à exécuter pour installer tous cespré-requis sont donc les suivantes :$ sudo apt-get install make python-sphinx python-pip mercurial$ sudo pip install sphinx-patchqueue$ sudo pip install --upgrade pygments

Et, pour compiler la documentation HTML :$ cd /opt/odoo/odoo/doc$ sudo -u odoo make html

Elle sera alors disponible dans le répertoire _build/html. Je vous encourage à suivre les tutoriels quisont très biens agencés si vous souhaitez en savoir plus sur le framework Odoo suite à la lecture decet article.Sinon, en ligne, la documentation est accessible à cette adresse :https://www.odoo.com/documentation/8.0/.

2.3 Créer votre première base de données

À partir de cet instant, toutes les commandes présentées dans cet article devront être exécutées parl'utilisateur odoo et je partirais du répertoire racine de cet utilisateur (/opt/odoo).Pour démarrer le serveur Odoo, il suffit d'exécuter le programme odoo.py dans le répertoire odoo :$ cd odoo$ ./odoo.py 2015-09-18 02:45:35,523 4624 INFO ? openerp: OpenERP version 8.02015-09-18 02:45:35,523 4624 INFO ? openerp: addons paths: ['/opt/odoo/.local/share/Odoo/addons/8.0', u'/opt/odoo/odoo/openerp/addons', u'/opt/odoo/odoo/addons']2015-09-18 02:45:35,524 4624 INFO ? openerp: database hostname: localhost2015-09-18 02:45:35,524 4624 INFO ? openerp: database port: 54322015-09-18 02:45:35,524 4624 INFO ? openerp: database user: odoo2015-09-18 02:45:35,896 4624 INFO ? openerp.service.server: HTTP service (werkzeug) running on 0.0.0.0:8069

Le serveur Odoo nous informe qu'il exécute la version 8.0. Il nous indique également le chemin derecherche des modules (nous en reparlerons très vite), les valeurs des paramètres de connexion à labase de données et l'adresse et le port sur lesquels il écoute.Il ne vous reste donc à ouvrir l'URL suivante dans votre butineur préféré : http://127.0.0.1:8069/ enremplaçant bien évidemment l'adresse IP 127.0.0.1 par celle de votre serveur Odoo s'il ne s'exécutepas sur la même machine que votre navigateur.Un serveur Odoo peut servir plusieurs bases de données simultanément. Il est ainsi possible de créerdes instances distinctes, par exemple une base de données de production, une autre pour le dévelop-pement, une autre encore qui serait une copie de la production pour expérimenter sur un jeu de don-nées réelles (un bac à sable), etc. Comme il n'existe pour l'instant aucune base de données dans

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 6

Page 7: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

notre installation, Odoo affiche directement l'écran qui permet d'en créer une. Ce que nous allonsnous empresser de faire. Cet écran est présenté en figure 1.

Le mot de passe principal est le mot de passe qui permet de gérer les bases de données, sa valeurpar défaut est admin et nous verrons un peu plus loin comment le modifier. Choisissez ensuite unnom pour votre base de données. Il vous faut ici rester classique et n'utiliser que des lettres, chiffreset quelques caractères de ponctuation (sous-ligné et trait d'union), bref ce qui peut constituer unnom autorisé pour une base de données PostgreSQL. Si vous souhaitez les modules fonctionnelsd'Odoo (CRM, ventes, comptabilité, etc.), vous pouvez cocher la case « Charger les données de dé-monstration », ainsi chaque module installé chargera des données de démonstration.Nous seronsd'ailleurs amenés à prévoir un tel jeux de données lors du développement de notre module. Enfin,choisissez une langue par défaut pour votre base de données ainsi que le mot de passe pour l'utilisa-teur admin et actionnez le bouton « Créer la base de données ».Il vous faudra alors patienter quelques dizaines de secondes pour laisser le temps à Odoo de créerune base de données PostgreSQL, charger la quinzaine de modules installés par défaut et créer lesobjets associés dans la base de données (pas moins de 113 tables et 92 séquences pour les 16 mo-dules installés par défaut).

2.4 Astuces

Avant de commencer, voici quelques personnalisations pour personnaliser notre base de donnéesafin de nous faciliter la vie.

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 7

Page 8: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

Le premier écran affiché après la création de la base de données et la liste des applications dispo-nibles localement et qu'il est possible d'installer. Mais ce n'est pas l'objet de cet article. Par contre,certains modules installés par défaut ne sont pas nécessaires et nous pouvons les supprimer. Pourobtenir la liste des modules installés, il nous faut changer de filtre. Les filtres sont très utilisés dansOdoo et ils fonctionnent de la même manière pour tous les écrans. Commencez par supprimer lefiltre « Applications » en cliquant sur la petite croix à sa droite ou sur celle qui se trouve dans le pe-tit cercle qui se trouve tout à fait à droite du champs qui contient les filtres appliqués. Cliquez en-suite sur le petit triangle pointé vers le bas juste à côté pour ouvrir la liste des filtres prédéfinis etchoisissez le filtre « Installé ». Et comme je préfère l'affichage sous forme de liste, je sélectionneégalement le bouton « vue Liste » qui se trouve sous le champs des filtres et comme cela est montrédans la figure 2.

Nous pouvons supprimer les modules « Tests » et « Bus de messagerie instantanée ». Ce dernierprovoquera la suppression des modules « Messagerie instantanée » et « Support en ligne d'Odoo »qui en dépendent. La messagerie instantanée n'est pas requise pour ce que nous souhaitons faire et ilvous faudra souscrire un contrat de support auprès de FIXME avant de pourvoir les contacter enligne. Pour supprimer un module, il suffit de cliquer sur ce module dans la liste puis utiliser le bou-ton « Désinstaller » et confirmer dans l'écran qui suit. Pour revenir à la liste des modules, il suffit decliquer sur le bouton « vue Liste » pour quitter la vue formulaire.Le deuxième élément de personnalisation consiste à activer l'accès aux écrans techniques d'Odoo.Pour cela, allez dans le menu « Utilisateurs > Utilisateurs » et cliquez sur l'utilisateur Administrator.Vous êtes alors en vue formulaire et, pour modifier cet enregistrement, utilisez le bouton « Modi-fier ». Dans l'onglet « Droits d'accès » cochez « Caractéristiques techniques » et, tant que nous ysommes, sélectionnez le fuseau horaire qui vous correspond dans l'onglet « Préférences » afin de

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 8

Page 9: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

faire disparaître l'icône inesthétique d'avertissement qui se trouve dans la barre supérieure del'écran. Cliquez sur le bouton « Enregistrer » pour sauvegarder les modifications. Encore une fois,tous les formulaires fonctionnent de la même manière dans Odoo, y compris ceux que nous serontamenés à développer.Forcez un rafraichissement de la page avec votre navigateur et vous aurez alors accès à de nouveauxmenus.Enfin, le dernier outil qui nous sera utile lors du développement de nouveaux modules et le modedéveloppeur qui peut être activé à partir de l'écran « À propos de Odoo » accessible via le menu af-fiché lorsque vous cliquez sur le nom de l'utilisateur actuellement connecté en haut à droite del'écran (« Administrator » pour l'instant). Lorsque le mode développeur est activé, le nom de la basede données active est affiché à droite du nom de l'utilisateur connecté (utile pour éviter de faire desbêtises sur la production), le code Javascript envoyé au navigateur n'est plus compacté afin d'êtreplus lisible et un menu contextuel contenant des outils d'aide au développement et à la personnalisa-tion d'Odoo est disponible en haut de l'écran.

2.5 Fichier de configuration

Lorsque nous commencerons à développer nos modules, nous devrons créer des répertoires (unpour chaque nouveau module) à un endroit du système de fichiers où Odoo le trouvera. Il faut pourcela le lui indiquer avec l'option --addons-path de la ligne de commande. Nous utiliserons le réper-toire ~/mes-modules (qui se traduit par /opt/odoo/mes-modules). Cependant, Odoo vérifie que ce ré-pertoire contient au moins un module avant de l'accepter, il nous faut donc le duper pour l'instant encréant un répertoire pour notre future premier module (cookbook), répertoire qui doit contenir aumoins les deux fichiers __init__.py et __openerp__.py (même s'ils sont vides pour l'instant) :$ mkdir ~/mes-modules$ mkdir ~/mes-modules/cookbook$ touch ~/mes-modules/cookbook/__init__.py$ touch ~/mes-modules/cookbook/__openerp__.py$ ./odoo.py --addons-path=~/mes-modules,~/odoo/addons

Ah ! Et, lorsqu'on utilise l'option --addons-path, Odoo retire de sa liste le répertoire addons de soninstallation, il faut donc l'ajouter systématiquement à la nouvelle liste des répertoires des modules(~/odoo/addons).Cependant, plutôt que spécifier les options en ligne de commande, nous préférerons utiliser un fi-chier de configuration dans lequel nous pourrons également configurer d'autres paramètres. Ce fi-chier est soit ~/.openerp_serverrc qui est le fichier par défaut recherché par Odoo, soit un autre fi-chier dans l'emplacement et le nom seront indiqués via l'option --config ou -c du serveur Odoo,c'est vous qui voyez.Voici le contenu du fichier ~/odoo.conf avec les paramètres les plus couramment modifiés :[options]addons_path = ~/mes-modules,~/odoo/addonsadmin_passwd = admindata_dir = ~/.local/share/Odoo#db_host = localhost#db_port = 5432db_password = db_user = odooxmlrpc_interface =xmlrpc_port = 8069

Nous avons déjà vu le rôle du paramètre addons_path (notez que, dans le fichier de configuration, letrait d'union dans le nom des paramètres est remplacé par un caractère souligné) et vous devinerezaisément quel est l'usage des paramètres commençant par db_. Le paramètre admin_password sert àmodifier le mot de passe principal, celui qui est utilisé pour gérer les bases de données d'Odoo. C'est

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 9

Page 10: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

pourquoi il faut faire attention aux droits d'accès associés au fichier de configuration (je ne sauraistrop recommander les droits 600). Le paramètre data_dir donne le nom d'un répertoire utilisé parOdoo pour stocker diverses données : informations de sessions et fichiers téléchargés notamment. Ilest donc important de l'inclure dans un plan de sauvegarde et je préfère donc changer son emplace-ment vers un répertoire au nom plus explicite plutôt qu'un répertoire dissimulé.Les deux derniers paramètres permettent de spécifier l'adresse et le port TCP sur lesquels le serveurOdoo écoutera. En production un serveur mandataire inverse est quasiment tout le temps mis enœuvre, c'est pourquoi, souvent, on préfèrera faire écouter Odoo sur l'adresse 127.0.0.1 uniquement.Une fois le fichier ~/odoo.conf apprêté, vous pouvez démarrer votre serveur Odoo ainsi :$ ~/odoo/odoo.py -c ~/odoo.conf

Pour obtenir la liste de tous les paramètres du serveur Odoo, .utilisez la commande ./odoo.py--help ou consultez la documentation.

3. Modules

Trêve de préliminaires, je vous sais impatient de commencer à mettre les mains dans le cambouis.

3.1 Le squelette

Comme je vous le disais un peu plus haut, un module Odoo est un répertoire qui contient au moinsles deux fichiers __init__.py et __openerp__.py. Le premier car un module Odoo est un module Py-thon et le second permet de décrire les caractéristiques du module. Occupons-nous pour commencerde ce dernier dont voici un exemple que je vous invite à saisir :# -*- coding: utf-8 -*-{ 'name': "Cookbook", 'summary': "Odoo can even make the cook!", 'description': """This module permit you to record recipes and to share themon the internet.""", 'author': "S. Namèche", 'category': 'Knowledge Management', 'application': True, 'version': '0.1', 'licence': 'AGPL', 'depends': ['base'], 'data': [ ], 'demo': [ ],}

Ce fichier contient uniquement un dictionnaire Python dont les clefs ont la signification suivante :- name : le nom du module, étant entendu que le nom technique (l'identifiant unique) est le nom durépertoire du module ;- summary : un sous-titre descriptif court ;- description : une description complète du module qui peut contenir au format reStructuredText ;- author : l'auteur du module ;- website :une URL vers un site donnant plus d'information sur le module ;- category : les catégories permettent de classer les modules par usage (FIXME) ;- application : un booléen qui indique si le module est une application FIXME ? ;

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 10

Page 11: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

- version : la version actuelle du module ;- licence : sa licence à choisir parmi GPL-2, GPL-2 or any later version, GPL-3, GPL-3 or anylater version, AGPL-3, LGPL-3, Other OSI approved licence et Other proprietary (la valeur pardéfaut étant AGPL-3) ;- depends : la liste des modules dont dépend notre module, si aucune dépendance spécifique n'est re-quise, utiliser simplement ['base'] ;- data et demo : des fichiers de données XML ou CSV qui seront chargés lors de l'installation ou dela mise à jour du module afin de décrire des écrans, des rapports, des droits d'accès et de données dedémonstration, les fichiers de la liste demo ne seront importés dans la base de données que si celle-cia été créée avec l'option « Charger les données de démonstration ». Pour l'instant ces deux listes desfichiers à importer sont vides mais nous ne tarderons pas à les utiliser.Même si notre module semble pour l'instant limité en terme de fonctionnalités, nous allons l'instal-ler dans notre base de données Odoo. Pour cela, commencez ensuite par cliquer sur le menu « Mo-dules > Mettre à jour la liste des modules » pour demander à Odoo de parcourir les répertoires listésdans le paramètres addons_path afin de repérer des nouveaux modules ou mettre à jour des modulesdéjà connus. Retournez ensuite dans le menu « Modules > Modules locaux », supprimez le filtre « Ap-plications » et saisissez le mot « cookbook » suivi de la touche Entrée afin de créer un filtre person-nalisé. Il ne vous reste plus qu'à cliquer sur le bouton « Installer ».

3.2 Le modèle de données

Ce n'est pour l'instant guère spectaculaire. Tachons d'avancer.La première chose dont nous avons besoin sont des données. Pour cela, Odoo met à notre disposi-tion un ORM qui nous déleste de toute la gestion de la base de données. Pas une seule ligne SQL àécrire.Dans ce premier module, nous utiliserons deux ensemble des données : des recettes classées par ca-tégories (entrée, plat, dessert, amuse-gueule, etc.). Dans la terminologie Odoo, il s'agit de modèles.Ces modèles sont créés sous forme de classes Python, donc dans un fichier avec une extension .py,et les bonnes pratiques énoncées par Odoo requiert de placer ce fichier dans un sous-répertoiremodels du répertoire du module.Nous allons donc créer ces deux premiers modèles dans le fichier ~/mes-modules/cookbook/models/recipe.py :# -*- coding: utf-8 -*-

from openerp import fields, models

DIFFICULTIES = [ ('1', 'soo easy'), ('2', 'easy'), ('3', 'average'), ('4', 'difficult'), ('5', 'arduous')]

class RecipeCategory(models.Model): _name = 'cookbook.recipe.category' _description = 'Recipe categories'

name = fields.Char(required=True)

class Recipe(models.Model): _name = 'cookbook.recipe' _description = 'Recipes'

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 11

Page 12: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

name = fields.Char(required=True) category_id = fields.Many2one('cookbook.recipe.category', string='Category') is_public = fields.Boolean(default=True) difficulty = fields.Selection(DIFFICULTIES, default=3, index=True) cooking_time = fields.Integer(help="Time in minutes for the preparation") baking_time = fields.Integer(help="Time in minutes for the baking") ingredients = fields.Html() preparation = fields.Html()

Comme recommandé par les règles de codage d'Odoo, ce fichier commence par la ligne # -*-coding: utf-8 -*- (vous n'utilisez plus d'autre codage qu'UTF-8, n'est-ce pas ?).Nous importons ensuite les modules models et fields du framework Odoo qui nous seront utiles,respectivement, pour déclarer des modèles et des champs de données associés à ces modèles.Suit la déclaration de deux classes Python. Le nom de ces classes importe peu, il est simplement re-commandé qu'il utilise la syntaxe CamelCase. Ces classes doivent hériter de la classe models.Model,c'est ainsi que leur seront conférer le comportement magique gérer par l'ORM (FIXME). La défini-tion de ces classes commence par la déclaration d'attributs dont _name qui est le plus important etqui définit le nom du modèle connu d'Odoo. De ce nom sera dérivé le nom de la table créée dans leschéma PostgreSQL. L'attribut _description est lui facultatif.Viennent enfin la déclaration des champs de données qui sont décrits comme des attributs de laclasse Python et des instances de classes définies dans le module fields d'Odoo. Nous utilisons dif-férents types de champs : fields.Char() pour une chaîne de caractères, fields.Boolean() pour unbooléen, fields.Integer() pour un entier, fields.Selection() pour une liste et fields.Html() pourun document HTML. Il existe également des champs de type fields.Float(), fields.Text(),fields.Date() et fields.DateTime(). Le champs nommé name est particulier dans le sens où il serautilisé par défaut par Odoo comme pour nommé les enregistrements, il est possible d'utiliser unautre champ pour cela en le déclarant avec l'attribut _rec_name.Le champ category_id dont le type est fields.Many2one() permet d'établir un lien (une relation)entre les deux modèles. Ici, il s'agit d'une relation plusieurs recettes associées à une catégorie. Ilexiste également des relations de type fields.One2many() et fields.Many2many().Ces champs sont caractérisés par différents paramètres qui peuvent être spécifiques ou non au typedu champ :- required : indique que le champ est requis ;- string : permet de changer le libellé du champ présenté à l'utilisateur et qui, par défaut, est le nomdu champs ;- default : est la valeur par défaut du champ ;- index : demande à Odoo de créer un index pour ce champ dans la base de données ;- help : contenu de la bulle d'aide qui sera affichée à l'utilisateur lorsque celui-ci placera sa sourissur le libellé du champ.Comme notre module Odoo est un module Python, nous ne devons pas oublier de spécifier dans lefichier __init__.py du répertoire principal du module de charger le contenu du répertoire models etdans dans le même fichier du répertoire models de charger le fichier recipe.py.Voici ce que dois contenir le fichier ~/mes-modules/cookbook/__init__.py :# -*- coding: utf-8 -*-import models

Voici ce que dois contenir le fichier ~/mes-modules/cookbook/models/__init__.py :# -*- coding: utf-8 -*-import recipe

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 12

Page 13: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

Avant de mettre à jour le module dans l'interface d'Odoo et comme nous avons modifier du codesource Python, il nous faut redémarrer le serveur Odoo. Dans la console qui vous a servi à démarrerle serveur, utilisez les touches Control-C pour interrompre le programme Python puis relancez-le.Enfin, retournez dans la liste des modules installés en utilisant le filtre adéquat (notre module figureégalement dans la liste des applications qui est le filtre par défaut), sélectionnez le module Cook-book et utilisez le bouton « Mettre à jour » comme sur l'écran visible sur la figure 3.

Sur cet écran, vous pourrez admirer la splendide icône (réalisée par Freepik, disponible surwww.flaticon.com et dont la licence est CC BY 3.0) que j'ai ajoutée au module. Pour ce faire, il suf-fit de copier une image nommée icon.png dans le répertoire static/description du module (donc~/mes-modules/cookbook/static/description/icon.png).Une fois encore, le résultat n'est pas très spectaculaire, sauf si nous allons consulter la liste des mo-dèles connus d'Odoo soit via le menu « Technique > Structure de la base de données > Modèles » en ap-pliquant un filtre « cookbook », soit un utilisant le très utile pgAdmin III pour accéder directement àla base de données. Nous découvririons alors sous nos yeux ébahis les modèles cookbook.recipe etcookcook.recipe.category (dans Odoo) et les tables cookbook_recipe et cookcook_recipe_category(dans pgAdmin).Observons d'ailleurs qu'Odoo a ajouté certains champs supplémentaires dans la base de données : id(identifiant unique séquentiel), create_uid (identifiant de l'utilisateur qui a créé l'enregistrement),create_date (date de création), write_uid (identifiant de l'utilisateur qui a, le dernier, modifié l'en-registrement) et write_date (date de la dernière modification).

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 13

Page 14: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

Pour l'instant, nous nous sommes focalisés uniquement sur le contenu de nos modèles, les données.Mais un modèle de données est plus que cela, il est également garant de la cohérence de ces don-nées par rapport à une réalité.

3.3 Champs calculés et contraintes

Plus loin dans cet article, nous allons créer des critères de recherches sur le temps total pour réaliserles recettes (temps de préparation plus temps de cuisson). Il nous faut pour cela un champ supplé-mentaire : total_time qui sera la somme des champs cooking_time et baking_time. Odoo met ànotre disposition un outil pour gérer ce type de situations : les champs calculés.Nous aurons besoins d'importer un nouveau module Python d'Odoo pour cela : le module api. Mo-difiez en conséquence le fichier models/recipe.py comme ceci :from openerp import api, fields, models

À la liste des champs de la classe Recipe, nous ajoutons la définition du champ total_time : total_time = fields.Integer(compute='_compute_total_time', store=True)

Nous définissons ce champ comme un champ traditionnel, mais deux options viennent modifier soncomportement : compute dont la valeur est le nom d'une méthode de la classe et store, un booléenindiquant que nous souhaitons conserver dans la base de données les valeurs de ce champ. En effet,par défaut l'ORM d'Odoo ne crée pas de colonne dans la table associée au modèle pour les champscalculés. Dans certaines cas, nous aurons besoin de stocker cette valeur, nous utilisons pour celal'option store=True. Cela est nécessaire, par exemple, afin de pouvoir faire des recherches sur cechamp, ce dont nous aurons besoin un peu plus tard (il existe un autre moyen pour effectuer des re-cherches sur un champ calculé mais il n'est pas possible de l'utiliser dans cette situation).Enfin, toujours au sein de la classe Recipe, nous déclarons la méthode _compute_total_time : @api.depends('cooking_time', 'baking_time') def _compute_total_time(self): for rec in self: rec.total_time = rec.cooking_time + rec.baking_time

Plusieurs choses ici. Tout d'abord le décorateur Python @api.depends() qui modifie le comporte-ment de la méthode qu'il précède de telle sorte qu'elle sera appelée dès que l'un des champs passésen argument au décorateur sera modifié (ici, cooking_time ou baking_time). Second point impor-tant, les méthodes définies dans un modèle Odoo attendent en paramètre (self) un ensemble d'enre-gistrements (recordset). Par conséquent, le corps de notre méthode devra parcourir tous les élémentsde cet ensemble avec une boucle for afin d'affecter au champ total_time de chaque élément lasomme des deux champs cooking_time et baking_time de ce même élément.Ainsi, la valeur du champ total_time sera gérée automatiquement par Odoo et mise à jour dès quel'un des deux champs cooking_time ou baking_time sera modifié.Attaquons-nous maintenant aux contraintes de nos modèles. Par exemple, il serait souhaitable queles noms de nos catégories soient uniques. Nous ne voudrions pas avoir deux catégories « Dessert ».Par ailleurs, un modèle de données reflétant une réalité, il serait également pertinent d'interdire queles recettes faciles soient trop longues à préparer.Avec Odoo 8.0, deux types de contraintes sont à notre dispositions : les contraintes SQL et lescontraintes modélisées par une méthode.Nous allons utiliser des contraintes SQL pour exprimer le fait qu 'une recette super facile ne devraitpas prendre plus de 30 minutes de préparation et un recette facile pas plus de 60 minutes. Lescontraintes SQL sont exprimées sous la forme d'un tableau de tuples, chacun de ces tuples estconstitué de trois éléments : le nom de la contrainte, une chaîne SQL et un un message d'erreur. Cetableau doit être affecté à la variable _sql_constraints de la classe :

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 14

Page 15: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

class Recipe(models.Model): .../... _sql_constraints = [ ('check_difficulty1', "check (difficulty != '1' or cooking_time < 30)", "The recipe can't be so easy with that much cooking time!"), ('check_difficulty2', "check (difficulty != '2' or cooking_time < 60)", "The recipe can't be so easy with that much cooking time!"), ]

Ainsi, le premier élément du tableau _sql_constraints décrit la contrainte nomméecheck_diffculty1 qui affichera le message « The recipe can't be so easy with that much cookingtime! » si elle n'est pas respectée. Elle provoquera l'exécution de l'instruction SQL suivante afin decréer la contrainte dans le schéma de la base de données PostgreSQL :ALTER TABLE cookbook_recipeADD CONSTRAINT cookbook_recipe_check_difficulty1CHECK (difficulty::text <> '1'::text OR cooking_time < 30);

La seconde forme de contrainte passe par l'écriture d'une méthode. Nous allons l'utiliser pour nousassurer de l'unicité du champ name du modèle cookbook.recipe.category. Nous aurions pu essayerd'utiliser une contrainte SQL comme ceci : _sql_constraints = [ ('name_uk', "unique(name)", "The category name must be unique."), ]

Mais, dans le cas de colonnes de type varchar et en UTF-8 qui plus est(vous n'utilisez plus d'autrecodage qu'UTF-8, n'est-ce pas ?), ce n'est pas si évident car plusieurs problèmes se posent : la dis-tinction entre les minuscules et les majuscules et la distinction entre les caractères accentués etautres signes diacritiques (notamment notre chère cédille). Nous voudrions que « entrée », « Entrée »et « ENTREE » soient tous considérés comme étants le même mot. Malheureusement, PostgreSQLne nous permet de créer une contrainte unique(lower(name)).Une solution de contournement consiste à utiliser l'opérateur =ilike qui permet de comparer deschaînes en ignorant la casse des caractères au sein d'une construction Odoo nommée : domaine. Etpour faire en sorte qu'une lettre accentuée soit considérée comme identique à la même lettre, nousallons utiliser une extension de PostgreSQL et une option bien pratique du serveur Odoo : unaccent.Avant d'aller plus loin, nous devons importer un nouveau module d'Odoo. Le module exceptionsdéfini des exceptions parmi lesquelles nous utiliserons exceptions.ValidationError() :from openerp import api, exceptions, fields, models

Voici maintenant le code de la méthode : @api.one @api.constrains('name') def _check_name(self): if self.search([('name', '=ilike', self.name), ('id', '!=', self.id)]): raise exceptions.ValidationError("The category name must be unique.")

Souvenez-vous que, par défaut, l'argument self des méthodes d'un modèle Odoo est un recordset,un ensemble d'enregistrements et nous devrions normalement parcourir cet ensemble avec uneboucle for. Le décorateur @api.one nous évite cela. Lorsque nous l'utilisons, nous sommes certainsque self est un enregistrement simple et nous n'avons plus besoin de boucle dans le corps de notreméthode.Le décorateur @api.constrains indique à Odoo que nous allons définir une méthode qui va vérifierles contraintes sur les champs qui lui sont passés en paramètres, ici name. Chaque fois que ce champsera modifié, cette méthode sera appelée.Vient ensuite la définition de la méthode dont le nom importe peu mais que les règles de codaged'Odoo, nous recommandent de nommer _check_name.Nous utilisons alors la méthode de l'ORM search() qui nous permet de rechercher les enregistre-ments du modèle qui correspondent à un critère. Il existe d'autres méthodes de l'API Odoo pour ma-

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 15

Page 16: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

nipuler les enregistrements dont les plus utilisées, avec search(), sont : create(), write() etbrowse().L'argument de la méthode search() est un domaine. Dans Odoo, un domaine est une liste de cri-tères, ils sont très utilisés en plusieurs endroits de l'API. Chaque critère est défini par un triplet :champ, opérateur, valeur. Par défaut ces critères sont combinés par un ET logique. L'opérateur bi-naire préfixé | peut être utilisé pour combiner les deux critères suivants avec un OU logique. L'opé-rateur unaire ! inverse, lui, la valeur logique de l'argument qu'il précède. Mous cherchons donc lesenregistrements dont le champ name est égal à self.name sans tenir compte de la casse (=ilike) etdont l'identifiant n'est pas celui de l'enregistrement courant afin qu'il ne se trouve pas lui-même.Malheureusement, les domaines ne permettent pas d'effectuer des requêtes trop complexes, parexemple le domaine suivant n'est pas valide : ('cooking_time+baking_time', '<', 60) car le pre-mier élément du tuple doit être le nom d'un champ. D'où le recourt au champ calculé total_timeque nous avons mis en place afin de pouvoir effectuer des recherches sur le temps nécessaire pourservir le plat sur la table.search() retourne un ensemble d'enregistrements. Si un enregistrement correspond au critère de re-cherche, la condition if est vérifiée et nous levons une exception avec raise. ExceptionValidationError qui est définie dans le module exceptions d'Odoo.Ça, c'était pour la comparaison des chaines de caractères sans distinction de casse. Qu'en est-il de lagestion des accents ? Pour l'instant le mot « Entrée » est toujours différent de « Entree ». C'est unpoint plus important qu'il n'y paraît. Pour y remédier, nous allons utiliser l'option --unaccent du ser-veur Odoo qui requiert l'installation de l'extension unaccent de PostgreSQL.Cette extension fournit la fonction unaccent qu'Odoo sait utiliser lorsque nous le lui demandonsavec l'option --unaccent. Pour l'activer, il faut tout d'abord installer le paquetage postgresql-contrib puis activer l'extension dans la base de données PostgreSQL avec l'instruction SQL createextension, comme ceci :$ sudo aptitude install postgresql-contrib$ sudo -u postgres psql cuisine -c "create extension unaccent;"

Cependant, cette dernière commande n'installe l'extension que dans la base de données cuisine. Sinous voulons qu'elle soit activée dans toutes les bases de données à venir, il faut simplement l'acti-ver dans la base de données template1 qui sert de modèle lors de la création d'une base de donnéesPostgreSQL, comme ceci :$ sudo -u postgres psql template1 -c "create extension unaccent;"

Il ne reste plus qu'à activer l'option unaccent dans le fichier ~/odoo.conf :[options].../...unaccent = True

Un redémarrage du serveur Odoo et nous pourrons comparer des chaînes de caractère en tenantcompte des accents.Voici un récapitulatif du fichier models/recipe.py avec les modifications effectuées dans cette sec-tion :# -*- coding: utf-8 -*-

from openerp import api, exceptions, fields, models

DIFFICULTIES = [ .../... ]

class RecipeCategory(models.Model): _name = 'cookbook.recipe.category' _description = 'Recipe categories'

name = fields.Char(required=True)

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 16

Page 17: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

@api.one @api.constrains('name') def _check_name(self): if self.search([('name', '=ilike', self.name), ('id', '!=', self.id)]): raise exceptions.ValidationError("The category name must be unique.")

class Recipe(models.Model): _name = 'cookbook.recipe' _description = 'Recipes' _sql_constraints = [ ('check_difficulty1', "check (difficulty != '1' or cooking_time < 30)", "The recipe can't be so easy with that much cooking time!"), ('check_difficulty2', "check (difficulty != '2' or cooking_time < 60)", "The recipe can't be so easy with that much cooking time!"), ]

name = fields.Char(required=True) category_id = fields.Many2one('cookbook.recipe.category', string='Category') is_public = fields.Boolean(default=True) difficulty = fields.Selection(DIFFICULTIES, default='3', index=True) cooking_time = fields.Integer(help="Time in minutes for the preparation") baking_time = fields.Integer(help="Time in minutes for the baking") total_time = fields.Integer(compute='_compute_total_time', store=True) ingredients = fields.Html() preparation = fields.Html()

@api.depends('cooking_time', 'baking_time') def _compute_total_time(self): for rec in self: rec.total_time = rec.cooking_time + rec.baking_time

Bon, nos modèles sont bien là, mais avant de voir comment créer des écrans pour y accéder, il nousfaut d'abord y importer quelques données.

3.4 Importer des données

Deux ensembles de données peuvent être importés lors de l'installation ou la mise à jour d'un mo-dule Odoo : des enregistrements qui seront importés quelque soit la situation et d'autres qui le serontuniquement si la base de données a été créée avec l'option « Charger les données de démonstra-tion ».Dans le cadre de notre module, nous allons créer des catégories par défaut et quelques recettes decuisine. Ces dernières ne seront utilisées que pour des démonstrations ou des tests.Il est possible de charger des enregistrements dans Odoo via un module en utilisant l'un de ces deuxformats : XML ou CSV. Ce dernier convient pour les enregistrements simples, possédant peu dechamps et de références, mais surtout dans le cas où de très nombreux enregistrements doivent êtreimportés. Dans un soucis de didactique, nous utiliserons le format CSV pour les catégories de re-cette et le format XML pour le jeu de test de quelques recettes.Les règles de codage d'Odoo demandent à ce que les fichiers de données soient stockés dans lesous-répertoire data du répertoire du module. De plus, pour le format CSV, le nom du fichier doitobligatoirement être le nom du modèle concerné. Nous créons donc les lignes suivantes dans le fi-chier ~/mes-modules/cookbook/data/cookbook.recipe.category.csv :"id","name""category_appetizer","Appetizer""category_first_course","First course""category_main_dish","Main dish""category_dessert","Dessert"

La première ligne du fichier correspond à la liste des champs. Le champ spécial id représente l'iden-tifiant externe de l'enregistrement. La notion d'identifiant externe permet de faire référence à des en-

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 17

Page 18: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

registrements aisément. En effet le seul autre identifiant unique disponible est la colonne id destables créées par Odoo et qui est remplie avec une séquence numérique. Cet identifiant externe n'estpas obligatoire mais il est chaudement recommandé de l'utiliser. Il doit être unique pour tous les en-registrements de tous les modèles d'un module.Il est également utilisé par Odoo lors de la mise-à-jour d'un module pour déterminer les enregistre-ments qui doivent être ajoutés (nouvel identifiant externe) ou modifiés (identifiant déjà connu).En ce qui concerne nos recettes de démonstration importées au format XML, les règles de codaged'Odoo stipulent que le fichier doit se trouver dans le sous-répertoire data et que son nom doit êtrecelui du modèle suivi de _demo dans le cas de données de démonstration (et _data sinon). Voici doncle contenu du fichier ~/mes-modules/cookbook/data/cookbook_recipe_demo.xml (je dévoile ici deuxrecettes phares de mon répertoire culinaire : ma cultissime tarte aux légumes encensée par mes en-fants et le pain perdu du dimanche soir qui me permet de ne pas jeter le pain rassis) :<openerp> <data> <record id="tarte_legume" model="cookbook.recipe"> <field name="name">Tarte aux légumes</field> <field name="category_id" ref="category_first_course"/> <field name="is_public" eval="False"/> <field name="difficulty">3</field> <field name="cooking_time">20</field> <field name="baking_time">20</field> <field name="ingredients" type="html"> <ul> <li>une pâte brisée ;</li> <li>25 cl de lait ;</li> <li>4 œufs ;</li> <li>un choux brocoli ;</li> <li>2 carottes, 2 oignons.</li> </ul> </field> <field name="preparation" type="html"> <ol> <li>Nettoyer, éplucher et faire cuire le choux brocoli et les carottes coupées en rondelles dans une casserole d'eau (les légumes doivent rester croquants).</li> <li>Éplucher et émincer les oignons et les faire revenir dans un peu d'huile d'olive.</li> <li>Laisser refroidir les légumes.</li> <li>Mélanger les œufs et le lait, saler, poivrer et ajouter des épices.</li> <li>Étaler la pâte dans un moule à tarte, y disposer les légumes et recouvrir avec l'appareil.</li> <li>Mettre au four préchauffé à 180 ºC et laisser cuire environ 20 minutes (vérifier la cuisson avec un couteau).</li> <li>Servir chaud ou froid.</li> </ol> </field> </record> <record id="pain_perdu" model="cookbook.recipe"> <field name="name">Pain perdu</field> <field name="category_id" ref="category_main_dish"/> <field name="is_public" eval="True"/> <field name="difficulty">2</field> <field name="cooking_time">5</field> <field name="baking_time">15</field> <field name="ingredients" type="html"> <ul> <li>25 cl de lait ;</li> <li>3 œufs ;</li> <li>75 g de sucre de canne ;</li> <li>des grosses tranches de vieux pain ;</li> <li>sucre, confitures, miel, chocolat en poudre pour accompagner.</li> </ul> </field> <field name="preparation" type="html">

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 18

Page 19: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

<ol> <li>Mélanger le lait, les œufs et le sucre de canne.</li> <li>Bien imbiber les tranches de pain dans le mélange.</li> <li>Les faire cuire dans une poêle généreusement beurrée et très chaude.</li> <li>Servir avec un peu de sucre, de confiture, de miel ou de chocolat.</li> </ol> </field> </record> </data></openerp>

Ce fichier XML doit contenir des éléments record dans un élément data dans un élément openerp.Chaque élément record représente un enregistrement pour le modèle spécifié dans son attributmodel et dont l'identifiant externe est indiqué par l'attribut id. Les éléments record contiennent deséléments field, un pour chaque champ de l'enregistrement. Chaque élément field possède obligatoi-rement un attribut name qui indique le nom de la colonne concernée. La valeur du champ peut êtrefournie de plusieurs manières : dans le corps de l'élément field, via l'attribut ref qui fait référence àl'identifiant externe d'un enregistrement existant (utilisé pour les champs Many2one notamment).D'autres méthodes sont possible encore, mais nous ne les aborderons pas ici. Notez l'attributtype="html" de l'élément field pour le champ preparation, sans lui Odoo considérait le corps del'élément comme invalide car il ne doit pas contenir des nœuds supplémentaires.Un fois ces deux fichiers créés, il faut indiquer à Odoo de les importer lors de l'installation ou lamise à jour de notre module. Cela se fait dans le fichier __openerp__.py avec les clefs data pour lesdonnées qui doivent toujours être importées et demo pour celles qui doivent l'être uniquement enmode démonstration. Modifiez donc les lignes adéquates du fichier ~/mes-modules/cookbook/__openerp__.py comme ceci : 'data': [ 'data/cookbook.recipe.category.csv', ], 'demo': [ 'data/cookbook_recipe_demo.xml', ],

Il suffit maintenant de mettre à jour le module et Odoo importera les données du premier fichier.Redémarrer le serveur Odoo n'est dans ce cas pas nécessaire car nous n'avons pas modifié de codePython (à l'exception du fichier __openerp__.py). Avec pgAdmin III, il est possible de voir que leserveur Odoo a bien importé le contenu du fichier cookbook.recipe.category.csv dans la tablecookbook_recipe_category.Il est également possible de vérifier que les identifiants externes correspondants ont été enregistréspar Odoo en allant dans le menu « Technique > Séquences & identifiants > Identifiants externes ».Dans le champ filtre entrez la valeur « cookbook » et choisissez « Rechercher cookbook dans Mo-dule » dans le menu contextuel qui s'affiche. La figure 4 montre l'écran ainsi obtenu, nous y voyonsles identifiants externes des catégories de nos recettes associés à la valeur du champ id de la tablecookbook_recipe_category (colonne « ID de l'enregistrement »). Nous y voyons aussi d'autres iden-tifiants externes relatifs à notre module, Odoo crée ces identifiants externes automatiquement pourbeaucoup d'objets de sa base de données.Si vous avez créé votre base de données sans les données de démonstration, nos deux recettes ne se-ront pas importées. Pour cela, il vous faudra créer une autre base de données. C'est très facile :

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 19

Page 20: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

1. Déconnectez-vous de la base de données actuelles : option Déconnexion dans le menu qui s'afficheen cliquant sur le nom de l'utilisateur connecté en haut à droite de l'écran (« Administrator »).2. Cliquez sur le lien Manage Databases en bas de l'écran de connexion, l'écran de gestion des basesde données s'affiche.3. Créez une nouvelle base de données en fournissant le mot de passe principal (admin si vous nel'avez pas changé dans le fichier de configuration du serveur Odoo), le nom de la nouvelle base dedonnées (cuisine_demo par exemple), cochez cette fois-ci la case Charger les données de démonstrationet choisissez la langue qui vous convient et un mot de passe pour l'utilisateur Administrator avantde cliquer sur le bouton Créer la base de données.4. Vous devrez maintenant re-configurer la nouvelle base de données comme nous l'avons fait pourla première (suppression éventuelle des modules inutiles, activation de l'accès aux données, etc.)avant d'installer notre module Cookbook. Cette fois-ci les données du fichier cookbook_recipe.xmlseront également importées.Après avoir créer cette seconde base de données, si vous vous déconnectez de nouveau, vous obser-verez qu'un nouveau champ est présenté dans l'écran de connexion : Database qui permet de choisirle nom de la base de données à laquelle vous souhaitez vous connecter.

3.5 Puis-je avoir le menu s'il-vous-plaît ?

Fort bien, nos modèles sont créés et nous disposons d'un jeu de données. Voyons maintenant com-ment y avoir accès.La première étape consiste à « accrocher » nos deux modèles à un menu. Les menus d'Odoo sont or-ganisés sous forme hiérarchique, le menu principal étant matérialisé par la barre noir en haut del'écran. Celle-ci est encore vide car aucun menu n'y est encore attaché (à l'exception du menu deConfiguration).

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 20

Page 21: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

Nous allons créer un menu principal Cookbook, dans lequel nous allons créer deux sections Recetteset Configuration (qui sont également des menus). Dans la première section nous ajouterons un lienpour atteindre nos recettes et dans la seconde un lien vers la liste des catégories de recette. DansOdoo, ces deux liens sont des actions. Nous allons donc créer deux actions et cinq entrées de menu.Les menus sont des enregistrements du modèle ir.ui.menu et les actions des enregistrements dumodèle ir.actions.act_window (du moins pour les actions qui ouvrent une fenêtre, il existe d'autrestype d'actions). Nous avons déjà vu comment importer des enregistrements pour nos modèlescookbook.recipe et cookbook.recipe.category. Ce n'est pas différent pour les modèles ir.ui.menuet ir.actions.act_window. Commençons par ce dernier, nous allons créer deux enregistrementsdans un fichier XML qui, selon les règles de codage d'Odoo, doit se trouver dans le sous-répertoireviews de notre module et être nommé selon le modèle principal de notre module. Il nous faut donccréer le fichier ~/mes-modules/cookbook/views/recipe_views.xml avec le contenu suivant :<openerp> <data> <record id="recipe_action" model="ir.actions.act_window"> <field name="name">Recipes</field> <field name="res_model">cookbook.recipe</field> <field name="view_mode">tree,form</field> </record>

<record id="category_action" model="ir.actions.act_window"> <field name="name">Recipe categories</field> <field name="res_model">cookbook.recipe.category</field> <field name="view_mode">tree,form</field> </record>

<menuitem id="cookbook_menu" name="Cookbook"/>

<menuitem id="cookbook_main_menu" name="Recipes" parent="cookbook_menu"/>

<menuitem id="cookbook_configuration_menu" name="Configuration" parent="cookbook_menu"/>

<menuitem id="cookbook_recipes_menu" name="Recipes" parent="cookbook_main_menu" action="recipe_action"/>

<menuitem id="cookbook_categories_menu" name="Categories" parent="cookbook_configuration_menu" action="category_action"/> </data></openerp>

Les deux premiers éléments record de notre fichier décrivent des enregistrements pour le modèleir.actions.act_window qui représente l'ouverture d'un écran. Comme d'habitude, l'attribut id leurattribut un identifiant externe et l'attribut model faire référence au modèle. Les champs qui doiventêtre renseignés sont : le nom de l'action (name), le modèle concerné (res_model) et les types de vuesdisponibles dans l'écran (view_mode). Nous activons ici deux types de vue : la vue liste (tree) et lavue formulaire (form).Le reste du fichier contient des éléments menuitem que nous ne connaissions pas. Il s'agit simple-ment d'un raccourci sympathique (un sucre de syntaxe) qui vient nous simplifier la vie pour créerdes enregistrements du modèle ir.ui.menu. L'attribut id de cet élément a exactement la même signi-fication que celui de l'élément record, mais il est nul besoin de préciser le modèle car il s'agit impli-citement de ir.ui.menu. Les autres attributs utilisés sont :- name : le nom du menu, celui qui sera affiché ;

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 21

Page 22: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

- parent : l'identifiant externe du menu parent, le premier n'en ayant pas, il sera accroché au menuprincipal, en haut de l'écran ;- action : pour les menus feuilles, l'identifiant externe de l'action à déclencher lorsque l'utilisateurclique sur ce menu.Ici, le menu Cookbook est le menu principal qui sera donc dans la barre des menus, Recipes et Confi-guration seront deux sections du menu affiché sur la gauche de l'écran et Recipes (encore) et Catego-ries seront des liens vers les actions décrites précédemment qui afficheront nos recettes et catégo-ries.Il vous suffit maintenant de modifier une nouvelle fois le fichier __openerp__.py du module pour yajouter le fichier views/recipe_views.xml dans la liste associée à la clef data : 'data': [ 'data/cookbook.recipe.category.csv', 'views/recipe_views.xml', ],

Enfin, mettez le module à jour comme nous l'avons déjà fait (inutile de recharger le serveur Odoo)et vous verrez apparaître en haut de l'écran deux menus : Cookbook et Configuration. Le premier estassocié à notre module et le second permet d'accéder au menu de configuration d'Odoo que nousconnaissons. Ce menu existait auparavant mais il était masqué car seul menu principal.

Si vous cliquez sur le menu Cookbook, par défaut vous arriverez sur le menu Recipes de la sectionRecipes. Ce menu déclenche l'action Recipes qui affiche la vue liste de nos recettes. Pour passer surla vue formulaire, il suffit de cliquer sur le nom de l'une des recettes ou sur l'icône vue Formulaire. Encliquant sur le bouton Modifier, nous pouvons éditer la recette comme le montre la Figure 6.

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 22

Page 23: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

Odoo a eu la gentillesse de générer une vue liste et une vue formulaire par défaut, mais elles ne sontni esthétiques, ni pratiques en ce qui concerne les recettes. Les vues liste et formulaire pour les caté-gories sont acceptables car elle ne contiennent qu'un seul et unique champ (le nom de la catégorie).

3.6 Vue imprenable

Voyons maintenant comment créer des vues plus conviviales pour nos recettes. La vue liste devraprésenter le nom de la recette, mais également la catégorie ainsi que le niveau de difficulté et lestemps de cuisson et de préparation. La vue formulaire va être complètement réorganisée.Dans Odoo, les vues sont, comme tout le reste… des enregistrements d'un modèle. Le modèleir.ui.view en l'occurence. Et nous savons FIXME comment importer des enregistrements d'un mo-dèle : par l'intermédiaire de fichiers de données XML (le format CSV ne conviendra pas ici car lesenregistrements sont trop complexes). Nous allons dans un premier temps nos occuper des vuesliste et formulaire pour les recettes. Deux vues, donc deux enregistrements.Le premier enregistrement pour la vue liste : <record id="recipe_view_tree" model="ir.ui.view"> <field name="name">cookbook.recipe.tree</field> <field name="model">cookbook.recipe</field> <field name="arch" type="xml"> <tree> <field name="name"/> <field name="category_id"/> <field name="difficulty"/> <field name="cooking_time"/> <field name="baking_time"/> <field name="total_time"/> </tree> </field> </record>

Nous reconnaissons la structure habituelle d'un élément record avec son identifient externe et lenom du modèle concerné dans ses attributs. En ce qui concerne les champs du modèle ir.ui.view,nous devons fournir au moins les trois suivants :- name : le nom de la vue (qui n'est pas affiché) ;- model : le modèle qui sera affiché par la vue ;- arch : la description de la vue, nous devons utiliser l'attribut type="xml".Le corps de l'élément <field name="arch"> est une description au format XML de la vue. Ici nousdécrivons une vue liste, l'élément englobant doit donc être tree. Ses éléments fils (field) donnent laliste des champs du modèle que nous souhaitons voir dans la vue liste : le nom de la recette, sa caté-gorie et sa difficulté ainsi que les temps de préparation, de cuisson et leur somme (le champtotal_time).L'enregistrement qui va décrire la vue formulaire de notre modèle cookbook.recipe est plus com-plexe : <record id="recipe_view_form" model="ir.ui.view"> <field name="name">cookbook.recipe.form</field> <field name="model">cookbook.recipe</field> <field name="arch" type="xml"> <form> <sheet> <label for="name" class="oe_edit_only"/> <h1><field name="name"/></h1> <label for="category_id" class="oe_edit_only"/> <h2><field name="category_id"/></h2> <notebook> <page string="Preparation"> <label for="ingredients"/> <field name="ingredients"/>

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 23

Page 24: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

<label for="preparation"/> <field name="preparation"/> </page> <page string="Properties"> <group> <group string="Cooking information"> <field name="difficulty"/> <field name="cooking_time"/> <field name="baking_time"/> <field name="total_time"/> </group> <group string="Access"> <field name="is_public"/> </group> </group> </page> </notebook> </sheet> </form> </field> </record>

Je passe sur les premiers éléments de l'enregistrement dont l'usage est identique à ceux de l'enregis-trement de la vue liste et en vient directement à la description XML de la vue. Ne vous inquiétezpas, cela est moins complexe qu'il n'y paraît.Cette fois-ci il s'agit d'un formulaire, l'élément englobant est form. Le corps de cet élément estconstitué du seul élément sheet qui contiendra tous les autres éléments du formulaire. Cet élémentsheet n'est pas obligatoire, sa fonction est purement esthétique : il présente le formulaire avec le vi-suel d'une feuille de papier (plus joli, mais restreint l'espace disponible pour le formulaire). La fi-gure 6 vous permet de vous faire une idée du rendu graphique.Au sein de cette feuille, nous commençons par présenter le nom de sa recette et la catégorie asso-ciée avec les éléments field comme nous l'avions fait pour la vue liste. Nous les encadrons des élé-ments HTML h1 et h2 pour les mettre en évidence et les précédons chacun d'un élément label car,dans ce contexte, le libellé du champ n'est pas affiché par défaut. Les attributs de ces éléments labelsont for qui permet de faire le lien avec le champ (et donc de déterminer le texte du libellé qui seraaffiché) et class qui contient le nom d'une classe CSS. Nous utilisons la classe oe_edit_only définiepar Odoo et qui n'affiche le libellé que lorsque le formulaire est en mode édition.Vient ensuite un élément notebook qui permet d'organiser le reste des champs sous forme d'onglets.Chaque onglet étant introduit par un élément fils page. Les noms des onglets sont précisés avec l'at-tribut string de ces éléments. Dans le premier onglet, nous présentons simplement les champsingredients et preparation avec leurs libellés.Dans le second onglet, nous affichons les propriétés « techniques » de notre recette. Mais nous utili-sons un élément group qui va organiser leur affichage sous la forme liste dans laquelle les libellés etles champs associés seront alignés verticalement. Il n'est donc pas nécessaire ici de préciser explici-tement le placement des libellés avec un élément label, l'élément group s'en chargeant. Avec un sipetit nombre de champs, un seul élément group pourrait suffire mais, pour en montrer un usage cou-rant, j'utilise un élément group englobant deux autres. Cette construction permet d'afficher leschamps sur deux colonnes. Enfin, il est possible de donner un titre à un groupe en utilisant l'attributstring.

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 24

Page 25: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

Ajoutez ces deux blocs de code au fichier views/recipe_views.xml, peu importe où mais au sein del'élément data. Vous connaissez la musique désormais, il nous faut mettre à jour le module afind'importer ces nouvelles vues dans notre base de données. Cependant, il est devient vite fastidieuxd'aller sur l'écran des modules, de rechercher le module Cookbook puis de cliquer dessus avant depouvoir atteindre le bouton Mettre à jour. De plus, souvent une modification d'un fichier Python re-querra le redémarrage du serveur. Il existe une option du serveur Odoo pour lui demander de mettreà jour un module lors de son démarrage : -u suivit du nom du module. Mais cette option requiertalors l'option -d qui indique la base de données concernée. Du coup, lors de la phase de développe-ment d'un module, il est bien plus efficace de redémarrer le serveur Odoo avec la ligne de com-mande suivante qui, en une seule manipulation en console, met à jour le code Python et le modulecookbook dans la base de données cuisine_demo :$ ~/odoo/odoo.py -c ~/odoo.conf -u cookbook -d cuisine_demo

Après la mise-à-jour du module, vous pourrez profiter de la plus belle des vues pour nos recettes.Mais je sens poindre une question dans l'esprit des plus affutés d'entre vous : comment Odoo a t-ilfait le lien entre nos actions (ir.actions.act_window) et les vues que nous venons de décrire ? Nulpart nous lui avons indiqué que nous souhaitions utiliser telle vue pour l'affiche liste ou formulairede nos recette. En fait, Odoo le détermine seul : lorsqu'il une action lui commande d'afficher unécran pour un modèle, il va rechercher une vue associée à ce modèle. S'il n'en trouve pas, il génèreun écran par défaut. Il est aussi possible de créer plusieurs vues pour un même modèle. Une actionir.actions.act_window peut alors demander explicitement l'affichage d'une vue (champs views etview_id). Sinon Odoo choisira une vue en s'aidant, notamment, du champ priority qu'il est pos-sible d'associer à une vue.Bien occupons-nous maintenant des catégories. Leur cas est bien plus simple, comme elles n'ontqu'un seul champ, nous pouvons les gérer directement dans la vue liste et nous passer de la vue for-mulaire. Dans ce cas, voici à quoi peut ressembler une telle vue liste : <record model="ir.ui.view" id="recipe_category_view_tree"> <field name="name">cookbook.recipe.category.tree</field> <field name="model">cookbook.recipe.category</field>

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 25

Page 26: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

<field name="arch" type="xml"> <tree editable="top" default_order="name"> <field name="name"/> </tree> </field> </record>

Rien de nouveau sous le soleil à l'exception des deux attributs editable et default_order de l'élé-ment tree. Le premier indique que les enregistrements peuvent être édités directement dans la vueliste et qu'un nouvel enregistrement sera inséré au début (top) ou à la fin de la liste (bottom) lorsquel'utilisateur utilisera le bouton Créer. default_order permet d'indiquer le nom du champ qui sera uti-lisé pour trier les enregistrements.Ajoutez ce code au fichier views/recipe_views.xml et profitez-en pour modifier le champview_mode l'action qui affiche la fenêtre des catégories de recettes afin de n'autoriser que la vue liste(retirez ,form) : <record id="category_action" model="ir.actions.act_window"> .../... <field name="view_mode">tree</field>

Mettez à jour le module Cookbook de la manière qui vous convient et vous pourrez tester cette nou-velle vue.Tout cela commence à prendre tournure. Mais, si nous revenons à la vue liste des recettes, et lorsquenotre collection de recettes sera digne du plus grand chef étoilé, nous pourrions vouloir fournir àl'utilisateur la possibilité d'effectuer simplement des recherches autres que sur le seul nom de la re-cette.

3.7 Filtres

Pour l'instant, lorsque l'utilisateur entre un critère de recherche dans le champ filtre de la vue listedes recettes, le seul choix qui lui est offert est une recherche sur le nom. Il peut, certes, utiliser descritères de recherche avancés qui s'affiche en cliquant sur le triangle pointé vers le bas dans lechamp filtre, mais nous devrions lui permettre d'effectuer rapidement des recherches sur certainscritères simples et souvent utilisés. Typiquement il serait intéressant de pouvoir identifier les re-cettes qui pourraient me permettre de cuisiner ces fichus brocolis, donc une recherche sur la listedes ingrédients. Pour les cuisiniers amateurs, un filtre identifiant les recettes de difficulté moyenneet en dessous serait d'une grande aide. Et, lorsque je rentre à la maison à 12h30 et comme le matchde rugby du grand commence à 13h30, je voudrais bien un mode crise qui me permette d'identifierles recettes dont le temps de préparation est inférieur à 10 minutes et le temps de cuisson inférieur à20., Enfin, comme nos recettes sont organisées par catégorie, nous devrions pouvoir les affichergroupées par catégories.Tout cela est relativement aisé. Il suffit pour cela de créer une vue d'un type un peu particulier en cesens où elle n'affiche pas vraiment des enregistrements mais propose des filtres de recherche à l'uti-lisateur. Si c'est une vue, c'est un enregistrement du modèle ir.ui.view : <record model="ir.ui.view" id="recipe_view_search"> <field name="name">cookbook.recipe.search</field> <field name="model">cookbook.recipe</field> <field name="arch" type="xml"> <search> <field name="name"/> <field name="ingredients"/> <filter string="Easy" domain="[('difficulty','&lt;=',2)]"/> <filter string="Quick!" domain="[('total_time','&lt;',30)]"/> <group string="Group By"> <filter string="Category" context="{'group_by': 'category_id'}"/> <filter string="Difficulty" context="{'group_by': 'difficulty'}"/> </group> </search> </field>

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 26

Page 27: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

</record>

L'élément racine de cette vue est, cette fois-ci, l'élément search. Les fils de cet élément pourrontêtre field, filter ou group (qui ne sert qu'à grouper des filtres).Trois types de recherche sont possibles : les recherches sur une chaîne de caractères appliquée à unchamp, les filtres décrits sous la forme de domaines et les filtres permettant de grouper les enregis-trements en fonction de la valeur d'un champ.Concernant le premier type de recherche, il s'agit simplement de préciser avec des éléments field lenom des champ sur lesquels il sera possible de faire une recherche rapide. Ici, il s'agit des champsname et ingredients. Dès que l'utilisateur commencera à taper un mot dans le champ filtre, un sous-menu lui proposera d'effectuer cette recherche dans le nom ou la liste des ingrédients des recettes.Le second type de recherche permet de spécifier des critères prédéfinis, l'utilisateur n'aura qu'à cli-quer sur le nom du filtre (ou de plusieurs filtres) afin de les appliquer. Il s'agit ici des filtres Easy etQuick! introduits par l'élément filter. Les critères de recherche de ces filtres sont décrits par des do-maines de recherche que nous avons déjà rencontrés. Notre premier filtre Easy filtre toutes les re-cettes dont le niveau de difficulté est inférieur ou égale à deux :domain="[('difficulty','&lt;=',2)]" (dans un document XML le caractère < doit être remplacépar l'entité &lt;). Le second filtre (Quick!) sélectionne les recettes dont le temps total pour la réalisa-tion est inférieur (&lt;) à 30 minutes : domain="[('total_time','&lt;',30)]".Dans un domaine, les critères de recherche peuvent être groupés avec des parenthèses et il existeseize opérateurs différents. Il est donc possibles de former des critères de recherche relativementcomplexes.Le dernier type de filtre n'en est pas exactement un, il s'agit plutôt de la possibilité de grouper lesenregistrements par valeurs d'un champ. Pour cela, il faut utiliser l'attribut context de l'élémentfilter comme ceci : context="{'group_by': 'category_id'}". Dans la présentation, Odoo ne dis-tingue pas les filtres classiques des filtres qui organisent les enregistrements en groupes. C'est pour-quoi, très souvent, l'élément group est employé pour les séparer et désigner les filtres de la secondecatégorie par Group By.Nous pourrions utiliser l'élément group afin de grouper des filtres similaires. Par exemple, pour fil-trer rapidement les recettes selon des temps de cuisson prédéfinis, nous pourrions utiliser cet extraitde code : <group string="Baking"> <filter string="&lt; 10mn" domain="[('baking_time','&lt;',10)]"/> <filter string="&lt; 20mn" domain="[('baking_time','&lt;',20)]"/> <filter string="&lt; 60mn" domain="[('baking_time','&lt;',45)]"/> <filter string="&lt; 60mn" domain="[('baking_time','&lt;',90)]"/> </group>

Il suffit de mettre à jour le module, puis de se rendre sur la liste des recettes et de cliquer sur le tri-angle inversé dans le champ des filtres pour afficher nos filtres prédéfinis. La figure 7 montre com-ment se présente la liste des recettes organisée par catégorie (filtre Category).Mais à force de relancer le serveur Odoo, je sais que vous n'avez pu vous empêcher de remarquerles deux avertissements suivants :

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 27

Page 28: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

2015-09-21 00:30:43,094 34595 WARNING cuisine_demo openerp.modules.loading: The model cookbook.recipe has no access rules, consider adding one. E.g. access_cookbook_recipe,access_cookbook_recipe,model_cookbook_recipe,,1,0,0,0

Odoo nous informe, très à propos, que nous n'avons pas définis de droits d'accès pour nos modèles.

3.8 Droits d'accès

Nous allons mettre une place une politique de sécurité relativement basique à trois niveaux : l'admi-nistrateur Odoo, le chef et le gâte-sauce. Les catégories ne pourront être modifiées que par l'admi-nistrateur Odoo mais seront accessibles en lecture par tous les utilisateurs. Quant aux recettes, seulle chef pourra les modifier et les gâtes-sauce les consulter. Pour octroyer ces droits, nous allonscréer deux groupes d'utilisateurs : celui des chefs (The Chief) et celui des gâtes-sauce (Sorry cook).Les groupes, vous l'aurez deviné, sont des enregistrements d'un modèle, res.groups. Pour présenterl'octroi d'un groupe à un utilisateur dans le formulaire utilisateur sous la forme d'une liste dérou-lante, il nous faut aussi créer un une catégorie de module (modèle ir.module.category). Créez le fi-chier security/recipe_security.xml suivant (les règles de codage d'Odoo requièrent que les fi-chiers de données relatifs à la gestion des droits d'accès soient placés dans le sous-répertoiresecurity du répertoire du module) :<openerp> <data> <record id="module_category_cookbook" model="ir.module.category"> <field name="name">Cookbook</field> <field name="sequence">200</field> </record>

<record id="cookbook_group_sorry" model="res.groups"> <field name="name">Sorry cook</field> <field name="category_id" ref="module_category_cookbook"/> </record>

<record id="cookbook_group_chief" model="res.groups"> <field name="name">The Chief</field>

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 28

Page 29: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

<field name="category_id" ref="module_category_cookbook"/> <field name="implied_ids" eval="[(4, ref('cookbook_group_sorry'))]"/> </record> </data></openerp>

Le premier enregistrement crée une catégorie (modèle ir.module.category) dont l'identifiant ex-terne est module_category_cookbook et le nom Cookbook. Il sera affiché en dernier dans la liste desapplications sur le formulaire des utilisateurs (200 est le numéro de séquence le plus élevé par rap-port aux autres catégories de modules).Les deux enregistrements suivants ont pour modèle res.groups et ils font tous deux référence àl'identifiant externe de la catégorie de modules que nous venons de créer(module_category_cookbook). Il y a cependant une petite subtilité quant au second groupe (identi-fiant externe cookbook_group_chief) : le champ dont le nom est implied_ids. Il s'agit d'un champ detype Many2many qui peut donc faire référence à plusieurs enregistrements d'un modèle. En fait il faitréférence au modèle res.groups, C'est-à-dire lui-même. La signification est la suivante : l'apparte-nance à ce groupe implique l'appartenance aux groupes référencés par ce champ. Ici, un utilisateurauquel le groupe The Chief sera associé, sera également membre implicite du groupe Sorry cook. Lasyntaxe utilisée pour cela est un peu particulière : dans un fichier de données XML, les champs detype Many2many attendent une liste de tuples de un à trois éléments. Le premier de ces éléments in-dique l'opération à réaliser et les autres, optionnels, sont les arguments de l'opération. Ici 4 signifieque l'argument est l'identifiant d'un enregistrement à ajouter à la liste des références du champMany2one. Mais comme nous ne connaissons pas l'identifiant de l'enregistrement associé au groupeSorry cook, nous utilisons la fonction ref() qui transforme un identifiant externe en un identifiantde l'enregistrement dans la table. Les autres opérations possibles sur un champ Many2many sont :- (0, 0, { ... }) : lie (crée une référence vers) un nouvel enregistrement qui sera créé à partir dudictionnaire passé en second argument ;- (1, ID, { ... }) : met à jour l'enregistrement dont l'identifiant est ID avec les valeurs du diction-naires (cet enregistre est déjà lié) ;- (2, ID) : supprime la référence vers l'enregistrement dont l'identifiant est ID et supprime cet enre-gistrement ;- (3, ID) : supprime la référence vers l'enregistrement dont l'identifiant est ID (mais ne supprimepas cet enregistrement) ;- (4, ID) : crée une référence vers l'enregistrement existant dont l'identifiant est ID ;- (5) : supprime toutes les références ;- (6, 0, [IDs]) : remplace toutes les références (supprime les références actuelles et crée de nou-velles références vers les enregistrements dont les identifiants est la liste [IDs]).Mais quelle est donc la raison pour laquelle je me donne tout ce mal ? Ne serait-il pas plus simplede créer deux groupes simples, sans référence vers une catégorie de module, sans champimplied_ids ? La raison est la suivante : je souhaite que ma liste des groupes associés à mon mo-dule soit présentée dans le formulaire utilisateur sous la forme d'une liste déroulante comme cela estvisible sur la Figure 8. Il faut pour cela que deux conditions soient remplies : que tous les groupesconcernés fassent appartiennent à la même catégorie de modules et que tous les groupes formentune hiérarchie directe et linéaire (le groupe A implique le groupe B qui implique le groupe C). Neme demandez pas pourquoi, c'est ainsi. Et c'est le genre d'information qu'on ne peut connaître quepar expérience ou en décortiquant le code source d'Odoo (pour les petits curieux, cela se passe dansla classe groups_implied du fichier openerp/addons/base/res/res_users.py).Une fois réglée la question de la définition des groupes, il nous faut maintenant octroyer des droits àces groupes. Pour cela, nous devons créer des enregistrements du modèle ir.model.access. Tradi-

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 29

Page 30: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

tionnellement cela est toujours fait par un fichier CSV qui doit donc être nomméir.model.access.csv (son nom doit correspondre au nom du modèle) et se trouver dans le réper-toire security du module (ça, ce sont les règles de codage d'Odoo qui le demande). Il serait tout àfait possible d'utiliser un format XML plutôt qu'un format CSV. Nous allons donc créer le fichiersecurity/ir.model.access.csv qui va définir les droits d'accès à nos deux modèles(coobook.recipe.category et cookbook.recipe) :id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlinkaccess_recipe_category,access_recipe_category,model_cookbook_recipe_category,,1,0,0,0access_recipe_sorry,access_recipe_sorry,model_cookbook_recipe,cookbook_group_sorry,1,0,0,0access_recipe_chief,access_recipe_chief,model_cookbook_recipe,cookbook_group_chief,1,1,1,1

La première ligne de ce fichier liste les champs du modèle ir.model.access et les trois lignes sui-vantes décrivent les droits d'accès à nos deux modèles. Voici la signification de chacun des champs :- id : identifiant externe associé à l'enregistrement courant ;- name : nom de l'enregistrement, peu significatif, identique à l'identifiant externe par facilité ;- model_id:id : identifiant externe du modèle concerné, Odoo a automatiquement créé les identi-fiants externes model_cookbook_recipe_category et model_cookbook_recipe lors de la définition denos modèles ;- group_id:id : identifiant externe du groupe auquel nous allons octroyer des droits pour le modèle,nous retrouvons ici les identifiants externes cookbook_group_chief et cookbook_group_sorry desgroupes que nous avons créés dans le fichier security/recipe_security.xml, si le groupe n'est pasfourni alors les accès définis par cet enregistrement sont octroyés à tous les utilisateurs ;- perm_read : si 1, les membres du groupe ont accès en lecture aux enregistrement du modèle ;

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 30

Page 31: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

- perm_write : si 1, les membres du groupe ont accès en écriture aux enregistrements du modèle ;- perm_create : si 1, ils peuvent créer des enregistrement pour ce modèle ;- perm_unlink : si 1, ils peuvent supprimer des enregistrements du modèle.Il me faut préciser ici qu'il n'est nul besoin d'octroyer des droits à l'utilisateur administrateur d'Odoo(Administrator par défaut) car cet utilisateur spécial contourne tout le système des droits et disposeainsi de tous les accès pour tous les modèles, tout comme l'utilisateur root d'un système Unix.Il nous faut ensuite ajouter ces deux nouveaux fichiers de données à la liste des fichiers de donnéesdu module dans le fichier __openerp__.py : 'data': [ .../... 'security/recipe_security.xml', 'security/ir.model.access.csv', ],

Une mise-à-jour du module importera alors les groupes et droits d'accès associés.Vous pourrez accéder aux groupes créés via le menu Configuration > Utilisateurs > Groupes et aux droitsd'accès dans le menu Configuration > Technique > Sécurité > Liste des contrôles d'accès.Vous pourrez maintenant (et je vous encourage à le faire) créer des utilisateurs et leur associer l'undes groupes de notre module Cookbook puis vérifier que les accès sont bien octroyés comme il sedoit.En implémentant des groupes et en leur octroyant des droits nous avons fait une bonne action etnous avons éliminé les deux messages d'avertissement affichés par le serveur Odoo. Cependant, cedernier formule encore une autre plainte à l'égard de notre module :2015-09-24 21:04:13,626 2448 WARNING cuisine_demo openerp.addons.base.ir.ir_translation: module cookbook: no translation for language fr

4. Traduction

L'internationalisation d'un module Odoo se fait en trois phases :1) L'extraction et la traduction des chaînes extractibles automatiquement par Odoo.2) L'extraction et la traduction des chaînes à traduire explicitement avec la fonction _().3) L'internationalisation du contenu des enregistrements.Le première phase est relativement simple, le menu Configuration > Traductions > Importer/Exporter >Export de la traduction ouvre une fenêtre de dialogue nous permettant d'extraire les chaînes à traduired'un module. Choisissez Nouvelle langue (modèle de traduction vierge) dans la liste de sélection Langue(c'est l'option par défaut), puis Fichier PO pour le format et Cookbook dans la liste des modules àexporter, comme cela est visible dans la Figure 9. Cliquez sur le bouton Exporter puis sur le lien Télé-charger cookbook.po dans la fenêtre suivante. Puis utilisez un éditeur de fichiers PO tel que POedit oul'utilitaire msginit (paquetage gettext) pour créer un fichier fr.po. Placez ce nouveau fichier dansle sous-répertoire i18n, mettez le module à jour et rechargez l'interface Web d'Odoo. Les menus etles écrans de notre module s'affichent désormais en Français.Par contre, le message affiché lorsque la contrainte implémentée par la méthode _check_name()(pour les catégories de recettes) est toujours en anglais. D'ailleurs vous n'avez pas trouver cettechaîne à traduire dans le fichier PO. En effet, Odoo est capable d'extraire seul beaucoup de chaînesdepuis les fichiers Python, XML ou CSV. Par exemple, il a extrait pour nous les libellés du champsélection difficulty, les descriptions des modèles (variable _description), les messages affichéspar les contraintes SQL, les options string et help ainsi que le nom des champs, etc. Mais il ne peut

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 31

Page 32: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

extraire les chaînes dans le code Python, typiquement le message de l'exception levée dans la mé-thode _check_name. Pour cela, il va nous falloir signaler explicitement que cette chaîne doit êtretraduite. Nous utiliserons pour cela la fonction _() du module Odoo _. Il nous faut donc l'importerdans le fichier models/recipe.py :from openerp import api, exceptions, fields, models, _

Puis nous utilisons la fonction _() pour la chaîne à traduire : def _check_name(self): if self.search([('name', '=ilike', self.name), ('id', '!=', self.id)]): raise exceptions.ValidationError(_("The category name must be unique."))

Après avoir mis à jour le module, il nous faut exporter de nouveau un fichier PO mais, cette fois-ci,en choisissant la langue French / Français (toujours au format PO et toujours pour le module Co-obook). Le fichier ainsi exporté est nommé fr.po et nous y trouverons la chaîne à traduire :#. module: cookbook#: code:addons/cookbook/models/recipe.py:23#, python-formatmsgid "The category name must be unique."msgstr "Le nom de la catégorie doit être unique."

Une fois la traduction effectuée, une dernière mise-à-jour du module Coobook nous permettrad'avoir un message en Français lors que la contrainte est enfreinte.La dernière phase de traduction concerne le contenu même des enregistrements. Comme nous nedoutons pas du fulgurant succès international de notre application, nous décidons de prévoir dèsmaintenant de publier le contenu de nos recettes en plusieurs langues. À commencer par le nom descatégories. Odoo peut gérer pour nous les différentes traductions du contenu de nos champs, pourcela il suffit de lui indiquer via l'option translate=True quels sont les champs qui devront être inter-nationalisés :class RecipeCategory(models.Model): .../... name = fields.Char(required=True, translate=True)

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 32

Page 33: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

.../...

class Recipe(models.Model): .../... name = fields.Char(required=True, translate=True) .../... ingredients = fields.Html(translate=True) preparation = fields.Html(translate=True)

Il suffit alors de mettre à jour le module puis d'aller modifier l'un des noms de catégorie de recettepour avoir accès un bouton stylisant un formulaire avec un drapeau (voir la Figure 10). Cliquer surce bouton ouvre un formulaire qui présente la liste de toutes les langues installées dans la base dedonnées Odoo et permet de traduire le champ dans chacune de ces langues. Pour l'instant, notrebase de données n'est configurée qu'avec deux langues : l'Anglais et le Français. L'Anglais est lalangue par défaut, aussi dans le formulaire de traduction, seule la ligne pour le français apparaît. Ilsuffit de traduire la valeur de la colonne Valeur de la traduction (voir la Figure 11) et Odoo affichera lavaleur traduite en Français lorsqu'il s'agira de la langue de l'utilisateur et en Anglais sinon.

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 33

Page 34: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

Nous avançons d'un bon pas. La partie privée (Backend) de notre application peut être considéréecomme aboutie. Voyons maintenant la partie publique, celle qui sera présentée aux internautes(Frontend).

5. Publication

Vous n'aurez pas manqué de remarquer qu'Odoo est une application Web. À ce titre, il nous est pos-sible de l'utiliser pour publier nos merveilleuses recettes sur la toile de l'arachnée mondiale. Deuxmécanismes vont nous aider en cela : les contrôleurs et les modèles QWeb. Mais il nous faut aupa-ravant ajuster les droits d'accès à nos recettes.

5.1 Recettes publiques

En effet, tout comme ils le sont côté Backend, les droits d'accès aux modèles sont respectés côtéFrontend. et, si nos catégories sont accessibles en lecture par tous, nos recettes, elles, ne le sont pas.Nous devons donc commencer par y donner accès à tous. Pour cela, ajoutez la ligne suivante au fi-chier security/ir.model.access.csv :access_recipe_public,access_recipe_public,model_cookbook_recipe,,1,0,0,0

Cependant, seules les recettes marquées comme publiques (champ is_public) devront être acces-sibles. Odoo possède un mécanisme pour cela : les règles. Elles permettent d'octroyer des accès enfonction du contenu même des enregistrements. Nous allons donc créer un enregistrement pour lemodèle ir.rule dans le fichier security/recipe_security.xml : <record id="cookbook_recipe_only_public" model="ir.rule"> <field name="name">Restrict access to public recipes</field> <field name="model_id" ref="model_cookbook_recipe"/> <field name="domain_force">[('is_public','=',True)]</field> <field name="perm_read" eval="True"/> <field name="perm_write" eval="False"/> <field name="perm_create" eval="False"/> <field name="perm_unlink" eval="False" /> </record>

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 34

Page 35: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

Le contenu de cet enregistrement est relativement simple et fait appel à des notions que nousconnaissons désormais : une référence vers l'identifiant externe du modèle concerné (model_id) etun domaine pour sélectionner les enregistrements concernés par la règle (ici, tous ceux dans lechamp is_public est vrai). Les champs perm_* octroient ou non les accès. Nous n'utilisons pas ici lechamp groups qui permet d'appliquer la règle à un ou plusieurs groupes. À partir du moment où ilexiste au moins une règle pour un modèle, Odoo interdira par défaut l'accès aux enregistrements dumodèle si aucune règle ne correspond. De plus, les deux systèmes sont nécessaires :ir.model.access et ir.rule. Nous pourrons tester plus tard que l'accès aux recettes secrètes est bienrefusé.

5.2 Contrôleur

Voyons maintenant comment publier nos ressources gastronomiques. Pour cela, nous allons utiliserune nouvelle classe héritée de http.Controller. Les règles de codage d'Odoo nous indiquent qu'elledoit être créée dans un fichier du sous-répertoire controllers de notre module. Créez donc le fichiercontrollers/main/py avec les lignes suivantes :# -*- coding: utf-8 -*-

from openerp import http

class CookbookController(http.Controller):

@http.route('/cookbook/', auth='public') def index(self): return 'Bienvenue !'

Il se passe trois choses ici : 1) La déclaration de la classe CookbookController (dont le nom importe peu) qui est une descen-dante de la classe http.Controller d'Odoo.2) L'usage du décorateur @http.route avec deux paramètres : le premier pour spécifier le cheminqui déclenchera l'appel à la méthode décorée, le second (auth='public') pour indique à Odoo quel'accès à ce chemin ne requiert pas d'authentification.3) La méthode index qui renvoie la simple chaîne « Bonjour Monde ! ».Comme il s'agit d'un nouveau fichier Python, nous devons l'importer dans le fichiercontrollers/__init__.py :# -*- coding: utf-8 -*-import main

Et nous devons ajouter le nom de ce répertoire au fichier __init__.py principal de notre module :# -*- coding: utf-8 -*-import modelsimport controllers

Mettez le module Cookbook à jour et ouvrez l'adresse http://127.0.0.1:8069/cookbook/ (ou touteautre adresse IP qui correspond à votre serveur Odoo). En production, nous aurions un serveur man-dataire inverse qui nous éviterait de préciser le numéro de port dans l'URL (vous penserez alors àutiliser l'option --proxy-mode du serveur Odoo et à interdire l'accès au chemin/web/database/manager). La page de bienvenue sera alors peut-être affichée.Peut-être car il y a ici une petite subtilité que je vais introduire en détaillant les conditions dans les-quelles une erreur 404 sera renvoyée plutôt que notre gentil message. Si vous avez créé deux basesde données au moins dans votre installation Odoo et si vous avez ouvert l'adressehttp://127.0.0.1:8069/cookbook/ avec un navigateur qui ne s'est jamais connecté à Odoo (ou alors dé-barrassé du cookie posé par Odoo), alors vous serez mal accueilli. Pourquoi cela ? Simplement

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 35

Page 36: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

parce que le serveur Odoo ne peut pas connaître le nom de la base de données qui doit gérer cetteURL s'il en existe plusieurs. Trois situations lui permettent de faire son choix :1) Il n'existe qu'une seule base de données dans votre installation d'Odoo.2) Odoo a déjà posé un cookie de session dans le navigateur et il sait quelle base de données y estassociée soit parce que vous vous y êtes déjà connecté, soit parce que vous l'avez juste sélectionnéedans la liste des bases de données affichée sur l'écran de connexion.3) L'utilisation du paramètre dbfilter du serveur Odoo qui permet de déterminer le nom de la basede données à utiliser à partir de la partie nom de domaine de l'URL. Par exemple, si on utilise l'op-tion dbfilter = %d (FIXME) dans le fichier de configuration d'Odoo, les caractères%d seront rem-placés par la première partie du nom de domaine de l'URL qui a été utilisée pour joindre le serveur(à l'exception de www qui est ignoré). Ainsi, si le nom cuisine_demo.monresto.net pointe versl'adresse IP du serveur (via le DNS ou le fichier /etc/hosts), alors les URLhttp://cuisine_demo.monresto.net:8069/cookbook ethttp://www.cuisine_demo.monresto.net:8069/cookbook feront toutes deux référence à la base dedonnées cuisine_demo. Il est à noté que ce mécanisme fonctionne également pour la partie backendd'Odoo et qu'il est fort pratique pour héberger plusieurs applications ou clients sur une seule ins-tance de serveur Odoo. Enfin, lorsque l'option dbfilter est activée, Odoo ne présente plus la listedes bases de données dans l'écran de connexion.Il est souvent plus pratique d'utiliser simultanément deux navigateurs différents. Le premier ouvertsur le backend d'Odoo et le second sur la partie frontend. Par mesure de sécurité, il est fort raison-nable de limiter par le serveur mandataire inverse les accès au chemin /web qui est le chemin debase du backend.

5.3 Modèles QWeb

Voyons maintenant comment afficher un véritable document HTML. Pour cela, Odoo met à notredisposition un langage de modèle nommé QWeb. Ce dernier offre des fonctionnalités simples et re-lativement communes, mais il fait le boulot. Voyons vite un exemple que vous devrez enregistrerdans le fichier recipe_templates.xml du sous-répertoire views de notre module :<openerp> <data> <template id="index"> <title>Cookbook</title> <h1>Welcome to our Cookbook</h1> <form method="get" action="/cookbook/search/"> <input name="search" placeholder="search..."/> </form> <h2>Categories</h2> <ul> <t t-foreach="categories" t-as="category"> <li> <a t-attf-href="/cookbook/category/{{category.id}}"><t t-esc="category.name"/></a> </li> </t> </ul> </template> </data></openerp>

Tout d'abord, nous remarquons l'existence du raccourci <template> pour créer des enregistrementsdu modèle ir.ui.view tout comme nous avions un raccourci <menuitem> pour le modèle ir.ui.view.Nous déclarons ainsi un enregistrement du modèle ir.ui.view dont le nom est index qui sera aussison identifiant externe.Si je ne précise pas les éléments <html>, <header> et <body>, Odoo est suffisamment sympa et les in-sére pour moi en positionnant l'élément <title> à la bonne place. Les lignes suivantes se passent de

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 36

Page 37: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

commentaires car il s'agit de code XHTML relativement basique pour placer sur la page un titre sui-vi d'un formulaire contenant un champ de recherche.Vient ensuite la partie où nous allons insérer une liste de liens qui nous permettront d'accéder à laliste des recettes de chacune des catégories. Ici, nous utiliserons les fonctionnalités de QWeb intro-duites par des attributs t-*. Les éléments <t> n'ont pas de signification particulière et servent à por-ter des attributs t-* si besoin (c'est-à-dire si nous n'avons aucun autre élément pour les porter). Ilsseront supprimés du rendu final. Le premier attribut QWeb employé est t-foreach conjointement àt-as. Le premier sert à créer une boucle qui va parcourir successivement tous les éléments de laliste qui lui est passée en paramètre (ici, la variable categories) et recopier autant de fois le contenudu corps de l'élément auquel il est attaché. t-as donne le nom de la variable qui prendra chacunedes valeurs au sein de la boucle.Le second attribut QWeb est t-attf-href. Il s'agit en fait de la famille des attributs t-att-<nom> ett-attf-<nom> qui vont créer l'attribut nom. La première évalue l'expression Python qui lui est donnéeen paramètre et la seconde attend une chaîne de formatage Python (format string). t-attf-href vadonc créer un attribut href pour notre élément a à partir de la chaîne de formatage/cookbook/category/{{category.id}}. Dans cette chaîne, nous reconnaissons la variable categoryde notre boucle. Vous aurez deviné que la variable categories contiendra une liste d'enregistre-ments du modèle cookbook.recipe.categorie. Nous construisons donc un lien vers le chemin/cookbook/category/ suivi de l'identifiant numérique de la catégorie dans la base de données(category.id).Enfin, le dernier attribut QWeb utilisé ici est t-esc qui prend en paramètre une expression Python,l'évalue et insère le résultat dans le document XHTML. Dans notre cas, il s'agit du nom de la caté-gorie.Comme il est possible de placer les attributs QWeb t-* dans n'importe quel élément, les cinq lignesde code comprises entre <t> et </t> peuvent être écrites de manière plus concise et sans nuire à la li-sibilité comme ceci : <li t-foreach="categories" t-as="category"> <a t-attf-href="/cookbook/category/{{category.id}}" t-esc="category.name"/> </li>

Il nous faut maintenant indiquer à Odoo l'existence d'un nouveau fichier de données à charger enajoutant une ligne dans __openerp__.py : 'data': [ .../... 'views/recipe_templates.xml',

Enfin, il nous faut modifier le code de la méthode index dans notre contrôleur afin de fournir à notremodèle QWeb la variable categories dont il a besoin : @http.route('/cookbook/', auth='public') def index(self): categories = http.request.env['cookbook.recipe.category'] return http.request.render('cookbook.index', { 'categories': categories.search([]), })

La première ligne de la méthode récupère via l'environnement un objet représentant le modèlecookbook.recipe.category. Cet objet va nous donner accès à une API nous permettant de manipulerles enregistrements de ce modèle. Dans l'API d'Odoo, l'environnement donne accès à certaines don-nées contextuelles : le curseur d'accès à la base de données, l'objet modélisant l'utilisateur connectéet le contexte courant. L'environnement est également utilisé pour accéder aux modèles commenous venons de le faire.Nous utilisons ensuite la fonction http.request.render() qui déclenche le rendu XHTML à partirdu modèle QWeb dont l'identifiant est passé en premier paramètre (cookbook.index). C'est de rendu

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 37

Page 38: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

que notre méthode retournera et qui sera envoyé au navigateur. Le second paramètre de la méthodeest un dictionnaire contenant des variables passées au modèle QWeb. Nous lui passons ici la va-riable categories qui est le résultat de l'appel de la fonction search() sur le modèlecookbook.recipe.category avec un domaine vide afin de récupérer la liste complète des catégories.Il suffit de relancer le serveur Odoo et de mettre à jour le module pour obtenir le splendide visuel denotre livre de cuisine public visible en Figure 13.

Vous remarquerez que la page s'affiche avec les noms des catégories en Français et que le mot « Ca-tegory » du modèle QWeb original a été traduit en « Catégories ». Odoo a utilisé la langue de votreconfiguré dans votre navigateur pour faire ce choix. Si vous la modifiez pour l'Anglais et que voussupprimez le cookie de session associé au nm de domaine, tous les termes seront affichés en An-glais. Pour obtenir la page intégralement en Français, il suffit d'ajouter la traduction des termes« Welcome to our Cookbook » et « search... » au fichier i10n/fr.po (Odoo semble avoir du mal àmettre à jour les termes traduits après une modification du fichier .po, il faut souvent passer par unedésinstallation et une ré-installation du module).Nous allons maintenant nous occuper de l'écran qui présent la liste des recettes. Il sera utilisé pourafficher le résultat du formulaire de recherche ainsi que les recettes par catégories. Ajoutez l'enre-gistrement suivant au fichier views/recipe_templates.xml : <template id="recipes"> <title>Recipes</title> <h1>Recipes</h1> <i t-if="not recipes"> Nothing found. </i> <table t-if="recipes"> <tr> <th>Name</th> <th>Category</th> <th>Difficulty</th> <th>Cooking time</th> <th>Baking time</th> <th>Total time</th> </tr> <tr t-foreach="recipes" t-as="recipe"> <td>

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 38

Page 39: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

<a t-attf-href="/cookbook/recipe/{{recipe.id}}" t-esc="recipe.name"/> </td> <td t-esc="recipe.category_id.name"/> <td t-esc="recipe.difficulty"/> <td t-esc="recipe.cooking_time"/> <td t-esc="recipe.baking_time"/> <td t-esc="recipe.total_time"/> </tr> </table> </template>

Rien de bien nouveau si ce n'est l'attribut QWeb t-if qui fait exactement ce qu'on attend de lui.Nous devons également ajouter au contrôleur les deux méthodes pour gérer les chemins/cookbook/search (appelé par le formulaire de recherche) et /cookbook/category/<id>. Comme cesdeux méthodes vont avoir en commun la recherche des enregistrements du modèle cookbook.recipeet l'appel de la fonction http.request.render(), nous utiliserons une méthode recipes() qui seraappelée par les deux autres méthodes : def recipes(self, domain=[]): recipes = http.request.env['cookbook.recipe'] return http.request.render('cookbook.recipes', { 'recipes': recipes.search(domain), })

@http.route('/cookbook/search/', auth='public') def search(self, search): return self.recipes([('name', 'ilike', search)])

@http.route('/cookbook/category/<model("cookbook.recipe.category"):category>', auth='public') def category(self, category): return self.recipes([('category_id', '=', category.id)])

La méthode outil recipes n'est pas appelée directement par un chemin. Elle n'a pas de décorateur.Elle effectue simplement une recherche des enregistrements du modèle cookbook.recipe qui corres-pondent au modèle passé en argument avant de déclencher le rendu du formulaire QWebcookbook.recipes auquel elle passe la variable recipes.La méthode search() qui sera appelée pour le chemin /cookbook/search appelle la méthoderecipes() en lui passant en argument le domaine [('name', 'ilike', search)]. La variable searchest remplie par Odoo avec le contenu du champ nommé search du formulaire, elle doit être déclaréedans la signature de la méthode. J'utilise l'opérateur ilike sur le champ name de la recette afin ded'effectuer une recherche insensible à la casse.Le chemin introduit par le décorateur de la méthode category est plus intéressant. Il utilise la syn-taxe <converter(arguments):name> où converter est un convertisseur et name le nom d'une variableà laquelle sera affectée la valeur convertie. Par exemple en utilisant la spécification /plop/<int:id>et le chemin /arthur/42 dans l'adresse entrée dans le navigateur, la variable id contiendra l'entier42. Par contre le chemin /arthur/accroc génère une erreur HTTP 404 car accroc ne peut êtreconvertit en entier. Dans notre cas, la spécification<model("cookbook.recipe.category"):category> va convertir un entier en un objet Recordsetcontenant l'enregistrement du modèle cookbook.recipe.category dont l'identifiant dans la base dedonnées est cet entier. Fort pratique ! Il ne nous reste plus qu'à appeler la méthode recipes() en luipassant le domaine [('category_id', '=', category.id)].Je trouve que tout cela tient de l'élégance : c'est simple, concis et cela fourni cependant des fonc-tionnalités évoluées.Il nous reste la page qui affiche une recette. Cela ne devrait pas être compliqué. Tout d'abord le mo-dèle QWeb : <template id="recipe"> <title>Recipe: <t t-esc="recipe.name"/></title> <h1 t-esc="recipe.name"/> <table id="properties">

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 39

Page 40: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

<tr> <th>Category</th> <td><span t-field="recipe.category_id.name"/></td> </tr> <tr> <th>Difficulty</th> <td><span t-field="recipe.difficulty"/></td> </tr> <tr> <th>Cooking time</th> <td><span t-field="recipe.cooking_time"/></td> </tr> <tr> <th>Baking time</th> <td><span t-field="recipe.baking_time"/></td> </tr> </table> <h2>Ingredients</h2> <span t-field="recipe.ingredients"/> <h2>Preparation</h2> <span t-field="recipe.preparation"/> </template>

J'emploie ici le nouvel attribut QWeb t-field qui est utilisé pour formater et insérer un champ récu-péré depuis un modèle Odoo. Il semble dans un premier temps similaire à t-esc mais il tient comptedes caractéristiques du champ dans Odoo, notamment de son type. Ainsi, dans ce cas la difficultéest affichée avec son étiquette plutôt que sa valeur et, pour les champs au format HTMLingredients et preparation, il altère leur contenu afin de protéger les caractères spéciaux enHTML. Pour les champs au format HTML, nous aurions pu également employer t-raw, le pendantde t-esc. Mais, de manière générale, il est plus intéressant d'utiliser t-field pour les champs desmodèles Odoo et t-esc (ou t-raw) pour les autres expressions.Et voici la méthode recipe() avec son décorateur pour gérer le chemin/cookbook/recipe/<recipe.id> : @http.route('/cookbook/recipe/<model("cookbook.recipe"):recipe', auth='public') def recipe(self, recipe): return http.request.render('cookbook.recipe, {'recipe': recipe})

Après un redémarrage du serveur Odoo et la mise-à-jour du module, l'ensemble de notre livre decuisine électronique devrait fonctionner correctement. Mais son esthétique laisse quelque peu à dé-sirer.

5.4 Sous-modèles

FIXME : à écrire ?! (t-call + formatage)

6. Héritage

Nous allons présenter dans cette section une fonctionnalité très importante du framework Odoo :l'héritage. Cet est aspect est capital dans le domaine des PGI car elle permet d'étendre les modulesstandards d'Odoo (et les autres) sans pour autant toucher à leur code source. Il suffit pour cela dedévelopper un nouveau module qui viendra étendre les objets existants (modèles, vues, etc.).Pour illustrer cela, imaginons que vous soyez l'utilisateur (heureux) du module Cookbook mais quevous n'en soyez pas l'auteur. Cependant, dans le contexte dans lequel vous employez ce module, ilvous manque un champ qui permettrait de qualifier l'origine (le pays) de la recette. Plusieurs solu-tions s'offrent à vous :1) Contacter l'auteur du module et lui demander de rajouter cette fonctionnalité ou d'intégrer unpatch que vous lui envoyez. Inconvénient : les autres utilisateurs du module n'ont, dans la grande

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 40

Page 41: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

majorité, pas besoin de cette fonctionnalité spécifique, pourquoi risquer l'introduction de bugs pourajouter un code spécifique ?2) Modifier le code du module sur votre installation d'Odoo. Mais il est inutile de souligner que celava poser des soucis de maintenance.3) Étendre le module existant en développant un nouveau module qui viendra le compléter.C'est, bien entendu, cette troisième voie que le sage choisira. Et nous verrons que créer un nouveaumodule n'est pas une affaire démesurée par rapport au besoin exprimé : ajouter un champ origineaux recette.Ce nouveau module va étendre le modèle de données et modifier une vue côté backend et une vuecôté frontend. Il fournira également des données de démonstration dans le cas où la base de donnéesest créée avec l'option démonstration. Nous allons donc créer le nouveau répertoire ~/mes-modules/cookbook_origin avec les sous-répertoires models, views et data. Dans le répertoire racinedu module, nous avons besoin du traditionnel fichier __openerp__.py :# -*- coding: utf-8 -*-{ 'name': "Cookbook Recipes Origin", 'summary': "Origin field for recipes.", 'description': """This module will add an origin (country) field to recipes.""", 'author': "S. Namèche", 'category': 'Knowledge Management', 'application': False, 'version': '0.1', 'license': 'AGPL-3', 'depends': ['cookbook'], 'data': [ 'views/recipe_views.xml', 'views/recipe_templates.xml', ], 'demo': [ 'data/cookbook_recipe_demo.xml', ],}

Tout cela reste classique. Vous remarquerez que ce module dépend du module cookbook.Nous avons besoin également du fichier __init__.py sur le quel je ne ferais aucun commentaire :# -*- coding: utf-8 -*-import models

Dans le sous-répertoire models, il nous faut un autre fichier __init__.py :# -*- coding: utf-8 -*-import recipe

Et le fichier models.py :# -*- coding: utf-8 -*-

from openerp import fields, models

class RecipeOrigin(models.Model): _name = 'cookbook.recipe' _inherit = 'cookbook.recipe'

country_id = fields.Many2one('res.country', string='Origin')

Ici, cela devient intéressant. Nous y définissons une classe Python RecipeOrigin (dont, je le rap-pelle, le nom importe peu pour Odoo) qui hérite toujours de models.Model. Mais, cette fois-ci, nousutilisons l'attribut _inherit afin d'indiquer que ce modèle Odoo vient étendre le modèle existantcookbook.recipe (c'est pour cela qu'il est important que le module Cookbook Origin dépende dumodule Cookbook : il doit être importé en second par Odoo). C'est le modèle existant que nous

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 41

Page 42: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

étendons car la valeur de l'attribut _inherit est identique à celle de l'attribut _name (qui, d'ailleurs,dans ce cas est facultatif). Nous utilisons là l'une des trois formes d'héritage disponibles dans Odoo.Ensuite, nous déclarons le plus naturellement du monde un champ country_id qui viendra s'ajouterà la liste des champs existants dans le modèle. Ce nouveau champ est de type Many2one() et fait ré-férence au modèle res.country qui est pré-chargé par défaut dans l'installation de base d'Odoo etqui contient déjà la liste de 253 pays. C'est aussi simple que cela.Nous définissons ensuite dans le fichier data/cookbook_recipe_demo.xml les données de démonstra-tion :<openerp> <data> <record id="cookbook.tarte_legume" model="cookbook.recipe"> <field name="country_id" ref="base.fr"/> </record> <record id="cookbook.pain_perdu" model="cookbook.recipe"> <field name="country_id" ref="base.es"/> </record> </data></openerp>

Comme l'identifiant externe de ces enregistrements (cookbook.tarte_legume etcookbook.pain_perdu) sont déjà connus d'Odoo (via le module Cookbook), les données de ce fichierviendront modifier les enregistrements existants. Les deux champs name font référence aux identi-fiants externes base.fr et base.es qui identifient les enregistrements du modèle res.country asso-ciés respectivement à la France et à l'Espagne (je ne sais pas, moi, si le pain perdu a été pour la pre-mière fois dégusté par les Ibères, mais nous le supposerons).Pour présenter les données dans la partie backend d'Odoo, nous étendons les vues recherche (pour yajouter la possibilité de regrouper les recettes par pays) et formulaire associées au modèlecookbook.recipe en créant deux nouvelles vues dans le fichier views/recipe_views.xml :<openerp> <data> <record model="ir.ui.view" id="recipe_origin_view_search"> <field name="name">cookbook.recipe.origin.search</field> <field name="model">cookbook.recipe</field> <field name="inherit_id" ref="cookbook.recipe_view_search"/> <field name="arch" type="xml"> <xpath expr="//group[@string='Group By']" position="inside"> <filter string="Origin" context="{'group_by': 'country_id'}"/> </xpath> </field> </record>

<record model="ir.ui.view" id="recipe_origin_view_form"> <field name="name">cookbook.recipe.origin.form</field> <field name="model">cookbook.recipe</field> <field name="inherit_id" ref="cookbook.recipe_view_form"/> <field name="arch" type="xml"> <xpath expr="//notebook" position="inside"> <page string="Other informations"> <group> <field name="country_id"/> </group> </page> </xpath> </field> </record> </data></openerp>

Les identifiants externes de ces deux vues sont cookbook_origin.recipe_origin_view_search etcookbook.recipe_origin_view_form) et leur nom sont cookbook.recipe.origin.search etcookbook.recipe.origin.form. Il s'agit donc bien de deux nouvelles vues. Elles viennent étendredeux autres vues existantes grâce à deux mécanismes : l'usage du champ inherit_id dans leur dé-

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 42

Page 43: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

claration et l'utilisation de l'élément <xpath> dans leur définition. Le champ inherit_id précisel'identifiant externe de la vue qui va être étendue (cookbook.recipe_view_search etcookbook.recipe_view_form).L'élément <xpath> dans la définition de ces nouvelles vues est employé pour modifier le corps de lavue originelle. Nous pourrions utiliser autant d'éléments <xpath> dans le corps de nos nouvellesvues (contenu de l'élément <field name="arch">) qui nécessaire. Cet élément possède deux attributspour indiquer comment modifier la vue originelle : expr et position. Le premier est une expressionXPath permettant d'identifier l'élément cible et le second indique comment introduire le code XMLcontenu dans le corps de l'élément <xpath>. L'attribut position peut avoir les valeurs suivantes :- inside : le contenu de l'élément <xpath> est ajouté à celui de l'élément cible ;- replace : le contenu de l'élément <xpath> remplace l'élément cible ;- after : le contenu de l'élément <xpath> est ajouté après de l'élément cible, dans le nœud XML quicontient ce dernier ;- before : le contenu de l'élément <xpath> est ajouté avant de l'élément cible, dans le nœud XML quicontient ce dernier ;- attributes : le contenu de l'élément <xpath> doit être un ou plusieurs éléments <attribute> avecun attribut name, si le corps de l'élément <attribute> est vide alors cet attribut est supprimé de l'élé-ment cible sinon l'attribut est ajouté (ou modifie l'attribut existant).Dans notre cas, nous ajoutons à la liste des champs contenus dans l'élément <group string="GroupBy"> (ciblé par l'expression XPath //group[@string='Group By']) de la vue recherche originelle,l'élément <filter string="Origin" context="{'group_by': 'country_id'}"/>. Et, à la vue formu-laire existante, nous ajoutons un nouvel onglet (<page></page>) au classeur identifié par l'expressionXPath //notebook. Cet onglet contient simplement un groupe avec le seul champ country_id.Il nous reste à étendre la vue du frontend dans le fichier views/recipe_templates.xml :<openerp> <data> <template id="recipe_origin" inherit_id="cookbook.recipe"> <xpath expr="//table[@id='properties']" position="inside"> <tr> <th>Origin</th> <td> <t t-esc="recipe.country_id.name"/> <!-- FIXME --> <span t-field="recipe.country_id.image" t-field-options='{"widget": "image"}'/> </td> </tr> </xpath> </template> </data></openerp>

Nous utilisons le même mécanisme pour modifier cette vue. Cette fois-ci notre chemin XPath//table[@id="properties"] utilise l'attribut id que j'avais positionné sur l'élément <table> du mo-dèle QWeb de la vue cookbook.recipe sans donner d'explication. Il est d'ailleurs recommandé deparsemer d'attributs id les éléments de vos vues afin que d'autres modules puissent s'en servircomme points d'ancrage clairement identifiables afin d'étendre vos vues.Deux remarques ici : vous remarquerez comment l'ORM d'Odoo est smart et nous permet de faireréférence directement au champ name de l'enregistrement res.country associé au champ country_idde notre modèle sons nous avoir à nous préoccuper d'aller le chercher dans la base de données. Vousaurez également noté l'utilisation de la nouvelle balise QWeb t-field-options qui est souvent utili-sée pour spécifier un widget pour formater le champ Odoo de manière spécifique. Ici, nous profi-

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 43

Page 44: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

tons que les drapeaux des pays sont stockés dans les enregistrements du modèle res.country pourafficher celui qui correspond au pays d'origine de la recette.Il nous faut alors simplement installer le module Coobook Origin pour avoir accès au nouveauchamp origin dans les vues recherche et formulaire des recettes ainsi que sur le site frontend denotre application.Pour parfaire ce nouveau module, il suffit d'exporter un fichier PO modèle afin de traduire les deuxou trois nouveaux termes que nous avons introduits dans ce module et placer le ficher fr.po ainsigénéré dans le sous-répertoire i18n comme nous l'avons fait pour le module Cookbook. Je vouslaisse cela comme exercice ainsi que l'ajout d'une colonne pays d'origine dans la vue liste originelledes recettes.Avec une centaine de lignes de codes, nous avons ajouté un champ à notre modèle et modifié troisvues pour l'afficher, sans modifier le code du module originel. Le développeur de ce dernier peutainsi y apporter moult évolutions et nous pouvons faire de même avec notre module.

7. Fonctionnalités étendues

Cet article est déjà bien long, mais il reste tant à dire sur le framework Odoo. Comme je ne peuxpasser sous silence certaines des fonctionnalités importantes que nous n'avons pas encore abordées,je me propose de le faire dans cette section, mais sur un rythme un peu plus soutenu.

7.1 Rapports

L'impressions des rapports est un élément essentiel dans un framework destiné à être la base d'unPGI. Avec Odoo, la gestion des rapports a souvent été compliquée. Cela s'est un peu amélioré avecles versions récentes qui ont rendu obsolètes tous les moteurs de rapport à l'exception de QWeb.Pour imprimer un rapport, il nous faut :- un enregistrement du modèle ir.actions.report.xml (pour lequel il existe le raccourcis XML<report>) ;- un modèle QWeb ;- un format de papier (un enregistrement du modèle report.paperformat).Il existe déjà deux formats de papier dans la base d'Odoo, dont le A4.IL ne nous reste donc plus qu'à définir le rapport et son modèle QWeb, ce que nous faisons dans lefichier recipe_report.xml du sous-répertoire report :<openerp> <data> <report id="cookbook_recipe" model="cookbook.recipe" string="Recette" report_type="qweb-pdf" name="cookbook.report_recipe"/>

<record id="cookbook_recipe" model="ir.actions.report.xml"> <field name="paperformat_id" ref="report.paperformat_euro"/> </record>

<template id="report_recipe"> <t t-call="report.html_container"> <t t-foreach="docs" t-as="r"> <t t-call="report.external_layout"> <div class="page"> <h1>Recette: <span t-esc="r.name"/></h1>

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 44

Page 45: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

<h2>Ingredients</h2> <span t-raw="r.ingredients"/> <h2>Preparation</h2> <span t-raw="r.preparation"/> </div> </t> </t> </t> </template> </data></openerp>

Le premier enregistrement <report> est un raccourcis pour un enregistrement du modèleir.actions.report.xml pour lequel nous définissons : un identifiant externe (attribut id), un modèleassocié (attribut model), le nom du rapport qui apparaîtra dans le menu Imprimer (attribut string), letype du rapport (report_type="qweb-pdf" pour générer un fichier au format PDF destiné à l'impres-sion) et le nom du modèle QWeb associé (attribut name).Le second enregistrement <record> n'en ai en fait pas vraiment un. En effet, il fait référence au pre-mier enregistrement (l'identifiant externe et le modèle sont les mêmes) et vient le compléter car ilexiste une lacune en ce qui concerne l'élément <report> : il ne permet pas de spécifier le format depapier. C'est donc ce que nous faisons ici. L'identifiant externe report.paperforamt_euro fait réfé-rence à un format A4. Les formats de papier sont décrits par le modèle report.paperformat et vouspouvez les modifier (notamment les marges) dans l'interface d'Odoo via le menu Configuration >Technique > Rapports > Format de papier.Enfin, le dernier enregistrement <template> est plus complexe. Nous reconnaissons un modèleQWeb dont l'identifiant externe est identique à la valeur de l'attribut name de l'enregistrement<report>. Mais cela se complique un peu ensuite car un rapport est un document HTML généré parOdoo puis transformé en PDF par le programme wkhtmltopdf. Pour nous simplifier la vie et afind'éditer des rapports avec une mise en page uniforme, Odoo met à notre disposition deux sous-mo-dèles report.html_container et report.external_layout que nous appelons avec l'attribut t-callde QWeb. Le premier de ces sous-modèles (report.html_container) met en place le squelette dudocument HTML, y compris les références aux feuilles de style Bootstrap. Le second est appelédans une boucle t-foreach car l'impression peut être demandée pour plusieurs documents. Cetteboucle parcourt le tableau docs qui est passé en paramètre au modèle. Pour chaque document à im-primer, le second sous-modèle appelé (report.external_layout) crée une mise en page standard(en-tête et pied de page) ce sous-modèle appelle lui-même les modèlesreport.external_layout_header et report.external_layout_footer. Cette mise en page peut êtrepersonnalisée en modifiant ces vues et son contenu (coordonnées de la société, logo, etc.) via l'écrande personnalisation Configuration > Sociétés > Sociétés > YourCompany > onglet Configuration des rapports.Le contenu minimal d'une modèle QWeb pour rapport est donc le suivant : <template id="<identifiant externe>"> <t t-call="report.html_container"> <t t-foreach="docs" t-as="r"> <t t-call="report.external_layout"> <div class="page"> .../... </div> </t> </t> </t> </template>

N'oubliez pas d'ajouter la ligne suivante au fichier __openerp__.py : 'data': [ .../... 'report/recipe_report.xml',

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 45

Page 46: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

Puis, après une mise à jour du module, un bouton Imprimer apparaît dans le vue formulaire ou la vueliste (lorsqu'au moins une recette est sélectionnée) des recettes. L'item Recette de ce menu corres-pond à notre rapport.

7.2 Wizards

Dans Odoo, les Wizards sont des fenêtres modales qui sont généralement utilisées pour déclencherune action sur le serveur Odoo à partir de paramètres fournis par l'utilisateur : édition de rapport,mise-à-jour de plusieurs enregistrements, recherche élaborée, etc. Un exemple typique est la fenêtrequi s'affiche lorsque vous avez demandé l'export de la traduction dans le menu Configuration > Tra-ductions > Importer / Exporter > Export de la traduction.FIXME : section à terminerLa déclaration du wizard dans le fichier models/recipe.py :class RecipeWizard(models.TransientModel): _name = 'cookbook.recipe.wizard'

ingredients = fields.Char() max_time = fields.Integer() max_difficulty = fields.Selection(DIFFICULTIES)

@api.multi def find(self): domain = []

if self.ingredients: ingredients = self.ingredients.split() for ingredient in ingredients: domain.append(('ingredients', 'ilike', ingredient))

if self.max_time: domain.append(('total_time', '<=', self.max_time))

if self.max_difficulty: domain.append(('difficulty', '<=', self.max_difficulty))

return { "type": "ir.actions.act_window", "res_model": "cookbook.recipe", "views": [[False, "tree"], [False, "form"]], "name": "Search result", "domain": domain, }

Les écrans du wizard à ajouter au fichier views/recipe_views.xml : <record model="ir.ui.view" id="wizard_find_recipe_view"> <field name="name">cookbook.find.wizard</field> <field name="model">cookbook.recipe.wizard</field> <field name="arch" type="xml"> <form string="Find recipe"> <group> <field name="ingredients"/> <field name="max_time"/> <field name="max_difficulty"/> </group> <footer> <button name="find" type="object" string="Find" class="oe_highlight"/> or <button special="cancel" string="Cancel" class="oe_link"/> </footer> </form> </field> </record>

<act_window id="launch_find_wizard" name="Find Recipes" res_model="cookbook.recipe.wizard" view_mode="form"

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 46

Page 47: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

target="new"/>

<menuitem id="cookbook_find_menu" name="Find Recipes" parent="cookbook_main_menu" action="launch_find_wizard"/>

7.3 Tests

FIXME : section à terminerDéfinir un test dans le fichier tests/test_recipe.py :# -*- coding: utf-8 -*-

from openerp.tests import common

class TestRecipe(common.TransactionCase): def test_total_time(self): values = {'name': 'Test', 'cooking_time': 10, 'baking_time': 32} record = self.env['cookbook.recipe'].create(values) self.assertEqual(record.total_time, 42)

Le fichier test/__init__.py pour importer le test :# -*- coding: utf-8 -*-import test_recipe

Import du répertoire tests dans le fichier __init__.py :# -*- coding: utf-8 -*-import modelsimport controllersimport tests

Pour exécuter les tests (le mot de passe de l'utilisateur admin doit être admin) ;$ ~/odoo/odoo.py -c ~/odoo.conf -u cookbook -d cuisine_demo --test-enable --stop-after-init

7.4 API

FIXME : section à terminer

7.4.1 XML-RPC

FIXME : section à terminerLe fichier de test test.py :#!/usr/bin/python

from xmlrpclib import ServerProxy

url = 'http://localhost:8069'db = 'cuisine_demo'user = 'admin'passw = 'admin'model = 'cookbook.recipe'

odoo = ServerProxy('{}/xmlrpc/2/common'.format(url))uid = odoo.authenticate(db, user, passw, {})

models = ServerProxy('{}/xmlrpc/2/object'.format(url))recipes = models.execute_kw(db, uid, passw, model, 'search_read', [])

for recipe in recipes: print recipe['name']

Son exécution :$ chmod 755 test.py$ ./test.py

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 47

Page 48: Le framework Odoo 2 - sebastien.nameche.frsebastien.nameche.fr/supports/Le_framework_Odoo_v0.1_20151125.pdf · Le framework Odoo v0.1 du 25 novembre 2015 Index des illustrations Illustration

Le framework Odoo v0.1 du 25 novembre 2015

Tarte aux légumesPain perdu

7.4.2 erppeek

FIXME : finir la rédactionInstallation :$ sudo pip install erppek

Utilisation :$ erppeek --server=http://localhost:8069/ -d Peggy -u admin.../...Password for 'admin': Logged in as 'admin'peggy >>> partners = model('res.partner')peggy >>> partners_list = partners.search([('user_id', '=', 1)])peggy >>> partner_recs = partners.browse(partners_list)peggy >>> partner_recs.write({'user_id': None})

8. Sujets à aborder

FIXME :- workflows (ad hoc et formels) ;- module mail (commentaires par objects) ;- événement onchange ;- droits par enregistrement (rules) et champ.

Corrections, suggestions, questions, félicitations (pourquoi pas ?) : <[email protected]> 48