DRF四 —— 模型序列化验证 请求和响应 视图

  • Post author:
  • Post category:其他




drf模型序列化验证

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

接着前面的二,新创建一个应用,创建完成之后记得在setting中注册一下

python manage.py startapp app03



模型类

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)
    

然后开始迁移

python manage.py makemigrations

python manage.py migrate



序列化

在app03下新建一个serializers.py的文件

from rest_framework import serializers
from .models import *
#导入模型类

class UserSerializer(serializers.ModelSerializer):
    phone = serializers.CharField(max_length=11,min_length=11,required=True)
    #手机号比较特殊,所以,这个字段需要重写一下
    class Meta:
        model = User
        fields = '__all__'



视图


from django.shortcuts import render
from .models import *
from .serializers import UserSerializer
from rest_framework.renderers import JSONRenderer
from django.http import JsonResponse,HttpResponse
from rest_framework.parsers import JSONParser
from django.http import Http404
from django.views.decorators.csrf import csrf_exempt
# Create your views here.


class JSONResponse(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):
    if request.method == 'GET':
        user = User.objects.all()
        ser = UserSerializer(instance=user,many=True)
        json_data = JSONRenderer().render(ser.data)
        return HttpResponse(json_data,content_type='applications/json',status=200)


    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serialize = UserSerializer(data=data)
        if serialize.is_valid():
            serialize.save()
            json_data = JSONRenderer().render(serialize.data)
            return HttpResponse(json_data, content_type='applications/json', status=200)
            #201表示创建成功
        json_data = JSONRenderer().render(serialize.errors)
        #表示序列化没有成功
        return HttpResponse(json_data, content_type='applications/json', status=400)
        #校验不成功,就返回401未经授权




@csrf_exempt
def user_detail(request,pk):
       try:
            user = User.objects.get(pk=pk)
       except User.DoesNotExist:
           return HttpResponse(status=404)

       if request.method == 'GET':
           serializer = UserSerializer(instance=user)
           return JSONResponse(serializer.data,status=200)

       elif request.method == 'PUT':
           data = JSONParser().parse(request)
           serializer = UserSerializer(instance=user,data=data)
           if serializer.is_valid():
               serializer.save()
               return JSONResponse(serializer.data)
           return JSONResponse(serializer.errors, status=400)


       elif request.method == 'PATCH':
      
           data = JSONParser().parse(request)
           serializer = UserSerializer(instance=art,data=data,partial=True)
           if serializer.is_valid():
               serializer.save()
               return JSONResponse(serializer.data)
           return JSONResponse(serializer.errors, status=400)
       elif request.method == 'DELETE':
           user.delete()
           return HttpResponse(status=204)
       #204表示删除成功



路由


from django.contrib import admin
from django.urls import path
from app03 import views


urlpatterns = [
    path('users',views.user_list,name='user-list'),
    path('users/<int:pk>',views.user_detail,name='user-detail'),

]

根及路由

from django.contrib import admin
from django.urls import path,include

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

然后开始跑程序,可以发现接口是通的

然后用postman,传入数据。

会发现手机号是11位相同的数字也合法,那这个时候,我们就需要自己写一个验证

在序列化中

from rest_framework import serializers
from .models import *
#导入模型类
import re

class UserSerializer(serializers.ModelSerializer):
    phone = serializers.CharField(max_length=11,min_length=11,required=True)
    #模型里面没有pwd1,但是一般的注册页面都会有重复密码,那么我们就需要在序列化里面补充一下这个字段
    pwd1 = serializers.CharField(write_only=True,max_length=64)
    #这个字段会在页面展示出来,只是没有在模型里面,所以不会进到数据库里面
    class Meta:
        model = User
        fields = '__all__'

    def validate_phone(self, phone):
        #这个函数的名字是validate_属性的名字
        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
    #如果是正则,一定要返回数据

    def validate(self, attrs):
        if attrs.get('pwd1') != attrs.get('pwd'):
            raise serializers.ValidationError('两次密码不一样')
        #如果一样的话,要删除pwd1,因为模型里面(数据库里面)压根没有pwd1,但是最后的
        #return attrs   包含pwd1,这样的话创建数据的时候就会多一个数据,就创建不成功
        attrs.pop('pwd1')#验证之后  删除   否则会报类型错误
        return attrs



运行

在这里插入图片描述



在前端展示性别

在序列化里面重写一下性别这个字段

在这里插入图片描述

gender = serializers.CharField(source='get_gender_display')

