django-rest-framework 基礎二 序列化器和路由

Hans_Wang發表於2022-05-10

django-rest-framework 基礎二 序列化器和路由

1. 序列化器

1. 序列化,序列化器會把模型物件(qs,book)轉換成字典,經過response以後變成json字串
2. 反序列化,把客戶端發(前端)送過來的資料,經過request以後變成字典(data),序列化器可以把字典轉成模型-->存到資料庫中
3. 反序列化,完成資料校驗功能---》前端傳入的資料是否合法,長度夠不夠等等, 進行資料校驗

1.1 Serializer的使用

使用序列化器完成增刪改查介面

準備資料,modles.py

from django.db import models

# Create your models here.
class Book(models.Model):
    name = models.CharField(max_length=128)
    auth = models.CharField(max_length=128)
    price = models.DecimalField(decimal_places=2, max_digits=5)
"""
增加一個書籍表
資料庫遷移:
python3 manage.py makemigrations
python3 manage.py migrate



如果之前這些步驟做過,可以忽略
"""

序例化檔案serializers.py

from rest_framework import serializers
from drftest.models import Book

class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField(min_length=3)    # 不但序列化,而且限制最小長度不能小於3
    auth = serializers.CharField()
    price = serializers.DecimalField(decimal_places=2, max_digits=5)
    
     def create(self, validated_data):
        # validated_data校驗過後的資料
        book = Book.objects.create(**validated_data)
        return book # 返回新增的物件


    def update(self, instance, validated_data):
        # instance為要修改的物件
        # validated_data校驗過後的資料
        instance.name = validated_data.get('name')
        instance.auth = validated_data.get('auth')
        instance.price = validated_data.get('price')
        instance.save()  # 模型物件自帶的save,儲存到資料庫中(必須要save,否則只修改了資料,但沒有儲存到資料庫裡)
        return instance
    
    
    """ 
    必須要重寫create和update 因為save裡只是定義了,但沒具體實現,因為不知道具體存到哪個表中,所以要在序列化類中實現
    
	def update(self, instance, validated_data):
        raise NotImplementedError('`update()` must be implemented.')

    def create(self, validated_data):
        raise NotImplementedError('`create()` must be implemented.')    
    """
    

檢視函式views.py

from drftest.serializers import BookSerializer
from drftest.models import Book

from rest_framework.views import APIView
from rest_framework.response import  Response

class BookView(APIView):
    # 查全部的資料
    def get(self,request):
        book_list = Book.objects.all()
        res = BookSerializer(instance=book_list, many=True)
        return Response(res.data)
	# 新增一條的資料
    def post(self, request):
        res = BookSerializer(data=request.data)
        if res.is_valid(): # 校驗資料
            res.save()  # 校驗通過儲存資料,儲存時要重寫create 方法,在序列化檔案裡
            return Response(res.data)
        return Response({"code" : 1001, "msg" : "資料驗證失敗", "error" : res.errors})
    
class BookViewDetail(APIView):
     # 查某一條的資料
    def get(self,request,pk):
        book_list = Book.objects.filter(pk=pk).first()
        res = BookSerializer(instance=book_list)
        return Response(res.data)
	# 修改某一條的資料
    def put(self,request, pk):
        book_list = Book.objects.filter(pk=pk).first()
        # 既有instance,又有data,表示修改
        res = BookSerializer(instance=book_list, data=request.data)
        if res.is_valid(): # 校驗資料
            res.save() # 校驗通過儲存時要重寫update 方法,在序列化檔案裡
            return  Response(res.data)
        return Response({"code": 1001, "msg": "資料驗證失敗", "error": res.errors})

	# 刪除某一條的資料
    def delete(self, request, pk):
        res = Book.objects.filter(pk=pk).delete()
        print(res)
        return Response({"code": 1002, "msg": "資料刪除成功"})

路由urls.py

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('books/', views.BookView.as_view()),
    path('books/<int:pk>/', views.BookViewDetail.as_view()),
]

示例:

查全部資料

image-20220331020716677

查某一條資料:

image-20220331020746762

新增一條資料:

image-20220331020917838

修改一條資料(修改價格為12.9)

image-20220331021012803

刪除一條資料:

image-20220331021131227

總結

  • 第一步:寫一個類:必須繼承drf中的Serializer及其子類

  • 第二步:在類中寫要序列化的欄位-->要序列化哪些,就寫哪些,不序列化的不寫

  • 第三步:使用序列化類,檢視類中用

    得到序列化類物件 物件.data,通過Response返回給前端

1.2 序列化器中的欄位型別

