3 options de filtre dans l'administration de Django

Découvrez 3 façons de filtrer des modèles dans l'administration de Django.

Publié le par Thibault Houdon (mis à jour le )
paceTemps de lecture estimé : 25 minutes

Django met à notre disposition de nombreux moyens de filtrer les modèles visibles dans l'administration de notre site.

Dans cet article, je vous présente trois façons de filtrer les modèles et les entrées de votre base de donnée afin de gagner du temps lors de l'édition de votre site.

Tous les exemples suivants sont implémentés dans les fichiers admin.py des applications concernées.

Filtrer les modèles par champ

Par défaut, la page d'administration d'un modèle ne dispose d'aucune option pour filtrer les modèles affichés.

Quand on commence à avoir beaucoup d'entrées dans notre base de données, il devient nécessaire de pouvoir filtrer les entrées selon certains champs.

Prenons l'exemple d'un modèle d'articles de blog (BlogPost) avec le code suivant dans le fichier admin.py de l'application blog :

@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
    list_display = ('title', 'publish_date', 'published', 'featured', 'read_count')
    list_editable = ('published', 'featured', 'publish_date')
    ordering = ('publish_date',)

Par défaut, nous n'avons aucun moyen de filtrer les articles dans l'administration :

Pour ajouter des options de filtre, il suffit d'ajouter l'attribut list_filter et une liste des champs avec lesquels nous souhaitons filtrer les entrées :

@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
    list_display = ('title', 'publish_date', 'published', 'featured', 'read_count')
    list_editable = ('published', 'featured', 'publish_date')
    ordering = ('publish_date',)
    list_filter = ('popular', 'publish_date')

En ajoutant les champs popular et publish_date à l'attribut list_filter, un nouveau panneau fait son apparition à droite de la page d'administration du modèle BlogPost :

Le champ popular est un booléen, les deux options qui nous sont proposées sont donc logiquement "oui" et "non" (pour respectivement une valeur True ou False).

Vous remarquerez que pour la date, Django nous propose de base des options plus intéressantes :

  • Toutes les dates
  • Aujourd’hui
  • Les 7 derniers jours
  • Ce mois-ci
  • Cette année

C'est une bonne base, mais elle n'est pas forcément adaptée à tous les cas de figure.

Par exemple dans mon cas j'aimerais bien afficher tous les articles publiés ces 2 dernières semaines.

Actuellement, cette option n'est pas disponible, mais comme avec tout dans Django, il est relativement facile de le personnaliser.

