You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django REST Framework分页列表接口添加include参数实现问询

Lightweight JSON:API-style include Implementation for Django REST Framework

Let's walk through a simple, package-free solution to add the include functionality you want, while keeping it as a sibling to the results field in your paginated response.

First, make sure you have serializers for your Author and Publisher models (if you don't already):

# serializers.py
from rest_framework import serializers
from .models import Book, Author, Publisher

class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = ['id', 'name']  # Add any other fields you want to expose

class PublisherSerializer(serializers.ModelSerializer):
    class Meta:
        model = Publisher
        fields = ['id', 'name']  # Adjust fields as needed

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['id', 'title', 'author_id', 'publisher_id']  # Keep only IDs in results

We need to modify the default pagination to keep the include parameter in the next/previous links:

# pagination.py
from rest_framework.pagination import LimitOffsetPagination
from urllib.parse import urlparse, urlencode

class CustomLimitOffsetPagination(LimitOffsetPagination):
    def get_next_link(self):
        if self.offset + self.limit >= self.count:
            return None
        
        url_parts = list(urlparse(self.request.build_absolute_uri()))
        query_params = dict(urlparse.parse_qsl(url_parts[4]))
        
        query_params.update({
            'offset': self.offset + self.limit,
            'limit': self.limit
        })
        
        # Preserve the include parameter if present
        include_param = self.request.query_params.get('include')
        if include_param:
            query_params['include'] = include_param
        
        url_parts[4] = urlencode(query_params)
        return urlparse.urlunparse(url_parts)
    
    def get_previous_link(self):
        if self.offset <= 0:
            return None
        
        url_parts = list(urlparse(self.request.build_absolute_uri()))
        query_params = dict(urlparse.parse_qsl(url_parts[4]))
        
        query_params.update({
            'offset': max(self.offset - self.limit, 0),
            'limit': self.limit
        })
        
        # Preserve the include parameter if present
        include_param = self.request.query_params.get('include')
        if include_param:
            query_params['include'] = include_param
        
        url_parts[4] = urlencode(query_params)
        return urlparse.urlunparse(url_parts)

Step 3: Modify the View to Handle include

This is where we'll collect and inject the related data into the paginated response. We'll override the get_paginated_response method to add the include field:

# views.py
from rest_framework.generics import ListAPIView
from .models import Book, Author, Publisher
from .serializers import BookSerializer, AuthorSerializer, PublisherSerializer
from .pagination import CustomLimitOffsetPagination

class BookListView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    pagination_class = CustomLimitOffsetPagination

    def get_paginated_response(self, data):
        # Get the base paginated response
        response = super().get_paginated_response(data)
        
        # Parse and validate the include parameter
        include_param = self.request.query_params.get('include', '')
        requested_includes = [item.strip() for item in include_param.split(',') if item.strip()]
        
        # Map plural include params to their model/serializer pairs
        valid_includes = {
            'authors': ('author', Author, AuthorSerializer),
            'publishers': ('publisher', Publisher, PublisherSerializer)
        }
        
        include_data = {}
        
        for include_key in requested_includes:
            if include_key not in valid_includes:
                continue  # Ignore invalid include values
            
            field_name, model_class, serializer_class = valid_includes[include_key]
            
            # Get distinct related IDs from the paginated books (efficient DB query)
            related_ids = list(self.paginator.page.values_list(
                f'{field_name}_id', flat=True
            ).exclude(**{f'{field_name}_id': None}).distinct())
            
            if not related_ids:
                continue
            
            # Fetch and serialize the related objects
            related_objects = model_class.objects.filter(id__in=related_ids)
            include_data[include_key] = serializer_class(related_objects, many=True).data
        
        # Add include data to the response if any
        if include_data:
            response.data['include'] = include_data
        
        return response

How It Works

  1. Request Parsing: The view reads the include parameter (e.g., include=authors,publishers) and validates it against allowed values.
  2. Efficient Data Collection: For each valid include, we fetch only the distinct related objects present in the current pagination page using a single DB query per include.
  3. Response Assembly: The include data is added as a sibling to results in the paginated response, matching the structure you want.
  4. Pagination Link Preservation: The custom pagination class ensures the include parameter stays in the next/previous links for consistent navigation.

Example Response

For a request like https://my-api-domain/api/books?limit=5&offset=0&include=authors, you'll get:

{
    "count": 100,
    "next": "https://my-api-domain/api/books?limit=5&offset=5&include=authors",
    "previous": null,
    "results": [
        {"id": 1, "title": "Django for Beginners", "author_id": 1, "publisher_id": 1},
        {"id": 2, "title": "REST API Design", "author_id": 1, "publisher_id": 2}
    ],
    "include": {
        "authors": [
            {"id": 1, "name": "William S. Vincent"}
        ]
    }
}

Answers to Your Questions

  • Is handling this in the view appropriate?: Yes! The view is the right place to assemble the final response structure, as it has access to both the request parameters and paginated data.
  • Should this be in the serializer?: No—serializers are meant to convert model instances to data, not to handle pagination metadata or cross-object include logic.
  • How to make include a sibling to results: By directly adding the include key to the response dictionary returned by get_paginated_response, which already contains count, next, previous, and results.
  • Lightweight solution: This approach uses only DRF's built-in tools, no external packages required.

内容的提问来源于stack exchange,提问作者MarkD

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:59:33