使用django rest框架中的视图集进行过滤

时间:2017-03-03 15:34:23

标签: django 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)        

现在我想通过过滤特定电影对象的所有Show对象来获取所有TimeTable对象(即日期列表)。这段代码似乎正在工作并获得我想要的列表

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

但我不知道如何在django rest框架中使用它?我尝试这样做(我很确定它是错的),我收到错误:

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)

错误:

  

class DateListViewSet(viewsets.ModelViewSet,movie_id):   NameError:名称'movie_id'未定义

如何在django rest框架中使用viewset进行过滤?或者,如果有任何其他优先方式,请列出来。谢谢。

5 个答案:

答案 0 :(得分:15)

设计

ModelViewSet假定您要实现CRUD(创建,更新,删除)
还有一个ReadOnlyModelViewSet仅使用GET方法来只读取端点 对于MovieShow模型,ModelViewSetReadOnlyModelViewSet是一个不错的选择,无论您是否想要实现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网址提供 Docs about extra routes

答案 1 :(得分:6)

将您的路线注册为

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)

使用检索方法,该方法将匹配任何对端点 /date-list/<id>/ 的GET请求。

优点是你不必显式处理序列化和返回响应,你使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()(获取/date-list/
  • create()(POST /date-list/
  • retrieve()(获取date-list/<id>/
  • update()(PUT /date-list/<id>/
  • partial_update()(PATCH,/date-list/<id>/
  • destroy()(删除/date-list/<id>/

如果您只想实现retrieve()(对端点date-list/<id>/ GET请求),则可以执行此操作而不是“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)

答案 2 :(得分:5)

错误

  

class DateListViewSet(viewsets.ModelViewSet,movie_id):NameError:未定义名称'movie_id'

发生是因为movie_id作为DataListViewSet的父类传递而不是您想象的参数

documentation中的这个示例应该是您正在寻找的。

调整您的网址:

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

另一种方法是:

调整您的网址:

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

请参阅documentation

答案 3 :(得分:1)

Ivan Semochkin的答案正确,但是详细信息装饰器已弃用。它由action decorator代替。

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)

答案 4 :(得分:0)

To improve @all-is-vanity answer, you can explicitly use movie_id as a parameter in the retrieve function since you are overriding the lookup_field class property:

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)

You can also call self.get_object() to get the 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)