django rest framework:从零开始搭建RESTful API

django rest framework:从零开始搭建RESTful API

UV教程:Python多版本管理神器

Django教程:Django项目开发全链路:数据库操作、多环境配置、windows/linux项目部署一站式指南

Django数据库配置:Django多数据库配置:mysql、mongo、redis、达梦

DRF官方:https://www.django-rest-framework.org/

GitHub:https://github.com/encode/django-rest-framework

一、安装DRF

1.运行uv add djangorestframework安装djangorestframework,未使用uv管理工具可使用pip install djangorestframework

2.进入项目目录,运行 uv run python manage.py startapp drfDemo新建应用,未使用uv管理工具可使用python manage.py startapp drfDemo

3.修改settings.py下的INSTALLED_APPS

python 复制代码
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'drfDemo'
]

4.修改drfDemo>view.py

python 复制代码
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from django.views.decorators.http import require_http_methods


@method_decorator(csrf_exempt, name='dispatch')
@method_decorator(require_http_methods(['GET']), name='dispatch')
class DrfDemo(APIView):
    def get(self, request):
        return Response({
            'code': status.HTTP_200_OK,
            'message': '请求成功'
        }, status=status.HTTP_201_CREATED)

5.新建drfDemo>urls.py

python 复制代码
from .views import DrfDemo
from django.urls import path


urlpatterns = [
    path('api', DrfDemo.as_view(), name='DrfDemo'),
]

6.修改settings.py同级urls.py

python 复制代码
from django.contrib import admin
from django.urls import include, path


urlpatterns = [
    path('drfDemo/', include('drfDemo.urls')),
    path('', admin.site.urls)
]

7.进入项目目录,运行uv run python manage.py runserver 6001启动服务,未使用uv管理工具可使用python manage.py runserver 6001

8.访问http://127.0.0.1:6001/drfDemo/api,如下图所示,说明DRF安装成功

二、DRF通用视图

1.DRF通用视图相关类,官网文档:https://www.django-rest-framework.org/api-guide/generic-views/

类别 类名称 说明
功能类 ListModelMixin 提供列表查询功能(get 方法实现)
功能类 CreateModelMixin 提供创建资源功能(post 方法实现)
功能类 RetrieveModelMixin 提供单资源查询功能(get 方法实现,需指定主键)
功能类 UpdateModelMixin 提供更新资源功能(put/patch 方法实现)
功能类 DestroyModelMixin 提供删除资源功能(delete 方法实现)
基础通用视图 GenericAPIView 所有通用视图的基类,提供核心功能(如查询集、序列化器管理)
具体视图类 CreateAPIView 仅支持创建资源(GenericAPIView + CreateModelMixin)
具体视图类 ListAPIView 仅支持列表查询(GenericAPIView + ListModelMixin)
具体视图类 RetrieveAPIView 仅支持单资源查询(GenericAPIView + RetrieveModelMixin)
具体视图类 DestroyAPIView 仅支持删除资源(GenericAPIView + DestroyModelMixin)
具体视图类 UpdateAPIView 仅支持更新资源(GenericAPIView + UpdateModelMixin)
具体视图类 ListCreateAPIView 支持列表查询和创建资源(ListModelMixin + CreateModelMixin)
具体视图类 RetrieveUpdateAPIView 支持单资源查询和更新(RetrieveModelMixin + UpdateModelMixin)
具体视图类 RetrieveDestroyAPIView 支持单资源查询和删除(RetrieveModelMixin + DestroyModelMixin)
具体视图类 RetrieveUpdateDestroyAPIView 支持单资源查询、更新和删除(Retrieve+Update+DestroyModelMixin)

2.修改drfDemo>models.py

python 复制代码
from django.db import models
from django.utils import timezone


class Student(models.Model):
    # 性别选择
    GENDER_CHOICES = (
        ('M', '男'),
        ('F', '女'),
        ('O', '其他'),
    )

    # 基本信息
    name = models.CharField(max_length=100, verbose_name='姓名')
    student_id = models.CharField(max_length=20, unique=True, verbose_name='学号')
    gender = models.CharField(max_length=1, choices=GENDER_CHOICES, verbose_name='性别')
    birth_date = models.DateField(verbose_name='出生日期')
    email = models.EmailField(blank=True, null=True, verbose_name='邮箱')
    phone = models.CharField(max_length=15, blank=True, null=True, verbose_name='电话')

    # 学业信息
    major = models.CharField(max_length=100, verbose_name='专业')
    grade = models.PositiveIntegerField(verbose_name='年级')
    admission_date = models.DateField(default=timezone.now, verbose_name='入学日期')

    # 状态信息
    is_active = models.BooleanField(default=True, verbose_name='是否在读')
    created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')

    class Meta:
        db_table = 'student'
        verbose_name = '学生'
        verbose_name_plural = '学生'
        ordering = ['student_id']  # 按学号排序

    def __str__(self):
        return f'{self.name}({self.student_id})'

    def get_age(self):
        """计算学生年龄"""
        today = timezone.now().date()
        age = today.year - self.birth_date.year
        # 考虑生日是否已过
        if (today.month, today.day) < (self.birth_date.month, self.birth_date.day):
            age -= 1
        return age

3.依次运行下面命令同步模型,需配置数据库

python 复制代码
 uv run python manage.py makemigrations drfDemo #根据模型变更创建新的数据库迁移文件,未使用uv管理工具可使用pthon manage.py makemigrations drfDemo
 uv run python manage.py migrate #同步模型与数据库,未使用uv管理工具可使用python manage.py migrate

4.新建drfDemo>serializers.py,序列化文件将查询集和模型实例等复杂数据转换为Python数据类型,官网文档:https://www.django-rest-framework.org/api-guide/serializers/

python 复制代码
from .models import Student
from rest_framework import serializers


class StudentSerializer(serializers.ModelSerializer):
    """
    学生信息序列化器,用于学生信息的序列化和反序列化
    支持所有字段的增删改查操作
    """
    # 只读字段,计算年龄
    age = serializers.IntegerField(source='get_age', read_only=True)
    created_at = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', read_only=True)
    updated_at = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', read_only=True)

    class Meta:
        model = Student
        # 包含模型所有字段
        fields = '__all__'
        # 可以根据需要指定只读字段,如自动生成的时间戳
        read_only_fields = ['created_at', 'updated_at']

    def validate_phone(self, value):
        """
        验证电话号码格式
        """
        if value and not (value.isdigit() and len(value) in [11]):
            raise serializers.ValidationError('请输入有效的11位手机号码')
        return value

    def validate_grade(self, value):
        """
        验证年级范围(1-6年级)
        """
        if value < 1 or value > 6:
            raise serializers.ValidationError('年级必须在1-6之间')
        return value

5.修改drfDemo>views.py

python 复制代码
from .models import Student
from .serializers import StudentSerializer
from rest_framework import generics, status
from rest_framework.response import Response


class DrfDemoAddView(generics.GenericAPIView):
    """ 提供学生列表查询和创建新学生的API接口 """
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def post(self, request):
        request_data = request.data  # 将请求数据存储在一个变量中

        serializer = self.get_serializer(data=request_data)
        if serializer.is_valid():
            serializer.save()
            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生添加成功'
            }, status=status.HTTP_200_OK)

        return Response({
            'code': status.HTTP_400_BAD_REQUEST,
            'message': '请求失败',
            'errors': serializer.errors
        }, status=status.HTTP_400_BAD_REQUEST)