在这里插入图片描述

但是这样会产生一个问题,但是当你创建数据的时候,会出错

在这里插入图片描述

这么写只支持序列化,但是不支持反序列化(创建数据)


创建数据的过程就是反序列化,把json数据转换成模型类数据



相反,查找数据,就是序列化,把模型数据转换成json数据


那么怎么解决呢?



解决性别,可查不可传的问题



方法一to_representation方法

在这里插入图片描述

在序列化里面再添加这个函数,也就是下面这个


    def to_representation(self, instance):
        representation = super(UserSerializer,self).to_representation(instance)
        #具体的返回值instance来决定
        representation['gender'] = instance.get_gender_display()
        return representation

再去添加数据,就能够添加数据,也能够查到数据



方法二重写性别这个字段

在这里插入图片描述

利用source实现可读可写的时候,要在最前面加上这个

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())))

整个序列化的代码。然后去访问网页,这时候即可读也可写。输入的时候输入1 ,也可以。

在这里插入图片描述



重复密码问题

重复密码我序列化(展示数据的时候)不想展示

但是我输入数据注册的时候又必须展示

如何解决?



使用额外字段

序列化中添加额外字段就可以

在这里插入图片描述



class UserSerializer(serializers.ModelSerializer):
    phone = serializers.CharField(max_length=11,min_length=11,required=True)
    #模型里面没有pwd1,但是一般的注册页面都会有重复密码,那么我们就需要在序列化里面补充一下这个字段
    pwd1 = serializers.CharField(write_only=True,max_length=64)
    #这个字段会在页面展示出来,只是没有在模型里面,所以不会进到数据库里面
    # gender = serializers.CharField(source='get_gender_display')
    #支持可读可写,重写这个性别字段
    GENDERS = (
        (1, '男'), (2, "女")
    )
    gender = ChoiceDisplayField(choices=GENDERS)
    class Meta:
        model = User
        fields = '__all__'
        extra_kwargs = {
            'pwd':{'write_only':True}
        }

在这里插入图片描述

但是如果是post的方法的时候,必须得传数据



总结

在这里插入图片描述



Validators



UniqueValidator:对象是唯一

下面的这个代码是序列化里面的

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位"
                                     })

这种的唯一在模型类里面就可以实现,

在这里插入图片描述



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='您已收藏')]  



DRF 请求和响应



请求

REST里面有个HttpRequest特别相似的对象叫request,主要获取前端传递过来的数据,获取数据的方法就是request.data



响应

REST里面有个HttpResponse特别相似的对象叫Response,主要用来给前端传递数据,传递数据的方法就是Response(data)



状态码

REST里面的状态码比较人性化。每个状态都用意思去表示。比如

HTTP_200_OK = 200 #OK

HTTP_201_CREATED = 201 #创建成功

HTTP_403_FORBIDDEN = 403 # 权限拒绝

‘’’

‘’’



API视图

API视图主要为了咱们RESTFUL风格的API。主要用来包装request,response、现在api请求方法。

如果想用drf里面的状态码还有请求 响应 可以用下面两种方法

  • 基于函数视图的

    @api_view

    装饰器
  • 基于类视图的

    APIView



综合用法(装饰器)

这个视图可以和上面的视图对比一下,用drf自带的东西会简单很多。

序列化的代码和上面的也是一样的,只是为了在视图中用DRF自带的东西

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status

@api_view(['GET', 'POST'])
#使用装饰器之后,这个request就是drf里面的request,
def user_list(request):
    if request.method == 'GET':
        users = User.objects.all()
        serializer = UserSerializer(users, many=True, context={'request': request})
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = UserSerializer(data=request.data, context={'request': request})
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

不取消csrf验证的原因:因为它里面帮助我们做了验证

from rest_framework.views import APIView

ctrl+鼠标点击 可以点APIView,进去看看源码

在这里插入图片描述

点进去之后会是下面这个界面,然后再进去DEFAULTS.源码就在那个里面

在这里插入图片描述

在这里插入图片描述

他的认证默认值是session类

from rest_framework.authentication import SessionAuthentication

我们把这个类拿出来,再点进去看

在这里插入图片描述

如果没有认证,这个CSRF就不需要传

现在我们就没有做认证,所以也不需要传

而且这个里面的

request也封装了


从源码可以看

from rest_framework.views import APIView

只要封装了,他就不是Django里面的request里面的了

他就是DRF里面的request

去运行是可以的。



类视图

视图代码


from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.http import Http404

