DRF 框架基础
一、DRF框架介绍
1、Django Rest Framework 介绍
1.1 框架介绍:Django REST Framework (DRF)是基于Django的二次开发框架,集成了大量的函数接口,使得代码更加简洁,其中函数大多都是继承自Django,DRF框架能够事项的功能原生Django都可以实现。
1.2 RESTful 接口设计方法
在进行前后端分离式开发时,API接口的定义要尽量遵循RESTful风格
2、序列化器
序列化操作
序列化完整流程:模型类字典 --> 字典 --> json
序列化器需要完成的工作是:模型类对象 --> 字典 的序列化过程,字典 -->j son过程是由视图完成
2.1、序列化操作标准执行流程
- (1)、获取目标数据(一个或多个模型类对象)
- 一个:
book = BookInfo.objects.get(pk=1) - 多个:
books = BookInfo.objects.all()
- 一个:
- (2)、实例化序列化器对象,通过
instance传入目标数据(通过many=True指明目标数据为多个对象)- 一个:
bs = BookInfoserializer(instance=book) - 多个:
bs = BookInfoSerializer(instance=books, many=True)
- 一个:
- (3)、获取序列化结果(单一对象结果为普通
dict,多个对象结果为orderedDict)bs.data
2.1.1、关联对象嵌套序列化(外键在序列化器中的定义)
-
序列化从表对象的时候,嵌套序列化关联的主表对象(一个)
- (1)、关联主表对象字段,序列化为关联对象的主键值:
hbook = serializers.PrimaryKeyRelatedField(read_only=True) - (2)、关联主表对象字段,序列化为关联对象
__str__方法返回的结果:hbook = serializers.StringRelatedField() - (3)、关联对象字段,使用自定义序列化器来序列化:
hbook = BookInfoSerializer()
- (1)、关联主表对象字段,序列化为关联对象的主键值:
-
序列化主表对象时,嵌套序列化关联的从表对象(多个)
-
模型类设置:通过从表设置外键约束条件 related_name 来指定主表隐藏字段记录关联从表多个对象数据
- (1)、序列化为关联对象的主键值,
read_only=True表明此属性/字段只参与序列化操作,write_only=True表示只参与反序列化操作 - (2)、序列化为关联对象的
__str__返回的结果:heros = serializers.StringRelatedFiel(mang=True) - (3)、使用自定义的关联对象模型类序列化器进行序列化:
hbooks =HeroInfoSerializers
- (1)、序列化为关联对象的主键值,
2.2、反序列化标准校验流程
2.2.1、标准流程
- (1)、获取前端传参
- (2)、实例化序列化器对象(通过
data传入被校验的前端参数,通过partial=True来控制部分校验/更新)- 必要字段,必传必校验(全校验):
bs = BookInfoSerializer(data=book_info) - 传什么字段就校验什么字段(部分校验):
bs = BookInfoSerializer(data=book_info, partial=True)
- 必要字段,必传必校验(全校验):
- (3)、启动校验步骤
bs.is_valid():默认会以返回值True表示校验成功,False表示校验失败bs.is_valid(raise_exception=True): 校验失败,会以抛出validationError异常形式,来表达校验失败
- (4)、获取校验结果
- 当且仅当校验成功,才能获取有效数据:
bs.validated_data - 校验失败,获取错误信息:
bs.errors
- 当且仅当校验成功,才能获取有效数据:
2.2.2 自定义校验
在利用序列化器反序列化时,前端数据经过 序列化器的 字段校验后类型校验还可以对字段进行自定义校验,自定义校验的方式有三种
-
1、序列化器内部字段校验时添加调用自定义的校验方法
validators=[自定义校验函数]def check(value): # 功能: 针对btitle字段,自定义一个校验函数 # 参数:一定有一个参数value,是当前字段经过前序校验的值 —— "静态django" # 返回值:无 if "django" not in value: # 说明校验失败 —— 抛出ValidationError异常来通知上层调用者校验失败 raise serializers.ValidationError('这不是一本关于djangod的书') class BookInfoSerializer(serializers.Serializer): # 略.... # validators用于指定针对当前字段的多个校验函数 btitle = serializers.CharField(label='名称', max_length=20, validators=[check]) -
2、在序列化器内部与class 同级定义函数
def validate_btitle()进行特定字段单独校验class BookInfoSerializer(serializers.Serializer): # 略...... btitle = serializers.CharField(label='名称', max_length=20) # 自定义一个特殊名称的实例方法,来针对btitle字段进行单独校验 # 方法名称固定格式:validate_<字段名> def validate_btitle(self, value): # 功能:针对btitle单独校验 # 参数:value —— 当前字段经过前序校验的值 # 返回值:经过当前校验之后的有效值 if 'django' not in value: raise serializers.ValidationError('这不是一本关于django的书') # 一定要返回经过当前校验之后的有效值 return value -
3、在序列化器内部与 class 同级定义函数
def validate()针对所有字段进行自定义校验,校验的数据是经过类型校验和字段校验后的结果进行再次介入校验,返回的值是最终的有效数据from rest_framework import serializers from datetime import date class BookInfoSerializer(serializers.Serializer): # 略...... btitle = serializers.CharField(label='名称', max_length=20) bpub_date = serializers.DateField(label='发布日期') # 终极校验函数***** def validate(self, attrs): # 功能:针对所有字段进行自定义校验 # 参数:attrs —— 字典类型,记录了经过前序校验之后的所有字段的值: {"btitle": "精通django", "bpub_date": date(1999, 8, 7)......} # 返回值:一定要返回经过当前校验之后的所有字段值 —— 返回值即是最终的"有效数据" btitle = attrs.get('btitle') print('书名:', btitle) bpub_date = attrs.get('bpub_date') print('出版日期:', bpub_date) if 'django' not in btitle: raise serializers.ValidationError('这不是一本关于django的书') # 约定,出版日期必须大于/晚于date(2017,1,1) if bpub_date <= date(2017, 1, 1): # 2017年1月1日: raise serializers.ValidationError('出版日期必须晚于2017年1月1日') # 返回最终的有效数据 return attrs -
校验步骤:
graph TD; a(1. 约束条件校验) --> b(2. 类型校验) b --> c(3. validators指定自定义函数校验) c --> d(4. 示例方法 validate_<字段名>校验) d --> e(5. 实例方法 validate 校验)
2.2.3 反序列化的新建流程
反序列化操作完整流程:json --> 字典 --> 校验 --> 新建模型类对象
- (1)、获取前端参数:
info = {'btitle':'练习', 'bpub_date':'2020-1-1'} - (2)、实例化序列化器对象:
bs = BookInfoSerializer(data=info) - (3)、启动校验步骤:
bs.is_valid() - (4)、新建/保存(当且仅当校验成功):
bs.save()bs.save()函数在此时是间接调用序列化器里面的create方法,并自动传入校验之后的有效数据,来完成数据的新建/保存动作
2.2.4 反序列化之标准更新流程
反序列化操作完整流程:json --> 字典 --> 校验 --> 更新模型类对象
- (1)、获取被更新的目标数据(模型类对象):
book = BookInfo.objects.get(pk=1) - (2)、获取前端参数:
info = {'btitle':'django', 'bpub_date': '2020-1-1'} - (3)、实例化序列化器对象:
bs = BookInfoSerializer(instance=book, data=info, partial=True)instance:通过该参数指定被更新的目标数据(模型类对象)data:通过该参数传入用于更新的前端传来的参数partial=True:通过设置该字段为True表明后续更新为部分更新(部分校验,传什么字段校验什么字段就更新什么字段)
- (4)、启动校验步骤:
bs.is_valid() - (5)、更新数据(当且仅当校验成功):
bs.save()- 此时
bs.save()方法本质上间接调用了序列化器里面的update方法,并传入instance和校验成功之后的有效数据validated_data,来实现更新动作
- 此时
3、视图
4、其他功能
二、DRF 后台管理站点项目记录
1、管理员登录
技术点:JWT (Json Web Token)认证机制 代替session机制
- 1、作用于用户状态保持,代替 session 和 cookie 机制,session的缺点是要在服务器端进行存储,并且多个服务需要使用 redis 时redis就需要是共享服务,不利于横向扩展
- 2、JWT 是利用规定好的加密保存方式和步骤对用户信息进行加密
- 3、浏览器用户登录时输入的用户名和密码,服务端会进行校验后给浏览器用户颁发一个 token 进行用户唯一标识,和前端约定可以在下一次请求时将 token 存进请求头或者请求体中
- 4、服务端校验过程:将token以点分割成三部分,分别得到 header 、payload、signature ;利用header、payload和 SECRET_KEY 利用相同的加密算法得到新的 signature 签名信息,和旧的签名信息对比
校验图示

