参考: rest-framework框架基础组件, 基于Token的WEB后台认证机制,rest-framework之序列化组件
restful组件-1
一、序列化
对比:
自己写的 for循环处理 (相对麻烦)
django自带序列化工具 (不可控, 字段太多, 会造成网络拥堵问题)
restful工具可控,但配置比较多, 后续会轻松些
1.1、django自带serializers
- 使用说明
# 1、导入 from django.core import serializers
# 2、实例化 res = serializers.serialize('json', queryset)
- 例子
from app01.models import Authers
from django.core import serializers
from rest_framework.views import APIView
from django.http.response import JsonResponse
class Mydjangoser(APIView):
def get(self, reuqest):
auther = Authers.objects.all()
ser = serializers.serialize('json', auther)
# safe=False 如果不设置这个 当字典中还有列表时返回就会报错
return JsonResponse(ser, safe=False)
# 访问,因为序列化已经是字符串了,所以看的结果就是ascii
"[{\"model\": \"app01.authers\", \"pk\": 1, \"fields\": {\"name\": \"xiong\", \"pwd\": \"12\"}}, {\"model\": \"app01.authers\", \"pk\": 2, \"fields\": {\"name\": \"we\", \"pwd\": \"321\"}}]"
# 可以直接返回 HttpResponse return HttpResponse(ser)
[{"model": "app01.authers", "pk": 1, "fields": {"name": "xiong", "pwd": "12"}}, {"model": "app01.authers", "pk": 2,
"fields": {"name": "we", "pwd": "321"}}]
# 字典中的字段无用的就太多了会给网络带来拥堵
1.2、drf的Serializer
- 使用说明
# 1、先在 setting.py中注册 restful
# 2、导入 from rest_framework import serializers
# 3、引用 res = AutherSerializer(instance=queryset对象, many=True)
# 4、返回数据 res.data
-
继承serializers.Serializers
# 想序列化哪一个模型就将字段序列化 class AutherSerializer(serializers.Serializer): name = serializers.CharField() age = serializers.CharField()
-
类使用
class Mydjangoser(APIView):
def get(self, reuqest):
auther = Authers.objects.all()
# 初始化方法 def __init__(self, instance=None, data=empty, **kwargs)
# many 如果传true说明queryset对象中有多条, kwargs.pop('many', None)
# many 传false说明就只有一条字典
res = AutherSerializer(instance=auther, many=True)
# res.data 获取序列化的数据
return JsonResponse(res.data, safe=False)
# 返回结果 [{"name": "xiong","pwd": "12"},{"name": "we","pwd": "321"}]
1.2.1、Serializer-source
# 模型:
class Userinfo(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
pwd = models.CharField(max_length=64)
userdetail = models.OneToOneField(to="Userdetail", to_field="id", on_delete=models.CASCADE)
class Userdetail(models.Model):
id = models.AutoField(primary_key=True)
phone = models.IntegerField(max_length=13)
email = models.EmailField()
# 视图
class MyUser(APIView):
def get(self, request, *args, **kwargs):
user_list = Userinfo.objects.all()
users = UserSerializers(instance=user_list, many=True)
return JsonResponse(users.data, safe=False)
-
正常
# 序列化 from rest_framework import serializers class UserSerializers(serializers.Serializer): name = serializers.CharField() pwd = serializers.CharField() userdetail = serializers.CharField() [{"name": "xiong","pwd": "123","userdetail": "Userdetail object (1)"}, {"name": "hello","pwd": "321","userdetail": "Userdetail object (2)"}]
-
**source属性-1 修改名称 **
# 将name修改为hello class UserSerializers(serializers.Serializer): hello = serializers.CharField(source="name") pwd = serializers.CharField() userdetail = serializers.CharField() [{"hello": "xiong","pwd": "123","userdetail": "Userdetail object (1)"}, {"hello": "hello","pwd": "321","userdetail": "Userdetail object (2)"}]
-
source属性-2 一对一
class UserSerializers(serializers.Serializer):
hello = serializers.CharField(source="name")
pwd = serializers.CharField()
userdetail = serializers.CharField(source="userdetail.email")
[{"hello": "xiong","pwd": "123","userdetail": "22@qq.com"},
{"hello": "hello","pwd": "321","userdetail": "33@qq.com"}]
1.2.2、SerializerMethodField
- 正常写法
class UserSerializers(serializers.Serializer):
hello = serializers.CharField(source="name")
pwd = serializers.CharField()
userdetail = serializers.SerializerMethodField()
# 这里的obj 其实就是 userinfo 的对象
def get_userdetail(self, obj):
return obj.userdetail.email
[{"hello": "xiong","pwd": "123","userdetail": "22@qq.com"},
{"hello": "hello","pwd": "321","userdetail": "33@qq.com"}]
- 返回关连表的全部, 像这样就需要一个一个的拼接比较麻烦
class UserSerializers(serializers.Serializer):
hello = serializers.CharField(source="name")
pwd = serializers.CharField()
userdetail = serializers.SerializerMethodField()
def get_userdetail(self, obj):
# obj就是一个一个的对象, 类似于已经 .first了
return {"email": obj.userdetail.email, "phone": obj.userdetail.phone}
- 多表关连 - 写法1
# 单表没有.all属性,切记切记,
class UserSerializers(serializers.Serializer):
name = serializers.CharField()
user = serializers.CharField()
publish = serializers.SerializerMethodField()
def get_publish(self, obj):
xx = obj.publish.all()
return [{"name": x.name} for x in xx]
- 多表关连- 写法2
class PublishSerializers(serializers.Serializer):
name = serializers.CharField()
class UserSerializers(serializers.Serializer):
name = serializers.CharField()
user = serializers.CharField()
publish = serializers.SerializerMethodField()
def get_publish(self, obj):
xx = obj.publish.all()
xy = PublishSerializers(instance=xx, many=True)
return xy.data
1.2.3、ModelSerializer
- 获取全部属性的方法
from app01.models import *
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
# 元属性, 固定格式如下
class Meta:
model = Book
fields = "__all__"
# 返回结果为 [{"id": 1, "name": "linux", "user": 1, "publish": [ 1 ]},.....]
- 只显示某个值(类型于白名单)
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ["name", "user"]
# 返回结果: [{"name": "linux", "user": 1},.....]
- 不显示某些值(黑名单)
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
exclude = ["id"]
user = serializers.CharField(source="user.name")
# 返回结果为 [{"name": "linux", "user": 1, "publish": [ 1 ]},.....]
-
depth 深度
class BookSerializer(serializers.ModelSerializer): class Meta: model = Book exclude = ["id"] depth = 3 # 官方建议是10层,但如果表的字段很多会造成性能问题,建议2-3层
-
配合source使用
class BookSerializer(serializers.ModelSerializer): class Meta: model = Book # fields = "__all__" fields = ["name", "user"] user = serializers.CharField(source="user.name") # 返回结果: [{"name": "linux", "user": "xiong"},.....]
-
配合 ModelSerializer
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
exclude = ["id"]
user = serializers.CharField(source="user.name")
publish = serializers.SerializerMethodField()
def get_publish(self, publish_obj):
publish_all = publish_obj.publish.all()
print(publish_all) # <QuerySet [<Publish: Publish object (1)>]>
return PublishSerializers(publish_all, many=True).data
# 返回结果
[{"user": "xiong", "publish": [{"name": " 上海出版"}],"name": "linux"},...]
1.2.4、HyperlinkedIdentityField
鸡肋,并没有什么用,URL前台拼就行
-
序列化
# class BookSerializer(serializers.Serializer): # name = serializers.CharField() # publish = serializers.HyperlinkedIdentityField(view_name="publish", # lookup_url_kwarg="pk", # lookup_field="publish") class BookSerializer(serializers.ModelSerializer): class Meta: model = Book exclude = ["id"] publish = serializers.HyperlinkedIdentityField(view_name="publish", lookup_url_kwarg="pk", lookup_field="pk") # view_name: 视图别名, lookup_url_kwarg: url后传的参数, # lookup_field: 本表的字段ID # lookup_url_kwarg 相当于是key, lookup_field相当是value
-
路由函数
from django.urls import re_path
from app01.views import *
urlpatterns = [
re_path("publish/(?P<pk>\d+)", Mypublish.as_view(), name="publish"),
]
-
视图
class Mypublish(APIView): def get(self, request, pk): response = {"status": 1000, "msg": None} res = Publish.objects.filter(pk=pk).first() if res: res_ser = PublishSerializers(instance=res, many=False) response["msg"] = res_ser.data else: response["status"] = 1002 response["msg"] = "没有这个字段" return JsonResponse(response, safe=False)
-
error_messages
# 错误字段显示为中文
name = serializers.CharField(error_messages={"required": "必填字段"})
1.3、反序列化
保存和修改, 必须是继承了ModelSerializer的类才能使用
- 保存
class Mybook(APIView):
def get(self, request, *args, **kwargs):
book_list = Book.objects.all()
books = BookSerializer(instance=book_list, many=True, context={'request': request})
return JsonResponse(books.data, safe=False)
def post(self, request, *args, **kwargs):
response = {"status": 1000, "msg": None}
data = request.data
# 数据检验
ret = BookSerializer(data=data, many=False)
# 检验数据是否正常
if ret.is_valid():
# 如果 是多对多表,这里需要做一下检验,比如是不是列表,是否为空
xx = ret.save() # 保存类型: <class 'app01.models.Book'>
publists = data.get("publish")
if publists:
if isinstance(publists, list):
for p_num in publists:
p = Publish.objects.filter(pk=p_num).first()
xx.publish.add(p)
else:
p = Publish.objects.filter(pk=data.get("publish")).first()
xx.publish.add(p)
response["msg"] = "添加成功"
else:
response["status"] = 1002
response["msg"] = "没有这个字段"
return JsonResponse(response, safe=False)
-
修改
class Mybookdetail(APIView): def get(self, request, pk, *args, **kwargs): response = {"status": 1000, "msg": None} book_detail = Book.objects.filter(pk=pk).first() if book_detail: ret = BookSerializer(instance=book_detail, many=False) response["msg"] = ret.data return JsonResponse(response, safe=False) def put(self, request, pk, *args, **kwargs): response = {"status": 1000, "msg": None} book_detail = Book.objects.filter(pk=pk).first() if book_detail: # BookSerializer(data=book_detail, ) ret = BookSerializer(data=request.data, instance=book_detail) data = request.data # ==================== 与添加一样 # 检验数据是否正常 if ret.is_valid(): # 如果 是多对多表,这里需要做一下检验,比如是不是列表,是否为空 xx = ret.save() # 保存类型: <class 'app01.models.Book'> publists = data.get("publish") if publists: if isinstance(publists, list): for p_num in publists: p = Publish.objects.filter(pk=p_num).first() xx.publish.add(p) else: p = Publish.objects.filter(pk=publists).first() xx.publish.add(p) response["msg"] = "添加成功" else: response["status"] = 1001 response["msg"] = ret.errors return JsonResponse(response, safe=False)
-
总结
# 添加: 获取数据并检验,最后给它保存
ret = BookSerializer(data=data, many=False)
if ret.is_valid(): ret.save()
# 修改: 与添加不同的是, 获取数据前,需要先前修改的数据取出,然后在做检验最后保存
ret = BookSerializer(data=request.data, instance=book_detail)
if ret.is_valid(): ret.save()
1.4、钩子函数
1.4.1、源码分析
if ret.is_valid(): ret.save()
# 直接从属性方法这进入到检验中, 需要注意的是 self.的都需要从第一级重新开始找, MRO
- 检验方法is_valid
def is_valid(self, raise_exception=False):
if not hasattr(self, '_validated_data'):
try:
# 这里是获取到全局的属性值
self._validated_data = self.run_validation(self.initial_data)
.....
if self._errors and raise_exception:
raise ValidationError(self.errors)
return not bool(self._errors)
-
self.run_validation
self.run_validation, 已知self是 BookSerializer,从父属性开始找 # 找到 Serializer/run_validation def run_validation(self, data=empty): # 这里返回的是每个参数的值 value = self.to_internal_value(data) try: self.run_validators(value) value = self.validate(value) assert value is not None, '.validate() should return the validated data' except (ValidationError, DjangoValidationError) as exc: raise ValidationError(detail=as_serializer_error(exc)) return value
-
value = self.validate(value), 全局钩子函数
# value = self.validate(value), 获取到这个属性,就是全局钩子函数,我们可以使用派生修改它 def validate(self, attrs): return attrs # 返回的结果就是每个序列化的值, 全局钩子函数
-
self.to_internal_value(data)
# 这里返回的是每个参数的值
value = self.to_internal_value(data)
# 局部钩子函数
def to_internal_value(self, data):
# filds 就是 user = serializers.CharField, 将其变为字典
for field in fields:
# 'validate_' + field.field_name 拼接的属性,这里就是局部的钩子函数
# 如果没有就是 none, 使用的restful自带的检验
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
....
return ret
1.4.2、钩子
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
exclude = ["id"]
# 局部钩子
def validate_name(self, data):
if len(data) >=10:
raise ValidationError("长度不能大于10")
return data
# 错误显示: "name": [ "长度不能大于10" ]
# 全局钩子
def validate(self, data):
print("全局钩子")
return data # 必须要返回data
# assert value is not None, '.validate() should return the validated data'
二、认证
2.1、认证源码分析
-
从url开始
path("atest/", Atest.as_view()),
-
进入到 ApiView/as_view()函数中
@classmethod
def as_view(cls, **initkwargs):
# 继承了父类的 as_view方法
view = super().as_view(**initkwargs)
view.cls = cls
view.initkwargs = initkwargs
return csrf_exempt(view)
-
此时是父类的 View/as_view
@classonlymethod def as_view(cls, **initkwargs): ...... def view(request, *args, **kwargs): ..... # 注意,这里的self是 函数也就是Atest, 又从 ApiView重新开始找 return self.dispatch(request, *args, **kwargs)
-
进入到 ApiView/dispatch中
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
# 在这里重新封装了 request,进入查看
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
......
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
-
request = self.initialize_request(request, *args, **kwargs)
def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ parser_context = self.get_parser_context(request) # 返回的是重新封装好的Request return Request( request, parsers=self.get_parsers(), # authenticators, 认证模块 authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context )
-
authenticators=self.get_authenticators(),
# 也就是说 authenticators 返回的是一个个的列表套对象 [对象内存地址1,2,3 ]
def get_authenticators(self):
return [auth() for auth in self.authentication_classes]
# 默认的配置文件
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
-
self.initial(request, *args, **kwargs)
def initial(self, request, *args, **kwargs): """ Runs anything that needs to occur prior to calling the method handler. """ self.format_kwarg = self.get_format_suffix(**kwargs) # Perform content negotiation and store the accepted info on the request neg = self.perform_content_negotiation(request) request.accepted_renderer, request.accepted_media_type = neg # Determine the API version, if versioning is in use. version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted self.perform_authentication(request) self.check_permissions(request) self.check_throttles(request)
-
self.perform_authentication(request)
def perform_authentication(self, request): # 最终返回的就是一个request.user request.user
-
继续往下看 request.user属性
from rest_framework.request import Request
# 进入Request中找着 user方法
@property
def user(self):
if not hasattr(self, '_user'):
# 错误处理
with wrap_attributeerrors():
self._authenticate()
return self._user
-
进入认证
self._authenticate()
# 需要注意的是def user(self)的这个self, 其实是request参数 def _authenticate(self): ''' 循环 self.authenticators其实就是 request.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: self._not_authenticated() raise ....
-
user_auth_tuple = authenticator.authenticate(self)
def authenticate(self, request):
return (self.force_user, self.force_token)
2.2、入门示例
- 模型
class Users(models.Model):
username = models.CharField(max_length=32)
pwd = models.CharField(max_length=12)
class Token(models.Model):
token = models.CharField(max_length=64)
user = models.OneToOneField(to=Users, to_field="id", on_delete=models.CASCADE)
- 登陆
class Login(APIView):
def post(self, request, *args, **kwargs):
response = {"status": 1000, "msg": "登陆成功"}
username = request.data.get("uesrname")
pwd = request.data.get("pwd")
print(request.data)
ret = Users.objects.filter(username=username, pwd=pwd).first()
if not ret:
token = uuid.uuid4()
Token.objects.create(token=token, user=ret)
response["token"] = token
else:
response["status"] = 1001
response["msg"] = "请先登陆"
return JsonResponse(response, safe=False)
- 认证
class Auth:
# 源码中的这一个 user_auth_tuple = authenticator.authenticate(self)
# self就是需要传递的参数
def authenticate(self, request):
token = request.query_params.get("token")
ret = Token.objects.filter(token=token).first()
if not ret:
raise exceptions.APIException("请先登陆")
return ret.user, ret
- 认证的模型
# 在需要被定义认证的类下中写, 需要注意的是,这里最多只能写三个,
# 并且如果AUth有返回值,后面的认证组件就不会在执行
class Mybookdetail(APIView):
# 在需要认证的模型上添加
authentication_classes = [Auth, ]
2.3、使用
2.3.1、全局使用
# # settings.py文件中定义, 所有的组件都可以放在这里
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ["app01.rest_auth.Auth"]
}
# 需要注意的是,如果定义了全局,那么直接使用login也会受到限制
2.3.2、局部使用\禁用
# 局部禁用
class Mybookdetail(APIView):
authentication_classes = [] # 直接设置为空就行
# 局部使用
class Mybookdetail(APIView):
authentication_classes = [PostAuth, ]
2.3.4、BaseAuthentication
# 继承基础组件, 也可以不继承
from rest_framework.authentication import BaseAuthentication
# 这个基础组件其实啥也没写, 关键在于就是继承authenticate_header头部类
class BaseAuthentication(object):
def authenticate(self, request):
raise NotImplementedError(".authenticate() must be overridden.")
def authenticate_header(self, request):
pass
2.4、忽略GET
# 1、先配置全局使用
# 2、允许GET属性查看, 其它的方法都需要登陆才能操作
class PostAuth(BaseAuthentication):
def authenticate(self, request):
http_method_names = ['post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
print(request.method)
if request.method.lower() in http_method_names:
token = request.query_params.get("token")
ret = Token.objects.filter(token=token).first()
if not ret:
raise exceptions.APIException("请先登陆")
return ret.user, ret
return
3、demo
公共使用
- setting.py
# 让全局生效
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ["app01.appAuthenticate.App01_authent", ]
}
- urls.py
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path("mybook/", views.Mybook.as_view(), name="mybook"),
path("login/", views.Login.as_view(), name="login"),
path("logout/", views.logout, name="logout"),
]
-
logout
def logout(request): auth.logout(request) return JsonResponse({"status": 1000, "msg": "退出成功"}) class Mybook(APIView): def get(self, request, *args, **kwargs): response = {"status": 1000, "msg": None} book_list = Book.objects.all() if all([book_list, ]): ser = BookSerializer(book_list, many=True) response["data"] = ser.data response["msg"] = "查询成功" else: response["msg"] = "查询列表为空" return JsonResponse(response)
3.1、token判断
- 模型
from django.db import models
from django.contrib.auth.models import User
class UserToken(models.Model):
token = models.CharField(max_length=64)
user = models.OneToOneField(to=User, to_field="id", on_delete=models.CASCADE)
- view
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from app01.appSerializer import BookSerializer
from app01.models import *
from django.http import JsonResponse
from django.contrib import auth
import uuid
class Login(APIView):
authentication_classes = []
def get(self, request):
return render(request, "test.html")
def post(self, request):
response = {"status": 1000, "msg": "登陆成功"}
username = request.data.get("username")
pwd = request.data.get("pwd")
ret = auth.authenticate(request, username=username, password=pwd)
if ret:
auth.login(request, ret)
token = uuid.uuid4()
# 存到session中进行判断
request.session["token"] = str(token)
UserToken.objects.update_or_create(user=ret, defaults={"token": token})
else:
response["msg"] = "登陆失败"
return JsonResponse(response)
- 认证
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from app01.models import *
# Create your views here.
class App01_authent(BaseAuthentication):
def authenticate(self, request):
try:
token = request.session["token"]
if token:
ret = UserToken.objects.filter(token=token).first()
if not ret:
raise exceptions.APIException("认证失败")
return ret.user, token
except Exception:
raise exceptions.APIException("请先登陆")
3.2、session表中判断
在自定义token减少操作
-
App01_authent
class App01_authent(BaseAuthentication): def authenticate(self, request): try: # 取出用户的 session, 如果没有登陆数据库中不会保存 token = request.session.session_key if token: ret = Session.objects.filter(session_key=token).first() if not ret: raise exceptions.APIException("认证失败") # 返回用户,跟token, return auth.get_user(request), token except Exception: raise exceptions.APIException("请先登陆")
-
views
class Login(APIView):
authentication_classes = []
def get(self, request):
return render(request, "test.html")
def post(self, request):
response = {"status": 1000, "msg": "登陆成功"}
username = request.data.get("username")
pwd = request.data.get("pwd")
ret = auth.authenticate(request, username=username, password=pwd)
if ret:
auth.login(request, ret)
else:
response["msg"] = "登陆失败"
return JsonResponse(response)