class UserDetail(APIView):
    def get_user(self, id):
        try:
            user = User.objects.get(id=id)
            return user
        except User.DoesNotExist:
            raise Http404

    def get(self, request, *args, **kwargs):
        user = self.get_user(kwargs.get('id'))
        serializer = UserSerializer(user, context={'request': request})
        return Response(serializer.data)

    def put(self, request, *args, **kwargs):
        user = self.get_user(kwargs.get('id'))
        serializer = UserSerializer(user, data=request.data, context={'request': request})
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def detete(self, request, *args, **kwargs):
        user = self.get_user(kwargs.get('id'))
        user.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)



序列化

from rest_framework import serializers
from .models import *
#导入模型类
import re


class UserSerializer(serializers.ModelSerializer):
    phone = serializers.CharField(max_length=11,min_length=11,required=True)
    #模型里面没有pwd1,但是一般的注册页面都会有重复密码,那么我们就需要在序列化里面补充一下这个字段
    pwd1 = serializers.CharField(write_only=True,max_length=64)

    class Meta:
        model = User
        fields = '__all__'



    def validate_phone(self, phone):
        #这个函数的名字是validate_属性的名字
        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
    #如果是正则,一定要返回数据

    def validate(self, attrs):
        if attrs.get('pwd1') != attrs.get('pwd'):
            raise serializers.ValidationError('两次密码不一样')
        if 'pwd1' in attrs:
            attrs.pop('pwd1')#验证之后  删除   否则会报类型错误
        return attrs



路由

from django.contrib import admin
from django.urls import path, include
from . import views

urlpatterns = [
    path('users/', views.user_list, name='user_list'),  # 获取或创建
    # path('users/<int:id>/', views.user_detail, name='user-detail'),  # 查找、更新、删除
    path('users/<int:id>/', views.UserDetail.as_view(), name='user-detail'),  # 查找、更新、删除
]

from rest_framework.urlpatterns import format_suffix_patterns

urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'api'])

然后去访问,增删改查都可以



补充:类视图取消csrf

from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt,name='dispatch')
class xxx(APIView)



视图



DRF类视图

mixins

我们常用的操作比如创建、更新、删除、查找。REST框架已经帮我们写好了,写的代码就在mixins这个类里面。

所以只有我们自己的视图直接继承这个类,就完全可以拥有上面的所有功能。

from rest_framework import mixins
from rest_framework import generics


class UserList(mixins.ListModelMixin,
               mixins.CreateModelMixin,
               generics.GenericAPIView):
   #ListModelMixin是获取数据 ,下面有源码分析           
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
	#因为继承的ListModelMixin和CreateModelMixin都帮我们写好了,我们只需要直接用就可以了
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

源码分析:ctrl+鼠标右键,点进去ListModelMixin

在这里插入图片描述

就是查数据,序列化数据,然后返回。他帮我们封装后,我们可以直接用

只是继承了mixins.ListModelMixin, mixins.CreateModelMixin,之后千万要继承generics.GenericAPIView

因为看上上面的图,有个get_queryset和get_serializer

这些方法都在

from rest_framework import generics

generics下,点进去可以看一下源码

在这里插入图片描述

一定要给他一个序列化好的数据和序列化好的类

在这里插入图片描述

他才能返回queryset

然后我们再写一下,获取数据、更新数据、删除数据的视图


class UserDetail(mixins.RetrieveModelMixin,
                 mixins.UpdateModelMixin,
                 mixins.DestroyModelMixin,
                 generics.GenericAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)
     def patch(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request,*args,**kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)



路由


from django.contrib import admin
from django.urls import path
from app03 import views

urlpatterns = [
    path('users',views.UserList.as_view(),name='user-list'),
    path('users/<int:pk>',views.UserDetail.as_view(),name='user-detail'),

]

跑一下,增删改查是都可以进行的



使用通用的基于类的视图

我们可以可以看到我的代码量已经减少了很多,但是我们还可以继续优化。REST把我们常见的操作继续封装了起来,封装 起来的代码就在

ListCreateAPIView



RetrieveUpdateDestroyAPIView

from rest_framework import generics

class UserList(generics.ListCreateAPIView):
#ListCreateAPIView里面继承了RetrieveModelMixin,UpdateModelMixin等,这样给他一个序列化类就可以直接使用
    queryset = User.objects.all()
    serializer_class = UserSerializer

class UserDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

这样也是可以跑起来的,并且视图也更加的简单



版权声明:本文为zxcvbbnn原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。