Фильтрация с использованием просмотров в 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)