3-4 序列化-DRF模型序列化验证

目录:

  • 初始化操作(模型、视图、urls)
  • 序列化验证:单个字段验证(validate_验证字段名)、组合字段验证(validate) =>都需要返回值
  • choices 单选display显示 => to_representation 决定每个字段的具体返回的格式,source,这可以自定义ChioceDisplayField 让支持可读可写
  • 额外字段(解决出参时不显示密码字段) => 可以实现字段不支持序列化,但是支持反序列化
  • Validators验证器

一、初始化操作(模型、视图、urls)

1.1、创建app03

D:\drf_test>python  manage.py startapp app03

settings.py注册

INSTALLED_APPS = [
    ...
    'app03.apps.App03Config',
    'rest_framework'
]

1.2、 模型类

说明:创建用户表

from django.db import models

# Create your models here.

class User(models.Model):
    genders = ((1, '男'), (2, "女"))
    name = models.CharField(max_length=10, verbose_name='名字')
    phone = models.CharField(max_length=11, verbose_name='手机号')
    gender = models.IntegerField(choices=genders, verbose_name='性别')
    pwd = models.CharField(verbose_name="密码",max_length=64)

    def __str__(self):
        return self.name

1.3、序列化

说明:在app03下创建serializers.py文件,完成序列化

from rest_framework import serializers
from .models import User


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = "__all__"

1.4、 视图

说明:编辑 views.py,新增 user_list和user_detail的函数。

from .serializers import UserSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from django.http import HttpResponse
from .models import User
from django.views.decorators.csrf import csrf_exempt


class JSONResponse(HttpResponse):
    """重新封装一下HttpResponse避免重复代码"""
    def __init__(self, data, **kwargs):
        content = JSONRenderer().render(data)
        kwargs['content_type'] = 'application/json'
        super(JSONResponse, self).__init__(content, **kwargs)


@csrf_exempt
def user_list(request):
    """
    文章标题
    :return:
    """
    if request.method == "GET":
        arts = User.objects.all()
        ser = UserSerializer(instance=arts, many=True, context={'request':request})
        return JSONResponse(ser.data, status=200)
    elif request.method == "POST":
        data = JSONParser().parse(request)
        ser = UserSerializer(data=data, context={'request':request})
        if ser.is_valid():
            ser.save()
            return JSONResponse(ser.data, status=201)
        return JSONResponse(ser.errors, status=401)


@csrf_exempt
def user_detail(request,id):
    """
    文章详情
    :param request:
    :param id: 文章id
    :return:
    """
    try:
        art = User.objects.get(id=id)
    except User.DoesNotExist as e:
        return HttpResponse(status=404)

    if request.method == "GET":
        ser = UserSerializer(instance=art, context={'request':request})
        return JSONResponse(ser.data, status=200)

    elif request.method == "PUT":
        data = JSONParser().parse(request)
        ser = UserSerializer(instance=art, data=data, context={'request':request})
        if ser.is_valid():
            ser.save()
            return JSONResponse(ser.data, status=201)
        return JSONResponse(ser.errors, status=400)
    elif request.method == "PATCH":
        data = JSONParser().parse(request)
        ser = UserSerializer(instance=art, data=data, partial=True, context={'request': request})
        if ser.is_valid():
            ser.save()
            return JSONResponse(ser.data, status=201)
        return JSONResponse(ser.errors, status=400)

    elif request.method == "DELETE":
        art.delete()
        return HttpResponse(status=204)
views.py

1.5、路由设置

#根级路由
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('app03.urls'))
]

#子集路由
from django.urls import path
from app03 import views

urlpatterns = [
    path("user/", views.user_list, name="user-list"),
    path("user/<int:id>/", views.user_detail, name="user-detail")
]

postman运行结果:

1.请求:POST:http://127.0.0.1:8000/api/user/

入参:
{
    "phone": "13641929921",
    "gender": 1,
    "name": "gaogao",
    "pwd": "1234"
}

出参:
{
    "id": 1,
    "name": "gaogao",
    "phone": "13641929921",
    "gender": 1,
    "pwd": "1234"
}

2.请求:GET: http://127.0.0.1:8000/api/user/1/

出参:
{
    "id": 1,
    "name": "gaogao",
    "phone": "13641929921",
    "gender": 1,
    "pwd": "1234"
}

二、序列化验证

 一般前后端分离的时候,我们都会校验前端的参数时候合法。如果我们ModelSerializer话,因为它本身已经帮我们写好create方法,所以我们基本不需要再写验证。但是一些特殊的我们就需要重写或者自己写验证方法。

2.1、验证单个字段(validate_验证字段名)

说明:其实model层已经帮我们验证了一些字段,但是不能验证全,比如说 phone手机号,是11位,而且是以 13、15、等开头的规则,那咋办。这个时候我们就要用到 单个字段的验证(phone规则验证)。用法如下:

from rest_framework import serializers
from .models import User
import re

