限流和序列化
1.限流
限流,限制用户访问频率,一般的限流用户信息都存于缓存之中,例如:用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次, 防止盗刷。
- 对于匿名用户,使用用户IP作为唯一标识。
- 对于登录用户,使用用户ID或名称作为唯一标识。
1.1限流组件使用步骤
(1)创建限流组件类
# 引入相关模块
from rest_framework import exceptions
from rest_framework import status
from rest_framework.throttling import SimpleRateThrottleclass 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 PublisherSerializerclass PublishView(APIView):def get(self, request):publish_list = Publish.objects.all()ser = PublishSerializer(instance=publish_list, many=True) # 如果序列化多条,要many=Truereturn 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=Truereturn 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,完成真正的保存# 保存,必须重写createdef 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_listdef __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=Trueauthors_list = AuthorSerializer(source='authors', many=True)
- 前端展示
2.8 多表关联反序列化和反序列化保存
# 反序列化保存
# 使用同一个序列化类会出现-序列化字段和反序列化字段不一致-序列化字段namepricepublish_detailauthor_list-反序列化字段:namepricepublishauthor-如果是共同的,不需要额外处理-如果是不同的,需要通过字段参数控制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.detailelse: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_validationdef 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 value5 全局钩子读完了: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_namevalidate_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.detailexcept DjangoValidationError as exc:errors[field.field_name] = get_error_detail(exc)except SkipField:passelse: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,报错了')