前后端混合开发(前后端不分离):返回的是html的内容,需要写模板
前后端分离:只专注于写后端接口,返回json,xml格式数据
分离之后,后端只需要关注于返回的JSON数据即可
xml格式如今用的特别少,因为没有JSON那么通透
# xml格式 <xml> <name>liuyuname> xml>
// JSON格式 {"name":"liuyu"}
什么是动态页面、静态页面:
可以理解为,每次请求的时候,数据都可能不一样的,叫做动态页面。反之,数据都是写死的,叫做静态页面
概念拓展:
'''
页面静态化: 先把页面渲染成一个静态页面,给所有人返回的时候就返回这个静态页面,当后端数据发生变化的时候
再去数据库查询,重新生成一个静态页面,这样可以减轻服务器的压力。
这种方式就叫做“页面静态化”。
'''
什么是API接口:
什么是postman:
Postman是一个接口测试工具,在做接口测试的时候,Postman相当于一个客户端,它可以模拟用户发起的各类HTTP请求,将请求数据发送至服务端,获取对应的响应结果,从而验证响应中的结果数据是否和预期值相匹配;并确保开发人员能够及时处理接口中的bug,进而保证产品上线之后的稳定性和安全性。
它主要是用来模拟各种HTTP请求的(如:get/post/delete/put…等等),Postman与浏览器的区别在于有的浏览器不能输出Json格式,而Postman更直观接口返回的结果。
返回的JSON格式数据,如果太长不方便看出层级关系,那么可以在网页上使用一些JSON解析工具
什么是RESTful:
RESTful是一种定义Web API接口的设计风格,尤其适用于前后端分离的应用模式中。
这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。
Restful规范(10条)
一、数据的安全保障
二、接口特征表现
一看就知道是个api接口,用api关键字标识接口url:
如:
- https://api.baidu.com
- https://www.baidu.com/api
看到api字眼,就代表该请求url链接是完成前后台数据交互的
三、多数据版本共存
在url链接中标识数据版本
如:
- https://api.baidu.com/v1
- https://api.baidu.com/v2
四、数据即是资源
均使用名词(可复数)如:books、users, 而不是动词,如:get_booklist、get_userlist
接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
- https://api.baidu.com/users
- https://api.baidu.com/books
- https://api.baidu.com/book
五、资源操作由请求方式决定
操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作
如:
- https://api.baidu.com/books - get请求:获取所有书
- https://api.baidu.com/books/1 - get请求:获取主键为1的书
- https://api.baidu.com/books - post请求:新增一本书书
- https://api.baidu.com/books/1 - put请求:整体修改主键为1的书
- https://api.baidu.com/books/1 - delete请求:删除主键为1的书
六、过滤
通过在url上传参的形式传递搜索条件
如:
https://api.example.com/v1/zoos?limit=10 :指定返回记录的数量
https://api.example.com/v1/zoos?offset=10 :指定返回记录的开始位置
https://api.example.com/v1/zoos?page=2&per_page=100 :指定第几页,以及每页的记录数
https://api.example.com/v1/zoos?sortby=name&order=asc :指定返回结果按照哪个属性排序,以及排序顺序
https://api.example.com/v1/zoos?animal_type_id=1 :指定筛选条件
七、返回响应状态码
返回的JSON字符串中带响应状态码
响应状态码:
2xx :常规请求等
3xx :重定向相关等
4xx :客户端异常等
5xx :服务器异常等
八、错误处理
应返回错误信息,error当做key
如:
{ error: "权限不足" }
其实没必要全部都遵循,可以用例如“msg”等作为key
九、返回的结果(数据)处理
返回的结果,针对不同操作,服务器向用户返回的结果应该符合以下规范“
GET请求:
- https://api.test.com/collection:返回资源对象的列表(数组)
https://api.test.com/collection/resource:返回单个资源对象POST请求:
- https://api.test.com/collection:返回新生成的资源对象
https://api.test.com/collection/resource:返回完整的资源对象
十、需要url请求的资源需要访问资源的请求链接
Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么
如:
{ "status": 0, "msg": "ok", "results":[ { "name":"肯德基(罗餐厅)", "img": "https://image.baidu.com/kfc/001.png" } ... ] }
Django Rest Framework(DRF)框架作用:
核心思想: 缩减编写api接口的代码。
Django REST framework是一个建立在Django基础之上的Web 应用开发框架,可以快速的开发REST API接口应用。在REST framework中,提供了序列化器Serialzier的定义,可以帮助我们简化序列化与反序列化的过程,不仅如此,还提供丰富的类视图、扩展类、视图集来简化视图的编写工作。REST framework还提供了认证、权限、限流、过滤、分页、接口文档等功能支持。REST framework提供了一个API 的Web可视化界面来方便查看测试接口。
基本的使用,以及序列化器Serialzier等其他内容,见其他章节。
安装:
win+R:
pip3 install djangorestframework
先分析推导,再介绍如何使用、能实现怎么作用以及实现原理。
在分析APIView源码之前,先从CBV的源码入手。
CBV:
# 视图层
from django.shortcuts import render,HttpResponse
from django.views import View
class Index(View):
def get(self,request):
return HttpResponse('get')
def post(self,request):
print(request.POST)
return HttpResponse('post')
# 路由层
urlpatterns = [
path('index',views.Index.as_view()),
]
前文中已经介绍过了,所以之类就长话短说。
突破口在于as_view(),因为直接就是加括号调用,所以查看源码发现,该函数是个闭包函数,返回内部的view函数,在该函数内部,又调用了我们上述案例代码中的Index类并实例化了一个对象名为self,随后view函数return了self.dispatch,查询dispatch源码可以看出,后续利用反射来获取当前访问方式所对应的视图函数,随后变量handler接收视图函数的内存地址,最后return的时候,handler加括号调用。
def view(request, *args, **kwargs):
#request是当次请求的request
self = cls(**initkwargs) #实例化得到一个对象,index对象
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)
def dispatch(self, request, *args, **kwargs):
#request是当次请求的request self是index对象
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
return handler(request, *args, **kwargs) #执行get(request)
APIView模块也是继承了django原生View,使用方法也大体相同。
# 路由
urlpatterns = [
path('index',views.Index.as_view()),
]
# 视图
from django.shortcuts import render,HttpResponse
from rest_framework.views import APIView
class Index(APIView):
def get(self,request):
return HttpResponse('get')
def post(self,request):
print(request.POST)
print(request.data)
return HttpResponse('post')
与CBV的切入点一样,也是先看as_view方法,但是由于现在Index类先继承的是APIView,然后才是原生View,所以这里按照面向对象属性查找就找到了APIView类中的as_view方法,如下:
#APIView的as_view方法(类的绑定方法)
def as_view(cls, **initkwargs):
view = super().as_view(**initkwargs) # 调用父类(View)的as_view(**initkwargs)
view.cls = cls
view.initkwargs = initkwargs
# 以后所有的请求,都没有csrf认证了,只要继承了APIView,就没有csrf的认证
return csrf_exempt(view)
super().as_view(**initkwargs)调用父类的as_view方法,而APIView的父类就是原生View,这就又回到了CBV的底层代码。
饶了一圈之后,又执行到了View --> as_view --> view --> dispatch
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
....
return self.dispatch(request, *args, **kwargs)
但是由于首先继承的是APIView类,所以在调用dispatch的时候,会先从index类中找,没有就先去APIView,而此时APIView刚好就有dispath方法,所以后面执行的,并不是View–>dispath,而是APIView下的dispatch。
# APIView的dispatch方法
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
# 重新包装成一个request对象,以后再用的request对象,就是新的request对象了
request = self.initialize_request(request, *args, **kwargs)
self.request = request
.......
在APIView的dispatch方法中,先对request请求进行重新封装request方法,添加了一些属性和方法,如request.data,作用:可以拿到任何编码提交的数据,随后再经过三大模块认证校验,通过之后就执行与View–>dispatch一样的代码,利用反射获取到方法的地址。
附:
APIView中diapach方法的后半段,及initialize_request方法
# APIView的dispatch方法 def dispatch(self, request, *args, **kwargs): self.args = args self.kwargs = kwargs # 重新包装成一个request对象,以后再用的request对象,就是新的request对象了 request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers 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 # 响应模块 response = handler(request, *args, **kwargs) except Exception as exc: # 异常模块 response = self.handle_exception(exc) # 渲染模块 ''' 区分出访问的客户端是浏览器还是POSTMAN,如果是浏览器,那么就将JSON数据展示在一个好看点的页面,如果是POSTMAN,那就直接返回JOSN数据,不做渲染 ''' self.response = self.finalize_response(request, response, *args, **kwargs) return self.response # APIView的initial方法 def initial(self, request, *args, **kwargs): # 认证组件:校验用户 - 游客、合法用户、非法用户 # 游客:代表校验通过,直接进入下一步校验(权限校验) # 合法用户:代表校验通过,将用户存储在request.user中,再进入下一步校验(权限校验) # 非法用户:代表校验失败,抛出异常,返回403权限异常结果 self.perform_authentication(request) # 权限组件:校验用户权限 - 必须登录、所有用户、登录读写游客只读、自定义用户角色 # 认证通过:可以进入下一步校验(频率认证) # 认证失败:抛出异常,返回403权限异常结果 self.check_permissions(request) # 频率组件:限制视图接口被访问的频率次数 - 限制的条件(IP、id、唯一键)、频率周期时间(s、m、h)、频率的次数(3/s) # 没有达到限次:正常访问接口 # 达到限次:限制时间内不能访问,限制时间达到后,可以重新访问 self.check_throttles(request)
只要继承了APIView,视图类中的request对象,都是新的,也就是下面那个request的对象,新的request对象用于更多的方法,如data等
request.data可以获取到任意编码提交过来的数据。
# 继承了APIView之后,视图类中的request对象,就成了下面这个。
from rest_framework.request import Request
新request与老request在用法上的区别,及源码分析:
- 没有区别
```python
def __getattr__(self, attr):
try:
return getattr(self._request, attr) #通过反射,取原生的request对象,取出属性或方法
except AttributeError:
return self.__getattribute__(attr)
老request在新request的._request中,在调用如request.POST时,由于内部定义了__getattr__方法,当加点调用时,执行函数体内部的代码,随后通过反射来获取老request中的方法,在使用过程中无感,不影响使用。
附:
request.data其实是并不是数据属性,而是一个方法,只是用**@property**伪装了
POST请求的数据都被封装到了request.data中,那么GET请求呢?
'''
二选一
'''
print(request.GET)
print(request.query_params)
'''
query_params源码
'''
@property
def query_params(self):
"""
More semantically correct name for request.GET.
"""
return self._request.GET
继续使用get仍然可以,但是我们获取get请求值的时候,获取的是url后面跟的参数,所以query_params会更合理一点,于是便做了封装。
在4.2的章节中,介绍了APIView源码,以及所做的效果,就是封装了request,并对其进行校验。
response在走的时候也针对访问客户端区别响应,如果是浏览器,那么就渲染出一个好看点的页面。
Serializer的作用:
在4.2章节中,利用APIView可以完成对请求和响应的封装等操作,但是并没有将Queryset对象,序列化成JSON格式数据。
所以Serializer的作用就是将后端查询出来的模型对象,也就是Queryset对象,转换成字典,随后经过response变成JSON格式的字符串,这个过程叫做序列化。
有序列化就有反序列化,反序列化就是,客户端发送过来的数据,经过request之后,由JSON格式转换成python中的字典,随后Serializer序列化器将字典转成模型(Queryset)
同时,Serializer还可以完成数据校验功能,对POST、PUT请求提交的数据做校验(支持自定义),类似于forms组件。
步骤总览:
新建py文件,或者再套一层文件夹,用来写一个序列化类,继承serializers.Serializer
在类中写要序列化的字段,想序列化哪个字段,就在类中写哪个字段。
假设有五个字段,如果只需要返回给前端三个字段的数据,那么序列化类中只需要写这三个字段就好。
使用时,需要在视图类中导入,并实例化得到序列化类的对象,把需要序列化的对象传入
序列化类的对象.data,可以得到一个字典,是由原来的Queryset对象转成的。
把字典以JSON格式返回,所以需要使用JsonResponse,除了JsonResponse以外,还可以使用rest_framework提供的Response
JsonResponse与Response的区别:
Response可以针对浏览器的请求,做出一些好看的页面,如下:
代码示例:
模型层代码略
一、新建序列化类
本示例中,是创建了一个文件夹,文件夹内的py文件中书写了序列化类,具体的层级关系如下:
# 序列化类 serializer.py文件
from rest_framework import serializers
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.CharField()
author = serializers.CharField()
publish = serializers.CharField()
由于需要将name字段、price字段、author字段、publish字段都通过Serializer序列为字典,所以这里就都写上了,
CharField()里面可以设置参数,与models.py类似,可以设置最长多少、最短多少,可用于对提交数据做校验。
二、路由与视图层代码
路由
urlpatterns = [
re_path(r'^books/$',views.Books.as_view()),
re_path(r'books/(\d+)', views.Books.as_view()),
]
视图
from rest_framework.views import APIView
# 导入序列化类
from app01.serializers.serializer import BookSerializer
from rest_framework.response import Response
from app01 import models
class Books(APIView):
# 当请求方式为GET,且并未传入主键值时,默认返回所有图书,返回结果为JSON格式。
def get(self,request,*args):
back_dic = {'status': '200', 'msg': '获取成功', 'data': []}
try:
id = args[0]
# 先查出指定获取的文章对象
book_obj = models.Book.objects.filter(pk=id).first()
# 实例序列化类
book_ser = BookSerializer(book_obj)
# book_ser.data 相当于book_obj的字典版本 (queryset转dict)
back_dic['data'].append(book_ser.data)
return Response(back_dic)
# 如果args取不到索引值,那么说明直接访问的是books页面,返回所有的图书
except IndexError:
book_queryset = models.Book.objects.all()
for book_obj in book_queryset:
book_ser = BookSerializer(book_obj)
# 将每一个book_obj都转成字典格式,塞进数据中,随后一起通过Response以JSON格式的形式发给前端。
back_dic['data'].append(book_ser.data)
return Response(back_dic)
book_ser:BookSerializer(
book_ser.data:{‘name’: ‘活着’, ‘price’: ‘31.00’, ‘author’: ‘余华’, ‘publish’: ‘作家出版社’}
book_ser.data 返回Queryset对象转成dict字典格式的数据
在上文的代码中,在处理所有Queryset对象序列化成字典的时候,是利用的for循环,其实DRF还提供了其他更方便的方法,那就是在序列化的时候就传入一个参数,告诉它需要序列化多个
参数名:
many=True
代码示例:
# views.py
class BooksView(APIView):
def get(self,request):
response_msg = {'status': 100, 'msg': '成功'}
# 获取Book表中所有的数据
books=Book.objects.all()
# 生成序列化对象,把需要进行序列化的对象传入,并指定序列化多条。
book_ser=BookSerializer(books,many=True) #序列化多条,如果序列化一条,不需要写
# 字典新增名为data的key,值为序列化完毕的字典格式数据。
response_msg['data']=book_ser.data
return Response(response_msg)
在3.3.1章节中,介绍了如何将后端的Queryset对象转成字典,那么也需要有对应的方法,可以将前端提交过来的JSON数据,转成后端方便使用的字典。
步骤总览:
依旧是书写一个序列化类,继承serializers.Serializer
随后在类中写需要反序列化的字段,而这些字段要与前端提交的数据相符合,如:
'''
前端提交:
{'name':"活着",'price':998,'author':'余华'}
'''
'''
后端序列化类:
'''
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.CharField()
author = serializers.CharField()
#publish = serializers.CharField(allow_blank=True) #字段对应
前端提交了三组键值对数据(虽然是JSON格式),那么后端就要有对应的字段做校验,后端可针对各个字段做校验限制,类似于forms组件
视图函数中导入序列化类,并实例化得到对象,由于本章节是要修改数据,所以还需要把被修改的对象与参照数据一起传入,如:
boo_ser = BookSerializer(book_obj,request.data)
'''
book_obj 为需要修改的对象(因为调用了修改数据接口,所以要以重新传入的数据为准)
request.data 由于APIView对请求request又完成了一次封装,所以.data可以获取到所有编码发送的数据,包括JSON
综合意思就是,实例化的时候先通过序列化类的字段普通校验,然后进行修改数据的准备工作,将需要进行修改的数据,和前端发送的更新数据,一起收集,用于后续更改。
另外一种写法: boo_ser = BookSerializer(instance=book_obj,data=request.data)
'''
与forms组件类似,校验完毕之后is_valid()方法可以查看是否通过,返回值为布尔,如果校验通过就调用save()保存,但是在设计的时候,这个**save()**方法需要重写。
剩下的整理下各种不通过的逻辑,以及返回给前端的状态码等,最后为了符合Restful规范,修改完毕之后返回对象。
代码:
路由略
视图
class Books(APIView):
# 重复代码略(上面发过)
def get(self,request,*args):
pass
def put(self, request, id):
response_msg = {'status':200,'msg':'成功'}
# 先获取需要修改的对象
book_obj = models.Book.objects.filter(pk=id).first()
# 得到一个序列化类的对象
book_ser = BookSerializer(instance=book_obj,data=request.data)
'''
表示:后者的数据,会用于修改前者。 也就是需要拿request.data的数据 来修改book_obj的信息,request.data为前端发送过来的数据,并且不受编码的影响。
也可以写成 BookSerializer(book_obj,request.data)
之所以推荐写成instance=book_obj的形式,是因为后面还有个参数叫data,而这个是关键词参数,直接传的话容易成位置参数传给instance,所以只是加深下印象,以便出错。
'''
# 类似于forms组件一样,到我们写的BookSerializer类中做校验
if book_ser.is_valid():
# 保存
book_ser.save()
# 字段校验之后,会在重写后的update方法里进行数据修改
# 修改完数据后,将修改之后的数据返回。
response_msg['data'] = book_ser.data
else:
# 校验没通过就说明有字段的值是空的,或者长度等不够等,不过我们并没有设置。
response_msg['status'] = 210
response_msg['msg'] = '数据校验失败'
response_msg['errprs'] = book_ser.errors
return Response(response_msg)
由于前面提到了,校验通过之后,直接执行save()方法是会报错的,因为不符合设计时使用的规范,所以需要重写。
查看到是book_ser调用的,而该对象又是通过序列化类生成的,所以直接在序列化类中进行重写。
序列化类
from rest_framework import serializers
from app01 import models
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.CharField()
author = serializers.CharField()
publish = serializers.CharField()
def update(self, instance, validated_data):
#instance是book_obj这个对象,validated_data是校验后返回的字典格式数据
# 由于上传的JSON数据都是通过校验好的,那么就直接开始按照对应关系,将book_obj重新赋值,随后save保存
instance.name=validated_data.get('name')
instance.price=validated_data.get('price')
instance.author=validated_data.get('author')
instance.publish=validated_data.get('publish')
'''
注意,由于是instance调用的save方法,而instance又是book_obj。
因此,此处的save()方法为ORM提供的。
'''
instance.save()
return instance
instance.save()这里调用的ORM的方法,重新赋值修改之后,保存到数据库。
查看效果:
接口截图来源与 Apipost软件
在更新的时候,可以传入partial=True,这样传入那些字段,就表示更新那些字段的值。
在不设置该参数或者read_only的时候,未传字段会报**“This field is required”**
步骤:
与PUT修改数据的步骤类似,但也有几个不同点:
一、在实例化的时候由于并不需要修改谁,所以instance参数不需要传,但该参数在定义时又是位于前列的位置参数,所以在传入data参数的时候,要以关键字传参的形式传入进去。
二、PUT修改中,在调用.save()方法时会报错,因为需要重写update方法,而在POST中,需要重写create方法。
# views.py
class BooksView(APIView):
# 新增
def post(self,request):
response_msg = {'status': 100, 'msg': '成功'}
#修改才有instance,新增没有instance,只有data
book_ser = BookSerializer(data=request.data)
# book_ser = BookSerializer(request.data) # 这个按位置传request.data会给instance,就报错了
# 校验字段
if book_ser.is_valid():
# 数据的新增,将在重写的create方法中进行操作,视图这里直接可以进行保存。
book_ser.save()
response_msg['data']=book_ser.data
else:
response_msg['status']=102
response_msg['msg']='数据校验失败'
response_msg['data']=book_ser.errors
return Response(response_msg)
#ser.py 序列化类重写create方法
def create(self, validated_data):
# 直接利用**打散,将 A:B 的字典形式,转换成 A=B
# 创建数据,最后需要把ORM创建返回的对象返回。
instance=Book.objects.create(**validated_data)
return instance
# urls.py
path('books/', views.BooksView.as_view()),
删除就不需要使用到序列化了,只是为了凑出增删改查四兄弟的。
# views.py
class BookView(APIView):
def delete(self,request,pk):
ret=Book.objects.filter(pk=pk).delete()
return Response({'status':100,'msg':'删除成功'})
# urls.py
re_path('books/(?P\d+)' , views.BookView.as_view()),
BooleanField | BooleanField() |
---|---|
NullBooleanField | NullBooleanField() |
CharField | CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) |
EmailField | EmailField(max_length=None, min_length=None, allow_blank=False) |
RegexField | RegexField(regex, max_length=None, min_length=None, allow_blank=False) |
SlugField | SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+ |
URLField | URLField(max_length=200, min_length=None, allow_blank=False) |
UUIDField | UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex' 如 "5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a" |
IPAddressField | IPAddressField(protocol=’both’, unpack_ipv4=False, **options) |
IntegerField | IntegerField(max_value=None, min_value=None) |
FloatField | FloatField(max_value=None, min_value=None) |
DecimalField | DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置 |
DateTimeField | DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None) |
DateField | DateField(format=api_settings.DATE_FORMAT, input_formats=None) |
TimeField | TimeField(format=api_settings.TIME_FORMAT, input_formats=None) |
DurationField | DurationField() |
ChoiceField | ChoiceField(choices) choices与Django的用法相同 |
MultipleChoiceField | MultipleChoiceField(choices) |
FileField | FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ImageField | ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ListField | ListField(child=, min_length=None, max_length=None) |
DictField | DictField(child=) |
选项参数:
参数名称 | 作用 |
---|---|
max_length | 最大长度 |
min_lenght | 最小长度 |
allow_blank | 是否允许为空 |
trim_whitespace | 是否截断空白字符 |
max_value | 最小值 |
min_value | 最大值 |
通用参数:
参数名称 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 表明该字段是否允许传入None,默认False |
validators | 该字段使用的验证器 |
error_messages | 包含错误编号与错误信息的字典 |
label | 用于HTML展示API页面时,显示的字段名称 |
help_text | 用于HTML展示API页面时,显示的字段帮助提示信息 |
read_only:
write_only:
测试:
class BookSerializer(serializers.Serializer):
id = serializers.CharField(read_only=True)
name = serializers.CharField()
price = serializers.CharField(write_only=True)
#author = serializers.CharField()
author = serializers.CharField(validators=[check_author])
publish = serializers.CharField()
以下的了解即可:
在六章节中,自定义序列化类继承ModelSerializer之后,由于不再一个个书写需要序列化的字段,那么read_only和write_only与设置方式就会有所不同。
代码示例:
# 注:需要结合6.1章节及以后内容
class BookSer(ModelSerializer):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.fields['bind_publish'].write_only = True
self.fields['bind_author'].write_only = True
class Meta:
model = models.Book
fields = ['title','price','publish_name','author_list','bind_publish','bind_author']
extra_kwargs={
'publish_name':{'read_only':True},
'author_list':{'read_only':True},
}
由于只读和只写不能同时出现在extra_kwargs中,且经过测试发现write_only写在extra_kwargs还是会报错,因此只能写在调用父类init方法的里面。
报错:AssertionError: May not set both
read_only
andwrite_only
5.6章节中介绍了很多用来校验的参数,但是如果没有自己想要的该怎办呢? 钩子函数,以及字段的validators参数
局部钩子:
全局钩子:
from rest_framework import serializers
# 导入抛出异常的模块
from rest_framework.validators import ValidationError
class BookSerializer(serializers.Serializer):
'''
略
'''
def update(self, instance, validated_data):
'''
略
'''
return instance
# 局部钩子,validate_字段名, data用于接收前端提交的,price字段相关的JSON数据
def validate_price(self, data):
#如果价格大于1000就抛出异常
if float(data)<1000:
return data
else:
raise ValidationError('你这书保熟吗')
# 全局钩子
def validate(self, data): #由于多个字段的值都在这,所以数据格式为字典,需要get取值
name = data.get('name')
author = data.get('author')
if author == name:
raise ValidationError('作者名与书名不能相同')
else:
return data
validators参数
def check_author(data):
if 'sb' in data:
raise ValidationError('名称不雅')
else:
return data
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.CharField()
author = serializers.CharField(validators=[check_author])
publish = serializers.CharField()
在前面的代码中,出现了大量的重复代码,例如下列的代码:
back_dic = {'status': '200', 'msg': '获取成功', 'data': []}
思路:
class MyResponse():
def __init__(self):
self.status = 100
self.msg = '成功'
@property
def get_dict(self):
return self.__dict__
res = MyResponse()
res.status = 101
res.msg = '查询失败'
# 需要添加返回信息的时候,可以直接.data来给对象添加属性
# res.data={'name':'liuyu'}
print(res.get_dict)
# {'status': 101, 'msg': '查询失败','data':{'name':'liuyu'}}
使用的时候直接实例化对象,然后通过对象来操作属性及值,最后通过开设的get_dict接口,访问到该对象所有的属性和方法。
@property : 将对象的方法,伪装成属性(调用不需要加括号)
__dict__ : 获取对象所有的属性和方法
source参数
可以改字段名字
xxx=serializers.CharField(source='title')
返回给前端的数据,可以“.”跨表
publish=serializers.CharField(source='publish.email')
开头和参数内可以理解为隐藏了个表,以案例的图书表为例,这段代码就变成了下列这种
book.publish=serializers.CharField(source='book.publish.email')
此时的publish为book表的外键字段,最终返回给前端的JSON数据为 {‘publish obj’ : ’ [email protected] '}
所以,在模型表的publish类中,最好定义双下str方法,这样key就变成了中文,而不是obj对象,看不出来是什么。
serializers.SerializerMethodField()
应用场景:当需要把多个值,序列化到一个key中的时候,如:
{
'name':'liuyu',
'age':22,
'hobby':[
{'type':'美食','detailed':'吃'},
{'type':'娱乐','detailed':'打游戏'}
]
}
代码示例:
class BookSerializer(serializers.Serializer):
name=serializers.CharField()
price=serializers.CharField()
publish=serializers.CharField()
authors=serializers.SerializerMethodField()
#SerializerMethodField需要有个配套方法,方法名叫get_字段名,返回值就是前端要显示的数据
def get_authors(self,instance):
# instance:book对象
authors_queryset = instance.authors.all() # 取出所有作者
back_dic=[]
for author_obj in authors_queryset:
back_dic.append({'name':author_obj.name,'age':author_obj.age})
return back_dic
以图书表为例,views.py在给前端返回JSON数据的时候,先生成Serializer对象,然后这个过程中完成序列化,authors字段在序列化的时候会执行下面的get_authors方法,命名格式就是get_字段名,注意需要传入instance参数,该参数的值,就是views.py中调用序列化类时所传入的book_queryset对象,有个图书对象以后,自然就可以直接进行跨表查询,随后封装下返回值。
1
ModelSerializer模型类序列化器与序列化器的区别:
序列化类父类继承不一样
# 这是序列化器,也就是五章节中用的
class BookSerializer(serializers.Serializer):
pass代码略
# 这是模型类序列化器,将在六章介绍介绍使用
from rest_framework.serializers import ModelSerializer
class BookModelSerializer(serializers.ModelSerializer):
特点一:序列化类不需要一个一个写字段
from app01 import models
class BookModelSerializer(serializers.ModelSerializer):
class Meta:
model= models.Book # 对应上models.py中的模型
fields='__all__'
# 只读只写在这里配置
extra_kwargs = {
'price': {'write_only': True},
}
特点二:修改和新增数据时,不需要重写update方法和create方法
在上面的代码中,直接fields=‘__all__’来展示所有字段,一些不希望展示的直接设置只读只写来控制,那么除了这么写以外,还有其他的写法。
将展示的字段,以元祖或列表的形式来展示。
class BookSer(ModelSerializer):
class Meta:
model = models.Book
fields = ('title','price',)
#fields = ['title','price'] #也可以是列表
以上图代码为例,title与price对应的就是models.py中book类的几个属性,那么也就以为着,fields可以传入函数
# ser.py
class BookSer(ModelSerializer):
class Meta:
model = models.Book
fields = ['title','price','publish_name','author_list']
# 序列化models.Book类中title属性、price属性、....的值,
# models.py
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
# 与出版社的外键
bind_publish = models.ForeignKey(to='Publish',on_delete=models.DO_NOTHING,db_constraint=False)
# 与作者表的外键
bind_author = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('bind_book','bind_author'))
@property
def publish_name(self):
return self.bind_publish.name
@property
def author_list(self):
author_list=self.bind_author.all()
# 返回对应的用户列表,该列表内的数据为对应作者的姓名和性别。
return [ {'name':author.name,'sex':author.get_sex_display()} for author in author_list]
效果展示:
在五章节中,有用到例如.data方法,本章节单独拎出来做总结。
源码分析见第四章
from rest_framework.request import Request
'''
其实就是将request进行了二次封装,将原生django request作为drf request对象的 _request 属性
'''
封装之后的request,常用的两个方法:
from rest_framework.response import Response
response方法,在上文中的时候,都是传入request.data,但该方法是可以传入多个参数的。
reponse方法,各个参数的作用:
def __init__(self, data=None, status=None,template_name=None, headers=None,exception=False, content_type=None):
data:要返回的数据,字典
status:返回的状态码,默认是200
return response(requese.data,status:201)
drf在status模块中,定义了很多常量,常量对应的有状态码,所以可以导入,在status参数这里,返回更为专业一点的状态码
from rest_framework import status
return response(requese.data,status:status.HTTP_200_OK)
template_name: 渲染的模板名字(自定制模板),不需要了解。
headers:响应头,可以往响应头放东西,格式为字典
content_type:响应的编码格式,application/json和text/html
Response在响应浏览器和接口测试工具的时候,响应的格式是不一样的。
如何设置不管是什么访问,都只返回JSON数据呢:
全局配置:
#全局使用:全局的视图类,所有请求,都有效
#在setting.py中加入如下
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类
'rest_framework.renderers.JSONRenderer', # json渲染器
'rest_framework.renderers.BrowsableAPIRenderer', # 浏览器API渲染器
)
}
由于JSON数据肯定是要返回的,所以当我们不想要浏览器返回模板的时候,就在django项目文件夹下的settings.py中,将上述代码拷贝进去,把BrowsableAPI这段注释掉就行。
局部配置:
# 对某个视图类有效
# 在视图类中写如下
from rest_framework.renderers import JSONRenderer
# 该视图函数对应的所有请求,都返回JSON格式,不会浏览器进行渲染。
class BooksView(APIView):
renderer_classes=[JSONRenderer,]
drf框架也有static文件夹、templates文件夹,还有一个api.html 这也就是为什么,drf没在django里注册,浏览器访问接口的时候,会报错的原因。
drf的settings文件中,有着大量的配置,类似于注册中间件一样,所以可以利用drf设定的查找顺序,在django项目的settings中进行修改。
drf有默认的配置文件—》先从项目的setting中找,找不到,采用默认的,而默认的REST_FRAMEWORK属性中,两个响应渲染类模块都是启用的。
APIView是REST framework提供的所有视图的基类,继承自Django的View父类。
APIView与View的不同之处在于:
代码与前面章节基本一致,再次提及主要是用于对比其他模块。
# views.py
from rest_framework.generics import GenericAPIView
from app01.models import Book
from app01.ser import BookSerializer
#models.py
class Book(models.Model):
name=models.CharField(max_length=32)
price=models.DecimalField(max_digits=5,decimal_places=2)
publish=models.CharField(max_length=32)
# 基于APIView写的
class BookView(APIView):
def get(self,request):
book_list=Book.objects.all()
book_ser=BookSerializer(book_list,many=True)
return Response(book_ser.data)
def post(self,request):
book_ser = BookSerializer(data=request.data)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.data)
else:
return Response({'status':101,'msg':'校验失败'})
class BookDetailView(APIView):
def get(self, request,pk):
book = Book.objects.all().filter(pk=pk).first()
book_ser = BookSerializer(book)
return Response(book_ser.data)
def put(self, request,pk):
book = Book.objects.all().filter(pk=pk).first()
book_ser = BookSerializer(instance=book,data=request.data)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.data)
else:
return Response({'status': 101, 'msg': '校验失败'})
def delete(self,request,pk):
ret=Book.objects.filter(pk=pk).delete()
return Response({'status': 100, 'msg': '删除成功'})
#ser.py
class BookSerializer(serializers.ModelSerializer):
class Meta:
model=Book
fields='__all__'
# urls.py
path('books/', views.BookView.as_view()),
re_path('books/(?P\d+)' , views.BookDetailView.as_view()),
1、BookView用于处理get与post请求,应为不需要传入主键值,BookDetailView用于处理Put、delete、get(单条数据)请求。
2、实例化传入值,由于序列化类使用的是ModelSerializer,所以不需要写上一大堆想要序列化的字段,直接内部顶一个名为Meta的类即可。
3、当序列化多条数据时,需要传入many=True
基于APIView写接口的代码量就这么多,接下来看看在使用其他封装程度更高的接口,能带来什么样的效果。
8.1中,视图类继承的是APIView,本章节将介绍是怎么基于GenericAPIView写接口的。
序列化类:
from rest_framework import serializers
from app01.models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model=Book
fields='__all__'
视图层:
from rest_framework.generics import GenericAPIView
class BookView(GenericAPIView):
queryset = Book.objects #queryset要传queryset对象,查询了所有的图书。也可以写成queryset=Book.objects.all()
serializer_class = BookSerializer #使用哪个序列化类来序列化这堆数据
def get(self,request):
book_list=self.get_queryset()
book_ser=self.get_serializer(book_list,many=True)
return Response(book_ser.data)
def post(self,request):
book_ser = self.get_serializer(data=request.data)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.data)
else:
return Response({'status':101,'msg':'校验失败'})
class BookDetailView(GenericAPIView):
queryset = Book.objects
serializer_class = BookSerializer
def get(self, request,pk):
book = self.get_object()
book_ser = self.get_serializer(book)
return Response(book_ser.data)
def put(self, request,pk):
book = self.get_object()
book_ser = self.get_serializer(instance=book,data=request.data)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.data)
else:
return Response({'status': 101, 'msg': '校验失败'})
def delete(self,request,pk):
ret=self.get_object().delete()
return Response({'status': 100, 'msg': '删除成功'})
路由:
path('books/', views.BookView.as_view()),
re_path('books/(?P\d+)' , views.BookDetailView.as_view()),
可以发现,继承了GenericAPIView之后,在写这五个接口时,只需要在类中定义queryset属性和serializer_class属性,后面每个接口的代码基本上都是大差不差,并且后续的代码中,我们并没有书写ORM查询,只是单纯的通过封装好的get_serializer方法来调用序列化器,并传入data、instance等参数并save即可。
针对于PUT修改、指定GET、DELETE删除,在继承APIView的时候,还需要书写ORM查询语句,filter(pk=id),但是到了这里,直接self.get_object()
并且,如果要对出版社表和作者表,也对外开放增删改查接口时,可以整体拷贝图书表的代码,只需要重新写一个序列化类,然后在视图层修改下queryset、serializer_class属性的值即可。
在8.2章节中,利用了再次封装了APIView的GenericAPIView,从而实现了减少重复代码的作用,但目前仍存在很多重复代码,所以drf又提供了五个视图扩展类。
ListModelMixin: list方法,可以直接获取到所有数据
CreateModelMixin: create方法,可以直接创建数据
UpdateModelMixin: update方法,修改数据
DestroyModelMixin:destroy方法,删除数据。
RetrieveModelMixin:retrieve方法,可以查询到指定主键的数据
使用示例:
from rest_framework.mixins import ListModelMixin,CreateModelMixin,UpdateModelMixin,DestroyModelMixin,RetrieveModelMixin
# views.py
class BookView(GenericAPIView,ListModelMixin,CreateModelMixin):
queryset=Book.objects
serializer_class = BookSerializer
def get(self,request):
return self.list(request)
def post(self,request):
return self.create(request)
class BookDetailView(GenericAPIView,RetrieveModelMixin,DestroyModelMixin,UpdateModelMixin):
queryset = Book.objects
serializer_class = BookSerializer
def get(self, request,pk):
return self.retrieve(request,pk)
def put(self, request,pk):
return self.update(request,pk)
def delete(self,request,pk):
return self.destroy(request,pk)
# urls.py
path('books/', views.BookView.as_view()),
re_path('books/(?P\d+)' , views.BookDetailView.as_view()),
注意:
路由的有名分组,和视图中get、put等方法中,必须叫pk,都改成id则会报错
报错:AssertionError: Expected view BookDetailTestView to be called with a URL keyword argument named “pk”. Fix your URL conf, or set the
.lookup_field
attribute on the view correctly.
请求来的时候,会被APIView进行封装,如果是get获取全部数据,那么会经过GenericAPIView的ListModelMixin扩展类,可调用该扩展类内部的list方法进行ORM查询,以及序列化返回给前端。 数据的来源以及序列化库中的哪些字段,这些由query和serializer_class属性来决定,而这二者的值由我们来传。
如果来得是post请求,同样会被APIView进行封装,提交的数据在request.data内,从中取出并先进行序列化校验,如果继承的不是APIView那么就指名道姓的调用该类来实例化对象(传入参数时需要注意data=),如果该视图类继承的是GenericAPIView,那么就需要serializer_class参数来决定由哪个序列化器进行校验。
校验完毕之后就是保存到数据,此时如果序列化类继承的是Serializer而不是ModelSerializer时,还需要重写create方法,新建数据的操作就是在这create方法中进行的,创建完毕之后return instance,随后把用户提交的数据,再**.data**序列化返回给前端,因为既然数据都创建出来了,那么用户输入的数据肯定没问题,没问题那就扭头告诉一声说创建好了,name叫什么 age是多少…正符合RESTful规范。
put、delete与指定主键get,这三个都需要在URL中以路径的形式,来传入主键值,从而确定用户想要哪条数据、想改哪条数据、想删哪条数据。
先说get,请求来了之后…略…,需要先查询数据,把用户想要的那一条查出来,然后对查出来的数据进行序列化,最后Response返回,但使用了GenericAPIView之后,ORM的查询不需要写了,调用.get_object()即可获取到,随后再调用.get_serializer()传入需要实例化的对象完成序列化,此时格式为字典,最后Response处理成JSON字符串返回出去。
如果不仅仅用了GenericAPIView,还用了ListModelMixin扩展类,那么直接self.list(request)即可(需要在视图了中指定两个参数)
get_object() 与 get_serializer()
就是获取我们视图类中一开始就定义的queryset和serializer_class这两个属性的值
前者用于指定序列化的数据,后者指定用于序列化采用的序列化类
再说put,和上面的post新增是一样的,如果序列化类继承的是Serializer,那么在修改数据的时候,需要重写序列化类中的update方法,然后调用ORM的save()保存,继承ModelSerializer时就不需要这样,这是序列化类的层面。
视图类这里,如果继承的是GenericAPIView那么就不需要我们写ORM语句,但是其他逻辑代码与基于APIView的接口代码,几乎是一样的。 如果再继承了UpdateModelMixin扩展类,那么就可以直接调用内部封装的update方法,直接完成更新,不需要再书写其他什么调用序列化类、序列化校验是否合法等等,直接return self.update(request,pk)
总结:
在基于GenericAPIView写接口的时候,可以发现我们少写了一些查询。
然后再利用GenericAPIView+5个扩展类,可以实现每个方法的代码进一步缩减,直接调用针对该请求封装好的方法即可。
目前我们的代码,两个视图类BookView与BookDetailView中,还是书写了get、post、put、delete请求方法,本章节可以实现的就是,可以连这些方法都不需要写。
9个视图子类,可以做到识别到请求方式,然后执行对应的5个扩展类方法,封装程度更高更方便。
代码如下:
这9个视图子类都继承了GenericAPIView和xxxModelMixin扩展类,所以直接继承就好。
from rest_framework.generics import CreateAPIView,ListAPIView,UpdateAPIView,RetrieveAPIView,DestroyAPIView
class BookView(ListAPIView,CreateAPIView):
queryset = Book.objects
serializer_class = BookSerializer
class BookDetailView(UpdateAPIView,RetrieveAPIView,DestroyAPIView):
queryset = Book.objects
serializer_class = BookSerializer
代码解析:
BookView视图类继承了 ListAPIView 和 CreateAPIView,而这两个类分别又继承了GenericAPIView + ListModelMixin扩展类、GenericAPIView + CreateModelMixin 扩展类。
ListAPIView类中定义了get函数,并且调用了ListModelMixin中的list方法,CreateAPIView类中定义了post函数,并且调用了CreateModelMixin中的create方法。
所以,串起来就是,当访问来时候,先进行路由匹配,成功之后执行视图类中的函数as_view函数,最后dispatch的时候,get方法post方法都在 ListAPIView 和 CreateAPIView中定义的,所以自然就去执行框架封装好的代码来响应请求。
BookView用于处理get全部和post新增,内部的get方法和post方法,都已经封装到了ListAPIView与CreateAPIView中。
BookDetailView这个类是处理put修改、get单个查询、删除操作,原本def put、def get…这些定义请求类型的视图函数,全部都被封装到了UpdateAPIView、RetrieveAPIView、DestroyAPIView中。
如果我这个视图类,只处理get和post请求,不接受其他的请求,那么只需要继承对应三个类即可。
同时为了更方便,ListAPIView与CreateAPIView还可以再合并,合并为ListCreateAPIView
另外三个组合为:(加起来就是9个)
在8.4中,此时的代码已经很少了,但是在处理这五个请求的时候,还是拆成了两个视图类来处理的,因为有几个需要传入主键值,虽然我们可以自己去解决这个问题,但没那么方便了。drf提供的ModelViewSet,就可帮我们解决这个问题。
使用方法:
# 视图
from rest_framework.viewsets import ModelViewSet
class Book5View(ModelViewSet): #5个接口都有
queryset = Book.objects
serializer_class = BookSerializer
urlpatterns = [
# 两个路由都对应一个视图函数
path('books5/',
views.BookView.as_view(actions={'get':'list','post':'create'})),
re_path('books5/(?P\d+)' ,
views.BookView.as_view(actions={'get':'retrieve','put':'update','delete':'destroy'})),
]
actions参数后面的值是什么意思呢?
路由其实也可以自动生成。详情见九章节。
上文中actions改写路由的方法,不只局限于ModelViewSet,ViewSetMixin也可以改写路由
# views.py
from rest_framework.viewsets import ViewSetMixin
class BookView(ViewSetMixin,APIView): #一定要放在APIVIew前
def get_all_book(self,request):
print("xxxx")
book_list = Book.objects.all()
book_ser = BookSerializer(book_list, many=True)
return Response(book_ser.data)
# urls.py
#继承ViewSetMixin的视图类,路由可以改写成这样
path('books/', views.BookView.as_view(actions={'get': 'get_all_book'})),
可以利用ViewSetMixin来实现,控制路由匹配,当匹配中了且访问是get请求,那么就交给BookView类中的get_all_book函数处理。
需要注意的是,在继承的时候ViewSetMixin一定要在APIView的前面,因为二者都拥有as_view和view方法,面向对象继承的属性查找顺序是:自己没有就先去第一个父类中查找,所以一定要放在前面。
ViewSet:
在8.5章节中,我们使用ModelViewSet和ViewSetMixin,可以实现路由改写这么一个效果,当路由匹配中了之后,根据请求的方式,来分别设定响应所要执行的方法,如:
path('books/', views.BookView.as_view(actions={'get':'list','post':'create'})),
#当路径匹配,又是get请求,会执行BookView的list方法 (list来源于ListModelMixin)
drf中,可以自动生成路由,不需要我们再去使用什么re_path写正则,或者使用转换器什么的。
使用之前需要注意,视图类需要继承ModelViewSet
routers模块:
第一步:导入routers模块。
第二步:有两个类,实例化得到对象。
# urls.py
from rest_framework import routers
routers.DefaultRouter
routers.SimpleRouter
'''
DefaultRouter 与 SimpleRouter 二选一
前者可以生成更多路由,但是没什么用,一般都是后者SimpleRouter简单路由
'''
第三步:注册
# router.register('前缀','继承自ModelViewSet视图类','别名') 别名可用作反向解析
router.register('books',views.BookViewSet)
第四步:将自动生成的路由,加入到原路由中
urlpatterns += router.urls
# 两个列表直接相加,列表相加等于拼接,当然也可以用for然后append
action 的作用:
使用示例:
from rest_framework.decorators import action # 装饰器
class BookViewSet(ModelViewSet):
queryset =Book.objects.all()
serializer_class = BookSerializer
@action(methods=['GET','POST'],detail=True)
def get_1(self,request,pk):
# print(pk)
book=self.get_queryset()[:2] # 从0开始截取一条
ser=self.get_serializer(book,many=True)
return Response(ser.data)
detail:该参数用来指定,此查询是否需要根据主键值进行精准查询,如果为True,那么下面被装饰的函数,就需要将pk传入。
在我们写接口的时候,有的需要登陆之后才可以查看,比如淘宝的首页就不需要登陆,但是添加购物车就需要登陆之后才可以,所以本章节就是介绍DRF模块中的认证,以及如何自定义书写认证功能。
认证的写法:
书写一个类,继承BaseAuthentication,随后重写authenticate方法,认证的逻辑就写在该方法内。
认证通过返回两个值,其中一个值最终给了Requet对象的user。
认证失败,抛异常:APIException或者AuthenticationFailed
与序列化校验组件一样,认证也是分为全局配置和局部配置的。
认证的源码分析:
前面的APIView源码分析汇总有提到,APIView中重新定义了as_view,然后绕了一圈又回到了自己的dispatch,dispatch方法中又调用了initial,而这里面有认证、权限、频率的校验。
阅读initial中,认证组件相关的代码:self.perform_authentication(request)
def perform_authentication(self, request):
request.user
可以发现源码里就一句request.user,这个request是经过APIView封装过的,(注:已经不是auth模块章节了,这里的.user不要记混淆)我们接着按照属性查找顺序,查找drf中的user方法,由于request其实现在是Request,所以直接去drf下的Request模块中查找user方法,并阅读下。
核心就在self._authenticate()
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
def _authenticate(self):
# 遍历拿到一个个认证器,进行认证
# self.authenticators 配置的一堆认证类产生的认证类对象组成的
# self.authenticators 在视图类中配置的一个个的认证类:authentication_classes=[认证类1,认证类2]
for authenticator in self.authenticators:
try:
# 认证器(对象)调用认证方法authenticate(认证类对象self, request请求对象)
# 返回值:登陆的用户与认证的信息组成的,tuple元祖
# 该方法被try包裹,代表该方法会抛异常,抛异常就代表认证失败
user_auth_tuple = authenticator.authenticate(self) #这里的self是request对象
except exceptions.APIException:
self._not_authenticated()
raise
# 返回值的处理
if user_auth_tuple is not None:
self._authenticator = authenticator
# 如何有返回值,就将 “登陆用户” 与 “登陆认证” 分别保存到 request.user、request.auth
self.user, self.auth = user_auth_tuple
return
# 如果返回值user_auth_tuple为空,代表认证通过,但是没有 登陆用户 与 登陆认证信息,代表游客
self._not_authenticated()
实现效果:
一、准备工作
由于涉及到保存用户信息,所以这里在原来图书表上,又新增了一些表。
class User(models.Model):
username=models.CharField(max_length=32)
password=models.CharField(max_length=32)
user_type=models.IntegerField(choices=((1,'超级用户'),(2,'普通用户'),(3,'注销用户')))
class UserToken(models.Model):
token=models.CharField(max_length=64)
user=models.OneToOneField(to=User,on_delete=models.CASCADE)
UserToken表用来储存给用户返回的随机字符串。
由于在django2.x及以上版本中,默认不在级联更新/级联删除,所以在创建外键字段的时候,需要加上on_delete参数。
on_delete参数参数的值,除了models.CASCADE级联更新删除,还有其他的,具体会在10.2章节补充。
由于需要登陆做认证,那么就先完成一个登陆接口。
路由
urlpatterns = [
...
path('login/', views.LoginView.as_view()),
...
]
视图
from rest_framework.views import APIView
from rest_framework.response import Response
import uuid
class LoginView(APIView):
authentication_classes = []
# 获取提交的登陆数据
def post(self,request):
username=request.data.get('username')
password=request.data.get('password')
user=models.User.objects.filter(username=username,password=password).first()
if user:
# 登陆成功,生成一个随机字符串
token=uuid.uuid4()
# models.UserToken.objects.create(token=token,user=user) 用它每次登陆都会重新记录一条token,不太好。
# update_or_create 有就更新,没有就新增
models.UserToken.objects.update_or_create(defaults={'token':token},user=user)
return Response({'status':100,'msg':'登陆成功','token':token})
else:
return Response({'status': 101, 'msg': '用户名或密码错误'})
一、update_or_create:如果当前有数据,那么就更新覆盖,如果原来没这数据,那就创建。 参数的含义为:要更新或创建什么字段的数据,并且判断的依据是什么。
结合起来就是,判断usertoken表中,user字段的值是否已经存在,如果没有那就创建,如果有,那么就更新usertoken表中token字段的数据。
update_or_create(defaults={‘token’:token},user=user)
- 前面的user表示usertoken表中的user字段
- 后面的user表示登陆用户的对象
在usertoken表中对应user字段存的就是对象,所以user字段的值 = 对象 = 对象 = user,如果usertoken表中已经存在该数据,那么就更新,反之就新建,继续存入这个对象以及生成的UUID。
二、uuid:随机字符串模块,**uuid.uuid4()**可以生成一串几乎不会有重复的字符串,但也是有风险的,会出现生成UUID重复的可能,本案例只是为了看出效果。
三、其他:
当登陆接口的逻辑写完之后,返回JSON数据,这里使用APIView的Response还是JSONresponse,都无所谓,因为这两个方法都是将字段序列化为JSON字符串然后返回的。(这个时候还没用到认证组件)
二、书写认证类
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01.models import UserToken
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
# 认证逻辑,如果认证通过,返回两个值
# 如果认证失败,抛出AuthenticationFailed异常
token=request.GET.get('token') # token值需要调用登陆API获取,如果没有那就是没登陆。
if token:
user_token=UserToken.objects.filter(token=token).first()
# 认证通过
if user_token:
return user_token.user, token
else:
raise AuthenticationFailed('认证失败')
else:
raise AuthenticationFailed('请求地址中需要携带token')
在认证类中,我们规定了需要在URL地址栏中,添加参数token,值为登陆之后返回给前端的token。
如果认证成功,那么就返回两个值。 如果不成功那么就抛出异常,推荐是AuthenticationFailed
其中,返回的两个值,会被request.user和request.auth所接收,前者后续需要在权限器中使用,来校验当前用户是否有权限,所以返回的第一个值需要是该用户对象。
三、使用认证类
局部使用:
class BookViewSet(ModelViewSet):
# 可以有多个认证,从左到右依次执行
authentication_classes=[MyAuthentication]
queryset =Book.objects.all()
serializer_class = BookSerializer
@action(methods=['GET',],detail=True)
def get(self,request,pk):
...
当路由匹配成功之后,如果是GET请求,那么就执行视图类中的get方法。
现在又局部使用了认证,并且指定的认证类为MyAuthentication,该类就是刚刚写的认证类,里面规定了需要以get请求的方式,传入token值做校验,至于说是放在哪里,是请求头还是浏览器缓存,这个是前端需要做的(前后端分离)。
如果没有携带token,那么就会根据我们书写的MyAuthentication抛出异常。
访问示例:http://127.0.0.1/book/?token=dce869ce-cac9-49ea-8d30-dc9d35b2163d get请求
全局使用
# 全局使用,在setting.py中配置
REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",]
}
# 附:局部禁用
authentication_classes=[]
注意:一旦使用全局,那么连登陆也需要校验,所以就要再使用局部禁用。
至此,当调用图书接口的时候,由于我们书写了自定义认证,需要在URL后面跟上token参数,并且根据值进行校验,而这个token值只有登陆之后才会生成,所以这就完成了接口的认证。
2023-2-7,补充下ORM章节遗漏
该参数表示级联关系:
models.CASCADE 级联更新删除(作者没了,详情也没)
models.DO_NOTHING 原封不动(出版社没了,书还是那个出版社出版)
models.SET_NULL 删除设置为空(部门没了,员工部门为空,但是该字段要有null=True)
models.SET_DEFAULT 删除设置为默认值(员工进入默认部门)
绑定了外键关系以后,在插入数据的时候会因为外键值当前并不存在而报错,如果需要先把数据新增,然后再完善外键关系时,就需要使用到断关联,断开实质上的外键关系。
断开以后就是逻辑上的关联,实质上没有外键联系,增删不会受外键影响,而且还可以提升效率。
db_constraint=False
在没有断开关联的时候,插入一条数据时,如果外键绑定的那条数据并不存在,那么会插入不进去。
如:新增图书,绑定出版社外键为1000的,但是id=1000的出版社并不存在,那么这个时候数据会插入不进去。
断开关联就可以直接插入进去,删除也是同理,删除作家出版社,如果绑定的有图书是产出不掉的。
需要注意脏数据,由于不去根据外键进行校验,所以图书很可能会绑定上并不存在的出版社或作者,这种数据就叫做脏数据,那么作为程序员就需要避免脏数据的产生。
在实际开发当中,每张表基本上都要有的字段,我们为了节省代码可以提取出一个基本类,然后由其他类继承这个基本类。
但是这个时候就会遇到一个问题,那就当执行数据库迁移命令的时候,会连同这张表一并创建到数据库,当不需要数据库新建出表的时候,就可以使用到抽象表
代码示例:
class BaseModel(models.Model):
is_delete=models.BooleanField(default=False)
# auto_now_add=True 只要记录创建,不需要手动插入时间,自动把当前时间插入
create_time=models.DateTimeField(auto_now_add=True,null=True)
# auto_now=True,只要更新,就会把当前时间插入
last_update_time=models.DateTimeField(auto_now=True,null=True)
class Meta:
abstract=True # 抽象表,不再数据库建立出表
class Book(BaseModel):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
# db_constraint=False 逻辑上的关联,实质上没有外键练习,增删不会受外键影响,但是orm查询不影响
bind_publish = models.ForeignKey(to='Publish',on_delete=models.DO_NOTHING,db_constraint=False)
bind_author = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('bind_book','bind_author'))
class Publish(BaseModel):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=64)
现在Book表和Publish表都有着共同的几个字段:是否删除、创建时间、更新时间
于是我们抽出来一个基类叫BaseModel,该类中定义Meta类,添加属性abstract=True,这样就表示为抽象类,执行数据库迁移命令不会自动创建表。
注意:
基本上与认证大差不差
权限使用步骤:
写一个类,继承BasePermission,重写has_permission,如果权限通过,就返回True,不通过就返回False
权限就不需要再抛出异常了。
一、书写认证代码
from rest_framework.permissions import BasePermission
class UserPermission(BasePermission):
def has_permission(self, request, view):
# 由于认证已经过了,并且返回了用户对象,request.user拿到当前登录用户对象
user = request.user
# 在表设计中,User表的user_type字段用于记录用户类型,1为超级用户。
# 不是超级用户的不可以调用
if user.user_type==1:
return True
else:
return False
二、全局/局部使用
# 局部使用
class TestView(APIView):
permission_classes = [app_auth.UserPermission]
# 全局使用
REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["导入自定义认证类的模块路径",],
'DEFAULT_PERMISSION_CLASSES': [
'导入自定义权限类的模块路径',
],
}
# 局部禁用
class TestView(APIView):
permission_classes = []
根据用户访问IP来限制访问次数:
代码示例:
应用.tools.throttle.py
from rest_framework.throttling import SimpleRateThrottle
class MyThrottle(SimpleRateThrottle):
scope='frequency'
def get_cache_key(self, request, view):
return request.META.get('REMOTE_ADDR') # 返回访问的IP地址
全局使用:
# settings.py文件
REST_FRAMEWORK={
"DEFAULT_THROTTLE_CLASSES": ["app01.tools.throttle.MyThrottle", ],
'DEFAULT_THROTTLE_RATES': {
'frequency': '3/m' #这里需要与自定义频率类定义的scope值一致,frequency单词意为:频率
},
}
局部使用:
# settings.py文件
REST_FRAMEWORK={
#"DEFAULT_THROTTLE_CLASSES": ["app01.tools.throttle.MyThrottle", ],
'DEFAULT_THROTTLE_RATES': {
'frequency': '3/m'
},
}
# views.py文件
from app01.tools.throttle import MyThrottle
class Books(APIView):
# 局部使用频率限制
throttle_classes = [MyThrottle,]
def get(self,request):
...
至此,所有用户再来访问的时候都是一分钟三次,因为是基于IP进行限制的。
附:
2023-01-29
在教学中,是利用内置限制类完成的,所以用户的认证也要采用内置的,认证所需的用户表是auth模块的user表。
如果我们使用自己写的user表和usertoken表来进行认证,然后认证通过后再区别未登录和登陆过的用户,然后分别设置不同的访问频率,这种需求就需要使用自定义限制类,但是问题就在于,自定义限制类目前没学过,没学过如何针对未登录用户做限制,如何对登陆用户做限制,如何对登陆的VIP用户或者超级用户做限制,所以本章节后续需要再重新补充。
2023-02-08
内置类相关笔记已清空。
自定义频率限制缺少针对用户的判断,如该用户是不是匿名用户,是不是普通用户,是不是VIP用户,缺乏这方面的频率。
可实现效果:
一、安装第三方模块
pip3 install django-filter
二、配置文件与路由的配置
# settings.py
INSTALLED_APPS = [
...略...
'django_filters',
]
# urls.py
urlpatterns = [
path('authors/',views.AuthorViews.as_view()),
]
三、局部配置
2023-01-30 全局配置没生效,所以这里只写局部了,十四章节中的排序也同样如此。
局部配置:
from rest_framework.generics import ListAPIView
from django_filters.rest_framework import DjangoFilterBackend
class AuthorViews(ListAPIView):
# 由于ListAPIView继承了GenericAPIView,所以queryset和serializer_class这两个属性都要定义。
queryset = Author.objects.all()
#自定义的序列化类
serializer_class = ser.AuthorModelSerializer
# 局部禁用认证
authentication_classes = []
# 局部禁用权限
permission_classes = []
# 局部配置使用内置的过滤类
filter_backends = [DjangoFilterBackend]
filterset_fields = ('name',) # 可根据name字段进行查找,可以配置多个可查询字段,但必须是元祖格式。
注意事项:
视图类继承ListAPIView,其他的不好使。
由于过滤我们没有使用自定义,而是直接使用内置的,所以在视图类中进行局部配置的时候,需要导入内置相关类DjangoFilterBackend
最后需要指定,通过那个字段,来进行过滤,如:filterset_fields = (‘name’,) 根据name字段的值来进行过滤。
filterset_fields 的值,必须是个元祖,所以上述代码中,就写成了**(‘name’,)**
附:python中元组数据类型的多种赋值方式
a = 1,
b = (1,)
c = 1,2
d = (1,2)
print(type(a)) #
print(type(b)) #
print(type(c)) #
print(type(d)) #
无全局配置
与过滤差不多,代码示例:
局部配置:
from rest_framework.generics import ListAPIView
from rest_framework.filters import OrderingFilter #导入内置排序类
from app01.models import Book
from app01.ser import BookSerializer
class Book2View(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 排序(与过滤共用一个filter_backends,所以可以再把过滤写上)
filter_backends = [OrderingFilter]
ordering_fields = ('id', 'price')
如何使用?
例如:根据id值进行排序
http://127.0.0.1:8000/test/?ordering=-id #反向(id值从大到小)
http://127.0.0.1:8000/test/?ordering=id #正向(id值从小到大)
在前面的认证、权限、频率等章节当中,当我们的访问出问题,比如没有携带token、没权限、频率上限了,这个时候会直接抛出异常,如:
可以发现并没有携带请求响应状态码,也没有msg信息,这显然不符合规范,那么我们就需要进行完善。
drf内置的有异常处理方法,但是只对请求是否是404、是否具有认证权限频率的异常,最后return none表示,其他的异常就不处理了,交给django自己去做处理,但交给django去处理的话,返回的就肯定不是JSON格式的数据(而是一个报错页面),所以我们需要写一个自定义异常类,对于内置的方法进行完善。
书写自定义异常处理方法:
from rest_framework.views import exception_handler # 内置的异常处理方法
from rest_framework.response import Response
from rest_framework import status
def my_exception_handler(exc, context):
# exc为异常对象,记录了错误信息及状态码。
# context为字典,里面详细记录了哪个视图函数报了怎么错,后续可做日志记录等
response = exception_handler(exc, context) # 先执行内置的异常处理
'''
内置的异常处理方法会有两种返回值,一种是none,一种是response
为none表示这个异常,内置的处理不了,直接交给django处理
为response表示,这个异常内置处理的(但是并不符合规范,所以我们后面针对这种可能,也要做进一步处理。)
'''
# 如果内置处理不了,那么就由下面的代码做处理
if not response:
# 可以做更精细粒度的异常处理,如视图中数字运算出现分母为0的情况,那么就可以书写下列两行代码,对这种类型的错误进行异常处理。
if isinstance(exc, ZeroDivisionError):
return Response(data={'status': 701, 'msg': "除以0的错误" + str(exc)}, status=status.HTTP_400_BAD_REQUEST)
# 如果内置的处理不了,那么就返回符合规范的JSON数据。
return Response(data={'status':1000,'msg':str(exc)},status=status.HTTP_400_BAD_REQUEST)
else:
# 如果内置的可以处理,说明就是认证、权限、频率这一块被查出来异常了
#由于默认只返回 {detail:xxxx},并没有响应码之类的,所以我们这边做了进一步的处理。
return Response(data={'status':888,'msg':response.data.get('detail')},status=status.HTTP_400_BAD_REQUEST)
全局配置
'EXCEPTION_HANDLER': '应用名.自定义的模块路径',
5.8章节中的扩展,进一步封装response。
from rest_framework.response import Response
class APIResponse(Response):
def __init__(self,code=100,msg='成功',data=None,status=None,headers=None,**kwargs):
dic = {'code': code, 'msg': msg}
if data:
dic = {'code': code, 'msg': msg,'data':data}
dic.update(kwargs)
super().__init__(data=dic, status=status,headers=headers)
# 使用
return APIResponse(data={"name":'liuyu'},token='111-222-333',others='abcdefg')
return APIResponse(data={"name":'liuyu'})
return APIResponse(code='101',msg='错误',data={"name":'liuyu'},token='111-222-333',others='abcdefg',header={})
路由:
urlpatterns = [
path('book/', views.BookView.as_view()),
re_path('book/(?P\d+)' , views.BookDetailView.as_view()),
]
模型:
class BaseModel(models.Model):
is_delete=models.BooleanField(default=False)
create_time=models.DateTimeField(auto_now_add=True,null=True)
last_update_time=models.DateTimeField(auto_now=True,null=True)
class Meta:
abstract=True # 抽象表,不再数据库建立出表
class Book(BaseModel):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
bind_publish = models.ForeignKey(to='Publish',on_delete=models.DO_NOTHING,db_constraint=False)
bind_author = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('bind_book','bind_author'))
# 序列化器中fields属性使用
@property
def publish_name(self):
return self.bind_publish.name
@property
def author_list(self):
author_list=self.bind_author.all()
return [ {'name':author.name,'sex':author.get_sex_display()} for author in author_list]
class Publish(BaseModel):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=64)
class Author(BaseModel):
name = models.CharField(max_length=16)
sex = models.IntegerField(choices=((1, '男'), (2, '女')),null=True)
author_detail = models.OneToOneField(to='AuthorDetail',on_delete=models.CASCADE)
class AuthorDetail(BaseModel):
msg = models.TextField()
序列化器:
class BookSer(ModelSerializer):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.fields['bind_publish'].write_only = True
self.fields['bind_author'].write_only = True
class Meta:
model = models.Book
# 仅序列化这些字段,列表中均为模型层中类的属性,那么既然是属性,方法也可以伪装成属性,
# 'bind_publish','bind_author'就是类中的伪属性方法。
fields = ['title','price','publish_name','author_list','bind_publish','bind_author']
extra_kwargs={
'id':{'read_only':True},
'publish_name':{'read_only':True},
'author_list':{'read_only':True},
}
视图:
from rest_framework.mixins import ListModelMixin,CreateModelMixin,UpdateModelMixin, DestroyModelMixin,RetrieveModelMixin
from rest_framework.generics import GenericAPIView,ListAPIView
from app01.tools import ser # 自定义序列化类,ser.BookSer
# 负责全查、单增多增、多更新
class BookTestView(ListAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects
serializer_class = ser.BookSer
# 全查
def get(self, request):
return self.list(request)
# 单增和多增
def post(self, request):
# 如果提交的数据格式为字典,这里默认为是单条数据,
# 如:{"title": "测试","price": "778","bind_publish": "4","bind_author": "7",}
if isinstance(request.data,dict):
return self.create(request) # 直接使用ListModelMixin扩展类中的create方法即可创建。
# 如果是列表格式,那默认是批量新增数据
# 如:[{"title": "测试1","price": "778"},{"title": "测试2","price": "885"}]
elif isinstance(request.data,list):
# 序列化多条数据
book_ser = ser.BookSer(data=request.data,many=True)
# 提交过来的数据经过序列化组件校验,判断是否合法。
if book_ser.is_valid(raise_exception=True):
# 如果指定raise_exception=True,其实可以不用书写if判断,因为直接就抛出异常了,然后经过内置异常+自定义异常完成response处理。
book_ser.save()
return Response(book_ser.data)
else:
return Response({'status': 101, 'msg': '校验失败'})
# 批量更新
def put(self,request):
# 当提交的数据为列表格式时,视为批量更新
# 如: http://127.0.0.1:8000/book 请求方式put
# 发送数据: [{'id':1,'name':'流浪地球2','price':47.8},{'id':2,'name':'第九区','publish':'文学出版社'}]
# 意为: 将id为1的图书,名称改为流浪地球2,价格为47.8。 id为2的图书,名称改为第九区,出版社改为文学出版社。
if isinstance(request.data,list):
# 被修改图书的列表
book_list = []
# 需要修改的内容(不包含ID因为ID不可被修改,所以pop删除掉)
modify_book_list = []
for book_obj in request.data:
# 会拿到被删除的值,该值为id值,不可用于修改,所在需要对将要修改的数据做处理。
pk = book_obj.pop('id') # 此时的book_obj {'name':'流浪地球2','price':47.8}
modify_book_list.append(book_obj)
# 拿到需要进行修改的图书
book = Book.objects.filter(pk=pk).first()
book_list.append(book)
# enumerate枚举
for index,book_obj in enumerate(book_list):
# index为图书列表中当前对象的索引
# book_obj为对象
book_ser = ser.BookSer(instance=book_obj,data=modify_book_list[index],partial=True)
book_ser.is_valid(raise_exception=True) # 使用内置的异常处理,所以就不If判断了。
book_ser.save()
return Response({'status':200,'msg':'更新成功'})
else:
return Response({'status':400,'msg':'更新失败'})
# 单个删除,略
# 负责单查,单更新
class BookDetailTestView(GenericAPIView, RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin):
queryset = Book.objects
serializer_class = ser.BookSer
# 单查
def get(self, request, pk):
return self.retrieve(request, pk)
# 单个更新
def put(self, request, pk):
return self.update(request,pk)
def delete(self, request, pk):
return self.destroy(request, pk)
# 单个删除,略。
内置的分页器有三种:
基本分页
使用方法:
注意事项:
视图类需要继承ListAPIView,由于ListAPIView继承自GenericAPIView,所以之前使用GenericAPIView书写的接口可以直接替换即成为ListAPIView即可。
视图类中的queryset属性必须是对象,不可以是QuerySet对象
queryset = Book.objects.all()
代码示例:
pagination_class = PageNumberPagination
from rest_framework.pagination import PageNumberPagination
# 代码沿用十七章节,路由、序列化类均不在重复书写。
class BookView(ListAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects
serializer_class = ser.BookSer
# 配置分页
pagination_class = PageNumberPagination
def get(self, request):
return self.list(request)
分页的条数等其他配置,有两种方式操作:
settings.py配置
# settings.py文件
REST_FRAMEWORK={
# 分页
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 3 # 每页数目
}
也可以在子类中定义属性
# views.py文件
class MyPageNumberPagination(PageNumberPagination):
page_size=3 # 一页展示三条
class BookTestView(ListAPIView):
queryset = Book.objects.all() #不叫all会报错
serializer_class = ser.BookSer
# 配置分页
pagination_class = MyPageNumberPagination # 指定成子类
效果展示:
除了控制每页的返回条数以外,还可以配置其他的:
page_size = 5 每页条数,每页4条
page_query_param=‘p’ 查询第几页的key,默认是page,示例中修改为 p
默认按照页数查询时URL:http://127.0.0.1:8000/book/?page=2 表示查询第二页
现在改成了: http://127.0.0.1:8000/book/?p=2
page_size_query_param=‘size’ 配置后可以在URL中选择每页显示的条数,示例中=size
例如:http://127.0.0.1:8000/book/&size=6 http://127.0.0.1:8000/book/?page=1&size=6
配置之后可以突破page_size=3的限制,展示更多条数据,但是同时又被max_page_size每页最大显示条数所限制
max_page_size = 5 每页最大显示条数,示例为最大显示5条。
偏移分页
代码示例:
from rest_framework.pagination import LimitOffsetPagination
class MyLimitOffsetPagination(LimitOffsetPagination):
default_limit = 5 #默认一页是多少条
#offset_query_param = 'offset' # 从哪里开始取的key
#limit_query_param = 'limit' # 取多少条的key
max_limit = 5 # 最多可以取多少条
class BookTestView(ListAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = ser.BookSer
# 配置分页
pagination_class = MyLimitOffsetPagination
常用参数:
default_limit = 5 默认每页条数,5条
offset_query_param = ‘offset’ 从哪里开始查询的key
offset_query_param = ‘offset’ 与 limit_query_param = ‘limit’ 连用,一般不修改,直接省略默认就是offset和limit
表示从哪里开始取,这一页取几位。
limit_query_param = ‘limit’ 取几位的key
两个参数不写默认就是offset和limit,使用示例:http://127.0.0.1:8000/book/?limit=5&offset=0
offset=0表示从主键值0往后取,取limit=5,也就是5位,但是这个与切片类似,同样是“顾头不顾腚”,因此想要取id为1-5时,offset需要等于-1等于0
max_limit = 5 一页最大只能取多少,示例为5条。
游标分页
该分页器的特性:
代码示例:
from rest_framework.pagination import CursorPagination
class MyCursorPagination(CursorPagination):
#cursor_query_param = 'cursor' # 每一页查询的key
page_size = 3 # 每页显示的条数
ordering = '-id' # 排序字段
class BookTestView(ListAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = ser.BookSer
# 配置分页
pagination_class = MyCursorPagination
常用参数:
cursor_query_param = ‘cursor’ 每一页查询的key,默认就好不需要改
由于游标分页只支持上下页,因为每一页都打上了标记,所以在面对大量数据的时候可以做到响应速度特别特别快。
返回数据的时候会带上 “next"与"previous”,如:
“next”: “http://127.0.0.1:8000/book/?cursor=cD04”,
"previous": "http://127.0.0.1:8000/book/?cursor=cj0xJnA9MTA%3D",
所以这个参数的作用就是这个,默认就是cursor,不用修改就好。
page_size = 3 每页显示的条数
ordering = ‘-id’ 根据那个字段进行排序,“-”为反向排序,不加表示正向。
出现这个报错是因为视图类中的queryset参数,赋值的时候偷懒,自己不写**.all()**,想让GenericAPIView补充
但是这里不自己加上**.all()**就会报这个错。
在上文中说道,使用分页器需要继承ListAPIView,但其实APIView和GenericAPIView也是可以使用的,只不过封装程度没有那么多,需要手动书写一些代码,不过换来的是可控性很高。
代码示例:
class TestTest(APIView):
def get(self,request):
book_list = Book.objects.filter(is_delete=False)
# 实例化分页器对象
page_obj = MyPageNumberPagination()
# 拿到图书分页queryset,也可以再获取上一页和下一页(因为游标分类需要)
book_page_queryset = page_obj.paginate_queryset(book_list,request)
next_url = page_obj.get_next_link()
previous_url = page_obj.get_previous_link()
# 将分页器处理好的querset对象进行序列化处理
book_ser = ser.BookSer(book_page_queryset,many=True)
# 完善response并返回
return Response({'next':next_url,'previous':previous_url,'results':book_ser.data})
作用:
一、安装:
pip3 install coreapi
二、路由与settings配置:
urls
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('docs/', include_docs_urls(title='API接口文档')),
]
settings
REST_FRAMEWORK={
# 自动生成API文档
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
三、视图类添加注释
views
class BookTestView(ListAPIView, ListModelMixin, CreateModelMixin):
'''
get:返回所有图书信息
post:新增图书
put:更新图书信息
'''
def get(self, request):
pass
def post(self, request):
pass
def put(self, request):
pass
如果该接口是单一方法,也就是说内部只有例如get方法,那么直接写注释。
如果该接口包含了多个方法,那么就需要使用“ 方法名 :注释信息”。
查看效果:
上图中create接口有几个必传参数,如"title"、“price”、"bind_publish"后面的描述信息原本是没有的,需要在models中给对应字段添加help_text属性。
# 示例:
class Book(models.Model):
title = models.CharField(max_length=32, help_text='图书标题')
price = models.DecimalField(max_digits=8,decimal_places=2, help_text='图书价格')
另外这个文档也是可以直接用来测试接口的:
先安装模块
pip3 install djangorestframework-jwt
什么是jwt:
jwt生成原理
jwt分三段式:头.体.签名
头和体是可逆加密的,让服务器可以反解出user对象。
签名是不可逆加密,保证整个token的安全性。
头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,
不可逆加密一般采用hash(md5)算法
头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
"company": "公司信息",
...
}
体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
"user_id": 1,
...
}
签名中的内容是安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码,加在一起再进行md5加密。
{
"head": "头的加密字符串",
"payload": "体的加密字符串",
"secret_key": "安全码"
}
校验
drf项目的jwt认证开发流程
用账号密码访问登录接口,登录接口逻辑中调用签发token 算法,得到token,返回给客户端,客户端自己存到cookies中
校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户
注:登录接口需要做 认证 + 权限 两个局部禁用
前言:
扩展auth user表
auth模块的user表没有手机号字段,没有头像字段等,所以这里我们选择进行扩展
模型层models.py
from django.contrib.auth.models import AbstractUser # 与auth.user表一样,都继承AbstractUser
# 也叫user没关系,因为会带上应用名前缀,如api_user,另外一个是auth_user
class User(AbstractUser):
phone = models.CharField(max_length=11)
photo = models.ImageField(upload_to='icon')
# ImageField字段依赖于pillow模块,pip3 install pillow
扩展auth.user表还需要在settings.py文件中进行配置,同时上传的头像这里选择保存在项目指定目录,后续再介绍上传到云cos对象存储或者redis等。
# 扩展user表
AUTH_USER_MODEL = 'api.User'
MEDIA_URL='/media/'
MEDIA_ROOT=os.path.join(BASE_DIR,'media')
扩展之后,创建超级用户
python manage.py createsuperuser
三种方法:
前两种都是使用jwt内置的ObtainJSONWebToken类,这个类又是视图类,里面写好了token内,头、荷载、签名的处理,并且返回值就是序列化之后的。所以我们可以直接在路由中使用该内置视图类ObtainJSONWebToken.as_view()
obtain_jwt_token是ObtainJSONWebToken视图类中,帮我们直接调用了as_view(),所以第二种等同于第一种
如果想要在“打包”三段token的时候自己可控,那么就可以自己书写逻辑即可。
代码示例(前两种):
#from rest_framework_jwt.views import ObtainJSONWebToken # 二选一
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
# path('login/', ObtainJSONWebToken.as_view()), # 二选一
path('login/', obtain_jwt_token),
]
ObtainJSONWebToken视图类中没有定义请求方法,但是它所继承的基类JSONWebTokenAPIView是有一个post方法的。
所以我们在路由配置好之后,就可以提交POST请求来获取token了,由于本章节使用的是扩展的auth user表,所以在提交POST请求的时候携带的key值为username和password
接口请求示例:
第三种(自定义):
上面利用内置类/方法写的login接口属于“自动签发”。
手动签发需要将user对象拿到,然后转成payload,再转成token,最后序列化返回
详细代码略,核心操作就下面这些。
from rest_framework_jwt.utils import jwt_payload_handler
from rest_framework_jwt.utils import jwt_encode_handler
payload = jwt_payload_handler(user) # 将获取到的用户对象做成荷载
token = jwt_encode_handler(payload) # 合成token
现在客户端拿到了token值,那么携带着token去进行访问的时候,需要在请求的头部,指定Authorization属性,值为“jwt token值”,由于内置的必须要在前面写上 “jwt和 空格” ,所以这里我们利用第十章节的自定义认证来进行处理,在这个过程中,如何对第二段校验、反取user、第三段的校验等,这里使用jwt给我们封装好的方法。
请求示例:
代码示例:
自定义校验类:
from rest_framework import exceptions
# BaseJSONWebTokenAuthentication继承于drf的BaseAuthentication
# 这里使用子类BaseJSONWebTokenAuthentication是因为含有authenticate_credentials方法,可从荷载中获取用户对象,而drf原生的认证类并没有。
class MyToken(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
# 根据规定从请求头中获取token
jwt_value = str(request.META.get('HTTP_AUTHORIZATION'))
# 认证
try:
payload = jwt_decode_handler(jwt_value)
except Exception:
raise exceptions.AuthenticationFailed("认证失败")
# 获取用户对象
user = self.authenticate_credentials(payload)
return user,None
jwt_value:表示从请求头中,获取到携带的token
payload:将token的三段拆开,解析出第二段的payload,同时检验有没有超时,有没有篡改。
user:payload荷载为用户相关的信息,数据格式为字典,调用authenticate_credentials方法转成user用户数据对象。
这个user对象就是当前登陆的用户,根据自定义认证类的原则,需要返回两个人值,一个是当前用户对象,一个随便,主要是用户对象,稍后可能还需要对权限进行校验。
视图类
# 视图
from api.utils.auth import MyToken
class BookView(APIView):
# 使用自定义校验认证,局部配置
authentication_classes = [MyToken,]
permission_classes = [IsAuthenticated]
def get(self,request):
print(request.user.email)
return Response('ok')
全局配置:和drf的认证一样
REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["api.utils.auth.MyToken",]
}
接口访问示例:
在20.1.2章节的开头,我们使用的是自定义校验,原因是内置的在传值时较为麻烦,但是内置的是有其他应用场景的。
附:
内置的认证,之所以需要在访问的时候在头部添加参数,并且还要是 “jwt+空格+token” 的形式,是因为jwt默认把当前来访问的用户是匿名用户,不带token也可以进行访问。但如果又在全局或者局部配置了内置的权限 permission_classes = [IsAuthenticated] #判断当前用户是否登陆 ,那么就代表只有登陆成功的用户才可以访问,匿名就不可以了。
我们在上一章节中,是觉得内置不好用,所以才自定义的,现在再回过头就发现,使用自定义auth认证那就必须携带token了。
代码示例:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
from api.auth import MyToken
class BookView(APIView):
# 使用内置的校验token
authentication_classes = [JSONWebTokenAuthentication]
permission_classes = [IsAuthenticated]
def get(self,request):
print(request.user.email)
return Response('ok')
'''
auth模块中的is_authenticated()方法,就是校验当前用户是否经过校验
同理,内置的IsAuthenticated权限类也是,也是校验当前用户是否经过校验
'''
接口访问示例:
因为使用的是内置的,所以需要 “jwt+空格” ,大小写无所谓。上图中是没有加空格,所以会被认为是匿名用户。
但是配置的权限认证是IsAuthenticated,就是校验是否是登陆用户的,所以此时该接口只能登录用户才可以访问。
在20.1章节中,我们实现的简单使用,并没有状态码之类的信息,所以本章节就是对20.1的进一步补充,对签发token和检验token的业务代码进行完善。
查看源码:
由于使用的内置的,所以jwt的settings中可以找到配置的认证类
from rest_framework_jwt import settings
'JWT_RESPONSE_PAYLOAD_HANDLER':
'rest_framework_jwt.utils.jwt_response_payload_handler',
# 查看内置的处理响应方法
from rest_framework_jwt.utils import jwt_response_payload_handler
jwt_response_payload_handler就是最终返回token的方法,如下:
def jwt_response_payload_handler(token, user=None, request=None):
return {
'token': token
}
可以看到,就只返回了token,这和我们在使用postman或者apipost接口测试工具的时候,返回的结果是一样的。
自定义获取token的返回值:
有了上面的分析,我们在自定义的时候直接重写源码中这个方法就好,然后在settings中配置一下,响应走我们重写的jwt_response_payload_handler方法。
# 示例新建路径:应用api-->utils文件夹-->auth.py
# views.py
# 自定义获取token的返回值
def jwt_response_payload_handler(token, user=None, request=None):
return {
'status':status.HTTP_200_OK,
'user':user.username,
'token': token
}
# settings.py
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER':'api.utils.auth.jwt_response_payload_handler'
}
接口示例:
自定义校验token的返回数据格式:
自定义认证基类:
BaseJSONWebTokenAuthentication DRF-JWT章节使用的认证基类
BaseAuthentication DRF章节使用的认证基类
二者没什么大的区别,前者为后者的子类,在原有的基础上封装了用于处理token三段的方法。
本章节将对继承BaseJSONWebTokenAuthentication和BaseAuthentication,分别介绍如何完成自定义返回值。
代码推导:
自定义认证token,可以更加精细化的捕获异常,最后根据自定义认证类规则,再返回两个值。
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication,BaseAuthentication
#BaseJSONWebTokenAuthentication源码中导入了BaseAuthentication,所以都在jwt中。
from rest_framework_jwt.utils import jwt_decode_handler
from rest_framework.exceptions import AuthenticationFailed #可以抛出身份校验失败的异常
import jwt # 内有jwt校验的几种错误类型
class MyToken(BaseAuthentication):
def authenticate(self, request):
jwt_value=str(request.META.get('HTTP_AUTHORIZATION'))
if jwt_value:
try:
# 如果携带token那就尝试着校验,并拿到payload
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature: # 过期签名错误
raise AuthenticationFailed('签名过期')
except jwt.InvalidTokenError: # 无效令牌错误
raise AuthenticationFailed('无效用户')
except Exception as e: # 捕获其他的全部异常
raise AuthenticationFailed(str(e))
#print(payload)
#{'user_id': 1, 'username': 'liuyu', 'exp': 1676233125, 'email': '[email protected]'}
user = User(id=payload.get('user_id'),username=payload.get('username'))
'''
这样子可以节省去数据库查询的时间。
'''
return user,jwt_value
raise AuthenticationFailed('没有多携带认证信息')
补充:利用模型类创建用户数据对象
from api.models import User
print(type(User.objects.filter(pk=1).first())) #
print(type(User(id=1,username='liuyu',password='11223355'))) #
继承BaseJSONWebTokenAuthentication
上面的代码都是继承BaseAuthentication认证基类完成的,JWT子类中把payload —转–> userobj 这一步封装成了一个方法:authenticate_credentials
class MyToken(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
jwt_value=str(request.META.get('HTTP_AUTHORIZATION'))
# 认证
if jwt_value:
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
raise AuthenticationFailed('签名过期')
except jwt.InvalidTokenError:
raise AuthenticationFailed('无效用户')
except Exception as e:
raise AuthenticationFailed(str(e))
# 但核心也是User(id=payload.get('user_id'),username=payload.get('username'))
user = self.authenticate_credentials(payload)
return user,jwt_value
raise AuthenticationFailed('没有多携带认证信息')
自定义签发和检验token返回数据格式、自定义异常处理、自定义Response对象,这三者的区别:
本章节的自定义返回数据
: 用于返回符合规范的数据,以及做整一些常规异常捕获,针对签名过期、签名非法等情况来返回合适的数据。自定义异常处理
: 直接抛出异常,django能处理就处理,处理不了就不管了,来了个大大的HTML页面报错。但是django处理的不行,所以我们需要分情况都做统一的异常处理。自定义Response
: 接口返回status、msg等信息时会遇到代码冗余,所以我们可以封装Response,节省重复代码。签发token返回数据的一些问题:
现象:签发token,如果输入的username和password有错误,那么会直接抛出异常,如果这个时候没有进行自定义异常处理,那么返回的结果没有响应状态码,这就不符合规范了。
示例:签发token的异常处理
校验token:
在一些登陆场景中,我们可以发现有一些是输入身份证号+密码、手机号+密码、学号+密码等等,多种方式都可以登陆效果
步骤:
1、由于是自动签发,那么就需要自己去书写login登陆接口,不再使用obtain_jwt_token,那么在书写视图类的时候,由于登陆是post提交,但是post通常又用于create新增数据,所以这里可以利用ViewSet,将视图方法更改为更加合理的名称,而不知局限于get post 。
修改之后,访问docs页面:
2、用户提交过来的数据,会先被视图类接收,这个时候可以交给序列化类,通过钩子函数来完成校验,分析提交的username字段,到底是用户名、还是手机号、还是邮箱号,与对应的密码是否正确等。
这个过程中,视图类与序列化类之间可以传输数据,比如序列化类校验完毕之后,将当前的用户对象返回给视图类; 视图类中把request交给序列化类,然后在序列化类中进行各种逻辑书写,不只局限于视图中书写逻辑。
序列化对象的context属性,就是用来传输值的。
3、最后需要再重新覆盖username字段,不进行数据库的校验,因为它会以为我们是要新建保存数据,所以会校验,这个时候我们再去登陆,它就会报错,说数据已存在,因为数据是是unique唯一。
代码示例:
路由
urlpatterns = [
path('login/', views.LoginView.as_view({'post':'login'})),
]
视图类
from rest_framework.viewsets import ViewSet
class LoginView(ViewSet): #
'''
login:登陆接口
'''
def login(self, request, *args, **kwargs):
# 自定义序列化类中,传入数据,context属性赋值,可以实现传递到序列化类中使用的效果。
login_ser = ser.LoginModelSerializer(data=request.data,context={'request':request})
'''
序列化类处理
'''
# 调用is_validad判断处理结果
login_ser.is_valid(raise_exception=True)
token=login_ser.context.get('token')
# 4 return
return Response({'status':100,'msg':'登录成功','token':token,'username':login_ser.context.get('username')})
序列化类
import re
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.utils import jwt_encode_handler,jwt_payload_handler
from api import models #导入应用下面的模型
class LoginModelSerializer(serializers.ModelSerializer):
# 重新覆盖username字段,数据中它是unique,post过来它认为我们是要保存数据,这个时候会报数据已存在。
username=serializers.CharField()
class Meta:
model=models.User
fields=['username','password']
def validate(self, attrs):
# 业务逻辑
username=attrs.get('username')
password=attrs.get('password')
# 通过判断,username数据不同,查询字段不一样
# 正则匹配,如果是手机号
if re.match('^1[3-9][0-9]{9}$',username):
user=models.User.objects.filter(mobile=username).first()
# 如果是邮箱
elif re.match('^.+@.+$',username):# 邮箱
user=models.User.objects.filter(email=username).first()
# 如果是手机号
else:
user=models.User.objects.filter(username=username).first()
# 存在用户
if user:
# 校验密码,因为是密文,要用check_password
if user.check_password(password):
# 签发token
payload = jwt_payload_handler(user) # 把user传入,得到payload
token = jwt_encode_handler(payload) # 把payload传入,得到token
# 可以给序列化对象以属性添加的方式,将token返回给视图层
self.context['token']=token
self.context['username']=user.username
# 根据全局钩子函数,这里需要返回attrs
return attrs
else:
raise ValidationError('密码错误')
else:
raise ValidationError('用户不存在')
接口请求示例:
token过期时间:
import datetime
JWT_AUTH={
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 过期时间,示例中表示7天才过期。
}
作为补充知识点
RBAC:
内有:
user表,group表,permission表
- 在django的RBAC中,还有第六个表, user --> permission,例如:可以让用户tom,在不绑定部门的情况下,赋与其他权限。
应用场景:
2023 - 02 - 10
自己目前遇到的问题:
- 如何对中间表进行操作?
更多详情见官方文档:https://docs.djangoproject.com/zh-hans/4.1/topics/cache/
缓存介绍:
在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增、删、查、改、渲染模板、执行业务逻辑,最后生成用户看到的页面。
当一个网站的用户访问量很大的时候,每一次的的后台操作,都会消耗很多的服务端资源,所以必须使用缓存来减轻后端服务器的压力。
缓存是将一些常用的数据保存内存或者memcache等非关系型数据库中,在一定的时间内有人来访问这些数据时,则不再去执行数据库及渲染等操作,而是直接从内存或非关系型数据库的缓存中去取得数据,然后返回给用户。
memcache因为可以缓存图片、视频等资源,所以用的会比较多。
django中的几种缓存模式:
开发调试缓存
内存缓存
文件缓存
数据库缓存
Memcache缓存(使用python-memcached模块)
Memcache缓存(使用pylibmc模块)
经常使用的有文件缓存和Mencache缓存
django中的几种缓存的配置:
一、开发调试(此模式为开发调试使用,实际上不执行任何操作)
settings.py文件配置
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache', # 缓存后台使用的引擎
'TIMEOUT': 300, # 缓存超时时间(默认300秒,None表示永不过期,0表示立即过期)
'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
},
}
}
二、内存缓存(将缓存内容保存至内存区域中)
settings.py文件配置
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', # 指定缓存使用的引擎
'LOCATION': 'unique-snowflake', # 写在内存中的变量的唯一值
'TIMEOUT':300, # 缓存超时时间(默认为300秒,None表示永不过期)
'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
}
}
}
三、文件缓存(把缓存数据存储在文件中)
settings.py文件配置
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', #指定缓存使用的引擎
'LOCATION': '/var/tmp/django_cache', #指定缓存的路径
'TIMEOUT':300, #缓存超时时间(默认为300秒,None表示永不过期)
'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
}
}
}
四、数据库缓存(把缓存数据存储在数据库中)
settings.py文件配置
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache', # 指定缓存使用的引擎
'LOCATION': 'cache_table', # 数据库表
'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
}
}
}
注意,创建缓存的数据库表使用的语句:
python manage.py createcachetable
五、Memcache缓存(使用python-memcached模块连接memcache)
Memcached是Django原生支持的缓存系统.要使用Memcached,需要下载Memcached的支持库python-memcached或pylibmc.
settings.py文件配置
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', # 指定缓存使用的引擎
'LOCATION': '192.168.10.100:11211', # 指定Memcache缓存服务器的IP地址和端口
'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
}
}
}
LOCATION也可以配置成如下:
'LOCATION': 'unix:/tmp/memcached.sock', # 指定局域网内的主机名加socket套接字为Memcache缓存服务器
'LOCATION': [ # 指定一台或多台其他主机ip地址加端口为Memcache缓存服务器
'192.168.10.100:11211',
'192.168.10.101:11211',
'192.168.10.102:11211',
]
六、Memcache缓存(使用pylibmc模块连接memcache)
settings.py文件配置
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', # 指定缓存使用的引擎
'LOCATION':'192.168.10.100:11211', # 指定本机的11211端口为Memcache缓存服务器
'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
},
}
}
LOCATION也可以配置成如下:
'LOCATION': '/tmp/memcached.sock', # 指定某个路径为缓存目录
'LOCATION': [ # 分布式缓存,在多台服务器上运行Memcached进程,程序会把多台服务器当作一个单独的缓存,而不会在每台服务器上复制缓存值
'192.168.10.100:11211',
'192.168.10.101:11211',
'192.168.10.102:11211',
]
Memcached是基于内存的缓存,数据存储在内存中.所以如果服务器死机的话,数据就会丢失,所以Memcached一般与其他缓存配合使用
前后端混合开发缓存的使用:
全站缓存
;还是单页面缓存
;还是页面局部缓存
**。全站缓存
由于请求过来和回去,都需要对缓存进行存储、获取、更新等操作,所以配置中间件即可,这里不需要自定义,直接用内置的。
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
...其他中间件略...
'django.middleware.cache.FetchFromCacheMiddleware',
]
CACHE_MIDDLEWARE_SECONDS=10 # 全站缓存时间,单位为秒。
注意前后顺序不要倒乱。
单页面缓存
由于是全栈开发,视图类直接返回页面,所以可以给视图类添加装饰器,装饰器内部针对页面进行缓存,这个也不需要自定义,内置写好了。
from django.views.decorators.cache import cache_page
@cache_page(5) # 缓存时间,单位:秒。
def test_cache(request):
import time
ctime = time.time()
# 刷新页面,查看5秒内时间是否还会有所变化,如果没有那就说明数据是从缓存中取的。
return render(request, 'index.html', context={'ctime': ctime})
页面局部缓存
模板语法过滤器即可
{ % load cache %}
{ % cache 5 'name' %} # 5表示5s钟,name是用来存储缓存的唯一key值
{{ctime}}
{ % endcache %}
由于是前后端分离,django后端在接收到请求后,先去缓存中进行查询,如果有直接返回,如果没有,就再去连表查询,查询数据拿到之后,返回的同时缓存数据。
所以,前后端分离的项目,如果想要使用缓存,那么获取缓存和添加缓存就需要我们掌握使用。
cache.set 添加缓存、cache.get 获取缓存。
from django.core.cache import cache
cache.set('key',value,超时时间) # value可以是任意数据类型。超时时间单位:秒
cache.get('key','未查询到返回的默认值')
更多用法见官方文档:https://docs.djangoproject.com/zh-hans/4.1/topics/cache/
个人是觉得,set方法的value可以是任意数据类型,那么就可以是对象,有了对象就可以实现很多效果,那么cache的其他方法就显得不那么重要,而且set方法可以设置超时时间,比手动用删除命令要好不少。