Django Rest Framework 序列号组件
serializers是什么?
将复杂的数据结构,例如ORM中的QuerySet或者Model实例对象转换成Python内置的数据类型,从而进一步方便数据和json,xml等格式的数据进行交互。
serializers的作用
1.将queryset与model实例等进行序列化,转化成json格式,返回给用户(api接口)。
2.将post与patch/put的上来的数据进行验证。
3.对post与patch/put数据进行处理。(后面的内容,将用patch表示put/patch更新,博主认为patch更贴近更新的说法)
简单来说,针对get来说,serializers的作用体现在第一条
但如果是其他请求,serializers能够发挥2,3条的作用!
常用的field
1 2 3 4 5
| mobile = serializers.CharField(max_length=11, min_length=11) age = serializers.IntegerField(min_value=1, max_value=100)
pay_time = serializers.DateTimeField(read_only=True,format='%Y-%m-%d %H:%M') is_hot = serializers.BooleanField()
|
HiddenField
HiddenField的值不依靠输入,而需要设置默认的值,不需要用户自己post数据过来,也不会显式返回给用户,最常用的就是user!!
我们在收藏某件商品的时候,后台要获取到当前的用户;相当于前台只需要传递过来一个商品的ID即可;那么在后台我根据当前的登入用户和当前的商品ID即可判断用户是否收藏过该商品;这就是一个联合唯一主键的判断;这同样需要使用HiddenField
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
class ArticleSerializer(serializers.Serializer): user = serializers.HiddenField() default=serializers.CurrentUserDefault()) name = serializers.CharField(max_length=20) content = serializers.CharField()
def create(self, validated_data):
user = self.context['request'].user name = validated_data['name '] content = validated_data['content '] return Article.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.name = validated_data.get('name') instance.content = validated_data.get('content') instance.save() return instance
|
字段参数
read_only:True表示不允许用户自己上传,只能用于api的输出。如果某个字段设置了read_only=True,那么就不需要进行数据验证,只会在返回时,将这个字段序列化后返回
在用户进行购物的时候,用户post订单时,肯定会产生一个订单号,而这个订单号应该由后台逻辑完成,而不应该由用户post过来,如果不设置read_only=True,那么验证的时候就会报错。
我们在网上购物时,支付时通常会产生支付状态,交易号,订单号,支付时间等字段,这些字段都应该设置为read_only=True,即这些字段都应该由后台产生然后返回给客户端;
在用户提交订单的时候,我们在这里给用户新增一个字段,就是支付宝支付的URL
要设置为read_only=True,这样的话,就不能让用户端提交了,而是服务器端生成
返回给用户的
1
| alipay_url = serializers.SerializerMethodField(read_only=True)
|
write_only: 与read_only对应;就是用户post过来的数据,后台服务器处理后不会再经过序列化后返回给客户端
最常见的就是我们在使用手机注册的验证码和填写的密码。
reuqired: 是否必填
allow_null/allow_blank: 是否允许为NULL/空
error_message: 出错时的提示信息
label: 字段显示
Help_text: 在指定字段增加一些提示文字,这两个字段作用于api页面比较有用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| code = serializers.CharField(required=True, write_only=True, label="验证码", max_length=6, min_length=6, error_messages={ "blank": "请输入验证码", "required": "请输入验证码", "max_length": "验证码格式错误", "min_length": "验证码格式错误" }, help_text="验证码")
username = serializers.CharField(required=True, allow_blank=False, label="用户名", validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")])
password = serializers.CharField( style={'input_type': 'password'}, help_text="密码", label="密码", write_only=True)
color_channel = serializers.ChoiceField( choices=['red', 'green', 'blue'], style={'base_template': 'radio.html'})
|
Validation自定义验证逻辑
field它能起到一定的验证作用,但很明显,它存在很大的局限性
拿实际生产环境下的例子来说,光针对用户输入的手机号码,我们在后端就需要进行验证
例如该手机号码是否注册,手机号码是否合法(例如满足手机号码的正则表达式,以及要验证该手机号码向后台发送请求验证短信的频率等等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class SmsSerializer(serializers.Serializer): """ 为什不用ModelSerializer来完成手机号码的验证呢? 因为VerifyCode中还有一个字段是手机验证码字段,直接使用ModelSerializer来验证会报错 因为ModelSerializer会自动生成VerifyCode模型中的所有字段;但是 我们传递过来的就是一个手机号,判断它是否是合法的 因此使用Serializer来自定义合法性校验规则,相当于就是钩子函数 单独对手机号码进行验证 """
mobile = serializers.CharField(max_length=11)
def validate_mobile(self, mobile): """ 验证手机号码,记住这里的validate_字段名一定要是数据模型中的字段 :param attrs: :return: """ if User.objects.filter(mobile=mobile).exists(): raise serializers.ValidationError("用户已经存在")
if not re.match(REGEX_MOBILE, mobile): raise serializers.ValidationError("手机号码格式不正确")
one_minute_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0) """ 如果添加时间在一分钟以内,它肯定是大于你一分钟之前的时间的 如果这条记录存在 """ if VerifyCode.objects.filter(add_time__gt=one_minute_ago, mobile=mobile): raise serializers.ValidationError("抱歉,一分钟只能发送一次") return mobile
|
订单号的生成,我们可以在这步生成一个订单号,然后添加到attrs这个字典中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| class OrderSerializer(serializers.ModelSerializer): user = serializers.HiddenField( default=serializers.CurrentUserDefault())
pay_status = serializers.CharField(read_only=True) trade_no = serializers.CharField(read_only=True) order_sn = serializers.CharField(read_only=True) pay_time = serializers.DateTimeField(read_only=True)
""" 在用户提交订单的时候,我们在这里给用户新增一个字段,就是支付宝支付的URL 要设置为read_only=True,这样的话,就不能让用户端提交了,而是服务器端生成 返回给用户的 """
alipay_url = serializers.SerializerMethodField(read_only=True)
def get_alipay_url(self, obj): alipay = AliPay( appid="2019081200490227", app_notify_url="http://47.92.87.172:8000/alipay/return/", app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_key_path, debug=True, return_url="http://47.92.87.172:8000/alipay/return/" )
url = alipay.direct_pay( subject=obj.order_sn, out_trade_no=obj.order_sn, total_amount=obj.order_mount, ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
return re_url
def generate_order_sn(self):
""" 后台系统生成订单号 这个是系统后台生成的: 当前时间+userId+随机数 """
random_ins = Random() order_sn = "{time_str}{user_id}{random_num}".format(time_str=time.strftime("%Y%m%d%H%M%S"), user_id=self.context["request"].user.id, random_num=random_ins.randint(1000, 9999)) return order_sn
def validate(self, attrs): attrs["order_sn"] = self.generate_order_sn() return attrs
class Meta: model = OrderInfo fields = "__all__"
def validate(self, attrs): """ 判断完毕后删除验证码,因为没有什么用了 """ attrs["mobile"] = attrs["username"] del attrs["code"] return attrs
|
Validators
validators可以直接作用于某个字段,这个时候,它与单独的validate作用差不多;当然,drf提供的validators还有很好的功能:UniqueValidator,UniqueTogetherValidator等;
UniqueValidator: 指定某一个对象是唯一的,如,用户名只能存在唯一:
UniqueTogetherValidator: 联合唯一,例如我们需要判断用户是否收藏了某个商品,前端只需要传递过来一个商品ID即可。这个时候就不是像上面那样单独作用于某个字段,而是需要进行联合唯一的判断,即用户ID和商品ID;此时我们需要在Meta中设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class UserFavSerializer(serializers.ModelSerializer): user = serializers.HiddenField( default=serializers.CurrentUserDefault() )
class Meta: model = UserFav """ 我们需要获取的是当前登入的user 以后要取消收藏,只需要获取这里的id即可 UniqueTogetherValidator作用在多个字段之上 因为是联合唯一主键 """ validators = [ UniqueTogetherValidator( queryset=UserFav.objects.all(), fields=('user', 'goods'), message="已经收藏" ) ] fields = ("user", "goods", "id")
|
ModelSerializer
讲了很多Serializer的,但还是强烈建议使用ModelSerializer,因为在大多数情况下,我们都是基于model字段去开发。
Why:
ModelSerializer已经重载了create与update方法,它能够满足将post或patch上来的数据进行进行直接地创建与更新,除非有额外需求,那么就可以重载create与update方法。
ModelSerializer在Meta中设置fields字段,系统会自动进行映射,省去每个字段再写一个field。
1 2 3 4 5 6 7 8 9 10
| class UserDetailSerializer(serializers.ModelSerializer): """ 用户详情序列化 """ class Meta: model = User fields = ("name", "gender", "birthday", "email", "mobile")
|
ModelSerializer需要解决的2个问题
某个字段不属于指定model,它是write_only,需要用户传进来,但我们不能对它进行save( ),因为ModelSerializer是基于Model,这个字段在Model中没有对应,这个时候,我们需要重载validate!
如在用户注册时,我们需要填写验证码,这个验证码只需要验证,不需要保存到用户这个Model中
1 2 3
| def validate(self, attrs): del attrs["code"] return attrs
|
某个字段不属于指定model,它是read_only,只需要将它序列化传递给用户,但是在这个model中,没有这个字段!我们需要用到SerializerMethodField。
假设需要返回用户加入这个网站多久了,不可能维持这样加入的天数这样一个数据,一般会记录用户加入的时间点,然后当用户获取这个数据,我们再计算返回给它。
1 2 3 4 5 6 7 8 9
| class UserSerializer(serializers.ModelSerializer): days_since_joined = serializers.SerializerMethodField() def get_days_since_joined(self, obj): return (now() - obj.date_joined).days class Meta: model = User
|
ModelSerializer需要解决的第二个问题中,其实还有一种情况,就是某个字段属于指定model,但不能获取到相关数据。
例如,编程语言–>python–>python入门学习课程,编程语言与python属于类别,另外一个属于课程,编程语言类别是python类别的一个外键,而且属于同一个model
1
| parent_category = models.ForeignKey('self', null=True, blank=True, verbose_name='父类目别',related_name='sub_cat')
|
现在获取编程语言下的课程,显然无法直接获取到python入门学习这个课程,因为它们两没有外键关系。SerializerMethodField( )也可以解决这个问题,只要在自定义的方法中实现相关的逻辑即可!
1 2 3 4 5
| courses = SerializerMethodField() def get_courses(self, obj): all_courses = Course.objects.filter(category__parent_category_id=obj.id) courses_serializer = CourseSerializer(all_course, many=True, context={'request': self.context['request']}) return courses_serializer.data
|
关于外键的serializers
假设现在有一门课python入门教学(course),它的类别是python(catogory)
以下两种都是外键正向获取
1 2 3
| category = serializers.PrimaryKeyRelatedField(queryset=CourseCategory.objects.all(), required=True)
category = CourseCategorySerializer()
|
外键反向获取
1 2 3
| class Course(model.Model): category = models.ForeignKey(CourseCategory, related_name='courses')
|
反向获取课程,需要通过related_name
一对多,一个类别下有多个课程,一定要设定many=True
1
| courses = CourseSerializer(many=True)
|