DRF

Django REST framework

测试工具:official::Postman

text::Django

official::Django REST framework


一、基础

1 配置DRF

安装:pip install djangorestframework

本质上Django REST framework是写一个写好的APP。

安装完成后到setting.py中注册

# setting.py
INSTALLED_APPS = [
... ,
'rest_framework',
]

2 生命周期

闭包中的view(免除了CSRF认证)

先创建CBV的视图对象

然后执行dispatch函数:

  1. 请求封装
  2. 认证
  3. 权限

Django Rest Framework 生命周期,认证鉴权 - 简书 (jianshu.com)

在DRF中,中间件是先于认证等操作被执行。


二、响应数据

1 简单使用

使用DRF的响应,数据在返回JSON的同时还会返回一个好看的页面。

访问:127.0.0.1:8000/login 将返回JSON的同时还会返回一个好看的页面。

访问:127.0.0.1:8000/login?format=json 将仅返回JSON。

# views.py
# 1.装饰器实现
@api_view(['GET'])
def login(resquest):
return Response({'status': True, 'message': 'success'})

# 2.继承类实现(常用)
class InfoView(APIView):
def get(self, request):
return Response({'status': True, 'message': 'success'})

2 FBV和CBV

FBV(function based views):视图基于函数

CBV(class based views):视图基于类

Django原本CBV实现是通过继承ViewUserview(View)

DRF的CBV实现通过继承APIViewUserview(APIView)

APIView的实质是继承了View,在返回函数的基础上加入了一些操作:

  • APIViewas_view()返回是return csrf_exempt(view),会免除掉CSRF校验。
  • APIViewdispatch()方法还负责处理异常、权限检查以及其它请求前后的逻辑处理。
# urls.py
from django.urls import path
from app01 import views

urlpatterns = [
path('auth/', views.auth), # FBV
path('user/', views.UserView.as_view()), # CBV(调用函数as_view()生成函数)
]
# views.py
from django.http import JsonResponse
from django.views import View

# Django的FBV(内部区分请求)
def auth(request):
if request.method =="GET":
return JsonResponse({'status': True,'message': "GET"})
elif request.method == "POST":
return JsonResponse({'status': True,'message': "POST"})
return JsonResponse({'status': True,'message': "..."})


# Django的CBV(函数名区分请求)
class Userview(View):
# 不同的请求
def get(self,request):
return JsonResponse({'status': True,'message': "GET"})
def post(self,request):
return JsonResponse({'status': True,'message': "POST"})
def put(self,request):
return JsonResponse({'status': True,'message': "PUT"})


# DRF的CBV(函数名区分请求)
class Userview(APIView):
# 不同的请求
def get(self,request):
return JsonResponse({'status': True,'message': "GET"})
def post(self,request):
return JsonResponse({'status': True,'message': "POST"})
def put(self,request):
return JsonResponse({'status': True,'message': "PUT"})

3 request请求参数

对于request参数,在DRF中,会对request再进行一次封装,封装成request._request和其他附加的resquest参数。实际使用时,直接使用request.method等即可。因为通过源码可知当request请求中不存在对应属性时,他自己会找到request._request中的值并返回。

# 调用_request中属性
request._request.method
request.method

# 获得get请求参数
request._request.GET.get("token")
request.query_params.get("token")

# 获得POST请求参数
request._request.body.get("token")
request.data.get("token")

4 自定义响应码

在返回的JSON中,可以用一个python文件存储所有的响应码,在使用时导入这个响应码,这样将便于维护。

# code.py
ERROR_CODE = 10001 # 表示错误
# view.py
...
return Response({"code":code.ERROR_CODE, "msg":"failed"})
...

三、认证

1 request请求用户

request.user:访问用户,默认是匿名用户(AnonymousUser,None),可以在setting.py通过配置修改。

# views.py

from rest_framework.views import APIView
from rest_framework.response import Response

class UserView(APIView):
def get(self, request):
print(request.user)
print(request.auth)
return Response("success")
# setting.py

REST_FRAMEWORK = {
"UNAUTHENTICATED_USER": None # 修改匿名用户名
}

2 用户认证

用户认证流程:

  1. 读取请求传递的token
  2. 校验合法性
  3. 返回值

认证类:

  • authenticate(self, request):执行认证,通过返回值判断认证成功
  • authenticate_header(self, request):认证失败时,响应头中www-Authenticate字段的值。(要重写该函数,不重写的话响应类型将会是403而非401)

authenticate(self, request)返回值:

  1. 返回元组(para1,para2):认证成功,request.user = para1,request.auth = para2
  2. 抛出异常:认证失败->返回错误信息
  3. 返回None:用于多个认证类,[类1,类2,类3,类4,类5, … ] -> 匿名用户(当类1返回None时,视图类将会去认证类2,以此类推,都不通过则返回匿名用户)