欄位 欄位構造方式
BooleanField BooleanField()
NullBooleanField NullBooleanField()
CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
EmailField EmailField(max_length=None, min_length=None, allow_blank=False)
RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False)
SlugField SlugField(maxlength=50, min_length=None, allow_blank=False) 正則欄位,驗證正則模式 [a-zA-Z0-9-]+
URLField URLField(max_length=200, min_length=None, allow_blank=False)
UUIDField UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose'"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex'"5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
IPAddressField IPAddressField(protocol=’both’, unpack_ipv4=False, **options)
IntegerField IntegerField(max_value=None, min_value=None)
FloatField FloatField(max_value=None, min_value=None)
DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位數 decimal_palces: 小數點位置
DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None)
TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
DurationField DurationField()
ChoiceField ChoiceField(choices) choices與Django的用法相同
MultipleChoiceField MultipleChoiceField(choices)
FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
ListField ListField(child=, min_length=None, max_length=None)
DictField DictField(child=)

其中ListFieldDictFieldmodels中沒有的型別,在反序列化時,如果前端傳入列表或字典可以使用這兩欄位進行反序列化。

1.2.1 欄位引數

寫在欄位類中的引數

選項引數:

引數名稱 作用
max_length 最大長度(CharField)
min_lenght 最小長度(CharField)
allow_blank 是否允許為空(CharField)
trim_whitespace 是否截斷空白字元(CharField)
max_value 最小值 (IntegerField)
min_value 最大值(IntegerField)

通用引數

引數名稱 說明
read_only 表明該欄位僅用於序列化輸出,預設False
write_only 表明該欄位僅用於反序列化輸入,預設False
required 表明該欄位在反序列化時必須輸入,預設True
default 反序列化時使用的預設值
allow_null 表明該欄位是否允許傳入None,預設False
validators 該欄位使用的驗證器(不太用)
error_messages 包含錯誤編號與錯誤資訊的字典
label 用於HTML展示API頁面時,顯示的欄位名稱
help_text 用於HTML展示API頁面時,顯示的欄位幫助提示資訊

通用引數中重點的兩個:

"""
read_only:表明該欄位僅用於序列化輸出,預設False
  如果 read_only=True,這個欄位只用來做序列化
		把物件---》json給前端
    
write_only:表明該欄位僅用於反序列化輸入,預設False
  如果 write_only=True,這個欄位只用來做反序列化
		前端json---》存到資料庫
    
    
什麼都不寫,表示既序列化,又反序列化
read_only=True 序列化給前端,前端看到的欄位,但前端傳資料的時候可以不傳這個對應的欄位
write_only=True 反序列化時,前端需要傳什麼過的欄位
但一個欄位裡不能即寫read_only=True又寫write_only=True。

示例:
	id = serializers.CharField(read_only=True)
    name=serializers.CharField(max_length=32,min_length=3,)
    auth=serializers.CharField(write_only=True)
"""

1.3 序列化時,定製序列化的欄位

例如定製一個price_info欄位

方法一:在序列化類中寫

class BookSerializer(serializers.Serializer):
	...
    price_info = serializers.SerializerMethodField()
    # 使用SerializerMethodField方法,下面必須要寫一個 以get_開頭後跟自定義欄位名的函式
    def get_price_info(self,obj):
        return "price is " + str(obj.price)
    
	...
    

# 只在序列化中增加,其他的內容不變
"""
class ExampleSerializer(self):
	extra_info = SerializerMethodField()

	def get_extra_info(self, obj):
		return ...  # Calculate some data to return.

"""

image-20220331023347330

方法二:在models中寫方法

"""models.py"""
from django.db import models

# Create your models here.

class Book(models.Model):
    name = models.CharField(max_length=128)
    auth = models.CharField(max_length=128)
    price = models.DecimalField(decimal_places=2, max_digits=5)

    #以下為新增的自定義的欄位
    @property
    def price_info(self):
        return "price is " + str(self.price)
    
# 在序列化類中
"""Serializers.py"""

class BookSerializer(serializers.Serializer):
    ...
    #models裡定義,序列化中使用 read_only=True 只有序列化中使用
    price_info = serializers.CharField(read_only=True)
    ...
    
# 其他的內容不變

image-20220331023846593

上面的兩個方法的效果一樣。

1.4 區域性勾子和全域性勾子

驗證順序:

先走欄位選項引數的規則,再走區域性鉤子,最後是走全域性鉤子

1.4.1 欄位選項引數的規則:

"""serializers.py"""

from rest_framework import serializers
from drftest.models import Book

