模型序列化验证
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
这样也是可以跑起来的,并且视图也更加的简单