DRF
Django REST framework
测试工具:official::Postman
text::Django
official::Django REST framework
一、基础 1 配置DRF
安装:pip install djangorestframework
本质上Django REST framework是写一个写好的APP。
安装完成后到setting.py
中注册
INSTALLED_APPS = [ ... , 'rest_framework' , ]
2 生命周期
闭包中的view(免除了CSRF认证)
先创建CBV的视图对象
然后执行dispatch函数:
请求封装
认证
权限
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。
@api_view(['GET' ] ) def login (resquest ): return Response({'status' : True , 'message' : 'success' }) 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实现是通过继承View
:Userview(View)
DRF的CBV实现通过继承APIView
:Userview(APIView)
APIView
的实质是继承了View
,在返回函数的基础上加入了一些操作:
APIView
的as_view()
返回是return csrf_exempt(view)
,会免除掉CSRF校验。
APIView
的dispatch()
方法还负责处理异常、权限检查以及其它请求前后的逻辑处理。
from django.urls import pathfrom app01 import viewsurlpatterns = [ path('auth/' , views.auth), path('user/' , views.UserView.as_view()), ]
from django.http import JsonResponsefrom django.views import Viewdef 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' : "..." }) 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" }) 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.method request.method request._request.GET.get("token" ) request.query_params.get("token" ) request._request.body.get("token" ) request.data.get("token" )
4 自定义响应码
在返回的JSON中,可以用一个python文件存储所有的响应码,在使用时导入这个响应码,这样将便于维护。
... return Response({"code" :code.ERROR_CODE, "msg" :"failed" }) ...
三、认证 1 request请求用户
request.user
:访问用户,默认是匿名用户(AnonymousUser,None),可以在setting.py通过配置修改。
from rest_framework.views import APIViewfrom rest_framework.response import Responseclass UserView (APIView ): def get (self, request ): print (request.user) print (request.auth) return Response("success" )
REST_FRAMEWORK = { "UNAUTHENTICATED_USER" : None }
2 用户认证
用户认证流程:
读取请求传递的token
校验合法性
返回值
认证类:
authenticate(self, request)
:执行认证,通过返回值判断认证成功
authenticate_header(self, request)
:认证失败时,响应头中www-Authenticate
字段的值。(要重写该函数,不重写的话响应类型将会是403而非401)
authenticate(self, request)
返回值:
返回元组(para1,para2)
:认证成功,request.user = para1,request.auth = para2
抛出异常:认证失败->返回错误信息
返回None:用于多个认证类,[类1,类2,类3,类4,类5, … ] -> 匿名用户(当类1返回None时,视图类将会去认证类2,以此类推,都不通过则返回匿名用户)
在写好自定义类以后,可以选择一个一个视图类添加认证,也可以选择在配置文件中直接进行配置,配置完成后,所有的APIView
都会需要进行对应的认证。
注:编写时,若全局引用了,不能将认证类放在views.py
中。否则会产生循环调用的问题(views.py
调用APIView
,APIView
中调用setting.py
,setting.py
又调用了views.py
中的认证)
from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.authentication import BaseAuthenticationfrom rest_framework.exceptions import AuthenticationFailedclass 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" class UserView (APIView ): authentication_classes = [MyAuthentication, ] def get (self, request ): return Response("Hello World!" )
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES" : ["自定义认证类的路径" , ] }
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的操作
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 ) class TestView (APIView ): 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 )
四、权限 1 权限定义
权限类:
allow_request(self, request)
:设置权限,通过返回的Bool值判断是否通过。
权限组件的执行与认证组件不同。认证组件在写好 [类1,类2,类3,类4,类5, … ] 后,将通过返回值判断是否执行下一个类,只要有一个成功就可以。而权限组件会执行所有的类。默认情况下,要保证所有的权限类中的allow_request(self, request)
都返回True才算具备权限(也可以进行扩展和自定义修改权限验证方式)。
from rest_framework.permissions import BasePermissionclass 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!" )
REST_FRAMEWORK = { "DEFAULT_PERMISSION_CLASSES" : ["自定义权限类的路径" , ] }
2 修改认证机制
由源码可知,权限认证主要是check_permissions(self, request)
函数。在源码中显示,只要列表中有一个权限不满足,直接就报错。因此,可以将该函数重写,完成认证机制的修改。
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 SimpleRateThrottleclass 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 else : ident = self.get_ident(request) return self.cache_format % {'scope' : self.scope, 'ident' : ident} class UserView (APIView ): throttle_classes = [MyThrottle, ] def get (self, request ): return Response("Hello World!" )
REST_FRAMEWORK = { "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 APIViewfrom rest_framework.response import Responsefrom rest_framework.versioning import QueryParameterVersioningclass HomeView (APIView ): versioning_class = QueryParameterVersioning def get (self, request ): print (request.version) return Response("..." )
urlpatterns= [ path('home/' , views.HomeView.as_view(), name="homeView" ) ] 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/
来表示版本
urlpatterns= [ path('api/<str:version>/home/' , views.HomeView.as_view(), name="homeView" ) ] from rest_framework_versioning import URLPathVersioningclass HomeView (APIView ): versioning_class = URLPathVersioning def get (self, request, *args, **kwargs ): print (request.version)
3 请求头传递版本
通过在请求头中携带参数来传递版本。
在请求头中加上Accept: application/json; version=v1
来传递版本
urlpatterns= [ path('api/home/' , views.HomeView.as_view(), name="homeView" ) ] from rest_framework_versioning import URLPathVersioningclass HomeView (APIView ): versioning_class = URLPathVersioning def get (self, request, *args, **kwargs ): url = request.versioning_scheme.reverse("homeView" , request=request) print (url)
4 全局配置 REST_FRAMEWORK = { "VERSION_PARAM" : "version" , "DEFAULT_VERSION" : "v1" , "ALLOWED_VERSIONS" : ["v1" , "v2" ], "DEFAULT_VERSIONING_CLASS" : "rest_framework.versioning.URLPathVersioning" }
七、解析器 1 系统解析器
定义:解析请求者发过来的数据。
解析流程:
读取请求头
根据请求头不同解析数据
系统解析器:
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.jpg
,Content-Type: */*
不配置解析器默认是:parser_classes =[JSONParser, FormParser, MultiPartParser]
渲染器content_negotiation_class
一般不变。
from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework. parsers import JSONParser,FormParserfrom rest_framework.negotiation import DefaultContentNegotiationclass HomeView (APIView ): parser_classes =[JSONParser, FormParser] content_negotiation_class = DefaultContentNegotiation def get (self,request,*args,**kwargs ): return Response("Get" )
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中
class Foo (object ): v1 = 123 def func (self ): return 999 Foo = type ("Foo" , (object , ), {"v1" : 123 , "func" : lambda self: 999 }) obj = Foo() print (obj.v1)print (obj.func())
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 class Base (object , metaclass=MyType): v1 = 123 def func (self ): return 999 class Foo (object ): pass
2 Serializer
目的:将从数据库中获取的QuerySet或数据对象转化成JSON格式
from rest_framework import serializersclass 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() ser = DepartSerializer(instance=depart_object) query_set = models.Depart.objects.all ().first() ser = DepartSerializer(instance=query_set, many=True ) context = {"status" : True , "data" : ser.data} return Response(content)
3 ModelSerializer
类似Django的ModelForm
from django.db import modelsclass 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 )
class DepartSerializer (serializers.ModelSerializer): class Meta : model = models.Depart fields = "__all__" class UserSerializer (serializers.ModelSerializer): 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" ] def get_func (self, obj ): return "{}-{}-{}" .format (obj.name, obj.age, obj.gender) class UserSerializer (serializers.ModelSerializer): xxx = serializers.SerializerMethodField() class Meta : model = models.UserInfo field = ["name" , "age" , "xxx" ] def get_xxx (self, obj ): query_set = obj.tags.all () result = [{"id" : tag.id , "caption" : tag.caption} for tag in query_set] return result
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() tags = D2(many=True ) class Meta : model = models.UserInfo fields = ["name" , "age" , "depart" , "tags" ] 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数据校验
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="邮箱格式错误" )]) 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) ser.is_valid(raise_exception=True ) models.Depart.objects.create(**ser.validated_data) return Response(ser.validated_data)
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="邮箱格式错误" )]) 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
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 }, } class DepartView (APIView ): def post (self, request, *args, **kwargs ): ser = DepartSerializer(data=request.data) if ser.is_valid(): 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 serializersfrom myapp.models import MyModelclass MyModelSerializer (serializers.ModelSerializer): class Meta : model = MyModel fields = '__all__' class MyModelListSerializer (serializers.ListSerializer): child = MyModelSerializer() my_model_objects = MyModel.objects.all () serializer = MyModelListSerializer(instance=my_model_objects, many=True ) serialized_data = serializer.data
九、数据校验 1 Serializer校验
序列化器有多种用法:
序列化:路由——视图——去数据库中获取对象——利用序列化器转化成列表、字典等——JSON处理
数据校验:路由——视图——request.data
——校验(利用序列化器的类)——操作(数据库操作,利用序列化器的类)
综合案例:在创建用户动作中,先对获得的数据进行校验(利用序列化器的类),再将数据进行保存,然后将新增的数据返回,此时一样用统一个类进行序列化(利用序列化器的类)
钩子函数验证:先执行上面设置的基础验证(例如:CharField),然后进行钩子验证,最后全局验证。
from django.core.validators import EmailValidator,RegexValidatorclass 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.chanField(validators=[RegexValidator(r"\d+" ,message="邮箱格式错误" ),]) def validate_email (self, value ): if len (value) > 6 : raise exceptions.ValidationError("字段钩子校验失败" ) return value def validate (self, att ): print ("validate=" , attrs) return attrs class DepartView (APIView ): def post (self, request, *args,**kwargs ): ser = DepartSerializer(data=request.data) if ser.is_valid(): print (ser.validated_data) else : print (ser.errors) return Response("成功" ) ser = DepartSerializer(data=request.data) ser.is_valid(raise_exception=True ) print (ser.validate_data) return Response("成功" )
REST_FRAMEWORK = { "NON_FIELD_ERRORS_KEY" : "xxxx" }
2 ModelSerializer校验 class DepartModelSerializer (serializers.ModelSerializer): I class Meta : model = models.Depart fields = ["title" ,"order" ,"count" ] extra_kwargs = { "id" : {"read_only" : True }, "title" : {"max_length" : 5 , "min_length" :1 }, "order" : {"min_value" : 53 }, "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,输入和输出要是不同的结果,解决方案如下。
class NbCharField (serializers.IntegerField): def __init__ (self, method_name=None , **kwargs ): self.method_name = method_name or None super ().__init__(**kwargs) def bind (self, field_name, parent ): if self.method_name is None : self.method_name = f'get_{field_name} ' super ().bind(field_name, parent) def get_attribute (self, instance ): method = getattr (self.parent, self.method_name) return method(instance) def to_representation (self, value ): return str (value) class NbModelSerializer (serializers.ModelSerializer): gender = NbCharField(method_name='get_gender_display' ) class Meta : model = models.NbUserInfo fields = ['id' , 'name' , 'age' , 'gender' ] extra_kwargs = {'id' : {'read_only' : True }} def get_gender_display (self, obj ): return obj.get_gender_display() 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 } } 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 [ { "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" } { "message" : "User created successfully" , "userId" : 3 }
1.3 更新用户数据
HTTP 方法: PUT 或 PATCH
URI:/api/users/{userId}
功能描述:更新指定ID用户的全部信息(PUT)或部分信息(PATCH)。
但是实际应用中,PUT或PATCH协议本身存在漏洞,可以用POST方法代替,URI不变。
PUT /api/users/3 Content-Type: application/json { "username" : "updateduser" , "email" : "updateduser@example.com" } 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 /api/users/3 POST /api/users/3 Content-Type: application/json { "action" : "delete" }
1.5 综合API开发
结合上述观点,在设计RESTful API时可以按下列方式设计。
api / 版本号 / 操作对象 / { id }
GET /api/v1/users 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校验:
对token切割。
对第二段base64解码,检查是否超时。
将第1,2部分拼接,然后进行hash256加密(有盐加盐),比较值是否一致。
{ "alg" : "HS256" , "typ" : "JWT" } { "id" : "123456" , "name" : "Bob" , "exp" : 3600 , } HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), [ your-256 -bit-secret] )
2.3 普通使用
安装:pip install pyjwt
import jwtfrom jwt import exceptionsimport datetimeSALT = "123456" 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} def JWT_deconde (token ): payload = None msg = None try : 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} return {"code" : 200 }
2.4 DRF中使用
安装:pip install pyjwt
。
注:不要去安装djangorestframework-jwt
,本质上这个库就是在调用pyjwt
,只不过封装了一些别的东西,建议直接用pyjwt
。
import jwtimport datetimefrom django.conf import settingsdef 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 import jwtfrom django.conf import settingsfrom rest_framework.authentication import BaseAuthenticationfrom rest_framework.exceptions import AuthenticationFailedclass JwtQueryParamsAuthentication (BaseAuthentication ): def authenticate (self, request ): token = request.query_params.get("token" , "" ) secret_key = settings.SECRET_KEY try : 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)
十一、简单案例