class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField(min_length=3)    # 不但序列化,而且限制最小長度不能小於3
    auth = serializers.CharField()
    price = serializers.DecimalField(decimal_places=2, max_digits=5)
    
"""
欄位選項引數的規則:
	name = serializers.CharField(min_length=3)  限制書籍名字最小長度不能小於3
	price = serializers.DecimalField(decimal_places=2, max_digits=5)
"""

書籍名字為不符合規範

image-20220331025112403

書籍名字為不符合規範並價錢也不符合

image-20220331025203422

1.4.2 區域性勾子驗證

class BookSerializer(serializers.Serializer):
	def create(self, validated_data):pass
    def update(self, instance, validated_data):
    # 區域性鉤子,只驗證某一個欄位
    # 要驗證哪個欄位, 必須要validate_開後,後面跟欄位名,如:validate_name
    def validate_name(self,attr):
        if attr.startswith('xx'):
            raise ValidationError("名字不能以xx開頭")
        else:
            return attr  # 沒有問題,正常返回

驗證(雖然符合欄位的規範,但不符合區域性勾子的規範)

image-20220331025646837

1.4.3 全部勾子

class BookSerializer(serializers.Serializer):
	def create(self, validated_data):pass
    def update(self, instance, validated_data):
        
    def validate(self, attrs):
        # attrs校驗過後的資料
        if attrs.get('name') == attrs.get('auth'):
            raise ValidationError('作者名不能等於書名')
        else:
            return attrs

image-20220331025920412

1.5 ModelSerializer模型類序列化器

上面使用的序列化器寫出的介面,在新增和修改的時候必須要重寫createupdate方法,可以使用ModelSerializer跟表模型做繫結,就不需要重寫createupdate方法了。

為了和之前寫的做區分,繼承ModelSerializer類的為第二個版本_v2

序列化serializers.py

class BookSerializer_v2(serializers.ModelSerializer):
    class Meta:
        model = Book
        # fields = '__all__'  # 拿全部欄位
        fields = ['id','name','auth', 'price','price_info']


	# 沒有create 和updata方法了
    # 區域性勾子
    def validate_name(self, attr):
        if attr.startswith('YY'):
            raise ValidationError("名字不能以YY開頭")
        else:
            return attr  # 沒有問題,正常返回
    # 全域性勾子
    def validate(self, attrs):
        # attrs校驗過後的資料
        if attrs.get('name') == attrs.get('auth'):
            raise ValidationError('作者名不能等於書名')
        else:
            return attrs

檢視views.py

class BookView_v2(APIView):
    def get(self,request):
        book_list = Book.objects.all()
        res = BookSerializer_v2(instance=book_list, many=True)
        return Response(res.data)

    def post(self, request):
        res = BookSerializer_v2(data=request.data)
        if res.is_valid():
            res.save()
            return Response(res.data)
        return Response({"code" : 1001, "msg" : "資料驗證失敗", "error" : res.errors})




class BookViewDetail_v2(APIView):
    def get(self, request, pk):
        book_list = Book.objects.filter(pk=pk).first()
        res = BookSerializer_v2(instance=book_list)
        return Response(res.data)

    def put(self, request, pk):
        book_list = Book.objects.filter(pk=pk).first()
        # 既有instance,又有data,表示修改
        res = BookSerializer_v2(instance=book_list, data=request.data)
        if res.is_valid():
            res.save()
            return Response(res.data)
        return Response({"code": 1001, "msg": "資料驗證失敗", "error": res.errors})

    def delete(self, request, pk):
        res = Book.objects.filter(pk=pk).delete()
        print(res)
        return Response({"code": 1002, "msg": "資料刪除成功"})

路由urls.py

rom django.contrib import admin
from django.urls import path
from drftest import  views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('books/', views.BookView.as_view()),
    path('books/<int:pk>/', views.BookViewDetail.as_view()),
    path('books_v2/', views.BookView_v2.as_view()),
    path('books_v2/<int:pk>/', views.BookViewDetail_v2.as_view()),
]

訪問新介面:

image-20220331030913817

修改一條資料

原資料:

image-20220331030958820

修改價格為:12.8

image-20220331031122342

使用ModelSerializer不用寫createupdate方法,依然可以修改和新增。

1.5.1 增加額外的引數

上面的方法不用寫createupdate方法了,但是如果想在使用欄位選項引數,沒辦法直接傳引數了。

解決方法:

extra_kwargs = {'欄位名':{引數:值}}

序列化serializers.py