class UserSerializer(serializers.ModelSerializer):
    #因为phone没有办法支持11位,需重新定义phone规则。
    phone = serializers.CharField(max_length=11,min_length=11,required=True)
    class Meta:
        model = User
        fields = "__all__"

    #自定义单个字段验证规则:validate_验证字段名(phone),参数:传入的是当前字段(phone)
    #当我们序列化的时候,phone字段就会走自定义序列化函数
    def validate_phone(self, phone):
        #检测手机是否合法
        if not re.match(r'1[3456789]\d{9}',phone):
            raise serializers.ValidationError("手机号码不合法")  #如果不匹配我抛出一个异常

        #检测手机号是否存在
        if User.objects.filter(phone=phone).all():
            raise serializers.ValidationError("手机号已被注册")

        return phone  #一定要有返回值,意思就是把你验证后的字段返回给模型类

postman运行结果:

1.POST:http://127.0.0.1:8000/api/user/

入参:
{
    "phone": "13641929921",  #已注册过的手机号
    "gender": 1,
    "name": "gaogao",
    "pwd": "1234"
}

出参:
{
    "phone": [
        "手机号已被注册"
    ]
}

2. POST:http://127.0.0.1:8000/api/user/

入参:
{
    "phone": "01641929921",  #手机号不合法
    "gender": 1,
    "name": "gaogao",
    "pwd": "1234"
}

出参:
{
    "phone": [
        "手机号码不合法"
    ]
}

2.2、组合字段验证(validate)

说明:我们有的时候需要验证两个字段,比如说,在注册的时候,需要输入密码,和输入确认密码,但是我们只需要保存一次即可,那这种情况咋办呐。这个时候我们就需要用到validate这种组合验证方式。

from rest_framework import serializers
from .models import User
import re

class UserSerializer(serializers.ModelSerializer):
    phone = serializers.CharField(max_length=11,min_length=11,required=True)

    #序列化一个新字段pwd1,只是我们这个字段的值不保存数据库,后续验证完毕之后需要删除
    pwd1 = serializers.CharField(max_length=64,write_only=True) #write_only表示必须要从前端传递过来
    class Meta:
        model = User
        fields = "__all__"

    def validate_phone(self, phone): #单个验证
        ....

    def validate(self, attrs): #组合验证
        #print(attrs)  #attrs是:OrderedDict([('phone', '13641929925'), ('pwd1', '1234'), ('name', 'gaogao'), ('gender', 1), ('pwd', '1234')])
        if attrs.get('pwd1') != attrs.get('pwd'):
            raise serializers.ValidationError("两次密码不一样")
        attrs.pop('pwd1') #验证完毕需要删除pwd1,因为pwd1在模型类User中没有这个字段
        return attrs #一定要有返回值

postman运行:

POST:http://127.0.0.1:8000/api/user/

入参:
{
    "phone": "13641929926",
    "gender": 1,
    "name": "gaogao",
    "pwd": "1234",  #两次密码不一样
    "pwd1": "12345"
}

出参:
{
    "non_field_errors": [
        "两次密码不一样"
    ]
}

2.3、登录密码加密

说明:我们在注册密码的时候,需要进行用户密码的加密,这个时候就需要重写 create 方法。

from rest_framework import serializers
from .models import User
import re

class UserSerializer(serializers.ModelSerializer):
    ....
    class Meta:
        model = User
        fields = "__all__"
    
    def create(self, validated_data):
        #重写create方法,对密码加密完之后,再插入到数据库中
        return User.objects.create()
    
    def validate_phone(self, phone): #单独验证函数
        .....

    def validate(self, attrs): #组合验证
        ...

三、choices 单选 显示

说明:我们在model类中,有的时候会存choice这样的选项。如下:

from django.db import models

# Create your models here.

class User(models.Model):
    genders = ((1, '男'), (2, "女"))
    ...
    gender = models.IntegerField(choices=genders, verbose_name='性别')
    ...

postman请求的结果如下:

GET: http://127.0.0.1:8000/api/user/1/

出参:
{
    "id": 1,
    "phone": "13641929921",
    "name": "gaogao",
    "gender": 1,  #只显示1或者2,不显示 男 或者 女
    "pwd": "1234"
}

很明显 只显示 1或者 2这样的数据,不是我们想要的,那我们咋办呐?三种办法:

3.1、序列化中重新定义gender字段

说明:根据source来源来定义字段,这种方式只能支持序列化,但是不支持反序列化,所以只能获取数据,但是不能创建数据。

from rest_framework import serializers
from .models import User
import re

class UserSerializer(serializers.ModelSerializer):

    gender = serializers.CharField(source='get_gender_display')  #根据source数据来源定义字段

    class Meta:
        model = User
        fields = "__all__"

postman运行结果:

GET:http://127.0.0.1:8000/api/user/1/

出参:
{
    "id": 1,
    "phone": "13641929921",
    "gender": "男",   #显示男
    "name": "gaogao",
    "pwd": "1234"
}

3.2、重写 to_representation 方法

