Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filtering using viewsets in django rest framework

Please consider these three models:

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

And each of them has their serializers:

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__'

And their routers

router.register(r'movie-list', views.MovieViewSet)
router.register(r'time-table', views.TimeTableViewSet)
router.register(r'show-list', views.ShowViewSet)        

Now I would like to get all the TimeTable objects (i.e. date list) by filtering all the Show objects by a specific movie object. This code seems to be the working and getting the list like I want it

m = Movie.objects.get(id=request_id)
TimeTable.objects.filter(show__movie=m).distinct()

But I have no clue how to use this in django rest framework? I tried doing this way (which I am pretty sure its wrong), and I am getting error:

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:

class DateListViewSet(viewsets.ModelViewSet, movie_id): NameError: name 'movie_id' is not defined

How can I filter using viewsets in django rest framework? Or if there is any other prefered way than please list it out. Thank you.

like image 257
Benjamin Smith Max Avatar asked Mar 03 '17 15:03

Benjamin Smith Max


2 Answers

ModelViewSet by design assumes that you want to implement a CRUD(create, update, delete)
There is also a ReadOnlyModelViewSet which implements only the GET method to read only endpoints.
For Movie and Show models, a ModelViewSet or ReadOnlyModelViewSet is a good choice whether you want implement CRUD or not.
But a separate ViewSet for a related query of a TimeTable which describes a Movie model's schedule doesn't looks so good.
A better approach would be to put that endpoint to a MovieViewSet directly. DRF provided it by @detail_route and @list_route decorators.

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)

This endpoint will be available by a movie-list/:id/date_list url
Docs about extra routes

like image 110
Ivan Semochkin Avatar answered Sep 19 '22 06:09

Ivan Semochkin


The error

class DateListViewSet(viewsets.ModelViewSet, movie_id): NameError: name 'movie_id' is not defined

happens because movie_id is being passed as parent class of DataListViewSet and not as parameter as you imagined

This example in the documentation should be what you are looking for.

Adjust your URL:

url(r'date-list/(?P<movie_id>.+)/', views.DateListView.as_view())

Adjust your Model:

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

Your view would look like this:

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

Another way to do it would be:

Adjust your URL:

router.register(r'date-list', views.DateListViewSet)

Adjust your Model:

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

Your view would look like this:

class DateListViewSet(viewsets.ModelViewSet):
     serializer_class = TimeTableSerializer
     queryset = TimeTable.objects.all()
     filter_backends = (filters.DjangoFilterBackend,)
     filter_fields = ('show__movie_id')

Which will allow you to make requests such as:

http://example.com/api/date-list?show__movie_id=1

See documentation

like image 43
Hugo Brilhante Avatar answered Sep 21 '22 06:09

Hugo Brilhante