class BookSerializer_v2(serializers.ModelSerializer):
    class Meta:
        model = Book
        # fields = '__all__'  # 拿全部欄位
        fields = ['id','name','auth', 'price','price_info']

        # 使用欄位選項引數
        extra_kwargs = {
            'id':{'read_only':True},
            'price_info':{'read_only':True},
            'name':{'min_length':3,'max_length':5}

        }


    def validate_name(self, attr):
        if attr.startswith('YY'):
            raise ValidationError("名字不能以YY開頭")
        else:
            return attr  # 沒有問題,正常返回
    # 全域性勾子
    def validate(self, attrs):
        # attrs校驗過後的資料
        if attrs.get('name') == attrs.get('auth'):
            raise ValidationError('作者名不能等於書名')
        else:
            return attrs
        
        
        
# 如果也想在這裡面定製欄位,比如像上面一樣,增加price_info,
# price_info,它不是資料庫中欄位,但也要在fields中註冊
 fields = ['id','name','auth', 'price','price_info']

# price_info欄位的增加也只能使用在models.py中寫方法了
"""models.py程式碼:"""

from django.db import models

# Create your models here.

class Book(models.Model):
    name = models.CharField(max_length=128)
    auth = models.CharField(max_length=128)
    price = models.DecimalField(decimal_places=2, max_digits=5)
    @property
    def price_info(self):
        return "price is " + str(self.price)

1.6 序列化多表操作

拿書籍的資料時,不但要拿基本資訊,還要拿對應的出版社和作者

序列化serializers.py

from rest_framework import serializers
from drftest.models import Book
from django.core.exceptions import ValidationError

class BookSerializer_v2(serializers.ModelSerializer):
    class Meta:
        model = Book
        # fields = '__all__'  # 拿全部欄位
        fields = ['id','name','auth', 'price','price_info','publish_list','auth_list']

        extra_kwargs = {
            'id':{'read_only':True},
            'price_info':{'read_only':True},
            'price':{'write_only':True},
            'name':{'min_length':3,'max_length':5},
            'publish_list':{'read_only':True},
            'auth_list':{'read_only':True},

        }


    def validate_name(self, attr):
        if attr.startswith('YY'):
            raise ValidationError("名字不能以YY開頭")
        else:
            return attr  # 沒有問題,正常返回
    # 全域性勾子
    def validate(self, attrs):
        # attrs校驗過後的資料
        if attrs.get('name') == attrs.get('auth'):
            raise ValidationError('作者名不能等於書名')
        else:
            return attrs

models.py

from django.db import models

# Create your models here.

class Book(models.Model):
    name = models.CharField(max_length=128)
    auth = models.CharField(max_length=128)
    price = models.DecimalField(decimal_places=2, max_digits=5)
    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE,default=1)
    bookToAuth = models.ManyToManyField(to='Authors')

    @property
    def price_info(self):
        return "price is " + str(self.price)

    @property
    def publish_list(self):
        #單條資料
        return {'name':self.publish.name,"address":self.publish.address,'phone':self.publish.phone}

    @property
    def auth_list(self):
        # 多條資料
        l = []
        for auth in self.bookToAuth.all():
            l.append({"name":auth.name, 'city':auth.city})
        return l


class Publish(models.Model):
    name = models.CharField(max_length=255)
    address = models.CharField(max_length=255)
    phone = models.CharField(max_length=20)

class Authors(models.Model):
    name = models.CharField(max_length=128)
    city = models.CharField(max_length=128)
    authdetail = models.ForeignKey(to='AuthDetail', on_delete=models.CASCADE,default=1)


class AuthDetail(models.Model):
    address = models.CharField(max_length=255)
    phone = models.CharField(max_length=20)

views.py

class BookView_v2(APIView):
    def get(self,request):
        book_list = Book.objects.all()
        res = BookSerializer_v2(instance=book_list, many=True)
        return Response(res.data)

    def post(self, request):
        res = BookSerializer_v2(data=request.data)
        if res.is_valid():
            res.save()
            return Response(res.data)
        return Response({"code" : 1001, "msg" : "資料驗證失敗", "error" : res.errors})
    
    
    
class BookViewDetail_v2(APIView):
    def get(self, request, pk):
        book_list = Book.objects.filter(pk=pk).first()
        res = BookSerializer_v2(instance=book_list)
        return Response(res.data)

    def put(self, request, pk):
        book_list = Book.objects.filter(pk=pk).first()
        # 既有instance,又有data,表示修改
        res = BookSerializer_v2(instance=book_list, data=request.data)
        if res.is_valid():
            res.save()
            return Response(res.data)
        return Response({"code": 1001, "msg": "資料驗證失敗", "error": res.errors})

    def delete(self, request, pk):
        res = Book.objects.filter(pk=pk).delete()
        print(res)
        return Response({"code": 1002, "msg": "資料刪除成功"})

