python测试开发django-rest-framework-88.反序列化(ModelSerializer)之校验传入参数

前言

serializers.Serializer可以对modle模型中的字段序列化,并且必须写create和update两个方法。ModelSerializer可以看成是Serializer的一个升级版,功能更强大,更方便。
实际上ModelSerializer类继承了Serializer类

序列化

序列化是把数据库里面的数据,转成json格式返回给用户,具体参考前面这篇https://www.cnblogs.com/yoyoketang/p/11538172.html
在models.py设计一个Goods商品表,里面包含多个字段和多个数据类型

from django.db import models
# Create your models here.
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/



class Goods(models.Model):
    """商品表"""
    goods_name = models.CharField(max_length=30,
                                  default="",
                                  verbose_name="商品名称")
    goods_code = models.CharField(max_length=30,
                                  unique=True,
                                  verbose_name="商品代号")
    merchant_id = models.CharField(max_length=30,
                                   default="",
                                   blank=True, null=True,
                                   verbose_name="商户ID")
    merchant_name = models.CharField(max_length=30,
                                     default="",
                                     blank=True, null=True,
                                     verbose_name="商户名称")
    goods_price = models.FloatField(blank=True, null=True,
                                    default=0,
                                    verbose_name="商品价格")
    goods_stock = models.IntegerField(blank=True, null=True,
                                      default=0,
                                      verbose_name="商品库存")
    goods_groupid = models.IntegerField(blank=True, null=True,
                                        default=0,
                                        verbose_name="商品分组")
    goods_status = models.IntegerField(choices=(
                                                (0, '下架'), 
                                                (1, '出售中')
                                               ),
                                       default=1,
                                       verbose_name="0下架 1出售中")

    price = models.FloatField(blank=True, null=True,
                              default=0,
                              verbose_name="成本价")

    create_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
    update_time = models.DateTimeField(auto_now=True, verbose_name="修改时间")

    class Meta:
        verbose_name_plural = '商品'
        verbose_name = "商品信息"

    def __str__(self):
        return self.goods_code

view视图

视图继承 drf 的 APIView,这里写了2个方法,get 查询全部商品,序列化后返回json数据。
post 请求是创建商品。

from rest_framework.views import APIView
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser
from rest_framework.authentication import TokenAuthentication
from .models import Goods

# Create your views here.
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/


class GoodsSerializer(serializers.ModelSerializer):
    """序列化商品models"""
    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S',required=False)
    update_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S',required=False)

    class Meta:
        model = Goods
        fields = '__all__'  # 返回全部的字段
        # exclude是不包含某些字段
        # exclude = ["price"]


class GoodsAPIView(APIView):
    """商品视图"""
    permission_classes = (AllowAny,)      # AllowAny 允许所有用户

    def get(self, request, *args, **kwargs):
        """返回所有的"""
        goods = Goods.objects.all()   # 查询全部
        serializer = GoodsSerializer(instance=goods, many=True)

        return Response({
            "code": 0,
            "msg": "success!",
            "data": serializer.data
        })


    def post(self, request, *args, **kwargs):
        """提交数据"""
        verify_data = GoodsSerializer(data=request.data)
        if verify_data.is_valid():
            save = verify_data.save()
            return Response({
                "code": 0,
                "msg": "success!",
                "data": GoodsSerializer(instance=save).data
            })
        else:
            return Response({
                "code": 10086,
                "msg": "参数不合法",
                "data": verify_data.errors
            })

urls.py配置访问路由

from django.conf.urls import url
from yoyo import views

urlpatterns = [
    url('^api/v1/goods/$', views.GoodsAPIView.as_view()),
]

序列化和反序列化

什么是序列化呢?

当用户需要查询数据的时候,把数据库里面的数据转成我们需要的json数据,这个过程就是序列化

在get方法里实例化GoodsSerializer对象,传2个参数

  • instance 是查询的queryset对象,也可以是单个Goods 的object对象
  • many 如果是queryset对象,就需要带上many=True ,说明是一个list,有多个数据,如果是单个object对象,就不需要这个参数
    def get(self, request, *args, **kwargs):
        """返回所有的"""
        goods = Goods.objects.all()   # 查询全部
        serializer = GoodsSerializer(instance=goods, many=True)

上面这个过程就是序列化,序列化后输出数据serializer.data

什么是反序列化?

用户在添加商品的时候,需要把数据存到数据库,这个过程我们需要先校验是不是合法的。

对用户传入的数据,我们需要先清洗下,因为用户可能会传一些数据库表里面没有的字段,这些我们不需要,于是可以用到 GoodsSerializer(data=request.data)

  • data 用户传入的参数,通过request.data 获取,request.data 实际上跟之前request.POST 是一样的获取用户传过来的数据
  • is_valid() 校验数据是否合法
  • save() 保存之前,必须先调用is_valid(),保存后返回一个Goods object对象
    def post(self, request, *args, **kwargs):
        """提交数据"""
        verify_data = GoodsSerializer(data=request.data)

