Bitte beachten Sie diese drei Modelle:
class Movie(models.Model):
name = models.CharField(max_length=254, unique=True)
language = models.CharField(max_length=14)
synopsis = models.TextField()
class TimeTable(models.Model):
date = models.DateField()
class Show(models.Model):
day = models.ForeignKey(TimeTable)
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class Meta:
unique_together = ('day', 'time')
Und jeder von ihnen hat seine Serialisierer:
class MovieSerializer(serializers.HyperlinkedModelSerializer):
movie_id = serializers.IntegerField(read_only=True, source="id")
class Meta:
model = Movie
fields = '__all__'
class TimeTableSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = TimeTable
fields = '__all__'
class ShowSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Show
fields = '__all__'
Und ihre Router
router.register(r'movie-list', views.MovieViewSet)
router.register(r'time-table', views.TimeTableViewSet)
router.register(r'show-list', views.ShowViewSet)
Jetzt möchte ich alle TimeTable-Objekte (d. H. Datumsliste) abrufen, indem alle Show-Objekte nach einem bestimmten Filmobjekt gefiltert werden. Dieser Code scheint die Funktion zu sein und die Liste so zu erhalten, wie ich es möchte
m = Movie.objects.get(id=request_id)
TimeTable.objects.filter(show__movie=m).distinct()
Aber ich habe keine Ahnung, wie ich dies im Django-Rest-Framework verwenden sollte? Ich habe versucht, dies zu tun (was ziemlich sicher ist, dass es falsch ist) und ich bekomme einen Fehler:
views.py:
class DateListViewSet(viewsets.ModelViewSet, movie_id):
movie = Movie.objects.get(id=movie_id)
queryset = TimeTable.objects.filter(show__movie=movie).distinct()
serializer_class = TimeTableSerializer
urls.py:
router.register(r'date-list/(?P<movie_id>.+)/', views.DateListViewSet)
error:
klasse DateListViewSet (viewsets.ModelViewSet, movie_id): NameError: Name 'movie_id' ist nicht definiert
Wie kann ich mithilfe von Viewsets im Django-Rest-Framework filtern? Oder, wenn es einen anderen bevorzugten Weg gibt, als diesen bitte aufzuführen. Vielen Dank.
ModelViewSet
setzt von Entwurf voraus, dass Sie eine CRUD implementieren möchten (Erstellen, Aktualisieren, Löschen).
Außerdem gibt es eine ReadOnlyModelViewSet
, die nur die GET
-Methode implementiert, um nur Endpunkte zu lesen.
Für Movie
- und Show
-Modelle ist ModelViewSet
oder ReadOnlyModelViewSet
eine gute Wahl, ob Sie CRUD implementieren möchten oder nicht.
Aber ein separates ViewSet
für eine verwandte Abfrage eines TimeTable
, das den Zeitplan eines Movie
-Modells beschreibt, sieht nicht so gut aus.
Ein besserer Ansatz wäre es, diesen Endpunkt direkt auf ein MovieViewSet
zu setzen. DRF lieferte es von @detail_route
und @list_route
Dekorateuren.
from rest_framework.response import Response
from rest_framework.decorators import detail_route
class MovieViewSet(viewsets.ModelViewset):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
@detail_route()
def date_list(self, request, pk=None):
movie = self.get_object() # retrieve an object by pk provided
schedule = TimeTable.objects.filter(show__movie=movie).distinct()
schedule_json = TimeTableSerializer(schedule, many=True)
return Response(schedule_json.data)
Dieser Endpunkt ist über eine movie-list/:id/date_list
-URL verfügbar
Dokumente über zusätzliche Routen
Der Fehler
klasse DateListViewSet (viewsets.ModelViewSet, movie_id): NameError: Name 'movie_id' ist nicht definiert
passiert, weil movie_id
als übergeordnete Klasse von DataListViewSet übergeben wird und nicht als Parameter, wie Sie es sich vorgestellt haben
Dieses Beispiel in der Dokumentation sollte genau das sein, was Sie suchen.
Passen Sie Ihre URL an:
url(r'date-list/(?P<movie_id>.+)/', views.DateListView.as_view())
Passen Sie Ihr Modell an:
class Show(models.Model):
day = models.ForeignKey(TimeTable, related_name='show')
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class Meta:
unique_together = ('day', 'time')
Ihre Sicht würde so aussehen:
class DateListView(generics.ListAPIView):
serializer_class = TimeTableSerializer
def get_queryset(self):
movie = Movie.objects.get(id=self.kwargs['movie_id'])
return TimeTable.objects.filter(show__movie=movie).distinct()
Eine andere Möglichkeit wäre:
Passen Sie Ihre URL an:
router.register(r'date-list', views.DateListViewSet)
Passen Sie Ihr Modell an:
class Show(models.Model):
day = models.ForeignKey(TimeTable, related_name='show')
time = models.TimeField(choices=CHOICE_TIME)
movie = models.ForeignKey(Movie)
class Meta:
unique_together = ('day', 'time')
Ihre Sicht würde so aussehen:
class DateListViewSet(viewsets.ModelViewSet):
serializer_class = TimeTableSerializer
queryset = TimeTable.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('show__movie_id')
So können Sie Anfragen stellen wie:
http://example.com/api/date-list?show__movie_id=1
Siehe Dokumentation
Registrieren Sie Ihre Route als
router.register(r'date-list', views.DateListViewSet)
Ändern Sie jetzt Ihre Ansichten wie unten gezeigt.
class DateListViewSet(viewsets.ModelViewSet):
queryset = TimeTable.objects.all()
serializer_class = TimeTableSerializer
lookup_field = 'movie_id'
def retrieve(self, request, *args, **kwargs):
movie_id = kwargs.get('movie_id', None)
movie = Movie.objects.get(id=movie_id)
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
Verwenden Sie eine Abrufmethode, die alle GET-Anforderungen an den Endpunkt/date-list/<id>/
abruft.
Vorteil ist, dass Sie die Serialisierung nicht explizit behandeln müssen und eine Antwort zurückgeben müssen Sie machen ViewSet, um diesen schwierigen Teil zu erledigen. Wir aktualisieren nur das zu serialisierende Abfrageset und der Rest erledigt den Rest.
Da ModelViewSet implementiert ist als,
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
Seine Implementierung umfasst die folgenden Methoden (HTTP-Verb und Endpunkt in Klammer)
list()
(GET /date-list/
) create()
(POST /date-list/
) retrieve()
(GET date-list/<id>/
) update()
(PUT /date-list/<id>/
) partial_update()
(PATCH, /date-list/<id>/
destroy()
(DELETE /date-list/<id>/
) Wenn Sie nur die retrieve()
( GET-Anforderungen an den Endpunkt date-list/<id>/
) implementieren möchten, können Sie dies anstelle von `ModelViewSet) tun
from rest_framework import mixins, views
class DateListViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
queryset = TimeTable.objects.all()
serializer_class = TimeTableSerializer
lookup_field = 'movie_id'
def retrieve(self, request, *args, **kwargs):
movie_id = kwargs.get('movie_id', None)
movie = Movie.objects.get(id=movie_id)
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
Ivan Semochkin hat die richtige Antwort, aber der Detaildekorateur ist veraltet. Es wurde durch Aktionsdekorateur ersetzt.
from rest_framework.decorators import action
class MovieViewSet(viewsets.ModelViewset):
@action(detail=True)
def date_list(self, request, pk=None):
movie = self.get_object() # retrieve an object by pk provided
schedule = TimeTable.objects.filter(show__movie=movie).distinct()
schedule_json = TimeTableSerializer(schedule, many=True)
return Response(schedule_json.data)
Um die Antwort von @ all-is-vanity zu verbessern, können Sie movie_id
explizit als Parameter in der Funktion retrieve
verwenden, da Sie die Klasseseigenschaft lookup_field
überschreiben:
def retrieve(self, request, movie_id=None):
movie = Movie.objects.get(id=movie_id)
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
Sie können auch self.get_object()
aufrufen, um das Objekt abzurufen:
def retrieve(self, request, movie_id=None):
movie = self.get_object()
self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
return super(DateListViewSet, self).retrieve(request, *args, **kwargs)