urls.py

rom django.contrib import admin
from django.urls import path
from drftest import  views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('books_v2/', views.BookView_v2.as_view()),
    path('books_v2/<int:pk>/', views.BookViewDetail_v2.as_view()),
]

image-20220331210742393

注意: 查詢、修改和刪除都沒有問題,但是新增有問題,因為涉及到多表,而且Book表中的publish欄位為主鍵和Publish表相關聯。直接新增會報錯,要先在Publish表中有了相關資料才能新增,同理Authors表中新增也一樣,它和AuthDetail相關聯。所以對應的表中都了資料才新增成功。

1.7 編寫檢視函式

之前繼承APIView編寫檢視函式,現在可以使用GenericAPIView它繼承了APIView,比之前多了一些屬性和方法。

1.7.1 編寫檢視函式第二種方法

繼承GenericAPIView方法寫檢視函式(以publish表為例,編寫5個介面),其他的內容不動。

views.py

from django.shortcuts import render
from drfViews.serializers import BookSerializer, BookSerializer_v2, PublishSerializer,AuthDetailSerializer,AuthorsSerializer
from drfViews.models import Book, Publish,AuthDetail, Authors

from rest_framework.views import APIView
from rest_framework.generics import  GenericAPIView
from rest_framework.response import  Response

class PublishView(GenericAPIView):
    queryset = Publish.objects.all()   # 這個Publish表中全部資料,名字必須為queryset
    serializer_class = PublishSerializer  # 用來序列化的類, 名字必須為serializer_class

    def get(self,request):
        obj = self.get_queryset()  # get_queryset就是queryset
        # ser = self.serializers(instance=obj,many=True)
        ser = self.get_serializer(instance=obj, many = True) # 和上面的程式碼同樣功能

        return Response(ser.data)

    def post(self,request):
        # ser = PublishSerializer(data=request.data)
        ser = self.get_serializer(data=request.data) # 和上面的程式碼同樣功能
        if ser.is_valid():
            ser.save()
            return Response({"code": 2000, 'msg': '資料新增成功', 'data': ser.data})
        return Response({"code": 4000, 'msg': '資料檢驗失敗', 'errors': ser.errors})

class PublishViewDetail(GenericAPIView):
    queryset = Publish.objects.all()
    serializer_class = PublishSerializer

    def get(self,request, *args, **kwargs):
        # publish = Publish.objects.all().filter(pk=pk).first()
        obj = self.get_object() # 和上面的程式碼同樣功能

        # ser = PublishSerializer(instance=publish)
        ser = self.get_serializer(instance=obj) # 和上面的程式碼同樣功能

        return Response(ser.data)


    def put(self,request, *args, **kwargs):
        obj = self.get_object()
        ser = self.get_serializer(instance = obj, data = request.data) # # 既有instance,又有data,表示修改
        if ser.is_valid():
            ser.save()
            return Response({"code": 2001, 'msg': '資料修改成功', 'data': ser.data})
        return Response({"code": 4000, 'msg': '資料檢驗失敗', 'errors': ser.errors})



    def delete(self,request, *args, **kwargs):
        obj = self.get_object().delete()
        return Response({"code": 2002, 'msg': '資料刪除成功'})

路由ulrs.py

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('publish/',views.PublishView.as_view()),
    path('publish/<int:pk>/',views.PublishViewDetail.as_view()),
]

GET查全部

image-20220402000254316

POST 新增資料

image-20220402000417543

GET 查一條資料

image-20220402000449512

PUT 修改資料

image-20220402000754881

DELETE 刪除資料

image-20220402000959721

1.7.2 編寫檢視函式第三種方法

使用rest_framework.mixins裡面的五個擴充套件檢視類配合GenericAPIView

from rest_framework.mixins import
ListModelMixin,   	# 列出所有資料集(get查所有)
CreateModelMixin,	# 建立例項(post建立資料)
DestroyModelMixin,  # 刪除例項 (delete刪除資料)
RetrieveModelMixin,	# 檢索例項 (get查一條資料)
UpdateModelMixin	# 更新例項 (put更新資料)
這5個是檢視擴充套件類(不是檢視類,沒有整合APIView,需要配合GenericAPIView),這五個類是單獨的,它們沒用繼承其他類


這五個擴充套件檢視類在使用的時候,一定要配合GenericAPIView

