-
Notifications
You must be signed in to change notification settings - Fork 1
Tuto django et uniformisation
Il y a trois éléments à distinguer quand on manipule Django avec REST, le modèle, le serializer, et la vue. Voici un guide qui permet de définir une utilisation basique mais souvent très très souvent largement suffisante pour notre utilisation.
Ce guide sert aussi d'harmonisation des pratiques, de sorte à ne pas créer plus de classe qu'il n'en faut, ou placer certaines fonctions inutiles, ou au mauvais endroit. A retenir de manière générale :
- Les droits sont à placer côté modèle
- La validation des données est à placer côté serializer
Le Modèle (Doc)
Le modèle sert à donner a Django la structure de nos ressources. Il se charge tout seul de manipuler la base de donnée. Le modèle est la classe principale de la ressource. Sur le modèle on placera :
- Les propriétés de la ressource (pas besoin de définir un id, il existe par défaut sur django)
- Éventuellement Des fonctions de manipulation de la ressource, et de ses attributs
- Les droits de la ressource (voir le paragraphe DRYPermission)
Le schéma de base d'une ressource est :
from django.db import models
class MonModel(models.Model):
################################################################
# CONSTANTS #
################################################################
# Liste de constantes à utiliser en cas de choix, par exemple ici, le champ type
TYPE_1 = 0
TYPE_2 = 2
################################################################
# FIELDS #
################################################################
# Liste des champs de l'objet
name = models.CharField(max_length=254)
group = models.ForeignKey('MonAutreModele')
type = models.PositiveSmallIntegerField(default=TYPE_0)
################################################################
# PERMISSIONS #
################################################################
def has_object_read_permission(self, request):
return True
@staticmethod
def has_read_permission(request):
return True
def has_object_write_permission(self, request):
return True
@staticmethod
def has_write_permission(request):
return True
Le serializer (Doc)
Il se charge d'effectuer la transformation : représentation <=> modèle django Ici, il se chargera de transformer une ressource django en json pour l'envoi, et convertir du json en ressource django pour modifier la base de donnée. Cette classe est souvent très vide puisqu'il existe une base RESTFramework qui permet de créer automatiquement le serializer à partir du modèle Django. Important, il ne peut pas automatiquement décrire une relation ! Il faut alors lui expliquer comment il est censé traduire une relation, en code JSON. Le plus souvent, il s'agit du cas OneToMany, où il suffit de lui dire de donner la clé primaire de l'objet pointé.
Exemple type d'un sérializer :
from rest_framework import serializers
from sigma_core.models.monmodel import MonModel
from sigma_core.models.monmodel import MonAutreModel
class MonSerializer(serializers.ModelSerializer):
class Meta:
model = MonModel
# On precise que la relation MonModel.group doit se traduire par la clef primaire de l'objet pointe
group = serializers.PrimaryKeyRelatedField(queryset=MonAutreModel.objects.all())
La vue (Doc)
Une vue est une fonction qui gère une requête, et renvoie la réponse appropriée.
De manière générale, le protocole de communication étant standardisé (REST), on peut laisser le REST-framework faire les choses pour nous à l'aide du ModelViewset
. A partir d'un modèle, et d'un serializer, il crée automatiquement les vues correspondantes au diverses actions.
Les diverses actions sont :
- Actions de lecture (
read
)- La récupération d'une liste d'élement (
list
) - La récupération d'un élément en particulier (
retreive
)
- La récupération d'une liste d'élement (
- Actions d'écriture (
write
)- La création (
create
) - L'édition d'un élément en particulier (
update
) - La suppression d'un élement particulier (
destroy
) On distingue aussi les actions globales (list, create) qui ne prennent pas de paramètre, et les actions objects (retreive, update, destroy) qui prennent un identifieur (appelépk
) de ressource.
- La création (
Il est possible de créer une route supplémentaire en utilisant un décorateur.
On en utilisant @detail_route
on créer une route qui prend un paramètre, et en utilisant @list_route
, une route qui ne prend aucun paramètre. Ces deux décorateurs prennent entre autre un argument, la méthode HTTP correspondant à la route, soit get, soit post. (ne pas confondre la méthode HTTP qui définie comment passer les données, et l'action qui définie qu'en faire)
Le nom de la vue est alors le nom de la fonction.
En pratique, dans une telle fonction, on récupère un ou des objets selon un critère, on sérialise le/les objets, puis on les retourne dans une Response
.
Important, la gestion des droits se fait de manière automatisée en utilisant DRYPermission. Il faut pour cela préciser à la vue d'utiliser DRYPermission. Je parlerais de la gestion des droits dans une autre partie.
Exemple type d'une vue :
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from dry_rest_permissions.generics import DRYPermissions
from sigma_core.models.monmodel import MonModel
from sigma_core.serializers.monserializer import MonSerializer
class MaViewset(viewsets.ModelViewSet):
queryset = MonModel.objects.all()
serializer_class = MonSerializer
permission_classes = [IsAuthenticated, DRYPermissions]
@detail_route(methods=['get'])
def objet_special(self, request, pk=None):
# [...]
# truc type :
data = [...]
serializer = UnSerializer(data)
return Response(serializer.data)
# s'il manque des droits
return Response("Pas possible", status=status.HTTP_403_FORBIDDEN)
Une fois la vue créée, il faut dire a Django de faire correspondre une url avec une vue. Encore une fois, grace à REST qui standardise le tout, le processus est simplifié. Il suffit de lui donner une Viewset et il va générer automatiquement les url correspondantes.
Dans le cas d'une action monaction()
particulière créée avec @detail_route
ou @list_route
dans un viewset "mavue", l'url correspondant est alors mavue/monaction
ou /?pk/mavue
Ligne à insérer dans le fichier sigma/url.py
:
router = routers.DefaultRouter() # deja present au debut du fichier
from sigma_core.views.maviewset import MaViewset
router.register(r'mavue', MaViewset) # genere l'url /mavue/... avec toutes les routes définies dans la viewset
Les droits (Doc)
La gestion des droits peut se faire de moult manières. De base, django le permet avec des fonctions à définir dans le modèle. Mais elles sont souvent assez limitée et inadaptée, on préfère donc un système de droit basée sur la vue, qui inclue donc la notion d'utilisateur connecté. Cela reste cependant souvent incomplet. D'où l'utilisation de DRYPermission !
Ce framework permet de définir très facilement les permissions. Mode d'emploi : on dit à la vue d'utiliser le système de droit de DRYPermission (comme fait dans la partie vue) Puis on définit dans le modèle, un certain nombre de fonctions de permissions.
On distingue deux type de fonction de permission :
- La permission globale, qui correspond à une action globale (type create, list, mais aussi toute
@list_route
). On définit alors une fonction statique@staticmethod def has_XXX_permission(request)
dans le modèle. XXX est le nom de l'action pour laquelle on veut définir la permission. Cette méthode sera automatiquement appelée par la vue lorsque la route demande l'action XXX, et la fonction d'action de la Viewset ne sera exécutée que sihas_XXX_permission
retourneTrue
. La fonction est statique, puis que l'action est globale, elle ne dépend d'aucun object en particulier. On a bien sur accès à l'utilisateur viarequest.user
mais aussi aux données de requêtes viarequest.data
- La permission objet, qui correspond à une action objet (type retreive, update, destroy, mais aussi toute
@detail_route). On définit alors une méthode
def has_object_XXX_permission(self, request)dans le modèle, où XXX est le nom de l'action pour laquelle on veut définir la permission. Même fonctionnement que pour la permission globale, la différence est que la fonction est cette fois ci une méthode. On a donc accès à l'objet concerné par la requête, via
self`.
En plus des actions de base, DRY définit deux types de permission qui permette d'englober les autres pour réduire la quantité de code. Il y a les actions de type read
: list, retreive, et toute route définie comme utilisant la méthode GET ; et les actions de type write
: create, update, destroy et toute route définie comme utilisant la méthode POST.