速通 DRF

发布时间 2023-08-09 14:54:33作者: SRIGT

0x01 概述

  • DRF 是 Django REST Framework 的缩写,有利于实现前后端分离项目(Django 基础

    • DRF 官网链接
    • DRF 相关包
      • coreapi:自动生成 API 文档
      • Markdown:解析 Markdown 语法
      • Pygments:语法高亮
      • django-filter:支持 Django 过滤器
      • django-guardian:实现 DRF 对象级权限控制
  • 前后端分离

    • 交互形式

      graph LR A(前端/客户端)--HTTP Method-->B(RESTful API) B-->C(后端/服务器) C--JSON or XML-->B B-->A
    • 开发模式

      graph LR 提出需求-->约定接口规范和数据格式 -->前后端并行开发 -->前后端对接 -->前端调试效果 -->集成 -->交付
    • 数据接口规范

      graph LR A(定制接口<br/>确定规范)-->B(前端开发<br/>模拟数据) -->C(连调<br/>校验格式) -->D(提测<br/>自动化测试) A-->E(后端开发<br/>数据自测) -->C
  • Restful API 最佳实践详解

    协议、域名、版本、路径、HTTP 动词、过滤信息、状态码、错误处理、返回结果、Hypermedia API

0x02 创建项目

  • 创建 Django 项目

    • 创建流程

    • 修改 settings.py

      ALLOWED_HOSTS = ['*']
      
      # ...
      
      STATIC_ROOT = os.path.join(BASE_DIR, "static")
      
      STATICFILES_DIRS = [
          os.path.join(BASE_DIR, "staticfiles")
      ]
      
  • 安装 DRF

    • pip install djangorestframework
  • 在 settings.py 中配置 DRF

    INSTALLED_APPS = [
        # ...
        'rest_framework',
        'rest_framework.authtoken',
    ]
    
    # ...
    
    REST_FRAMEWORK = {
        # 分页器
        'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
        'PAGE_SIZE': 50,
    
        # 时间字段
        'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S',
    
        # Response 对象参数
        'DEFAULT_RENDER_CLASSES': [
            'rest_framework.renders.JSONRenderer',
            'rest_framework.renders.BrowsableRenderer',
        ],
    
        # Request 解析器
        'DEFAULT_PARSER_CLASSES': [
            'rest_framework.parsers.JSONParser',
            'rest_framework.parsers.FormParser',
            'rest_framework.parsers.MultiPartParser',
        ],
    
        # 权限
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.IsAuthenticated',
        ],
        
        # 认证
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'rest_framework.authentication.BasicAuthentication',
            'rest_framework.authentication.SessionAuthentication',
            'rest_framework.authentication.TokenAuthentication',
        ]
    }
    
  • 在 ./urls.py 配置根路由

    from django.urls import path, include
    
    urlpatterns = [
        path('api/', include('rest_framework.urls')),
    ]
    

0x03 添加模型

  • 在 models.py 中创建新模型

    from django.db import models
    
    
    class User(models.Model):
        username = models.CharField(max_length=30, unique=True)
        password = models.CharField(max_length=255)
        age = models.IntegerField(default=0)
    
        class Meta:
            verbose_name = '用户表'
            verbose_name_plural = verbose_name
            ordering = ('age',)
    
        def __str__(self):
            return self.username
    
  • 在 admin.py 中配置模型

    from django.contrib import admin
    from App.models import *
    
    @admin.register(User)
    class UserAdmin(admin.ModelAdmin):
        # 需要显示的字段
        list_display = ('username', 'password', 'age')
    
        # 可以搜索的字段
        search_fields = list_display
    
        # 需要过滤的字段
        list_filter = list_display
    