在写好自定义类以后,可以选择一个一个视图类添加认证,也可以选择在配置文件中直接进行配置,配置完成后,所有的APIView都会需要进行对应的认证。

注:编写时,若全局引用了,不能将认证类放在views.py中。否则会产生循环调用的问题(views.py调用APIViewAPIView中调用setting.pysetting.py又调用了views.py中的认证)

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed


# 自定义认证类
class MyAuthentication(BaseAuthentication):
# 认证函数
def authenticate(self, request):
token = request.query_params.get("token")
if token:
return "user", token
raise AuthenticationFailed("认证失败")

# 认证失败的响应头回显
def authenticate_header(self, request):
return "API"
# return "Basic realm='API'" # 认证失败返回一个登录框而不是数据


# 需要认证后才可访问
class UserView(APIView):
# 可以写多个认证,也可以不写认证
authentication_classes = [MyAuthentication, ]

def get(self, request):
return Response("Hello World!")
# setting.py
REST_FRAMEWORK = {
# 路径案例:app.views.MyAuthentication
"DEFAULT_AUTHENTICATION_CLASSES": ["自定义认证类的路径", ]
}
# 案例:token在URL、请求头、请求体中任意位置,且访问失败不支持匿名访问
class URLAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token")
if token:
return "success", token
return

class HeaderAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.meta.get( "xxxxx")
if token:
return "success", token
return

class BodyAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.data.get("xXxxx")
if token:
return "success", token
return


# 上述都认证不成功直接抛出异常,不允许匿名访问
class NoAuthentication(BaseAuthentication):
def authenticate(self, request):
raise "failed"

class OrderView(APIView):
authentication_classes = [URLAuthentication, HeaderAuthentication, BodyAuthentication, NoAuthentication]

def get(self, request):
print(request.user,request.auth)
return Response("orderView")

3 修改request类

如果觉得DRF自带的request类不好用,可以依照源码,写出一个父类,重写initialize_request(self, request, *args, **kwargs)函数,自定义对request的操作

# DRF3.14.0源码
def initialize_request(self, request, *args, **kwargs):
parser_context = self.get_parser_context(request)

return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)


# 重写类中的return Request即可
class TestView(APIView):
parser_context = self.get_parser_context(request)

# 重写返回值完成对request的自定义
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)

四、权限

1 权限定义

权限类:

  • allow_request(self, request):设置权限,通过返回的Bool值判断是否通过。

权限组件的执行与认证组件不同。认证组件在写好 [类1,类2,类3,类4,类5, … ] 后,将通过返回值判断是否执行下一个类,只要有一个成功就可以。而权限组件会执行所有的类。默认情况下,要保证所有的权限类中的allow_request(self, request)都返回True才算具备权限(也可以进行扩展和自定义修改权限验证方式)。

from rest_framework.permissions import BasePermission

class MyPermission(BasePermission):
# 自定义错误信息返回
message = {"status": False, 'msg': '无权访问'}

# 校验函数
def has_permission(self, request, view):
v1 = random.randint(1, 3)
if v1 == 2:
return True
return False

# 需要认证后才可访问
class UserView(APIView):
# 可以写多个认证,也可以不写认证
permission_classes = [MyPermission, ]

def get(self, request):
return Response("Hello World!")
# setting.py
REST_FRAMEWORK = {
# 默认认证,路径案例:ext.permission.MyPermission
"DEFAULT_PERMISSION_CLASSES": ["自定义权限类的路径", ]
}

2 修改认证机制

由源码可知,权限认证主要是check_permissions(self, request)函数。在源码中显示,只要列表中有一个权限不满足,直接就报错。因此,可以将该函数重写,完成认证机制的修改。

# DRF3.14.0源码
def check_permissions(self, request):
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)


# 重写类,修改成任意一个条件满足(其他类使用时继承该类即可)
class TestView(APIView):
def check_permissions(self, request):
no_permission_objects = []
for permission in self.get_permissions():
if permission.has_permission(request, self):
return
else:
no_permission_objects.append(permission)
else:
self.permission_denied(
request,
message=getattr(no_permission_objects[0], 'message', None),
code=getattr(no_permission_objects[0], 'code', None)
)

五、限流

1 限流类

限流类:

  • get_cache_key(self, request, view):获取用户唯一标识

访问频率:单位有s,m,h,d。可以写全,比如hours。因为根据源码,它只会获得时间单位的第一个字母,比如hours就是获得h,所以不会影响判定。

当有多个限流类时,会按照规则依次检查。

from rest_framework.throttling import SimpleRateThrottle

class MyThrottle(SimpleRateThrottle):
# 访问用户
scope = "xxx"
# 访问频率
THROTTLE_RATES = {"xxx": "5/m"}
# 指定缓存
cache = default_cache

# 获取用户的唯一标识
def get_cache_key(self, request, view):
if request.user:
ident = request.user.pk # 用户id
else:
ident = self.get_ident(request) # 用户IP
return self.cache_format % {'scope': self.scope, 'ident': ident}

