drf入门到精通
drf入门到精通
1.Web应用模式
在开发Web应用中,有两种应用模式:
-
前后端不分离
-
前后端分离
2.API接口
通过网络,规定了前后台信息交互规则的url链接,也就是前后台信息交互的媒介
Web API接口和一般的url链接还是有区别的,Web API接口简单概括有下面四大特点
-
url:长得像返回数据的url链接
-
请求方式:get、post、put、patch、delete
- 采用get方式请求上方接口
-
请求参数:json或xml格式的key-value类型数据
- ak:6E823f587c95f0148c19993539b99295
- region:上海
- query:肯德基
- output:json
-
响应结果:json或xml格式的数据
- 上方请求参数的output参数值决定了响应数据的格式
- 数据
# xml格式 https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295®ion=%E4%B8%8A%E6%B5%B7&query=%E8%82%AF%E5%BE%B7%E5%9F%BA&output=xml # json格式 https://api.map.baidu.com/place/v2/search?ak=6E823f587c95f0148c19993539b99295®ion=%E4%B8%8A%E6%B5%B7&query=%E8%82%AF%E5%BE%B7%E5%9F%BA&output=json { "status":0, "message":"ok", "results":[ { "name":"肯德基(罗餐厅)", "location":{ "lat":31.415354, "lng":121.357339 }, "address":"月罗路2380号", "province":"上海市", "city":"上海市", "area":"宝山区", "street_id":"339ed41ae1d6dc320a5cb37c", "telephone":"(021)56761006", "detail":1, "uid":"339ed41ae1d6dc320a5cb37c" } ... ] }
3.接口测试工具:Postman
Postman是一款接口调试工具,是一款免费的可视化软件,同时支持各种操作系统平台,是测试接口的首选工具。还有postwoman、apifox
Postman可以直接从官网:https://www.getpostman.com/downloads/下载获得,然后进行傻瓜式安装。
- 工作面板
- 简易的get请求
- 简易的post请求
- 案例:请求百度地图接口
# get请求,没有请求体,post等请求有
# post请求,有多种编码格式
-urlencoded 格式 --->默认格式 b'xx=xx&yy=yy'
-form-data 格式 --->传文件格式,也可以带数据,选form-data有下拉框选file
----------------------251971281264627087498320-- 带数据,带文件
-json格式(用的最多) --->只能传数据,选raw,然后下拉框json b'{"name":"lqz","age":18}'
# 如果是json形式提交数据 ---> django中想取,从request.POST取出到的为空,只能从request.body中取出,取出来的是二进制的字节类型数据,需要json.loads反序列化。
# Django 的内置解析器可以处理 “multipart/form-data” 格式的请求体。但是,如果请求体中包含的数据格式不正确或者数据非常大,可能会导致解析出错,从而引起 request.body 报错。
# day1 作业
'''
# postman装好
# 写三个接口
-接收urlencoded格式的请求,post接口,把接收到的数据,后端打印
-接口form-data,把文件保存到本地,数据打印出来
-接收json形式,把数据打印出来
-json.loads(request.body)
'''
# 路由
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^drf01/$', views.drf01),
url(r'^drf02/$', views.drf02),
url(r'^drf03/$', views.drf03),
]
# 视图
import os
from django.http import JsonResponse
def drf01(request):
name = request.POST.get('name')
print(name)
return JsonResponse('drf01', safe=False)
def drf02(request):
file = request.FILES.get('cpdd')
print(file, type(file), file.name)
# type:<class 'django.core.files.uploadedfile.InMemoryUploadedFile'>
"""
class InMemoryUploadedFile(UploadedFile):
def __init__(self, file, field_name, name, content_type, size, charset, content_type_extra=None):
super().__init__(file, name, content_type, size, charset, content_type_extra)
self.field_name = field_name
"""
if file is not None:
# 打开一个文件进行写入操作
file_path = os.path.join('static',file.name)
with open(file_path, 'wb+') as f:
# 将文件分块读取并写入
# for chunk in file.chunks():
# f.write(chunk)
f.writelines(file)
# 返回上传成功的信息
return JsonResponse('File has been uploaded.', safe=False)
else:
# 返回文件上传失败的信息
return JsonResponse('No file was selected.', safe=False)
'''
for chunk in file.chunks() 是在 Django 中使用的方法,它可以将文件分段读取,而不是将整个文件加载到内存中。
这对于上传和处理大型文件非常有用,因为它可以减少内存的使用。
for line in writelines() 是 Python 的内置方法。它用于将一个可迭代的序列写入文件中。
'''
def drf03(request):
name = json.loads(request.body)
print(name)
return JsonResponse(name)
4.Restful规范
# RESTful是一种定义API接口的设计风格,AIP接口的编写规范,尤其适用于前后端分离的应用模式中
这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源
我们可以使用任何一个框架都可以实现符合restful规范的API接口
# 10条规范
1 数据的安全保障,通常使用https协议进行传输
2 url地址中带接口标识:一般这样
- https://api.baidu.com
- https://www.baidu.com/api
3 多版本共存,url地址中带版本信息
- https://api.baidu.com/v1/login/
- https://api.baidu.com/v2/login/
4 数据即是资源,均使用名词:
url地址尽量使用名词
# 接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
- https://api.baidu.com/users
- https://api.baidu.com/books
- https://api.baidu.com/book
- 注:一般提倡用资源的复数形式,在url链接中不要出现操作资源的动词,错误示范:https://api.baidu.com/delete-user
# 特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义
- https://api.baidu.com/place/search
- https://api.baidu.com/login
5 资源操作由请求方式决定
#操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作
- 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的书
6 url地址中带过滤条件 ?后带过滤条件
- https://api.baidu.com/books - get请求表示查询所有图书
- https://api.baidu.com/books?name_contains=红:要查名字中有红的图书
- 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:指定筛选条件
7 响应状态码(http响应中带状态码)
# https://www.sohu.com/a/278045231_120014184 趣味图解HTTP状态码的含义
# http常见的响应状态码:https://blog.csdn.net/meng2lin/article/details/128955775
-1xx:请求正在处理
-2xx:请求成功 200 201
-3xx:重定向
-4xx:客户端错误
-5xx:服务的错误
- http的响应的数据中带状态码(公司自己规定的)
- {code:100}
8 返回的数据中带错误信息
- {code:101,msg:用户名或密码错误}
- {code:100,msg:成功}
9 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范
GET /books:返回资源对象的列表(数组)
-[{name:金品梅梅,price:88},{name:西游记,price:88}]
-{code:100,msg:成功,data:[{name:金品梅梅,price:88},{name:西游记,price:88}]}
GET /books/1:返回单个资源对象
-{name:金品梅梅,price:88}
-{code:100,msg:成功,data:{name:金品梅梅,price:88}}
POST /books:返回新生成的资源对象
-{id:4,name:金品梅梅,price:88}
-{code:100,msg:成功}
PUT /books/4:返回完整的资源对象
-{id:4,name:金品梅梅,price:188}
-{code:100,msg:修改成功}
DELETE /books/4: 返回一个空文档
-{code:100,msg:删除成功}
10 返回的结果中带url链接
5.序列化和反序列化
# API接口开发,最核心最常见的一个过程就是序列化,所谓序列化就是把数据转换格式,可以分两个阶段
# 序列化: 把我们识别的数据转换成指定的格式提供给别人。json.dumps
例如:我们在django中获取到的数据默认是模型对象(queryset),但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人。
queryset = models.Book.objects.all()
# 反序列化:把别人提供的数据转换/还原成我们需要的格式。 json.loads
例如:前端js提供过来的json数据,对于python而言就是字符串,我们需要进行反序列化换成模型类对象,这样我们才能把数据保存到数据库中
request.data
# 序列化:drf称为 read
# 反序列化:drf称为 write
6.drf介绍和快速使用
# book表为例,写5个接口(后面你写的所有接口,都是这5个,及其变形)
-查询所有图书
-新增一本图书
-修改一本图书
-查询一本图书
-删除一本图书
from django.urls import path
from app01.views import BookView,BookDetailView
urlpatterns = [
# 查询所有书籍
path('books/', BookView.as_view()),
# 查询某本书籍,使用转换器
path('books/<int:pk>/', BookDetailView.as_view()),
]
class BookView(View):
# 查询所有书籍
def get(self, request):
book_lst = Book.objects.all()
books = []
for book in book_lst:
books.append({'name': book.name, 'price': book.price})
return JsonResponse({'status': '200', 'msg': 'success!', 'result': books})
# 新增某本书籍
def post(self, request):
# 默认urlencoded格式和form_data格式
# name = request.POST.get('name')
# price = request.POST.get('price')
# Book.objects.create(name=name, price=price)
# return JsonResponse({'status': '200', 'msg': 'success!'})
# json格式
book_dic = json.loads(request.body)
Book.objects.create(name=book_dic['name'], price=book_dic['price'])
return JsonResponse({'status': '200', 'msg': 'success!', 'result': request.data})
class BookDetailView(View):
# 查询某本书籍
def get(self, request, pk):
book_obj = Book.objects.filter(pk=pk).first()
book = {'name': book_obj.name, 'price': book_obj.price}
return JsonResponse({'status': '200', 'msg': 'success!', 'result': book})
# 修改某本书籍
def put(self, request, pk):
# json格式
book_dic = json.loads(request.body)
book_obj = Book.objects.filter(pk=pk).update(name=book_dic['name'], price=book_dic['price'])
return JsonResponse({'status': '200', 'msg': 'success!', 'result': book_obj}) # 影响的行数
# 删除某本书
def delete(self, request, pk):
book_obj = Book.objects.filter(pk=pk).delete()
return JsonResponse({'status': '200', 'msg': 'success!', 'result': book_obj})
6.1 drf介绍
# django 中有个app:djangorestframework。drf帮助我们快速实现符合resful规范的接口
# 下载:(有个坑)
pip3.9 install djangorestframework==3.2.12
# 如果你是django2, 直接这样装,装最新drf,他们不匹配---》pip会自动把django卸载,安装最新django,安装最新drf
# django3 ,这样没有任何问题
pip3.8 install djangorestframework --upgrade 强制更新
# 补充一点点东西:
- 如果写了一个包或app,想给别人用-->把你写的包,放到pypi上别人pip install 安装--->使用
6.2 drf快速使用(不需要会)
路由
from app01.views import BookView
# 导入一个类
from rest_framework.routers import SimpleRouter
# 类实例化一个对象
router = SimpleRouter()
# 注册
router.register('books', BookView, 'books')
urlpatterns = [
]
# 添加进路由
urlpatterns += router.urls
视图
from .serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet # 包括5个扩展类GenericViewSet
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
序列化类 serializer.py
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
7.drf之APIView源码分析
7.1 基于APIView的5个接口
视图类
from rest_framework.views import APIView # APIView的父类是View
from .serializer import BookSerializer # drf提供了序列化类
from rest_framework.response import Response
class BookView(APIView):
# 查询所有书籍
def get(self, request):
book_lst = Book.objects.all()
ser = BookSerializer(instance=book_lst,many=True) # 序列化
return Response({'status': '200', 'msg': 'success!', 'result': ser.data})
# 新增某本书籍
def post(self, request):
ser = BookSerializer(data=request.data) # 反序列化
if ser.is_valid(): # 数据校验,不合法的数据会禁止
ser.save() # 保存到数据库中
return Response({'status': '200', 'msg': 'success!'})
class BookDetailView(APIView):
# 查询某本书籍
def get(self, request, pk):
book_obj = Book.objects.filter(pk=pk).first()
ser = BookSerializer(instance=book_obj, many=False) # 序列化
return Response({'status': '200', 'msg': 'success!', 'result': ser.data})
# 修改某本书籍
def put(self, request, pk):
book_obj = Book.objects.filter(pk=pk)
ser = BookSerializer(instance=book_obj,data=request.data) # 反序列化
if ser.is_valid(): # 数据校验,不合法的数据会禁止
ser.save() # 保存到数据库中
return Response({'status': '200', 'msg': 'success!'})
# 删除某本书
def delete(self, request, pk):
book_obj = Book.objects.filter(pk=pk).delete()
return Response({'status': '200', 'msg': 'success!', 'result': book_obj})
序列化类
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
路由
urlpatterns = [
path('books/', BookView.as_view()),
path('books/<int:pk>/', BookDetailView.as_view()),
]
7.2 CBV源码分析
# cbv写法:
1 视图中写视图类,继承View,写跟请求方式同名的方法
class BookView(View):
def get(self,request):
return 四件套
2 在路径用写
path('books/', BookView.as_view())
# 如上写法,为什么能够执行
# 前置条件:前端请求,一旦路径匹配成功,就会执行 BookView.as_view()(request传入,)
# 入口在 BookView.as_view()--->执行结果---》View中有个as_view类的绑定方法
@classmethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs)
res=self.dispatch(request, *args, **kwargs)
return res
return view
# 执行结果是view的内存地址: 请求来了,执行view(request)
path('books/', view)
# 执行 View类中的as_view方法中的内层的view函数,路由匹配成功,本质是在执行
self.dispatch(request, *args, **kwargs)
# self是谁的对象?BookView的对象
# 去BookView中dispatch,找不到,去父类,View中找到了
# View这个类的dispatch
def dispatch(self, request, *args, **kwargs):
# request.method.lower() 如果是get请求,‘get’ 在这个列表里面
if request.method.lower() in self.http_method_names:
# handler=getattr(BookView的对象,'get')
# handler就是BookView类中的get方法
handler = getattr(self, request.method.lower())
else:
handler = self.http_method_not_allowed
# 执行 BookView类中的get方法 (request)
return handler(request, *args, **kwargs)
# 最终本质跟写fbv的执行流程一样
# 最终结论:什么请求方式,就会执行视图类中的什么方法
7.3 APIView执行流程分析
# 有了drf,后期都写CBV,都是继承APIView及其子类
# 执行流程:
-入口:path('books/', BookView.as_view())---》请求来了,执行BookView.as_view()(request)
-as_view 是谁的? APIView的as_view
@classmethod
def as_view(cls, **initkwargs):
# super()代指的是:父类对象 View类的对象
# View的as_view(**initkwargs)----》执行结果是view,是View类的as_view方法中的view
view = super().as_view(**initkwargs)
view=csrf_exempt(view) # 局部禁用csrf,
return view
-path('books/', View类的as_view中的view,只是去掉了csrf的认证)
# -请求来了,执行 【View类的as_view中的view,只是去掉了csrf的认证(request)】
-执行:self.dispatch(request, *args, **kwargs), self要从根上找
-self.dispatch 是APIView的dispatch,源码如下
def dispatch(self, request, *args, **kwargs):
# request新的(rest_framework.request.Request), request老的(django.core.handlers.wsgi.WSGIRequest)
request = self.initialize_request(request, *args, **kwargs)
self.request = request
try:
# 执行了认证,权限和频率
self.initial(request, *args, **kwargs)
# 在执行视图类方法之前,去掉了csrf认证,包装了新的request,执行了认证频率和权限
# 执行请求方式字符串对应的方法
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)
# 无论是在三大认证,还是视图类的方法中,出现错误,都会被异常捕获,统一处理
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
# 总结:
1 以后只要继承APIView的所有视图类的方法,都没有csrf的校验了
2 以后只要继承APIView的所有视图类的方法中的request是新的request了
3 在执行视图类的方法之前,执行了三大认证(认证,权限,频率)
4 期间出了各种错误,都会被异常捕获,统一处理
# 补充(装饰器语法糖):
fbv,局部禁用csrf,如何写?
@csrf_exempt
def index(request):
pass
原理是(装饰器本质):index=csrf_exempt(index)
# 作业:写一个装饰器,装饰在fbv上,这个fbv可以接受前端的编码格式可以是urlencoded,form-data,json,取数据,都是从request.data中取
# 装饰器
def load_json(func):
def inner(request,*args,**kwargs):
try:
request.data = json.loads(request.body) # 如果是json格式就成功,如果是其它格式直接报错
except Exception as e:
request.data = request.POST
res = func(request,*args,**kwargs)
return res
return inner
@load_json
def testbook(request):
# 默认urlencoded格式和form_data格式
# name = request.POST.get('name')
# price = request.POST.get('price')
# Book.objects.create(name=name, price=price)
# return JsonResponse({'status': '200', 'msg': 'success!'})
# json格式
# book_dic = json.loads(request.body)
# Book.objects.create(name=book_dic['name'], price=book_dic['price'])
print(request.data)
# Book.objects.create(name=request.data['name'],price=request.data['price'])
# Book.objects.create(**request.data)
return JsonResponse({'status': '200', 'msg': 'success!', 'result': request.data})
8 Request类的源码分析
# APIView+Response写个接口
def __getattr__(self, attr):
"""
If an attribute does not exist on this instance, then we also attempt
to proxy it to the underlying HttpRequest object.
"""
try:
return getattr(self._request, attr)
# attr是一个字符串,如果你拿method,那么attr='method'
except AttributeError:
return self.__getattribute__(attr)
@property
def query_params(self):
"""
More semantically correct name for request.GET.
"""
return self._request.GET
# 总结:
1 新的request有个data方法包装成属性,以后只要是在请求body体中的数据,无论什么编码格式,无论什么请求方式
2 取文件还是从:request.FILES
3 取其他属性,跟之前完全一样 request.method ....
-from rest_framework.request import Request
-原理是:新的Request重写了__getattr__,通过反射获取老的request中的属性
4 request.GET 现在可以使用 request.query_params
@property # 将方法包装成属性
def query_params(self):
return self._request.GET
# 源码中找出来
-老的request在新的request._request
-照常理来讲,如果取method,应该request._request.method,但是我现在可以request.method
# 魔法方法之 __getattr__, . 拦截(点拦截),对象.属性 当属性不存在时,会触发类中 __getattr__的执行
# get请求能不能在body体中带数据
-之前不允许带,前段时间研究出的结果是能。
9.序列化组件介绍
1. 序列化,序列化器会把模型对象(queryset,单个对象)转换成字典,经过Response以后变成json字符串
2. 反序列化,把客户端发送过来的数据,经过request.data以后变成字典,序列化器得到ser,ser.save()保存到数据库,可以把字典转成模型
3. 反序列化,完成数据校验功能,ser.is_valid() ---->三层:字段自己的,局部钩子,全局钩子
10 序列化类的基本使用
# 1 创建book表模型
# 2 写查询所有图书的接口:APIView+序列化类+Response
# 面试题:BigIntegerField跟IntegerField有什么区别?范围不一样
10.1 查询所有和查询单条
views.py
class BookView(APIView):
def get(self, request):
book_list = Book.objects.all()
# 使用序列化类,完成序列化 两个很重要参数: instance实例,对象 data:数据
# 如果是多条many=True 如果是queryset对象,就要写
# 如果是单个对象 many=False,默认是False,可以不写
serializer = BookSerializer(instance=book_list, many=True)
# serializer.data # 把qs对象,转成列表套字典 ReturnList
# print(serializer.data)
# print(type(serializer.data)) <class 'rest_framework.utils.serializer_helpers.ReturnList'>
# return Response(serializer.data)
return Response({'code': 100, 'msg': '成功', 'data': serializer.data})
class BookDetailView(APIView):
def get(self, request, pk):
book = Book.objects.all().get(pk=pk)
serializer = BookSerializer(instance=book)
return Response({'code': 100, 'msg': '成功', 'data': serializer.data})
urls.py
urlpatterns = [
path('books/', views.BookView.as_view()),
path('books/<int:pk>/', views.BookDetailView.as_view()),
]
序列化类
from rest_framework import serializers
class BookSerializer(serializers.Serializer):
# 要序列化的字段
# id = serializers.IntegerField()
name = serializers.CharField()
# price = serializers.IntegerField()
总结
# 序列化类的使用
1 写一个类,继承serializers.Serializer
2 在类中写字段,要序列化的字段
3 在视图类中使用:(多条,单条)
serializer = BookSerializer(instance=book_list, many=True)
serializer = BookSerializer(instance=book)
10.2 常用字段类和参数(了解)
- 常用字段类
字段 | 字段构造方式 |
---|---|
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=) |
# 1.常用字段类
IntegerField CharField DateTimeField DecimalField
ListField和DictField---》比较重要,但是后面以案例形式讲
- 字段参数(校验数据来用的)
选项参数:(CharField,IntegerField)
参数名称 | 作用 |
---|---|
max_length | 最大长度 CharField |
min_lenght | 最小长度 CharField |
allow_blank | 是否允许为空 CharField |
trim_whitespace | 是否截断空白字符 |
max_value | 最小值 IntegerField |
min_value | 最大值 IntegerField |
通用参数:
参数名称 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 表明该字段是否允许传入None,默认False |
# 2.字段参数(校验数据来用)
read_only write_only 很重要,后面以案例讲
11 反序列化之校验
# 反序列化,有三层校验
-1 字段自己的(写的字段参数:required max_length 。。。)
-2 局部钩子:写在序列化类中的方法,方法名必须是 validate_字段名
def validate_name(self, name):
if 'sb' in name:
# 不合法,抛异常
raise ValidationError('书名中不能包含sb')
else:
return name
-3 全局钩子:写在序列化类中的方法 方法名必须是 validate
def validate(self, attrs):
price = attrs.get('price')
name = attrs.get('name')
if name == price:
raise ValidationError('价格不能等于书名')
else:
return attrs
# 只有三层都通过,在视图类中:
ser.is_valid(): 才是True,才能保存
12 反序列化之保存
# 新增接口:
-序列化类的对象,实例化的时候:ser = BookSerializer(data=request.data)
-数据校验过后----》调用 序列化类.save()--->但是要在序列化类中重写 create方法
def create(self, validated_data):
book=Book.objects.create(**validated_data)
return book
# 修改接口
-序列化类的对象,实例化的时候:ser = BookSerializer(instance=book,data=request.data)
-数据校验过后----》调用 序列化类.save()--->但是要在序列化类中重写 update方法
def update(self, book, validated_data):
for item in validated_data: # {"name":"jinping","price":55}
setattr(book, item, validated_data[item])
book.save()
return book
# 研究了一个问题
在视图类中,无论是保存还是修改,都是调用序列化类.save(),底层实现是根据是否有instance做一个判断
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, ('`update()` did not return an object instance.')
else:
self.instance = self.create(validated_data)
assert self.instance is not None, ('`create()` did not return an object instance.')
return self.instance
13 5个接口代码
路由
urlpatterns = [
path('books/', views.BookView.as_view()),
path('books/<int:pk>/', views.BookDetailView.as_view()),
]
视图
from .models import Book
from .serializer import BookSerializer
class BookView(APIView):
def get(self, request):
book_list = Book.objects.all()
# 使用序列化类,完成序列化 两个很重要参数: instance实例,对象 data:数据
# 如果是多条many=True 如果是queryset对象,就要写
# 如果是单个对象 many=False,默认是False
serializer = BookSerializer(instance=book_list, many=True)
# serializer.data # 把qs对象,转成列表套字典 ReturnList
# print(serializer.data)
# print(type(serializer.data))
# return Response(serializer.data)
return Response({'code': 100, 'msg': '成功', 'data': serializer.data})
# 新增
def post(self, request):
# 前端会传入数据,request.data--->把这个数据保存到数据库中
# 借助于序列化类,完成 校验和反序列化
# data 前端传入的数据 {"name":"三国演义","price":88}
ser = BookSerializer(data=request.data)
# 校验数据
if ser.is_valid(): # 三层:字段自己的校验,局部钩子校验,全局钩子校验
# 校验通过,保存
print(ser.validated_data) # validated_data:校验过后的数据
# 如果没有save,如何保存,自己做
# Book.objects.create(**ser.validated_data)
ser.save() # 会保存,但是会报错,因为它不知道你要保存到那个表中
return Response({'code': 100, 'msg': '新增成功'})
else:
print(ser.errors) # 校验失败的错误
return Response({'code': 101, 'msg': '新增失败', 'errors': ser.errors})
class BookDetailView(APIView):
def get(self, request, pk):
book = Book.objects.all().get(pk=pk)
serializer = BookSerializer(instance=book)
return Response({'code': 100, 'msg': '成功', 'data': serializer.data})
def put(self, request, pk):
book = Book.objects.get(pk=pk)
ser = BookSerializer(instance=book, data=request.data)
if ser.is_valid():
ser.save() # 也会报错,重写update
return Response({'code': 100, 'msg': '修改成功'})
else:
return Response({'code': 101, 'msg': '修改失败', 'errors': ser.errors})
def delete(self,request,pk):
press_lst = Presses.objects.filter(pk=pk).delete()
return Response({'code': 100, 'msg': '删除成功'})
序列化类
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from .models import Book
class BookSerializer(serializers.Serializer):
# 要序列化的字段
id = serializers.IntegerField(required=False) # 前端传入数据,可以不填这个字段
name = serializers.CharField(allow_blank=True, required=False, max_length=8,
min_length=3, error_messages={'max_length': '太长了'}) # allow_blank: 这个字段传了,value值可以为空
price = serializers.IntegerField(max_value=100, min_value=10, error_messages={'max_value': '必须小于100'})
# price = serializers.CharField()
# 局部钩子:给某个字段做个校验
# 书名中不能包含sb
# validate_字段名
def validate_name(self, name):
if 'sb' in name:
# 不合法,抛异常
raise ValidationError('书名中不能包含sb')
else:
return name
def validate_price(self, item):
if item == 88:
raise ValidationError('价格不能等于88')
else:
return item
# 全局钩子
# 价格和书名不能一样 validate
def validate(self, attrs):
price = attrs.get('price')
name = attrs.get('name')
if name == price:
raise ValidationError('价格不能等于书名')
else:
return attrs
def create(self, validated_data):
# validated_data校验过后的数据,字典
book = Book.objects.create(**validated_data)
return book
# def update(self, book, validated_data):
# # instance 要修改的对象
# # validated_data:前端传入,并且校验过后的数据
# book.name = validated_data.get('name')
# book.price = validated_data.get('price')
# # 一定不要忘了
# book.save()
# return book
def update(self, book, validated_data):
for item in validated_data: # {"name":"jinping","price":55}
setattr(book, item, validated_data[item])
# 等同于下面
# setattr(book,'name','jinping')
# setattr(book,'price',55)
# 等同于
# book.name = validated_data.get('name')
# book.price = validated_data.get('price')
book.save()
return book
表模型
from django.db import models
# Create your models here.
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.BigIntegerField()
# 面试题:BigIntegerField跟IntegerField有什么区别
14 小补充:数据库三大范式
# 第一范式(1NF):确保每个数据列都是原子性的,也就是确保每个数据列都是不可分割的基本数据项,不含重复的元组。
-数据表中的每个字段的值必须具有原子性,是不可再拆分的最小数据单元
-属性的原子性是主观的,我们要根据实际项目的需求来设计,比如说地址,如果项目没有说要细分为省,市,县,镇这么具体的话,我们一般就可以不拆分。
# 第二范式(2NF):在满足第一范式的前提下,确保非主键字段都完全依赖于主键,也就是每个非主键字段都要与主键字段有关联。
# 第三范式(3NF):在满足第二范式的前提下,确保非主键字段之间不存在传递依赖关系,即不存在非主键字段决定其他非主键字段的情况。
'''
数据的标准化有助于消除数据库中的数据冗余,但是降低了查询效率,而且可能会使得一些索引失效
'''
15 序列化高级用法之source(了解)
# 1 创建了5个表(图书管理的5个)
on_delete:
CASCADE:级联删除,只要删除publish,跟publish关联的book,全都被删除
SET_DEFAULT:只要删除publish,跟publish关联的book,的publish字段会变成默认值,一定要配合default使用
SET_NULL:只要删除publish,跟publish关联的book,的publish字段会变成空,一定要配合null=True使用
models.SET(add):括号中可以放个值,也可以放个函数内存地址,只要删除publish,跟publish关联的book,的publish字段会变成set设的值或执行函数
models.DO_NOTHING:什么都不做,但它需要跟db_constraint=False配合,表示不建立外键约束,创建逻辑外键,不是物理外键
# 不建立物理外键的好处?增删查改数据快
# 缺点:容易出现脏数据
# 实际工作中,都不建立物理外键,都是建立逻辑外键
# 2 对booke进行序列化
# 总结:source的用法
-1 修改前端看到的字段key值---》source指定的必须是对象的属性
book_name = serializers.CharField(source='name')
# 有个坑,name = serializers.CharField(source='name')会报异常,source='name'字段与name字段相同,需要移除
# 同一个字段可以序列化多次,但一般不会写
-2 修改前端看到的value值,---》source指定的必须是对象的方法
表模型中写方法
def sb_name(self):
return self.name + '_sb'
序列化类中
book_name = serializers.CharField(source='sb_name')
-3 可以关联查询(得有关联关系)
publish_name = serializers.CharField(source='publish.name')
# publish是book表里的的外键字段,对应Publish表
16 序列化高级用法之定制字段的两种方式(非常重要)
# 方式一:在序列化类中写
1 写一个字段,对应的字段类是:SerializerMethodField
2 必须对应一个get_字段名的方法,方法必须接受一个obj序列化类对象,返回什么,这个字段对应的value就是什么
# 方式二:在表模型中写
1 在表模型中写一个方法(可以使用:property修饰就相当于类属性,),方法有返回值(字典,字符串,列表)
2 在序列化类中,使用DictField,CharField,ListField
16.1 序列化类中写
### 16.1 定制字段方式1
class BookSerialzier(serializers.Serializer):
name = serializers.CharField()
price = serializers.CharField()
# 拿出出版社的id和名字和addr,放到一个字典中
# 方式一:SerializerMethodField来定制,如果写了这个,必须配合一个方法get_字段名,这个方法返回什么,这个字段的值就是什么
publish_detail = serializers.SerializerMethodField()
def get_publish_detail(self, book):
# print(obj) # 要序列化的book对象
return {'id': book.publish.pk, 'name': book.publish.name, 'addr': book.publish.addr}
# 练习:拿出所有作者的信息--》多条 [{name:,phone:},{}]
author_list = serializers.SerializerMethodField()
def get_author_list(self, book):
l = []
for author in book.authors.all():
l.append({'id': author.pk, 'name': author.name, 'phone': author.phone, 'age': author.author_detail.age})
return l
16.2 表模型中写
### 16.2 定制字段方式2
class BookSerialzier(serializers.Serializer):
name = serializers.CharField()
price = serializers.CharField()
# 1 序列化类中这样写
# 2 到表模型中写一个方法,方法名必须叫 publish_detail,这个方法返回什么,这个字段的value就是什么
publish_detail = serializers.DictField()
author_list = serializers.ListField()
###### ###### ###### ###### ###### 表模型###### ###### ###### ######
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.CharField(max_length=32)
publish = models.ForeignKey(to='Publish', on_delete=models.SET_NULL, null=True)
authors = models.ManyToManyField(to='Author')
def sb_name(self):
return self.name + '_sb'
@property
def publish_detail(self):
return {'id': self.publish.pk, 'name': self.publish.name, 'addr': self.publish.addr}
def author_list(self):
l = []
for author in self.authors.all():
l.append({'id': author.pk, 'name': author.name, 'phone': author.phone, 'age': author.author_detail.age})
return l
17 多表关联反序列化保存
# 序列化和反序列化,用的同一个序列化类
-序列化的字段:name,price , publish_detail,author_list
-反序列化字段:name,price , publish,author
17.1 反序列化之保存
视图类
class BookView(APIView):
# 新增一条
def post(self, request):
ser = BookSerialzier(data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
序列化类
class BookSerialzier(serializers.Serializer):
# 即用来做序列化,又用来做反序列化
name = serializers.CharField(max_length=8)
price = serializers.CharField()
# 这俩,只用来做序列化
publish_detail = serializers.DictField(read_only=True)
author_list = serializers.ListField(read_only=True)
# 这俩,只用来做反序列化
publish_id = serializers.IntegerField(write_only=True)
authors = serializers.ListField(write_only=True)
def create(self, validated_data): # {name:西游记,price:88,publish:1,authors:[1,2]
authors = validated_data.pop('authors')
book = Book.objects.create(**validated_data)
book.authors.add(*authors)
return book
17.2 反序列化之修改
视图类
class BookDetailView(APIView):
def put(self, request,pk):
# 修改一条
book=Book.objects.get(pk=pk)
ser = BookSerialzier(data=request.data,instance=book)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '更新成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
序列化类
class BookSerialzier(serializers.Serializer):
# 即用来做序列化,又用来做反序列化
name = serializers.CharField(max_length=8)
price = serializers.CharField()
# 这俩,只用来做序列化
publish_detail = serializers.DictField(read_only=True)
author_list = serializers.ListField(read_only=True)
# 这俩,只用来做反序列化
publish_id = serializers.IntegerField(write_only=True)
authors = serializers.ListField(write_only=True)
def update(self, instance, validated_data):
authors = validated_data.pop('authors')
for item in validated_data:
setattr(instance, item, validated_data[item])
instance.authors.set(authors)
instance.save()
return instance
18 反序列化字段校验其他
# 视图类中调用:ser.is_valid()---》触发数据的校验
-4层
-字段自己的:max_length,required。。。
-字段自己的:配合一个函数name = serializers.CharField(max_length=8,validators=[xxx,vvv])
-局部钩子
-全局钩子
19 ModelSerializer使用
# 之前写的序列化类,继承了Serializer,写字段,跟表模型没有必然联系
class XXSerialzier(Serializer)
id=serializer.CharField()
name=serializer.CharField()
XXSerialzier既能序列化Book,又能序列化Publish
# 现在学的ModelSerializer,表示跟表模型一一对应,用法跟之前基本类似
1 写序列化类,继承ModelSerializer
2 在序列化类中,再写一个类,必须叫
class Meta:
model=表模型
fields=[] # 要序列化的字段
3 可以重写字段,一定不要放在class Meta
-定制字段,跟之前讲的一样
4 自定制的字段,一定要在fields中注册一下
5 class Meta: 有个extra_kwargs,为某个字段定制字段参数
6 局部钩子,全局钩子,完全一致
7 大部分请情况下,不需要重写 create和update了
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'name', 'price', 'authors', 'publish','publish_detail','author_list']
extra_kwargs = {
'publish': {'write_only': True},
'authors': {'write_only': True},
}
# 序列化只读,首要把参数写在这里
publish_detail = serializers.DictField(read_only=True)
author_list = serializers.ListField(read_only=True)
# 反序列化只写,在上面写了就不用在下面写
# publish = serializers.CharField()
# authors = serializers.ListField()
# 作业:author的5个接口,-新增或修改author时,把author_detail的内容也新增进去
# 序列化类
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ['id', 'name', 'phone', 'email', 'age']
email = serializers.CharField(source='author_detail.email')
age = serializers.IntegerField(max_value=200, source='author_detail.age')
def create(self, validated_data):
author_detail = validated_data.pop('author_detail')
author_detail_obj = AuthorDetail.objects.create(**author_detail)
validated_data['author_detail_id'] = author_detail_obj.id
author = Author.objects.create(**validated_data)
author.save()
return author
def update(self, instance, validated_data):
author_detail = validated_data.pop('author_detail')
AuthorDetail.objects.filter(pk=instance.author_detail.pk).update(**author_detail)
Author.objects.filter(pk=instance.pk).update(**validated_data)
return instance
# 视图类
class AuthorView(APIView):
def get(self, request):
author_obj = Author.objects.all()
ser = AuthorSerializer(author_obj, many=True)
return Response({'code': 200, 'msg': 'success!', 'data': ser.data})
def post(self, request):
ser = AuthorSerializer(data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 200, 'msg': 'success!', 'data': ser.data})
else:
return Response({'code': 201, 'msg': 'failure!', 'errors': ser.errors})
class AuthorDDetail(APIView):
def get(self, request, index):
author_obj = Author.objects.filter(pk=index).first()
ser = AuthorSerializer(author_obj)
return Response({'code': 200, 'msg': 'success!', 'data': ser.data})
def put(self, request, index):
author_obj = Author.objects.filter(pk=index).first()
ser = AuthorSerializer(author_obj, request.data)
if ser.is_valid():
ser.save()
return Response({'code': 200, 'msg': 'success!'})
else:
return Response({'code': 201, 'msg': 'failure!', 'errors': ser.errors})
def delete(self, request, index):
author_obj = Author.objects.filter(pk=index).first()
AuthorDetail.objects.filter(pk=author_obj.author_detail.id).delete()
return Response('okokok')
# 路由
urlpatterns = [
path('authors/', AuthorView.as_view()),
path('authors/<int:index>/', AuthorDDetail.as_view()),
]
# 反序列化,小知识点点
1.source的反序列化不行吗?不行
2.前端的key可以任意写,因为重写create和update的时候可手动上传
小补充:模块与包的使用
# 模块与包
-模块:一个py文件,被别的py文件导入使用,这个py文件称之为模块,运行的这个py文件称之为脚本文件
-包:一个文件夹下有__init__.py
# 模块与包的导入问题
'''
0 导入模块有相对导入和绝对导入,绝对的路径是从环境变量开始的
1 导入任何模块,如果使用绝对导入,都是从环境变量开始导入起
2 脚本文件执行的路径,会自动加入环境变量
3 相对导入的话,是从当前py文件开始计算的,建议在同一个文件夹下用相对导入
4 以脚本运行的文件,不能使用相对导入,只能用绝对导入
'''
20 反序列化校验源码分析(了解)
# 序列化类的校验功能
-局部钩子:必须validate_字段名
-全局钩子:validate
# 入口:
-ser.is_valid 才做的校验---》入口
-BookSerializer---》Serializer——-》BaseSerializer---》is_valid---》继承了Field
-is_valid 方法
def is_valid(self, *, raise_exception=False):
# self中没有_validated_data,只有执行完后,才有
if not hasattr(self, '_validated_data'):
try:
# 核心---》这一句
# 想看它的源代码,按住ctrl+鼠标点击是不对的---》只能找当前类的父类
#但它真正的执行是,从根上开始找
self._validated_data = self.run_validation(self.initial_data)
except ValidationError as exc:
self._validated_data = {}
self._errors = exc.detail
else:
self._errors = {}
if self._errors and raise_exception:
raise ValidationError(self.errors)
return not bool(self._errors)
-self.run_validation(self.initial_data),不能按住ctrl+鼠标点点击,要从根上开始找
-Serializer的run_validation
def run_validation(self, data=empty):
# 局部钩子
value = self.to_internal_value(data)
try:
# 全局钩子
value = self.validate(value) # BookSerializer只要写了,优先执行它的
except (ValidationError, DjangoValidationError) as exc:
raise ValidationError(detail=as_serializer_error(exc))
return value
-self.to_internal_value(data)---》Serializer类的方法
def to_internal_value(self, data):
for field in fields: #序列化类中写的一个个的字段类的对象列表
# 一个field是name对象,field.field_name字符串 name
# self是谁的对象:序列化类的对象,BookSerializer的对象 validate_name
validate_method = getattr(self, 'validate_' + field.field_name, None)
try:
# 字段自己的校验规则
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
# 局部钩子
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = get_error_detail(exc)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
# 总结:
-ser.is_valid---》走局部钩子的代码---》是通过反射获取BookSerializer中写的局部钩子函数,如果写了,就会执行----》走全局钩子代码---》self.validate(value)--->只要序列化类中写了,优先走自己的
# 补充:
-写在类中得方法,只要写了,就会执行,不写就不执行
-面向切面编程 AOP OOP:面向对象编程
-在程序的前期,中期,后期插入一些代码来执行
-装饰器:实现aop的一种方式
-钩子函数:实现aop的一种方式
21 断言
# 断言
assert hasattr(self, 'initial_data'), (
'Cannot call `.is_valid()` as no `data=` keyword argument was '
'passed when instantiating the serializer instance.'
)
# 断定某个东西是我认为的,如果不是就抛异常
# 等同于if判断+抛异常
def add(a, b):
return a + b
res = add(8, 9)
# assert res == 16, Exception('不等于16')
if not res==16:
raise Exception('不等于16')
print('随便')
22 drf之请求
# 视图类:APIView
# 序列化组件:Serializer,ModelSerializer
# drf:Request类的对象
# drf:Response
22.1 Request类对象的分析
1).data
request.data 返回解析之后的请求体数据。类似于Django中标准的request.POST和 request.FILES属性,但提供如下特性:
包含了解析之后的文件和非文件数据
包含了对POST、PUT、PATCH请求方式解析后的数据
利用了REST framework的parsers解析器,不仅支持表单类型数据,也支持JSON数据
2).query_params
request.query_params与Django标准的request.GET相同,只是更换了更正确的名称而已
3) 其他的属性用起来跟之前一样
22.2 请求能够接受的编码格式
# urlencoded
# form-data
# json
三种都支持
限制只能接受某种或某几种编码格式
from rest_framework.settings import DEFAULT
from rest_framework.parsers import JSONParser
# 限制方式一:在视图类上写---》只是局部视图类有效
# 总共有三个:JSONParser, FormParser, MultiPartParser
class BookView(APIView):
parser_classes = [JSONParser, FormParser]
# 限制方式二:在配置文件中写---》全局有效
# drf的配置,统一写成它
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
# 'rest_framework.parsers.FormParser',
# 'rest_framework.parsers.MultiPartParser',
],
}
# 全局配置了只支持json,局部想支持3个
-只需要在局部,视图类中,写3个即可
class BookView(APIView):
parser_classes = [JSONParser, FormParser,MultiPartParser]
# 总结:能够处理的请求方式编码
-优先从视图类中找
-再去项目配置文件找
-再去drf默认的配置中找
23 drf之响应
23.1 响应类的对象Response
# return Response({code:100})
-data:响应体的内容,可以字符串,字典,列表
-status:http响应状态码
-drf把所有响应码都定义成了一个常量
template_name:模板名字,用浏览器访问,看到好看的页面,用postman访问,返回正常数据
-自定制页面
-根本不用
headers:响应头加数据(后面讲跨域问题再讲)
-headers={'name':'lqz'}
content_type:响应编码,一般不用
# 三个重要的:data,status,headers
23.2 响应的格式
# 默认是两种:纯json,浏览器看到的样子
# 注册restframework就可以用网页打开json格式数据
# 限制方式一:在视图类上写---》只是局部视图类有效
# 总共有两个:JSONRenderer,BrowsableAPIRenderer
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
class BookView(APIView):
renderer_classes = [JSONRenderer]
# 限制方式二:在配置文件中写---》全局有效
# drf的配置,统一写成它
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
# 'rest_framework.renderers.BrowsableAPIRenderer',
],
}
# 全局配置了只支持json,局部想支持2个
-只需要在局部,视图类中,写2个即可
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
class BookView(APIView):
renderer_classes = [JSONRenderer,BrowsableAPIRenderer]
24 视图之两个视图基类
# 视图类:
-APIView:drf最顶层的视图类,继承了原生djagno的View
-1 去除了csrf
-2 包装了新的request
-3 在执行视图类的方法之前,执行了三大认证
-4 处理了全局异常
-GenericAPIView:GenericAPIView继承了APIView
# GenericAPIView
-类属性:
queryset:要序列化的所有数据
serializer_class:序列化类
lookup_field = 'pk' :查询单条时的key值,为实际存在的字段名称
filter_backends:过滤类
pagination_class:分页类
-方法:绑定给对象的方法(写成方法的目的是解耦合,提高扩展性)
-get_queryset():获取所有要序列化的数据【后期可以重写】
-get_serializer : 返回序列化类 ser=get_serializer(instance=qs,many=True)
-get_object :获取单个对象
-get_serializer_class:仅仅是返回序列化类,不能传任何参数
serializer_class=self.get_serializer_class()
serializer_class(instance=qs,many=True)
#总结:以后继承GenericAPIView写接口
1 必须配置类属性
queryset
serializer_class
2 想获取要序列化的所有数据
get_queryset()
3 想使用序列化类:
get_serializer
4 想拿单条
get_object
-以后,如果涉及到数据库操作,尽量用GenericAPIView
24.1 使用APIView+序列化类+Response写接口
from rest_framework.views import APIView
from .serializer import BookSerialzier
from rest_framework.response import Response
from .models import Book
# class BookView(APIView):
# def get(self, request):
# qs = Book.objects.all()
# ser = BookSerialzier(qs, many=True)
# return Response({'code': 100, 'msg': '成功', 'results': ser.data})
#
# def post(self, request):
# ser = BookSerialzier(data=request.data)
# if ser.is_valid():
# ser.save()
# return Response({'code': 100, 'msg': '成功'})
# else:
# return Response({'code': 100, 'msg': ser.errors})
#
#
# class BookDetailView(APIView):
# def get(self, request, pk):
# book = Book.objects.all().get(pk=pk)
# ser = BookSerialzier(book)
# return Response({'code': 100, 'msg': '成功', 'results': ser.data})
#
# def put(self, request, pk):
# book = Book.objects.get(pk=pk)
# ser = BookSerialzier(data=request.data, instance=book)
# if ser.is_valid():
# ser.save()
# return Response({'code': 100, 'msg': '更新成功'})
# else:
# return Response({'code': 100, 'msg': ser.errors})
补充:序列化类源码分析之——many参数的作用
# 补充:
魔法方法:类中使用__开头 __结尾的方法,它不需要主动调用,某种情况下会自动触发
object类身上的
-如果传了many=True,序列化多条----》得到的对象不是BookSerializer对象,而是ListSerialzier的对象中套了一个个的BookSerializer对象
-如果传了many=False,序列化单条---》得到的对象就是BookSerializer的对象
-类实例化得到对象,是谁控制的?
def __new__(cls, *args, **kwargs):
if kwargs.pop('many', False):
return cls.many_init(*args, **kwargs)
return super().__new__(cls, *args, **kwargs)
24.2 使用GenericAPIView+序列化类+Response写接口
from rest_framework.generics import GenericAPIView
class BookView(GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerialzier
def get(self, request):
qs = self.get_queryset()
ser = self.get_serializer(qs, many=True)
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
def post(self, request):
ser = self.get_serializer(data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
class BookDetailView(GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerialzier
def get(self, request, pk):
book = self.get_object()
ser = self.get_serializer(book)
return Response({'code': 100, 'msg': '成功', 'results': ser.data})
def put(self, request, pk):
book = self.get_object()
ser = self.get_serializer(data=request.data, instance=book)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '更新成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
# 作业
# 1 原生django想往响应头加数据,如何加?
def test(request):
res = HttpResponse('ok')
res['Access-Control-Allow-Methods'] = 'get,post,hahahahha'
return res
# HttpResponse、render,redirect本质上都是HttpResponse响应对象
# 2 编写一个post接口,只能接受json编码格式,响应json格式,响应头中加入
Access-Control-Allow-Origin = "*"
class Test(APIView):
parser_classes = [JSONParser, ]
renderer_classes = [JSONRenderer, ]
def post(self, request):
print(request.data)
res = Response('ok')
res['Access-Control-Allow-Origin'] = '*'
return res
25 5个视图扩展类
from .models import Book
from .serializer import BookSerialzier
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin
class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
class BookDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
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)
# 5 个视图扩展类--->不是视图类---》必须配合GenericAPIView及其子类使用---》不能配合APIView使用
# 5个视图扩展类,每一个类中只有一个方法,完成5个接口中的其中一个,想写多个接口,就要继承多个
26 9个视图子类
# ModelViewSet:
-继承它后,只需要在视图类中写两行
queryset = Book.objects.all()
serializer_class = BookSerialzier
-配置路由,5个接口都有了
path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
path('books/<int:pk>/', BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
# 一般要重写某个方法
# ModelViewSet 源码分析
-继承了:
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin
GenericViewSet
-ViewSetMixin :没有见过,重写了 as_view
-GenericAPIView
-只要继承了ModelViewSet,路由写法变了,谁控制它变的:ViewSetMixin
# ViewSetMixin 如何控制路由写法变了?
-BookView.as_view 是在执行,其实是ViewSetMixin的as_view
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
#我们传入的 actions={'get': 'list', 'post': 'create'}
def view(request, *args, **kwargs):
self = cls(**initkwargs)
for method, action in actions.items():
# method:get
# action:list
# 通过反射,self是BookView的对象,取BookView对象中反射list
handler = getattr(self, action)
# 通过反射:setattr(BookView的对象,'get',list方法)
setattr(self, method, handler)
# APIViwe的dispatch
return self.dispatch(request, *args, **kwargs)
return csrf_exempt(view)
# 总结:
'''
1 只要继承了ViewSetMixin及其子类,路由写法就变了,必须传actions参数
2 变成映射关系了:
path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
3 以后,只要是books路由匹配成功,的get请求,就会执行视图类BookView的list方法
4 以后视图类中的方法名,可以随意命名
5 这个类,必须配合视图类使用(APIView,GenericAPIView,9个视图子类),必须放在视图类之前
'''
27 视图集
python源文件右键--diagrams--show-- python class就会出现继承逻辑图
27.1 总结
# 两个视图基类(-以后,如果涉及到数据库操作,尽量用GenericAPIView)
-APIView(继承Django的View类)
-GenericAPIview(继承APIView)
-类属性:
-queryset:要序列化的数据
-serializer_class:指定序列化类
-lookup_field:获取单条数据的查询条件
-filter_backends:过滤类
-pagination_class:分页类
-如何使用:
self:一个对象
self.queryset--->先找对象自己,而自己没有,才用了类的
-类方法:绑定给对象的方法(写成方法的目的是解耦合,提高扩展性)
-get_queryset:返回所有要序列化的数据
-get_object:获取单条数据,路由必须:path('/<int:pk>/', PublishDetailView.as_view()),
-get_serializer:真正的实例化得到序列化类 ser=get_serializer(instance=qs,many=True)
-get_serializer_class:仅仅是返回序列化类,不能传任何参数
serializer_class=self.get_serializer_class()
serializer_class(instance=qs,many=True)
# 5 个视图扩展类(不是视图类,需要配合GenericAPIView及其子类使用,不能配合APIView使用)
-因为五个视图扩展类用到了GenericAPIView类中的属性和方法,所以必须结合GenericAPIView一起使用。需要什么功能就继承GenericAPIView和视图扩展类
from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin
class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
class BookDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
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)
# 9个视图子类
from rest_framwork.generics import ListAPIView,CreateAPIView,RetrieveAPIView,UpdateAPIView,DestroyAPIView,ListCreateAPIView,RetrieveUpdateDestroyAPIView,RetrieveUpdateAPIView,RetrieveDestroyAPIView
-分别继承GenericAPIView和5个扩展类里的某些,代码只用写queryset和serializer_class
# 视图集:
from rest_framwork.viewsets import ModelViewSet
ModelViewSet
-继承了5个接口的扩展类和GenericViewSet
-继承它后,只需要在视图类中写两行 queryset = Book.objects.all() serializer_class = BookSerialzier
-配置路由,5个接口都有了
path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
path('books/<int:pk>/', BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
-一般要重写某个方法,可以在保存前后添加某些功能
ReadOnlyModelViewSet:继承了两个接口,ListModelMixin、RetrieveModelMixin和GenericViewSet
ViewSetMixin:魔法,不能单独使用,必须配合视图类用,会使路由写法改变,注意查找顺序,写在左边
ViewSet:继承了ViewSetMixin、APIView,以后想继承APIView,但是想路由写法变化,视图类中方法可以任意命名
GenericViewSet:继承了ViewSetMixin、GenericAPIView,以后想继承GenericAPIView,但是想路由写法变化,视图类中方法可以任意命名
28 drf之路由
# 路由写法有多种
-原始写法
-映射的写法:path('books/', BookView.as_view({'get': 'list', 'post': 'create'}))
-自动生成路由
-必须要继承ViewSetMixin及其子类的视图类,才能用
-继承了5个视图扩展类+ViewSetMixin的视图类,能自动生成路由(自动映射),生成的路由跟咱们写的这个是一样的
-path('books/', BookView.as_view(actions={'get': 'list', 'post': 'create'})),
-path('books/<int:pk>/', BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
-1.导入路由类 from rest_framwork import SimpleRouter,DefaultRouter
-2.实例化得到对象 router=SimpleRouter()
-3.注册路由(视图类)
router.register('lqz',SMSView,'lqz') # 前缀/视图类/别名,
# 路径是:http://127.0.0.1:8000/api/v1/lqz/lqz/
-4.加入到路由中:
-4.1 urlpatterns+=router.urls
-4.2 使用include urlpatterns=[
path('lqz',include(router.urls))
]
-自己写的视图类的方法,如何映射
-映射的方式我们会
-自动生成的方式
1 继承了 5个视图扩展类+ViewSetMixin的视图类,能自动生成路由(get:list,get:retrieve..)
2 我们自己命名的: 方法名:login send_sms,需要使用装饰器来做
# 视图类:需要使用装饰器做映射,否则不知道method对应视图中的哪个
from rest_framework.decorators import action
class SMSView(ViewSet):
# url_path不写的话会自动将方法名写在路径中
@action(methods=['GET'], detail=False, url_path='lqz', url_name='lqz')
def lqz(self, request):
# 路由
-1.导入路由类 from rest_framwork import SimpleRouter
-2.实例化得到对象 router=SimpleRouter()
-3.注册路由(视图类)
router.register('lqz',SMSView,'lqz') # 前缀/视图类/别名
-4.加入到路由中:
-4.1 urlpatterns+=router.urls
-4.2 使用include urlpatterns=[
path('lqz',include(router.urls)) # 前面可以不写为空
]
# 路径是:http://127.0.0.1:8000/api/v1/lqz/lqz/
3 action装饰器的参数
methods:请求方式
detail:一个True,一个False,用True,表示生成详情的路径 <int:pk>
# True,books/1/方法名/
# False,books/方法名/
url_path:路径名字,需要加上前面的路径一起,如果不加,默认以函数名作为路径名
url_name:反向解析使用的名字(用的不多)
# 路由类,有两个,用法完全一致,区别是DefaultRouter生成的路径多
SimpleRouter :用的最多
DefaultRouter
# DefaultRouter与SimpleRouter的区别是,DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据。
补充:django转换器,配置文件作用
# django转换器:django 2.x以后,为了取代 re_path
-int path('books/<str:name>') ---->/books/1----》name=1---》当参数传入视图类的方法中
-str
-path 匹配非空字段,包括分隔符/
-slug
-uuid
# django配置文件
1 djagno项目要运行,优先执行配置文件的内容,做一下配置加载工作manage.py,
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drf_day07.settings')
2 任何一个django项目都有两套配置:
-一套是项目自己的,自己有那个配置参数,优先用自己的
-一套是内置的(django内置的)
3 配置参数的作用
配置文件settings
from pathlib import Path
# 1 项目的根路径
BASE_DIR = Path(__file__).resolve().parent.parent
# 2 密钥---》djagno中涉及到加密的,大部分都会用这个密钥
SECRET_KEY = 'django-insecure-eh=o(kt_70%8wj4r+le-7*$7t+fn%_2rfh61f09(2^&3q-5vk)'
# 3 是否开启调试模式,上线一定关闭
# 只要是调试模式:
# 访问的路径不存在,他会显示出所有能访问的路径
# 视图类出了异常,浏览器中能看到
DEBUG = False
# 4 项目是要部署在某个服务器上,这个列表写,部署服务器的ip地址, * 表示任意地址都可以
ALLOWED_HOSTS = ['*']
# 5 内置,我们自己写的app
from django.contrib import auth
INSTALLED_APPS = [
'django.contrib.admin', # 后台管理---》很多表不是它的,是别的app的
'django.contrib.auth', # auth 模块,UsrInfo表----》有6个表
'django.contrib.contenttypes', # 有个django_content_type表是,这个app的
'django.contrib.sessions', # session相关的
'django.contrib.messages', # 消息框架
'django.contrib.staticfiles', # 静态文件开启的app
'app01.apps.App01Config', # app01
'rest_framework' # drf
]
# 6 中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', # session相关
'django.middleware.common.CommonMiddleware', # 公共
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# 7 根路由
ROOT_URLCONF = 'drf_day07.xxx'
# 8 模板文件所在路径
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # 必须是个列表
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# 9 项目上线,运行application,后面再说
WSGI_APPLICATION = 'drf_day07.wsgi.application'
# 10 数据库配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# 密码认证相关,忽略
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# 11 做国际化
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# 13 静态文件
STATIC_URL = '/static/'
# 14 表中,默认可以不写id,id主键自增,之前全是AutoField,长度很短
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
29 登陆功能
表模型
class UserInfo(models.Model):
name = models.CharField(max_length=32)
password = models.CharField(max_length=64)
class UserToken(models.Model):
token = models.CharField(max_length=64)
user = models.OneToOneField(to=UserInfo, on_delete=models.CASCADE)
视图类
class UserView(ViewSet):
@action(methods=['POST'], detail=False)
def login(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = UserInfo.objects.filter(name=username, password=password).first()
if user:
# 登录成功
# 1 生成一个随机字符串 token
token = str(uuid.uuid4())
# 2 把token存到表中,UserToken表有值就更新,没有值就新增
UserToken.objects.update_or_create(user=user, defaults={'token': token})
return Response({'code': 100, 'msg': '登录成功', 'token': token})
else:
return Response({'code': 101, 'msg': '用户名或密码错误'})
# 回顾
UserToken表中有user字段
拿到了一个UserToken表的对象 user_token
user_token.token 就是字符串
user_token.user 基于对象的跨表查询,拿到的是user对象 user_token.user.password
user_token.user_id 隐藏了这个字段,是可以用的,它是管理的user对象的id号
查询功能
UserToken.objects.filter(user=user对象)
UserToken.objects.filter(user_id=user.id)
路由
# 方式一:
path('login/', views.UserView.as_view({'post':'login'})),
# 路由如果这样写,是不需要使用action装饰器
# 方式二:自动生成路由---》视图类中一定要用action装饰器
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user', views.UserView, 'user')
urlpatterns = [
path('admin/', admin.site.urls),
]
# http://127.0.0.1:8000/user/login/
urlpatterns += router.urls
30 认证组件
# APIView执行流程
-在视图视图类的方法之前,执行了三大认证
-认证
# 认证:登录认证
-登录认证---》控制,某个接口必须登录后才能访问
from rest_framework.authentication import BaseAuthentication
from .models import UserToken
from rest_framework.exceptions import AuthenticationFailed
# 1 写一个类,继承BaseAuthentication
class LoginAuth(BaseAuthentication):
# 2 类中重写方法
def authenticate(self, request): # 父类中有,一定要重写
# 3 在方法中,完成登录认证,如果不是登录的,抛异常
# token携带地方有2处:请求地址和请求头
# 3.1取出token
token = request.query_params.get('token') # 请求地址
token = request.META.get('HTTP_AUTHORIZATION') # 请求头
# 3.2去数据库中根据token,校验有没有数据
user_token = UserToken.objects.filter(token=token).first()
if user_token:
user_obj = user_token.user
# 4 如果是登陆的,返回登陆用户和token
return user_obj,token
else:
raise AuthenticationFailed('没有登陆,禁止访问')
# 5 在视图类中,使用认证类(局部使用在类里使用,全局使用在配置文件中配置)
# 认证组件使用步骤(固定用法)
1 写一个类,继承BaseAuthentication
2 在类中写:authenticate
3 在方法中,完成登录认证,如果不是登录的,抛异常
4 如果是登录的,返回登录用户和token
5 在视图类中,使用认证类(局部使用)
class BookView(APIView):
authentication_classes = [LoginAuth, ]
6 全局使用:
# 全局使用
### 重点:不要在配置文件中,导入莫名其妙的包
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'app01.auth.LoginAuth'
],
}
7 全局使用后,局部禁用
class UserView(ViewSet):
# 局部禁用
authentication_classes = []
8 认证类的使用顺序
-优先用视图类配置的
-其次用项目配置文件
-最后用drf默认的配置
# 小重点;一旦通过认证,在request中就有当前登录用户
def get(self, request):
# 一旦通过认证,在request中就有当前登录用户
print(request.user.name,'访问了接口')
31 权限组件
# 大家都登录了,但有的功能(接口),只有超级管理员能做,有的功能所有登录用户都能做----》这就涉及到权限的设计了
# 权限设计:比较复杂---》有acl,rbac,abac。。。
# 咱们现在只是为了先讲明白,drf的权限组件如何用,咱们先以最简单的为例
-查询所有图书:所有登录用户都能访问(普通用户和超级管理员)
-其实没有权限控制
-删除图书,只有超级管理员能访问
-给其它用户设置的权限
from rest_framework.permissions import BasePermission
class UserPermission(BasePermission):
def has_permission(self, request, view):
if request.user.user_type == 1:
return True
else:
# 错误信息的渲染
self.message='您的权限过低,无法访问'
return False
# 权限类的使用步骤
1 写一个类,继承BasePermission
2 在类中写方法:has_permission
-如果有权限,就返回True
-如果没有权限,就返回False
-错误信息是self.message='字符串'
3 局部使用
class BookDetailView(APIView):
permission_classes = [AdminPermission, ]
4 全局使用
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'app01.permission.AdminPermission'
],
}
5 局部禁用
class BookView(APIView):
permission_classes = []
32 频率组件
# 限制访问频次
-比如某个接口,一分钟只能访问5次,超过了就得等
-按IP地址 限制
-按用户id 限制
from rest_framework.throttling import SimpleRateThrottle
class FrequencyThrottle(SimpleRateThrottle):
# 类属性限制频率
scope = 'blue'
def get_cache_key(self, request, view):
# 返回什么就以什么做频率限制:主要是用户id和客户端ip地址
# ip地址
# ip = request.META.get('REMOTE_ADDR')
# return ip
# 用户id
id = request.user.id
print(id)
return id
# 频率类的使用步骤
1 写个类,继承:SimpleRateThrottle
# 继承BaseThrottle,重写allow_request,如果频率运行,就返回True,如果频率不允许就返回False
2 重写某个方法:get_cache_key
-可以返回ip或用户id
-返回什么,就以什么做频率限制
3 写一个类属性,随意命名一个名
scope = 'lqz'
4 在配置文件中配置:
'DEFAULT_THROTTLE_RATES': {
'lqz': '3/m' # 一分钟访问3次
},
5 全局用
'DEFAULT_THROTTLE_CLASSES': [
],
6 局部用
class BookView(APIView):
throttle_classes = [MyThrottle]
32.1 自定义频率类
# 1 频率类
- 写一个类,继承SimpleRateThrottle,重写get_cache_key,返回[ip,用户id]什么,就以什么做限制,编写类属性 scope = 字符串,在配置文件中配置
'DEFAULT_THROTTLE_RATES': {
'字符串': '3/m',
}
-配置在视图类,全局使用(配置在配置文件中)
# 2 自定义频率类
-源码中找---》自定义频率类如何写---》写个类,重写allow_request,如果能访问就返回True,不能访问返回False
-APIView---》dispatch---》
def check_throttles(self, request):
throttle_durations = []
# 在视图类上配置的频率类的列表中一个个频率类的对象
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
throttle_durations.append(throttle.wait())
if throttle_durations:
durations = [
duration for duration in throttle_durations
if duration is not None
]
duration = max(durations, default=None)
self.throttled(request, duration)
# 3 自定义频率类
class MyThrottles():
VISIT_RECORD = {} # 访问者 ip 字典,格式是{ip1:[时间4,时间3,时间2,时间1],ip2:[时间,时间],ip3:[当前时间]}
def __init__(self):
self.history = None
def allow_request(self, request, view):
# (1)取出访问者ip
# print(request.META)
ip = request.META.get('REMOTE_ADDR')
import time
ctime = time.time()
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
if ip not in self.VISIT_RECORD:
# {ip地址作为key:[当前时间,时间1,时间2,时间3]}
self.VISIT_RECORD[ip] = [ctime, ]
return True
self.history = self.VISIT_RECORD.get(ip)
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
while self.history and ctime - self.history[-1] > 60:
self.history.pop()
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
if len(self.history) < 3:
self.history.insert(0, ctime)
return True
else:
return False
def wait(self):
import time
ctime = time.time()
return 60 - (ctime - self.history[-1])
# 3 SimpleRateThrottle源码分析
-allow_request:必须有的,频率类,必须重写它
-get_cache_key:没有具体实现,直接抛了异常,需要子类重写
-wait:必须有的,返回一个数字,告诉前端,还有多长时间能访问
-------都是为了给allow_request辅助的-----
get_rate
parse_rate
throttle_failure
throttle_success
def allow_request(self, request, view):
if self.rate is None: # 要取init中看,self.rate=3/m,如果自己在频率类中写rate=5/m,我们就不需要写scope和配置文件了
return True
# get_cache_key返回了ip:192.168.1.19
self.key = self.get_cache_key(request, view)
if self.key is None: # 如果get_cache_key返回None,也不会做频率限制
return True
# self.history = self.VISIT_RECORD.get(self.key, [])
# self.cache 是缓存,相当于咱们的self.VISIT_RECORD
self.history = self.cache.get(self.key, [])
self.now = self.timer()# 取出当前时间
# self.duration:配置的1分钟访问3次,self.duration就是60
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
if len(self.history) >= self.num_requests: # '5/m',num_requests就是5
return self.throttle_failure()
return self.throttle_success()
# __init__
def __init__(self):
if not getattr(self, 'rate', None): # 如果没有,执行下面的代码
self.rate = self.get_rate()
self.num_requests, self.duration = self.parse_rate(self.rate)
# self.get_rate()
def get_rate(self):
# self.scope 我们自己写的字符串
# self.THROTTLE_RATES配置文件中的那个字典
return self.THROTTLE_RATES[self.scope] # 咱们配置的 3/m
# self.num_requests, self.duration = self.parse_rate(self.rate)---》self.rate是3/s
def parse_rate(self, rate):
if rate is None:
return (None, None)
# 3/m---->num=3,period=m
num, period = rate.split('/')
# num_requests=数字3
num_requests = int(num)
#
d={'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
period[0]='m'
d[m]
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
return (num_requests, duration)
# 作业:使用django中间件实现,按ip地址,一分钟只能访问5次的限制
# 自己写频率,同样的ip,一分钟只能访问3次---》频率类
# 自定义的逻辑{ip1:[第二次访问时间,第一次访问时间],ip2:[第一次访问时间,],ip3:[当前时间]}
#(1)取出访问者ip
#(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
#(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
#(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
#(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
33 权限源码(了解)
33.1 权限源码
# 继承了APIView,才有的---》执行流程---》dispatch中----》三大认证
#1 APIView的dispatch的大约497行self.initial(request, *args, **kwargs)
def initial(self, request, *args, **kwargs): # 399行
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
# 2 读权限:APIView的方法self.check_permissions(request)
def check_permissions(self, request):
# permission_classes = [AdminPermission]
# self.get_permissions()我们配置在视图类上permission_classes列表中的认证类,一个个的对象
# self.get_permissions() 是 [AdminPermission(),]
for permission in self.get_permissions():
# 写的权限类,要重写has_permission方法
# 猜permission是我们写的权限类的对象
# self 是视图类的对象(BookView,PublisViwe)
if not permission.has_permission(request, self): # 权限没通过
self.permission_denied(
request,
# self.message 错误文字
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
# 3 读 APIView--->self.get_permissions
def get_permissions(self):
return [permission() for permission in self.permission_classes]
# 翻译:
l=[]
for permission in self.permission_classes:
l.append(permission())
return l
# 记住:
- 写的权限类,一定要写一个方法has_permission,返回True或False
- 配置再视图类上
33.2 补充
# 1 视图类的方法,必须返回 4件套或drf的Response
# 2 return和raise完全不一样
# 3 视图类继承了
视图类:ViewSetMixin,ListModelMixin----》查询所有好多代码不用写----》可以自动生成路由
router.register('book',BookView,'book')
/book/--->get请求过来被映射了 list---》执行视图类中的list----》
BookView中写了get方法,根本不会执行
自动生成路由
34 认证源码(了解)
34.1 认证源码
# 继承了APIView,才有的---》执行流程---》dispatch中----》三大认证
#1 APIView的dispatch的大约497行self.initial(request, *args, **kwargs)
def initial(self, request, *args, **kwargs):
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
# 2 self.perform_authentication(request)
def perform_authentication(self, request):
request.user # 这是个方法,包装成了数据属性
# 3 Request类的user 220 行
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
# 4 Request类的self._authenticate() 373 行
def _authenticate(self):
# self.authenticators 就是你写的认证类列表---》列表推导式---》[LoginAuth(),],是从Request类传过来,Request类初始化是在APIView的dispatch前面authenticators=self.get_authenticators(),
'''
def get_authenticators(self):
return [auth() for auth in self.authentication_classes]
'''
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
# 抛了异常被捕获了
if user_auth_tuple is not None:
# 如果返回了两个值,就会执行这句话
# self是Request的对象
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
# 5 Request类初始化
APIView---》dispathc的前面
# 总结:
1 认证类,必须写一个方法authenticate
2 如果认证通过,可以返回None,也可也返回两个值,但是第一个值,尽量是当前登录用户,第二个值一般放token
3 认证失败,抛异常AuthenticationFailed,继承了APIException,能捕获
34.2 Django中的翻译函数
# 只要做了国际化,会自动翻译成当前国家的语言
from django.utils.translation import gettext_lazy as _
_('hello')
35 过滤
# restful规范中
-请求地址中带过滤条件
# 带过滤的接口只有:查询所有
# 2.1 内置过滤类
from rest_framework.filters import SearchFilter
class BookView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
filter_backends = [SearchFilter]
# 支持这种搜索
# # http://127.0.0.1:8000/api/v1/books/?search=红
# search_fields = ['name']
#http://127.0.0.1:8000/api/v1/books/?search=11 只要name或price中带11都能搜出来
search_fields = ['name','price']
# 2.2 第三方过滤类
# pip3 install django-filter
from django_filters.rest_framework import DjangoFilterBackend
class BookView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
filter_backends = [DjangoFilterBackend]
# http://127.0.0.1:8000/api/v1/books/?name=红楼梦&price=45
# 按名字和价格精准匹配
filterset_fields = ['name', 'price']
# 2.3 自定义过滤类 价格再100----200之间的图书
from .filter import MyFilter
class BookView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
filter_backends = [MyFilter]
# 必须配合一个过滤类
from rest_framework import filters
from django.db.models import Q
class MyFilter(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
# 返回的数据,就是过滤后的数据
# http://127.0.0.1:8000/api/v1/books/?price=44&name=红楼梦 按名字或价格
price = request.query_params.get('price')
name = request.query_params.get('name')
queryset = queryset.filter(Q(name=name) | Q(price=price))
return queryset
# 过滤和排序可以一起使用
36 排序
# restful规范中
-请求地址中带过滤条件
# 排序功能的接口:查询所有
from rest_framework.filters import OrderingFilter
class BookView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
filter_backends = [OrderingFilter] # 排序类
# 配合一个类属性,按哪些字段可以排序
# http://127.0.0.1:8000/api/v1/books/?ordering=-price,-id
# 先按价格倒序排,价格一样,再按id倒叙排
ordering_fields = ['price','id']
37 分页
# 查询所有接口,过滤和排序了,但是实际上,这个接口,都需要有分页功能
-分页的展现形式
web:下一页点解
app,小程序:下滑下一页
-接口都一样,要支持分页
# drf提供给咱们,三种分页方式
# 基本分页
# 偏移分页
# 游标分页
from .page import MyPageNumberPagination,MyLimitOffsetPagination,MyCursorPagination
# 这种方式用的多
class BookView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerialzier
# http://127.0.0.1:8000/api/v1/books/?page=2&page_size=3
# pagination_class = MyPageNumberPagination # 只能按一种方式分页,不要放到列表中了
# http://127.0.0.1:8000/api/v1/books/?limit=4&offset=3 # 从第三条数据开始,取4条
# pagination_class = MyLimitOffsetPagination # 只能按一种方式分页,不要放到列表中了
pagination_class = MyCursorPagination # 只能按一种方式分页,不要放到列表中了
#### page.py
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination
## 基本分页
class MyPageNumberPagination(PageNumberPagination):
# 重写几个类属性 :4个
page_size = 2 # 每页显示的条数
page_query_param = 'page' # page=4 表示第4页
page_size_query_param = 'page_size' # page=4&page_size=5 表示查询第4页,每页显示5条
max_page_size = 5 # 每页最大显示多少条
## 偏移分页
class MyLimitOffsetPagination(LimitOffsetPagination):
# 重写几个类属性 :4个
default_limit = 2 # 每页显示多少条
limit_query_param = 'limit' # limit=3 这一页取3条
offset_query_param = 'offset' # 偏移量是多少 offset=3&limit=2 从第3条开始,拿2条
max_limit = 5 # 最多取5条
## 游标分页,只能上一页和下一页,不能直接跳到某一页,但是这个的速度快---》app上用它多
class MyCursorPagination(CursorPagination):
# 重写几个类属性 :3个
cursor_query_param = 'cursor' # 查询参数,其实用不到
page_size = 2 # 每页显示多少条
ordering = 'id' # 必须是要分页的数据表中的字段,一般按id来
# 作业:继承APIView,实现过滤排序和分页
class PublishView(ViewSet):
search_fields = ['name', 'city']
def list(self, request, *args, **kwargs):
publish_list = Publish.objects.all()
# 实例化得到一个过滤对象
filter_obj = SearchFilter()
fq = filter_obj.filter_queryset(request,publish_list,view=self)
# 实例化得到一个排序对象
order_obj = OrderingFilter()
order_obj.ordering_fields = ['id']
oq = order_obj.filter_queryset(request, fq, view=self)
# 实例化得到一个分页器对象
basicpage_obj = BasicPagination()
pq = basicpage_obj.paginate_queryset(oq, request, view=self)
serializer = PublishSerializer(pq, many=True)
return basicpage_obj.get_paginated_response(serializer.data)
# 补充
# 面向对象封装---》
-__开头,隐藏了,
-内部使用self.__属性/方法
-外部使用:person._类名__字段名
-别人,公司内部,不太使用 __
-约定俗称,类内部使用 _开头,本意就是隐藏,不给外部使用
补充:修改项目名字
# 1 先找到原文件路径:copy path--Absolute Path,去电脑文件资源管理器进行粘贴,找到后拷贝
# 2 refactor--rename--rename directory文件夹&project项目,终极大法:右键--- replace in files--replace all
# 3 环境变量设置:file--settings--language&framework--django--修改root和settings的路径
# 4 终端运行: python3.9 manage.py runserver
38 全局异常处理
# 对于前端来讲,后端即便报错,也要返回统一的格式,前端便于处理
{code:999,msg:'系统异常,请联系系统管理员'}
# 只要三大认证,视图类的方法出了异常,都会执行一个函数:rest_framework.views import exception_handler
### 注意:exception_handler
# 如果异常对象是drf的APIException对象,就会返回Response
# exception_handler只处理了drf的异常,其它的异常需要我们自己处理
# 如果异常对象不是drf的APIException对象,就会返回None
'''
我们要做的事就是重写该函数(因为源码原则上来说不可以改),然后写在配置文件中,这样代码出异常就走我写的exception_handler来统一格式。
源码可以改,直接在第三方模块中copy该模块到项目中,即走该模块可以魔改,缺点是实时更新
'''
# 补充:
-函数和方法的区别?
与类和实例无绑定关系的function都属于函数
与类和实例有绑定关系的function都属于方法
函数是封装了一些独立的功能,可以直接调用,能将一些数据(参数)传递进去进行处理,然后返回一些数据(返回值),也可以没有返回值。可以直接在模块中进行定义使用。 所有传递给函数的数据都是显式传递的。
方法和函数类似,同样封装了独立的功能,但是方法是只能依靠类或者对象来调用的,表示针对性的操作。
方法是自动传参self,函数是主动传参
# 补充:
- isinstance() 判断一个对象是不是某个类的对象 isinstance(对象,类)
- issubclass() 判断一个类,是不是另一个类的子类
from rest_framework.response import Response
from rest_framework.views import exception_handler
def common_exception_handler(exc, context):
# exc:错误对象;context:上下文对象,里面有请求对象(request)和视图类的对象(View)
# 只要走到这里,一定出异常了,我们正常的项目要记录日志(后面讲)
# 两种可能:一个是Response对象,一个是None
res = exception_handler(exc, context)
if res:
# 说明是drf的异常,它处理了
if isinstance(res.data, dict):
detail = res.data.get('detail')
else:
detail = res.data
return Response({'code': 998, 'msg': detail})
else:
# 说明是其它异常,它没有处理
# return Response({'code': 999, 'msg': '系统异常,请联系系统管理员'})
return Response({'code': 999, 'msg': str(exc)})
### 配置文件
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.excepitons.common_exception_handler',
}
######目的:处理全局异常,返回统一格式
39 接口文档
# 后端把接口写好后
-登录接口
-注册接口
-查询所有图书带过滤接口
# 前端人员需要根据接口文档,进行前端开发
# 前后端需要做对接----》对接第一个东西就是这个接口文档---》前端照着接口文档开发
# 公司3个人,每个人开发了10个接口,3个人都要同时写接口文档
# 接口文档的编写形式
-1 word,md,编写,大家都可以操作,写完放在git,公司的文档管理平台上
-2 第三方的接口文档平台(收费)
https://www.showdoc.com.cn/
-3 公司自己开发接口文档平台
-4 公司使用开源的接口文档平台,搭建
-YAPI:百度开源的
-https://zhuanlan.zhihu.com/p/366025001
-5 项目自动生成接口文档
-coreapi
-swagger
# 使用coreapi自动生成接口文档
-使用步骤:
-1 安装:pip3 install coreapi
-2 加一个路由
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('docs/', include_docs_urls(title='站点页面标题'))
]
-3 在视图类上加注释
-4 配置文件中配置:
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
-5 表模型或序列化类的字段上写 help_text--->会显示在接口文档的字段介绍上
-6 访问地址:
http://127.0.0.1:8000/docs/
40 base64编码和解码
# base64并不是一种加密反射,只是编码解码方式
# 字符串可以转成base64编码格式:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 解码成base64
import json
import base64
# d = {'user_id': 1, 'username': "lqz"}
# d_str = json.dumps(d)
# # 对字符串进行bashe64 编码
# res=base64.b64encode(bytes(d_str,encoding='utf-8'))
# print(res) # eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImxxeiJ9
# 解码
# res=base64.b64decode('TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ=')
# print(res)
# 记住: base64 编码,长度一定是4的倍数。如果不够,用 = 补齐
# base64的用途
'''
1 互联网中,前后端数据交互,使用base64编码
2 jwt 字符串使用base64编码
3 互联网中一些图片,使用base64编码
'''
s = 'iVBORw0KGgoAAAANSUhEUgAAAMcAAADHCAIAAAAiZ9CRAAAHB0lEQVR42u3dwXakSAxEUf//T3v2MwuD4kmkch7Lsl1Q5KUrCHFO//y6udHbj6fArUvVT7Y92tN/fvnJG+K7qB3Pk/d5dajUEYan5cnxFE6LqlSlKlVtVFU7I9QnCcH1XQlP/mrAR21fFLjn76MqValKVatVhetKnQhqXV8dxsA1Rq3iq8umtoIFCapSlapUpaqHZy1MCdT9f1+8o0zj1UnTSVCVqlSlqv+zqnClqTMSxiA8vuClQ23htuYqValKVaoaUfVhM5t77SsmBmLQZGuANx2qUpWqVLVXVfh8la9c8wr5fJWqfEVVvnKwKmrrmwGHceqTW3oqgE42OAwDValKVarao+qQG13qVpy6NsKTEBb6fVPhvutZVapSlarWqaKKb/yRKTzeDeQPKgYN7IsauqtKVapS1QWqaneqeB6i/goPWFTW6StTqKYj+VyqUpWqVLVRVe2+nRo8Uzf5fXDx6gSfT1CFS8t0WVWqUpWqlquqfSTqUZ5D6oy+Qj9c6bA1CA9MVapSlaruVhWea7waGAhzuA+8SQ9r9/AkqEpVqlLVBaqobEE9mYQniVpLTl1s4fwb/1z4+VGVqlSlqgtUHVI0Uy1wON8NlyG8NnB5n+UqValKVao6T1WIqe85JEo53iZPzuPDqyUsOAonXFWqUpWqFqliv1Zbi288aVHHgz/LhQcjKleRz1epSlWqUtV5qqgb+MkGnEKJj8/xdptKok2XuqpUpSpVLVLV99QRFQ5q4CZPMXUOJ+NU+A/EH7lKVapSlapuUUUdN1VhU78T0sEvtoHGJBHz5FBVpSpVqWqjqvAcDTTpVCsdZjiqjK5VMFTAwifQqlKVqlS1TlV4344HGgo3khJA3ANFwCfR7Y+JjapUpSpVbVBFdesD5W94Lx0OjKmZNFVVhJfWQc8sqEpVqlLVR6rmJ5HlveOUqboc7wg+2VdyflSlKlWp6g5V4Vcv/ogSPooOe/O+hoIqQfrqldfduqpUpSpV3aKKWjP89phqivFj/mSK0Dfyb8lVqlKVqlQ1q6rvWZy+roGaf4fO+gbqkz1+zZCqVKUqVV2gKrzf7nslvJPHa+VPdvFJS55MWVSlKlWpaqMqvOENUwKuHP8Rta6f9PjUWqhKVapS1U2qaj11bT1q1XPf7Tr1kBkVjMLLBt9UpSpVqeomVVQPS8UOvM7om1tTY91P9oWHQlWpSlWqWqdqYFIbntnw7roPLt504LG1bwKtKlWpSlUXqKrd2w8MMtnv+44seJqPsJQZSuuqUpWqVHWMqr6ieZJF3wWAf+Rj+wjkSlCVqlSlqkWq8O/XvlxVyw1Uv1y76sJnniYpI2uqKlWpSlWLVHWPG8steV//HsaO2huGXqmpRl83pCpVqUpVN6minuChBpl9qQWvnvF6ZaDKCXehKlWpSlXrVFF5KJxo4lPhbxuKyfl3X+lQOL2qUpWqVLVIFbXVeuowUvSV9bU/p6D0lezhBfk6ratKVapS1cGqQg3U13O4MFQoxJNN2K0P9DWfpXVVqUpVqjpGFV4Q901ha0kLv1qo0v/bv+rt1lWlKlWp6jpV+Ci6j2lNQ5/yyWHwZNuuKlWpSlV3q6LuwAeKgIEeP1y80P1AQzGUq1SlKlWp6hhVeDQJQ8bADfPAA1vUqIDKnU0RWVWqUpWqNqoKb3Sptf+FNirD1YLRAKbamaeqeVWpSlWqukAVhanvCzu8pccffsJ/eTLVhZf6n59UVapSlapWq8KnwpMBKwwQ1C19OLvtq1eoXbzOVapSlapUdZ6q2vyy9hU+EJU+gVKLJrVwSV02+NRcVapSlaquVEV9u4coB5YhHJ9TFieLkvB9/vWKqlSlKlUtUtVXjofLEHa+YT1NLRU+QqYeaMPH+apSlapUtU7VwCM4fa00VQSEn4vSif9OX+B73a2rSlWqUtV5qmozV+pbOVy88OCpd6bu26lMSWXcwuqoSlWqUtVGVVSS6CuIQ7gD3MPWAI+b+KKoSlWqUtWVqqj2Fv/zgUYAL/Rr4PqiW3j1Pv9cqlKVqlS1SFVYH4S9ME4HbwSoywafx+M5uGVioypVqUpVB6uiAkTT8zod+YM66VQ/EobLyerkdVpXlapUpaoNqvoqbOQLuzWo4aPfvvDU9yNkTVWlKlWpapEqKkmEU8++N8Sn3eGVEB4z9SO8f1eVqlSlqr2qDrnJr53ivoFxmDaoyDWwLyRlqkpVqlLValXsuPG34T//oNpkinu4eLXDOKRbV5WqVKUqVeEjUmoY3DcjD4fTh4QnasCgKlWpSlWqCpchzARhwKIqj0+CI1WmJFeCqlSlKlWtVkV99YZTTyqIhDqpfVEj7b5WJRylqEpVqlLVXlVUm9yXJMJynEpseGr51iKbIFWlKlWpapEqNzdwU5Ubv/0DEut6+QvkIFgAAAAASUVORK5CYII='
res = base64.b64decode(s)
with open('code.png', 'wb') as f:
f.write(res)
41 jwt介绍和原理
# https://www.cnblogs.com/liuqingzheng/p/16154439.html
# cookie,session,token发展历史
-会话管理
-cookie:客户端浏览器的键值对
-session:服务的的键值对(djangosession表,内存中,文件,缓存数据库)
-token:服务的生成的加密字符串,如果存在客户端浏览器上,就叫cookie
-三部分:头,荷载,签名
-签发:登录成功,签发
-认证:认证类中认证
# jwt:Json web token (JWT),web方向的token认证
-长得样子:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
41.1使用jwt认证和使用session认证的区别
# 看图
https://www.cnblogs.com/liuqingzheng/articles/17413681.html
基于session认证
# session登陆:登陆接口带着用户名和密码到服务端,根据用户名和密码去查询,查询通过后生成一个随机字符串token串,将用户主键和token存放在session表中。这里有一个加锁的问题,比如说有多个人并发登陆,都要存在session表中,我们对表都是写操作,写操作需要加锁,所以效率较低。所以说登陆之后,把该存的数据存到session表中,并且往前端返回一个token串,前端存到cookie中去。
# 访问登陆才能访问的接口(校验):请求携带token串到服务端,服务端从请求头中拿出token串,查询session表,校验得到user对象,存储在request.user中供后台逻辑使用,就是之前写的认证类
# 一个项目在一台电脑上跑起来叫单击运行,如果把这一个项目部署在两台或者是多台机器上,这叫集群部署。多台机器的话访问后端地址会发生改变,这时需要在服务端前挡一个软件Nginx,它可以监听任意端口,然后请求转发。
# 请求转发:来个请求是要转发给不同的人,这样我们前端访问地址都是nginx服务器的地址啊,但它可以给我转发到不同的集群的某个节点上,做到了项目上的精细化部署。
# 你说我项目为什么要做集群化的部署啊?
- 防止一台机器挂掉后,服务对外不可用
- 提高并发量
# 并发量上来后,但我们连的还是那一个数据库?但去操作session表的时候还是这一个数据库?那它的并发量是不是不会显著的提升?所以数据库配套的也要做集群,这样比较麻烦,所以出来jwt认证
基于jwt认证
# jwt登陆:用户携带用户名和密码,访问我,我们校验通过,给用户用代码生成一个token串,生成之后给前端,存储在cookie中
# 校验: 前端在访问我需要登录的接口之后需要从请求中携带这个token过来,我们不需要在数据库中查,只需要利用签发token的逆运算代码解析token即可。校验得到user对象,存储在request.user中供后台逻辑使用。认证通过继续往后走,认证不通过提示错误
'''
优点:
- 1 不会有数据库写操作,所以效率极高
- 2 在做集群时,不需要过多的进行其它安全处理(token认证是一套原理)
- 3 服务器不需要存token压力小
'''
41.2 三段式
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
#1 header jwt的头部承载两部分信息:
声明类型,这里是jwt
声明加密的算法 通常直接使用 HMAC SHA256
公司信息
{
'typ': 'JWT',
'alg': 'HS256'
}
然后将头部进行base64编码,构成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
# 2 payload 载荷就是存放有效信息的地方。
当前用户信息,用户名,用户id
exp: jwt的过期时间,这个过期时间必须要大于签发时间
定义一个payload:
{
"sub": "1234567890",
"name": "John Doe",
"user_id":99
}
然后将其进行base64编码,得到JWT的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
# signature JWT的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
42 jwt开发流程
# 使用jwt认证的开发流程,就是两部分
-第一部分:签发token的过程,登录做的
-用户携带用户名和密码,访问我,我们校验通过,生成token串,返回给前端
-第二部分:token认证过程,登录认证时使用,其实就是咱们之前讲的认证类,在认证类中完成对token的认证操作
-用户访问我们需要登陆后才能访问的接口,必须携带我们签发的token串(一般请求头)
-我们取出token,验证该token是否过期,是否被篡改,是否是伪造的
-如果正常,说明荷载中的数据,就是安全的,可以根据荷载中的用户id,查询出当前登录用户,放到request中即可。在认证类中放,request.user
# 没有认证类的话,就在中间键里做,只要在视图类方法之前完成认证即可
43 drf-jwt快速使用
# djagno+drf框架中,使用jwt来做登录认证
# 使用第三方:
-django-rest-framework-jwt:https://github.com/jpadilla/django-rest-framework-jwt (停止维护)
-djangorestframework-simplejwt:https://github.com/jazzband/djangorestframework-simplejwt (替代品)
# 我们可以自己封装 :https://gitee.com/liuqingzheng/rbac_manager/tree/master/libs/lqz_jwt
# 安装
pip3 install djangorestframework-jwt
# 补充:
- 密码明文一样,每次加密后都不一样,如何做,动态加盐,动态的盐要保存,跟密码保存在一起
- 有代码,有数据库,没有登录不进去的系统
# 解决不了:
token被获取,模拟发送请求
不能篡改
不能伪造
- 如何保证token串的安全:设置过期时间
# 快速使用
-签发过程(快速签发),必须是auth的user表(人家帮你写好了)
- user表里的密码pwd:pbkdf2_sha256$260000$lk8N5xDenUTcqNVuf5tX5t$l58cQoolP+t7j92o9zOfn4498K3epHywhyFUl8XeHZo=
- $符号做分割,第一部分是PBKDF2算法和SHA-256哈希函数,第二部分是PBKDF2重复迭代的次数,第三部分是盐,第四部分是口令和盐进行PBKDF2计算得到的密钥的哈希值
- 这里面是用make_password函数进行加密
- 登录接口--》基于auth的user表签发的,就不需要写额外的代码
-认证过程
- 登录认证,认证类(写好了)
- auth模块规定token串要写在请求头中,并且key为Authoriation,value为jwt 空格 token串
# 总结:
-签发:只需要在路由中配置
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
-认证:视图类上加
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated # 是否认证通过
class BookView(APIView):
authentication_classes = [JSONWebTokenAuthentication] # 认证类,drf-jwt提供的
permission_classes = [IsAuthenticated] # 权限类,drf提供的
-访问的时候,要在请求头中携带,必须叫
Authorization:jwt token串
44 drf-jwt定制返回格式
# 登录签发token的接口,要返回code,msg,username,token等信息
# 对返回格式进行定制
-1 写个函数,函数返回字典格式,返回的格式,会被序列化,前端看到
def common_response(token, user=None, request=None):
return {
'code': '100',
'msg': '登录成功',
'username': user.username,
'token': token,
}
-2 写的函数配置一下
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.jwt_response.common_response',
}
45 drf-jwt自定义用户表签发
# 1 创建一个用户表
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
age = models.IntegerField()
email = models.CharField(max_length=64)
# 注意⚠️from django.contrib.auth.models import AbstractUser
然后继承该类,不是自定义user表,是扩展,还是使用auth的user表,如果这么写不需要自己写签发,用内置即可
'''
from django.contrib.auth.models import AbstractUser
class Customer(AbstractUser):
USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = []
# 需要额外扩充的内容
....
可以重新指定 username 字段 USERNAME_FIELD
可以重新指定 email 的字段 EMAIL_FIELD
还有命令行在 createsuperuser 创建超级用户时提醒要输入信息的字段 REQUIRED_FIELDS
'''
# 2 登录接口
class UserView(ViewSet):
def login(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
if user:
# 登录成功,签发token--->先背过,
# 1 通过user,获取payload
payload = jwt_payload_handler(user)
print(payload)
# 2 通过payload,得到token
token = jwt_encode_handler(payload)
return Response({'code': 100, 'msg': '登录成功', 'username': user.username, 'token': token})
else:
return Response({'code': 101, 'msg': '用户名或密码错误'})
-配置文件,过期时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
# 3 登录接口签发token返回给前端
46 drf-jwt自定义认证类
# drf的认证类定义方式,之前学过
# 在认证类中,自己写逻辑
class JWTAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_TOKEN')
# 背过
# 校验token是否过期,合法,如果都通过,查询出当前用户,返回
# 如果不通过,抛异常
try:
payload = jwt_decode_handler(token)
# 如果认证通过,payload就可以认为是安全的,我们就可以使用
user_id = payload.get('user_id')
# 每个需要登录后,才能访问的接口,都会走这个认证类,一旦走到这个认证类,机会去数据库查询一次数据,会对数据造成压力?
user = User.objects.get(pk=user_id) # 类实例化得到对象,user.id得到id,user.name得到name,没有去数据库中查询,减少数据库压力。但是有坑,有的字段没有放进去
# 优化后的
# user = User(username=payload.get('username'), id=user_id)
# user = {'username':payload.get('username'), 'id':user_id}
except jwt.ExpiredSignature:
raise AuthenticationFailed('token过期')
except jwt.DecodeError:
raise AuthenticationFailed('解码失败')
except jwt.InvalidTokenError:
raise AuthenticationFailed('token认证异常')
except Exception:
raise AuthenticationFailed('token认证异常')
return user, token
# 全局使用,局部使用
47 drf-jwt的签发源码分析
# from rest_framework_jwt.views import obtain_jwt_token
# obtain_jwt_token就是ObtainJSONWebToken.as_view()---》视图类.as_view()
# 视图类
class ObtainJSONWebToken(JSONWebTokenAPIView):
serializer_class = JSONWebTokenSerializer
# 父类:JSONWebTokenAPIView
class JSONWebTokenAPIView(APIView):
# 局部禁用掉权限和认证
permission_classes = ()
authentication_classes = ()
def post(self, request, *args, **kwargs):
# serializer=JSONWebTokenSerializer(data=request.data)
serializer = self.get_serializer(data=request.data)
# 调用序列化列的is_valid---》字段自己的校验规则,局部钩子和全局钩子
# 读JSONWebTokenSerializer的局部或全局钩子
if serializer.is_valid(): # 全局钩子在校验用户,生成token
# 从序列化类中取出user
user = serializer.object.get('user') or request.user
# 从序列化类中取出token
token = serializer.object.get('token')
# 咱么定制返回格式的时候,写的就是这个函数
response_data = jwt_response_payload_handler(token, user, request)
response = Response(response_data)
return response
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# JSONWebTokenSerializer的全局钩子
class JSONWebTokenSerializer(Serializer):
def validate(self, attrs):
# attrs前端传入的,校验过后的数据 {username:lqz,password:lqz12345}
credentials = {
'username': attrs.get('usernme'),
'password': attrs.get('password')
}
if all(credentials.values()): # 校验credentials中字典的value值是否都不为空
# user=authenticate(username=前端传入的,password=前端传入的)
# auth模块的用户名密码认证函数,可以传入用户名密码,去auth的user表中校验用户是否存在
# 等同于:User.object.filter(username=username,password=加密后的密码).first()
user = authenticate(**credentials)
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
payload = jwt_payload_handler(user)
return {
'token': jwt_encode_handler(payload),
'user': user
}
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "{username_field}" and "password".')
msg = msg.format(username_field=self.username_field)
raise serializers.ValidationError(msg)
48 认证源码
# from rest_framework_jwt.authentication import JSONWebTokenAuthentication
# JSONWebTokenAuthentication
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
def get_jwt_value(self, request):
# auth=['jwt','token串']
auth = get_authorization_header(request).split()
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
if not auth:
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None
if smart_text(auth[0].lower()) != auth_header_prefix:
return None
if len(auth) == 1:
msg = _('Invalid Authorization header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid Authorization header. Credentials string '
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return auth[1] # token串
# 父类中找:BaseJSONWebTokenAuthentication---》authenticate,找到了
class BaseJSONWebTokenAuthentication(BaseAuthentication):
def authenticate(self, request):
# 拿到前端传入的token,前端传入的样子是 jwt token串
jwt_value = self.get_jwt_value(request)
# 如果前端没传,返回None,request.user中就没有当前登录用户
# 如果前端没有携带token,也能进入到视图类的方法中执行,控制不住登录用户
# 所以咱们才加了个权限类,来做控制
if jwt_value is None:
return None
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
# 通过payload拿到当前登录用户
user = self.authenticate_credentials(payload)
return (user, jwt_value)
# 如果用户不携带token,也能认证通过
# 所以我们必须加个权限类来限制
class IsAuthenticated(BasePermission):
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
49 权限控制
# ACL(访问控制列表)的权限控制:(针对互联网用户的产品)
用户表
id name password
1 zhangsan 123
权限表
id user_id 权限
1 1 评论权限
2 1 发抖音权限
张三:[评论权限,发抖音权限]
# RBAC(Role-Based Access Control)基于角色的访问控制:(针对于公司内部项目)
权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便
# django的auth其实就实现了基于角色的访问控制---》通过表控制的
-auth_user :用户表,存用户信息
-auth_group:组,角色,存放角色信息
-auth_permission:权限,存放权限信息
# 分析:一个组(角色)中,有多个用户,一个用户,属于多种角色----》多对多
-auth_user_groups:用户和组的多对多中间表
# 分析:一个组,可能有多个权限,一个权限,也可能属于多个组---》多对多
-auth_group_permissions:组和权限的多对多中间件
# django,多了张表:auth_user_user_permissions
# 分析:一个用户,可以有多个权限,一个权限,可以分配个多个用户---》多对多
-auth_user_user_permissions:用户和权限多对多中间表
49.1 我们开发中做权限控制
# python开发,公司内部项目多,使用基于角色的访问控制的权限,比较多
# python+django 开发出一套公司内部项目,要带rbac的权限控制
-1 基于django的auth+admin 快速开发,有更多操作,但是没学
-2 基于django的auth+admin+第三方美化 快速开发
-国内的:simple-ui,xadmin(弃用了)
-国外的:
-3 基于django+vue+自定制权限
-djagno-vue-admin :第三方开源
-4 完全自己写
49.2 基于django的auth+admin+第三方美化 快速开发
# 1 安装
pip3 install django-simpleui
# 2 配置文件配置,注册app
INSTALLED_APPS = [
'simpleui',
...
]
# 3 菜单栏定制
# 4 自定义菜单和页面
# 5 自定义按钮
# 6 切换图标
# 7 首页显示的隐藏
# 其实它可以开发出任意各种功能
49.3 Django-vue-admin演示
# https://gitee.com/liqianglog/django-vue-admin