class DrfDemoGetView(generics.GenericAPIView):
    """ 提供学生列表查询和创建新学生的API接口 """
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def get(self, request):
        students = self.get_queryset()
        serializer = self.get_serializer(students, many=True)

        return Response({
            'code': status.HTTP_200_OK,
            'message': '请求成功',
            'data': serializer.data
        }, status=status.HTTP_200_OK)

class DrfDemoUpdateView(generics.GenericAPIView):
    """ 提供学生列表查询和创建新学生的API接口 """
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def post(self, request):
        """
        通过POST方法修改学生信息
        请求数据中需包含id字段指定要修改的学生
        """
        # 从请求数据中获取学生ID
        student_id = request.data.get('student_id')
        if not student_id:
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': '缺少student_id字段'
            }, status=status.HTTP_400_BAD_REQUEST)

        # 获取要修改的学生对象
        try:
            # 这里假设id是学生表的主键,如果是其他字段(如student_id)可修改查询条件
            student = self.queryset.get(student_id=student_id)
        except Student.DoesNotExist:
            return Response({
                'code': status.HTTP_404_NOT_FOUND,
                'message': f'ID为{student_id}的学生不存在'
            }, status=status.HTTP_404_NOT_FOUND)

        # 初始化序列化器(传入要更新的对象和新数据)
        serializer = self.get_serializer(
            instance=student,
            data=request.data,
            partial=True  # 允许部分字段更新
        )

        if serializer.is_valid():
            serializer.save()  # 执行更新操作
            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生信息修改成功'
            }, status=status.HTTP_200_OK)

        return Response({
            'code': status.HTTP_400_BAD_REQUEST,
            'message': '请求失败'
        }, status=status.HTTP_400_BAD_REQUEST)


class DrfDemoDeleteView(generics.GenericAPIView):
    """提供学生信息删除的API接口(使用GET方法)"""
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def get(self, request):
        """
        通过GET方法删除学生信息
        """

        student_id = request.GET.get('student_id')

        if not student_id:
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': '缺少student_id字段'
            }, status=status.HTTP_400_BAD_REQUEST)

        try:
            student = self.queryset.get(student_id=str(student_id))
            student_name = student.name  # 记录姓名用于返回提示
            student.delete()  # 执行删除操作(物理删除,若需逻辑删除可改为student.is_active=False + save())

            # 4. 返回删除成功响应
            return Response({
                'code': status.HTTP_200_OK,
                'message': f'学生「{student_name}」(student_id:{student_id})删除成功'
            }, status=status.HTTP_200_OK)
        # 处理学生不存在的情况
        except Student.DoesNotExist:
            return Response({
                'code': status.HTTP_404_NOT_FOUND,
                'message': f'student_id为{student_id}的学生不存在,无法删除'
            }, status=status.HTTP_404_NOT_FOUND)

6.修改drfDemo>urls.py

python 复制代码
from .views import DrfDemoAddView, DrfDemoDeleteView, DrfDemoGetView, DrfDemoUpdateView
from django.urls import path


urlpatterns = [
    path('drfDemo/add', DrfDemoAddView.as_view(), name='添加数据'),
    path('drfDemo/query', DrfDemoGetView.as_view(), name='查询数据'),
    path('drfDemo/update', DrfDemoUpdateView.as_view(), name='修改数据'),
    path('drfDemo/delete', DrfDemoDeleteView.as_view(), name='删除数据'),
]

7.修改修改settings.py同级urls.py

python 复制代码
from django.contrib import admin
from django.urls import include, path


urlpatterns = [
    path('api/', include('drfDemo.urls')),
    path('', admin.site.urls)
]

8.测试增删改查请求,如下图

9.修改drfDemo>views.py,使用功能类优化请求,如:ListModelMixin、

python 复制代码
CreateModelMixin、UpdateModelMixin、DestroyModelMixin
from .models import Student
from .serializers import StudentSerializer
from rest_framework import generics, status
from rest_framework.exceptions import ValidationError
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, UpdateModelMixin
from rest_framework.response import Response


class DrfDemoAddView(generics.GenericAPIView,CreateModelMixin):
    """ 提供学生列表查询和创建新学生的API接口 """
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def post(self, request, *args, **kwargs):
        try:
            # 调用create()方法的异常
            self.create(request, *args, **kwargs)
            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生添加成功'
            }, status=status.HTTP_200_OK)
        except ValidationError as e:
            # 捕获序列化器验证错误(如字段格式错误)
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': '请求失败',
                'errors': e.detail  # 详细错误信息
            }, status=status.HTTP_400_BAD_REQUEST)


class DrfDemoGetView(generics.GenericAPIView, ListModelMixin):
    """ 提供学生列表查询和创建新学生的API接口 """
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def get(self, request, *args, **kwargs):
        response = self.list(request, *args, **kwargs)

        return Response({
            'code': status.HTTP_200_OK,
            'message': '请求成功',
            'data': response.data
        }, status=status.HTTP_200_OK)

class DrfDemoUpdateView(generics.GenericAPIView, UpdateModelMixin):
    """ 提供学生列表查询和创建新学生的API接口 """
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def get_object(self):
        """
        重写此方法,从请求数据中获取学生ID
        """
        student_id = self.request.data.get('student_id')
        if not student_id:
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': '缺少student_id字段'
            }, status=status.HTTP_400_BAD_REQUEST)

        obj = self.queryset.get(student_id=student_id)

        # 检查对象权限(如果需要)
        self.check_object_permissions(self.request, obj)
        return obj

    def post(self, request, *args, **kwargs):
        """
        通过POST方法修改学生信息
        请求数据中需包含id字段指定要修改的学生
        """
        # 从请求数据中获取学生ID
        kwargs['partial'] = True
        try:
            # 调用create()方法的异常
            self.update(request, *args, **kwargs)
            return Response({
                'code': status.HTTP_200_OK,
                'message': '修改成功'
            }, status=status.HTTP_200_OK)
        except ValidationError as e:
            # 捕获序列化器验证错误(如字段格式错误)
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': '请求失败',
                'errors': e.detail  # 详细错误信息
            }, status=status.HTTP_400_BAD_REQUEST)

class DrfDemoDeleteView(generics.GenericAPIView, DestroyModelMixin):
    """提供学生信息删除的API接口(使用GET方法)"""
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def get_object(self):
        """
        重写此方法,从请求数据中获取学生ID
        """
        student_id = self.request.GET.get('student_id')
        if not student_id:
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': '缺少student_id字段'
            }, status=status.HTTP_400_BAD_REQUEST)

        obj = self.queryset.get(student_id=student_id)

        # 检查对象权限(如果需要)
        self.check_object_permissions(self.request, obj)
        return obj

    def get(self, request, *args, **kwargs):
        """
        通过GET方法删除学生信息
        """
        student_id = request.GET.get('student_id')
        try:
            self.destroy(request, *args, **kwargs)

            # 4. 返回删除成功响应
            return Response({
                'code': status.HTTP_200_OK,
                'message': f'学生(student_id:{student_id})删除成功'
            }, status=status.HTTP_200_OK)
        except Student.DoesNotExist:
            return Response({
                'code': status.HTTP_404_NOT_FOUND,
                'message': f'student_id为{student_id}的学生不存在,无法删除'
            }, status=status.HTTP_404_NOT_FOUND)

10.修改drfDemo>views.py,使用视图类优化请求,如:CreateAPIView、ListAPIView、DestroyAPIView、UpdateAPIView

python 复制代码
from .models import Student
from .serializers import StudentSerializer
from rest_framework import generics, status
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response