# 限流
class UserView(APIView):
# 可以写多个限流类
throttle_classes = [MyThrottle, ]

def get(self, request):
return Response("Hello World!")
# setting.py
REST_FRAMEWORK = {
# 全局配置限流速率(scope: rate)
"DEFAULT_THROTTLE_RATES": {
"x1": "5/m",
"x2": "3/s"
}
}

2 自定义错误提示

通过源码可知,直接在此处抛出异常,修改detail内容即可。

def throttle_failure(self):
minutes_to_wait = int(self.wait() / 60) + 1
raise Throttled(detail={"code": ResponseCode.ERROR, "errors": [f"请求过于频繁,请{minutes_to_wait}分钟后再试"], "data": {}})

3 自定义限流时间

修改掉:parse_rate(self, rate)函数(不要在源码中改,新写出一个类继承源码)

修改后,可以写:5/15m这样的时间,表示每15分钟限制5次。

class MyThrottle(SimpleRateThrottle):
def parse_rate(self, rate):
"""
Given the request rate string, return a two tuple of:
<allowed number of requests>, <period of time in seconds>
"""
if rate is None:
return (None, None)
num, period = rate.split('/')
num_requests = int(num)
time_units = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
if period[0] in 'smhd':
duration = time_units[period[0]]
else:
duration = int(period[:-1]) * time_units[period[-1]]
return (num_requests, duration)

六、版本

1 Get传递版本

通过get请求的参数值来传递版本。

在APIView中存在默认get传递的版本参数名是version。如果要修改,需要修改的值是VERSION_PARAM。修改后,在get中传递的参数名需要修改,但是获得版本值还是request.version

versioning_class:设置版本参数位置,有get,url,请求头三种。

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning

class HomeView(APIView):
# 配置携带版本信息,默认是version
versioning_class = QueryParameterVersioning

def get(self, request):
print(request.version)
return Response("...")
# 反向生成带版本的url
# urls.py
urlpatterns= [
path('home/', views.HomeView.as_view(), name="homeView")
]

# views.py
class HomeView(APIView):
versioning_class = QueryParameterVersioning

def get(self, request):
url = request.versioning_scheme.reverse("homeView", request=request)
print(url)

2 URL传递版本

常用。

通过直接修改url来传递版本,例如下面案例中可以输入:api/v1/home/来表示版本

# urls.py
urlpatterns= [
path('api/<str:version>/home/', views.HomeView.as_view(), name="homeView")
]

# views.py
from rest_framework_versioning import URLPathVersioning
class HomeView(APIView):
versioning_class = URLPathVersioning

def get(self, request, *args, **kwargs):
print(request.version)

3 请求头传递版本

通过在请求头中携带参数来传递版本。

在请求头中加上Accept: application/json; version=v1来传递版本

# urls.py
urlpatterns= [
path('api/home/', views.HomeView.as_view(), name="homeView")
]

# views.py
from rest_framework_versioning import URLPathVersioning
class HomeView(APIView):
versioning_class = URLPathVersioning

def get(self, request, *args, **kwargs):
# 也可以反向生成url
url = request.versioning_scheme.reverse("homeView", request=request)
print(url)

4 全局配置

# setting.py 修改配置
REST_FRAMEWORK = {
# 请求的参数名
"VERSION_PARAM": "version",
# 不传参数时默认的版本值
"DEFAULT_VERSION": "v1",
# 合法的版本值
"ALLOWED_VERSIONS": ["v1", "v2"],
# 默认传递方式
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning"
}

七、解析器

1 系统解析器

定义:解析请求者发过来的数据。

解析流程:

  1. 读取请求头
  2. 根据请求头不同解析数据

