Ne permettez pas la création de tout et n’importe quoi
Attaquons-nous à présent à un autre sujet : la création d’entités et plus précisément de catégories, dans notre cas.
Notre endpoint actuel permet la lecture seulement, car il est destiné aux visiteurs de notre boutique. Nous allons très vite en mettre un second en place, qui sera dédié aux administrateurs qui, eux, auront la possibilité de créer, modifier et supprimer des données.
La création d’entité impose d’effectuer certains contrôles qui sont généralement de deux types :
Les contrôles sur un champ, comme par exemple vérifier que le nom d’une catégorie n'existe pas déjà, et ainsi éviter les doublons.
Les contrôles multichamps, comme la vérification que les deux mots de passe saisis à l’inscription sont les mêmes.
DRF nous permet de réaliser ces deux types de contrôles au travers de la réécriture de méthodes sur le serializer :
validate_XXX
où XXX est le nom du champ à valider ;validate
qui permet un contrôle global sur tous les champs du serializer.
N’attendons pas plus longtemps pour mettre en place l'endpoint et les contrôles qui l’accompagnent. ;)
Validez les données d’un champ
Commençons tout de suite par la mise en place du nouvel endpoint sur l’URLhttp://127.0.0.1:8000/api/admin/category/
qui, comme son nom l’indique, servira à administrer les catégories.
Pourquoi ne pas utiliser l’endpoint déjà existant ?
Dans les endpoints d’administration :
Des serializers différents sont utilisés et les données retournées diffèrent ;
Certains accès peuvent également être limités à certaines personnes authentifiées.
Dans le cadre de notre boutique, nous avons décidé de définir nos endpoints en fonction des acteurs qui les utilisent.
Créons notre nouvel endpoint d’administration qui cette fois-ci étend ModelViewset
et non plus ReadOnlyViewset
. Celui-ci ne doit pas avoir de limitation sur les catégories actives, car il s’agit d’un endpoint d’administration.
class AdminCategoryViewset(MultipleSerializerMixin, ModelViewSet):
serializer_class = CategoryListSerializer
detail_serializer_class = CategoryDetailSerializer
def get_queryset(self):
return Category.objects.all()
Puis définissons notre nouvel endpoint en le déclarant auprès de notre routeur.
router.register('admin/category', AdminCategoryViewset, basename='admin-category')
Vérifions que notre endpoint est bien fonctionnel sur l’URLhttp://127.0.0.1/api/admin/category/
.
Attendez, tout en bas, un formulaire est apparu avec un bouton POST, c’est quoi ?
Maintenant que nous utilisons un Viewset, la création de catégorie est possible. C’est à la création que sert ce formulaire en nous permettant d'effectuer des actions POST. Les actions de mise à jour et de suppression sont disponibles sur les URL de détail des catégories.
Validons à présent nos données, sans oublier que la création d’un doublon de catégorie ne doit pas être permis. Il nous faut pour cela modifier notre serializer de liste, car c’est lui qui est utilisé pour l’action create
.
La validation d’un champ unique se fait en écrivant la méthode validate_XXX
où XXX
est le nom du champ. Dans notre cas, validate_name
:
class CategoryListSerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'date_created', 'date_updated', 'name']
def validate_name(self, value):
# Nous vérifions que la catégorie existe
if Category.objects.filter(name=value).exists():
# En cas d'erreur, DRF nous met à disposition l'exception ValidationError
raise serializers.ValidationError('Category already exists')
return value
Si nous tentons à présent de créer une catégorie qui existe déjà, une réponse en 400 avec des données contenant la nature de l’erreur sont renvoyées.
La validation de champ unique permet d’effectuer plein de contrôles, tant qu’ils sont effectués sur ce champ précis. On pourrait imaginer un système de filtre de mots pour un forum, par exemple.
Mettez en place une validation multiple
Pour notre boutique, nous souhaitons optimiser le référencement et avoir un rappel du nom de la catégorie également présent dans la description. Nous pouvons effectuer automatiquement ce contrôle au travers d’une validation multiple.
La validation entre champs se fait au travers de la méthode validate
. Vérifions que le nom est bien présent dans la description :
class CategoryListSerializer(serializers.ModelSerializer):
class Meta:
model = Category
# Pensons à ajouter « description » à notre liste de champs
fields = ['id', 'date_created', 'date_updated', 'name', 'description']
def validate_name(self, value):
if Category.objects.filter(name=value).exists():
raise serializers.ValidationError('Category already exists')
return value
def validate(self, data):
# Effectuons le contrôle sur la présence du nom dans la description
if data['name'] not in data['description']:
# Levons une ValidationError si ça n'est pas le cas
raise serializers.ValidationError('Name must be in description')
return data
Nous pouvons alors constater que notre validation fonctionne si le nom de la catégorie n’est pas présent dans sa description.
Dans le screencast ci-dessous, je vous montre comment j'ai mis en place la validation dans notre projet :
À vous de jouer
Mettons en place un endpoint d’administration des articles pour les administrateurs de la boutique. Certains contrôles doivent être effectués :
Le prix doit être supérieur à 1 €.
Le produit associé doit être actif.
Pour réaliser cela, vous pouvez partir de la branche P2C4_exercice. Elle contient déjà ce que nous venons de faire ensemble. Une solution est proposée sur la branche P2C4_solution.
En résumé
Utiliser un ModelViewset permet l’utilisation de l’ensemble des actions du CRUD.
La validation d’un champ se fait au travers de la méthode
validate_XXX
du serializer.La validation multichamp se fait au travers de la méthode
validate
du serializer.
La validation des données lors de la création et/ou la modification est un facteur clé pour assurer la cohérence des données, n’hésitez pas à valider toutes les données importantes. Maintenant, voyons comment tester les API externes avec les mocks – suivez-moi au prochain chapitre !