前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >从入门到"精通"Django REST Framework-(五)

从入门到"精通"Django REST Framework-(五)

原创
作者头像
rxg456
发布2025-03-01 00:08:59
发布2025-03-01 00:08:59
7800
代码可运行
举报
运行总次数:0
代码可运行

一. 什么是 GenericAPIView?

GenericAPIView 是 Django REST Framework (DRF) 中的一个基础视图类,它继承自 APIView,并添加了一些常用的功能,特别是与数据库模型交互的功能。它是 DRF 中通用视图和视图集的基础,提供了查询、序列化、分页等常用操作的标准实现。本质上它是 DRF 中所有通用视图(如 ListAPIView、RetrieveAPIView 等)的基础。

二. 为什么要使用 GenericAPIView?

  • 减少重复代码 - 提供了常见操作的标准实现,如获取查询集、序列化数据等
  • 提高开发效率 - 内置了分页、过滤、排序等功能
  • 代码组织更清晰 - 将通用逻辑与业务逻辑分离
  • 易于扩展 - 可以通过重写方法来自定义行为
  • 与 DRF 生态系统集成 - 与 DRF 的其他组件(如序列化器、权限等)无缝协作

三. 基础用法

创建视图类

代码语言:python
代码运行次数:0
复制
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from .models import Book
from .serializers import BookSerializer

class BookListView(GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    
    def get(self, request):
        queryset = self.get_queryset()
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)
    
    def post(self, request):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data, status=201)

配置 URL 路由

代码语言:python
代码运行次数:0
复制
from django.urls import path
from .views import BookListView

urlpatterns = [
    path('books/', BookListView.as_view(), name='book-list'),
]

四. 核心详解

请求数据的访问

GenericAPIView 继承了 APIView 的所有功能,因此可以通过 request 对象访问请求数据:

代码语言:python
代码运行次数:0
复制
def post(self, request):
    # 访问请求体数据
    data = request.data
    
    # 访问查询参数
    query_params = request.query_params
    
    # 访问用户信息
    user = request.user
    
    # 使用序列化器处理数据
    serializer = self.get_serializer(data=data)
    # ...

响应数据的返回

与 APIView 一样,使用 Response 对象返回响应:

代码语言:python
代码运行次数:0
复制
from rest_framework.response import Response
from rest_framework import status

def get(self, request):
    # ...
    return Response(data, status=status.HTTP_200_OK)

GenericAPIView核心概念详解

1. 查询集(queryset)与 get_queryset()

queryset 属性:定义视图将操作的数据集

代码语言:python
代码运行次数:0
复制
class BookView(GenericAPIView):
    queryset = Book.objects.all()  # 所有图书

get_queryset() 方法:允许动态定义查询集,比如基于当前用户过滤数据。

代码语言:python
代码运行次数:0
复制
def get_queryset(self):
    """只返回当前用户的图书或公开图书"""
    base_queryset = Book.objects.all()
    
    # 未登录用户只能看到公开图书
    if not self.request.user.is_authenticated:
        return base_queryset.filter(is_public=True)
    
    # 登录用户可以看到自己的图书和公开图书
    return base_queryset.filter(
        Q(owner=self.request.user) | Q(is_public=True)
    )

何时使用:

  • 使用 queryset 属性:当查询集是固定的,不需要根据请求动态变化
  • 使用 get_queryset() 方法:当需要根据请求用户、查询参数等动态调整查询集
2. 序列化器(serializer_class)与 get_serializer()

serializer_class 属性:指定用于序列化和反序列化的类。

代码语言:python
代码运行次数:0
复制
class BookView(GenericAPIView):
    serializer_class = BookSerializer

get_serializer_class() 方法:允许根据不同情况返回不同的序列化器类。

