mayan/apps/navigation/ — gestion déclarative des liens, menus et colonnes de liste.
Navigation fournit trois abstractions centrales :
Link — un lien vers une vue, avec vérification de permissionMenu — un conteneur de liens, rendu dans les templatesSourceColumn — une colonne de tableau dans les vues listeCes trois objets sont déclarés dans les apps (typiquement dans links.py, menus.py, column_widgets.py) et enregistrés dans des registres globaux. Les templates les résolvent au moment du rendu en fonction du contexte de la requête.
Link — un lien résolu à la volée# mayan/apps/navigation/links.py
Link(
text=_('Éditer'),
view='documents:document_edit',
kwargs={'document_id': 'object.pk'},
permission=permission_document_edit,
icon=icon_document_edit,
)
Link.resolve(context))Lors du rendu, Link.resolve(context) :
condition (callable optionnel) → abandonne si Falsereverse(view, kwargs=...) ou utilise url statiqueobj est présent dans le contexte → vérification ACL sur l'objetrequest.userResolvedLink (URL, icône, texte, actif/inactif)Si la permission est refusée, resolve() retourne None → le lien n'est pas affiché.
| Classe | Usage |
|---|---|
Separator |
Ligne de séparation dans un menu déroulant |
Text |
Texte non-cliquable (label) |
Menu — registre de liens par sourceUn Menu est un conteneur nommé. Les apps y attachent des liens via bind_links().
# Déclaration (dans navigation/menus.py ou une app)
menu_object = Menu(name='object', label=_('Actions'))
# Enregistrement d'un lien sur un modèle
menu_object.bind_links(
links=[link_document_edit, link_document_delete],
sources=[Document],
position=10,
)
bind_links(links, sources, position, exclude)| Paramètre | Rôle |
|---|---|
links |
Liste de Link à attacher |
sources |
Modèles, vues ou noms de vues cibles |
position |
Ordre de tri dans le menu |
exclude |
Sources à exclure explicitement |
Menu.resolve(context, request, source, sort_results)Lors du rendu d'un template, resolve() :
source active (modèle de l'objet courant ou nom de la vue)get_links_for_class_cached()) — un lien lié à Document s'applique aussi aux sous-classes proxyResolvedLink triés par positionMenu.resolve()
├── get_links_for_class_cached(source) ← @cache, remonte MRO proxy
│ └── liens attachés à Document, DocumentFile, etc.
└── Link.resolve(context) ← filtre permission + condition
└── ResolvedLink (url, icon, text)
SourceColumn — colonnes de tableau déclarativesDans Django classique, les colonnes d'un tableau sont définies directement dans le template HTML. Mayan fait le contraire : les colonnes sont déclarées en Python, dans apps.py, lors du démarrage de l'app. Le template ne sait pas quelles colonnes existent — il demande au registre SourceColumn quelles colonnes sont enregistrées pour le type d'objet affiché.
Avantage : une app tierce peut ajouter une colonne à la liste des documents sans toucher au template de l'app documents. Il suffit de déclarer un SourceColumn(source=Document, ...) dans son propre ready().
DocumentFileVoici les colonnes réelles déclarées dans mayan/apps/documents/apps.py :
# Colonne principale : nom du fichier, cliquable, lien vers l'objet
SourceColumn(
source=DocumentFile,
attribute='filename',
is_identifier=True, # colonne principale → rendue comme lien
is_object_absolute_url=True, # l'URL = DocumentFile.get_absolute_url()
)
# Vignette (widget personnalisé)
SourceColumn(
source=DocumentFile,
label=_('Thumbnail'),
order=-99, # affiché en premier (ordre croissant)
widget=ThumbnailWidget, # rendu HTML délégué au widget
html_extra_classes='text-center document-thumbnail-list',
)
# Nombre de pages — appel de méthode avec argument dynamique
SourceColumn(
source=DocumentFile,
attribute='get_page_count', # méthode sur DocumentFile
kwargs={'user': 'request.user'}, # argument résolu depuis le contexte
include_label=True,
order=-8,
)
# Taille du fichier — triable, avec champ de tri explicite
SourceColumn(
source=DocumentFile,
attribute='get_size_display', # méthode → valeur affichée
include_label=True,
is_sortable=True,
sort_field='size', # champ DB réel utilisé pour ORDER BY
)
Ce que Mayan affiche à l'écran pour la liste des fichiers d'un document est exactement ces colonnes, dans l'ordre défini par order.
SourceColumn.resolve(context) fonctionnePour chaque ligne du tableau, Mayan appelle column.resolve(context) avec context['object'] = l'objet de la ligne courante.
column.resolve(context)
├── si attribute='filename'
│ → resolve_attribute(obj=document_file, attribute='filename')
│ → document_file.filename (= "rapport.pdf")
│
├── si attribute='get_page_count' + kwargs={'user': 'request.user'}
│ → resolve_attribute(obj=document_file, attribute='get_page_count',
│ kwargs={'user': <User: marie>})
│ → document_file.get_page_count(user=marie) (= 12)
│
├── si func=my_callable
│ → my_callable(context=context)
│
└── si widget=ThumbnailWidget
→ ThumbnailWidget(column=self, request=request, value=result).render()
→ HTML <img src="...">
| Paramètre | Type | Rôle |
|---|---|---|
source |
modèle Django | Quel type d'objet cette colonne concerne |
attribute |
str |
Chemin pointé résolu sur l'objet ('document.label', 'get_size_display') |
func |
callable | Alternative à attribute — appelé avec le contexte complet |
kwargs |
dict | Arguments passés à l'attribute/func, résolus depuis le contexte de la requête |
is_identifier |
bool | Colonne principale — rendue avec un lien vers l'objet |
is_object_absolute_url |
bool | L'URL du lien = obj.get_absolute_url() |
is_attribute_absolute_url |
bool | L'URL = attribute_result.get_absolute_url() |
is_sortable |
bool | En-tête cliquable pour trier la liste |
sort_field |
str |
Champ DB pour ORDER BY (si différent de attribute) |
order |
int | Position dans le tableau (tri croissant, défaut 0) |
widget |
classe | Délègue le rendu HTML à un widget |
include_label |
bool | Affiche le label de la colonne à côté de la valeur |
empty_value |
str |
Valeur affichée si le résultat est falsy |
get_column_matches(source) — résolution par typeQuand Mayan veut afficher un tableau, il appelle SourceColumn.get_for_source(source). La source peut être :
queryset.model)La méthode remonte aussi la hiérarchie des classes (MRO) pour inclure les colonnes des classes parentes et des modèles proxy.
SourceColumnWidget ← classe de base (render via template)
└── SourceColumnLinkWidget ← enveloppe la valeur dans un <a href="...">
Les widgets reçoivent column, request et value (la valeur résolue), et retournent du HTML. SourceColumnLinkWidget utilise column.absolute_url pour construire le lien.
Mayan déclare plusieurs menus globaux réutilisés dans toute l'interface :
| Nom | Usage |
|---|---|
menu_main |
Barre de navigation principale |
menu_object |
Actions sur l'objet courant (boutons d'action) |
menu_list_facet |
Facettes dans les vues liste (filtres latéraux) |
menu_secondary |
Actions secondaires |
menu_tools |
Outils d'administration |
menu_user |
Menu utilisateur (profil, déconnexion) |
App bootstrap (ready())
└── menu_object.bind_links([link_edit], sources=[Document])
└── SourceColumn(source=DocumentFile, attribute='filename', is_identifier=True)
Requête HTTP → vue liste des fichiers d'un document
└── template {% get_navigation_links menu=menu_object %}
└── Menu.resolve(context, source=DocumentFile)
└── ResolvedLink(url='/documents/files/42/', text='rapport.pdf')
└── template {% render_list object_list=files %}
└── SourceColumn.get_for_source(source=files_queryset)
→ [colonne filename, colonne thumbnail, colonne taille, ...]
└── pour chaque fichier : column.resolve(context)
→ "rapport.pdf", <img …>, "1.2 Mo", ...
| Fichier | Contenu |
|---|---|
links.py |
Classes Link, ResolvedLink, Separator, Text |
menus.py |
Classe Menu, bind_links, resolve |
source_columns.py |
Classe SourceColumn, get_column_matches, resolve |
column_widgets.py |
SourceColumnWidget, SourceColumnLinkWidget |
templatetags/navigation_tags.py |
Tags template {% get_navigation_links %} etc. |
templates/navigation/ |
Templates des widgets et menus |