1、配置响应格式
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类
'rest_framework.renderers.JSONRenderer', # json渲染器
'rest_framework.renderers.BrowsableAPIRenderer', # 浏览API渲染器
)
}
2、自定义响应类
from rest_framework.response import Response
class APIResponse(Response):
def __init__(self, code=100,msg=None,data=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None,**kwargs):
dic = {'code':code,'msg':msg}
if data:
dic['data'] = data
if kwargs:
dic.update(kwargs)
super(APIResponse, self).__init__(data=dic,status=status,
template_name=template_name,headers=headers,exception=exception,
content_type=content_type)
3、自动生成路由
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('publish',views.PublishView)
urlpatterns = [
path('admin/', admin.site.urls),
# 添加路由的方法一:
path('api/',include(router.urls))
]
# 添加路由的方法二:
urlpatterns += router.urls
# 只有继承了ViewSetMixin+9个视图子类才能自动生成路由
4、可以使用action配置自定义路由
class PublishView(ModelViewSet):
queryset = models.Publish.objects.all()
serializer_class = serializer.PublishSerializer
# detail为False的路由
# http://127.0.0.1:8000/publish/send_sms/
@action(methods=['GET'], detail=False)
def send_sms(self, request):
return APIResponse(msg='发送成功')
# detail为True的路由
# http://127.0.0.1:8000/publish/10/send_sms/
@action(methods=['GET'], detail=True)
def send_email(self, request, *args, **kwargs):
print(args) # ()
print(kwargs) # {'pk': '10'}
return APIResponse(msg='发送成功')
5、登录认证类
5.1、认证类编写
app01/auth
from app01 import models
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
# 继承BaseAuthentication类
class LoginAuth(BaseAuthentication):
# 函数名必须为authenticate
def authenticate(self, request):
token = request.GET.get('token')
user_token = models.UserToken.objects.filter(token=token).first()
if user_token:
# 必须返回两个值,第二个值不重要,可以为空
return user_token.user, ''
else:
raise AuthenticationFailed('没有登录')
5.2、全局使用
settings.py
REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["app01.auth.LoginAuth",]
}
5.3、局部禁用
在要禁用登录认证的类中写以下代码:
authentication_classes = []
6、权限类
6.1、权限类编写
class PermissionAuth(BasePermission):
message = '你没有权限'
def has_permission(self, request, view):
if request.user.user_type == 1:
return True
else:
self.message = '你是%s,你没有权限' % request.user.get_user_type_display()
return False
6.2、全局使用
REST_FRAMEWORK={
'DEFAULT_PERMISSION_CLASSES':['app01.auth.PermissionAuth'],
}
6.3、局部禁用
在要禁用的视图类中写以下代码:
permission_classes = []
7、频率类
7.1、频率类编写
class ThrottleAuth(SimpleRateThrottle):
scope = 'ip_th'
def get_cache_key(self, request, view):
# 返回用户ip,按照ip对频率进行限制
return self.get_ident(request)
7.2、全局使用
REST_FRAMEWORK={
'DEFAULT_THROTTLE_CLASSES':['app01.auth.ThrottleAuth'],
'DEFAULT_THROTTLE_RATES':{
'ip_th':'5/m',
},
}
7.3、局部禁用
在要禁用的类下面写以下代码:
throttle_classes = []
8、过滤类
8.1、内置过滤类的使用
8.1.1、在视图类中配置过滤类
# 只有继承了GenericApiView的类才能使用内置过滤类
from rest_framework.filters import SearchFilter
class BookView(ViewSetMixin, ListAPIView):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
# 配置过滤类
filter_backends = [SearchFilter,]
# 配置过滤字段
search_fields = ['name','price']
8.1.2、使用过滤
# 支持模糊查询
http://127.0.0.1:8000/book/?search=三英战吕布
http://127.0.0.1:8000/book/?search=582.02
8.1.3、全局使用
REST_FRAMEWORK={
'DEFAULT_FILTER_BACKENDS': ['rest_framework.filters.searchFilter',]
}
注意:需要在要使用过滤的视图中配置过滤字段
8.2、第三方过滤类的使用
8.2.1、第三方过滤类的配置(推荐使用)
# 1、安装
pip3 install django-filter
# 2、在settings.py中配置
INSTALLED_APPS = [
'django_filters'
]
8.2.2、在视图类中配置过滤类
from django_filters.rest_framework import DjangoFilterBackend
class BookView(ViewSetMixin, ListAPIView):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
# 使用第三方过滤类
filter_backends = [DjangoFilterBackend, ]
filter_fields = ['name', 'price']
8.2.3、使用过滤
# 不支持模糊查询
http://127.0.0.1:8000/book/?name=双节棍
http://127.0.0.1:8000/book/?name=双节棍&price=582.02
8.2.4、全局使用
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend',]
}
注意:需要在要使用过滤的视图中配置过滤字段
8.3、自定义过滤类
1 写一个过滤类
from rest_framework.filters import BaseFilterBackend
class Myfilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
name = request.GET.get('name')
queryset = queryset.filter(name__contains=name)
return queryset
2 在视图类中配置
## 自己定义的
filter_backends = [Myfilter, ]
9、排序
9.1、配置排序类
from rest_framework.filters import OrderingFilter
class BookView(ViewSetMixin, ListAPIView):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
# 使用排序类
filter_backends = [OrderingFilter, ]
ordering_fields = ['price','name']
9.2、使用排序类
http://127.0.0.1:8000/book/?ordering=price # 升序
http://127.0.0.1:8000/book/?ordering=-price # 降序
http://127.0.0.1:8000/book/?ordering=-price,-name # 按多个关键字排序
10、自动生成接口文档
# 1、安装
pip3 install coreapi
# 2、使用步骤
1 在路由中
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('doc/', include_docs_urls(title='路飞项目接口文档')),
]
2 在配置文件中
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
3 在视图类中方法上加注释即可
4 如果是ModelViewSet
"""
list:
返回图书列表数据,通过Ordering字段排序
retrieve:
返回图书详情数据
latest:
返回最新的图书数据
read:
查询单个图书接口
"""
5 字段描述,写在models的help_text上
11、全局异常封装
查看无车承运人流程
12、分页器
12.1、普通分页PageNumberPagination
# 1、自定义一个类,继承PageNumberPagination
from rest_framework.pagination import PageNumberPagination
class MyPagination(PageNumberPagination):
# 重写四个类属性
page_size = 3 # 默认每页显示的条数
page_query_param = 'page' # 查询条件叫page ?page=3
page_size_query_param = 'size' # 每页显示的条数的查询条件 ?page=3&size=10
max_page_size = 5 # 每页显示的最大条数
# 2、在视图类中配置
from app01.page import MyPagination
class BookView(ViewSetMixin, ListAPIView):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
# 使用分页
pagination_class = MyPagination # 不能是列表
12.2、偏移分页LimitOffsetPagination
# 1、自定义一个类,继承LimitOffsetPagination
from rest_framework.pagination import LimitOffsetPagination
class MyLimitOffsetPagination(LimitOffsetPagination):
default_limit = 3
limit_query_param = 'limit'
offset_query_param = 'offset' # ?offset=2&limit=5 从第二条开始展示五条
max_limit = 5
# 2、在视图类中配置
from app01.page import MyPagination
class BookView(ViewSetMixin, ListAPIView):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
# 使用分页
pagination_class = MyLimitOffsetPagination # 不能是列表
12.3、游标分页
# 1、自定义一个类,继承CursorPagination
from rest_framework.pagination import CursorPagination
# 这种分页方式很特殊,只能选择上一页和下一页,不能指定跳转到某一页
# 优点是速度快,适合大数据量和app的分页
class MyCursorPagination(CursorPagination):
cursor_query_param = 'cursor' # 查询参数
page_size = 3 # 每页显示多少条
ordering = 'nid' # 按照什么字段排序
# 2、在视图类中配置
class BookView(ViewSetMixin, ListAPIView):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
pagination_class = MyCursorPagination # 不能是列表
12.4、三种分页方式对比
1、普通分页和游标分页可以从中间位置获取任意一页,游标分页只能获取上一页和下一页
2、普通分页和游标分页在获取某一页的时候,都需要从开始过滤到要取的页面数的数据
3、游标分页先对数据进行排序,内部维护了一个游标,游标只能选择往前走或者往后走,在取某一页的时候,不需要过滤之前的数据
13、rbac
# python用来做公司内部项目居多,人事系统,进销存,报销审批,自动化运维
-公司内部项目对执行效率要求不高(人少)
-对开发效率要求高(越快开发出越好,成本越低越好)
-知乎,豆瓣用python写的---》随着用户量增大---》切换语言
# 对外的权限比较简单:普通注册用户,VIP用户,超级VIP --》优酷,网易云音乐,百度网盘
# 公司内部系统:通常使用RBAC的权限控制
-公司内有部门(开发部,运维部,市场部,总裁办,人力资源部门)
-权限和角色(部门)绑定
-举个例子:发工资权限,招人权限,开发代码权限---》招人权限,发工资权限给人力资源--》开发代码权限给开发部门
# RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
# 权限赋予给角色(部门),而把角色(部门)又赋予用户
# 针对于公司内部项目,后台管理居多(运营在使用),使用rbac居多
# django-vue-admin:后端用drf,前端用vue,权限管理的 脚手架---》前后端分离
# django的admin---》混合的后台管理用的多---》基于django的admin二次开发
# simpleui:对django admin的美化
# django的admin自带rbac权限管理(表设计完成权限管理)---》6张表
-用户表
-角色表(组表,部门表)
-权限表
----------
-角色和权限多对多中间表
-用户和角色多对多中间表
-----django-admin中多了一张表-----
用户对权限多对多中间表
### 基于django的admin做二次开发,开发出公司内部的管理系统
-纯基于原生
-使用第三方美化:xadmin(早就不维护了,弃坑了),simpleui(国内的,主流),国外也有很多
# 在关系型数据库的关系中:只有三种---》本质只有一种:外键关系
-一对多
-多对多
-一对一
# 前后端分离,验证码如何实现?写验证码接口
14、jwt认证
14.1、jwt介绍
# cookie,session,token的区别?
https://www.cnblogs.com/liuqingzheng/articles/8990027.html
# 认证:session机制:需要在后端存储数据---》之前使用的django-session
# 如果登录用户很多,需要在后端存很多数据,频繁查询数据库,导致效率低---》能不能想一种方案,不在服务端存数据---》客户端存数据(数据安全)---》token认证机制
# Json web token (JWT),token是一种认证机制,用在web开发方向,叫jwt
# JWT的构成---》三段式---》每一段都使用base64编码
-典型的jwt串样子,通过. 分隔成三段:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
-第一段:头:声明类型,这里是jwt,声明加密的算法,公司信息等等。。。 ---》 目前作用不大
-第二段:荷载(payload):有效信息
-用户名,用户id,登陆时间,token失效时间。。。。
-第三段:签名(signature):通过 头+荷载 使用某种加密方式加密后得到的
# base64编码和解码---》只是编码和解码,不能叫加密
import base64
# 编码
# s = b'''{"name":"lqz","age":19}'''
# res = base64.b64encode(s)
# print(res) # eyJuYW1lIjoibHF6IiwiYWdlIjoxOX0=
# 解码
# s=b'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9'
# res=base64.b64decode(s)
# print(res)
## jwt的签发和认证---》保证安全
# 签发---》登陆过程---》如果没有第三方模块帮助我们做,我们就自己做
"""
1)用基本信息公司信息存储json字典,采用base64算法得到 头字符串
2)用关键信息存储json字典,采用base64算法得到 荷载字符串,过期时间,用户id,用户名
3)用头、体加密字符串通过加密算法+秘钥加密得到 签名字符串
拼接成token返回给前台
"""
# 认证---》访问需要登陆的接口
"""
1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间是安全信息,确保token没过期
3)再用 第一段 + 第二段 + 加密方式和秘钥得到一个加密串,与第三段 签名字符串 进行比较,通过后才能代表第二段校验得到的user对象就是合法的登录用户
"""
# 大部分的web框架都会有第三方模块支持----》如果没有需要自己写
django中有一个django-rest-framework-jwt,咱们讲的:
# https://github.com/jpadilla/django-rest-framework-jwt
django中有一个,django-rest-framework-simplejwt,咱们不讲,公司可能会用:
# https://github.com/jazzband/djangorestframework-simplejwt
# 区别
# https://blog.csdn.net/lady_killer9/article/details/103075076
14.2、jwt安装
pip3 install djangorestframework-jwt
14.3、签发token
# 1、配置路由
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/',obtain_jwt_token)
]
# 2、使用postman模拟发送POST请求,jwt会基于auth_user表签发token
http://127.0.0.1:8001/login/
# 3、得到token
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InRpYW55YW5sb25nIiwiZXhwIjoxNjUyMTQ5MzczLCJlbWFpbCI6InRpYW55YW5sb25nMjAwMEAxNjguY29tIn0.EsiJc8pwkrNeRjSOmjVhNJxJ-llf5avNdQY2ZnHoIQ0"
}
14.4、认证token
# 1、在视图类中配置
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
class BookView(ViewSetMixin, ListAPIView):
queryset = models.Book.objects.all()
serializer_class = serializer.BookSerializer
# jwt认证类
authentication_classes = [JSONWebTokenAuthentication, ]
# 加一个权限类才可以使用
permission_classes = [IsAuthenticated, ]
# 2、使用
http://127.0.0.1:8000/book/
访问这个url时,需要在请求头中添加 Authorization : jwt token字符串 这一组键值对
14.5、jwt定制前端返回格式
# 1、自定义函数==》函数返回什么,前端就能看到什么格式
utils.py
def jwt_response_payload_handler(token, user=None, request=None):
return {
'code': 100,
'msg': "登陆成功",
'token': token,
'username': user.username
}
# 2、在配置文件中配置
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
}
14.6、自定义表签发token
14.6.1、把认证逻辑写在视图类中
# 如果项目中的User表使用auth_user表,使用快速签发token即可
# 如果自定义User表,签发token,需要手动签发---》自己写
from app01.response import APIResponse
from rest_framework.viewsets import ViewSetMixin
from rest_framework.decorators import action
from app01 import models
from rest_framework_jwt.settings import api_settings
from rest_framework.generics import GenericAPIView
from app01 import serializer
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class UserInfoView(ViewSetMixin, GenericAPIView):
queryset = models.UserInfo.objects.all()
serializer_class = serializer.UserInfoSerializer
@action(methods=['POST'], detail=False)
def login(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = models.UserInfo.objects.filter(username=username, password=password).first()
if user:
# 用户存在,签发token
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return APIResponse(msg='登陆成功', payload=payload, token=token)
else:
return APIResponse(code=101, msg='用户名或密码错误')
14.6.2把认证逻辑写在序列化类中
# 序列化类
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = ['username','password']
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
user = models.UserInfo.objects.filter(username=username,password=password).first()
if user:
# 表示用户名和密码正确,签发token
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
self.context['token'] = token
self.context['payload'] = payload
return attrs
else:
raise ValidationError('用户名或密码错误')
# 视图类
class UserInfoView(ViewSetMixin, GenericAPIView):
queryset = models.UserInfo.objects.all()
serializer_class = serializer.UserInfoSerializer
@action(methods=['POST'], detail=False)
def login(self, request):
ser = serializer.UserInfoSerializer(data=request.data)
if ser.is_valid():
# context是视图类和序列化类沟通的桥梁
token = ser.context.get('token')
payload = ser.context.get('payload')
return APIResponse(msg='登录成功', payload=payload, token=token)
else:
return APIResponse(code=101, msg=ser.errors)
14.7、自定义认证token
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
import jwt
from rest_framework_jwt.settings import api_settings
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
# jwt自定义登录认证类
class JWTAuthentication(BaseAuthentication):
def authenticate(self, request):
# 先获取到token
jwt_value = request.META.get('HTTP_TOKEN')
if jwt_value is None:
raise AuthenticationFailed('token为空')
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = '签名已过期'
raise AuthenticationFailed(msg)
except jwt.DecodeError:
msg = '签名被篡改'
raise AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise AuthenticationFailed('未知错误')
user = models.UserInfo.objects.filter(pk=payload['user_id'])
return (user, jwt_value)