Actions d’administration

Le mode de travail de base de l’administration de Django est en résumé : « sélectionner un objet, puis le changer ». Cela fonctionne bien pour la majorité des cas d’utilisation. Toutefois, si vous devez effectuer la même modification à plusieurs objets à la fois, ce mode de travail peut être assez fastidieux.

Dans ces cas, l’administration de Django vous permet d’écrire et d’inscrire des « actions » – de simples fonctions appelées avec une liste d’objets sélectionnés depuis la page de liste pour modification.

Si vous examinez n’importe quelle liste d’objets à modifier dans l’administration, vous verrez cette fonctionnalité en action ; Django est livré avec une action « supprimer les objets sélectionnés » disponible pour tous les modèles. Par exemple, voici le module utilisateur de l’application django.contrib.auth intégrée dans Django :

../../../../_images/admin-actions.png

Avertissement

L’action « supprimer les objets sélectionnés » utilise QuerySet.delete() pour des raisons d’efficacité, entraînant une importante mise en garde : la méthode delete() de vos modèles ne sera pas appelée.

Si vous souhaitez modifier ce comportement, vous pouvez surcharger ModelAdmin.delete_queryset() ou écrire une action personnalisée qui s’occupe de la suppression à votre manière – par exemple, en appelant Model.delete() pour chacun des éléments sélectionnés.

Pour plus d’informations sur la suppression en vrac, voir la documentation sur la suppression d’objets.

Lisez la suite pour savoir comment ajouter vos propres actions à cette liste.

Écriture d’actions

La meilleure façon d’expliquer les actions est de montrer un exemple. Allons-y !

Un cas d’utilisation courant pour les actions d’administration est la mise à jour en vrac d’un modèle. Imaginez une application simple de nouvelles avec un modèle Article:

from django.db import models

STATUS_CHOICES = (
    ('d', 'Draft'),
    ('p', 'Published'),
    ('w', 'Withdrawn'),
)

class Article(models.Model):
    title = models.CharField(max_length=100)
    body = models.TextField()
    status = models.CharField(max_length=1, choices=STATUS_CHOICES)

    def __str__(self):
        return self.title

Une tâche courante que nous pourrions réaliser avec un tel modèle est de mettre à jour l’état d’un article de « brouillon » à « publié ». Nous pourrions facilement le faire dans l’administration un article à la fois, mais si nous voulions publier en vrac une série d’articles, ce serait fastidieux. Écrivons donc une action qui nous permet de modifier l’état d’un article à « publié ».

Écriture des fonctions d’action

Tout d’abord, nous devons écrire une fonction qui est appelée quand l’action est déclenchée depuis l’administration. Les fonctions d’action sont de simples fonctions ordinaires qui acceptent trois paramètres :

  • La classe ModelAdmin concernée
  • Un objet HttpRequest représentant la requête actuelle
  • Un jeu de requête QuerySet contenant l’ensemble des objets sélectionnés par l’utilisateur.

Notre fonction de publication d’articles n’aura pas besoin de la classe ModelAdmin ni de l’objet requête, mais nous allons utiliser le jeu de requête :

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')

Note

Pour de meilleures performances, nous utiliserons la méthode update (mise à jour) du jeu de requête. D’autres types d’actions pourraient avoir besoin de manipuler chaque objet individuellement ; dans ce cas, nous itérerions simplement sur le jeu de requête :

for obj in queryset:
    do_something_with(obj)

C’est en fait tout ce qu’il y a besoin d’écrire pour une action ! Toutefois, nous allons effectuer une étape supplémentaire facultative mais utile et donner un « joli » titre à l’action dans l’interface d’administration. Par défaut, cette action apparaît dans la liste d’actions comme « Make published » – le nom de la fonction avec les soulignements remplacés par des espaces. C’est bien, mais nous pouvons fournir un nom meilleur et plus convivial en donnant à la fonction make_published un attribut short_description:

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"

Note

Cela peut sembler familier ; l’option d’administration list_display utilise aussi la même technique pour fournir des descriptions lisibles pour les fonctions de rappel qui y sont inscrites.

Ajout d’actions à la classe ModelAdmin

Ensuite, il faudra informer la ModelAdmin de l’action. Cela fonctionne comme n’importe quelle autre option de configuration. Ainsi, le fichier admin.py complet avec l’action et son inscription ressemblerait à :

from django.contrib import admin
from myapp.models import Article

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"

class ArticleAdmin(admin.ModelAdmin):
    list_display = ['title', 'status']
    ordering = ['title']
    actions = [make_published]

admin.site.register(Article, ArticleAdmin)

Avec ce code, la liste d’objets à modifier dans l’interface d’administration ressemble à ceci :