class DrfDemoAddView(generics.CreateAPIView):
    """ 提供学生列表查询和创建新学生的API接口 """
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def post(self, request, *args, **kwargs):
        try:
            # 调用create()方法的异常
            self.create(request, *args, **kwargs)
            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生添加成功'
            }, status=status.HTTP_200_OK)
        except ValidationError as e:
            # 捕获序列化器验证错误(如字段格式错误)
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': '请求失败',
                'errors': e.detail  # 详细错误信息
            }, status=status.HTTP_400_BAD_REQUEST)


class DrfDemoGetView(generics.ListAPIView):
    """ 提供学生列表查询和创建新学生的API接口 """
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def get(self, request, *args, **kwargs):
        response = self.list(request, *args, **kwargs)

        return Response({
            'code': status.HTTP_200_OK,
            'message': '请求成功',
            'data': response.data
        }, status=status.HTTP_200_OK)

class DrfDemoUpdateView(generics.UpdateAPIView):
    """ 提供学生列表查询和创建新学生的API接口 """
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def get_object(self):
        """
        重写此方法,从请求数据中获取学生ID
        """
        student_id = self.request.data.get('student_id')
        if not student_id:
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': '缺少student_id字段'
            }, status=status.HTTP_400_BAD_REQUEST)

        obj = self.queryset.get(student_id=student_id)

        # 检查对象权限(如果需要)
        self.check_object_permissions(self.request, obj)
        return obj

    def post(self, request, *args, **kwargs):
        """
        通过POST方法修改学生信息
        请求数据中需包含id字段指定要修改的学生
        """
        # 从请求数据中获取学生ID
        kwargs['partial'] = True
        try:
            # 调用create()方法的异常
            self.update(request, *args, **kwargs)
            return Response({
                'code': status.HTTP_200_OK,
                'message': '修改成功'
            }, status=status.HTTP_200_OK)
        except ValidationError as e:
            # 捕获序列化器验证错误(如字段格式错误)
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': '请求失败',
                'errors': e.detail  # 详细错误信息
            }, status=status.HTTP_400_BAD_REQUEST)

class DrfDemoDeleteView(generics.DestroyAPIView):
    """提供学生信息删除的API接口(使用GET方法)"""
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def get_object(self):
        """
        重写此方法,从请求数据中获取学生ID
        """
        student_id = self.request.GET.get('student_id')
        if not student_id:
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': '缺少student_id字段'
            }, status=status.HTTP_400_BAD_REQUEST)

        obj = self.queryset.get(student_id=student_id)

        # 检查对象权限(如果需要)
        self.check_object_permissions(self.request, obj)
        return obj

    def get(self, request, *args, **kwargs):
        """
        通过GET方法删除学生信息
        """
        student_id = request.GET.get('student_id')
        try:
            self.destroy(request, *args, **kwargs)

            # 4. 返回删除成功响应
            return Response({
                'code': status.HTTP_200_OK,
                'message': f'学生(student_id:{student_id})删除成功'
            }, status=status.HTTP_200_OK)
        except Student.DoesNotExist:
            return Response({
                'code': status.HTTP_404_NOT_FOUND,
                'message': f'student_id为{student_id}的学生不存在,无法删除'
            }, status=status.HTTP_404_NOT_FOUND)

三、DRF视图集

1.DRF视图集相关,官网文档:https://www.django-rest-framework.org/api-guide/viewsets/

视图集类名 说明
ViewSet 基础ViewSet类,提供action映射机制但无具体实现,需手动实现list、retrieve等方法
GenericViewSet 继承GenericAPIView,提供通用视图功能但无默认action实现,需手动实现具体方法
ModelViewSet 完整实现类,继承GenericViewSet并包含所有标准CRUD操作,自动提供全部action
ReadOnlyModelViewSet 只读版本,仅提供list和retrieve两个只读操作,不包含创建更新删除功能
视图集内置方法 HTTP 方法 URL 路径 描述
list GET /path/ 获取资源列表
retrieve GET /path/{id}/ 获取单个资源详情
create POST /path/ 创建新资源
update PUT /path/{id}/ 全量更新资源
partial_update PATCH /path/{id}/ 部分更新资源
destroy DELETE /path/{id}/ 删除资源
2.修改drfDemo>views.py,使用ViewSet优化Student请求
python 复制代码
from .models import Student
from .serializers import StudentSerializer
from rest_framework import status, viewsets
from rest_framework.response import Response


