1 restful规范
2 认证
- 问题:有些api需要用户登录才能访问,有些无需登录就能访问
- 基本使用——解决:
- 创建两张表
- 用户登录(返回token,并保存到数据库)
2.1 用户登录并生成token
models.py
from django.db import models
class 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'''
# 一个用户对应一条token
user = models.OneToOneField(to='UserInfo', on_delete=models.CASCADE)
token = models.CharField(max_length=64)
执行数据库迁移
应用api的urls.py
from django.conf.urls import url
from api import views
app_name = 'api'
urlpatterns = [
url(r'^api/v1/auth/$',views.AuthView.as_view())
]
views.py
from django.http import JsonResponse
from rest_framework.views import APIView
from api import models
def md5(user):
"""token = 时间戳+md5(用户名)"""
import hashlib
import time
ctime = 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的view
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'] = 1001
ret['msg'] = '用户名或密码错误'
# 为登录用户创建token
token = md5(user)
# 存在就更新,不存在就创建
models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
ret['data'] = token
except Exception as e:
ret['code'] = 1002
ret['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 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
- 每个认证类必须实现这两个方法
- 为了规范,每个认证类最好继承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.auth
ret = {'code':1000,'msg':None}
try:
ret['data'] = ORDER_DICT
except Exception as e:
pass
return 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对象**
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1283987/1638863406451-054ad3cc-796f-4d77-8af9-b0b470a00527.png#clientId=ue2255359-2142-4&from=paste&height=288&id=u1ac922c3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=576&originWidth=1512&originalType=binary&ratio=1&size=100917&status=done&style=none&taskId=ua6359443-e11e-4d28-ab86-028de642976&width=756)
2. 在**_settings.py_**
```python
REST_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'] = 1001
ret['msg'] = '用户名或密码错误'
token = md5(user)
models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
ret['data'] = token
except Exception as e:
ret['code'] = 1002
ret['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_DICT
except Exception as e:
pass
return 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 MyPermission
class OrderView(APIView):
"""
订单相关业务(只有SVIP可看)
"""
permission_classes = [MyPermission,]
def get(self,request,*args,**kwargs):
ret = {'code':1000,'msg':None}
try:
ret['data'] = ORDER_DICT
except Exception as e:
pass
return 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 = None
def allow_request(self, request, view):
# 1.获取用户ip
remote_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 = history
while history and history[-1] < ctime - 10:
history.pop()
if len(history) < 3:
history.insert(0, ctime)
return True
def 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 VisitThrottle
class 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 BaseThrottle
VISIT_RECORD = {}
class VisitThrottle(BaseThrottle):
"""访问频率限制:10s内只能访问3次"""
def __init__(self):
self.history = None
def allow_request(self, request, view):
# 1.获取用户ip
remote_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 = history
while history and history[-1] < ctime - 10:
history.pop()
if len(history) < 3:
history.insert(0, ctime)
return True
def 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):
# key
scope = 'visit'
def get_cache_key(self, request, view):
# 返回用户ip
return 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 补充
以上都是对匿名用户进行访问控制,那想要同时对匿名用户和登录用户就进行访问控制,应该如何实现?
```python
class VisitThrottle(SimpleRateThrottle):
# key
scope = 'visit'
def get_cache_key(self, request, view):
# 返回用户ip
return 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 = ParamVersion
def get(self,request,*args,**kwargs):
print(request.version)
return HttpResponse('用户列表')
versioning_class = ParamVersion
使用内置类
引用:from rest_framework.versioning import QueryParameterVersioning
versioning_class = QueryParameterVersioning
```python from rest_framework.versioning import QueryParameterVersioning
class UsersView(APIView):
versioning_class = QueryParameterVersioning
def get(self,request,*args,**kwargs):
print(request.version)
return HttpResponse('用户列表')
`QueryParameterVersioning`中可以从配置文件中配置三个参数<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1283987/1639013363646-39b78c13-0de4-4d55-a0af-7ffadb2f1f13.png#clientId=ub05fc64e-40fe-4&from=paste&height=114&id=u0e34d547&margin=%5Bobject%20Object%5D&name=image.png&originHeight=228&originWidth=1227&originalType=binary&ratio=1&size=36062&status=done&style=none&taskId=u683d9a38-9b4f-414a-b628-07259e17c41&width=613.5)
```python
default_version = api_settings.DEFAULT_VERSION
allowed_versions = api_settings.ALLOWED_VERSIONS
version_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_path
from api2 import views
app_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 URLPathVersioning
class UsersView(APIView):
versioning_class = URLPathVersioning
def 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 URLPathVersioning
class 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 views
app_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 JSONParser
parser_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 serializers
class 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 serializers
from api2.models import Role
class RoleSerializer(serializers.ModelSerializer):
class Meta:
model = Role
fields = '__all__'
views.py
from api2.serializers import RoleSerializer
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)
print(ret)
return HttpResponse(ret)
!!!怎么感觉还不如直接使用django自带的序列化呢!!!!
以上返回的字段可以自定义显示!
序列化深度控制
- depth建议取值:0~4
自定义验证字段
8 分页
未分页前:
serializers.py
from rest_framework import serializers
from inventory.models import Inventory
class InventorySerializer(serializers.ModelSerializer):
class Meta:
model = Inventory
fields = '__all__'
views.py
from inventory.serializers import InventorySerializer
class 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