web-dev-qa-db-de.com

Filtern mit Viewsets im Django-Rest-Framework

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.

14

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

12
Ivan Semochkin

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

5
Hugo Brilhante

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)
4

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)
0
LonnyT

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)
0
busla