class DrfDemoView(viewsets.ViewSet):

    @staticmethod
    def get_queryset():
        """获取所有学生数据"""
        return Student.objects.all()

    @staticmethod
    def get_serializer(*args, **kwargs):
        """获取序列化器"""
        return StudentSerializer(*args, **kwargs)

    def list(self, request, *args, **kwargs):
        """
        获取所有学生
        GET /api/students/
        """
        queryset = self.get_queryset()
        serializer = self.get_serializer(queryset, many=True)
        return Response({
            'code': status.HTTP_200_OK,
            'message': '请求成功',
            'data': serializer.data
        }, status=status.HTTP_200_OK)

    def create(self, request, *args, **kwargs):
        """
        创建新学生
        POST /api/students/
        """
        try:
            serializer = self.get_serializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            serializer.save()

            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生添加成功'
            }, status=status.HTTP_200_OK)

        except Exception as e:
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': f'创建学生失败: {str(e)}'
            }, status=status.HTTP_400_BAD_REQUEST)

    def update(self, request, pk=None):
        """
        更新学生信息
        PUT /api/students/{id}/
        """
        try:
            student = self.get_queryset().get(student_id=pk)
            serializer = self.get_serializer(student, data=request.data,partial=True)
            serializer.is_valid(raise_exception=True)
            serializer.save()

            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生信息更新成功',
                'data': serializer.data
            })
        except Student.DoesNotExist:
            return Response({
                'code': status.HTTP_404_NOT_FOUND,
                'message': '学生不存在'
            }, status=status.HTTP_404_NOT_FOUND)
        except Exception as e:
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': f'更新学生失败: {str(e)}',
                'errors': serializer.errors if 'serializer' in locals() else None
            }, status=status.HTTP_400_BAD_REQUEST)

    def destroy(self, request, pk=None):
        """
        删除学生信息
        DELETE /api/students/{id}/
        """
        try:
            student = self.get_queryset().get(student_id=pk)
            student.delete()

            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生信息删除成功'
            })
        except Student.DoesNotExist:
            return Response({
                'code': status.HTTP_404_NOT_FOUND,
                'message': '学生不存在'
            }, status=status.HTTP_404_NOT_FOUND)
        except Exception as e:
            return Response({
                'code': status.HTTP_500_INTERNAL_SERVER_ERROR,
                'message': f'删除学生失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

3.修改drfDemo>urls.py,官网文档:https://www.django-rest-framework.org/api-guide/routers/

python 复制代码
'''
GET http://127.0.0.1:6001/api/drfDemo/ 获取
POST http://127.0.0.1:6001/api/drfDemo/ 添加
PUT http://127.0.0.1:6001/api/drfDemo/1/ 更新,1为学生ID
DELETE  http://127.0.0.1:6001/api/drfDemo/1/ 删除,1为学生ID
'''

from .views import DrfDemoView
from rest_framework import routers


# 创建路由器
router = routers.SimpleRouter()
router.register(r'drfDemo', DrfDemoView, basename='drfDemo')
urlpatterns = router.urls

4.修改drfDemo>views.py,使用GenericViewSet优化Student请求,内置get_queryset、get_serializer等方法,但是需要配置queryset、serializer_class

python 复制代码
from .models import Student
from .serializers import StudentSerializer
from rest_framework import status, viewsets
from rest_framework.response import Response


class DrfDemoView(viewsets.GenericViewSet):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    def list(self, request, *args, **kwargs):
        """
        获取所有学生
        GET /api/students/
        """
        queryset = self.get_queryset()
        serializer = self.get_serializer(queryset, many=True)
        return Response({
            'code': status.HTTP_200_OK,
            'message': '请求成功',
            'data': serializer.data
        }, status=status.HTTP_200_OK)

    def create(self, request, *args, **kwargs):
        """
        创建新学生
        POST /api/students/
        """
        try:
            serializer = self.get_serializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            serializer.save()

            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生添加成功'
            }, status=status.HTTP_200_OK)

        except Exception as e:
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': f'创建学生失败: {str(e)}'
            }, status=status.HTTP_400_BAD_REQUEST)

    def update(self, request, pk=None):
        """
        更新学生信息
        PUT /api/students/{id}/
        """
        try:
            student = self.get_queryset().get(student_id=pk)
            serializer = self.get_serializer(student, data=request.data, partial=True)
            serializer.is_valid(raise_exception=True)
            serializer.save()

            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生信息更新成功',
                'data': serializer.data
            })
        except Student.DoesNotExist:
            return Response({
                'code': status.HTTP_404_NOT_FOUND,
                'message': '学生不存在'
            }, status=status.HTTP_404_NOT_FOUND)
        except Exception as e:
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': f'更新学生失败: {str(e)}',
                'errors': serializer.errors if 'serializer' in locals() else None
            }, status=status.HTTP_400_BAD_REQUEST)

    def destroy(self, request, pk=None):
        """
        删除学生信息
        DELETE /api/students/{id}/
        """
        try:
            student = self.get_queryset().get(student_id=pk)
            student.delete()

            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生信息删除成功'
            })
        except Student.DoesNotExist:
            return Response({
                'code': status.HTTP_404_NOT_FOUND,
                'message': '学生不存在'
            }, status=status.HTTP_404_NOT_FOUND)
        except Exception as e:
            return Response({
                'code': status.HTTP_500_INTERNAL_SERVER_ERROR,
                'message': f'删除学生失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

5.修改drfDemo>views.py,使用ModelViewSet优化Student请求,ModelViewSet内置list、create、update、delete等请求,如需自定义响应结果结构可参考GenericViewSet

python 复制代码
from .models import Student
from .serializers import StudentSerializer
from rest_framework import viewsets


class DrfDemoView(viewsets.ModelViewSet):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

6.由于视图集中内置请求都是的path都是固定的,如需达到图二的效果,需自定义action

python 复制代码
from .models import Student
from .serializers import StudentSerializer
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response


class DrfDemoView(viewsets.GenericViewSet):
    serializer_class = StudentSerializer

    @action(detail=False, methods=['get'])
    def query(self, request, *args, **kwargs):
        """
        获取所有学生
        """
        queryset = Student.objects.all()
        serializer = self.get_serializer(queryset, many=True)
        return Response({
            'code': status.HTTP_200_OK,
            'message': '请求成功',
            'data': serializer.data
        }, status=status.HTTP_200_OK)

    @action(detail=False, methods=['post'])
    def add(self, request, *args, **kwargs):
        """
        获取所有学生
        """
        try:
            serializer = self.get_serializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            serializer.save()

            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生添加成功'
            }, status=status.HTTP_200_OK)

        except Exception as e:
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': f'创建学生失败: {str(e)}'
            }, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=False, url_path='update',methods=['post'])
    def new_update(self, request, *args, **kwargs):
        """
        获取所有学生
        """
        try:
            queryset = Student.objects.all()
            student = queryset.get(student_id=request.data['student_id'])
            serializer = self.get_serializer(student, data=request.data, partial=True)
            serializer.is_valid(raise_exception=True)
            serializer.save()

            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生信息更新成功'
            })
        except Student.DoesNotExist:
            return Response({
                'code': status.HTTP_404_NOT_FOUND,
                'message': '学生不存在'
            }, status=status.HTTP_404_NOT_FOUND)
        except Exception as e:
            return Response({
                'code': status.HTTP_400_BAD_REQUEST,
                'message': f'更新学生失败: {str(e)}'
            }, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=False, methods=['post'])
    def delete(self, request, *args, **kwargs):
        try:
            queryset = Student.objects.all()
            student = queryset.get(student_id=request.data['student_id'])
            student.delete()

            return Response({
                'code': status.HTTP_200_OK,
                'message': '学生信息删除成功'
            })
        except Student.DoesNotExist:
            return Response({
                'code': status.HTTP_404_NOT_FOUND,
                'message': '学生不存在'
            }, status=status.HTTP_404_NOT_FOUND)
        except Exception as e:
            return Response({
                'code': status.HTTP_500_INTERNAL_SERVER_ERROR,
                'message': f'删除学生失败: {str(e)}'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

四、统一请求响应结果格式

1.setting.py同目录新建renderers.py渲染器,统一请求响应结果格式,官网文档:https://www.django-rest-framework.org/api-guide/renderers/

python 复制代码
from rest_framework import status
from rest_framework.renderers import JSONRenderer as DjangoJSONRenderer


class JSONRenderer(DjangoJSONRenderer):
    """
    自定义JSON渲染器,统一所有状态码的响应格式

    响应格式:
    - 成功 (2xx): {"status": "success", "code": 200, "message": "操作成功", "data": {...}}
    - 重定向 (3xx): {"status": "redirect", "code": 301, "message": "重定向", "data": {...}}
    - 客户端错误 (4xx): {"status": "error", "code": 400, "message": "错误信息", "errors": [...]}
    - 服务器错误 (5xx): {"status": "error", "code": 500, "message": "服务器错误", "errors": []}
    """

    def render(self, data, accepted_media_type=None, renderer_context=None):
        if renderer_context is None:
            renderer_context = {}

        response = renderer_context.get('response')
        status_code = getattr(response, 'status_code', status.HTTP_200_OK)
        data = data or {}

        # 定义状态码到状态文本的映射
        status_mapping = {
            'success': range(200, 300),      # 2xx
            'redirect': range(300, 400),     # 3xx
            'error': range(400, 600),        # 4xx和5xx
        }

        # 确定状态文本
        response_status = 'success'
        for status_text, code_range in status_mapping.items():
            if status_code in code_range:
                response_status = status_text
                break

        # 默认消息映射
        default_messages = {
            # 2xx 成功
            status.HTTP_200_OK: '请求成功',
            status.HTTP_201_CREATED: '创建成功',
            status.HTTP_202_ACCEPTED: '请求已接受',
            status.HTTP_204_NO_CONTENT: '删除成功',

            # 3xx 重定向
            status.HTTP_301_MOVED_PERMANENTLY: '永久重定向',
            status.HTTP_302_FOUND: '临时重定向',
            status.HTTP_304_NOT_MODIFIED: '资源未修改',

            # 4xx 客户端错误
            status.HTTP_400_BAD_REQUEST: '请求错误',
            status.HTTP_401_UNAUTHORIZED: '未授权',
            status.HTTP_403_FORBIDDEN: '禁止访问',
            status.HTTP_404_NOT_FOUND: '资源不存在',
            status.HTTP_405_METHOD_NOT_ALLOWED: '方法不允许',
            status.HTTP_409_CONFLICT: '资源冲突',
            status.HTTP_422_UNPROCESSABLE_ENTITY: '参数验证失败',

            # 5xx 服务器错误
            status.HTTP_500_INTERNAL_SERVER_ERROR: '服务器内部错误',
            status.HTTP_502_BAD_GATEWAY: '网关错误',
            status.HTTP_503_SERVICE_UNAVAILABLE: '服务不可用',
        }

        # 获取消息
        message = default_messages.get(status_code, '操作完成')

        # 构建统一的响应格式
        if 200 <= status_code < 300:
            # 成功响应
            if isinstance(data, dict) and 'status' in data and 'data' in data:
                # 已经是自定义格式,保持原样
                final_data = data
            else:
                final_data = {
                    'status': response_status,
                    'code': status_code,
                    'message': message,
                    'data': data
                }

        elif 300 <= status_code < 400:
            # 重定向响应
            final_data = {
                'status': response_status,
                'code': status_code,
                'message': message,
                'data': {
                    'redirect_url': data.get('url', data.get('redirect_url', '')) if isinstance(data, dict) else '',
                    'original_data': data
                }
            }

        else:
            # 错误响应 (4xx和5xx)
            final_data = {
                'status': response_status,
                'code': status_code,
                'message': message,
                'data': None
            }
            if 'errors' in data:
                errors = data['errors']
                final_data['errors'] = errors

        return super().render(final_data, accepted_media_type, renderer_context)

2.修改setting.py,增加REST_FRAMEWORK配置

python 复制代码
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'demo.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',  # 保留可浏览API
    ]
}

