Je souhaite enregistrer et mettre à jour plusieurs instances à l'aide de Django Rest Framework avec un seul appel d'API. Par exemple, supposons que j'ai un modèle "Salle de classe" pouvant avoir plusieurs "Enseignants". Si je voulais créer plusieurs enseignants et mettre à jour tous leurs numéros de classe, comment pourrais-je le faire? Dois-je faire un appel API pour chaque enseignant?
Je sais qu'actuellement nous ne pouvons pas sauvegarder les modèles imbriqués, mais j'aimerais savoir si nous pouvons le sauvegarder au niveau de l'enseignant . Merci!
Je sais que cela a été demandé il y a quelque temps maintenant, mais je l'ai trouvé en essayant de comprendre cela moi-même.
Il s'avère que si vous passez many=True
lors de l'instanciation de la classe de sérialiseur pour un modèle, celui-ci peut alors accepter plusieurs objets.
Ceci est mentionné ici dans la documentation de Django Rest Framework
Pour mon cas, ma vue ressemblait à ceci:
class ThingViewSet(viewsets.ModelViewSet):
"""This view provides list, detail, create, retrieve, update
and destroy actions for Things."""
model = Thing
serializer_class = ThingSerializer
Je ne voulais pas vraiment écrire une charge de boiler, juste pour avoir un contrôle direct sur l'instanciation du sérialiseur et passer many=True
, donc dans ma classe de sérialiseur, je remplace le __init__
à la place:
class ThingSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
many = kwargs.pop('many', True)
super(ThingSerializer, self).__init__(many=many, *args, **kwargs)
class Meta:
model = Thing
fields = ('loads', 'of', 'fields', )
Enregistrement des données dans l'URL de la liste pour cette vue au format:
[
{'loads':'foo','of':'bar','fields':'buzz'},
{'loads':'fizz','of':'bazz','fields':'errrrm'}
]
Créé deux ressources avec ces détails. Qui était Nice.
Je suis arrivé à la même conclusion que Daniel Albarral, mais voici une solution plus succincte:
class CreateListModelMixin(object):
def get_serializer(self, *args, **kwargs):
""" if an array is passed, set serializer to many """
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
Je n'arrivais pas à comprendre comment obtenir la requête.DATA pour convertir un dictionnaire en tableau - ce qui limitait ma capacité à la solution de Tom Manterfield. Voici ma solution:
class ThingSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
many = kwargs.pop('many', True)
super(ThingSerializer, self).__init__(many=many, *args, **kwargs)
class Meta:
model = Thing
fields = ('loads', 'of', 'fields', )
class ThingViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet ):
queryset = myModels\
.Thing\
.objects\
.all()
serializer_class = ThingSerializer
def create(self, request, *args, **kwargs):
self.user = request.user
listOfThings = request.DATA['things']
serializer = self.get_serializer(data=listOfThings, files=request.FILES, many=True)
if serializer.is_valid():
serializer.save()
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Et puis je lance l'équivalent de ceci sur le client:
var things = {
"things":[
{'loads':'foo','of':'bar','fields':'buzz'},
{'loads':'fizz','of':'bazz','fields':'errrrm'}]
}
thingClientResource.post(things)
Voici une autre solution: vous n'avez pas besoin de remplacer votre méthode de sérialiseur __init__
. Remplacez simplement la méthode 'create'
de votre vue (ModelViewSet). Remarquez many=isinstance(request.data,list)
. Ici many=True
lorsque vous envoyez un tableau d'objets à créer et False
lorsque vous n'en envoyez qu'un. De cette façon, vous pouvez enregistrer un élément et une liste!
from rest_framework import status, viewsets
from rest_framework.response import Response
class ThingViewSet(viewsets.ModelViewSet):
"""This view snippet provides both list and item create functionality."""
#I took the liberty to change the model to queryset
queryset = Thing.objects.all()
serializer_class = ThingSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=isinstance(request.data,list))
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Vous pouvez simplement écraser la méthode get_serializer
dans votre APIView et passer many=True
dans get_serializer
de la vue de base comme suit:
class SomeAPIView(CreateAPIView):
queryset = SomeModel.objects.all()
serializer_class = SomeSerializer
def get_serializer(self, instance=None, data=None, many=False, partial=False):
return super(SomeAPIView, self).get_serializer(instance=instance, data=data, many=True, partial=partial)
Je pense que le meilleur moyen de respecter l'architecture proposée du framework sera de créer un mix comme celui-ci:
class CreateListModelMixin(object):
def create(self, request, *args, **kwargs):
"""
Create a list of model instances if a list is provides or a
single model instance otherwise.
"""
data = request.data
if isinstance(data, list):
serializer = self.get_serializer(data=request.data, many=True)
else:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
Ensuite, vous pouvez remplacer le CreateModelMixin de ModelViewSet comme ceci:
class <MyModel>ViewSet(CreateListModelMixin, viewsets.ModelViewSet):
...
...
Maintenant, dans le client, vous pouvez travailler comme ceci:
var things = [
{'loads':'foo','of':'bar','fields':'buzz'},
{'loads':'fizz','of':'bazz','fields':'errrrm'}
]
thingClientResource.post(things)
ou
var thing = {
'loads':'foo','of':'bar','fields':'buzz'
}
thingClientResource.post(thing)
MODIFIER:
Comme le suggère Roger Collins dans sa réponse est plus habile pour écraser la méthode get_serializer que la méthode 'create'.
La Vues génériques page dans Django REST La documentation de Framework indique que la vue ListCreateAPIView generic est "utilisée pour les noeuds finaux en lecture-écriture représentant une collection d'instances de modèle".
C'est là que je commencerais à chercher (et je vais le faire, car nous aurons bientôt besoin de cette fonctionnalité dans notre projet).
Notez également que les exemples de la page Vues génériques utilisent ListCreateAPIView
.
Je suis venu avec un exemple simple dans post
Serializers.py
from rest_framework import serializers
from movie.models import Movie
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = [
'popularity',
'director',
'genre',
'imdb_score',
'name',
]
Views.py
from rest_framework.response import Response
from rest_framework import generics
from .serializers import MovieSerializer
from movie.models import Movie
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
class MovieList(generics.ListCreateAPIView):
queryset = Movie.objects.all().order_by('-id')[:10]
serializer_class = MovieSerializer
permission_classes = (IsAuthenticated,)
def list(self, request):
queryset = self.get_queryset()
serializer = MovieSerializer(queryset, many=True)
return Response(serializer.data)
def post(self, request, format=None):
data = request.data
if isinstance(data, list): # <- is the main logic
serializer = self.get_serializer(data=request.data, many=True)
else:
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Ces lignes représentent la logique réelle de Multiple Instance -
data = request.data
if isinstance(data, list): # <- is the main logic
serializer = self.get_serializer(data=request.data, many=True)
else:
serializer = self.get_serializer(data=request.data)
Si vous êtes confus avec plusieurs = True, voyez ceci
Lorsque nous envoyons des données, elles seront à l'intérieur de list
, un peu comme ceci -
[
{
"popularity": 84.0,
"director": "Stanley Kubrick",
"genre": [
1,
6,
10
],
"imdb_score": 8.4,
"name": "2001 : A Space Odyssey"
},
{
"popularity": 84.0,
"director": "Stanley Kubrick",
"genre": [
1,
6,
10
],
"imdb_score": 8.4,
"name": "2001 : A Space Odyssey"
}
]
La méthode la plus simple que j'ai rencontrée:
def post(self, request, *args, **kwargs):
serializer = ThatSerializer(data=request.data, many=isinstance(request.data, list))
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)