Ce TD fait suite au TD1 (exercice 10). Vous devez donc repartir de votre code.
U:/VM/INFO/Linux-Ubuntu/Odoo14Dev
Mot de passe root Linux: matskipwd
Mot de passe pour administrer les bases de données Odoo (sauvegarde, etc.): matskipwd
Dans la VM, chemins pour accéder à Odoo :
Ce TD a pour objectif d’approfondir le framework de développement interne à Odoo.
Plus précisément, la création du module va suivre les étapes suivantes :
Ce TD requiert quelques compétences de développement en Python.
Vous ne finaliserez l’application qu’en fin de TD (traductions, données data/demo, etc.).
Nous allons ajouter du code Python permettant d’améliorer les fonctionnalités du module développé dans la version 1.0.
Exercice 20
Modifier la version dans le fichier __manifest__.py
: 2.0
.
Pour ne pas avoir de problème avec le fichier de démo car nous allons ajouter des champs obligatoires dans la classe Mark, mettre en commentaire (#) la ligne suivante dans le fichier __manifest__.py
. Les autres données ne devraient pas poser problème normalement.
Vous testerez votre code au fur et à mesure.
Ajouter un champ (Coefficient) de type fields.Selection
dans la classe StudentsMark
. Pour vous aider :
Valeurs possibles : '1', '2', '3', '5' (à stocker dans un tableau).
Paramètres optionnels à ajouter :
Créer une nouvelle base de données à votre nom, en installant les données de démonstration, pour tester votre code (Master password : matskipwd). Installer ensuite le module.
RAPPEL : pensez à arrêter puis relancer l’exécution du projet dans PyCharm. Mettre ensuite à jour le module après toute modification.
Si vous avez ce type d’erreur (« Connexion perdue »), c’est que votre code Python est incorrect.
Vous pourrez alors visualiser l’erreur dans la console.
Q1. Réaliser deux copies d’écran (les mêmes que ci-dessus) et les conserver dans un fichier Word.
Bien numéroter vos questions dans le fichier Word.
Ajouter un champ dans la classe StudentsMark
calculant et affichant la note coefficientée (note * coefficient).
Le champ sera de type Computed Fields.
Plus de détails:
Pour définir le nom d’un champ Computed field, ajouter le paramètre string=""
ATTENTION, pour que le calcul fonctionne, tous les champs doivent être numériques !!!! Vous devrez donc caster le coefficient en float.
Apres enregistrement:
Liste:
Q2. Réaliser 3 copies d’écran.
Nous pouvons remarquer que le champs "Weighted Mark" n'est pas calculé directement quand on le modifie dans la vue form. On a du sauvegarder l'enregistrement avant de voir apparaitre la note coefficientée.
Pour pallier à cela nous allons ajouter du code qui va indiquer à Odoo de calculer le champ directement à la modification de la note ou du coefficient.
Vous utiliserez le décorateur @api.onchange("monchamp1", "monchamp2", ...)
. Ce décorateur déclenchera l'appel de la fonction décorée si l'un des champs spécifiés dans le décorateur est modifié dans le formulaire. Le ou les champs spécifiés doivent appartenir au même modèle.
Exemple de code : https://www.odoo.com/documentation/14.0/reference/orm.html#common-orm-methods
.
L’utilisation de @api.xxx
nécessite d’importer le module api
:
from odoo import models, fields, api
Q3. Réaliser une copie d’écran.
Ajouter un champ dans la classe StudentsStudent
calculant et affichant la moyenne générale pondérée de l’étudiant (champ non modifiable) sur 20.
Indications:
record.mark_ids
(mark_ids
est ici le champ de type one2many
qui permet de stocker la liste des notes).if record.markids:
ou if len(record.markids)>0:
record.mark_ids.coefficient
@api.onchange("mark_ids")
Q4. Réaliser deux copies d’écran.
Nous allons afficher le nom et le prénom de l’étudiant dans la liste déroulante de saisie d’une note. Or, il n’est pas possible de concaténer des champs dans l’attribut _rec_name
.
Mettre le champ _rec_name
(# _rec_name="number"
) de Students.Student
en commentaire.
Par défaut on remarque que c’est maintenant l’ID qui s’affiche (students.student,id
).
Il est donc nécessaire de redéfinir la méthode (override
) name_get()
de la classe StudentsStudent
.
Exemples de code :
ATTENTION, la méthode append
prend id
comme 1er argument et non number
, puisqu’il s’agit de remplacer l’id
qui s’affiche par la concaténation du nom et du prénom.
Q5. Réaliser une copie d’écran.
1ère contrainte:
La note devra être comprise entre 0 et 20 (utiliser @api.constrains
) :
Bien vérifier ensuite que votre code fonctionne si vous créez plusieurs notes à la fois, par exemple dans le formulaire des étudiants (table des notes).
Après avoir cliqué sur le bouton « Sauvegarder », si vous obtenez l’erreur suivante, c’est qu’il faut parcourir chaque ligne.
L’erreur « Expected singleton » est levée quand votre code ne fonctionne que pour une seule ligne. Il faut donc parcourir la collection retournée par self
.
Q6. Réaliser une copie d’écran (celle-ci-dessus).
2eme contrainte:
Le code du diplôme doit être unique (pas de doublon possible). Utiliser @api.constrains
et ajouter le code Python permettant de rechercher si le code existe déjà parmi les valeurs :
self.env['modelname'].search([('champ', '=', valeur)])
. Ici, modelname
sera students.training
self.env[…].search(…)
: https://www.odoo.com/documentation/14.0/reference/orm.htmlVous gérerez les exceptions.
Remarque : la gestion des exceptions nécessite d’importer la classe ValidationError
du module exceptions : from odoo.exceptions import ValidationError
Q7. Réaliser une copie d’écran.
Le module développé dépend du module base
, situé dans le dossier odoo/odoo/addons
.
Ouvrir le fichier models/res_country.py
du module base et regarder le code de la classe Country
, permettant de gérer les pays. On remarque que le nom de l’objet est res.country
.
Ajouter un champ nationalité (non obligatoire ; type relationnel) dans la classe StudentsStudent
qui permet d’afficher une liste déroulante contenant les pays. Ce champ ne sera visible que dans la vue form
.
Q8. Réaliser une copie d’écran.
Créer une classe StudentsStudentContinuous
héritant de la classe StudentsStudent
, qui permet de gérer les étudiants de formation continue.
Elle y ajoutera un nouveau champ (obligatoire ; type relationnel) permettant de sélectionner l’entreprise où l’étudiant est en alternance. Les entreprises sont gérées dans la classe Partner
du fichier models/res_partner.py
du module base
.
Remarque : un heritage vient créer un nouveau model, celui est-ci est nommé en utilisant l'attribut _name
. Il ne faut pas confondre avec une extension de model, l'extension vient ajouter des fonctionnalités à un model sans en créé un nouveau, nous verrons celà dans la section suivante.
Pour l'heritage des vues:
En mode Heritage il faut recréer entièrement les vues. Faire un copier-coller des vues form
et tree
de students.student
et effectuer les modications (id, model, ajout champ).
Modifier les menus de la façon suivante (ajouter un menu principal au-dessus des menus cliquables):
La gestion des notes n’est pour l’instant pas fonctionnelle car elles sont liées à l’objet students.student et non à l’objet que vous venez de créer.
Nous allons etendre le modèle res.partner
pour ajouter un champ de type relationnel nous permettant de voir les étudiants qui sont en alternance dans une entreprise.
Installer l'application Contacts (contacts
). C'est un module qui permet l'accès à tous les contacts (res.partner
) d'une seule vue.
Pour étendre le modèle res.partner
, créer un nouveau fichier res_partner.py
dans le module students/models
, ajouter from . import res_partner
dans students/models/__init__.py
.
Ajouter dans le nouveau fichier:
from odoo import fields, models class ResPartner(models.Model): _inherit = "res.partner"
Créer un nouveau champ de type One2Many qui permet la liason avec les étudiants continus créés précedemment.
Aller dans le module Contacts sur Odoo et ouvrer un contact. Nous allons étendre cette vue pour afficher le nouveau champ.
Il nous faut l'id
de la vue que nous allons étendre, pour le trouver vous avez deux possibilités :
Connaitre le module dans le quel la vue se trouve et chercher dans le code. Ici : odoo/odoo/addons/base/views/res_partner_views.xml
Ou plus simple, en mode debug=1
cliquer sur Edit view form / Editer vue formulaire et regarder le champ ID Externe
. (Attention de ne pas modifier directement la vue depuis cette fenetre, les modifications seront effacées lors de la mise à jour du module)
Créer un nouveau fichier res_partner_views.xml
dans students/views
et ajouter le code suivant :
<odoo> <record id="view_partner_form_inherit_students" model="ir.ui.view"> <field name="name">res.partner.form.inherit</field> <field name="model">res.partner</field> <field name="inherit_id" ref="base.view_partner_form"/> <field name="arch" type="xml"> <notebook position="inside"> <page string="Continuous students" name="continuous_students"> <field name="student_continuous_ids" /> </page> </notebook> </field> </record> </odoo>
N'oubliez pas de modifier le fichier __manifest__.py
Dans le nouveau fichier nous avons utiliser <notebook position="inside">
pour selectionner le tag notebook
et ajouter à l'interieur de ce tag la nouvelle page.
Il existe plusieurs manières pour placer un nouveau champ où on le souhaite dans l'extension d'une vue :
position
.<notebook>
car il n'existe qu'un seul notebook. Mais si on veut ajouter un champ à la suite d'un autre champ nous aurions utiliser : <field name="nomChamp" position="after">
<xpath expr="chemindutag" position="position">
(Cf. TDB – Personnalisation et paramétrage du cas Matski)Q10. Réaliser une capture d'écran
Exercice 21
Pour réaliser un rapport, nous avons besoin de :
Un enregistrement du modèle ir.actions.report, pour lequel il existe le raccourci XML
Un format de papier : un enregistrement du modèle base.paperformat. Nous utiliserons le format « European A4 », un des formats de base fournis par Odoo dans le module base.
Un modèle QWeb définissant la structure du rapport. Contenu minimum du modèle QWeb :
<template id="mon ID externe"> <t t-call="web.html_container"> <t t-foreach="docs" t-as="o"> <t t-call="web.external_layout"> <div class="page"> … </div> </t> </t> </t> </template>
Pour plus de détail sur la syntaxe QWeb (commandes t-if
, t-else
, t-foreach
, t-call
, t-field
, etc.), vous pouvez vous référer à https://www.odoo.com/documentation/14.0/reference/qweb.html
Plus de détails sur les rapports : https://www.odoo.com/documentation/14.0/reference/reports.html
Dans le module CONFIGURATION, menu Technique > Analyse > Format de papier, nous pouvons visualiser le format « A4 » que nous allons utiliser.
Vous pouvez l’ouvrir pour visualiser ses détails.
Créer un dossier reports
dans le dossier du module.
Dans ce dossier, créer le fichier students_report.xml
Modifier le fichier __manifest__.py
:
Version: 2.1
"data": [ ..., ..., "reports/students_report.xml", ]
Créer un rapport permettant d’afficher pour chaque étudiant ses notes, ainsi que sa moyenne. Le rapport pourra afficher les données de plusieurs étudiants sélectionnés ou d’un seul.
Données à afficher pour chaque étudiant :
Contenu A COMPLETER du fichier students_report.xml :
<odoo> <record id="action_students_transcript_report" model="ir.actions.report"> <field name="name">Students Transcript</field> <field name="model">students.student</field> <field name="report_type">qweb-pdf</field> <field name="report_name">students.report_transcript</field> <field name="paperformat_id" ref="base.paperformat_euro"/> <field name="binding_model_id" ref="model_students_student" /> <field name="binding_type">report</field> </record> <template id="report_transcript"> <t t-call="web.html_container"> <t t-call="web.external_layout"> <t t-foreach="docs" t-as="o"> <div class="page"> <h1>Transcript</h1> <h3>Student :</h3> <span t-raw="o.firstname"/> <span t-raw="o.lastname"/> <!-- A COMPLETER --> </div> </t> </t> </t> </template> </odoo>
La balise <record id="action_students_transcript_report" model="ir.actions.report">
contient:
name
) qui apparaitra dans le bouton Imprimer.model
utilisé dans le rapport.report_type
). Le type peut être PDF (qweb-pdf
) ou HTML (qweb-html
).report_name
). Attention à bien préfixer ce nom par celui du module, ici students.paperformat_id
). Format A4 : base.paperformat_euro
Pour afficher le bouton d'impression il faut indiquer:
binding_model_id
)binding_type
)Le tag <template>
permet de définir le modèle QWeb. Son ID externe sera celui défini précédemment sans le préfixe du nom du module. Afin de simplifier la construction des rapports, Odoo met à disposition deux sous-modèles (balises) spécifiques web.html_container
et web.external_layout
qu’il faut appeler avec l'attribut t-call
de QWeb. Le premier permet de mettre en place le squelette du document HTML, dont 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 enregistrements (étudiants dans notre cas). O
fait référence à docs
et représente chaque enregistrement du modèle students
.student que l’on souhaite imprimer. On a donc accès à chaque attribut du modèle en utilisant o.nomattribut
.
Résultat du code ci-dessus :
Pour vous aider à compléter le code, ouvrir le fichier purchase_quotation_templates.xml
ou purchase_order_templates.xml
du dossier purchase/report
pour voir comment formater un tableau.
Résultats :
Vous pourrez améliorer ces visuels.
Comme vu dans le TDB, modifier également les informations de la société (nom, adresse, logo) dans le module CONFIGURATION, menu Utilisateurs et Sociétés > Sociétés. Ajouter, par exemple le logo de l’IUT.
Résultat:
Q11. Réaliser des copies d’écran des rapports (rapport sans note, rapport avec notes).
Exercice 22
Modifier la version dans le fichier __manifest__.py
: 2.2.
Dans le fichier students_views.xml
, ajouter une vue search permettant de filtrer les étudiants par nationalité. Il faudra que vous saisissiez la nationalité des étudiants au préalable.
Visuel :
Code à completer :
<record id="students_student_view_search" model="ir.ui.view"> <field name="name">students.search</field> <field name="model">students.student</field> <field name="arch" type="xml"> <search string="Search Students"> <filter name="filter_see_french" string="French students" domain="[('country_id.code', '=','FR')]"/> <!-- a completer --> </search> </field> </record>
Ici, nous utilisons la relation entre students.student
(champ country_id
) et res.country
. res.country
contient un champ code
qui nous permet d’avoir accès au code du pays.
Plus de détails : https://www.odoo.com/documentation/14.0/reference/views.html#search
Afin d’améliorer les performances, vous indexerez ce champ dans students.py
.
Q12. Réaliser une copie d’écran de ces 2 filtres (Cf. exemple ci-dessus). Copier-coller la ligne XML ajoutée dans le fichier Word.
De la même façon, modifier la vue search
afin de filtrer les étudiants sur leur moyenne générale.
On remarque que l’on ne peut ajouter de filtre personnalisé sur le champ « Grade point average » car il n’est pas disponible dans la liste :
En effet, par défaut, Odoo ne stocke pas les champs calculés (computed field
) dans la base de données.
Ajouter store=True
dans la définition du champ dans le fichier students.py
. L’indexer également.
Maintenant que le champ est stocké dans la base, grade_point_average
ne se mettra plus à jour si vous ajoutez une note. Vous devez ajouter @api.depends("mark_ids")
devant le code de calcul de la moyenne générale.
Ajouter les filtres sur les étudiants :
False
>=
(et non >=).<
Remarque: Vous pouvez ajouter le tag <separator />
pour inserer une ligne de separation dans les filtres.
Q13. Réaliser 3 copies d’écran de ces filtres. Copier-coller la code XML ajouté.
Exercice 23
Gestion des groupes utilisateurs :
Ajouter un dossier security
à la racine du module. Y créer le fichier students_security.xml
.
Ce fichier va contenir les groupes d’utilisateurs (students
et teachers
). Nous allons les relier à une catégorie (= Application) afin de plus facilement les retrouver sur l’écran de configuration des droits.
Copier-coller le code suivant dans le fichier xml:
<?xml version="1.0"?> <odoo> <data> <record model="ir.module.category" id="category_student_management"> <field name="name">Student management</field> <field name="sequence">200</field> </record> <record model="res.groups" id="group_students"> <field name="name">Students</field> <field name="category_id" ref="category_student_management"/> </record> </data> </odoo>
Modifier le fichier __manifest__.py
:
Version: 2.3
"data": [ "security/students_security.xml", ..., ..., ]
Mettre à jour le module, activer le mode développeur puis ouvrir le groupe Student Management / Students dans le module CONFIGURATION.
On remarque qu’il n’y a pas encore de droit d’accès.
Ouvrir un utilisateur. On remarque le groupe Students appartenant à l’application Student management.
Droits d’accès :
Il est maintenant nécessaire de donner des droits d’accès au groupe.
Le fichier des droits d’accès doit se nommer ir.model.access.csv
et se trouver dans le dossier security
.
Modifier le fichier __manifest__.py
:
"data": [ "security/students_security.xml", "security/ir.model.access.csv", ..., ..., ]
Copier-coller le contenu suivant :
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" "access_students_mark_students","Access to StudentsMark by students","model_students_mark","group_students",1,0,0,0 "access_students_student_students","Access to StudentsStudent by students","model_students_student","group_students",1,1,0,0 "access_students_continuousstudent_students","Access to StudentsContinuousStudent by students","model_students_student_continuous","group_students",1,1,0,0
ATTENTION, à bien indiquer le bon nom de modèle pour la gestion des étudiants en formation continue : model_<nom du modèle dans lequel on remplace le . par _>
.
Explications :
model_
. Exemple : model_students_student
: la table se nomme students_student
auquel on rajoute le préfixe model_
students_security.xml
.Mettre à jour le module.
Ouvrir le groupe Students dans le module CONFIGURATION :
Les étudiants auront accès en lecture seule aux notes, et en lecture/écriture (modification) aux informations des étudiants et étudiants de FC. Ils n’auront pas accès aux informations des diplômes.
Créer l’utilisateur suivant (qui fait partie des étudiants créés dans les données de démonstration).
Remarque : étudiant des données de démonstration :
Modifier son mot de passe pour « pmart » (même login et mot de passe).
Se connecter avec cet utilisateur. Contrairement à l’administrateur, cet utilisateur a bien accès au module. Vérifier ses droits.
Notes (accès en lecture seule) :
Étudiants :
L’utilisateur n’a pas accès aux diplômes (pas de menu).
Remarque : on pourrait limiter l’affichage des données à celles de l’utilisateur connecté. Dans ce cas, il serait nécessaire d’ajouter une règle (Cf. TDA Administration). Pour ce faire, nous devrions également rajouter le login dans le modèle students.student
.
Faire de même pour la gestion des professeurs :
teachers
Tester les droits en créant un utilisateur appartenant au groupe teachers.
Q14. Réaliser 2 copies d’écran (les mêmes que ci-dessus).
Modifier le mot de passe de cet utilisateur : meme que le nom d'utilisateur.
Se connecter et vérifier ses droits.
Remarque : si vous donnez le droit « Teachers » à l’Administrateur, vous n’aurez plus besoin d’activer les droits super-utilisateur (rafraichir l’écran).
Règles :
Pour le moment, les étudiants ont accès aux informations de tous les étudiants et à toutes les notes. Il est donc nécessaire de créer des règles qui vont permettre de restreindre ces droits : chaque étudiant ne pourra que visualiser ses notes et visualiser/modifier ses informations.
Ajouter un champ relationnel avec le modèle res.users
dans le modèle students.student
:
user_id = fields.Many2one( string="User", comodel_name="res.users", )
Afficher ce champ dans la vue Form des étudiants. Et affecter l'utilisateur à l'étudiant.
Dans le fichier students_security.xml
, ajouter la règle suivante :
<record model="ir.rule" id="students_mark_personal_rule"> <field name="name">Students view only their personal marks</field> <field ref="model_students_mark" name="model_id"/> <field name="domain_force">[('student_id.user_id', '=', user.id)]</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"/> <field name="groups" eval="[(4, ref('group_students'))]"/> </record>
Le champ groups
définit sur quel groupe la règle s’applique. Si aucun groupe n'est spécifié, la règle est globale et appliquée à tous les utilisateurs.
eval="[(4, ref('group_students'))]"
permet d’ajouter (4 signifie « ajouter ») une relation entre l’ID du groupe (retourné grâce à ref
) et la règle. https://www.odoo.com/fr_FR/forum/aide-1/question/what-is-the-meaning-of-the-4-in-the-expression-eval-4-ref-base-group-user-84907
ref="model_students_mark"
permet de définir le modèle sur lequel s’applique la règle. Il doit toujours être préfixé par model_
.
Le champ domain_force
définit les conditions à appliquer. Ici, un étudiant n’aura accès qu’à ses notes : l’utilisateur connecté doit être identique au champ student_id.user_id
de chaque objet note.
Les champs booléens (lecture, écriture, création, suppression) de ir.rule
signifient Appliquer cette règle pour ce type d'opération.
Mettre à jour le module. Vous pouvez visualiser la règle créée :
Tester la règle. L’étudiant connecté ne voit que ses notes :
Ajouter une règle permettant à l’étudiant connecté de ne visualiser et/ou modifier que ses propres données.
L’étudiant ne voit que ses informations et peut les modifier.
Il ne peut par contre pas modifier ses notes.
Règle créée :
Q15. Réaliser une copie d’écran de la règle (la même que ci-dessus). Copier-coller le code XML relatif à la règle créée.
Faire de même pour les étudiants de formation continue.
Q16. Réaliser une copie d’écran de la règle. Copier-coller le code XML relatif à la règle créée.
Exercice 24
Modifier la version dans le fichier __manifest__.py
: 2.4.
Améliorer l’interface à votre guise. Certaines vues tree en bas des formulaires sont notamment inutiles.
Q17. Réaliser des copies d’écran du travail réalisé (IHM).
Exercice 25
Modifier la version dans le fichier __manifest__.py
: 2.5.
Améliorer l’interface à votre guise. Certaines vues tree en bas des formulaires sont notamment inutiles.
Q18. Réaliser des copies d’écran du travail réalisé (IHM).
Compléter les données demo.
Q19. Copier-coller le code des fichiers XML
Modifier les traductions.
Q20. Réaliser des copies d’écran du travail réalisé (IHM).