-
5、JWT组成部分:
-
加密过程:
-
header 头信息(固定格式):头信息的载荷信息是把json数据通过base64编译得到
import json, base64 header = {'typ': 'JWT', 'alg':'HS256'} # 1、把header字典通过json模块序列化为json字符串 header = json.dumps(header) # 2、把header的json格式字符串通过base64编码 header = base64.b64encode(header.encode()).decode() -
payload 载荷信息:字典格式可以有多个任意键值对,但不能包含敏感信息
import json, base64 payload = {'user_id': 1, 'username': 'admin'} # 1、把载荷信息转换为json格式字符 payload = json.dumps(payload) # 2、把payload json格式转换为base64格式 payload = base64.b16encode(payload.encode()).decode() -
signature 签名信息: 根据一个密钥通过 sha256 加密数据
import json, base64 import hmac, hashlib # 加盐 SECRET_KEY = b'@+0*-zgcihcjkwic9evv%3h36a8y%e7q(3_vsvxurjzm4#)jwc' # 1、获取哈希对象 message = header + '.' + payload h_obj = hmac.new(SECRET_KEY, msg=message.encode(), digestmod=hashlib.sha256) # 2、生成签名 (哈希/散列值) signature = h_obj.hexdigest() TOKEN = header + '.' + payload + '.' + signature -
解密校验过程:
-
解密校验原理:
(1)、如果加密信息不变,未被篡改,则使用相同的密钥,生成的签名一定是是固定不变的 (2)、第三部分的生成签名的过程是不可逆的。(无法通过签名,反向获取原加密信息) 校验的方式就是:把解析出来的header和payload配合相同的密钥,再一次进行加密得到新的 signature,对比就签名 如果一致则表明未被篡改(数据完整),反之则数据被篡改# 获取前端token token_from_clien = TOKEN # 1、解析token获取payload header = token_from_client.split('.')[0] # 2、解析token获取payload payload = token_from_client.split('.')[1] # 3、获取 signature old_signature = token_from_client.split('.')[2] # 校验 message = header + '.' + payload h_obj = hmac.new(SECRET_KEY, msg=message.encode(), digestmod=hashlib.sha256) new_signature = h_obj.hexdigest() if new_signature == old_signature: user_info = json.loads(base64.b64decode(payload_from_client.encode()).decode()) else: print('token校验失败') -
在 DRF 框架中,jwt 校验过程已经封装成为一个函数,在路由时直接调用即可,签发token和校验token都已封装
from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ path('authorizations/', obtain_jwt_token) ]
-
2、数据统计
用户数量统计、日增用户统计、每日活跃用户数据、每日下单用户量统计、最近30天新增用户数统计、每日分类商品访问量 的代码逻辑重点:
# 代码技术点1:
获取当前日期的零时刻:
from django.utils import timezone
now_date = timezone.localtime()
cur_0_time = now_date.replace(hour=0, minute=0, second=0)
代码示例1 :
# 日下单用户量统计 -- 订单和用户表联合查询
class UserOrderCountView(APIView):
permission_classes = [IsAdminUser]
def get(self, request):
# 接口分析:借助从表订单表过滤条件查询主表用户表用户的数量
now_date = timezone.localtime()
cur_0_time = now_date.replace(hour=0, minute=0, second=0)
# 方法一:查询从表的所有订单
# order_count = OrderInfo.objects.filter(create_time__gte=cur_0_time)
# # 多个订单可能是同一个用户,所以使用集合自动去重相同的用户
# users = set()
# # 遍历所有订单从表查询集得到每个订单对应的用户,添加进集合
# for order in order_count:
# users.add(order.user)
#
# count = len(users)
# 方法二:从主表开始查询从表的,在主表中利用从表的过滤条件查询主表数据
# 但前题条件是要在从表中设置 related_name,并使用以下特殊语法查询,查询结果依旧是没有去重的用户
users = User.objects.filter(order__create_time__gte=cur_0_time)
count = len(set(users))
print("users集合", users, set(users))
return Response({'count': count, 'date': now_date.date()})
代码示例2:
# 最近30天新增用户统计
class UserMonthCountView(APIView):
permission_classes = [IsAdminUser]
def get(self, request):
# 月增用户代码逻辑:
now_date = timezone.localtime()
end_0_time = now_date.replace(hour=0, minute=0, second=0)
start_0_time = end_0_time - timedelta(days=29)
data = []
for index in range(29):
calc_0_time = start_0_time + timedelta(days=index)
next_0_time = calc_0_time + timedelta(days=1)
count = User.objects.filter(
date_joined__gte=calc_0_time,
date_joined__lt=next_0_time
).count()
data.append({
'count': count,
'date': calc_0_time.date()
})
return Response(data)
3、用户管理

