Ich möchte mehrere Instanzen mit dem Django Rest Framework mit einem API-Aufruf speichern und aktualisieren. Angenommen, ich habe ein "Klassenzimmer" -Modell, das mehrere "Lehrer" haben kann. Wenn ich mehrere Lehrer erstellen und später alle Klassenzahlen aktualisieren möchte, wie würde ich das tun? Muss ich für jeden Lehrer einen API-Aufruf durchführen?
Ich weiß, dass wir derzeit keine verschachtelten Modelle speichern können, aber ich würde gerne wissen, ob wir sie auf Lehrerebene speichern können. Vielen Dank!
Antworten:
Ich weiß, dass dies vor einiger Zeit gefragt wurde, aber ich habe es gefunden, als ich versucht habe, dies selbst herauszufinden.
Wenn Sie
many=True
beim Instanziieren der Serializer-Klasse für ein Modell übergeben, können mehrere Objekte akzeptiert werden.Dies wird hier in den Django Rest Framework-Dokumenten erwähnt
Für meinen Fall sah meine Ansicht folgendermaßen aus:
class ThingViewSet(viewsets.ModelViewSet): """This view provides list, detail, create, retrieve, update and destroy actions for Things.""" model = Thing serializer_class = ThingSerializer
Ich wollte nicht wirklich eine Ladung Boilerplate schreiben, um die Instanziierung des Serializers direkt zu steuern und zu übergeben
many=True
, also überschreibe ich in meiner Serializer-Klasse__init__
stattdessen Folgendes :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', )
Senden von Daten an die Listen-URL für diese Ansicht im folgenden Format:
[ {'loads':'foo','of':'bar','fields':'buzz'}, {'loads':'fizz','of':'bazz','fields':'errrrm'} ]
Erstellt zwei Ressourcen mit diesen Details. Welches war schön.
quelle
Ich bin zu einem ähnlichen Ergebnis gekommen wie Daniel Albarral, aber hier ist eine prägnantere Lösung:
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)
quelle
Hier ist eine weitere Lösung: Sie müssen Ihre Serializer-
__init__
Methode nicht überschreiben . Überschreiben Sie einfach die ModelViewSet-'create'
Methode Ihrer Ansicht . Hinweismany=isinstance(request.data,list)
. Hier,many=True
wenn Sie ein Array von Objekten zum ErstellenFalse
senden und wenn Sie nur das eine senden. Auf diese Weise können Sie sowohl einen Artikel als auch eine Liste speichern!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)
quelle
transaction.atomic()
Block hinzufügen , um sicherzustellen, dass alle Elemente hinzugefügt werdenExpected a dictionary, but got list.
Fehler : in der akzeptierten Antwort gestoßen und dieser hat ihn für mich behoben. Vielen Dank.Ich konnte nicht genau herausfinden, ob die Anfrage.DATA von einem Wörterbuch in ein Array konvertiert werden sollte - was meine Fähigkeit, mit Tom Manterfields Lösung zu arbeiten, einschränkte. Hier ist meine Lösung:
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)
Und dann führe ich das Äquivalent auf dem Client aus:
var things = { "things":[ {'loads':'foo','of':'bar','fields':'buzz'}, {'loads':'fizz','of':'bazz','fields':'errrrm'}] } thingClientResource.post(things)
quelle
many=True
in denget_serializer
AnrufIch denke, der beste Weg, um die vorgeschlagene Architektur des Frameworks zu respektieren, wird darin bestehen, ein Mixin wie dieses zu erstellen:
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)
Anschließend können Sie das CreateModelMixin von ModelViewSet folgendermaßen überschreiben:
class <MyModel>ViewSet(CreateListModelMixin, viewsets.ModelViewSet): ... ...
Jetzt können Sie im Client folgendermaßen arbeiten:
var things = [ {'loads':'foo','of':'bar','fields':'buzz'}, {'loads':'fizz','of':'bazz','fields':'errrrm'} ] thingClientResource.post(things)
oder
var thing = { 'loads':'foo','of':'bar','fields':'buzz' } thingClientResource.post(thing)
BEARBEITEN:
Wie Roger Collins in ihrer Antwort vorschlägt, ist es klüger, die Methode get_serializer zu überschreiben als die Methode 'create'.
quelle
Sie können einfach die überschreiben
get_serializer
Methode in Ihrer APIView und passierenmany=True
inget_serializer
etwa so die Basisansicht: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)
quelle
data
Schlüsselwortargument übergeben wird, müssen Sie ihn aufrufen,.is_valid()
bevor Sie versuchen, auf die serialisierte.data
Darstellung zuzugreifen . Sie sollten entweder.is_valid()
zuerst anrufen oder.initial_data
stattdessen zugreifen .from rest_framework.fields import empty def get_serializer(self, instance=None, data=empty, many=False, partial=False): return super(SomeAPIView, self).get_serializer(instance=instance, data=data, many=True, partial=partial)
Die Generic Views Seite in Django REST - Framework in der Dokumentation besagt , dass die ListCreateAPIView generische Ansicht " " "für Lese- / Schreibendpunkte verwendet wird, um eine Sammlung von Modellinstanzen darzustellen".
Dort würde ich anfangen zu suchen (und ich werde es tatsächlich tun, da wir diese Funktionalität bald auch in unserem Projekt benötigen werden).
Beachten Sie auch, dass die Beispiele auf der Seite "Allgemeine Ansichten" zufällig verwendet werden
ListCreateAPIView
.quelle
Ich habe mir ein einfaches Beispiel ausgedacht
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)
Diese Zeile ist die eigentliche Logik von 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)
Wenn Sie mit many = True verwechselt werden, sehen Sie dies
Wenn wir Daten senden, werden diese in
list
etwa so sein -[ { "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" } ]
quelle
Die einfachste Methode, die mir begegnet ist:
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)
quelle