Ce que tu vas apprendre
Kanban QWeb
Les templates <t t-name="card"> pour composer tes cartes.
Drag-and-drop
default_group_by — faire glisser une carte change le champ groupé.
Widgets visuels
priority, state_selection, progressbar,
many2one_avatar_user, many2many_tags.
Graph & Pivot
Deux vues analytiques quasi-gratuites pour dashboards.
Prérequis
- Module
odooskills_helpdeskv19.0.1.8.0 (T16). - Vues Form / List / Search déjà en place + menus fonctionnels.
- Notions de QWeb :
t-if,t-attf-class,t-out.
1. Deux champs à ajouter au modèle
Pour profiter pleinement du Kanban, on ajoute deux Selection conventionnels :
priority (1 à 3 étoiles) et kanban_state (bouton d'état vert/gris/rouge).
# models/helpdesk_ticket.py
priority = fields.Selection(
selection_add=[
('0', 'Normale'),
('1', 'Importante'),
('2', 'Haute'),
('3', 'Urgente'),
],
ondelete={'1': 'set default', '2': 'set default', '3': 'set default'},
string='Priorité',
default='0',
tracking=True,
)
kanban_state = fields.Selection(
selection=[
('normal', 'En cours'),
('done', 'Prêt'),
('blocked', 'Bloqué'),
],
string='État kanban',
default='normal',
tracking=True,
)
priority — ce champ est déjà défini par héritage
(dans notre cas via mail.thread). Utiliser selection= génère un
warning "overrides existing selection; use selection_add instead". On emploie donc
selection_add et on précise ondelete pour chaque valeur ajoutée
— règles appliquées si une valeur est supprimée (désinstallation du module).
2. La vue Kanban complète
Le Kanban est essentiellement un template QWeb appliqué à chaque enregistrement. Voici la vue complète, commentée :
<record id="view_helpdesk_ticket_kanban" model="ir.ui.view">
<field name="name">helpdesk.ticket.kanban</field>
<field name="model">helpdesk.ticket</field>
<field name="arch" type="xml">
<kanban default_group_by="state" group_edit="false">
<!-- Champs nécessaires au rendu mais non explicitement utilisés -->
<field name="is_overdue"/>
<field name="kanban_state"/>
<!-- Barre de progression en haut de chaque colonne -->
<progressbar field="kanban_state"
colors='{"done": "success", "normal": "muted", "blocked": "danger"}'/>
<templates>
<t t-name="card" class="oe_kanban_card">
<div class="oe_kanban_details">
<div class="d-flex justify-content-between">
<strong class="o_kanban_record_title">
<field name="reference"/>
</strong>
<field name="priority" widget="priority"/>
</div>
<div class="mt-1"><field name="name"/></div>
<div class="text-muted small mt-1">
<field name="partner_id"/>
<span t-if="record.category_id.raw_value"> · </span>
<field name="category_id"/>
</div>
<div class="mt-2">
<field name="tag_ids" widget="many2many_tags"
options="{'color_field': 'color'}"/>
</div>
<div class="d-flex justify-content-between align-items-center mt-2">
<span t-if="record.deadline.raw_value"
t-attf-class="badge #{record.is_overdue.raw_value ? 'text-bg-danger' : 'text-bg-light'}">
<i class="fa fa-calendar" title="Échéance"/>
<field name="deadline"/>
</span>
<div>
<field name="kanban_state" widget="state_selection"/>
<field name="user_id" widget="many2one_avatar_user"/>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
Sept éléments à comprendre :
default_group_by="state"— définit les colonnes. Chaque valeur destatedevient une colonne, déplacer une carte change automatiquement le champ.<progressbar field="kanban_state">— barre en haut de chaque colonne agrégeant les valeurs dekanban_state, avec couleurs Bootstrap mappées par le dictcolors.<t t-name="card">— nom obligatoire en Odoo 19 (le nomkanban-boxdes versions antérieures est supprimé).widget="priority"— transforme unSelectionà 4 valeurs (0..3) en étoiles cliquables.widget="state_selection"— transforme unSelectionà 3 valeurs (normal/done/blocked) en rond cliquable (gris/vert/rouge).t-if="record.category_id.raw_value"— affiche le séparateur "·" uniquement si la catégorie est renseignée.recordest le record du kanban,.raw_valuedonne la valeur brute (id pour un Many2one).t-attf-class— classe dynamique (attf= attribute formatted) évaluée via#{expression}. Permet de mettre la pastille deadline en rouge siis_overdue.
3. Mettre le Kanban par défaut dans l'action
On met kanban en première position dans view_mode pour que l'action
l'ouvre par défaut. On en profite pour ajouter graph et pivot :
<record id="action_helpdesk_ticket" model="ir.actions.act_window">
<field name="name">Tickets</field>
<field name="res_model">helpdesk.ticket</field>
<field name="view_mode">kanban,list,form,graph,pivot</field>
<field name="search_view_id" ref="view_helpdesk_ticket_search"/>
<field name="context">{'search_default_filter_new': 1}</field>
</record>
L'ordre dans view_mode définit à la fois la vue par défaut (la première)
et l'ordre des boutons de switch en haut à droite.
4. Les widgets kanban les plus utiles
| Widget | Type de champ | Usage |
|---|---|---|
priority | Selection 0→3 | Étoiles cliquables directement depuis la carte. |
state_selection | Selection 3 valeurs (normal/done/blocked) | Pastille ronde verte/grise/rouge. |
many2one_avatar_user | Many2one res.users | Avatar compact au lieu du nom. |
many2many_tags | Many2many | Puces colorées ; option color_field pour le coloriage. |
image | Image | Miniature redimensionnée. |
percentpie | Integer/Float (0-100) | Donut de progression. |
boolean_toggle | Boolean | Interrupteur on/off. |
handle | Integer | Handle de drag pour sequence (vue list). |
5. La vue <graph>
Trois lignes et tu as un graphique interactif : barres empilées par catégorie et par état.
<record id="view_helpdesk_ticket_graph" model="ir.ui.view">
<field name="name">helpdesk.ticket.graph</field>
<field name="model">helpdesk.ticket</field>
<field name="arch" type="xml">
<graph string="Analyse tickets" type="bar" stacked="1">
<field name="category_id"/>
<field name="state"/>
</graph>
</field>
</record>
Options notables :
type="bar"/"line"/"pie"— type par défaut.stacked="1"— empile plusieurs<field>dans une même barre.- Le premier
<field>devient l'axe X, les suivants sont des mesures. Pour agréger une mesure numérique (sum,avg…), ajoutertype="measure".
6. La vue <pivot>
<pivot string="Pivot tickets">
<field name="category_id" type="row"/>
<field name="state" type="col"/>
<field name="hours_spent" type="measure"/>
</pivot>
Les trois type essentiels :
row— le champ devient une ligne du tableau.col— le champ devient une colonne.measure— le champ est agrégé (sum, avg, count) dans les cellules.
row pour
faire du drill-down (ex: catégorie puis sous-catégorie), ou plusieurs
measure pour afficher côte à côte heures et coût.
Migration Kanban v17 → v19 — les changements
| v17 (et avant) | v19 |
|---|---|
<t t-name="kanban-box"> | <t t-name="card"> |
t-esc="value" | t-out="value" |
attrs="{'invisible': [...]}" | invisible="state == 'done'" |
<div class="o_kanban_record_top"> + bloc Bootstrap manuel |
Utilitaires Bootstrap 5 directement (d-flex, justify-content-between…) |
Menu d'actions manuel (kanban-menu) |
Automatique dans Odoo 19 — les actions du bouton trois-points viennent de cog |
Upgrade et test visuel
./odoo-bin -c config/odoo.conf -u odooskills_helpdesk -d ta_base --stop-after-init
# Démarre Odoo normalement, puis :
# Menu Helpdesk → Tickets → la vue Kanban s'ouvre par défaut.
# - Glisse une carte d'une colonne à l'autre → state change.
# - Clique sur les étoiles → priority change sans ouvrir la form.
# - Clique sur la pastille d'état kanban → cycle normal/done/blocked.
# - Switch view (top-right) → graph puis pivot.
Les pièges Kanban à éviter
- Utiliser
<t t-name="kanban-box">— renommécarden Odoo 19, le kanban reste vide sinon. - Oublier
<field name="champ"/>avantrecord.champ.raw_value— un champ non déclaré n'est pas chargé côté client,raw_valuerenvoieundefined. - Surcharger
priorityavecselection=alors qu'il est hérité — warning à chaque chargement. Utiliserselection_add. - Mettre
type="measure"sur un champ non numérique dans pivot/graph — l'agrégation échoue silencieusement. - Oublier
group_edit="false"quand tu ne veux pas que l'utilisateur renomme les colonnes (valeurs fixes duSelection).
Voir aussi dans cette série
parent_id, _parent_store
create, write, actions
backend, menus, widgets
Prochain article — T18
On passe à l'héritage de vues : ajouter des champs
à une vue existante avec xpath, position="after",
replace, sans casser la vue parente.