- 功能点介绍:
- 请求:GET
/meiduo_admin/users/?keyword=<搜索内容>&page=<页码>&pagesize=<页容量> - 1、显示所有用户
- 2、在查询栏中输入用户名关键字可显示相关用户
- 3、增加用户,增加的用户是管理员权限用户
- 注意点:只用
ListAPIView自带分页器,其他子类没有这个分页器
- 请求:GET
代码逻辑:
# 分页器:
1. 分页采用的也是 drf 框架的封装函数 PageNumberPagination, 并且重写了分页器的get_paginated_response 函数的返回值为'自定义返回值'格式
# 序列化器
1. 采用序列化器返回列表数据,需要定义用户模型类的序列化器,用户序列化器的返回自定义字段,前端需要什么字段就返回什么字段
2. 序列化器中添加反序列化校验字段约束
3. 在序列化器中重写 create 函数的 validated_data 有效数据,使反序列化时 添加的用户有管理员权限和密码加密处理
4. 或者在 校验过程中介入,对校验值attrs 中的值进行处理加密后
# 视图
1. 视图函数继承 ListAPIView,定义全局属性模型类对象和序列化器对象和分页器对象
2. 视图函数中引用自定义分页器,自定义分页器封装在函数中,并重写返回函数
3. 在视图函数中重写 ListAPIView 继承的 GenericAPIView 中的查询集 get_queryset() 函数,获取传入前端查询字符串参数 keyword ,进行模型类筛选的到新的查询集返回
代码示例:
'# 1、用户模型类序列化器'
from users.models import User
from rest_framework.serializers import ModelSerializer
from django.contrib.auth.hashers import make_password
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = ['id','username','mobile','email',
'password', # password用于反序列化校验新建逻辑
]
# 修改字段约束,添加额外的字段约束
extra_kwargs = {
'password': {'write_only': True, 'min_length': 8, 'max_length': 20},
# password 只参与反序列化
'username': {'min_length': 3, 'max_length': 20}
}
# 解决添加用户时用户密码未加密存储,并且给定添加用户一个管理员权限
# 方案一、在反序列化校验过程中介入
# def validate(self, attrs):
# attrs['is_staff'] = True
# raw_password = attrs.get['password'] # 取出明文密码
# attrs['password'] = make_password(raw_password) # 对明文密码进行加密
# return attrs
# 方案二、在 添加用户时 create 介入
def create(self, validated_data):
validated_data['is_staff'] = True # validated_data 有效数据中追加is_staff=True
user = User.objects.create_user(**validated_data) # 用户密码需要加密
return user
'# 2、重写分页器函数的返回函数'
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
class MyPage(PageNumberPagination):
page_query_param = 'page' # 页数
page_size_query_param = 'pagesize' # 总页数
max_page_size = 10 # 设置每页显示数据条数为10
def get_paginated_response(self, data):
# 功能:构建一个响应对象(封装自定义响应参数)
return Response({
'count': self.page.paginator.count, # 当前页显示的数据数量
'lists': data, # 当前页的数据
'page': self.page.number, # 当前是第几页,页码
'pagesize': self.page_size # 后端默认每页数量,页容量
})
'# 3、视图函数'
class UserView(ListAPIView):
# 指定管理员权限
permission_classes = [IsAdminUser]
queryset = User.objects.all() # 指定查询集
serializer_class = UserSerializer # 指定序列化器
pagination_class = MyPage # 用于查询集分页样式
# 根据查询字符串参数keyword过滤,重写 GenericAPIView 的 get_queryset方法
def get_queryset(self):
# 1、获取前端url中的查询字符串参数 keyword --> request.guery_params.get("keyword")
# 在非视图函数中获取查询集参数,使用self.request即可
keyword = self.request.query_params.get('keyword')
# 2、过滤
if keyword: # contains: 模糊查询,是否包含之意
return self.queryset.filter(username__contains=keyword)
else:
return self.queryset.all()
4、商品管理
-
1、重写
update方法 -
2、重写
create方法 -
3、非视图函数获取前端传参的方法
-
4、反序列化主表插入数据时对从表数据进行插入
-
图片管理中关于 ImageField 类型字段处理:
- (1)、接收到的图片数据上传至 fdfs
- (2)、把文件的id值(索引值),存储在mysql中(image字段)
-
【重点】5、图片上传的四种方法
-
django存储后端:改写了Django的文件存储后端,对接了fdfs分布式文件存储系统,实现了文件上传服务
- 反序列化过程
获取前端参数 -> 构建序列化器对象 -> 校验 - 获得有效数据 -> 保存 - 大前提:序列化器和视图不会自动帮我们把图片上传到FastDFS,而数据库中只会存入图片的url路径,这样的话就需要认为介入
- (1)、在视图函数中重写
create方法 - (2)、在图片序列化器中的反序列化过程的最后一步保存时重写
create方法 - (3)、在序列化器中的校验过程中介入,重写
validate函数 - (4)、在fastdfs自定义存储后端中的
_save()方法中介入,将图片存储到 FastDFS 后返回的文件路径存入数据库,但前提是 image是 ImageField类型字段对象时才会触发文件存储后端进行存储
# 方法一:在视图函数中重写create方法 '缺点:不符合 DRF 风格,视图不处理任何校验过程,都是序列化器负责' from django.conf import settings from rest_framework.viewsets import ModelViewSet from meiduo_admin.paginations import MyPage from meiduo_admin.serializer.image_serializers import * from rest_framework.generics import ListAPIView from fdfs_client.client import Fdfs_client from rest_framework.response import Response # 获取所有图片 class ImageView(ModelViewSet): serializer_class = ImageSerializers queryset = SKUImage.objects.all() pagination_class = MyPage def create(self, request, *args, **kwargs): im = request.FILES.get("image") data = im.read() conn = Fdfs_client(settings.FDFS_PATH) res = conn.upload_by_buffer(data) if res['Status'] != 'Upload successed.': return Response({'code': 400, 'errmsg': '图片上传失败'}) file_id = res['Remote file_id'] sku_id = request.POST.get('sku') image = SKUImage.objects.create( sku_id=sku_id, image=file_id ) return Response({ 'id': image.id, 'sku': sku_id, 'image': image.image.url }, status=201 )# 方法二: 在序列化器中重写create实现保存图片 '缺点:在更新图片时有需要重写update函数,麻烦' def create(self, validated_data): f = validated_data.get('image') data = f.read() conn = Fdfs_client(settings.FDFS_PATH) res = conn.upload_by_buffer(data) if res['Status'] != 'Upload successed.': raise ValidationError('图片上传失败') file_id = res['Remote file_id'] image = SKUImage.objecte.create( sku=validated_data.get('sku'), image=file_id ) return image# 方法三: 在校验得到有效数据前中介入,将图片存储后返回图片路径 '缺点:如果其他序列化器活功能模块也需要进行图片上传和下载时就有需要再写一变相同的方法到序列化器中' def validate(self, attrs): # (1)、获取图片 image = attrs.get('image') # 获取类型校验后的图片对象 data = image.read() # 读图片对象,是一个二进制格式 # (2)、上传图片,创建连接对象-上传图片 conn = Fdfs_client(settings.FDFS_PATH) res = conn.upload_by_buffer(data) if res["Status"] != 'Upload successed.': raise serializers.ValidationError('图片上传失败') file_id = res['Remote file_id'] attrs['image'] = file_id return attrs# 方法四:利用fastdfs存储后端进行图片存储 '前提:在ImageField类型字段被赋值为一个文件对象的时候触发调用保存文件数据' from django.core.files.storage import Storage from django.conf import settings from fdfs_client.client import Fdfs_client from rest_framework.exceptions import ValidationError class FastDFSStorage(Storage): """"自定义文件存储系统,修改存储方案""" def __init__(self, fdfs_base_url=None): self.fdfs_base_url = fdfs_base_url or settings.FDFS_BASE_URL def _open(self, name, mode='rb'): # 功能:打开django本地文件 - 将文件保存在Django本地 pass # TODO:图片文件上传方法4:只要是ImageField类型的模型类字段被赋值成为了一个文件对象时 # 图片文件上传时前端传到Django的是一个字节类型的数据,当经过序列化器校验后就是一个文件对象 def _save(self, name, content): """ 功能:保存文件 在ImageField类型字段被赋值为一个文件对象时触发调用此save保存函数 :param name: 文件名; :param content: 文件对象; :return: 返回值就是将来保存到数据库的值(返回文件id) """ # (1)、提取文件数据 data = content.read() # (2)、上传fdfs conn = Fdfs_client(settings.FDFS_PATH) res = conn.upload_by_buffer(data) if res['Status'] != 'Upload successed.': raise ValidationError("上传失败") file_id = res['Remote file_id'] # (3)、返回文件id return file_id def exists(self, name): """ 功能:用于判断保存的文件是否重复 :param name: 本地保存的文件名 :return: True表示存在,False表示不存在 注意:此处如果返回False,表示文件未保存,直接调用_save 方法实现上传fdfs 而fdfs服务器会自行判断文件是否存在不会重复保存 """ return False def url(self, name): """拼接返回name所指的文件的绝对路径URL""" return self.fdfs_base_url + name - 反序列化过程
-
5、订单管理
技术点:
-
1、三层嵌套序列化器
-
2、在同一个视图函数中,
list函数使用列表表单序列化器,retrieve函数使用单一列表详情序列化器,需要重写generics中的get_serializer_class函数class OrderView(UpdateModelMixin, ReadOnlyModelViewSet): queryset = OrderInfo.objects.all() # 默认序列化器是简单序列化,作用与self.list接口 serializer_class = OrderSimpleModelSerializer pagination_class = Mypage def get_queryset(self): keyword = self.request.query_params.get('keyword') if keyword: return self.queryset.filter(order_id__contains=keyword) return self.querset.all() # self.list 接口应当使用简单序列化器返回非详情列表数据 # self.retrieve 接口应当使用订单详情序列化器 # 总结:同一个视图类中,如何在不同的接口中使用不同的序列化器来实现,构建不同的序列化结果 # 其中 判断此次请求对应的视图函数要使用 action 属性 def get_serializer_class(self): if self.action == 'list': return OrderSimpleModelSerializer if self.action == 'retrieve': return OrderDetailModelSerializer if self.action == 'partial_update': return OrderSimpleModelSerializer return self.serializer_class # 否则使用默认序列化器
6、系统管理
技术点
-
1、用户和权限表之间有一个中间表用于记录用户和权限的多对多关系表,在对中间表插入数据时,只需要在用户或权限表其中任意一个模型类中设置一个字段是
manytomany字段类型即可;在构建序列化器时在fields中包含该字段,DRF 反序列化时就会自动为我们创建中间表数据,人为是无需介入的;下列代码演示反序列化是中间表是如何插入数据的:def create(self, validated_data): # 经过校验之后, permissions中记录的就是权限对象; permissions = [<吃饭:102>, <睡觉:103>] # (1)、把权限从验证数据中取出 permissions = validated_data.pop('permissions') # (2)、新建分组 group = Group.objects.create(**validated_data) # (3)、插入分组权限中间表 -- 组对象.中间表字段.set() group.permissions.set(permissions) -
2、用户权限的判断
user = User.objects.get(pk=5) # 1.用户所有权限 user.user_permissions.all() # 2.用户对goods应用是否有权限 应用名.权限名称 返回值为 True 表示有,否则为False user.has_perm(需要查询的应用名.需要查询的权限名称) # 3.获取用户所属组的权限, 间接获取组权限 g = user.group.all() a.permissions.all()

浙公网安备 33010602011771号