DRF四 ------ 模型序列化驗證 請求和響應 視圖

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把我們常見的操作繼續封裝了起來,封裝 起來的代碼就在ListCreateAPIViewRetrieveUpdateDestroyAPIView

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

這樣也是可以跑起來的,並且視圖也更加的簡單

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章