(以Authors表為例,編寫5個介面)

views.py

from django.shortcuts import render
from drfViews.serializers import BookSerializer, BookSerializer_v2, PublishSerializer,AuthDetailSerializer,AuthorsSerializer
from drfViews.models import Book, Publish,AuthDetail, Authors

from rest_framework.views import APIView
from rest_framework.generics import  GenericAPIView
from rest_framework.mixins import CreateModelMixin,ListModelMixin,DestroyModelMixin,RetrieveModelMixin,UpdateModelMixin
from rest_framework.response import  Response

# 取全部資料和新增,繼承CreateModelMixin,ListModelMixin
class AuthorsView(GenericAPIView,CreateModelMixin,ListModelMixin):
    queryset = Authors.objects.all()  # 拿到例項
    serializer_class = AuthorsSerializer  # 序列化類

    def get(self,request):
        return super().list(request)  # 取全部資料

    def post(self,request): 
        return super().create(request) # 取新增資料


class AuthorsDetailView(GenericAPIView, UpdateModelMixin,DestroyModelMixin,RetrieveModelMixin):
    queryset = Authors.objects.all()
    serializer_class = AuthorsSerializer

    def get(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)   # 取某指定資料(一條)

    def put(self, request, *args, **kwargs):
        return  super().update(request, *args, **kwargs) # 更新資料

    def delete(self,request, *args, **kwargs):
        return super().destroy(request, *args, **kwargs)   # 刪除資料

路由urls.py

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('author/', views.AuthorsView.as_view()),
    path('author/<int:pk>/', views.AuthorsDetailView.as_view()),
]

由於AuthorsAuthDetail表有外來鍵關聯,在新增作者的時候,作者詳情表要先有對應的資料才能新增成功,

所以在新增作者的時候要麼先新增作者詳情表,要麼在models.pyAuthors裡面重寫create方法新增這兩個表的資料。

serializers.pyAuthorSerialzier裡面重寫create方法:

serializers.py

address = serializers.CharField(write_only=True)
phone = serializers.CharField(max_length=20, write_only=True)

def create(self,validated_data):
    datail = AuthDetail.objects.create(address=validated_data.get('address'), phone=validated_data.get('phone'))
	print(datail)
	author = Authors.objects.create(authdetail=datail, name=validated_data.get('name'),city=validated_data.get('city'))
	return author

1.7.3 編寫檢視函式第四種方法

通過9個檢視子類,編寫檢視函式.

from rest_framework.generics import 
CreateAPIView,		# 建立	(POST)
ListAPIView, 		# 顯示	(GET查全部)
DestroyAPIView, 	# 刪除	(delete)
RetrieveAPIView, 	# 篩選	(GET查某一條)
UpdateAPIView,		# 更新	(UPDATE)
ListCreateAPIView, 	# 建立和顯示		(POST、GET全部)
RetrieveUpdateAPIView, 	#篩選和更新	(GET查某一條、UPDATE)
RetrieveUpdateDestroyAPIView,	# 篩選,更新和刪除	(GET查某一條、UPDATE和DELETE)
RetrieveDestroyAPIView		# 篩選和刪除		(GET查某一條和DELETE)


繼承了這些類後,裡面的增刪改查介面都現實了。

AuthDetail表為例實現五個介面

views.py

from rest_framework.generics import CreateAPIView, ListAPIView,UpdateAPIView,RetrieveAPIView, DestroyAPIView

class AuthDetailView(ListAPIView,CreateAPIView):
    #  查詢所有和新增
    queryset = AuthDetail.objects.all()
    serializer_class = AuthDetailSerializer

class AuthDetail_detailView(RetrieveAPIView,UpdateAPIView,DestroyAPIView):
    # 查詢單條,刪除,修改
    queryset = AuthDetail.objects.all()
    serializer_class = AuthDetailSerializer

urls.py

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

urlpatterns = [
    path('admin/', admin.site.urls),
    
    path('authDetail/', views.AuthDetailView.as_view()),
    path('authDetail/<int:pk>/', views.AuthDetail_detailView.as_view()),
]

GET獲取所有

image-20220403144243319

POST新增

image-20220403144526278

GET獲取一條

image-20220403144612163

PUT修改

image-20220403145352162

DELETE刪除

image-20220403145430688

除了這五個類之外還有四個組合的類

from rest_framework.generics import
ListCreateAPIView,
RetrieveUpdateAPIView, 
RetrieveUpdateDestroyAPIView,	
RetrieveDestroyAPIView,

使用這四個組合的類:

from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView, RetrieveUpdateAPIView,RetrieveDestroyAPIView

class AuthDetailView(ListCreateAPIView):   
    # 就相當於ListAPIView,CreateAPIView  查詢所有和新增
    queryset = AuthDetail.objects.all()
    serializer_class = AuthDetailSerializer

    
class AuthDetail_detailView(RetrieveUpdateDestroyAPIView): 
    # 相當於:RetrieveAPIView,UpdateAPIView,DestroyAPIView 查詢單條,刪除,修改
    queryset = AuthDetail.objects.all()
    serializer_class = AuthDetailSerializer
    
#實現和上面一樣的結果


class AuthDetail_detailView(RetrieveUpdateAPIView)  # 查詢單條和更新

class AuthDetail_detailView(RetrieveDestroyAPIView) # 查詢單條和刪除

class AuthDetail_detailView(UpdateAPIView,DestroyAPIView) # 更新和刪除


# 這種方法以後是用的最多的,因為可以重寫一些方法:
# 有可能要重寫--》get_queryset--》get_serializer_class--》perform_create--》get,post方法

1.7.4 編寫檢視函式第五種方法

5個介面都使用一個檢視類:ModelViewSet,但是需要修改路由。

views.py

from rest_framework.viewsets import ModelViewSet

class AuthDetailView(ModelViewSet):   # 5個介面
    queryset = AuthDetail.objects.all()
    serializer_class = AuthDetailSerializer

urls.py

from django.contrib import admin
from django.urls import path
from drfViews import  views
#匯入DRF的routers模組
from rest_framework import  routers

router = routers.SimpleRouter()
#註冊
router.register('authDetailView',views.AuthDetailView, 'authDetailView')

urlpatterns = [
	path('admin/', admin.site.urls),
]
urlpatterns += router.urls

這樣五個介面就可以訪問了。

但有時候,我們只想讓訪問GET介面,其他的介面不能訪問,就可以使用ReadOnlyModelViewSet

"""views.py"""
from rest_framework.viewsets import  ReadOnlyModelViewSet

class AuthDetailView(ReadOnlyModelViewSet):
    queryset = AuthDetail.objects.all()
    serializer_class = AuthDetailSerializer
    
    
"""urls.py"""

from django.contrib import admin
from django.urls import path
from drfViews import  views
#匯入DRF的routers模組
from rest_framework import  routers

router = routers.SimpleRouter()
#註冊
router.register('authDetailView',views.AuthDetailView, 'authDetailView')

urlpatterns = [
	path('admin/', admin.site.urls),
]
urlpatterns += router.urls

使用GET方法訪問時是沒有問題的,但使用POST,PUT,DELETE方法則會報錯
    
"detail": "Method \"POST\" not allowed."
"detail": "Method \"PUT\" not allowed."
"detail": "Method \"DELETE\" not allowed."

ModelViewSetReadOnlyModelViewSet之所有能達到這種效果,主要是它們繼承了以下幾個類。

from rest_framework.viewsets import 

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    pass	
    
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
                           mixins.ListModelMixin,
                           GenericViewSet):
        pass	
    
"""    
CreateModelMixin,
RetrieveModelMixin,
UpdateModelMixin,
DestroyModelMixin,
ListModelMixin, 
""" # 這五個就是上面寫的增刪改查的 擴充套件檢視類

# 而且這兩個類都繼承了GenericViewSet,
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
    pass

# 之所有要重寫路由主要是:ViewSetMixin類,使用它就必須要重寫路由。

以後只要想重寫路由必須要繼承ViewSetMixin類或子類

from rest_framework.viewsets import 
ViewSetMixin

ModelViewSet = CreateModelMixin+RetrieveModelMixin+UpdateModelMixin+DestroyModelMixin+ListModelMixin+GenericViewSet

ReadOnlyModelViewSet = RetrieveModelMixin+ListModelMixin+GenericViewSet

GenericViewSet = ViewSetMixin+generics.GenericAPIView

ViewSet = ViewSetMixin +  views.APIView

# 只要繼承了ViewSetMixin以子類ModelViewSet,ReadOnlyModelViewSet,GenericViewSet,ViewSet就必須在urls.py裡匯入routes自動生成路由

1.7.5 以上類的總結:

image-20220404000545183

2. 路由元件

2.1 兩種使用ViewSetMixin的方法

# 方法一:
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin

class TestAPIView(ViewSetMixin,APIView):  # ViewSetMixin必須要寫在前面
    pass

from rest_framework.viewsets import ViewSet
class TestAPIView(ViewSet):
    pass