../../../../_images/adding-actions-to-the-modeladmin.png

C’est vraiment tout ce qu’il y a à faire ! Si vous brûlez d’écrire vos propres actions, vous en savez maintenant assez pour débuter. Le reste de ce document ne couvre que des techniques plus avancées.

Gestion des erreurs dans les actions

S’il y a des conditions d’erreur prévisibles qui peuvent survenir lors de l’exécution de votre action, vous devriez faire amende honorable en informant l’utilisateur du problème. Cela signifie gérer les exceptions et utiliser django.contrib.admin.ModelAdmin.message_user() pour afficher une description conviviale du problème dans la réponse.

Techniques d’action avancées

Il y a quelques options et possibilités supplémentaires que vous pouvez exploiter pour des options plus avancées.

Actions en tant que méthodes de ModelAdmin

L’exemple ci-dessus montre l’action make_published définie comme une fonction simple. C’est tout à fait correct, mais ce n’est pas parfait d’un point de vue conception de code : comme l’action est étroitement couplée à l’objet Article, il paraît logique de fusionner l’action dans l’objet ArticleAdmin même.

C’est assez facile à réaliser :

class ArticleAdmin(admin.ModelAdmin):
    ...

    actions = ['make_published']

    def make_published(self, request, queryset):
        queryset.update(status='p')
    make_published.short_description = "Mark selected stories as published"

Remarquez d’abord que nous avons transformé make_published en une méthode et renommé le paramètre modeladmin à self, puis que nous avons placé la chaîne make_published dans actions à la place d’une référence de fonction directe. Cela indique à ModelAdmin de rechercher l’action en tant que méthode.

Définir les actions en tant que méthodes donne à l’action un accès plus direct et idiomatique à la classe ModelAdmin elle-même, permettant à l’action d’appeler l’une des méthodes définies par l’application d’administration.

Par exemple, nous pouvons utiliser self pour afficher un message à l’utilisateur l’informant que l’action a réussi :

class ArticleAdmin(admin.ModelAdmin):
    ...

    def make_published(self, request, queryset):
        rows_updated = queryset.update(status='p')
        if rows_updated == 1:
            message_bit = "1 story was"
        else:
            message_bit = "%s stories were" % rows_updated
        self.message_user(request, "%s successfully marked as published." % message_bit)

Cela fait ressembler l’action à ce que l’administration elle-même réalise après avoir réussi à effectuer une action :

../../../../_images/actions-as-modeladmin-methods.png

Actions qui fournissent des pages intermédiaires

Par défaut, après qu’une action a été effectuée, l’utilisateur est simplement redirigé vers la page d’origine de la liste d’édition. Cependant, certaines actions, en particulier les plus complexes, pourraient devoir renvoyer à des pages intermédiaires. Par exemple, l’action de suppression intégrée demande confirmation avant de supprimer les objets sélectionnés.

Pour fournir une page intermédiaire, il suffit de renvoyer une réponse HttpResponse (ou une sous-classe) depuis votre action. Par exemple, vous pourriez écrire une fonction d’exportation simple qui utilise les fonctions de sérialisation de Django pour exporter certains objets sélectionnés en JSON :

from django.core import serializers
from django.http import HttpResponse

def export_as_json(modeladmin, request, queryset):
    response = HttpResponse(content_type="application/json")
    serializers.serialize("json", queryset, stream=response)
    return response

En général, l’exemple ci-dessus n’est pas considéré comme une bonne idée. La plupart du temps, la pratique conseillée est de renvoyer une réponse HttpResponseRedirect et de rediriger l’utilisateur vers une vue que vous avez écrite, en passant la liste des objets sélectionnés dans la chaîne de requête GET. Cela vous permet de fournir une logique d’interaction complexe sur les pages intermédiaires. Par exemple, si vous vouliez fournir une fonction d’exportation plus complète, vous voudriez que l’utilisateur puisse choisir un format et peut-être une liste de champs à inclure dans l’exportation. La meilleure chose à faire serait d’écrire une petite action qui redirige tout simplement vers la vue d’exportation personnalisée :

from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect

def export_selected_objects(modeladmin, request, queryset):
    selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
    ct = ContentType.objects.get_for_model(queryset.model)
    return HttpResponseRedirect("/export/?ct=%s&ids=%s" % (ct.pk, ",".join(selected)))

Comme vous pouvez le voir, l’action est la partie la plus simple ; toute la logique complexe est déléguée à la vue d’exportation. Celle-ci doit se charger de traiter des objets de tout type, d’où la présence du modèle ContentType.

La rédaction de cette vue est laissée en exercice au lecteur.

