TD2 Etudiants - Approfondissement de la programmation d’un module sous ODOO v14
Ce TD fait suite au TD1 (exercice 10). Vous devez donc repartir de votre code.
Machine virtuelle: VM
OS : Linux Ubuntu 64 bits
Mode Attachement multiple
RAM : 8 Go minimum.
3 ou 4 processeurs (Onglet « Système » puis « Processeur »).
Mémoire vidéo : 128 Mo. Activer l’accélération 3D.
Activer le presse-papier partagé.
Mot de passe root Linux: matskipwd Informations de connexion à Odoo : admin : admin
Objectifs
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 :
Ajout de code python : champs calculés, contraintes, héritage, etc.
Création de rapports QWeb.
Vue search.
Amélioration de l’interface & finalisation de l’application
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.).
1. AJOUT DE CODE PYTHON
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èmes.

Vous testerez votre code au fur et à mesure.
Question 1
Champ selection
Ajouter un champ (Coefficient) de type fields.Selection dans la classe StudentsMark. Pour vous aider :
https://www.odoo.com/documentation/14.0/reference/orm.html#fields
http://odoo-new-api-guide-line.readthedocs.io/en/latest/fields.html
Attention à remplacer from openerp (ancienne API) par from odoo dans le code fourni en exemple.
Valeurs possibles : '1', '2', '3', '5' (à stocker dans un tableau).
Paramètres optionnels à ajouter :
Valeur par défaut (paramètre default) : 1
Le champ sera indexé (index=True) Remarque : index=True demande à Odoo de créer un index sur le champ.
Bulle d’aide : "Select a coefficient in the list".
Le champ est obligatoire.
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.



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.
Quesiton 2
Champs calculés
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:
Computed fields:
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.



Q2. Réaliser 3 copies d’écran.
Question 3
Nous pouvons remarquer que le champ "Weighted Mark" n'est pas calculé directement quand on le modifie dans la vue form. On a dû 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.
Question 4
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:
Pour parcourir la collection de notes :
record.mark_ids(mark_idsest ici le champ de typeone2manyqui permet de stocker la liste des notes).Pour tester si le tableau des notes est vide :
if record.markids:ouif len(record.markids)>0:Exemple de code pour avoir accès au coefficient :
record.mark_ids.coefficientAttention à ne pas diviser par zéro.
Ce champ sera mis à jour pour tout changement de note :
@api.onchange("mark_ids")


Q4. Réaliser deux copies d’écran.
Question 5
Redéfinition de méthode
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 :
https://www.cybrosys.com/blog/how-to-use-of-name-get-function-in-odoo
https://stackoverflow.com/questions/42441851/use-name-get-in-odoo-9 ATTENTION, il n’est plus utile d’utiliser @api.multi dans la version 14.
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.
Question 6
Ajout de contraints
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).
Question 7
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 :
Utiliser
self.env['modelname'].search([('champ', '=', valeur)]). Ici,modelnameserastudents.trainingPlus de détails sur
self.env[…].search(…): https://www.odoo.com/documentation/14.0/reference/orm.html
Vous gérerez les exceptions. Remarque : la gestion des exceptions nécessite d’importer la classe ValidationError du module exceptions : from odoo.exceptions import ValidationError

Question 8
Utlisation d'une classe d'un autre module
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.
Question 9
Heritage
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): 
Q9. Réaliser une capture d'écran
Question 10
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.
Extension
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:
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.xmlOu plus simple, en mode
debug=1cliquer sur Edit view form / Editer vue formulaire et regarder le champID 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 :
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 :
En renseignant directement le tag avec une identification et en ajoutant l'attribute
position. Dans notre exemple nous avons directement mis le tag<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">La premiere solution est la plus simple et plus lisible mais elle ne fonctionne que s'il n'y a pas plusieurs champs identiques. Pour cibler précisemment un tag on utilse
<xpath expr="chemindutag" position="position">(Cf. TDB – Personnalisation et paramétrage du cas Matski)

Q10. Réaliser une capture d'écran
2. RAPPORT (QWEB)
Exercice 21
Question 11
Pour réaliser un rapport, nous avons besoin de :
Un enregistrement du modèle ir.actions.report, pour lequel il existe le raccourci XML
<report>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 :
Nom
Prénom
Nom de la formation
Un tableau affichant (pour chaque étudiant) les lignes de note (matière, note, coefficient, note coefficientée)
Moyenne générale pondérée.
Contenu A COMPLETER du fichier students_report.xml :
La balise <record id="action_students_transcript_report" model="ir.actions.report"> contient:
Le nom du rapport (
name) qui apparaitra dans le bouton Imprimer.Le
modelutilisé dans le rapport.Le type de rapport (
report_type). Le type peut être PDF (qweb-pdf) ou HTML (qweb-html).Le nom du modèle QWeb associé (
report_name). Attention à bien préfixer ce nom par celui du module, ici students.Le format de papier à utiliser (
paperformat_id). Format A4 :base.paperformat_euro
Pour afficher le bouton d'impression il faut indiquer:
Le modele sur lequel s'affichera le bouton d'impression (
binding_model_id)Le type d'impression (
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 :
Impression à partir de la liste

Impression à partir du formulaire

Rapport

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).
Question 12
3. VUE SEARCH
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 :
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.
Question 13
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 :
Qui n’ont pas de note. Pour savoir si un champ n’a pas de valeur, il suffit de le comparer à
False
Qui obtiennent leur diplôme (moyenne générale >= 10). Utiliser
>=(et non >=).
Qui ont des notes, mais qui n’obtiennent pas leur diplôme (moyenne générale < 10). Utiliser
<
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é.
Question 14
4. Groupes utilisateurs et droits d'accès
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:
Modifier le fichier __manifest__.py:
- "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:
Copier-coller le contenu suivant :
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 :
id : un identifiant unique pour la règle d'accès.
name : nom de la règle.
model_id:id : la table à laquelle s'applique cette règle. Le nom de la table doit toujours être précédé du préfixe
model_. Exemple :model_students_student: la table se nommestudents_studentauquel on rajoute le préfixemodel_group_id:id : le groupe d'utilisateurs auquel s'applique cette règle. Ici, il s’agit du groupe que nous avons créé dans le fichier
students_security.xml.perm_read : permission de lire les données (1 ou 0).
perm_write : permission de modifier les données (1 ou 0).
perm_create : permission de créer des données (1 ou 0).
perm_unlink : permission de supprimer les données (1 ou 0).
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). 
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 :
Créer un groupe
teachersLes utilisateurs de ce groupe auront accès à l’ensemble des modèles en lecture/écriture (tous les droits).
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).
Question 15
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:
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 :
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.
Question 16
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.
5. Pour les plus rapides : Amélioration de l'application
Question 17
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).
Question 18
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).
Question 19
Compléter les données demo. Q19. Copier-coller le code des fichiers XML
Question 20
Modifier les traductions. Q20. Réaliser des copies d’écran du travail réalisé (IHM).