3.修改drfDemo>views.py

python 复制代码
from .models import Student
from .serializers import StudentSerializer
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response


class DrfDemoView(viewsets.GenericViewSet):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    @action(detail=False, methods=['get'])
    def query(self, request, *args, **kwargs):
        """
        获取所有学生
        """
        queryset = Student.objects.all()
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @action(detail=False, methods=['post'])
    def add(self, request, *args, **kwargs):
        """
        获取所有学生
        """
        serializer = self.get_serializer(data=request.data)
        try:
            serializer.is_valid(raise_exception=True)
            serializer.save()

            return Response(request.data, status=status.HTTP_200_OK)
        except ValidationError:
            return Response({
                'errors': serializer.errors
            },status=status.HTTP_400_BAD_REQUEST)

    @action(detail=False, url_path='update',methods=['post'])
    def new_update(self, request, *args, **kwargs):
        """
        获取所有学生
        """

        try:
            queryset = self.get_queryset()
            student = queryset.get(student_id=request.data['student_id'])
            serializer = self.get_serializer(student, data=request.data, partial=True)

            try:
                serializer.is_valid(raise_exception=True)
            except ValidationError:
                return Response({
                    'errors': serializer.errors
                },status=status.HTTP_400_BAD_REQUEST)

            serializer.save()

            return Response(serializer.data, status=status.HTTP_200_OK)
        except KeyError:
            return Response({
                'errors': '缺少student_id参数'
            }, status=status.HTTP_400_BAD_REQUEST)
        except Student.DoesNotExist:
            return Response({
                'errors': '学生不存在'
            }, status=status.HTTP_404_NOT_FOUND)

    @action(detail=False, methods=['post'])
    def delete(self, request, *args, **kwargs):
        try:
            queryset = self.get_queryset()
            student = queryset.get(student_id=request.data['student_id'])
            student.delete()

            return Response(status=status.HTTP_200_OK)
        except KeyError:
            return Response({
                'errors': '缺少student_id参数'
            }, status=status.HTTP_400_BAD_REQUEST)
        except Student.DoesNotExist:
            return Response({
                'message': '学生不存在'
            }, status=status.HTTP_404_NOT_FOUND)

4.效果图如下

五、JWT认证配置

1.运行uv add djangorestframework-simplejwt --dev安装djangorestframework-simplejwt,未使用uv管理工具可使用pip install djangorestframework-simplejwt,官网文档:https://django-rest-framework-simplejwt.readthedocs.io/en/latest/

2.进入项目目录,运行 uv run python manage.py startapp users新建应用,未使用uv管理工具可使用python manage.py startapp users

3.修改setting.py

python 复制代码
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework_simplejwt',
    'drfDemo',
    'users',
]
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'demo.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}


SIMPLE_JWT = {
    # Token有效时间
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
    # Refresh Token有效时间
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    # 是否允许刷新Token
    'ROTATE_REFRESH_TOKENS': False,
    # 是否在刷新Token时将旧Token加入黑名单
    'BLACKLIST_AFTER_ROTATION': True,

    # Token验证设置
    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': None,

    # Token验证字段
    'AUTH_HEADER_TYPES': ('Bearer',),
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',

    # 权限验证
    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',

    # 刷新Token的Sliding Token设置
    'JTI_CLAIM': 'jti',
    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}

4.修改users>view.py

python 复制代码
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken


class UserView(viewsets.GenericViewSet):
    """
    登录视图集,使用GenericViewSet实现
    提供登录功能,返回JWT令牌和用户信息
    """
    permission_classes = [AllowAny]

    @action(detail=False, methods=['post'])
    def login2(self, request, *args, **kwargs):
        """
        处理登录请求
        POST /api/login/
        """

        user_info = {
            'user_id': '001',
            'username': request.data.get('username'),
            'password': request.data.get('password')
        }

        # 验证输入
        if not user_info['username'] or not user_info['password']:
            return Response(
                {'detail': '用户名和密码不能为空'},
                status=status.HTTP_400_BAD_REQUEST
            )

        # 生成JWT令牌
        refresh = RefreshToken()
        refresh.payload.update(user_info)

        return Response({
            **user_info,
            'token': str(refresh.access_token)
        })

5.创建users>urls.py

python 复制代码
from .views import UserView
from rest_framework import routers


# 创建路由器
router = routers.SimpleRouter()
router.register(r'users', UserView, basename='user')
urlpatterns = router.urls

6.修改settings.py同级urls.py

python 复制代码
from django.contrib import admin
from django.urls import include, path


urlpatterns = [
    path('api/', include('users.urls')),
    path('api/', include('drfDemo.urls')),
    path('', admin.site.urls)
]

7.访问/api/drfDemo/add/请求会发现请求报未授权,说明JWT已配置

8.验证JWT配置成功:访问/api/users/login/,拿到token,这里的账户密码是django后台管理的账户

9.验证JWT配置成功:使用/api/users/login/请求拿到的token,再次请求/api/drfDemo/add/,会发现请求成功,说明JWT配置成功

六、生成API文档

1.运行 uv add drf-spectacular安装drf-spectacular,未使用uv管理工具可使用pip install drf-spectacular,官网文档:https://drf-spectacular.readthedocs.io/en/latest/

2.修改setting.py

python 复制代码
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework_simplejwt',
    'drf_spectacular',
    'drfDemo',
    'users',
]
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'demo.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema'
}
SPECTACULAR_SETTINGS = {
    'TITLE': 'API文档',  # 标题
    'DESCRIPTION': 'API接口文档描述',  # 描述
    'VERSION': '1.0.0',  # 版本
    'SERVE_INCLUDE_SCHEMA': False,  # 不包含schema本身的端点
    'TAGS': [
        {'name': '用户管理', 'description': '用户登录等相关接口'},  # 第1个显示
        {'name': '学生管理', 'description': '学生信息CRUD接口'},    # 第2个显示
    ],
    'SWAGGER_UI_SETTINGS': {
        'defaultModelsExpandDepth': -1,  # 不显示任何模型(包括Schemas分组)
    },
}

3.修改setting.py同目录urls.py

python 复制代码
from django.contrib import admin
from django.urls import include, path
from drf_spectacular.views import (
    SpectacularAPIView,  # 生成OpenAPI schema文件
    SpectacularRedocView,  # ReDoc
    SpectacularSwaggerView,  # Swagger UI
)


