1 restful规范
2 认证
- 问题:有些api需要用户登录才能访问,有些无需登录就能访问
- 基本使用——解决:
- 创建两张表
- 用户登录(返回token,并保存到数据库)
2.1 用户登录并生成token
models.py
from django.db import modelsclass UserInfo(models.Model):user_type_choices = ((1, '普通用户'),(2, 'VIP'),(3, 'SVIP'),)user_type = models.IntegerField(choices=user_type_choices)username = models.CharField(max_length=32, unique=True) # 用户名唯一password = models.CharField(max_length=64)class UserToken(models.Model):'''保存用户token'''# 一个用户对应一条tokenuser = models.OneToOneField(to='UserInfo', on_delete=models.CASCADE)token = models.CharField(max_length=64)
执行数据库迁移
应用api的urls.py
from django.conf.urls import urlfrom api import viewsapp_name = 'api'urlpatterns = [url(r'^api/v1/auth/$',views.AuthView.as_view())]
views.py
from django.http import JsonResponsefrom rest_framework.views import APIViewfrom api import modelsdef md5(user):"""token = 时间戳+md5(用户名)"""import hashlibimport timectime = str(time.time())m = hashlib.md5(bytes(user,encoding='utf-8'))m.update(bytes(ctime,encoding='utf-8'))return m.hexdigest()class AuthView(APIView):# APIView继承Django的viewdef post(self,request,*args,**kwargs):ret = {'code':1000,'msg':None}try:user = request.POST.get('username')pwd = request.POST.get('password')obj = models.UserInfo.objects.filter(username=user,password=pwd).first()# 没有该用户if not obj:ret['code'] = 1001ret['msg'] = '用户名或密码错误'# 为登录用户创建tokentoken = md5(user)# 存在就更新,不存在就创建models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})ret['data'] = tokenexcept Exception as e:ret['code'] = 1002ret['msg'] = '请求异常'return JsonResponse(ret)
**def md5(user):**是生成用户token的方法,token = 时间戳 + md5(用户名),返回随机数,后期应该将这个方法写入工具类中AuthView:继承rest_framework的APIView- 首先获取post方法中的用户名与密码,与数据库中比对,不存在该用户返回错误信息;如果存在该用户,为该用户生成token【新用户生成token,已登录的用户更新token:
models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})】
测试方法,使用postman,携带请求体:
数据库中生成token
2.2 基于token的认证
局部配置(视图级别)
使用步骤:
- 先定义一个认证类Authtication
- 再在需要认证的试图类中使用:
在views.py中定义一个认证类
from rest_framework import exceptionsfrom rest_framework.authentication import BaseAuthenticationfrom api import modelsclass Authtication(BaseAuthentication):def authenticate(self,request):token = request.GET.get('token')token_obj = models.UserToken.objects.filter(token=token).first()if not token_obj:raise exceptions.AuthenticationFailed('用户认证失败')# 在rest framework内部,会将整个两个字段赋值给request,以供后续操作使用return (token_obj.user,token_obj)def authenticate_header(self, request):pass
- 每个认证类必须实现这两个方法
- 为了规范,每个认证类最好继承BaseAuthentication
def authenticate(self,request):自定义认证操作def authenticate_header(self, request):认证失败时给浏览器返回的响应头
在视图类OrderView中使用authentication_classes = [Authtication,]
class OrderView(APIView):"""订单相关业务"""authentication_classes = [Authtication,]def get(self,request,*args,**kwargs):# request.user# request.authret = {'code':1000,'msg':None}try:ret['data'] = ORDER_DICTexcept Exception as e:passreturn JsonResponse(ret)
这样在访问 http://127.0.0.1:8000/api/v1/order/,如果不带token参数,返回:用户认证失败
带token:
配置全局认证
- 在当前应用下新建一个utils包,然后新建一个auth文件,将上述views.py中的Authtication类,放入auth文件中 ```python from rest_framework import exceptions from rest_framework.authentication import BaseAuthentication from api import models
class Authtication(BaseAuthentication):
def authenticate(self,request):token = request.GET.get('token')token_obj = models.UserToken.objects.filter(token=token).first()if not token_obj:raise exceptions.AuthenticationFailed('用户认证失败')# 在rest framework内部,会将整个两个字段赋值给request,以供后续操作使用return (token_obj.user,token_obj)def authenticate_header(self, request):pass
- `token_obj.user`:返回的是该token对应的**user对象**2. 在**_settings.py_**```pythonREST_FRAMEWORK = {# rest framework全局认证'DEFAULT_AUTHENTICATION_CLASSES' : ['api.utils.auth.Authtication',],# 设置匿名用户"UNAUTHENTICATED_USER": None,"UNAUTHENTICATED_TOKEN": None,}
- 其中DEFAULT_AUTHENTICATION_CLASSES列表中填写认证类的路径

- 视图views.py中直接写视图类就行,不需要使用
authentication_classes = [Authtication,]

哪此时又产生一个问题,登录视图不需要认证,但此时已经配置全局认证,导致登录视图也需要认证,如果设置局部不需要认证?在不需要认真的视图中使用:
authentication_classes = []
如登录视图不需要认证
class AuthView(APIView):"""用户登录认证"""authentication_classes = []def post(self,request,*args,**kwargs):ret = {'code':1000,'msg':None}try:user = request.POST.get('username')pwd = request.POST.get('password')obj = models.UserInfo.objects.filter(username=user,password=pwd).first()# 没有该用户if not obj:ret['code'] = 1001ret['msg'] = '用户名或密码错误'token = md5(user)models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})ret['data'] = tokenexcept Exception as e:ret['code'] = 1002ret['msg'] = '请求异常'return JsonResponse(ret)
源码流程:https://www.yuque.com/u1046159/auxwxv/qrt58h#ZM9x6
2.3 内置认证类(一般自定义)

- 使用
- 创建类,必须继承BaseAuthentication(
**from **rest_framework.authentication **import **BaseAuthentication),必须实现以下这两个方法:其中只有authenticate方法自己写,另一个authenticate_header方法直接照抄,不用具体实现
- 创建类,必须继承BaseAuthentication(

返回值
● 抛出异常● None,当前认证函数不管,交给下一个认证函数● 返回一个元组(元素1,元素2),第一个参数赋值给request.user,第二个赋值给request.auth
- 全局使用:https://www.yuque.com/u1046159/lp12n4/ksnhgv#Wl2RA
- 源码流程:
- dispatch
- 封装request
- 获取定义的认证类(全局/局部),通过列表生成式创建对象
- initial
- perform_authentication
- request.user(内部循环….)
- perform_authentication
- 封装request
- dispatch
3 权限
为不同的用户赋予不同的权限
现在有一个需求:订单相关业务(只有SVIP可看)—OrderView
用户类型
user_type_choices = ((1, '普通用户'),(2, 'VIP'),(3, 'SVIP'),)
views.py/OrderView
class OrderView(APIView):"""订单相关业务(只有SVIP可看)"""def get(self,request,*args,**kwargs):if request.user.user_type !=3:return HttpResponse('无权访问,只有SVIP可看!')ret = {'code':1000,'msg':None}try:ret['data'] = ORDER_DICTexcept Exception as e:passreturn JsonResponse(ret)
当用户user_type不等于3时:
等于3时:
但此时视图与权限代码杂糅,有没有方法将权限判断代码抽离出来?有
3.1 局部配置
在工具包utils中新建python文件permission,编写权限函数MyPermission
api/utils/permission.py
class MyPermission(object):message = '只有SVIP才能访问!'def has_permission(self,request,view):if request.user.user_type !=3:return False # 无权访问# 有权访问return True
- message修改返回的消息
然后在需要权限配置的视图中使用
from api.utils.permission import MyPermissionclass OrderView(APIView):"""订单相关业务(只有SVIP可看)"""permission_classes = [MyPermission,]def get(self,request,*args,**kwargs):ret = {'code':1000,'msg':None}try:ret['data'] = ORDER_DICTexcept Exception as e:passreturn JsonResponse(ret)
- 引用包:
from api.utils.permission import MyPermission - 设置权限:
permission_classes = [MyPermission,]
可以写多个权限函数,然后根据实际需求,引用权限函数
3.2 全局配置
REST_FRAMEWORK = {"DEFAULT_PERMISSION_CLASSES":['api.utils.permission.MyPermission',],}
3.3 源码流程:https://www.yuque.com/u1046159/auxwxv/qrt58h#eEeGJ
4 访问频率控制(自定义)
需求:限制用户ip,60s内只能访问3次
思路:
- 定义一个字典,字典的键设置为IP,字典的值为该IP 的访问时间,为一个列表。将用户ip以及访问时间保存下来
- 如果用户首次访问,将用户当前ip以及访问时间加入字典中,并返回true(表示未达到访问限制)
- 如果用户非首次访问,从字典中获取用户历史记录(包含ip以及访问时间),判断history有值,并且把时间间隔大于60秒的都pop掉(history.pop()默认移除列表中最后一个元素,即最早访问时间);小于60的继续放在里面,最后对列表的长度进行整体判断
- 假如列表的长度大于3,说明60秒内访问的次数大于3次,我们就限制它的访问
4.1 局部配置
在utils包下面新建一个throttle文件,里面写VisitThrottle类
VISIT_RECORD = {}class VisitThrottle(object):"""访问频率限制:10s内只能访问3次"""def __init__(self):self.history = Nonedef allow_request(self, request, view):# 1.获取用户ipremote_addr = request.META.get('REMOTE_ADDR')# 2.当前时间ctime = time.time()# 首次访问if remote_addr not in VISIT_RECORD:VISIT_RECORD[remote_addr] = [ctime, ]return True# 非首次访问history = VISIT_RECORD.get(remote_addr)self.history = historywhile history and history[-1] < ctime - 10:history.pop()if len(history) < 3:history.insert(0, ctime)return Truedef wait(self):"""还需等多少秒才能访问"""# 当前时间ctime = time.time()return 60 - (ctime - self.history[-1])
VISIT_RECORD = {}:全局变量,用于保存用户ip以及访问时间,此处有一个问题。当程序重启时,全局变量被清空,但django rest framework默认将其放入缓存中remote_addr = request.META.get('REMOTE_ADDR'):rest framework中自带获取当前用户ip- 必须实现两个方法allow_request与wait;allow_request方法进行访问逻辑判断,wait方法当访问频率达到限制时,提示还需等多少秒才能访问
- 视图类中使用
throttle_classes = [VisitThrottle]引用访问控制类
视图类中引用
from api.utils.throttle import VisitThrottleclass AuthView(APIView):throttle_classes = [VisitThrottle]def post(self, request, *args, **kwargs):#其他
下面是访问被限制
4.2 全局配置
在utils包下面新建一个throttle文件,里面写VisitThrottle类,同上
settings.py
REST_FRAMEWORK = {"DEFAULT_THROTTLE_CLASSES":['api.utils.throttle.VisitThrottle'],}
4.3 源码流程 https://www.yuque.com/u1046159/auxwxv/qrt58h#xitgy
4.4 内置类实现访问频率控制
自定义类继承BaseThrottle
from rest_framework.throttling import BaseThrottle

代码改造
from rest_framework.throttling import BaseThrottleVISIT_RECORD = {}class VisitThrottle(BaseThrottle):"""访问频率限制:10s内只能访问3次"""def __init__(self):self.history = Nonedef allow_request(self, request, view):# 1.获取用户ipremote_addr = self.get_ident(request)# 2.当前时间ctime = time.time()# 首次访问if remote_addr not in VISIT_RECORD:VISIT_RECORD[remote_addr] = [ctime, ]return True# 非首次访问history = VISIT_RECORD.get(remote_addr)self.history = historywhile history and history[-1] < ctime - 10:history.pop()if len(history) < 3:history.insert(0, ctime)return Truedef wait(self):"""还需等多少秒才能访问"""# 当前时间ctime = time.time()return 60 - (ctime - self.history[-1])
- 将
remote_addr = request.META.get('REMOTE_ADDR')改为remote_addr = self.get_ident(request)
4.5 更简单的实现方法
以上都是自定义类,rest framework里面自带一个内置的SimpleRateThrottle
控制类
class VisitThrottle(SimpleRateThrottle):# keyscope = 'visit'def get_cache_key(self, request, view):# 返回用户ipreturn self.get_ident(request)
- 定义一个类,继承SimpleRateThrottle
- 需要给一个scope字段,当做唯一标识key来使用
- get_cache_key方法返回用户ip
- 在settings.py中配置
```python
REST_FRAMEWORK = {
“DEFAULT_THROTTLE_CLASSES”:[‘api.utils.throttle.VisitThrottle’],
“DEFAULT_THROTTLE_RATES”:{
} }"visit":'3/m'
- 配置当前控制类的位置:DEFAULT_THROTTLE_CLASSES- 设置访问频率:DEFAULT_THROTTLE_RATES,**visit**是控制类中的scope字段(唯一标识),**3/m**表示一分钟只能访问三次---<a name="cGjOM"></a>## 4.6 补充以上都是对匿名用户进行访问控制,那想要同时对匿名用户和登录用户就进行访问控制,应该如何实现?```pythonclass VisitThrottle(SimpleRateThrottle):# keyscope = 'visit'def get_cache_key(self, request, view):# 返回用户ipreturn self.get_ident(request)class UserThrottle(SimpleRateThrottle):scope = 'user'def get_cache_key(self, request, view):return request.user.username
配置
REST_FRAMEWORK = {# 登录用户控制访问全局配置"DEFAULT_THROTTLE_CLASSES":['api.utils.throttle.UserThrottle'],"DEFAULT_THROTTLE_RATES":{"visit":'3/m',"user":'10/m',}}
views.py
class AuthView(APIView):"""用户登录认证"""authentication_classes = []permission_classes = []throttle_classes = [VisitThrottle]def post(self, request, *args, **kwargs):# ....
5 版本
5.1 url中通过GET传参(一般不用)
自定义写法
class ParamVersion():def determine_version(self, request, *args, **kwargs):version = request.GET.get('version')return version
然后在试图类中引用
class UsersView(APIView):versioning_class = ParamVersiondef get(self,request,*args,**kwargs):print(request.version)return HttpResponse('用户列表')
versioning_class = ParamVersion使用内置类
引用:from rest_framework.versioning import QueryParameterVersioningversioning_class = QueryParameterVersioning```python from rest_framework.versioning import QueryParameterVersioning
class UsersView(APIView):
versioning_class = QueryParameterVersioningdef get(self,request,*args,**kwargs):print(request.version)return HttpResponse('用户列表')
`QueryParameterVersioning`中可以从配置文件中配置三个参数<br />```pythondefault_version = api_settings.DEFAULT_VERSIONallowed_versions = api_settings.ALLOWED_VERSIONSversion_param = api_settings.VERSION_PARAM
settings.py
REST_FRAMEWORK = {"DEFAULT_VERSION":"v1","ALLOWED_VERSIONS":['v1,v2'],"VERSION_PARAM":"version",}
5.2 在url路径中传参(推荐)
局部配置
引用
from rest_framework.versioning import URLPathVersioning
urls.py配置
from django.urls import re_pathfrom api2 import viewsapp_name = 'api2'urlpatterns = [# path('api2/users/',views.UsersView.as_view()),re_path(r'^api2/(?P<version>[v1|v2]+)/users/$',views.UsersView.as_view()),]
- 上述的url路径中使用正则表达式
views.py
from rest_framework.versioning import URLPathVersioningclass UsersView(APIView):versioning_class = URLPathVersioningdef get(self,request,*args,**kwargs):print(request.version)return HttpResponse('用户列表')


全局配置
settings.py
REST_FRAMEWORK = {"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",}
然后视图类中不需要再设置versioning_class = URLPathVersioning
from rest_framework.versioning import URLPathVersioningclass UsersView(APIView):def get(self,request,*args,**kwargs):print(request.version)return HttpResponse('用户列表')
5.3 反向生成url
使用django rest framework
request.versioning_scheme.reverse(viewname='api2:user',request=request)
- 当有多个应用,注意viewname写法
views.py
urls.py
from api2 import viewsapp_name = 'api2'urlpatterns = [re_path(r'^api2/(?P<version>[v1|v2]+)/users/$',views.UsersView.as_view(),name="user"),]
访问:http://127.0.0.1:8000/api2/v1/users/,后台打印结果:
http://127.0.0.1:8000/api2/v1/users/
使用django自带
u2 = reverse(viewname='api2:user',kwargs={'version':1})
- 需要传参:版本号
6 解析器
6.1 django自带的解析器
django:request.POST / request.body
请求头要求:
Content-Type: "application/x-www-form-urlencoded"
ps:如果请求头中的Content-Type: “application/x-www-form-urlencoded” ,request.POST中才有值(去request.body中解析数据)
数据格式要求:
name=alex&age=18
如:
- form表单提交
- ajax提交
6.2 rest framework解析器使用
局部使用
class ParserView(APIView):from rest_framework.parsers import JSONParserparser_classes = [JSONParser,]def post(self, request, *args, **kwargs):"""允许用户发送json格式"""print(request.data)return HttpResponse('parser')
- JSONParser表示只能解析application/json 请求头
- FormParser: 表示只能解析 application/x-www-form-urlencoded 请求头
- 最常用的就是json,前后端交互一般使用json格式
- 用的时候才解析,即上述代码使用
request.data调用:- 使用流程:(1)获取用户请求头(2)获取用户请求体(3)根据用户请头和
parser_classes = []中支持的请求头进行比较,谁符合交给谁处理(4)JSONParser对象处理请求体(5)处理后赋值给request.data
- 使用流程:(1)获取用户请求头(2)获取用户请求体(3)根据用户请头和
此时访问:
后端能获取到前端传递的json格式,如果不是json格式就报错:
全局配置(一般使用全局配置)
在settings.py中配置
REST_FRAMEWORK = {"DEFAULT_PARSER_CLASSES": ["rest_framework.parsers.JSONParser", "rest_framework.parsers.FormParser"]}
后端返回的格式
除了上述解析器:还有其他解析器:https://www.cnblogs.com/shenjianping/p/11509858.html,比如文件上传FileUploadParser,全局配置后,若想要某个视图函数支持文件解析,需要局部配置:
parser_classes = [FileUploadParser,]
6.3 rest framework解析器原理
https://www.cnblogs.com/wdliu/p/9128990.html
总结
- 使用的时候进行全局配置
- 使用request.data获取值
本质:
- 请求头
- 状态码
- 请求方法
若面试说到本质可能会问上述a、b、c
源码流程
7 序列化
比如当进行数据库查询的时候,django查询的结果是一个对象QuerySet
class RoleView(APIView):def get(self, request, *args, **kwargs):roles = models.Role.objects.all()print(roles)return HttpResponse(roles)
我们一般需要转化为json对象返回给前端
转换方法一般有以下方式:https://blog.csdn.net/shirukai/article/details/81086242
django自带的serializers序列化model
def edit(request):# ajax传值m_number = request.GET.get('m_number')inventory= Inventory.objects.filter(m_number=m_number)json_data = serializers.serialize('json', inventory)return HttpResponse(inventory, content_type="application/json")
方式一
首先定义一个序列化serializers类
from rest_framework import serializersclass RoleSerializer(serializers.Serializer):id = serializers.IntegerField()titile = serializers.CharField()
然后在查询中使用
class RoleView(APIView):def get(self, request, *args, **kwargs):roles = models.Role.objects.all()ser = RoleSerializer(instance=roles,many=True)ret = json.dumps(ser.data,ensure_ascii=False)return HttpResponse(ret)
- ser.data就是序列化的结果
返回结果:
[{"id": 1, "titile": "老师"}, {"id": 2, "titile": "学生"}, {"id": 3, "titile": "护士"}]
但是上述方法略显麻烦:定义模型类后,还要定义一个RoleSerializer,重复写模型类中字段id、titile
方式二
创建一个名为 api2/serializers.py的文件,来用作我们的数据表示。
api2/serializers.py
from rest_framework import serializersfrom api2.models import Roleclass RoleSerializer(serializers.ModelSerializer):class Meta:model = Rolefields = '__all__'
views.py
from api2.serializers import RoleSerializerclass RoleView(APIView):def get(self, request, *args, **kwargs):roles = models.Role.objects.all()ser = RoleSerializer(instance=roles,many=True)ret = json.dumps(ser.data,ensure_ascii=False)print(ret)return HttpResponse(ret)
!!!怎么感觉还不如直接使用django自带的序列化呢!!!!
以上返回的字段可以自定义显示!
序列化深度控制
- depth建议取值:0~4

自定义验证字段
8 分页
未分页前:
serializers.py
from rest_framework import serializersfrom inventory.models import Inventoryclass InventorySerializer(serializers.ModelSerializer):class Meta:model = Inventoryfields = '__all__'
views.py
from inventory.serializers import InventorySerializerclass InventoryView(APIView):def get(self, request, *args, **kwargs):inventories = models.Inventory.objects.all()serializers = InventorySerializer(instance=inventories, many=True)ret = json.dumps(serializers.data,ensure_ascii=False)return HttpResponse(ret)
8.1 看第n页,每页显示n条数据
8.2 在某个位置,向后查看n条数据
8.3 加密分页,上一个和下一页
9 视图
9.1 GenericAPIView(一般不用)
9.2 GenericViewSet
9.3 ModelViewSet
9.4 总结
- 完成基本的增删改查 ModelViewSet
- 增删 CreateModelMixin,DestroyModelMixin
- 复杂的逻辑:GenericViewSet或者APIV
