模型序列化驗證
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
這樣也是可以跑起來的,並且視圖也更加的簡單