urlpatterns = [
    path('api/', include('users.urls')),
    path('api/', include('drfDemo.urls')),
    path('api/schema', SpectacularAPIView.as_view(), name='schema'),  # schema文件
    path('swagger', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger'),
    path('redoc', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
    path('', admin.site.urls)
]

4.修改drfDemo>serializers.py

python 复制代码
from .models import Student
from rest_framework import serializers
from rest_framework.validators import UniqueValidator


class StudentSerializer(serializers.ModelSerializer):
    """
    学生信息序列化器,用于学生信息的序列化和反序列化
    支持所有字段的增删改查操作
    """

    # 第一种字段校验方式
    name = serializers.CharField(
        max_length=100,
        error_messages={'max_length': '姓名必须是11位数字', 'required': '缺少name参数'},
    )
    student_id = serializers.CharField(
        required=True,
        max_length=20,
        error_messages={
            'required': '缺少student_id参数',
            'max_length': '学号最大长度为20字符'
        },
        # 显式添加唯一校验器
        validators=[
            UniqueValidator(
                queryset=Student.objects.all(),
                message='学号已存在,请更换'  # 自定义错误提示
            )
        ]
    )
    gender = serializers.ChoiceField(
        choices=Student.GENDER_CHOICES,
        error_messages={
            'required': '缺少gender参数',
            'invalid_choice': f'性别必须是 {[choice[0] for choice in Student.GENDER_CHOICES]} 中的一个',
        },
    )
    birth_date = serializers.DateField(
        error_messages={'required': '缺少birth_date参数', 'invalid': '出生日期格式错误,请使用YYYY-MM-DD'}
    )
    email = serializers.EmailField(
        required=False,
        allow_blank=True,  # 允许空字符串
        error_messages={
            'invalid': '邮箱格式错误(例如:example@domain.com)'  # 自定义错误提示
        },
    )
    major = serializers.CharField(
        max_length=100,
        error_messages={'max_length': '专业最大长度100', 'required': '缺少major参数'},
    )
    grade = serializers.IntegerField(
        min_value=1,
        max_value=6,
        error_messages={
            'required': '缺少grade参数',
            'min_value': '年级必须大于等于1',
            'max_value': '年级必须小于等于6',
            'invalid': '年级必须是数字',
        },
    )
    admission_date = serializers.DateField(
        required=False,
        error_messages={'invalid': '入学日期格式错误,请使用YYYY-MM-DD'}
    )
    is_active = serializers.BooleanField(default=True)
    age = serializers.IntegerField(source='get_age', read_only=True)
    created_at = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', read_only=True)
    updated_at = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', read_only=True)

    class Meta:
        model = Student
        # 包含模型所有字段
        fields = '__all__'
        # 可以根据需要指定只读字段,如自动生成的时间戳
        read_only_fields = ['created_at', 'updated_at']
        # 第二种字段校验方式
        extra_kwargs = {
            # 入学日期:可选 + 格式校验
            'admission_date': {
                'required': False,
                'error_messages': {
                    'invalid': '入学日期格式错误,请使用YYYY-MM-DD'
                }
            }
        }

    # 第三种字段校验方式
    def validate_phone(self, value):
        """
        验证电话号码格式
        """
        if value and not (value.isdigit() and len(value) in [11]):
            raise serializers.ValidationError('手机号必须是11位数字')
        return value


class StudentIdValidate(serializers.Serializer):
    """
    学生信息序列化器,用于学生信息的序列化和反序列化
    支持所有字段的增删改查操作
    """

    student_id = serializers.CharField(
        required=True,
        max_length=20,
        error_messages={
            'required': '缺少student_id参数',
            'max_length': '学号最大长度为20字符'
        }
    )

5.修改drfDemo>views.py

python 复制代码
from .models import Student
from .serializers import StudentIdValidate, StudentSerializer
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_view
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response


@extend_schema_view(
    query=extend_schema(
        tags=['学生管理'],
        summary='获取所有学生',
        description='查询数据库中所有学生的信息列表',
    ),
    add=extend_schema(
        tags=['学生管理'],
        summary='添加新学生',
        description='通过提交学生信息(如姓名、学号等)创建新学生记录',
        responses={
            200: OpenApiResponse(
                description='创建成功,返回新学生信息',
                response=StudentSerializer
            )
        }
    ),
    new_update=extend_schema(
        tags=['学生管理'],
        summary='更新学生',
        description='根据student_id更新指定学生的信息(支持部分字段更新)',
        responses={
            200: OpenApiResponse(
                description='更新成功,返回更新后的学生信息',
                response=StudentSerializer
            )
        }
    ),
    delete=extend_schema(
        tags=['学生管理'],
        summary='删除学生',
        description='根据student_id删除指定的学生记录',
        request=StudentIdValidate,
        responses={
            200: OpenApiResponse(description='删除成功'),
            400: OpenApiResponse(description='缺少student_id参数'),
            404: OpenApiResponse(description='学生不存在')
        }
    ),
)
class DrfDemoView(viewsets.GenericViewSet):
    swagger_tags = ['学生管理']
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    @action(detail=False, methods=['get'])
    def query(self, request, *args, **kwargs):
        """
        获取所有学生
        """
        queryset = Student.objects.all()
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @action(detail=False, methods=['post'])
    def add(self, request, *args, **kwargs):
        """
        获取所有学生
        """
        serializer = self.get_serializer(data=request.data)
        try:
            serializer.is_valid(raise_exception=True)
            serializer.save()
            return Response(request.data, status=status.HTTP_200_OK)
        except ValidationError:
            return Response({'errors': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=False, url_path='update',methods=['post'])
    def new_update(self, request, *args, **kwargs):
        """
        获取所有学生
        """
        student_id_validate = StudentIdValidate(data=request.data)
        try:
            student_id_validate.is_valid(raise_exception=True)
            queryset = self.get_queryset()
            student = queryset.get(student_id=request.data['student_id'])
            serializer = self.get_serializer(student, data=request.data, partial=True)
            try:
                serializer.is_valid(raise_exception=True)
            except ValidationError:
                return Response({'errors': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

            serializer.save()

            return Response(serializer.data, status=status.HTTP_200_OK)
        except ValidationError:
            return Response({'errors': student_id_validate.errors}, status=status.HTTP_400_BAD_REQUEST)
        except Student.DoesNotExist:
            return Response({'errors': '学生不存在'}, status=status.HTTP_404_NOT_FOUND)

    @action(detail=False, methods=['post'])
    def delete(self, request, *args, **kwargs):
        student_id_validate = StudentIdValidate(data=request.data)
        try:
            student_id_validate.is_valid(raise_exception=True)
            queryset = self.get_queryset()
            student = queryset.get(student_id=request.data['student_id'])
            student.delete()

            return Response(status=status.HTTP_200_OK)
        except ValidationError:
            return Response({'errors': student_id_validate.errors}, status=status.HTTP_400_BAD_REQUEST)
        except Student.DoesNotExist:
            return Response({'message': '学生不存在'}, status=status.HTTP_404_NOT_FOUND)

6.新增users>serializers.py

python 复制代码
from rest_framework import serializers


class LoginValidate(serializers.Serializer):

    username = serializers.CharField(error_messages={'required': '缺少username参数'})
    password = serializers.CharField(error_messages={'required': '缺少password参数'})

class LoginSuccessResponse(serializers.Serializer):
    user_id = serializers.CharField()
    username = serializers.CharField()
    password = serializers.CharField()
    access_token = serializers.CharField()

7.新增users>views.py

python 复制代码
from .serializers import LoginSuccessResponse, LoginValidate
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_view
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken


@extend_schema_view(
    login=extend_schema(
        tags=['用户管理'],
        summary='用户登录',
        description='通过用户名和密码登录,返回JWT令牌',
        request=LoginValidate,
        responses=OpenApiResponse(
            description='登录成功,返回用户信息和JWT令牌',
            response=LoginSuccessResponse
        )
    )
)
class UserView(viewsets.GenericViewSet):
    """
    登录视图集,使用GenericViewSet实现
    提供登录功能,返回JWT令牌和用户信息
    """
    permission_classes = [AllowAny]

    @action(detail=False, methods=['post'])
    def login(self, request, *args, **kwargs):
        """
        处理登录请求
        POST /api/login/
        """
        login_validate = LoginValidate(data=request.data)
        try:
            login_validate.is_valid(raise_exception=True)

            user_info = {
                'user_id': '001',
                'username': request.data.get('username'),
                'password': request.data.get('password')
            }

            # 生成JWT令牌
            refresh = RefreshToken()
            refresh.payload.update(user_info)

            return Response({
                **user_info,
                'access_token': str(refresh.access_token)
            })
        except ValidationError:
            return Response({'errors': login_validate.errors}, status=status.HTTP_400_BAD_REQUEST)

8.Swagger UI文档访问http://127.0.0.1:6001/swagger#/,官网文档:https://swagger.io/docs

9.ReDoc文档访问http://127.0.0.1:6001/redoc,官网文档:https://redocly.com/redoc

10.配置详解

python 复制代码
SPECTACULAR_DEFAULTS = {
    # 一个正则表达式,用于指定所有操作路径的共同前缀。如果
    # SCHEMA_PATH_PREFIX 设置为 None,drf-spectacular 将尝试估算
    # 一个共同前缀。使用 '' 可禁用。
    # 主要用于标签提取,例如路径 '/api/v1/albums' 搭配
    # SCHEMA_PATH_PREFIX 正则 '/api/v[0-9]' 会生成标签 'albums'。
    'SCHEMA_PATH_PREFIX': None,
    # 从操作路径中移除匹配的 SCHEMA_PATH_PREFIX。通常与
    # SERVERS 中附加的前缀配合使用。
    'SCHEMA_PATH_PREFIX_TRIM': False,
    # 手动向操作路径插入一个前缀,例如 '/service/backend'。
    # 例如,当 API 作为子资源挂载在代理后面而 Django 未察觉时,
    # 可使用此配置对齐路径。也可通过 SERVERS 指定前缀,但此配置
    # 能让操作路径更明确。
    'SCHEMA_PATH_PREFIX_INSERT': '',
    # {pk} 到 {id} 的转换由 SCHEMA_COERCE_PATH_PK 控制。此外,
    # 某些库(如 drf-nested-routers)使用 "_pk" 后缀的路径变量。
    # 此设置全局将类似 "{user_pk}" 的路径变量转换为 "{user_id}"。
    'SCHEMA_COERCE_PATH_PK_SUFFIX': False,

    # 影响组件构建方式的 schema 生成参数。
    # 某些 schema 功能可能无法很好地适配目标场景。
    # 拆分/修改组件可能有助于缓解这些问题。
    'DEFAULT_GENERATOR_CLASS': 'drf_spectacular.generators.SchemaGenerator',
    # 为 PATCH 端点创建单独的组件(不含 required 列表)
    'COMPONENT_SPLIT_PATCH': True,
    # 在适当情况下将组件拆分为请求和响应部分
    # 强烈推荐启用此设置以获得最准确的 API 描述,
    # 但代价是会生成更多组件。
    'COMPONENT_SPLIT_REQUEST': False,
    # 帮助那些难以处理只读属性的客户端生成器目标。
    'COMPONENT_NO_READ_ONLY_REQUIRED': False,

    # 为不允许空字符串的字段添加 "minLength: 1"。默认禁用,
    # 因为序列化器不会在响应中严格执行此规则,因此
    # "minLength: 1" 可能并不总是准确描述 API 行为。
    # 当启用 COMPONENT_SPLIT_REQUEST 时会隐式启用,因为
    # 分离请求和响应组件后可以准确建模。
    'ENFORCE_NON_BLANK_FIELDS': False,

    # 此版本字符串将显示在 schema 头部。默认 OpenAPI 版本为 3.0.3,
    # 经过充分测试。现在也支持 3.1.0,包含相同功能和一些
    # 强制性但次要的更改。
    'OAS_VERSION': '3.0.3',

    # 用于 SpectacularAPIView 提供 schema 子集的配置
    'SERVE_URLCONF': None,
    # 完整的公共 schema 或基于请求用户的子集
    'SERVE_PUBLIC': True,
    # 将 schema 端点包含到 schema 中
    'SERVE_INCLUDE_SCHEMA': True,
    # spectacular 视图的认证/权限类列表
    'SERVE_PERMISSIONS': ['rest_framework.permissions.AllowAny'],
    # None 将默认使用 DRF 的 AUTHENTICATION_CLASSES
    'SERVE_AUTHENTICATION': None,

    # 传递给 SwaggerUI({ ... }) 的通用配置字典
    # https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
    # 配置通过 json.dumps() 序列化。如果需要自定义 JS,可使用字符串,
    # 字符串必须包含有效的 JS 且会原样传递。
    'SWAGGER_UI_SETTINGS': {
        'deepLinking': True,
    },
    # 使用额外的 OAuth2 配置初始化 SwaggerUI
    # https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/
    'SWAGGER_UI_OAUTH2_CONFIG': {},

    # 传递给 Redoc.init({ ... }) 的通用配置字典
    # https://redocly.com/docs/redoc/config/#functional-settings
    # 配置通过 json.dumps() 序列化。如果需要自定义 JS,可使用字符串,
    # 字符串必须包含有效的 JS 且会原样传递。
    'REDOC_UI_SETTINGS': {},

    # swagger 和 redoc 的 CDN。可根据需求更改版本甚至自行托管。
    # 关于自行托管,参见 README 中的 sidecar 选项。
    'SWAGGER_UI_DIST': 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@latest',
    'SWAGGER_UI_FAVICON_HREF': 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@latest/favicon-32x32.png',
    'REDOC_DIST': 'https://cdn.jsdelivr.net/npm/redoc@latest',

    # 向路径和组件追加 OpenAPI 对象(附加到生成的对象)
    'APPEND_PATHS': {},
    'APPEND_COMPONENTS': {},

    # 强烈不推荐(除了 djangorestframework-api-key 库)
    # 请不要再使用此配置,因为它可能带来难以解决的问题。
    # 对于认证,强烈推荐使用 OpenApiAuthenticationExtension,
    # 因为它们更健壮且易于编写。
    # 但如果使用,方法列表会附加到 schema 中的每个端点!
    'SECURITY': [],

    # 在 schema 生成结束时运行的后处理函数。
    # 必须满足接口:result = hook(generator, request, public, result)
    'POSTPROCESSING_HOOKS': [
        'drf_spectacular.hooks.postprocess_schema_enums'
    ],

    # 在 schema 生成前运行的预处理函数。
    # 必须满足接口:result = hook(endpoints=result),其中 result 是
    # 元组列表 (path, path_regex, method, callback)。
    # 示例:'drf_spectacular.hooks.preprocess_exclude_path_format'
    'PREPROCESSING_HOOKS': [],

    # 确定操作应如何排序。如果打算通过 PREPROCESSING_HOOKS 进行排序,
    # 请确保禁用此设置。如果配置,排序将在 PREPROCESSING_HOOKS 之后应用。
    # 接受 True(drf-spectacular 的字母排序器)、False 或 sort 键的可调用对象。
    'SORT_OPERATIONS': True,

    # 枚举名称覆盖。键为 "YourEnum" 且值为 "field.choices" 的字典
    # 例如:{'SomeEnum': ['A', 'B'], 'OtherEnum': 'import.path.to.choices'}
    'ENUM_NAME_OVERRIDES': {},
    # 在适当情况下添加 "blank" 和 "null" 枚举选项。在客户端生成有问题时禁用
    'ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE': True,
    # 向枚举描述字符串添加/追加 (``选项值`` - 选项名称) 列表
    'ENUM_GENERATE_CHOICE_DESCRIPTION': True,
    # 生成的枚举的可选后缀
    # 例如:{'ENUM_SUFFIX': "Type"} 会生成枚举名称 'StatusType'
    'ENUM_SUFFIX': 'Enum',

    # 返回应从文档字符串提取中排除的所有类的列表的函数
    'GET_LIB_DOC_EXCLUDES': 'drf_spectacular.plumbing.get_lib_doc_excludes',

    # 返回用于视图处理的模拟请求的函数。对于 CLI 使用,
    # original_request 将为 None。
    # 接口:request = build_mock_request(method, path, view, original_request, **kwargs)
    'GET_MOCK_REQUEST': 'drf_spectacular.plumbing.build_mock_request',

    # 将 "operationId" 和路径参数名称等驼峰化
    # 操作 schema 本身的驼峰化需要添加
    # 'drf_spectacular.contrib.djangorestframework_camel_case.camelize_serializer_fields'
    # 到 POSTPROCESSING_HOOKS。请注意,此钩子依赖于
    # ``djangorestframework_camel_case``,而 CAMELIZE_NAMES 本身不依赖。
    'CAMELIZE_NAMES': False,

    # 更改生成的 OperationId 中动作/方法的位置。例如,
    # "POST": "group_person_list", "group_person_create"
    # "PRE":  "list_group_person", "create_group_person"
    'OPERATION_ID_METHOD_POSITION': 'POST',

    # 确定是否以及如何在 schema 中生成自由格式的 'additionalProperties'。
    # 某些代码生成器目标对此敏感。None 禁用通用的 'additionalProperties'。
    # 允许的值为 'dict'、'bool'、None
    'GENERIC_ADDITIONAL_PROPERTIES': 'dict',

    # 路径转换器 schema 覆盖(例如 <int:foo>)。可用于修改默认行为
    # 或为通过 register_converter(...) 注册的自定义转换器提供 schema。
    # 以转换器标签为键,以基本 Python 类型、OpenApiType 或原始 schema 为值。
    # 示例:{'aint': OpenApiTypes.INT, 'bint': str, 'cint': {'type': ...}}
    'PATH_CONVERTER_OVERRIDES': {},

    # 确定操作参数应按字母数字排序还是按原始顺序排列。
    # 接受 True、False 或 sort 键的可调用对象。
    'SORT_OPERATION_PARAMETERS': True,

    # @extend_schema 允许指定 200 以外的状态码。此功能通常用于
    # 描述错误响应,很少使用列表机制。因此,默认情况下我们会在
    # 非 2XX 状态码上抑制列表(分页和过滤)。切换此设置可启用
    # 无论状态码如何,都使用 ListSerializers/many=True 的列表响应。
    'ENABLE_LIST_MECHANICS_ON_NON_2XX': False,

    # 此设置允许你通过访问不同的模型属性来偏离默认管理器。
    # 为了兼容性,我们默认使用 "objects"。使用 "_default_manager"
    # 可能会解决大多数问题,但你也可以自由选择任何名称。
    "DEFAULT_QUERY_MANAGER": 'objects',

    # 控制在 schema 中暴露哪些认证方法。如果不为 None,将隐藏
    # 不在白名单中的认证类。使用完整的导入路径,
    # 例如 ['rest_framework.authentication.TokenAuthentication', ...]。
    # 空列表 ([]) 将隐藏所有认证方法。默认 None 将显示所有。
    'AUTHENTICATION_WHITELIST': None,
    # 控制在 schema 中暴露哪些解析器。工作方式类似于 AUTHENTICATION_WHITELIST。
    # 允许的解析器列表或 None(允许所有)。
    'PARSER_WHITELIST': None,
    # 控制在 schema 中暴露哪些渲染器。工作方式类似于 AUTHENTICATION_WHITELIST。
    # 如果白名单为 None,默认忽略 rest_framework.renderers.BrowsableAPIRenderer
    'RENDERER_WHITELIST': None,

    # 关闭错误和警告消息的选项
    'DISABLE_ERRORS_AND_WARNINGS': False,

    # 作为 "./manage.py check --deploy" 的一部分运行示例性 schema 生成并发出警告
    'ENABLE_DJANGO_DEPLOY_CHECK': True,

    # 通用 schema 元数据。参考规范获取有效输入
    # https://spec.openapis.org/oas/v3.0.3#openapi-object
    'TITLE': '',
    'DESCRIPTION': '',
    'TOS': None,
    # 可选:可以包含 "name"、"url"、"email"
    'CONTACT': {},
    # 可选:必须包含 "name",可以包含 URL
    'LICENSE': {},
    # 静态设置 schema 版本。也可以是空字符串。当与视图版本控制一起使用时,
    # 对于 'v2' 版本的请求,将变为 '0.0.0 (v2)'。
    # 如果只想渲染请求版本,将 VERSION 设置为 None。
    'VERSION': '0.0.0',
    # 可选的服务器列表。
    # 每个条目必须包含 "url",可以包含 "description"、"variables"
    # 例如:[{'url': 'https://example.com/v1', 'description': '文本'}, ...]
    'SERVERS': [],
    # 在全局范围内定义的标签
    'TAGS': [],
    # 可选:OpenAPI 3.1 webhooks 列表。每个条目应为 OpenApiWebhook 实例的导入路径。
    'WEBHOOKS': [],
    # 可选:必须包含 'url',可以包含 "description"
    'EXTERNAL_DOCS': {},

    # 附加到 schema 的 info 对象的任意规范扩展。
    # https://swagger.io/specification/#specification-extensions
    'EXTENSIONS_INFO': {},

    # 附加到 schema 的根对象的任意规范扩展。
    # https://swagger.io/specification/#specification-extensions
    'EXTENSIONS_ROOT': {},

    # 与 Oauth2 相关的设置。例如用于 django-oauth2-toolkit。
    # https://spec.openapis.org/oas/v3.0.3#oauth-flows-object
    'OAUTH2_FLOWS': [],
    'OAUTH2_AUTHORIZATION_URL': None,
    'OAUTH2_TOKEN_URL': None,
    'OAUTH2_REFRESH_URL': None,
    'OAUTH2_SCOPES': None,
}
相关推荐
㏕追忆似水年华あ18 小时前
逻辑600解析本03
python·flask
AndrewHZ18 小时前
【图像处理基石】遥感图像高度信息提取:Python实战全流程+常用库汇总
图像处理·人工智能·python·计算机视觉·cv·遥感图像·高程信息
盼哥PyAI实验室18 小时前
序列的力量——Python 内置方法的魔法解密
java·前端·python
Rhys..19 小时前
POM思想的理解与示例
前端·javascript·python·html·pom
MonkeyKing_sunyuhua19 小时前
什么是python中的一等函数和闭包
开发语言·python
ZHOU_WUYI19 小时前
LLMs-from-scratch :embeddings 与 linear-layers 的对比
pytorch·python·llm
Blossom.11819 小时前
把AI“浓缩”到1KB:超紧凑型决策树在MCU上的极限优化实战
人工智能·python·单片机·深度学习·决策树·机器学习·数据挖掘
weixin_4296302619 小时前
第四章 决策树
python·决策树·机器学习
Freya冉冉20 小时前
【PYTHON学习】推断聚类后簇的类型DAY18
python·学习·聚类