代码语言:python
代码运行次数:0
复制
def get_serializer_class(self):
    """根据请求方法和用户角色返回不同的序列化器"""
    # 管理员使用完整序列化器
    if self.request.user.is_staff:
        return BookAdminSerializer
    
    # GET 请求使用详细序列化器
    if self.request.method == 'GET':
        return BookDetailSerializer
    
    # POST/PUT 请求使用带验证的序列化器
    return BookWriteSerializer

get_serializer() 方法:创建序列化器实例,处理常见参数如 many=True

代码语言:python
代码运行次数:0
复制
# 在视图方法中使用
def get(self, request):
    books = self.get_queryset()
    serializer = self.get_serializer(books, many=True)
    return Response(serializer.data)

# 自定义 get_serializer 方法
def get_serializer(self, *args, **kwargs):
    """添加额外上下文到序列化器"""
    kwargs['context'] = self.get_serializer_context()
    kwargs['context']['extra_data'] = self.get_extra_data()
    return self.serializer_class(*args, **kwargs)
3. 对象查找(lookup_field 和 lookup_url_kwarg)

这两个属性控制如何从 URL 中获取单个对象。

lookup_field:模型中用于查找对象的字段名,默认为 'pk'。

lookup_url_kwarg:URL 中的参数名,默认与 lookup_field 相同。