TestAPIView(ViewSet)  == TestAPIView(ViewSetMixin,APIView):
    
    
# 方法二:
from rest_framework.generics import GenericAPIView
from rest_framework.viewsets import ViewSetMixin

class TestGenericAPIView(ViewSetMixin,GenericAPIView):  # ViewSetMixin必須要寫在前面
    pass


from rest_framework.viewsets import GenericViewSet
class TestGenericAPIView(GenericViewSet):  
    pass

TestGenericAPIView(GenericViewSet)  == TestGenericAPIView(ViewSetMixin,GenericAPIView):

只要繼承了ViewSetMixin檢視類中的方法就可以不用寫成之前的get,post,put,delete,名字隨意寫,不過要在路由中寫成path('url/',views.類名.as_view({'get':'自己定義的方法','put':'自己定義的方法2'}))

示例:

"""views.py"""
from rest_framework.viewsets import ViewSet
class TestView(ViewSet):
    def list(self,request):
        return Response("GET方法")
    def create(self,request):
        return Response("POST方法")
    
    
"""urls.py"""

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('testView/', views.TestView.as_view({'get':"list",'post':"create"}))

]

image-20220404001923398

image-20220404001940128

例項這種寫法過程:

檢視中的類中繼承了ViewSetMixin類,而ViewSetMixin方法中重新寫了as_view()方法,而重寫的as_veiw()方法用法:
    view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
    
def as_view(cls, actions=None, **initkwargs):
其中actions就是{'get': 'list', 'post': 'create'},如果actions沒有值則會報錯:
    "The `actions` argument must be provided when "
    	"calling `.as_view()` on a ViewSet. For example "
    	"`.as_view({'get': 'list'})`"
       

2.2 自動生成路由步驟:

自動生成路由必須要繼承的類或子類:

GenericViewSet + 5個擴充套件檢視類之一(
    				CreateModelMixin,
					RetrieveModelMixin,
					UpdateModelMixin,
					DestroyModelMixin,
					ListModelMixin, 
					)
才能自動生成,因為請求要相互對應:
{'get':'list','post':'create','put':'update','delete':'destroy','get':'retrieve'}

# 這就是為什麼ModelViewSet和 ReadOnlyModelViewSet可以自動生成路由,如果沒有繼承這5個擴充套件檢視類之一,則不能自動生成

自動生成路由步驟:

# 第一步 匯入routers
from django.urls import path, include
from rest_framework import  routers
# 第二步 例項化:
router = routers.SimpleRouter()

# 第三步 註冊:
router.register('URL地址',views.對應的類名, '別名')
router.register('authDetailView',views.AuthDetailView, 'authDetailView')
# 第四步:
urlpatterns += router.urls
或
urlpatterns = [
    path('URL地址', include(router.urls) )
]

2.3 action再生成路由

from rest_framework.viewsets import GenericViewSet

from rest_framework.decorators import action

class TestView(GenericViewSet):
    @action(methods=['GET', 'POST'], detail=False)
    def login(self,request):
        return Response("GET方法")
    def test(self,request):
        return Response("POST方法")



    
action(methods=None, detail=None, url_path=None, url_name=None, **kwargs):
    methods = 請求方式,是個列表
    detail = 是否帶id, False為不帶
    url_path= url地址,不寫則預設方法名為地址
    url_name = 別名
    
    
給上面的login加上action裝飾器後,
會再之前http://172.0.0.1:8000/test/的後面生成路徑--->:http://172.0.0.1:8000/test/login/
訪問的時候直接訪問http://172.0.0.1:8000/test/login/,由於方法裡methods=['GET', 'POST']寫了get和post方法,所以get和post方法都會執行。
    如果設定url_path
    @action(methods=['GET', 'POST'], detail=False,url_path='hello')
    def login(self,request):
    則訪問的時候就要訪問:
    http://172.0.0.1:8000/test/hello/
            

            
如果 detail=True(方法不常用), 則訪問路徑: http://172.0.0.1:8000/test/pk值/login/
        
程式碼:  
"""   
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action

class TestView(GenericViewSet):
    @action(methods=['GET', 'POST'], detail=True)
    def login(self,request,pk):
        return Response("GET方法")
    def test(self,request):
        return Response("POST方法")
"""



路由:urls.py
"""
from django.contrib import admin
from django.urls import path, include
from drfViews import  views
from rest_framework import  routers

router = routers.DefaultRouter()
router.register('test',views.TestView,'test')

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

上面把自動成生的路由放在了api裡面所以訪問地址:
    http://127.0.0.1:8000/api/test/login/

相關文章