系统解析器:

  • JSONParser:JSON数据,请求头含有Content-Type: application/json
  • FormParse:表单数据,请求头含有Content-Type: application/x-www-form-urlencoded
  • MultiPartParser:即可上传文件也可以上传数据,请求头含有Content-Type: multipart/form-data
  • FileUploadParser:文件上传,请求头含有Content-Disposition: attachment; filename=upload.jpgContent-Type: */*

不配置解析器默认是:parser_classes =[JSONParser, FormParser, MultiPartParser]

渲染器content_negotiation_class一般不变。

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework. parsers import JSONParser,FormParser
from rest_framework.negotiation import DefaultContentNegotiation
class HomeView(APIView):
# 配置解析器
parser_classes =[JSONParser, FormParser]

# 根据请求,匹配对应的解析器; 寻找渲染器(何种方式展示数据)
content_negotiation_class = DefaultContentNegotiation

def get(self,request,*args,**kwargs):
return Response("Get")

# setting.py 修改配置
REST_FRAMEWORK = {
# 默认解析器
"DEFAULT_PARSER_CLASSES": ["rest_framework.parsers.JSONParser", ]

}

八、序列化

1 元类

元类(metaclass)是用来创建类的类。在Python中,一切皆对象,包括类也是对象。元类可以用来动态地创建类,控制类的创建行为,或者实现一些高级的自定义功能。

类创建本质就是调用type类进行创建的,也就是说类class Foo(object)默认样式是class Foo(object, metaclass=type)。如果需要修改类为自己写的type类时,则需要修改参数值为class Foo(object, metaclass=MyType)

创建类小知识:

  • 类是由MyType创建的,所以类其实是MyType类的对象
  • Base是类,是MyType类的对象,所以Base()等于是MyType()()
  • 实例出来的对象,比如base = Base(),在加上括号后(base()),会调用类中的__call__方法
  • 所以实际上在新建一个类时,会最先调用type的__call__方法,而在type的__call__方法中定义了先执行__new__再执行__init__,所以初始化类的顺序如此。

在创建类时,字段值和函数值是先被创建的。因为先得现有字段值和函数值,后才能将它们作为参数传入type中

# 方式1:本质也是type创建
class Foo(object):
v1 = 123

def func(self):
return 999

# 方式2
# 类名 = type("类名", (父类, ), {成员})
Foo = type("Foo", (object, ), {"v1": 123, "func": lambda self: 999})

# 执行结果一样
obj = Foo()
print(obj.v1)
print(obj.func())
# 自定义type类
class MyType(type):
def __init__(cls, *args, **kwargs):
super().__init__(cls)

def __new__(cls, *args, **kwargs):
xx = super().__new__(cls, *args, **kwargs)
print("创建了类:", xx)
return xx

def __call__(cls, *args, **kwargs):
obj = cls.__new__(cls, *args, **kwargs)
cls.__init__(obj, *args, **kwargs)
return obj

# 使用MyType
class Base(object, metaclass=MyType):
v1 = 123

def func(self):
return 999

# 父类定义了metaclass,子类也会继承过去
class Foo(object):
pass

2 Serializer

目的:将从数据库中获取的QuerySet或数据对象转化成JSON格式

# 对数据库中取到的数据做序列化
from rest_framework import serializers

class DepartSerializer(serializers.Serializer):
title = serializers.CharField()
count = serializers.IntegerField()

class DepartView(APIView):
def get(self, request, *args, **kwargs):
# 获得一条数据
depart_object = models.Depart.objects.all().first()
# 序列化器转化成JSON
ser = DepartSerializer(instance=depart_object)

# 获得多条数据
query_set = models.Depart.objects.all().first()
# 设置many为True
ser = DepartSerializer(instance=query_set, many=True)

context = {"status": True, "data": ser.data}
return Response(content)

3 ModelSerializer

类似Django的ModelForm

# 数据库
from django.db import models

class Depart(models.Model):
title = models.CharField(verbose_name="部门", max_length=32)
order = models.IntegerField(verbose_name="顺序")
count = models.IntegerField(verbose_name="人数")

class UserInfo(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32)
age = models.IntegerField(verbose_name="年龄")

gender = models.SmallIntegerField(verbose_name="性别", choices=((1, "男"), (2, "女")))
depart = models.ForeginKey(verbose_name="部门", to="Depart", on_delete=models.CASCADE)

ctime = models.DateTimeField(verbose_name="时间", auto_now_add=True)
tags = models.ManyToManyField(verbose_name="标签", to="Tag")

class Tag(models.Model):
caption = models.CharField(verbose_name="标签", max_length = 32)
# 使用ModelSerializer(类似写ModelForm)
class DepartSerializer(serializers.ModelSerializer):
class Meta:
model = models.Depart
fields = "__all__"


# 格式数据
class UserSerializer(serializers.ModelSerializer):
# 取数据库中名字为gender的字段值,重新赋给sex
# sex = serializers.IntergerField(source="gender")

# gender字段存在choices时可以按如下方式获得元组值
sex = serializers.IntergerField(source="get_gender_display")

# 外键,跨表获得字段(直接点对应表的字段名)
depart = serializers.IntergerField(source="depart.title")

# 格式化时间
ctime = serializers.DateTimeField(format="%Y-%m-%d")

class Meta:
model = models.Depart
fields = ["name", "age", "sex", "depart"]


# 自定义显示数据的函数
class UserSerializer(serializers.ModelSerializer):
func = serializers.SerializerMethodField()

class Meta:
model = models.UserInfo
fields = ["name", "age", "gender", "func"]

# 自定义数据返回(obj指所以传入的对象)
# 因为对于fields中,func是自定义的,数据库中没有func字段,所以会执行get_func函数,然后返回自定义的结果
def get_func(self, obj):
return "{}-{}-{}".format(obj.name, obj.age, obj.gender)


# ManyToManyField获得对应表的值
class UserSerializer(serializers.ModelSerializer):
xxx = serializers.SerializerMethodField()

class Meta:
model = models.UserInfo
field = ["name", "age", "xxx"]

def get_xxx(self, obj):
# query_set值为[ Tag对象, Tag对象 , ... ]
query_set = obj.tags.all()
result = [{"id": tag.id, "caption": tag.caption} for tag in query_set]

return result
# 类的嵌套,初始化UserSerializer时可以同时获得多个表的值
class D1(serializers.ModelSerializer):
class Meta:
model = models.Depart
fields = "__all__"


class D2(serializers.ModelSerializer):
class Meta:
model = models.Tag
fields = "__all__"


class UserSerializer(serializers.ModelSerializer):
depart = D1()
# 多对多也要加many=True
tags = D2(many=True)

class Meta:
model = models.UserInfo
fields = ["name", "age", "depart", "tags"]


# 类的继承,在UserSerializer中可以直接使用Base中的值
class Base(serializers.Serializer):
xx = serializers.CharField(source="name")

class UserSerializer(serializers.ModelSerializer, Base):
class Meta:
model = models.UserInfo
fields = ["name", "age", "xx"]

4 简单数据校验

类似Django数据校验

# views.py
# 序列化类
class DepartSerializer(serializers.Serializer):
# 内置规则校验
title = serializers.CharField(required=True, max_length=20, min_length=6)
order = serializers.IntegerField(required=False, max_value=100, min_value=10)
level = serializers.ChoiceField(choices=[("1""高级"),(2"中级")])
# 自定义校验方式和错误返回,等价于email = serializers.EmailField()
# validators是个列表,可以写入多个校验规则
email = serializers.CharField(validators=[EmailValidator(message="邮箱格式错误")])
# RegexValidator:正则校验器
email = serializers.CharField(validators=[RegexValidator(r"\d+ ", message="邮箱格式错误")])


# 视图类(一般写法)
class DepartView(APIView):
def post(self, request, *args, **kwargs):
ser = DepartSerializer(data=request.data)
# 数据校验
if ser.is_valid():
# 将数据存入数据库
models.Depart.objects.create(**ser.validated_data)
# 返回校验结果
return Response(ser.validated_data)
else:
# 返回校验错误
return Response(ser.errors)


# 视图类(自动捕获异常写法)
class DepartView(APIView):
def post(self, request, *args, **kwargs):
ser = DepartSerializer(data=request.data)
# 校验失败时会 raise ValidationError(self.errors),然后被捕获自动返回数据
ser.is_valid(raise_exception=True)

models.Depart.objects.create(**ser.validated_data)
return Response(ser.validated_data)
# setting.py
# 设置异常返回结果为中文
LANGUAGE_CODE = 'zh-hans'

5 钩子函数

类似Django的钩子函数

# 序列化类
class DepartSerializer(serializers.Serializer):
# 内置规则校验
title = serializers.CharField(required=True, max_length=20, min_length=6)
order = serializers.IntegerField(required=False, max_value=100, min_value=10)
level = serializers.ChoiceField(choices=[("1""高级"),(2"中级")])
email = serializers.CharField(validators=[EmailValidator(message="邮箱格式错误")])

# 自定义校验函数(validate_<field_name>(self, value))
def validate_email(self, value):
if len(value) > 6:
raise exceptions.ValidationError("字段钩子校验失败")
return value

# 所有校验函数完成后会到此处(可用于额外的校验,比如检测敏感词)
def validate(self, attrs):
print("validate=" , attrs)
# 故意抛出全局错误
raise exceptions.ValidationError("全局钩子校验失败")

return attrs

# setting.py
# 定制全局错误的返回字段名,默认是:non_field_errors
REST_FRAMEWORK = {
"NON_FIELD_ERRORS_KEY": "global_errors"
}

6 ModelSerializer校验

类似Django的ModelForm数据校验

# 序列化类
class DepartModelSerializer(serializers.ModelSerializer):
# 假设定义了额外字段(需要在请求中传递,但是数据库中没有)
more = serializers.CharField(required=True)

class Meta:
model = models.Depart
fields = ["title" ,"order" , "count"]
# 字段校验
extra_kwargs = {
"title" : {"max_length": 5, "min_length": 1},
"order" : {"min_value ": 5},
# "count" : { "validators ": [RegexValidator(r"\d+",message="格式错误")]},
}


# 视图类
# 视图类(一般写法)
class DepartView(APIView):
def post(self, request, *args, **kwargs):
ser = DepartSerializer(data=request.data)
# 数据校验
if ser.is_valid():
# 将数据库中不存在的字段需要先pop,否则save会报错
more = ser.validated_data.pop("more")
# 将数据存入数据库(直接可以在括号中补充字段插入数据库)
ser.save(extra=100)
# 返回校验结果
return Response(ser.validated_data)
else:
# 返回校验错误
return Response(ser.errors)

7 ListSerializer

多个ModelSerializer进行校验。

from rest_framework import serializers
from myapp.models import MyModel

class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'

class MyModelListSerializer(serializers.ListSerializer):
# 指定ModelSerializer
child = MyModelSerializer()

# 使用MyModelListSerializer来序列化多个MyModel对象
my_model_objects = MyModel.objects.all()
serializer = MyModelListSerializer(instance=my_model_objects, many=True)
serialized_data = serializer.data

九、数据校验

1 Serializer校验

序列化器有多种用法:

  • 序列化:路由——视图——去数据库中获取对象——利用序列化器转化成列表、字典等——JSON处理
  • 数据校验:路由——视图——request.data——校验(利用序列化器的类)——操作(数据库操作,利用序列化器的类)
  • 综合案例:在创建用户动作中,先对获得的数据进行校验(利用序列化器的类),再将数据进行保存,然后将新增的数据返回,此时一样用统一个类进行序列化(利用序列化器的类)

钩子函数验证:先执行上面设置的基础验证(例如:CharField),然后进行钩子验证,最后全局验证。

# views.py
# Serializer校验
from django.core.validators import EmailValidator,RegexValidator

class DepartSecializer(serializers.Serializer):
title = serializers.CharField(required=True,max_length=20,min_length=6)
order = serializers.IntegerField(required=False,max_value=100,min_value=10)
level = serializers.ChoiceField(choices=[(1"高级"),(2"中级")])
# 使用默认邮箱类型
# email = serializers.EmailField
# 自定义异常值
# email = serializers.CharField(validators=[EmailValidator(message="邮箱格式错误")])
# 正则表达式判断
email = serializers.chanField(validators=[RegexValidator(r"\d+",message="邮箱格式错误"),])

# 钩子函数,写上validate_字段名,对应的字段将会进行校验
def validate_email(self, value):
if len(value) > 6:
raise exceptions.ValidationError("字段钩子校验失败")
return value

# 全局校验,上面校验全部通过后的结果,attrs是校验后的值
def validate(self, att):
print("validate=", attrs)
# 全局钩子函数异常
# raise exceptions.ValidationError("全局钩子校验失败")
return attrs

class DepartView(APIView):
def post(self, request, *args,**kwargs):
# 1.获取原始数据
# print(request.data)

# 2.校验(可自定义报错结果方式)
ser = DepartSerializer(data=request.data)
if ser.is_valid():
print(ser.validated_data)
else:
print(ser.errors)
return Response("成功")

# 2.校验(内部自动捕获异常方式)
ser = DepartSerializer(data=request.data)
ser.is_valid(raise_exception=True) # 成功向下执行,失败抛出异常并返回
print(ser.validate_data)
return Response("成功")
# setting.py
# 定制全局钩子函数错误(之后构造函数异常后的异常名便是xxxx)
REST_FRAMEWORK = {
"NON_FIELD_ERRORS_KEY": "xxxx"
}

2 ModelSerializer校验

class DepartModelSerializer(serializers.ModelSerializer): I
class Meta:
# 数据库
model = models.Depart
# 字段,必填,全部就是'__all__'
fields = ["title","order","count"]
# 额外处理
extra_kwargs = {
# 仅可读(序列化的时候有用)
"id": {"read_only": True},
"title": {"max_length": 5, "min_length":1},
"order": {"min_value": 53},
# 正则校验
# "count": {"validators": [RegexValidator(r"\d+",message="格式错误")},
# 仅可写(数据校验的时候有用)
"count": {"write_only": True}
}


class DepartView(APIView):
def post(self, request, *args, **kwargs):
ser = DepartModelSerilizer(data = request.data)
if ser.is_valid():
print("view:", ser.valildated_data)
# 拿出验证后的多余数据(数据库中不存在的数据,否则保存时会报错)
more = ser.validate_data.pop("more")
# 保存数据,验证后的数据若数据库中存在,但是验证后的数据中不存在要加上
ser.save(count = 100)

3 SerializerMethodField

要求:

  • 输入:{"name": "x1", "age": 11, "gender": 1}
  • 返回:{"id": 1, "name": "x3", "gender": "男"}

对于同一个字段gender,输入和输出要是不同的结果,解决方案如下。

# 方法1
# 定义NbCharField类,它继承自serializers.IntegerField
class NbCharField(serializers.IntegerField):
# 初始化方法,接收method_name参数,并将其存储在实例变量self.method_name中,默认为None
def __init__(self, method_name=None, **kwargs):
self.method_name = method_name or None # 设置默认值为None
super().__init__(**kwargs)

# 重写bind方法,将field_name与父类绑定,并根据method_name的设置动态生成属性获取方法名
def bind(self, field_name, parent):
if self.method_name is None:
# 如果method_name未指定,则使用"get_{field_name}"格式自动生成
self.method_name = f'get_{field_name}'

# 格式化并设置method_name
super().bind(field_name, parent)

# 重写get_attribute方法,从模型实例中通过parent类上的method_name所指向的方法获取gender属性的实际值
def get_attribute(self, instance):
method = getattr(self.parent, self.method_name)
return method(instance)

# 重写to_representation方法,将获取到的gender属性值转换为字符串形式输出(改变序列化输出)
def to_representation(self, value):
return str(value)


# 定义NbModelSerializer类,继承自serializers.ModelSerializer
class NbModelSerializer(serializers.ModelSerializer):
# 定义gender字段,使用NbCharField序列化器,并关联`get_gender_display`方法
gender = NbCharField(method_name='get_gender_display')

class Meta:
# 指定模型类和需要序列化的字段
model = models.NbUserInfo
fields = ['id', 'name', 'age', 'gender']
# 额外配置:设置id字段为只读
extra_kwargs = {'id': {'read_only': True}}

# 在NbModelSerializer类中定义用于获取gender显示值的方法
def get_gender_display(self, obj):
# 使用Django模型的get_FOO_display方法获取gender字段对应的显示文本
return obj.get_gender_display()



# 方法2(推荐)
# 钩子类,重写序列化名字
class NbHookModelSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
ret = OrderedDict()
fields = self.__readable_fields

for field in fields:
# 此处修改钩子函数的名字
if hasattr(self, 'nb_%s'%field.field_name):
value = getattr(self,'nb_%s'%field.field_name)(instance)
ret[field.field_nane] = value
else:
try:
attribute = field.get_attribute(instance)
except SkipField:
continue

check_for_none = attribute.pk if isinstance(attribute, PKOnly0bject) else attribute
if check_for_none is None:
ret[field.field_name] = None
else:
ret[field.field_name] = field.to_representation(attribute)
return ret


class NbModelSerializer(NbHookModelSerializer):
class Meta:
model = models.NbUserInfo
fields = ["id", "name", "age", "gender"]
extra_kwargs = {
"id": {"read_only": True}
}

# 重新设置gender的输出
def nb_gender(self,obj):
return obj.get_gender_display()

十、扩展知识

1 RESTful API

REST(Representational State Transfer),有代表性的状态转移,是一组设计原则和约束条件。

传统的API设计通常基于远程过程调用(RPC),使用不同的端点和动词来表示不同的操作,如get_user,delete_user。数据通常通过请求体或查询参数进行传递,状态管理通常由服务器维护。

RESTful API设计基于REST原则,强调资源的概念,每个资源都有唯一的标识符(URI),通过标准HTTP方法对资源执行各种操作(GET、POST、PUT、DELETE)。资源的状态和数据在请求和响应之间传递,客户端和服务器之间没有状态交互,即无状态通信。RESTful API通常使用JSON或XML作为数据格式,使得API更加灵活和可扩展。

实际使用RESTful API时,由于PUT和DELETE可能存在安全问题,所以通常只使用GET和POST。

1.1 获取用户数据

HTTP 方法: GET

URI:/api/users

功能描述:列出系统中所有的用户信息。

// 请求方式
GET /api/users

// 响应数据(状态码200 OK)
[
{
"id": 1,
"username": "user1",
"email": "user1@example.com"
},
{
"id": 2,
"username": "user2",
"email": "user2@example.com"
}
]

1.2 创建新用户

HTTP 方法: POST

URI:/api/users

功能描述:创建一个新的用户账户。

// 请求方法
POST /api/users
Content-Type: application/json
{
"username": "newuser",
"email": "newuser@example.com",
"password": "password123"
}

// 响应数据(状态码201 Created)
{
"message": "User created successfully",
"userId": 3
}

1.3 更新用户数据

HTTP 方法: PUT 或 PATCH

URI:/api/users/{userId}

功能描述:更新指定ID用户的全部信息(PUT)或部分信息(PATCH)。

但是实际应用中,PUT或PATCH协议本身存在漏洞,可以用POST方法代替,URI不变。

// 请求方法PUT
PUT /api/users/3
Content-Type: application/json
{
"username": "updateduser",
"email": "updateduser@example.com"
}

// 响应数据(状态码200 OK 或 204 No Content): 对于PUT请求,可能返回更新后的完整用户信息;对于PATCH请求,通常不返回内容或仅返回成功提示。

// 请求方法POST
POST /api/users/3
Content-Type: application/json
{
"action": "update"
"data": {
"username": "updateduser",
"email": "updateduser@example.com"
}
}

1.4 删除用户数据

HTTP 方法: DELETE

URI:/api/users/{userId}

功能描述:删除指定ID的用户。

但是实际应用中,DELETE协议本身存在漏洞,可以用POST方法代替,URI不变。

// 请求方法DELETE
DELETE /api/users/3

// 响应示例(状态码204 No Content): 成功删除用户后,服务器通常不会返回任何具体的内容,仅通过HTTP状态码表明操作成功。

// 请求方法POST
POST /api/users/3
Content-Type: application/json
{
"action": "delete"
}

1.5 综合API开发

结合上述观点,在设计RESTful API时可以按下列方式设计。

api / 版本号 / 操作对象 / { id }

// 获取用户数据
GET /api/v1/users

// 通过ID获取用户数据
GET /api/v1/users?userId={userId}

// 通过性别获取用户数据
GET /api/v1/users?gender={gender}

// 通过年龄获取用户数据
GET /api/v1/users?age={age}


// 创建新用户
POST /api/v1/users/{userId}
Content-Type: application/json
{
"action": "create",
"data": {
"username": "newuser",
"email": "newuser@example.com",
"password": "password123"
}
}


// 更新用户数据
POST /api/v1/users/{userId}
Content-Type: application/json
{
"action": "update",
"data": {
"username": "updateduser",
"email": "updateduser@example.com"
}
}


// 删除用户数据
POST /api/v1/users/{userId}
Content-Type: application/json
{
"action": "delete"
}

2 JWT

2.1 概念

定义:json web token,一般用于用户认证。(前后端分离 / 微信小程序 / app开发)

基于传统的token认证:用户登录时,服务端会返回token,并将token保存在服务端。以后用户再访问时,需要携带token,服务端再获取token后进行数据库中的校验。

基于JWT(JSON Web Token):用户登录时,服务端给用户返回一个token,服务端不保存token。以后用户再来访问时,需要携带token,服务端获取token后再进行校验。

比较:

  • JWT优势:相较于传统的token相比,它无需再服务端保存token。

2.2 使用

official::JSON Web Tokens - jwt.io

JWT字符串:

  • 第一段:HEADER,内部包含算法 / token类型。这部分json转化成字符串后,用base64编码。
  • 第二段:PAYLOAD,返回给前端的自定义内容(不返回敏感信息)。这部分json转化成字符串后,用base64编码。
  • 第三段:VERIFY SIGNATURE。先将1,2部分密文拼接起来,然后进行Hash256(alg所指向的算法),也可以加盐,之后进行base64编码。

token校验:

  1. 对token切割。
  2. 对第二段base64解码,检查是否超时。
  3. 将第1,2部分拼接,然后进行hash256加密(有盐加盐),比较值是否一致。
// Header
{
"alg": "HS256",
"typ": "JWT"
}

// Payload
{
"id": "123456",
"name": "Bob",
"exp": 3600, // 过期时间
}

// verify signature
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
[your-256-bit-secret] // 加盐
)

2.3 普通使用

安装:pip install pyjwt

import jwt
from jwt import exceptions
import datetime

SALT = "123456"

# JWT加密
def JWT_encode(id, name):
headers = {
"typ": "jwt",
"alg": "HS256"
}
payload = {
"id": id,
"name": name,
# 超时时间
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=5)
}
result = jwt.encode(payload=payload, key=SALT, algorithm="HS256",headers=headers).decode("utf-8")

return {"code": 200, 'data': result}

# JWT解密
def JWT_deconde(token):
payload = None
msg = None
try:
# True:在解码的同时验证签名。
payload = jwt.decode(token, SALT, True)
except exceptions.ExpiredSignatureError:
msg = "token已失效"
except jwt.DecodeError:
msg = "token认证失败"
except jwt.InvalidTokenError:
msg = "非法的token"
if not payload:
return {"code": 403, "error": msg}

# 拿到paylaod里的值
# print(payload['id'], payload["name"])

return {"code": 200}

2.4 DRF中使用

安装:pip install pyjwt

注:不要去安装djangorestframework-jwt,本质上这个库就是在调用pyjwt,只不过封装了一些别的东西,建议直接用pyjwt

# jwt生成函数
import jwt
import datetime
from django.conf import settings

def create_token(payload: dict, timeout: int = 1):
secret_key = settings.SECRET_KEY

headers = {
"typ": "jwt",
"alg": "HS256"
}

payload["exp"] = datetime.datetime.utcnow() + datetime.timedelta(minutes=timeout)

token = jwt.encode(payload=payload, key=secret_key, algorithm="HS256", headers=headers).encode("utf-8")

return token


# jwt认证类
import jwt
from django.conf import settings
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

class JwtQueryParamsAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token", "")

secret_key = settings.SECRET_KEY

try:
# 使用 secret_key 替换原本的 SALT 变量
payload = jwt.decode(token, secret_key, algorithms=["HS256"], options={"verify_exp": True})

except jwt.ExpiredSignatureError:
raise AuthenticationFailed({"code": 403, "error": "token已失效"})

except jwt.DecodeError:
raise AuthenticationFailed({"code": 403, "error": "token认证失败"})

except jwt.InvalidTokenError:
raise AuthenticationFailed({"code": 403, "error": "非法的token"})

return (payload, token)

十一、简单案例