django restframework是基于django和restful协议开发的框架
在restful协议里,一切皆是资源,操作是通过请求方式控制
在开始前,你需要对CBV和FBV两种架构模式有个大概的了解,django restframework用的就是CBV架构,这里提供一篇博客供你欣赏CBV
安装:pip install djangorestframework
django原生request
首先看到下面这段代码
def post(self, request): print(request.body) print(request.POST)
请求数据一般封装在请求头中,而上面打印的数据,都是经过处理后的数据,那背后是怎么进行封装的呢?
对于get请求,直接去url后面的数据
而对于post请求
request.body: a=1&b=2 request.POST: if contentType:urlencoded: a=1&b=2 ---> {"a":1,"b":2}
当发urlencoded数据时,两个都能打印数据,但是如果就发json数据,就只有request.body里有了
request源码剖析
怎么看源码呢?打印下request的类型就可以了,print(type(request)), 查看打印的WSGIRequest就可以了
from django.core.handlers.wsgi import WSGIRequest
去它下面找POST,发现这么一句
POST = property(_get_post, _set_post)
在_get_post方法中,进入这个方法self._load_post_and_files(),在它里面就这么一段代码
elif self.content_type == 'application/x-www-form-urlencoded': #如果urlencoded类型才把self.body赋给了self._post self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict() else: self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()
restframework request
对于restframework的request,我们需要了解下在restframework里请求流程,它和django大致相同,因为它的APIView继承是django的View,但在APiView中重写了dispatch方法
看到这段代码
url(r'^publishers/$', views.PublishViewSet.as_view(),name="publish_list"),
执行PublishViewSet就是APIView的as_view方法
class APIView(View):
APIView继承了View,APIView中有as_view方法,所以会执行这个方法,方法中有这么一句代码
view = super(APIView, cls).as_view(**initkwargs)
最终还是执行了父类里的as_view方法,所以最终执行结果,得到这么这个view函数
def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs)
当请求来时,会执行view函数,把dispatch结果返回,而这里dispatch方法则不是View里的,而是APIView的,因为APIView重写了这个方法,而django restframework的精髓就全部在这里边了
def dispatch(self, request, *args, **kwargs): """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ self.args = args self.kwargs = kwargs #构建一个新的request,把旧的request封装给新的request的_request字段 #执行.data会执行新request类中的data属性,而在self._load_data_and_files()里 ''' if not _hasattr(self, '_data'): self._data, self._files = self._parse() if self._files: self._full_data = self._data.copy() self._full_data.update(self._files) else: self._full_data = self._data ''' #最终返回self._full_data,而上面就是对请求内容进行解析并封装 request = self.initialize_request(request, *args, **kwargs) ''' #post print("request.data", request.data) print("request.data type", type(request.data)) #get print(request._request.GET) print(request.GET) ''' self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request, *args, **kwargs) # Get the appropriate handler method if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed #获取get post等方法后执行,这里用是新的request response = handler(request, *args, **kwargs) except Exception as exc: response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
在dispatch方法,通过请求方式映射,获取到我们写好的请求方法,并执行
序列化
restframework在前后端传输数据时,主要是json数据,过程中就要需要把其他数据转换成json数据,比如数据库查询所有数据时,是queryset对象,那就要把这对象处理成json数据返回前端
models
from django.db import models # Create your models here. class Book(models.Model): title=models.CharField(max_length=32) price=models.IntegerField() pub_date=models.DateField() publish=models.ForeignKey("Publish", on_delete=True) authors=models.ManyToManyField("Author") def __str__(self): return self.title class Publish(models.Model): name=models.CharField(max_length=32) email=models.EmailField() def __str__(self): return self.name class Author(models.Model): name=models.CharField(max_length=32) age=models.IntegerField() def __str__(self): return self.name
那么这里提供三种序列化的方式:
- 对查询的数据类型进行基础数据类型的强转,比如list(queryset对象.values('name', 'sex')), 单个数据对象 model_to_dict(obj)
- django提供的serialize方法,data=serializers.serialize("json",book_list)
- restframework提供的serialize方法,但事先要定义序列化模型,BookSerializers(book_list,many=True),并且使用restframework提供Response返回数据(需要注意的是,你必现在app里注册了rest_framework)
from django.shortcuts import render, HttpResponse from rest_framework.views import APIView from .models import * # Create your views here. from rest_framework import serializers from rest_framework.response import Response class AuthorSerializers(serializers.Serializer): name = serializers.CharField(max_length=32) age = serializers.IntegerField() class AuthorView(APIView): def get(self, request, *args, **kwargs): authors = Author.objects.all() # 方式1 # data = authors.values("name","age") # from django.forms.models import model_to_dict # data = [] # for obj in authors: # data.append(model_to_dict(obj)) #方式2 # from django.core import serializers # data = serializers.serialize("json", authors) # return HttpResponse(data) #方式3 author_ser = AuthorSerializers(authors, many=True) # return Response(author_ser.data)
上面也只是说了下单表序列化,如果表中涉及到一对多,多对多怎么操作呢?
- 一对多,通过source="publish.name"指定字段
- 多对多,通过get_字段名钩子函数来定义要获取的内容
from django.shortcuts import render, HttpResponse from rest_framework.views import APIView from .models import * # Create your views here. from rest_framework import serializers from rest_framework.response import Response class BookSerializers(serializers.Serializer): title = serializers.CharField(max_length=32) price = serializers.IntegerField() pub_date = serializers.DateField() # 一对多 # publish = serializers.CharField() #不加source时,默认给的是Publish模型定义__str__返回的字段 publish = serializers.CharField(source="publish.name") # 多对多 # authors = serializers.CharField(source="authors.all") #获取是一个queryset对象 字符串 authors = serializers.SerializerMethodField() #通过钩子函数自定制需要的信息 def get_authors(self, obj): temp = [] for author in obj.authors.all(): temp.append({'name':author.name, 'email':author.age}) return temp class BookView(APIView): def get(self, request, *args, **kwargs): books = Book.objects.all() bs = BookSerializers(books, many=True) return Response(bs.data)
当然上面的过程,定义serializers模型,针对表定义每个字段,有些繁琐,所以序列化模型也有类似于ModelForm用法,不过一对多,多对多都是默认值,取得都是对应对象的id
如果你想定制多对多和一对多,在ModelSerializers重写这类型字段,但是需要注意的是,里面提供的create方法不支持source定制,所以你还需要重写create方法
from django.shortcuts import render, HttpResponse from rest_framework.views import APIView from .models import * # Create your views here. from rest_framework import serializers from rest_framework.response import Response class BookSerializers(serializers.ModelSerializer): class Meta: model = Book fields = "__all__" #source指定字段,不影响get查看序列化,但是影响post创建数据,所以需要你重写create方法 publish = serializers.CharField(source='publish.name') authors = serializers.SerializerMethodField() #通过钩子函数自定制需要的信息 def get_authors(self, obj): temp = [] for author in obj.authors.all(): temp.append({'name':author.name, 'email':author.age}) return temp def create(self, validated_data): # author_list = validated_data.pop("authors") obj = Book.objects.create(**validated_data) # obj.authors.add(*author_list) return obj class BookView(APIView): def get(self, request, *args, **kwargs): books = Book.objects.all() bs = BookSerializers(books, many=True) return Response(bs.data) def post(self, request, *args, **kwargs): bs=BookSerializers(data=request.data, many=False) if bs.is_valid(): print(bs.validated_data) bs.save() return Response(bs.data) else: return HttpResponse(bs.errors)
序列化超链接:HyperlinkedIdentityField序列化字段,指定三个参数 view_name url别名,lookup_field填入链接里的值对应字段, lookup_url_kwarg url里对应的形参名
class BookSerializers(serializers.ModelSerializer): publish= serializers.HyperlinkedIdentityField( view_name='publish_detail', lookup_field="publish_id", lookup_url_kwarg="pk") class Meta: model=Book fields="__all__" #depth=1
urlpatterns = [ url(r'^books/$', views.BookViewSet.as_view(),name="book_list"), url(r'^books/(?P<pk>d+)$', views.BookDetailViewSet.as_view(),name="book_detail"), url(r'^publishers/$', views.PublishViewSet.as_view(),name="publish_list"), url(r'^publishers/(?P<pk>d+)$', views.PublishDetailViewSet.as_view(),name="publish_detail"), ]
对于重写create方法情景的总结(Django 1.10.1):
1.序列化类继承serializers.Serializer,需要重写create,update方法,看源码这两个方法只是定义了抛错
class AuthorSerializers(serializers.Serializer): name = serializers.CharField(max_length=32) age = serializers.IntegerField() def create(self, validated_data): obj = Author.objects.create(**validated_data) return obj
2.表对象为单表,序列化类继承serializers.ModelSerializer,不需要重写create,update方法,源码里就在这个类下实现了这两个方法,另外这两个方法是在序列化类下,所以它跟你使用哪个视图类无关,比如使用generics.ListCreateAPIView
class PublishSerializers(serializers.ModelSerializer): class Meta: model = Publish fields = "__all__"
3.表对象为关联表,序列化类继承serializers.ModelSerializer,不存在定制某个字段,不需要重写create,update方法
class BookSerializers(serializers.ModelSerializer): class Meta: model = Book #book表关联表,有一对多,多对多 fields = "__all__"
4.表对象为关联表,序列化类继承serializers.ModelSerializer,source指定某个字段,提示指定字段要发实例(一对多),存在的疑问就是怎么在前端添加实例?
多对多,如果通过SerializerMethodField生成,序列化时,不提供多对多字段传过来的值,等同这种情景下,多对多只是只读显示,不用来post添加操作,如果要操作把添加对象和多对多添加分成两步操作
class BookSerializers(serializers.ModelSerializer): class Meta: model = Book #book表关联表,有一对多,多对多 fields = "__all__" authors = serializers.SerializerMethodField() def get_authors(self, obj): temp = [] for author in obj.authors.all(): temp.append({'name':author.name, 'email':author.age}) return temp publish = serializers.CharField(source="publish.email") def create(self, validated_data): # authors = validated_data.pop("authors") obj = Book.objects.create(**validated_data) # obj.authors.add(*authors) return obj
视图
第一阶段 老老实实的干
下面视图代码,可以说是规规矩矩的做法,每个视图类下,都写各个请求方法,细心的你肯定发现了,每个视图的同类请求方法实现过程处理序列化模型和查询的表不同,其他的都一样,代码重复
from rest_framework.views import APIView from rest_framework.response import Response from .models import * from django.shortcuts import HttpResponse from django.core import serializers from rest_framework import serializers class BookSerializers(serializers.ModelSerializer): class Meta: model=Book fields="__all__" #depth=1 class PublshSerializers(serializers.ModelSerializer): class Meta: model=Publish fields="__all__" depth=1 class BookViewSet(APIView): def get(self,request,*args,**kwargs): book_list=Book.objects.all() bs=BookSerializers(book_list,many=True,context={'request': request}) return Response(bs.data) def post(self,request,*args,**kwargs): print(request.data) bs=BookSerializers(data=request.data,many=False) if bs.is_valid(): print(bs.validated_data) bs.save() return Response(bs.data) else: return HttpResponse(bs.errors) class BookDetailViewSet(APIView): def get(self,request,pk): book_obj=Book.objects.filter(pk=pk).first() bs=BookSerializers(book_obj,context={'request': request}) return Response(bs.data) def put(self,request,pk): book_obj=Book.objects.filter(pk=pk).first() bs=BookSerializers(book_obj,data=request.data,context={'request': request}) if bs.is_valid(): bs.save() return Response(bs.data) else: return HttpResponse(bs.errors) class PublishViewSet(APIView): def get(self,request,*args,**kwargs): publish_list=Publish.objects.all() bs=PublshSerializers(publish_list,many=True,context={'request': request}) return Response(bs.data) def post(self,request,*args,**kwargs): bs=PublshSerializers(data=request.data,many=False) if bs.is_valid(): # print(bs.validated_data) bs.save() return Response(bs.data) else: return HttpResponse(bs.errors) class PublishDetailViewSet(APIView): def get(self,request,pk): publish_obj=Publish.objects.filter(pk=pk).first() bs=PublshSerializers(publish_obj,context={'request': request}) return Response(bs.data) def put(self,request,pk): publish_obj=Publish.objects.filter(pk=pk).first() bs=PublshSerializers(publish_obj,data=request.data,context={'request': request}) if bs.is_valid(): bs.save() return Response(bs.data) else: return HttpResponse(bs.errors)
第二阶段 mixin封装类编写
restframework已经对我们使用的这些请求方法,封装到一些类里面,我们只要继承这些类,并调用特定的方法,把结果返回即可
至于是哪个序列化模型和哪个模型的数据,通过静态字段queryset和serializer_class指定
而GenericAPIView继承了APIView,所以在程序启动时,执行的还是APIView的as_view方法
代码简化如下
from rest_framework import mixins from rest_framework import generics class BookViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): queryset = Book.objects.all() serializer_class = BookSerializers def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) class BookDetailViewSet(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView): queryset = Book.objects.all() serializer_class = BookSerializers def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs)
我们大概可以看下这些类封装方法的实现过程,其中源码中的方法可以在GenericAPIView类找到
def list(self, request, *args, **kwargs): #self.get_queryset()获取静态字段指定数据 #filter_queryset支持配置文件筛选数据 queryset = self.filter_queryset(self.get_queryset()) #分页 page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) #get_serializer获取静态字段中指定的序列化类,并实例对象 serializer = self.get_serializer(queryset, many=True) return Response(serializer.data)
第三阶段 通用的基于类
当然上面这个实现过程,在定义请求方法,还是有点重复,rest框架提供了一组已经混合好(minxed-in)的通用视图进一步封装
generics.ListCreateAPIView 查看多条和添加视图
generics.RetrieveUpdateDestroyAPIView 查看单条,修改和删除视图
from rest_framework import mixins from rest_framework import generics class BookViewSet(generics.ListCreateAPIView): queryset = Book.objects.all() serializer_class = BookSerializers class BookDetailViewSet(generics.RetrieveUpdateDestroyAPIView): queryset = Book.objects.all() serializer_class = BookSerializers class PublishViewSet(generics.ListCreateAPIView): queryset = Publish.objects.all() serializer_class = PublshSerializers class PublishDetailViewSet(generics.RetrieveUpdateDestroyAPIView): queryset = Publish.objects.all() serializer_class = PublshSerializers
点进去一个类,发现它其实就是通过多继承组合了第二阶段中的类
class ListCreateAPIView(mixins.ListModelMixin, mixins.CreateModelMixin, GenericAPIView): """ Concrete view for listing a queryset or creating a model instance. """ def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs)
第四阶段 viewsets.ModelViewSet
上面的代码已经够简洁了吧,它还可以简洁,你可以观察一下,就是在对所有的书操作和单本书操作,他们使用的数据和序列化模型的一样的,rest框架还做到这方面的简化,他们两本质的区别在于操作所有书是get,post方法,而单本就是get,put,delete方法,实现原理是在url上对请求方法进行映射
url(r'^books/$', views.BookViewSet.as_view({"get":"list","post":"create"}),name="book_list"), url(r'^books/(?P<pk>d+)$', views.BookViewSet.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }),name="book_detail"),
from rest_framework.viewsets import ModelViewSet
class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializers
而ModelViewSet实现过程也是多继承
class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet): pass
那我们再看下,最后这种方式在请求到来时,是怎么执行的,从多继承中看,找as_view函数
最终还是会找到ViewSetMixin下的as_view执行,把请求映射关系传给了actions
def as_view(cls, actions=None, **initkwargs):
传入到view函数中,闭包
def view(request, *args, **kwargs): self = cls(**initkwargs) # We also store the mapping of request methods to actions, # so that we can later set the action attribute. # eg. `self.action = 'list'` on an incoming GET request. self.action_map = actions # Bind methods to actions # This is the bit that's different to a standard view for method, action in actions.items(): handler = getattr(self, action) setattr(self, method, handler) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs # And continue as usual return self.dispatch(request, *args, **kwargs)
当请求来时,执行闭包的view函数,在下列这段代码进行请求方法的映射
for method, action in actions.items(): #method = get,post #action=list,create handler = getattr(self, action) #handler==self.list,self.create函数 setattr(self, method, handler) #self.get-->self.list, self.post-->self.create
执行dispatch下这段代码时
if request.method.lower() in self.http_method_names: #get请求时,执行self.list.... handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
认证组件
认证前提是需要登录的,所以你还需要写好登录
models
class User(models.Model): name = models.CharField(max_length=32) pwd = models.CharField(max_length=32) def __str__(self): return self.name class Token(models.Model): user = models.OneToOneField("User") token = models.CharField(max_length=128) def __str__(self): return self.token
登录视图
def get_random_str(user): import hashlib,time ctime=str(time.time()) md5=hashlib.md5(bytes(user,encoding="utf8")) md5.update(bytes(ctime,encoding="utf8")) return md5.hexdigest() from .models import User class LoginView(APIView): def post(self,request): name=request.data.get("name") pwd=request.data.get("pwd") user=User.objects.filter(name=name,pwd=pwd).first() res = {"state_code": 1000, "msg": None} if user: random_str=get_random_str(user.name) token=Token.objects.update_or_create(user=user,defaults={"token":random_str}) res["token"]=random_str else: res["state_code"]=1001 #错误状态码 res["msg"] = "用户名或者密码错误" import json return Response(json.dumps(res,ensure_ascii=False))
首先你必须明确的是,认证,权限,频率那都是请求到来时的操作,大概都能猜到会在分发dispatch下执行,那我们就找到APIView下dispatch看下,下面这几句代码
你会发现,在进入到认证..这些操作的时候,request已经是重新封装后的新的request
request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: #认证 权限 频率都在这里面做了 self.initial(request, *args, **kwargs)
在initial方法下,有这三句代码,分别对应认证,权限,频率组件
self.perform_authentication(request) self.check_permissions(request) self.check_throttles(request)
那我们就先看认证组件干了些什么事,一旦理解认证组件,其他的两个就好理解了,实现方式是类似的
在perform_authentication认证方法里,就一句代码,你可能惊呼,这啥啊,就这么一句?给人感觉是字段啊,但深究下,如果是字段肯定实现不了认证功能,你需要大胆猜测是静态方法,是不是?我们去这个request下找下就知道了,注意:这里request是新的request
def perform_authentication(self, request): """ Perform authentication on the incoming request. Note that if you override this and simply 'pass', then authentication will instead be performed lazily, the first time either `request.user` or `request.auth` is accessed. """ request.user
也就是这个返回的request
request = self.initialize_request(request, *args, **kwargs)
而它返回的就是这个request对象
return Request( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context )
去它下面还真找到user静态方法
@property def user(self): """ Returns the user associated with the current request, as authenticated by the authentication classes provided to the request. """ if not hasattr(self, '_user'): with wrap_attributeerrors(): self._authenticate() return self._user
最后它会执行_authenticate方法
def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. """ for authenticator in self.authenticators: try: user_auth_tuple = authenticator.authenticate(self) except exceptions.APIException: self._not_authenticated() raise if user_auth_tuple is not None: self._authenticator = authenticator self.user, self.auth = user_auth_tuple return self._not_authenticated()
要想搞明白上面代码,就必须要知道self.authenticators是啥?
在__init__方法里,有这么句,而authenticators在实例化对象传下来的
self.authenticators = authenticators or ()
实例化对象时,传入是下面这玩意,那它又干了啥呢?
authenticators=self.get_authenticators(),
下面self就是我们定义的视图类,它会去我们定义的类下面找authentication_classes,循环并实例化
def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """ return [auth() for auth in self.authentication_classes]
authentication_classes又是些啥了?从名字上看,就是认证类,所以这个是由你来定义的,但是如果我们不定义的呢?
当前类没有,就会找父类去中,一直找到APIView下
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
会去api_settings下取个DEFAULT_AUTHENTICATION_CLASSES默认的认证类
api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
None,DEFAULTS对应APISettings下__init__方法的user_settings和defaults
而DEFAULTS则是settings配置文件的变量,并配置这个,默认情况下就是这些认证类
'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ),
api_settings.DEFAULT_AUTHENTICATION_CLASSES会去执行APISettings的__getattr__方法
def __getattr__(self, attr): if attr not in self.defaults: raise AttributeError("Invalid API setting: '%s'" % attr) try: # Check if present in user settings #self.user_settings这里调用时静态方法 #attr=DEFAULT_AUTHENTICATION_CLASSES ''' @property def user_settings(self): #user_settings为None,所以这里hasattr(self, '_user_settings')为False if not hasattr(self, '_user_settings'): #尝试去settings配置文件找REST_FRAMEWORK这么个配置 self._user_settings = getattr(settings, 'REST_FRAMEWORK', {}) return self._user_settings ''' #如果在settings找REST_FRAMEWORK配置就返回配置,没找到就是{} #没配置空字典获取DEFAULT_AUTHENTICATION_CLASSES就报错,走异常分支 val = self.user_settings[attr] except KeyError: # Fall back to defaults ''' 去默认配置文件取到这两项 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ), ''' val = self.defaults[attr] # Coerce import strings into classes if attr in self.import_strings: val = perform_import(val, attr) # Cache the result self._cached_attrs.add(attr) setattr(self, attr, val) return val
所以我们可以得出
如果想局部配置认证类,在我们定义视图类下通过authentication_classes指定(列表)
如果想全局在settings下配置
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.TokenAuth",] }
回到request下user属性方法下的_authenticate方法
def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. """ for authenticator in self.authenticators: #authenticator 认证类实例 try: #调用认证类实例下的authenticate方法,传入了self,也就是request #所以你认证类要有authenticate方法,返回值为元组 user_auth_tuple = authenticator.authenticate(self) except exceptions.APIException: self._not_authenticated() raise if user_auth_tuple is not None: self._authenticator = authenticator #元组内容 用户信息 和 auth信息,我们用的token self.user, self.auth = user_auth_tuple return
认证类
from rest_framework.authentication import BaseAuthentication from rest_framework.authentication import exceptions class Authentication(BaseAuthentication): def authenticate(self, request): token = request.GET.get("token") token_obj = Token.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed("验证失败") else: return token_obj.user.name, token_obj.token
视图类
def get_random_str(user): import hashlib,time ctime=str(time.time()) md5=hashlib.md5(bytes(user,encoding="utf8")) md5.update(bytes(ctime,encoding="utf8")) return md5.hexdigest() from app01.service.auth import * from django.http import JsonResponse class LoginViewSet(APIView): authentication_classes = [Authentication,] #一般情况下登录页面是不需要认证的,你可以把这里改成空列表 def post(self,request,*args,**kwargs): res={"code":1000,"msg":None} try: user=request._request.POST.get("user") pwd=request._request.POST.get("pwd") user_obj=UserInfo.objects.filter(user=user,pwd=pwd).first() print(user,pwd,user_obj) if not user_obj: res["code"]=1001 res["msg"]="用户名或者密码错误" else: token=get_random_str(user) UserToken.objects.update_or_create(user=user_obj,defaults={"token":token}) res["token"]=token except Exception as e: res["code"]=1002 res["msg"]=e return JsonResponse(res,json_dumps_params={"ensure_ascii":False})
权限组件
了解认证组件的源码后,下面这两个组件就不带看了,直接看怎么用吧
权限类里重写has_permission方法
from rest_framework.permissions import BasePermission class SVIPPermission(BasePermission): message="SVIP才能访问!" def has_permission(self, request, view): if request.user.user_type==3: return True return False
视图类里通过permission_classe指定权限类
from app01.service.permissions import * class BookViewSet(generics.ListCreateAPIView): permission_classes = [SVIPPermission,] queryset = Book.objects.all() serializer_class = BookSerializers
全局配置
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",] }
访问频率组件
依据什么来判定它的访问频率,IP?访问相关的信息你都可以从request.META中获取
频率类里定义allow_request方法
from rest_framework.throttling import BaseThrottle class VisitThrottle(BaseThrottle): ''' 一分钟内访问超过5次 禁用两分钟 ''' VISIT_RECORD = {} def allow_request(self, request, view): remote_addr = request.META.get('REMOTE_ADDR') print(remote_addr) now_time = time.time() if remote_addr not in VisitThrottle.VISIT_RECORD: VisitThrottle.VISIT_RECORD[remote_addr] = { 'start_time' : [now_time,], 'forbid' : False } return True start_time = VisitThrottle.VISIT_RECORD[remote_addr]['start_time'] forbid_state = VisitThrottle.VISIT_RECORD[remote_addr]['forbid'] while start_time and start_time[-1] < now_time - 60 and not forbid_state: start_time.pop() if forbid_state: if now_time - start_time[-1] > 120: print('两分钟过了,解禁') VisitThrottle.VISIT_RECORD[remote_addr] = { 'start_time': [now_time, ], 'forbid': False } print(VisitThrottle.VISIT_RECORD[remote_addr]) return True else: print("两分钟内禁止访问,已经过了%s秒"%(now_time - start_time[-1])) return False else: if len(start_time) < 5: print("访问%s次"%(len(start_time) + 1)) print(start_time) start_time.insert(0, now_time) return True else: print("访问%s次,禁止访问"%len(start_time)) VisitThrottle.VISIT_RECORD[remote_addr] = { 'start_time': [now_time, ], 'forbid': True } return False
视图类中通过throttle_classes指定频率类
from app01.service.throttles import * class BookViewSet(generics.ListCreateAPIView): throttle_classes = [VisitThrottle,] queryset = Book.objects.all() serializer_class = BookSerializers
全局配置
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",], "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",] }
内置玩法
class VisitThrottle(SimpleRateThrottle): scope="visit_rate" def get_cache_key(self, request, view): return self.get_ident(request)
settings
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",], "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",], "DEFAULT_THROTTLE_RATES":{ "visit_rate":"5/m", } }
解析器和响应器
上面已经对django和restframework的request解析过程进行分析过了,解析器主要对请求体里的信息进行解析
局部视图
from rest_framework.parsers import JSONParser,FormParser class PublishViewSet(generics.ListCreateAPIView): parser_classes = [FormParser,JSONParser] queryset = Publish.objects.all() serializer_class = PublshSerializers def post(self, request, *args, **kwargs): print("request.data",request.data) return self.create(request, *args, **kwargs)
全局视图
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",], "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",], "DEFAULT_THROTTLE_RATES":{ "visit_rate":"5/m", }, "DEFAULT_PARSER_CLASSES":['rest_framework.parsers.FormParser',] }
响应器主要是Response返回的内容,它会帮你生成展示数据的页面,页面支持你定义好的请求方法,如果你不想看它给页面,也可以通过?format=json查看
分页组件
分两种情况,一种是原生get,另外一种就是钩子映射到list方法,所以你想如果想进行分页,可以这么做
重写get或list方法
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination def get(self,request): book_list=Book.objects.all() pp=PageNumberPagination() pager_books=pp.paginate_queryset(queryset=book_list,request=request,view=self) print(pager_books) bs=BookSerializers(pager_books,many=True) #return Response(bs.data) return pp.get_paginated_response(bs.data)
当然上面这个过程是没有给一页显示多少的,而且分页内部没有默认值,它会去settings下取PAGE_SIZE,所以你好配置这个
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.TokenAuth",] "PAGE_SIZE":2 }
当然这个是全局的,如果你想局部,你可以这么干,自定义分页类,下面实例过程就换成你定义这个类
pp=PageNumberPagination()
自定义分页类
class PNPagination(PageNumberPagination): page_size = 1 page_query_param = 'page' #url分页参数名 page_size_query_param = "size" #url每页数参数名 max_page_size = 5 #当?page=1&size=6 size超过5时就不生效
针对钩子映射的,还可以这么做,通过pagination_class指定分页器,当然指定可以不用重写list方法了,因为在list方法的源码里,会自动获取这个指定的分液器
class AuthorModelView(viewsets.ModelViewSet): pagination_class = PNPagination authentication_classes = [TokenAuth,] permission_classes = [] throttle_classes = []# 限制某个IP每分钟访问次数不能超过20次 queryset = Author.objects.all() serializer_class = AuthorModelSerializers
对LimitOffsetPagination偏移分页,自定义类时 default_limit指定限制多少,url里 offset参数指定偏移量