Ajoutez de l’interaction avec les actions

 
Ne soyez pas trop actif !

Une action, on a déjà parlé de ça, non ?

Nous avons parlé du paramètre action  des Viewsets, mais dans ce chapitre nous allons voir le décorateur action,  fourni par DRF, qui permet de réaliser d’autres types d’actions que les classiques du CRUD, comme « Demander en ami », « S’abonner à un fil d’actualité » ou « Publier un article ».

Vous devez penser Action chaque fois qu’un besoin fait référence à une entité, mais que le verbe ne correspond pas à un élément du CRUD. Par exemple, dans « Nous souhaitons que nos visiteurs puissent liker des publications », l’entité est la publication et l’action est liker.

Une action se crée dans DRF en mettant en place le décorateur action  sur une méthode d’un Viewset. Les paramètres suivants sont disponibles :

  • methods  est la liste des méthodes HTTP qui appellent cette action, parmi GET, POST, PATCH, PUT, DELETE.

  • detail  est un booléen qui précise si l’action est disponible sur l’URL de liste ou de détail.

  • url_path  permet de déterminer l’URL qui sera ajoutée à la fin de l'endpoint de liste ou de détail. S'il n’est pas précisé, alors le nom de la méthode est utilisé.

Pour notre boutique en ligne, on pourrait imaginer une action qui permette d’activer ou de désactiver une catégorie.

Mais on ne pourrait pas juste faire un PATCH sur la catégorie pour mettre active  à False  ?

On pourrait effectivement, mais nous aimerions également désactiver tous les produits qui composent cette catégorie. Plutôt que de laisser les applications clientes faire tous ces appels, mettons-leur à disposition un seul endpoint qui réalise cela.

D’ailleurs, vous savez quoi ? On va le faire tout de suite… ;)

 
Soyez actif quand même !

Les actions se mettent en place sur les Viewsets, alors allons modifier notre CategoryViewset  pour lui ajouter une action disable  que nous souhaitons accessible en POST.

@transaction.atomic
    @action(detail=True, methods=['post'])
    def disable(self, request, pk):
        # Nous avons défini notre action accessible sur la méthode POST seulement elle concerne le détail car permet de désactiver une catégorie
        # Nous avons également mis en place une transaction atomique car plusieurs requêtes vont être exécutées en cas d'erreur, nous retrouverions alors l'état précédent
        # Désactivons la catégorie
        category = self.get_object()
        category.active = False
        category.save()

        # Puis désactivons les produits de cette catégorie
        category.products.update(active=False)

        # Retournons enfin une réponse (status_code=200 par défaut) pour indiquer 
        # le succès de l'action
        return Response()

Ainsi, la réalisation d’un POST sur l'endpointhttp://127.0.0.1:8000/api/category/2/disable/  aura pour effet de :

Ajoutons une méthode disable  à notre model Category  :

class Category(models.Model):
 
    date_created = models.DateTimeField(auto_now_add=True)
    date_updated = models.DateTimeField(auto_now=True)
 
    name = models.CharField(max_length=255)
    active = models.BooleanField(default=False)
 
    @transaction.atomic
    def disable(self):
        if self.active is False:
        # Ne faisons rien si la catégorie est déjà désactivée
            return
        self.active = False
        self.save()
        self.products.update(active=False)
class MultipleSerializerMixin:
    # Un mixin est une classe qui ne fonctionne pas de façon autonome
    # Elle permet d'ajouter des fonctionnalités aux classes qui les étendent

    detail_serializer_class = None

    def get_serializer_class(self):
        # Notre mixin détermine quel serializer à utiliser même si elle ne sait pas ce que c'est ni comment l'utiliser
        if self.action == 'retrieve' and self.detail_serializer_class is not None:
            # Si l'action demandée est le détail alors nous retournons le serializer de détail
            return self.detail_serializer_class
        return super().get_serializer_class()

class CategoryViewset(MultipleSerializerMixin, ReadOnlyModelViewSet):
 
    serializer_class = CategoryListSerializer
    detail_serializer_class = CategoryDetailSerializer
 
    @action(detail=True, methods=['post'])
    def disable(self, request, pk):
        # Nous pouvons maintenant simplement appeler la méthode disable
        self.get_object().disable()
        return Response()

Cette modification ne change en rien le fonctionnement de notre API, mais permet une lecture plus claire du code. L’action ne fait qu'appeler une méthode de notre model Category.

 

En résumé
  • Il est possible de créer d’autres actions en dehors de celles du CRUD.

  • DRF met à disposition un décorateur action  qui permet de créer de nouvelles actions.

  • Les actions peuvent être mises en place sur les URL de liste et de détail d’un endpoint.

  • Les actions peuvent utiliser n’importe quelle méthode HTTP (GET, POST, PATCH, DELETE…).

Les actions servent à gérer des morceaux de logique métier, et ainsi à éviter aux applications clientes de les gérer, car elles pourraient les gérer différemment. Le fait d’ajouter un article au panier d’un utilisateur est un bon exemple d’action qui ne peut être réalisé que d’une seule façon, son traitement doit donc être réalisé par le serveur et non les applications clientes. Dans le chapitre suivant, vous découvrirez comment valider les données transmises par ces actions !