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 :
Désactiver la catégorie, ce qui ne rendra plus visible la catégorie sur l'endpoint
http://127.0.0.1:8000/api/category/
.Désactiver les produits de cette catégorie, ce qui ne rendra plus visible ces produits sur l'endpoint
http://127.0.0.1:8000/api/product/
.
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
.
Suivez-moi dans le screencast ci-dessous sur la mise en place de notre action disable
:
À vous de jouer
Mettons en place le même système pour désactiver un produit qui désactive également les articles associés. Nous pourrions également améliorer la désactivation d’une catégorie en désactivant les articles de chaque produit.
Pour réaliser cela, vous pouvez partir de la branche P2C3_exercice. Elle contient déjà ce que nous venons de faire ensemble. Une solution est proposée sur la branche P2C3_solution.
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 !