0x04 序列化

  • 序列化:将 Django 的 queryset 数据或 instance 数据转换为 JSON 数据

    • 反序列化:将 JSON 数据转换为 queryset 数据或 instance 数据
  • 序列化作用:

    • 验证处理 request.data
    • 验证器的参数
    • 同时序列化多个对象
    • 序列化过程中添加上下文
    • 对无效的数据进行异常处理
  • 继承序列化模型类 ModelSerializer

    • 在 App 目录下新建 serializers.py

      from rest_framework import serializers
      from App.models import *
      
      
      class UserSerializer(serializers.ModelSerializer):
          # 外键字段
          foreignKeyName = serializers.ReadOnlyField(source='Table.Key')
          class Meta:
              model = User
              
              # 排除指定字段
              # exclude = ('id',)
              
              # 设置需要显示的字段
              fields = '__all__'
              
              # 指定遍历深度
              depth = 2
      
  • 实现携带 URL 的 HyperlinkedModelSerializer

    • 修改 serializers.py

      class UserSerializer(serializers.HyperlinkedModelSerializer):
          class Meta:
              model = User
              # url 参数名是默认值,可在 settings.py 的 URL_FIELD_NAME 中修改
              fields = ('url',)
      

0x05 API 接口开发

(1)RESTful API 接口

  • 在 App/views.py 中,RESTful API开发方法

    • Function Based View,FBV 函数式编程,Django 原生方法

      import json
      from django.http import HttpResponse, JsonResponse
      from django.views.decorators.csrf import csrf_exempt
      
      @csrf_exempt
      def user_list(request):
          datas = {
              'username': '111',
              'age': '20'
          }
          if request.method == 'GET':
              return JsonResponse(datas)
          if request.method == 'POST':
              user = json.loads(request.body.decode('utf-8'))
              return HttpResponse(json.dumps(user), content_type='application/json')
      
      • 装饰器 csrf_exempt 用于对 POST 请求取消 CSRF 的限制
    • Classed Based View,CBV 类视图

      import json
      from django.http import HttpResponse, JsonResponse
      from django.views.decorators.csrf import csrf_exempt
      from django.views import View
      
      class UserList(View):
          def get(self, request):
              datas = {
                  'username': '111',
                  'age': '20'
              }
              return JsonResponse(datas)
      
          @csrf_exempt
          def post(self, request):
              user = json.loads(request.body.decode('utf-8'))
              return HttpResponse(json.dumps(user), content_type='application/json')
      
      • 思路:对不同的请求方法,用对于的函数处理

      • 对于装饰器的使用可以放在类外:

        from django.utils.decorators import method_decorator
        
        @method_decorator(csrf_exempt, name='dispatch')
        class UserList(View):
            def get(self, request):
                # ...
        
            def post(self, request):
                # ...
        
    • Generic Classed Based View,GCBV 通用类视图

    • viewsets,DRF 视图集

(2)FBV