说明:前面我们也讲了,所有字段的显示,其实就是在 to_representation方法里面,那我们在 to_representation 方法重新定义 gender字段不就可以了。这样既支持序列化,也可以支持反序列化。

from rest_framework import serializers
from .models import User
import re

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = "__all__"

    def to_representation(self, instance):  #重写to_representation方法
        representation = super(UserSerializer, self).to_representation(instance)  #继承UserSerializer父类
        representation['gender'] = instance.get_gender_display()  #重新定义gender字段

        return representation

postman运行结果:

1.GET:http://127.0.0.1:8000/api/user/1/

出参:
{
    "id": 1,
    "phone": "13641929921",
    "name": "gaogao",
    "gender": "男",  #序列化没有问题
    "pwd": "1234"
}


2.POST:http://127.0.0.1:8000/api/user/

入参:
{
    "phone": "13641929927",
    "gender": 2,
    "name": "gaogao",
    "pwd": "1234",
    "pwd1": "1234"
}

出参:
{
    "id": 3,
    "phone": "13641929927",
    "name": "gaogao",
    "gender": "女",   #反序列化也是可以的
    "pwd": "1234"
}

3.3、重写genders 字段

说明:这种效果跟上面的效果差不多,但是代码量太大,我觉得没啥用。但是还是要学一下。

from rest_framework import serializers
from .models import User
from collections import OrderedDict
#重新定义字段,实现可读可写
class ChoiceDisplayField(serializers.Field):
    """Custom ChoiceField serializer field."""

    def __init__(self, choices, **kwargs):
        """init."""
        self._choices = OrderedDict(choices)
        super(ChoiceDisplayField, self).__init__(**kwargs)

    # 返回可读性良好的字符串而不是 1,-1 这样的数字
    def to_representation(self, obj):
        """Used while retrieving value for the field."""
        return self._choices[obj]

    def to_internal_value(self, data):
        """Used while storing value for the field."""
        for i in self._choices:
            # 这样无论用户POST上来但是CHOICES的 Key 还是Value 都能被接受
            if i == data or self._choices[i] == data:
                return i
        raise serializers.ValidationError("Acceptable values are {0}.".format(list(self._choices.values())))


class UserSerializer(serializers.ModelSerializer):
    GENDERS = ((1,'男'),(2,'女'))
    gender = ChoiceDisplayField(choices=GENDERS)  #调用ChoiceDisplayField
    class Meta:
        model = User
        fields = "__all__"

运行结果跟重写 重写 to_representation 方法 效果是一样的

四、额外字段(解决出参时不显示密码字段)

说明:我们在入参的时候可能想输入一些参数,但是在出参的时候,不想显示一些参数,比如说,密码,不可能我注册的时候,响应给我密码多少。那这个怎么解决呐?我们就用额外字段解决。

from rest_framework import serializers
from .models import User

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = "__all__"
        extra_kwargs = {  #额外字段,就是说你必须传参,但是不需要出参。额外字段:改变我们字段的修饰
            "pwd": {"write_only": True}  #write_only: True 表示只允许入参,不允许出参
        }

postman运行结果:

1.GET: http://127.0.0.1:8000/api/user/1/

出参:
{
    "id": 1,
    "phone": "13641929921",
    "name": "gaogao",
    "gender": 1
}


2.POST:http://127.0.0.1:8000/api/user/

入参:
{
    "phone": "13641929929",
    "gender": 2,
    "name": "gaogao",
    "pwd": "1234",
    "pwd1": "1234"
}

出参:
{
    "id": 5,
    "phone": "13641929929",
    "name": "gaogao",
    "gender": 2
}

五、Validators验证器

5.1、UniqueValidator:对象是唯一

说明:其实这个我们在model模型类中用unique=True 也可以验证。

username = serializers.CharField(required=True, allow_blank=False, label="用户名", max_length=16, min_length=6,validators=[UniqueValidator(queryset=User.objects.all(), 
message="用户已经存在")],error_messages={
                                         "blank": "用户名不允许为空",
                                         "required": "请输入用户名",
                                         "max_length": "用户名长度最长为16位",
                                         "min_length": "用户名长度至少为6位"
                                     })

5.2、UniqueTogetherValidator:联合唯一

说明:两个组合联合唯一

class UserFav(models.Model):
    user = models.ForeignKey(User,verbose_name="用户",on_delete=False)
    goods = models.ForeignKey(Goods,verbose_name="商品",on_delete=False)
    add_time = models.DateTimeField(default=datetime.now,verbose_name="用户收藏")

    class Meta:
        verbose_name = "用户收藏"
        verbose_name_plural=verbose_name
        unique_together = (('user','goods'),)
        
class UserFavSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())

    class Meta:
        model = UserFav
        fields = ('id','user', 'goods')
        validators = [UniqueTogetherValidator(queryset=UserFav.objects.all(),fields=('user','goods'),message='您已收藏')] 

 

posted @ 2020-04-26 13:46  帅丶高高  阅读(819)  评论(0)    收藏  举报