代码语言:python
代码运行次数:0
复制
class BookDetailView(GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    lookup_field = 'slug'  # 使用 slug 字段查找
    lookup_url_kwarg = 'book_slug'  # URL 中的参数名
    
    # URL 配置: path('books/<str:book_slug>/', BookDetailView.as_view())
    
    def get(self, request, book_slug):
        book = self.get_object()  # 自动使用 book_slug 查找对象
        serializer = self.get_serializer(book)
        return Response(serializer.data)
4. 分页(pagination_class)

控制如何对查询结果进行分页。

代码语言:python
代码运行次数:0
复制
from rest_framework.pagination import PageNumberPagination

class CustomPagination(PageNumberPagination):
    page_size = 20
    page_size_query_param = 'size'
    max_page_size = 100

class BookListView(GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    pagination_class = CustomPagination
    
    def get(self, request):
        queryset = self.filter_queryset(self.get_queryset())
        
        # 执行分页
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            # 返回分页响应(包含分页链接等信息)
            return self.get_paginated_response(serializer.data)
        
        # 如果未启用分页,返回所有结果
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

分页响应格式:

代码语言:json
复制
{
    "count": 100,
    "next": "http://api.example.org/books/?page=2",
    "previous": null,
    "results": [
        // 当前页的数据
    ]
}
5. 过滤(filter_backends)

控制如何过滤查询集。

代码语言:python
代码运行次数:0
复制
from rest_framework.filters import SearchFilter, OrderingFilter
from django_filters.rest_framework import DjangoFilterBackend

class BookListView(GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    
    # 配置过滤后端
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    
    # DjangoFilterBackend 配置
    filterset_fields = ['category', 'author', 'published_year']
    
    # SearchFilter 配置
    search_fields = ['title', 'description', 'author__name']
    
    # OrderingFilter 配置
    ordering_fields = ['title', 'published_date', 'rating']
    ordering = ['-published_date']  # 默认排序
    
    def get(self, request):
        # filter_queryset 会应用所有配置的过滤器
        queryset = self.filter_queryset(self.get_queryset())
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

使用示例:

  • 精确过滤:/books/?category=fiction&author=1
  • 搜索:/books/?search=django
  • 排序:/books/?ordering=-rating,title
6. get_object() 方法详解

此方法用于获取单个对象,并自动处理权限检查和 404 错误。

代码语言:python
代码运行次数:0
复制
def get_object(self):
    """
    获取对象并进行自定义处理
    """
    # 获取查询集
    queryset = self.filter_queryset(self.get_queryset())
    
    # 获取查找参数
    lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
    filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
    
    # 从查询集获取对象
    obj = get_object_or_404(queryset, **filter_kwargs)
    
    # 检查对象权限
    self.check_object_permissions(self.request, obj)
    
    # 记录访问日志
    self.log_object_access(obj)
    
    return obj
    
def log_object_access(self, obj):
    """记录对象访问日志"""
    AccessLog.objects.create(
        user=self.request.user,
        object_id=obj.id,
        object_type=obj.__class__.__name__
    )

实际应用场景示例

场景 1: 带权限控制的 API
代码语言:python
代码运行次数:0
复制
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status

class ArticleView(GenericAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [IsAuthenticated]
    
    def get_queryset(self):
        """根据用户角色返回不同的查询集"""
        user = self.request.user
        if user.is_staff:
            return Article.objects.all()
        return Article.objects.filter(status='published')
    
    def get(self, request):
        """获取文章列表"""
        articles = self.filter_queryset(self.get_queryset())
        page = self.paginate_queryset(articles)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        serializer = self.get_serializer(articles, many=True)
        return Response(serializer.data)
    
    def post(self, request):
        """创建新文章"""
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        
        # 添加作者信息
        serializer.validated_data['author'] = request.user
        serializer.save()
        
        return Response(serializer.data, status=status.HTTP_201_CREATED)
场景 2: 自定义响应格式
代码语言:python
代码运行次数:0
复制
class StandardResponse(GenericAPIView):
    """提供标准响应格式的基类"""
    
    def get_standard_response(self, data=None, message="", code=0, status=status.HTTP_200_OK, **kwargs):
        """生成标准响应格式"""
        response_data = {
            "code": code,
            "message": message,
            "data": data or {},
        }
        # 添加额外数据
        response_data.update(kwargs)
        return Response(response_data, status=status)

class ProductListView(StandardResponse):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    
    def get(self, request):
        products = self.get_queryset()
        serializer = self.get_serializer(products, many=True)
        
        # 使用标准响应格式
        return self.get_standard_response(
            data=serializer.data,
            message="获取产品列表成功",
            total_count=products.count()
        )
场景 3: 复杂查询和聚合
代码语言:python
代码运行次数:0
复制
from django.db.models import Count, Avg, Sum

class SalesAnalyticsView(GenericAPIView):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer
    
    def get(self, request):
        # 获取时间范围参数
        start_date = request.query_params.get('start_date')
        end_date = request.query_params.get('end_date')
        
        # 构建基础查询集
        queryset = self.get_queryset()
        if start_date:
            queryset = queryset.filter(created_at__gte=start_date)
        if end_date:
            queryset = queryset.filter(created_at__lte=end_date)
        
        # 执行聚合查询
        analytics = queryset.aggregate(
            total_sales=Sum('total_amount'),
            average_order_value=Avg('total_amount'),
            order_count=Count('id')
        )
        
        # 按产品分组统计
        product_stats = queryset.values('product__name').annotate(
            sales=Sum('total_amount'),
            quantity=Sum('quantity')
        ).order_by('-sales')
        
        # 构建响应
        response_data = {
            'summary': analytics,
            'product_stats': product_stats
        }
        
        return Response(response_data)

五. 与 Mixin 类的关系

GenericAPIView 本身不提供 CRUD 操作的实现,但 DRF 提供了一系列 Mixin 类,可以与 GenericAPIView 组合使用:

  • ListModelMixin: 提供 list() 方法,实现列表查询
  • CreateModelMixin: 提供 create() 方法,实现创建对象
  • RetrieveModelMixin: 提供 retrieve() 方法,实现获取单个对象
  • UpdateModelMixin: 提供 update()partial_update() 方法,实现更新对象
  • DestroyModelMixin: 提供 destroy() 方法,实现删除对象
代码语言:python
代码运行次数:0
复制
from rest_framework.mixins import ListModelMixin, CreateModelMixin
from rest_framework.generics import GenericAPIView

class BookListCreateView(ListModelMixin, CreateModelMixin, GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
    
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

六. 其他技巧

1. 使用动态查询参数

代码语言:python
代码运行次数:0
复制
class DynamicFilterBookView(GenericAPIView):
    queryset = Book.objects.all()  # 设置基础查询集为所有图书
    serializer_class = BookSerializer  # 设置序列化器
    
    def filter_queryset(self, queryset):
        # 首先调用父类的 filter_queryset 方法
        # 这会应用配置的 filter_backends(如果有)
        queryset = super().filter_queryset(queryset)

        # 遍历所有查询参数
        for param, value in self.request.query_params.items():
            # 排除分页参数
            if param not in ['page', 'page_size'] and hasattr(Book, param):
                # 检查参数名是否是 Book 模型的属性
                filter_kwargs = {param: value}
                # 应用过滤条件
                queryset = queryset.filter(**filter_kwargs)

        return queryset
    
    def get(self, request):
        # 获取过滤后的查询集
        queryset = self.filter_queryset(self.get_queryset())
        # 序列化数据(注意 many=True 表示序列化多个对象)
        serializer = self.get_serializer(queryset, many=True)
        # 返回响应
        return Response(serializer.data)
工作原理
  1. 当收到 GET 请求时,视图调用 get 方法
  2. get 方法首先调用 self.get_queryset() 获取基础查询集
  3. 然后调用 self.filter_queryset() 应用过滤
  4. filter_queryset 中,首先调用父类方法应用配置的过滤器
  5. 然后遍历所有查询参数,检查是否与模型字段匹配
  6. 对于匹配的参数,构建过滤条件并应用到查询集
  7. 最后序列化过滤后的查询集并返回响应
使用示例

假设 Book 模型有 title、author、genrepublished_year 字段,用户可以这样使用 API:

  • /api/books/?title=Django - 过滤标题包含 "Django" 的图书
  • /api/books/?author=Martin&genre=Fantasy - 过滤作者为 "Martin" 且类型为 "Fantasy" 的图书
  • /api/books/?published_year=2022 - 过滤 2022 年出版的图书
优点
  • 灵活性 - 无需为每个过滤条件编写专门的代码
  • 可扩展性 - 添加新的模型字段后,自动支持对该字段的过滤
  • 简洁性 - 代码简洁明了,易于维护

2. 添加自定义权限检查

代码语言:python
代码运行次数:0
复制
from rest_framework.permissions import IsAuthenticated
from rest_framework.exceptions import PermissionDenied

class ProtectedBookView(GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = [IsAuthenticated]
    
    def check_permissions(self, request):
        """添加自定义权限检查"""
        super().check_permissions(request)
        
        # 自定义权限逻辑
        if not request.user.is_staff and request.method != 'GET':
            raise PermissionDenied("只有管理员可以修改数据")
    
    def get(self, request):
        # ...

3."全面"分页

列表页分页
代码语言:python
代码运行次数:0
复制
# views.py
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination

# 自定义分页类
class StandardResultsSetPagination(PageNumberPagination):
    page_size = 10  # 每页显示数量
    page_size_query_param = 'page_size'  # 允许客户端通过此参数控制每页大小
    max_page_size = 100  # 每页最大显示数量

class UserListView(GenericAPIView):
    pagination_class = StandardResultsSetPagination
    
    def get(self, request):
        users = User.objects.all()
        
        # 获取分页器实例
        paginator = self.pagination_class()
        # 对查询集进行分页
        paginated_users = paginator.paginate_queryset(users, request)
        
        # 序列化分页后的数据
        serializer = UserSerializer(paginated_users, many=True)
        
        # 返回带分页信息的响应
        return paginator.get_paginated_response(serializer.data)
页码分页 (PageNumberPagination)
代码语言:python
代码运行次数:0
复制
# 客户端请求示例: /api/users/?page=2
class PageNumberPaginationView(GenericAPIView):
    def get(self, request):
        paginator = PageNumberPagination()
        paginator.page_size = 10
        
        users = User.objects.all()
        result_page = paginator.paginate_queryset(users, request)
        serializer = UserSerializer(result_page, many=True)
        
        return paginator.get_paginated_response(serializer.data)
限制偏移分页 (LimitOffsetPagination)
代码语言:python
代码运行次数:0
复制
# 客户端请求示例: /api/users/?limit=10&offset=20
from rest_framework.pagination import LimitOffsetPagination

class LimitOffsetPaginationView(GenericAPIView):
    def get(self, request):
        paginator = LimitOffsetPagination()
        
        users = User.objects.all()
        result_page = paginator.paginate_queryset(users, request)
        serializer = UserSerializer(result_page, many=True)
        
        return paginator.get_paginated_response(serializer.data)
游标分页 (CursorPagination)

适用于大型数据集和实时数据流,基于"游标"而非页码:

代码语言:python
代码运行次数:0
复制
# 客户端请求示例: /api/users/?cursor=cD0yMDIwLTAxLTAxKzAwJTNBMDAlM0EwMA==
from rest_framework.pagination import CursorPagination

class MyCursorPagination(CursorPagination):
    ordering = '-created_at'  # 排序字段
    page_size = 10

class CursorPaginationView(GenericAPIView):
    def get(self, request):
        paginator = MyCursorPagination()
        
        users = User.objects.all()
        result_page = paginator.paginate_queryset(users, request)
        serializer = UserSerializer(result_page, many=True)
        
        return paginator.get_paginated_response(serializer.data)
全局配置分页

settings.py中可以全局配置分页:

代码语言:python
代码运行次数:0
复制
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
}
自定义分页响应格式
代码语言:python
代码运行次数:0
复制
class CustomPagination(PageNumberPagination):
    def get_paginated_response(self, data):
        return Response({
            'links': {
                'next': self.get_next_link(),
                'previous': self.get_previous_link()
            },
            'count': self.page.paginator.count,
            'total_pages': self.page.paginator.num_pages,
            'current_page': self.page.number,
            'results': data
        })

七、总结

GenericAPIView 是 DRF 中非常强大的基础视图类,它提供了与数据库模型和序列化器交互的通用功能,包括:

  1. 查询集管理(queryset 和 get_queryset())
  2. 序列化器管理(serializer_class 和 get_serializer())
  3. 对象查找(lookup_field 和 get_object())
  4. 分页(pagination_class 和 paginate_queryset())
  5. 过滤(filter_backends 和 filter_queryset())

通过合理使用这些功能,可以大大简化 API 开发工作,提高代码的可维护性和可读性。同时,GenericAPIView 也是 DRF 中更高级视图(如 ListAPIView、RetrieveAPIView 等)的基础。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 什么是 GenericAPIView?
  • 二. 为什么要使用 GenericAPIView?
  • 三. 基础用法
    • 创建视图类
    • 配置 URL 路由
  • 四. 核心详解
    • 请求数据的访问
    • 响应数据的返回
    • GenericAPIView核心概念详解
      • 1. 查询集(queryset)与 get_queryset()
      • 2. 序列化器(serializer_class)与 get_serializer()
      • 3. 对象查找(lookup_field 和 lookup_url_kwarg)
      • 4. 分页(pagination_class)
      • 5. 过滤(filter_backends)
      • 6. get_object() 方法详解
    • 实际应用场景示例
      • 场景 1: 带权限控制的 API
      • 场景 2: 自定义响应格式
      • 场景 3: 复杂查询和聚合
  • 五. 与 Mixin 类的关系
  • 六. 其他技巧
    • 1. 使用动态查询参数
    • 2. 添加自定义权限检查
    • 3."全面"分页
      • 列表页分页
      • 页码分页 (PageNumberPagination)
      • 限制偏移分页 (LimitOffsetPagination)
      • 游标分页 (CursorPagination)
      • 全局配置分页
      • 自定义分页响应格式
  • 七、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档