上面这个过程,用户传过来的数据先清洗,校验数据合法性,再存入数据库的过程,就是反序列化

校验用户数据必传项required=True

看过接口文档的应该知道,有些参数是必传的,有些是非必传的,那么我们可以在GoodsSerializer控制字段的必传和非必传
详情参考前面这篇https://www.cnblogs.com/yoyoketang/p/14291206.html

class GoodsSerializer(serializers.ModelSerializer):
    """序列化商品models"""
    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S',required=False)
    update_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S',required=False)

    # 必传字段
    goods_code = serializers.CharField(required=True)
    goods_stock = serializers.IntegerField(required=True)

    class Meta:
        model = Goods
        fields = '__all__'  # 返回全部的字段

序列化的时候,设置 goods_code 和 goods_stock 是必传字段,那么在添加商品的时候,如果不传就会提示

{"goods_code":["该字段是必填项。"],"goods_stock":["该字段是必填项。"]}}

校验忽略某些字段read_only=True

如果在创建商品的时候,有些字段我不想让用户去修改,比如 goods_status(商品状态),默认就是出售中,

不想让用户创建的时候设置下架,于是可以忽略 goods_status(商品状态) 字段, 设置 read_only=True

# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/


class GoodsSerializer(serializers.ModelSerializer):
    """序列化商品models"""
    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S',required=False)
    update_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S',required=False)

    # 必传字段
    goods_code = serializers.CharField(required=True)
    goods_stock = serializers.IntegerField(required=True)

    # 忽略字段,设置read_only=True
    goods_status = serializers.IntegerField(read_only=True)

    class Meta:
        model = Goods
        fields = '__all__'  # 返回全部的字段

现在不管 goods_status 传什么都不会影响保存结果

校验字符串和整数范围

校验 goods_code 字符串长度是8-15位,校验goods_stock 整数范围是1-10000

class GoodsSerializer(serializers.ModelSerializer):
    """序列化商品models"""
    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S',required=False)
    update_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S',required=False)

    # 必传字段
    goods_code = serializers.CharField(required=True,
                                       max_length=15,
                                       min_length=8)
    goods_stock = serializers.IntegerField(required=True,
                                           min_value=1,
                                           max_value=10000)

    # 忽略字段,设置read_only=True
    goods_status = serializers.IntegerField(read_only=True)



    class Meta:
        model = Goods
        fields = '__all__'  # 返回全部的字段

这时候就会对传入的字符串和整数范围做校验

自定义校验字段

如果我想用户的商品code命名,必须按sp开头,针对某个字段单独写校验方式,可以自定义 validate_<field_name>

  • value 参数是传入的数据
  • raise 抛出的异常会serializers.ValidationError("goods_code 不是 sp 开头"), 会在 verify_data.errors显示
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/


class GoodsSerializer(serializers.ModelSerializer):
    """序列化商品models"""
    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S',required=False)
    update_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S',required=False)

    # 必传字段
    goods_code = serializers.CharField(required=True,
                                       max_length=15,
                                       min_length=8)
    goods_stock = serializers.IntegerField(required=True,
                                           min_value=1,
                                           max_value=10000)

    # 忽略字段,设置read_only=True
    goods_status = serializers.IntegerField(read_only=True)

    def validate_goods_code(self, value):
        """校验字段 validate_<Field>"""
        if not value.startswith("sp"):
            raise serializers.ValidationError("goods_code 不是 sp 开头")
        return value

    class Meta:
        model = Goods
        fields = '__all__'  # 返回全部的字段

多个字段校验

如果我想校验 goods_price(商品售卖价格)不能小于 (price)成本价, 万一哪个运营设置商品价低于成本价,那不得亏惨!
这里涉及到传入参数的2个值互相校验

# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/


class GoodsSerializer(serializers.ModelSerializer):
    """序列化商品models"""
    create_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S',required=False)
    update_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S',required=False)

    # 必传字段
    goods_code = serializers.CharField(required=True,
                                       max_length=15,
                                       min_length=8)
    goods_stock = serializers.IntegerField(required=True,
                                           min_value=1,
                                           max_value=10000)

    # 忽略字段,设置read_only=True
    goods_status = serializers.IntegerField(read_only=True)

    def validate_goods_code(self, value):
        """校验字段 validate_<Field>"""
        if not value.startswith("sp"):
            raise serializers.ValidationError("goods_code 不是 sp 开头")
        return value

    def validate(self, attrs):
        """自定义校验"""
        goods_price = attrs.get('goods_price', 0)
        price = attrs.get('price', 0)
        if goods_price < price:
            raise serializers.ValidationError('goods_price 不能小于 price')
        return attrs

    class Meta:
        model = Goods
        fields = '__all__'  # 返回全部的字段

原文地址:https://www.cnblogs.com/yoyoketang/p/14342631.html