装饰器 api_view

  • views.py

    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from rest_framework import status
    from App.models import *
    from App.serializers import *
    
    @api_view(["GET", "POST"])
    def user_list(request):
        if request.method == "GET":
            userSerializer = UserSerializer(instance=User.objects.all(), many=True)
            return Response(data=userSerializer.data, status=status.HTTP_200_OK)
        elif request.method == "POST":
            userSerializer = UserSerializer(data=request.data, partial=True)
            if userSerializer.is_valid():
                userSerializer.save()
                return Response(data=userSerializer.data, status=status.HTTP_201_CREATED)
            return Response(userSerializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    • 对信息进行获取、更新、删除

      @api_view(["GET", "PUT", "DELETE"])
      def user_detail(request, pk):
          try:
              user = User.objects.get(pk=pk)
          except User.DoesNotExist:
              return Response(data={"msg": "Not Exist"}, status=status.HTTP_404_NOT_FOUND)
          else:
              if request.method == "GET":
                  serializer = UserSerializer(instance=user)
                  return Response(data=serializer.data, status=status.HTTP_200_OK)
              elif request.method == "PUT":
                  serializer = UserSerializer(instance=user, data=request.data)
                  if serializer.is_valid:
                      serializer.save()
                      return Response(data=serializer.data, status=status.HTTP_200_OK)
                  return Response(data=serializer.data, status=status.HTTP_400_BAD_REQUEST)
              elif request.method == "DELETE":
                  user.delete()
                  return Response(status=status.HTTP_204_NO_CONTENT)
      
  • 子路由 App/urls.py

    from django.urls import path, include
    from App.views import *
    
    urlpatterns = [
        path('fbv/list/', user_list, name='userList'),
        path('fbv/detail/<int:pk>/', user_detail, name='userDetail'),
    ]
    
  • 根路由 ./urls.py

    from django.urls import path, include
    
    urlpatterns = [
        path('user/', include('App.urls'))
    ]
    

(3)CBV

需要导入 APIView

  • views.py

    from rest_framework.views import APIView
    
    class UserList(APIView):
        def get(self, request):
            queryset = User.objects.all()
            serializer = UserSerializer(instance=queryset, many=True)
            return Response(serializer.data, status=status.HTTP_200_OK)
    
        def post(self, request):
            serializer = UserSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save(user = self.request.user)
                return Response(data=serializer.data, status=status.HTTP_201_CREATED)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    • 对信息进行获取、更新、删除

      class UserDetail(APIView):
          @staticmethod
          def get_object(pk):
              try:
                  return User.objects.all(pk=pk)
              except User.DoesNotExist:
                  return
      
          def get(self, request, pk):
              obj = self.get_object(pk)
              if not obj:
                  return Response(data={"msg": "Not Exist"}, status=status.HTTP_404_NOT_FOUND)
              serializer = UserSerializer(instance=obj)
      
          def put(self, request, pk):
              obj = self.get_object(pk)
              if not obj:
                  return Response(data={"msg": "Not Exist"}, status=status.HTTP_404_NOT_FOUND)
              serializer = UserSerializer(instance=obj)
              if serializer.is_valid():
                  serializer.save()
                  return Response(data=serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          def delete(self, request, pk):
              obj = self.get_object(pk)
              if not obj:
                  return Response(data={"msg": "Not Exist"}, status=status.HTTP_404_NOT_FOUND)
              obj.delete()
              return Response(status=status.HTTP_204_NO_CONTENT)
      
  • 子路由 App/urls.py

    from django.urls import path
    from App import views
    
    urlpatterns = [
        path('cbv/list/', views.UserList.as_view(), name='userList'),
        path('cbv/detail/<int:pk>', views.UserDetail.as_view(), name='userDetail'),
    ]
    

(4)Generic Classed Based View

需要导入 generics

  • views.py

    from rest_framework import generics
    
    class GUserList(generics.ListCreateAPIView):
        # 变量名继承于 generics,名称固定
        queryset = User.objects.all()
        serializer_class = UserSerializer
    
        # 重写方法
        def perform_create(self, serializer):
            serializer.save(user=self.request.user)
    
  • 子路由 App/urls.py

    from App import views
    
    urlpatterns = [
        path('gcbv/list/', views.GUserList.as_view(), name='userList'),
        path('gcbv/detail/<int:pk>', views.GUserDetail.as_view(), name='userDetail'),
    ]
    

(5)viewsets

  • views.py

    from rest_framework import viewsets
    
    class UserViewSet(viewsets.ModelViewSet):
        # 忽略权限、认证、限流等操作
        queryset = User.objects.all()
        serializer_class = UserSerializer
        
        def perform_create(self, serializer):
            serializer.save(user=self.request.user)
    
  • 子路由 App/urls.py

    • 方法一

      from App import views
      
      urlpatterns = [
          path('viewsets/', views.UserViewSet.as_view(
              # 传入字典,key 为 HTTP 方法,value 为视图集的 Mixin 中的方法
              {"get": "list", "post": "create"}
          ), name='userViewSetList'),
          path('viewsets/<int:pk>/', views.UserViewSet.as_view(
              # put:全部更新
              # patch:部分更新
              {"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destory"}
          ), name='userViewSetDetail'),
      ]
      
    • 方法二

      from django.urls import path, include
      from App import views
      from rest_framework.routers import DefaultRouter
      
      router = DefaultRouter()
      router.register(prefix="viewsets", viewset=views.UserViewSet)
      
      urlpatterns = [
          path("", include(router.urls))
      ]
      

0x06 认证与权限

认证:用户登录的身份信息校验

权限:已登录用户访问接口的校验

(1)认证方式

  • BasicAuthentication
    • 基本账号密码验证,适用于测试开发环境,不建议用于生产环境
  • SessionAuthentication
    • 使用基本 Django 后端会话验证,需要 CSRF_Token
  • TokenAuthentication

(2)Token 生成

  • 在 settings.py 中进行配置

    INSTALLED_APPS = [
        'rest_framework.authtoken',
    ]
    
  • 在 views.py 中

    • 生成 Token

      # 信号
      from django.db.models.signals import post_save
      # 接收
      from django.dispatch import receiver
      # 模型
      from django.conf import settings
      from rest_framework.authtoken.models import Token
      
      
      @receiver(post_save, sender=settings.AUTH_USER_MODEL)
      def generate_token(sender, instance=None, created=False, **kwargs):
          if created:
              Token.objects.create(user=instance)
      
    • 绑定认证方式

      from rest_framework.decorators import api_view, authentication_classes
      from rest_framework.authentication import BasicAuthentication
      
      @authentication_classes((BasicAuthentication, ))
      def user_list(request):
          # ...
      
  • 在根路由 urls.py 中配置路由

    from rest_framework.authtoken import views
    
    urlpatterns = [
        # 获取 Token 的接口
        path('api-token-auth/', views.obtain_auth_token),
    
  • 使用 POST 方法获取 Token

(3)权限控制

a. 常用的权限类与使用

  • 常见权限类

    • IsAuthenticatedOrReadOnly:已登录可以增删改查,未登录仅可查询
    • IsAuthenticated:已登录可以增删改查,未登录不允许任何操作
    • IsAdminUser:仅管理员请求
    • AllowAny:允许任何请求
  • 使用

    • views.py

      from rest_framework.decorators import permission_classes
      from rest_framework.permissions import IsAuthenticated
      
      # FBV
      @permission_classes((IsAuthenticated, ))
      def user_list(request):
          # ...
      
      # GCBV
      class GUserList(generics.ListCreateAPIView):
          permission_classes = IsAuthenticated
      

b. 自定义对象级别权限

  • 新建文件 App/permission.py

    from rest_framework import  permissions
    
    class IsOwnerReadOnly(permissions.BasePermission):
        # 仅允许对象的所有者进行编辑,其他人只读
        def has_object_permission(self, request, view, obj):
            if request.method in permissions.SAFE_METHODS:
                return True
            return request.user == obj.user
    
  • 导入 views.py

    from App.permission import IsOwnerReadOnly
    
    class GUserDetail(generics.RetrieveUpdateDestroyAPIView):
        permission_classes = (IsAuthenticated, IsOwnerReadOnly)
    

0x07 API 接口文档

(1)生成

  • 安装 pyyaml、uritemplate

  • settings.py

    REST_FRAMEWORK = {
        'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',
    }
    
  • 根路由 urls.py

    from rest_framework.schemas import get_schema_view
    
    # 视图概要
    schema_view = get_schema_view(title="API DOCUMENTATION", description="DRF")
    
    urlpatterns = [
        path('schema/', schema_view),
    ]
    

(2)配置 coreapi

  • 安装 coreapi

  • 修改 settings.py

    REST_FRAMEWORK = {
        'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    }
    
  • 修改根路由 urls.py

    from rest_framework.documentation import include_docs_urls
    
    urlpatterns = [
        path('docs/', include_docs_urls(title="API DOCUMENTATION", description="DRF")),
    ]
    

-End-