限流和序列化
1.限流
限流,限制用户访问频率,一般的限流用户信息都存于缓存之中,例如:用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次, 防止盗刷。
- 对于匿名用户,使用用户IP作为唯一标识。
- 对于登录用户,使用用户ID或名称作为唯一标识。
1.1限流组件使用步骤
(1)创建限流组件类
# 引入相关模块
from rest_framework import exceptions
from rest_framework import status
from rest_framework.throttling import SimpleRateThrottle
class CommonThrottle(SimpleRateThrottle):
cache = default_cache # 访问记录存放在django的缓存中(需设置缓存)
scope = "user" # 构造缓存中的key
# 唯一标识符
cache_format = 'throttle_%(scope)s_%(ident)s'
# 设置访问频率,例如:1分钟允许访问10次
# 其他:'s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day'
THROTTLE_RATES = {"user": "10/m"}
# 获取用户信息,组成唯一标识
def get_cache_key(self, request, view):
# 返回什么,就会以什么做限制,以ip地址限制
return request.META.get('REMOTE_ADDR')
# 超出限制提示信息方法
def throttle_failure(self):
# 调用父类wait方法,得到需要等待的时间
wait = self.wait()
# 异常信息
detail = {
"code": 1005,
"data": "访问频率限制",
'detail': "需等待{}s才能访问".format(int(wait))
}
# 抛出异常
raise ThrottledException(detail)
-
创建了限流类CommonThrottle,继承
SimpleRateThrottle
-
访问记录全部存放在django的缓存中
-
THROTTLE_RATES = {"user": "10/m"}设置访问频率
-
如果超过限流限制,走
throttle_failure
方法提示异常信息
(2)调用限流类
class OrderView(APIView):
# 声明throttle_classes的值为限流类名
throttle_classes = [CommonThrottle, ]
def get(self, request):
return Response({"code": 0, "data": "数据..."})
1.2 多个限流类
本质,每个限流的类中都有一个 allow_request
方法,此方法内部可以有三种情况:
- 返回True,表示当前限流类允许访问,继续执行后续的限流类。
- 返回False,表示当前限流类不允许访问,继续执行后续的限流类。所有的限流类执行完毕后,读取所有不允许的限流,并计算还需等待的时间。
- 抛出异常,表示当前限流类不允许访问,后续限流类不再执行。
1.3 全局配置限流类
- 如果每个类都需要使用限流组件,可以在settings配置文件里配置全局限流
- 配置完全局限流组件后,视图类中则不需要在单独引用限流组件
- 如果说在全局配置中配置了限流频率,在限流了中给予无需再设置了
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES":["app名称.限流组件的py文件.限流组件类名", ],
"DEFAULT_THROTTLE_RATES": {
# 限流频率
"user": "10/m",
"xx":"100/h"
}
}
- 如果说某个视图类想单独使用某个限流组件
- 在视图类下单独声明一个空的
throttle_classes
则可以覆盖全局默认的限流组件
class OrderView(APIView):
throttle_classes = []
def get(self, request):
return Response({"code": 0, "data": "数据..."})
1.4 限流类源码执行流程
2. 序列化器 Serializer,转json序列化,转字典反序列化
2.1 序列化器作用
- 序列化,序列化器会把模型对象转换成字典,经过视图中response对象以后变成json字符串
- 反序列化,视图中request会把客户端发送过来的数据转换成字典,序列化器可以把字典转成模型
- 反序列化,把客户端发送过来的数据进行校验,并存储入库
2.2 序列化类的使用
# 使用步骤:
# 1 写个py文件,叫serializer.py
# 2 写个类,继承serializers.Serializer
# 3 在类中写要序列化的字段
class PublishSerializer(serializers.Serializer):
# 写字段,要序列化的字段
name = serializers.CharField()
addr = serializers.CharField()
id = serializers.IntegerField()
# 4 在视图类中使用,完成 序列化
-多条
-ser = PublishSerializer(instance=publish_list, many=True)
-ser.data 序列化后的数据
-单条:
-ser = PublishSerializer(instance=publish)
-ser.data 序列化后的数据
2.3 序列化类的快速使用
- 视图类中引用
from app01.Serializer import PublisherSerializer
class PublishView(APIView):
def get(self, request):
publish_list = Publish.objects.all()
ser = PublishSerializer(instance=publish_list, many=True) # 如果序列化多条,要many=True
return Response({'code': 100, 'msg': '查询所有成功', 'results': ser.data})
class PublishDetailView(APIView):
def get(self, request, pk):
publish = Publish.objects.filter(pk=pk).first()
ser = PublishSerializer(instance=publish) # 单个不写many=True
return Response(
{'code': 100, 'msg': '查询单条成功', 'results': ser.data})
- 创建序列化类
from rest_framework import serializers
class PublishSerializer(serializers.Serializer):
# 写字段,要序列化的字段
name = serializers.CharField()
addr = serializers.CharField()
- 路由
urlpatterns = [
path('publish/', views.PublishView.as_view()),
path('publish/<int:pk>',views.PublishDetailView.as_view()),
]
2.4 序列化类反序列化校验
# 序列化类可以做字段校验---》三层
# 第一层:字段自己
serializers.CharField(max_length=12,min_length=3)
# 第二层:局部钩子
def validate_name(self, name):
# 待校验的前端传入的name的数据
if name.startswith("sb"):
# 不行,抛异常
raise ValidationError('不能以sb开头')
return name
# 全局钩子--》attrs前端多传的,这里不会有,attrs所有的参数
def validate(self, attrs):
print(attrs)
# 多个字段同时校验
# 出版社名和地址不能一样---》出版社前3个字不能和地址前3个字一样
if attrs.get('name')[:3] == attrs.get('addr')[:3]:
raise ValidationError('出版社名和地址不能一样')
return attrs
# 使用步骤
# 写序列化类:写三层规则
# 视图类中:
ser = PublishSerializer(data=request.data) # 把待校验数据传入
if ser.is_valid(): # 做数据校验---》三层
print(ser.data)
else:
print(ser.errors) # 没有校验通过,打印错误信息
2.5 序列化类保存
# 使用步骤:
# 1.在序列化类中,必须重写 create,完成真正的保存
# 保存,必须重写create
def create(self, validated_data):
# validated_data 校验过后的数据---》多传的数据,在这没有
publish = Publish.objects.create(**validated_data)
return publish # 不要忘了返回新增的对象---》后续会拿着这个对象做序列化 ser.data--->根据它做序列化的
# 2.在视图类中,数据校验通过后,调用ser.save()
ser.save() # 使用序列化类保存--》会报错---》咱们没有指定保存到那个表--》必须重写create方法
# 修改功能,也要校验和保存
# 修改使用步骤
# 1.在序列化类中,必须重写 update,完成真正的修改
def update(self, instance, validated_data):
# 笨方法
instance.name=validated_data.get('name')
instance.addr=validated_data.get('addr')
instance.save() # publish 对象的save---》保存到数据中
# 高级方法
# for key in validated_data:
# setattr(instance,key,validated_data[key])
# instance.save()
return instance
# 2.视图类中
ser = PublishSerializer(instance=publish, data=request.data)
if ser.is_valid():
# ser.save()虽然新增或修改都是调用save,但是内部做了判断
return Response({'code': 100, 'msg': '修改成功', 'result': ser.data})
else:
return Response({'code': 101, 'msg': ser.errors})
2.6 序列化类的常用字段
- 除了CharField 以外,还要很多别的---》表模型中 models.CharField --->基本一一对应
- 如果跟 表模型中对不上:你统一用 CharField
序列化器 字段 |
模型 字段 |
序列化器字段选项 |
---|---|---|
BooleanField | BooleanField | BooleanField() |
CharField | CharField TextField等 |
CharField( max_length=最大长度, min_length=最小长度, allow_blank=False, 表示是否允许客户端提交空字符串,False表示不允许 trim_whitespace=True,表示是否移除字符串两边的空白字符,True表示移除 ) |
EmailField | EmailField | EmailField(max_length=None, min_length=None, allow_blank=False) |
RegexField | CharField | RegexField(regex=正则表达式, max_length=None, min_length=None, allow_blank=False) |
SlugField | SlugField | SlugField(max_length=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9*-]+ |
URLField | URLField | URLField(max_length=200, min_length=None, allow_blank=False) |
UUIDField | UUIDField | UUIDField(format='hex_verbose') format: 设置UUID格式,一般默认使用hex_verbose 'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 'hex' 如 "5ce0e9a55ffa654bcee01238041fb31a" 'int' - 如: "123456789012312313134124512351145145114" 'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a" |
IPAddressField | IPAddressField | IPAddressField(protocol='both', unpack_ipv4=False, **options) |
IntegerField | SmallIntegerFiled IntegerField BigIntegerField |
IntegerField(max_value=最大值, min_value=最小值) |
FloatField | FloatField | FloatField(max_value=None, min_value=None) |
DecimalField | DecimalField | DecimalField( max_digits=数值的数字总个数, decimal_places=小数位个数, coerce_to_string=None, max_value=None, min_value=None) |
DateTimeField | DateTimeField | DateTimeField( format=api_settings.DATETIME_FORMAT, 表示日期格式 input_formats=None) |
DateField | DateField | DateField(format=api_settings.DATE_FORMAT, input_formats=None) |
TimeField | TimeField | TimeField(format=api_settings.TIME_FORMAT, input_formats=None) |
DurationField | DurationField | DurationField() |
ChoiceField | 对应整型或字符串中的choices=属性选项 | ChoiceField(choices=元祖选项) choices与Django的用法相同 |
MultipleChoiceField | 对应整型或字符串中的choices=属性选项 | MultipleChoiceField(choices=元祖选项) choices与Django的用法相同 |
FileField | FileField | FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ImageField | ImageField | ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ListField | python里面的List | ListField(child=模型列表, min_length=None, max_length=None) |
DictField | python里面的Dict | DictField(child=模型对象) |
字段的选项参数:
参数名称 | 作用 |
---|---|
max_length | 最大长度 |
min_lenght | 最小长度 |
allow_blank | 是否允许为空字符串 |
trim_whitespace | 是否移除两边的空白字符 |
max_value | 最小数值 |
min_value | 最大数值 |
字段的通用选项参数:
参数名称 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
miss | 序列化时使用的默认值 |
allow_null | 表明反序列化时该字段是否允许传入None,默认False |
validators | 表明反序列化时该字段使用的验证器函数 |
error_messages | 表明反序列化时如果验证出错了,返回错误错误信息的字典 |
label | 用于HTML展示API页面时,显示的字段名称。 如果不写,则默认采用模型的verbose_name,但是前提是当前序列化器继承ModelSerializer |
help_text | 用于HTML展示API页面时,显示的字段帮助提示信息 如果不写,则默认采用模型的help_text,但是前提是当前序列化器继承ModelSerializer |
2.7 多表关联序列化和反序列化
2.7.1 定制返回格式之source
# 1.source 定制返回字段名,必须跟models表中字段必须对应,
book_name = serializers.CharField(source='name')
# 2.可以跨表查询
publish_name=serializers.CharField(source='publish.name')
# 3.所有字段都可以被转成CharField
2.7.2 定制返回字段
(1)方案一在模型表中定义方法
- Models.py
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish_date = models.DateField(auto_now=True, null=True)
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
authors = models.ManyToManyField(to='Author')
# 出版社详情
def publish_detail(self):
return {'name': self.publish.name, 'city': self.publish.city}
# 作者详情
def author_detail(self):
author_list = []
for author in self.authors.all():
author_list.append({'name': author.name, 'age': author.age})
return author_list
def __str__(self):
return self.name
- serializer
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.IntegerField()
publish_date = serializers.DateField(required=False)
# publish_detail 此字段表中没有,通过在models表模式中定义方法
# 此处直接应用,字段名必须与models模型表中的方法名一致
publish_detail = serializers.DictField()
# 可以搭配source使用
authors = serializers.ListField(source='author_detail')
- 展示前端
(2)方案二通过SerializerMethodField
-
一定要配合一个方法--》get_字段名,方法返回什么,前端就看到什么
-
Serializer.py
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.IntegerField()
publish_date = serializers.DateField(required=False)
publish_detail = serializers.SerializerMethodField()
def get_publish_detail(self, obj):
return {'name': obj.publish.name, 'city': obj.publish.city}
authors = serializers.SerializerMethodField()
def get_authors(self, obj):
author_list = []
for author in obj.authors.all():
author_list.append({'name': author.name, 'age': author.age})
return author_list
- 前端展示
(3)方案三 子序列化
- serializer.py
- 自定义字段需要使用的表都序列化一下,就是创建class 表名Serializer(serializers.Serializer):
class PublishSerializer(serializers.Serializer):
name = serializers.CharField()
city = serializers.CharField()
email = serializers.EmailField()
class AuthorSerializer(serializers.Serializer):
name = serializers.CharField()
age = serializers.CharField()
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish_date = serializers.DateField()
# 子序列化
# source='publish' 映射model表中的publish字段
publish_detail = PublishSerializer(source='publish')
# 多条结果时 many=True
authors_list = AuthorSerializer(source='authors', many=True)
- 前端展示
2.8 多表关联反序列化和反序列化保存
# 反序列化保存
# 使用同一个序列化类会出现
-序列化字段和反序列化字段不一致
-序列化字段
name
price
publish_detail
author_list
-反序列化字段:
name
price
publish
author
-如果是共同的,不需要额外处理
-如果是不同的,需要通过字段参数控制
read_only 表明该字段仅用于序列化输出,默认False,序列化过程
write_only 表明该字段仅用于反序列化输入,默认False,反序列化过程
# 注意:
1 read_only write_only 控制序列化类中某些字段,只用来序列化或反序列化
2 重写updata和create,保存逻辑,我们自己写
3 视图类中 serializer = BookSerializer(instance=pk, data=request.data)
-后续在序列化类中的update中def update(self, instance, validated_data):
-instance就是当时给的
# 不要有误区---》增加图书,只是增加图书,选择作者和出版社(传:id)
{name:书名,price:11,publish:{'name':'北京出版社',city:北京},authors:[{},{}]}
{name:书名,price:11,publish:2,authors:[1,2]}
- views.py
class BookView(APIView):
def post(self, request):
serializer = BookSerializer(data=request.data)
if serializer.is_valid():
print(serializer.validated_data) # 校验过后的数据
serializer.save()
return Response('成功')
else:
return Response({'code': 100, 'msg': serializer.errors})
class BookDetailView(APIView):
def put(self, request, pk):
# obj = Book.objects.all().filter(pk=pk).first()
# 传入的instance是什么,到了 update中,instance就是什么
serializer = BookSerializer(instance=pk, data=request.data)
if serializer.is_valid():
serializer.save()
return Response('成功')
else:
return Response({'code': 100, 'msg': serializer.errors})
- serializer.py
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish_date = serializers.DateField(required=False,default=datetime.now)
#### 序列化的字段和反序列化字段不一致####
## 1 笨办法:再写个序列化类,单独用来反序列化
## 2 通过 read_only write_only 控制
# 这个字段用来做序列化
publish_detail = PublishSerializer(source='publish',read_only=True)
# 这个字段用来做序列化
authors_list = AuthorSerializer(source='authors', many=True,read_only=True)
# 反序列化字段--》只用来保存---》多表关联
publish=serializers.IntegerField(write_only=True) # 前端传入数字---》自动跟出版社id做对应
authors=serializers.ListField(write_only=True)
# 反序列化字段-->可以随意命名,跟表字段没关系--》但是后续保存和修改要对应好才行
# publish_id=serializers.IntegerField(write_only=True) # 前端传入数字---》自动跟出版社id做对应
# authors_xx=serializers.ListField(write_only=True)
# 保存
def create(self, validated_data):
publish_id=validated_data.pop('publish')
authors=validated_data.pop('authors')
book=Book.objects.create(**validated_data,publish_id=publish_id)
book.authors.add(*authors) # 向中间表中插入数据
return book
# 更新
def update(self, instance, validated_data):
publish_id=validated_data.pop('publish')
authors=validated_data.pop('authors')
book_qs=Book.objects.filter(pk=instance) # 查询qs对象
book_qs.update(**validated_data,publish_id=publish_id) # 使用qs更新
instance=book_qs.first() # 单个对象
instance.authors.set(authors) # 向中间表中插入数据
# instance.author.clear()
# instance.authors.add(*authors)
return instance
2.9 反序列化校验源码分析
#1 执行 ser.is_valid() 就会执行 反序列化的校验---》字段自己--》局部钩子---》全局钩子
#2 入口是:ser.is_valid()---》BaseSerializer 找到了
1 自己写的BookSerializer---》serializer.Serializer---->BaseSerializer
2 源码如下
def is_valid(self, *, raise_exception=False):
# self 是 ser对象---》自己写的BookSerializer的对象--》一开始没有
# 一旦有了,就不执行了,优化is_valid被多次调用,只会走一次校验
if not hasattr(self, '_validated_data'):
try:
# 一旦执行过,以后self中就有_validated_data
# 接下来看self.run_validation(self.initial_data)
self._validated_data = self.run_validation(self.initial_data)
except ValidationError as exc:
self._validated_data = {}
self._errors = exc.detail
else:
self._errors = {}
if self._errors and raise_exception:
raise ValidationError(self.errors)
return not bool(self._errors)
3 self.run_validation(self.initial_data)---》serializer.Serializer类的,不要按住ctrl点击,否则会进 Field 类,看错了
4 serializer.Serializer类的run_validation
def run_validation(self, data=empty):
# data前端传入的--{"name":"张三","age":68}
# value是---》前端传入的,字段自己校验通过的字典---{"name":"张三","age":68}
value = self.to_internal_value(data) # 执行局部钩子
try:
self.run_validators(value) # 先不看,忽略掉
# self 是 BookSerializer的对象,如果我们写了全局钩子,走我们自己的,如果没写,走父类的,父类的根本没做校验
# value={"name":"张三","age":68}
value = self.validate(value)# 执行全局钩子
except (ValidationError, DjangoValidationError) as exc:
raise ValidationError(detail=as_serializer_error(exc))
return value
5 全局钩子读完了:self 是 BookSerializer的对象,如果我们写了全局钩子,走我们自己的,如果没写,走父类的,父类的根本没做校验
6 局部钩子:value = self.to_internal_value(data)--》Serializer类的
# for循环着去BookSerializer的对象中反射 validate_字段名的方法,如果有就执行,没有就不执行
def to_internal_value(self, data):
for field in fields: # 序列化类中所有字段类的对象 name=CharField()
# self 是BookSerializer类的对象
# 去BookSerializer类中,反射 validate_field字段类的对象.field_name
validate_method = getattr(self, 'validate_' + field.field_name, None)
try:
# 如果能拿到,说明咱么写了局部钩子
if validate_method is not None:
# 执行局部钩子--》传入了当前字段的value值
validated_value = validate_method(validated_value)
except ValidationError as exc:
# 如果抛异常,会被捕获
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = get_error_detail(exc)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
# #####读了局部和全局钩子的执行位置#####
# 保存,修改也好,都要用validated_data,它是最准确的
2.10 ModelSerializer使用
# 之前写序列化类,没有显示指明跟哪个表一一对应
# ModelSerializer 可以跟表做一一对应关系
-序列化类中,就不需要一个个写字段了--》跟表有对应关系
-序列化类中,就不需要重写create和update
# 局部钩子全局钩子跟之前一样(注意层级)
- serializer.py
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model=Book
# all代表全部字段
# fields = '__all__'
# 部分字段放在列表里
fields=['id','name','publish_detail','authors_list']
extra_kwargs={
'name':{'max_length':8}, # 限制name不能超过8
'publish':{'write_only':True},
'authors':{'write_only':True},
}
# 自己再重写的字段
# 这个字段用来做序列化
publish_detail = PublishSerializer(source='publish',read_only=True)
# 这个字段用来做序列化
authors_list = AuthorSerializer(source='authors', many=True,read_only=True)
# 不需要写create了,但是字段必须是:publish和authors
- 前端传参数图片
3. 断言
# 断言 语法
assert 条件,'字符串'
翻译成if
if 条件:
条件成立,继续走后续代码
else:
raise Exception('字符串')
a = 10
## assert 后写条件,只要不符合条件,就会抛AssertionError异常,后面写异常信息
assert a == 11, ("不等于11,报错了")
# 等同于---》上面只要一行代码,源码中喜欢用
if not a == 11:
raise Exception('不等于11,报错了')
标签:serializers,name,05,self,publish,限流,序列化,data
From: https://www.cnblogs.com/Formerly/p/18194206