Actions disponibles pour l’ensemble d’un site

AdminSite.add_action(action, name=None)[source]

Certaines actions sont plus efficaces lorsqu’elles sont mises à la disposition de tous les objets dans le site d’administration – l’action d’exportation définie ci-dessus serait un bon candidat. Vous pouvez rendre une action globalement disponible en utilisant AdminSite.add_action(). Par exemple :

from django.contrib import admin

admin.site.add_action(export_selected_objects)

Cela rend l’action export_selected_objects globalement disponible en tant qu’action nommée « export_selected_objects ». Vous pouvez explicitement donner un nom à l’action – utile si vous souhaitez plus tard programmatiquement supprimer l’action – en passant un second argument à AdminSite.add_action()

admin.site.add_action(export_selected_objects, 'export_selected')

Désactivation des actions

Parfois, vous devez désactiver certaines actions – en particulier celles enregistrées globalement – pour des objets particuliers. Il y a plusieurs façons dont vous pouvez désactiver les actions :

Désactivation d’une action à l’échelle du site

AdminSite.disable_action(name)[source]

Si vous avez besoin de désactiver une action à l’échelle du site vous pouvez appeler AdminSite.disable_action().

Par exemple, vous pouvez utiliser cette méthode pour supprimer l’action intégrée « supprimer les objets sélectionnés » :

admin.site.disable_action('delete_selected')

Une fois que vous avez fait ce qui précède, cette action ne sera plus disponible à l’ensemble du site.

Si toutefois vous avez besoin de réactiver une action globalement désactivée pour un modèle en particulier, il suffit de l’indiquer explicitement dans la liste ModelAdmin.actions:

# Globally disable delete selected
admin.site.disable_action('delete_selected')

# This ModelAdmin will not have delete_selected available
class SomeModelAdmin(admin.ModelAdmin):
    actions = ['some_other_action']
    ...

# This one will
class AnotherModelAdmin(admin.ModelAdmin):
    actions = ['delete_selected', 'a_third_action']
    ...

Désactivation de toutes les actions pour une classe ModelAdmin particulière

Si vous ne voulez aucune action en vrac disponible pour une classe ModelAdmin donnée, il suffit de définir ModelAdmin.actions à None:

class MyModelAdmin(admin.ModelAdmin):
    actions = None

Cela indique à la classe ModelAdmin de n’afficher ni d’autoriser aucune des actions, y compris les actions globales au site.

Activation ou désactivation conditionnelle des actions

ModelAdmin.get_actions(request)[source]

Enfin, vous pouvez activer ou désactiver conditionnellement les actions selon la requête (et donc par utilisateur) en redéfinissant ModelAdmin.get_actions().

Cela renvoie un dictionnaire d’actions autorisées. Les clés sont des noms d’action, et les valeurs sont des tuples (fonction, nom, description courte).

La plupart du temps vous utiliserez cette méthode pour supprimer conditionnellement les actions de la liste obtenue par la classe parente. Par exemple, si je voulais seulement les utilisateurs dont le nom commence par « J » pour être en mesure de supprimer des objets en vrac, je pourrais faire ce qui suit :

class MyModelAdmin(admin.ModelAdmin):
    ...

    def get_actions(self, request):
        actions = super().get_actions(request)
        if request.user.username[0].upper() != 'J':
            if 'delete_selected' in actions:
                del actions['delete_selected']
        return actions

Définition de permissions pour les actions

New in Django 2.1:

Les actions peuvent limiter leur disponibilité aux utilisateurs ayant des permissions spécifiques en définissant l’attribut allowed_permissions sur la fonction d’action

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.allowed_permissions = ('change',)

L’action make_published() ne sera disponible que pour les utilisateurs qui passent le contrôle ModelAdmin.has_change_permission().

Si allowed_permissions possède plus d’une permission, l’action sera disponible si l’utilisateur passe au moins l’un des contrôles.

Les valeurs disponibles pour allowed_permissions et leur méthodes de contrôle correspondantes sont :

Vous pouvez indiquer d’autres valeurs pour autant que vous implémentiez une méthode correspondante has_<valeur>_permission(self, request) pour la classe ModelAdmin concernée.

Par exemple :

from django.contrib import admin
from django.contrib.auth import get_permission_codename

class ArticleAdmin(admin.ModelAdmin):
    actions = ['make_published']

    def make_published(self, request, queryset):
        queryset.update(status='p')
    make_published.allowed_permissions = ('publish',)

    def has_publish_permission(self, request):
        """Does the user have the publish permission?"""
        opts = self.opts
        codename = get_permission_codename('publish', opts)
        return request.user.has_perm('%s.%s' % (opts.app_label, codename))
Back to Top