首页 > 解决方案 > django-rest-framework,在列表响应中添加“包含”,带有分页

问题描述

使用我目前在 django-rest-framework 中开发的 API,我正在尝试实现类似于 json-api 标准中的功能的东西。给定书籍模型:

class Book(models.Model):
    title = models.CharField(max_length=255)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)

我想在 url 中包含一个参数,include让用户定义是否要在响应中包含author和模型。publisher额外的问题是我正在使用限制/偏移分页。因此,以下网址:

https://my-api-domain/api/books?limit=5&offset=0&include=authors

应该返回如下内容:

{
  "count": 152,
  "next": "https://my-api-domain/api/books/limit=5&offset=5&include=authors"
  "previous": null,
  "results": [
    {"id": 1, "title": "Book 1", "author": 1, "publisher": 18},
    {"id": 2, "title": "Book 2", "author": 2, "publisher": 26},
    ...
  ],
  "include": {
    "authors": [
      {"id": 1, "first_name": "Author", "last_name": "One"},
      {"id": 2, "first_name": "Author", "last_name": "Two"},
      ... for all author ids in paged `results` field above
    ]
  }
}

到目前为止,我的观点如下:

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = (IsAuthenticated,)
    pagination_class = LimitOffsetPagination
    filter_class = BookFilterSet
    filter_backends = [DjangoFilterBackend]

    def list(self, request, *args, **kwargs):
        # Grab include from url parameters
        include = request.query_params.get('include', None)
        # Apply incoming filters to books
        queryset = self.filter_queryset(self.get_queryset())
        # Apply pagination to queryset
        page = self.paginate_queryset(queryset)

        # Assemble include data if necessary
        if include is not None:
            include_data = {}
            includes = include.split(',')
            response_data = serializer.data
            response_data['includes'] = {}
            for entity in includes:
                if entity == 'authors':
                    authors = Author.objects.distinct().filter(book__in=page)
                    authors_serializer = AuthorSerializer(authors, many=True)
                    include_data['authors'] = authors_serializer.data
                elif entity == 'publishers':
                    publishers = Publisher.objects.distinct().filter(book__in=page)
                    publishers_serializer = PublisherSerializer(publishers, many=True)
                    include_data['publishers'] = publishers_serializer.data

        serializer = self.get_serializer(page, many=True)

        # PROBLEM: How can I inject include_data into my response below???
        return self.get_paginated_response(serializer.data)

所以我能够接受传入的请求,获取过滤和分页的查询,并提取正确的author序列publisher化数据。但是,我不确定通过分页响应如何注入这些数据(参见上述代码的最后两行)。

关于如何实现这一点的任何想法?在视图中执行此操作是否正确?或者我是否需要以某种方式获取序列化程序中的所有内容?如果我这样做,有没有办法在我的响应中将include数组作为数组的兄弟results(而不是include嵌入results)?

另外,我意识到有一个django-rest-framwork-jsonapi包,但是它也会以一种需要我对我的客户端代码进行重大更改的方式转换我的数据,这是我试图避免的。因此,我在这里提出的“轻”版本。

标签: pythondjangodjango-rest-framework

解决方案


添加

class CustomPagination(pagination.PageNumberPagination):
    def get_paginated_response(self, data, include_data):
        return Response({
            'links': {
                'next': self.get_next_link(),
                'previous': self.get_previous_link()
            },
            'count': self.page.paginator.count,
            'results': data,
            'include': include_data
        })

并配置其使用

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.CustomPagination',
    'PAGE_SIZE': 100
}

然后在你的列表方法中做

return self.get_paginated_response(serializer.data, include_data)

推荐阅读