Фильтрация с использованием просмотров в django rest framework

Пожалуйста, рассмотрите эти три модели:

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

И каждый из них имеет свои сериализаторы:

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

И их маршрутизаторы

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

Теперь я хотел бы получить все объекты TimeTable (т.е. список дат), путем фильтрации всех объектов Show определенным объектом фильма. Этот код, похоже, работает и получает список, как я хочу.

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

Но я понятия не имею, как использовать это в django rest framework? Я пробовал делать это (я уверен, что это неправильно), и я получаю сообщение об ошибке:

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)

Ошибка:

класс DateListViewSet (viewsets.ModelViewSet, movie_id): NameError: имя 'movie_id' не определено

Как я могу фильтровать использование просмотров в django rest framework? Или, если есть другой предпочтительный способ, пожалуйста, перечислите его. Спасибо.

Ответ 1

ModelViewSet по дизайну предполагает, что вы хотите реализовать CRUD (создать, обновить, удалить)
Существует также ReadOnlyModelViewSet, который реализует только метод GET для чтения только конечных точек.
Для моделей Movie и Show a ModelViewSet или ReadOnlyModelViewSet - хороший выбор, хотите ли вы реализовать CRUD или нет.
Но отдельный ViewSet для связанного запроса TimeTable, который описывает график модели Movie, выглядит не так хорошо.
Лучшим подходом было бы прямое подключение этой конечной точки к MovieViewSet. DRF предоставил его декораторам @detail_route и @list_route.

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)

Эта конечная точка будет доступна с помощью movie-list/:id/date_list url Документы о дополнительных маршрутах

Ответ 2

Ошибка

класс DateListViewSet (viewsets.ModelViewSet, movie_id): NameError: имя 'movie_id' не определено

происходит потому, что movie_id передается как родительский класс DataListViewSet, а не как параметр, который вы себе представляли

Этот пример в документации должен быть тем, что вы ищете.

Настройте URL-адрес:

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

Отрегулируйте модель:

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

Ваше мнение будет выглядеть так:

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

Другой способ сделать это:

Настройте URL-адрес:

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

Отрегулируйте модель:

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

Ваше мнение будет выглядеть так:

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

Это позволит вам делать такие запросы, как:

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

См. документация

Ответ 3

Зарегистрируйте свой маршрут как

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

теперь измените свой вид, как показано ниже,

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)

Используйте метод извлечения, который будет соответствовать любым запросам GET для конечной точки /date-list/<id>/.

Преимущество в том, что вам не нужно явно обрабатывать сериализацию и возвращаемый ответ, чтобы вы делали ViewSet для выполнения этой сложной части. Мы только обновляем запрос, который должен быть сериализован, а остальная среда остального. Остальные.

Так как ModelViewSet реализован как

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

В его реализацию входят следующие методы (HTTP-глагол и конечная точка на скобке)

  • 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>/)

Если вы хотите только реализовать retrieve() (запросы GET для конечной точки date-list/<id>/), вы можете сделать это вместо "ModelViewSet" ),

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

Чтобы улучшить ответ "все-есть-тщеславие", вы можете явно использовать movie_id в качестве параметра в функции retrieve, поскольку вы переопределяете свойство класса lookup_field:

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)

Вы также можете вызвать self.get_object(), чтобы получить объект:

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)