Pour ça, nous allons créer une classe qui hérite de DateFieldListFilter (je vous conseille d'aller jeter un coup d'oeil au code source de Django, c'est très instructif et finalement pas si compliqué que ça. Quelques calculs avec le module datetime et le tour est joué) :

import datetime
from django.contrib.admin import DateFieldListFilter

class CustomDateFilter(DateFieldListFilter):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # On crée deux objets pour aujourd'hui et la date d'il y a 2 semaines
        today = datetime.date.today()
        two_weeks = today - datetime.timedelta(days=14)

        # On ajoute cet intervalle à la liste des liens de filtre
        self.links = list(self.links)
        self.links.insert(3, ('Les 14 derniers jours', {
            self.lookup_kwarg_since: str(two_weeks),
            self.lookup_kwarg_until: str(today),
        }))

Tout d'abord, nous créons deux objets avec le module datetime, correspondants à la date d'aujourd'hui et la date 14 jours en arrière (grâce à timedelta) :

today = datetime.date.today()
two_weeks = today - datetime.timedelta(days=14)

Il ne nous reste plus qu'à ajouter cet intervalle de temps à la liste des liens qui servent à filtrer les modèles (self.links).

Cette liste étant en fait un tuple, nous devons la convertir au préalable en liste pour pouvoir la modifier (avec list(self.links)).

Puis, grâce à la méthode insert, nous insérons en 3e position, notre intervalle de temps avec le texte associé ("Les 14 derniers jours") :

self.links = list(self.links)  # On récupère le tuple sous forme de liste
self.links.insert(3, ('Les 14 derniers jours', {
    self.lookup_kwarg_since: str(two_weeks),
    self.lookup_kwarg_until: str(today),
}))

On utilise insert pour pouvoir positionner précisément notre nouveau lien après l'entrée "Les 7 derniers jours". On pourrait également utiliser append mais dans ce cas-ci notre lien se retrouverait en dernier. À vous d'utiliser insert ou append avec le bon indice en fonction de la position que vous souhaitez donner à votre lien dans la liste.Pour finir, il ne nous reste plus qu'à indiquer dans l'attribut list_filter que nous souhaitons utiliser notre classe personnalisée.

Il suffit de remplacer la chaîne de caractères 'publish_date' par le tuple ('publish_date', CustomDateFilter) :

@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
    list_display = ('title', 'publish_date', 'published', 'featured', 'read_count')
    list_editable = ('published', 'featured', 'publish_date')
    ordering = ('publish_date',)
    list_filter = ('popular', ('publish_date', CustomDateFilter))

Et voilà, nous pouvons maintenant filtrer les articles en affichant uniquement les articles publiés dans les 2 dernières semaines 🥳

Un filtrage encore plus précis avec date_hierarchy

Un autre attribut très intéressant, date_hierarchy, nous permet de filtrer les articles par année, mois et jour.

Rajoutons date_hierarchy = 'publish_date' à notre modèle d'administration pour les articles :

@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
    list_display = ('title', 'publish_date', 'published', 'featured', 'read_count')
    list_editable = ('published', 'featured', 'publish_date')
    ordering = ('publish_date',)
    list_filter = ('popular', ('publish_date', CustomDateFilter))
    date_hierarchy = 'publish_date'

Une seule ligne de code suffit pour maintenant avoir la possibilité de filtrer nos articles par année, puis par mois et ensuite par jours.

Vous remarquerez que là encore Django est suffisamment intelligent pour n'afficher que les informations pertinentes. Une fois arrivé au mois de septembre, seulement 3 dates sont affichées, correspondant aux 3 dates auxquelles j'ai publié des articles :

Description

Description

On filtre par année

Puis par mois

Et pour finir par jour

Filtrer un champ avec menu déroulant

Pour les champs de type ForeignKey, l'interface d'administration de Django vous présente par défaut un menu déroulant qui ne permet pas de facilement trouver une entrée de votre base de donnée.

Sur Docstring, avec les centaines de vidéos de formation et sans option de filtre, c'est quasiment mission impossible de trouver la bonne vidéo :

Heureusement là encore, il existe un attribut très simple à mettre en place pour vous permettre de rechercher une entrée dans votre base de donnée.

L'attribut autocomplete_fields permet d'utiliser un composant HTML qui ressemble à la liste déroulante par défaut mais qui apporte une fonction de recherche qui charge les options de manière asynchrone.

La recherche est rendue plus intuitive dans les cas où le modèle lié possède de nombreuses instances (et comme c'est asynchrone, c'est également plus rapide).

Au niveau du code, ça donne ça :

@admin.register(Session)
class SessionAdmin(admin.ModelAdmin):
    list_display = ('name', 'slug', 'position', 'session_type', 'video', 'free_to_view')
    autocomplete_fields = ('video',)

Mais ce n'est pas tout !

Il faut également ajouter l'attribut search_fields au modèle référencé par autocomplete_fields.

Dans mon cas, le champ video que j'ai ajouté dans autocomplete_fields est un champ ForeignKey vers mon modèle VimeoVideo.

Je dois donc également rajouter l'attribut search_fields dans la classe qui définie l'administration de mon modèle VimeoVideo :

@admin.register(VimeoVideo)
class VimeoVideoAdmin(admin.ModelAdmin):
    list_display = ('name', 'uri', 'duration')
    search_fields = ('name',)

C'est grâce à cet attribut search_fields que je peux filtrer les vidéos par leur nom dans l'interface d'administration de mon modèle Session.

Limiter le nombre d'éléments affichés

Si vous affichez beaucoup d'informations sur vos modèles, il se peut que l'interface d'administration de Django devienne un peu lente.

C'est normal, afficher beaucoup de champs nécessites plus de requêtes vers votre base de donnée et le temps d'affichage se trouve donc impacté.

Une façon efficace de limiter le temps d'affichage tout en conservant un nombre important de champs affichés est tout simplement de limiter le nombre d'éléments affichés sur chaque page.

Par défaut, Django affiche 100 éléments sur chaque page :

L'attribut list_per_page nous permet d'indiquer le nombre d'éléments que nous souhaitons afficher. Il est donc possible d'en afficher moins pour rendre le chargement de la page plus rapide (ou plus, si vous souhaitez avoir plus d'éléments affichés sur chaque page) :

@admin.register(Session)
class SessionAdmin(admin.ModelAdmin):
    list_display = ('name', 'slug', 'position', 'session_type', 'video', 'free_to_view')
    list_editable = ('slug', 'position', 'session_type', 'free_to_view')
    list_display_links = ('name',)
    list_filter = ('session_type',)
    ordering = ('position',)
    search_fields = ('name',)
    list_per_page = 10

Et voilà, nous n'affichons plus que 10 éléments par page, ce qui rend le chargement beaucoup plus rapide.

Conclusion

Il existe plein d'autres façons de filtrer des données dans l'administration de Django.

Les options par défaut sont déjà très puissantes, mais vous voyez qu'avec peu de code, il est possible de se simplifier encore plus la vie et d'avoir une interface d'administration vraiment personnalisées pour les besoins de notre site.