03-序列化詳細

小满三岁啦發表於2024-04-13

序列化常見欄位

和models裡面的欄位是一一對應的,知識多出了兩個欄位

多出了倆 ListField DictField

欄位 欄位構造方式
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=)

序列化常見欄位引數

作用: 用來做反序列化校驗的。

通用欄位

required	表明該欄位在反序列化時必須輸入,預設為True
defatult	反序列化時使用的預設值
allow_null	表明該欄位是否允許傳入None,預設False
validators	驗證欄位的合法性
erorr_message 包含錯誤變化於錯誤資訊的字典
label		用於HTML展示API頁面時,顯示的欄位名稱
help_text	用於HTML展示API頁面時,顯示的欄位幫助提示資訊


--------- 非常重要 ----------
read_only	表明該欄位僅用於序列化輸出,預設False,一般是給前端資料的時候使用
write_one	表明該欄位僅用於反序列化輸入,預設False 一般用於往資料庫裡面存放資料

# CharField
max_length	最大長度
min_length	最小長度
allow_length	是否允許為空
trim_withspace	是否截斷空白字元

建立表

# models.py
from django.db import  models

class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name="書籍名稱")
    price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="書籍價格")
    publish_date = models.DateField(verbose_name="出版日期")
    
    publish = models.ForeignKey(to='publish', on_delete=models.CASCADE, verbose_name="外來鍵,關聯出版社,設定級聯更新和級聯刪除")
    # 資料庫中不會有這個欄位。
    # 會生成第三張表 authors
    authors = models.ManyToManyField(to="Authors")
    
    class Meta:
        db_table = "book"
    

class Publish(models.Model):
    name = models.CharField(max_length=32, verbose_name='出版社名稱')
    addr = models.CharField(max_length=32, verbose_name="出版社地址")
    
    class Meta:
        db_table = "publish"
    
    

class Authors(models.Model):
    name = models.CharField(max_length=32, verbose_name='作者姓名')
    age = models.IntegerField(verbose_name="作者的年齡")
    
    class Meta:
        db_table = "authors"

驗證合法性 validators 欄位自己的校驗 (很少用到)

會把欄位的值,傳到指定的函式里面去做校驗,如果驗證透過正常返回,如果驗證失敗觸發異常。 定義的函式形參名稱隨意。

# 如何使用
# 在類外面定義一個函式,函式放一個形參,名稱可以隨意
# 在對應的欄位中,validators=[ 建立的函式 ], 比如 name = serializers.CharField(validators=[check_field])

from rest_framework import serializers
from datetime import datetime 
from rest_framework.exceptions import ValidationError

# 自己定義的函式,用於給validators校驗
def check_name(text):
    if "sb" in text:
        raise ValidationError("包含不合法的輸入 sb ")
    else:
        return text

class BookSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=8, validators=[check_name])  # 支援放入多個validators=[check_name, check_length...]
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    # 這裡的allow_null如果設定了,一般需要結合預設值一起去設定才會生效
    publish_date = serializers.DateField(allow_null=True, default=datetime.now)

自定義錯誤資訊返回字典 error_message

# 自定義序列化類.py
name = serializers.CharField(max_length=8, min_length=2, error_messages={"max_length": "書名最多8位", "min_length": "書名最少2位"})

# 前端報錯資訊
{
    "code": 100,
    "msg": {
        "name": [
            "書名最少2位"
        ]
    }
}

返回定製欄位

自定義返回的名稱 source

image-20240412191317073

# book_name 表示代表顯示在前端的欄位, source=name 這個name就表示在模型層中建立的欄位
book_name = serializers.CharField(source="name")

# 可以跨表查詢
publish_name = serializers.CharField(source="publish.name")
publish_addr = serializers.CharField(source='publish.addr')

# 所有的欄位,都可以設定轉成CharField
# 可以 但是非特殊情況不建議 建議按照規範去操作
# publish_id = serializers.CharField(source="publish.id")
publish_id = serializers.IntegerField(source="publish.id")

ListField 和 DictField

  1. 單個結果用字典,即DictField
  2. 多個結果用列表,即ListField
# 後期想實現如下格式返回的資料
{name:書名,price:價格,publish:{name:出版社名,addr:地址},authors:[{},{}]}


# 方式一:在表模型中定義方法

在模型表中定義方法 需要模型表和序列化類一起配合使用

# 序列化類裡面的欄位
# 出版社物件詳細資訊
publish_detail = serializers.DictField()

# 作者詳細資訊
authors_detail = serializers.ListField()

# 模型層中定義
@property
def publish_detail(self):
    # 這裡返回列表還是欄位,取決於序列化類的欄位定義的是 ListField 還是 DictField
    return {"publish_name": self.publish.name, "publish_addr": self.publish.addr}

@property
def authors_detail(self):
    items = []
    # 因為這裡是 透過 manytomany建立的第三張表, authors 是一個物件,要拿全部資料要透過 all
    for author in self.authors.all():
        items.append(model_to_dict(author))

透過此方法返回自定義欄位,而不透過source實現

思考?這樣操作有什麼好處?

  1. 更加靈活的定製要返回的資料,隱藏一些敏感資訊,或者增加一些資訊
  2. 比如身份證號、銀行卡、電話號碼、地址等,隱藏部分敏感資訊。
# 自定義序列化類
book_name = serializers.CharField()

# 模型層
@property
def book_name(self):
    return  '可愛的作者:' + self.name

image-20240412202704848

程式碼

# 檢視層
from  django.shortcuts import render, HttpResponse
from django.views import View
from .serializers import BookSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Book
from django.forms.models import model_to_dict

class BookView(APIView):
    def get(self, request):
        book_obj = Book.objects.all()
        serializer = BookSerializer(instance=book_obj, many=True)
        return Response({"code": 100, "msg": "查詢成功!", "results": serializer.data})
    
    def post(self, request):
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({"code": 100, "msg": "新增成功!"})
        else:
            return Response({"code": 100, "msg": serializer.errors})
# 模型層
from django.db import  models
from django.forms.models import model_to_dict

class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name="書籍名稱")
    price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="書籍價格")
    publish_date = models.DateField(verbose_name="出版日期")
    # 這裡是一個物件,所以可以直接透過 . 獲取資料
    publish = models.ForeignKey(to='publish', on_delete=models.CASCADE, verbose_name="外來鍵,關聯出版社,設定級聯更新和級聯刪除")
    # 資料庫中不會有這個欄位。
    # 會生成第三張表 authors
    authors = models.ManyToManyField(to="Authors")
    
    class Meta:
        db_table = "book"
        
    
    # 如果要在序列化類使用欄位,那麼一定要保證模型層中有
    # 所以這裡包裝成資料屬性,這樣序列化類可以直接使用,然後渲染給前端
    @property
    def publish_detail(self):
        # 這裡返回列表還是欄位,取決於序列化類的欄位定義的是 ListField 還是 DictField
        return {"publish_name": self.publish.name, "publish_addr": self.publish.addr}
    
    
    @property
    def authors_detail(self):
        items = []
        # 因為這裡是 透過 manytomany建立的第三張表, authors 是一個物件,要拿全部資料要透過 all
        for author in self.authors.all():
            items.append(model_to_dict(author))
        
        return items
    
    @property
    def book_name(self):
        return '經典書籍:' + self.name
    

class Publish(models.Model):
    name = models.CharField(max_length=32, verbose_name='出版社名稱')
    addr = models.CharField(max_length=32, verbose_name="出版社地址")
    
    class Meta:
        db_table = "publish"
    
    
    def __str__(self):
        return self.name
        

class Authors(models.Model):
    name = models.CharField(max_length=32, verbose_name='作者姓名')
    age = models.IntegerField(verbose_name="作者的年齡")
    
    class Meta:
        db_table = "authors"
# 序列化類
from rest_framework import serializers
from datetime import datetime 
from rest_framework.exceptions import ValidationError
from .models import Book

def check_name(book_name):
    if "sb" in book_name:
        raise ValidationError("包含不合法的輸入 sb ")
    else:
        return book_name


class BookSerializer(serializers.Serializer):
    # book_name = serializers.CharField(source="name", max_length=8, validators=[check_name])
    # 嘗試透過 在模型層定義方法 返回 book_name 不適用 source
    book_name = serializers.CharField()
    price = serializers.DecimalField(max_digits=5, decimal_places=2, error_messages={"max_digits": "最少5為"})
    # 這裡的allow_null如果設定了,一般需要結合預設值一起去設定才會生效
    publish_date = serializers.DateField(allow_null=True, default=datetime.now)
    # publish = serializers.CharField(source="publish.id")  這裡不需要指定publish.id  不然會報錯
    # 如果這樣寫,會把publish的列印的樣子給前端,models可以配合__str__列印描述資訊,但是不建議這樣
    # 因為是一個物件,我需要裡面更多的資料,而不是單單列印一個publish.name (models)設定的__str__   
    # publish = serializers.CharField() 
    
    publish = serializers.CharField()
    
    # 出版社物件詳細資訊
    publish_detail = serializers.DictField()
    
    # 作者詳細資訊
    # authors_detail = serializers.ListField()
    
    
    # 可以跨表查詢
    # 
    publish_id = serializers.CharField(source="publish.id")
    # publish_id = serializers.IntegerField(source="publish.id")
    publish_name = serializers.CharField(source="publish.name")
    publish_addr = serializers.CharField(source='publish.addr')
    
    
    def create(self, validated_data):
        publish = validated_data.pop("publish")
        book = Book.objects.create(**validated_data, publish_id=publish)
        return book

透過 SerializerMethodField 去定製 只用到序列化類和模型表沒有關係

  1. 一定要配合一個方法 get_欄位名
  2. 其他的和上面的ListField/DictField一樣
# 嘗試透過 SerializerMethodField 返回book_name
book_name = serializers.SerializerMethodField()

# 出版社物件詳細資訊
publish_detail = serializers.SerializerMethodField()

# 作者詳細資訊
authors_detail = serializers.SerializerMethodField()

def get_publish_detail(self, obj):
    # 這個obj 就是當前序列化的 book 物件
    return model_to_dict(obj.publish)


def get_authors_detail(self, obj):
    # 因為是第三張表 不要忘記 all 拿取所有
    return [model_to_dict(author) for author in obj.authors.all()]


# 也可以自己去定製
def get_book_name(self, obj):
    return "必讀經典書籍:" + obj.name

image-20240412210047185

子序列化

  1. 定義一個類,繼承serializers.Serializer
  2. 透過名稱指定即可:publish_detail = PublisuSerializer(source="publish")
  3. 如果是多條需要指定many=True
    1. 多條,一般是多對多的第三張表
    2. 單條,一般是一對多的第三張表
# 定義子序列化類,一般是有外來鍵關係的 多對多 一對多
class PublisuSerializer(serializers.Serializer):
    # 這下面就寫要序列化給前端的欄位
    id = serializers.IntegerField()  # 出版社ID
    name = serializers.CharField()  # 出版社名稱
    addr = serializers.CharField()  # 出版社地址

class AuthorsSerializer(serializers.Serializer):
    id = serializers.IntegerField()  # 作者ID
    name = serializers.CharField()  # 作者姓名
    small_age = serializers.IntegerField(source="age")  # 作者年齡(重新命名為small_age)

class BookSerializer(serializers.Serializer):
    
    # 子序列化 展示出版社詳細資訊
    # 一樣可以透過source去指定名稱
    # 拿著 PublisuSerializer 類裡面的格式去做序列化
    publish_detail = PublisuSerializer(source="publish")  # 出版社詳細資訊序列化器
    
    # 子序列化,展示作者詳細資訊
    authors_detail = AuthorsSerializer(many=True, source="authors")  # 作者詳細資訊序列化器(多個)

image-20240412212159985

反序列化儲存 即 儲存前端校驗透過的資料

read_only和write_only

  1. 後端傳遞給前端的資料,使用read_only
  2. 後端反序列化前端傳過來的資料,寫入到資料庫裡面,使用write_only
  3. read_only和write_only一起使用
  4. 如果是read_only的欄位,不填寫前端不會報錯必填。
  5. 反序列化的欄位,可以隨意命名,跟表欄位沒關係,但是後續儲存和修改要對應好才行。
# 注意:
	1 read_only write_only 控制序列化類中某些欄位,只用來序列化或反序列化
    2 重寫updata和create,儲存邏輯,我們自己寫
    3 檢視類中 serializer = BookSerializer(instance=pk, data=request.data)
    	-後續在序列化類中的update中def update(self, instance, validated_data):
        -instance就是當時給的

image-20240412214843332

程式碼

# 序列化類.py
from rest_framework import serializers
from datetime import datetime 
from rest_framework.exceptions import ValidationError
from .models import Book
from django.forms.models import model_to_dict


# 定義子序列化類,一般是有外來鍵關係的 多對多 一對多
class PublisuSerializer(serializers.Serializer):
    # 這下面就寫要序列化給前端的欄位
    id = serializers.IntegerField()
    name = serializers.CharField()
    addr = serializers.CharField()

class AuthorsSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField()
    small_age = serializers.IntegerField(source="age")

class BookSerializer(serializers.Serializer):
    book_name = serializers.CharField(source="name")
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    publish_date = serializers.DateField(allow_null=True, default=datetime.now)
    
    # 子序列化 展示出版社詳細資訊
    # 一樣可以透過source去指定名稱
    publish_detail = PublisuSerializer(source="publish", read_only=True)
    
    # 子序列化,展示作者詳細資訊
    authors_detail = AuthorsSerializer(many=True, source="authors", read_only=True)
    
    # 反序列化 儲存資料 
    # 這裡的publish 是models的一對多外來鍵 前端傳入數字 會自動根出版社的id做對應
    publish = serializers.IntegerField(write_only=True)
    # 多對多 是列表 所以要使用ListField
    authors = serializers.ListField(write_only=True)
    

    def create(self, validated_data):
        # {'name': '天龍八部', 'price': Decimal('89.00'), 'publish_date': datetime.datetime(2024, 4, 12, 22, 18, 5, 785495), 'publish': 1, 'authors': [1]}
        publish_id = validated_data.pop("publish")
        authors = validated_data.pop("authors")
        
        # 先獲取物件
        book = Book.objects.create(**validated_data, publish_id=publish_id)
        
        # 向中間表插入資料
        book.authors.add(*authors)
        
        return book

	
    # 更新1 前端傳入的是 pk
    def update(self, instance, validated_data):

        publish_id = validated_data.pop("publish")
        authors = validated_data.pop("authors")
        
        # 下面的方法會報錯 因為uodate返回的並不是qs物件
        # book = Book.objects.filter(pk=instance).update(**validated_data, publish_id=publish_id)
        # book.authors.set(*authors)  # AttributeError: 'int' object has no attribute 'authors'
        
        book_qs = Book.objects.filter(pk=instance) # 找到這個qs物件
        book_qs.update(**validated_data, publish_id=publish_id) # 使用qs去更新
        obj = book_qs.first()
        obj.authors.set(authors)
        
    # 更新2 前端傳入的是物件
    # def update(self, instance, validated_data):
    #     publish_id = validated_data.pop("publish")
    #     authors = validated_data.pop("authors")
        
    #     for attr, value in validated_data.items():
    #         setattr(instance, attr, value)
        
    #     instance.publish_id = publish_id
    #     instance.authors.set(authors)
    #     instance.save()
    #     return instance
# views.py
from  django.shortcuts import render, HttpResponse, get_object_or_404
from django.views import View
from rest_framework import status
from .serializers import BookSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Book
from django.forms.models import model_to_dict


class BookView(APIView):
    def get(self, request):
        book_obj = Book.objects.all()
        serializer = BookSerializer(instance=book_obj, many=True)
        return Response({"code": 100, "msg": "查詢成功!", "results": serializer.data})

    
    # 新增一本書
    def post(self, request):
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({"code": 100, "msg": "新增成功!"})
        else:
            return Response({"code": 100, "msg": serializer.errors})
    
    
class BookDetailView(APIView):
    def get(self, request, pk):
        obj = Book.objects.filter(pk=pk).first()
        serializer = BookSerializer(instance=obj)
        return Response({"code": 100, "msg": serializer.data})
    
    
    def delete(self, request, pk):
        instance = get_object_or_404(Book, pk=pk)
        instance.delete()
        return Response({"code": 100, "msg": "刪除成功!"}, status=status.HTTP_204_NO_CONTENT)
    
    
    def put(self, request, pk):
        # obj = Book.objects.filter(pk=pk).first()  如果要修改,因為這裡是多表關聯,所以可以不用傳入物件,傳入pk操作更方便
        # 傳入的instance是什麼,到了update中,就是什麼。
        serializer = BookSerializer(instance=pk, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({"code": 100, "msg": "修改成功!"})
        else:
            return Response({"code": 100, "msg": serializer.errors})

ModelSerializer的使用 重要!

  1. 新增和更新,不需要再寫了,不過欄位要一一對應,不能修改,預設模型表定義的什麼欄位,序列化類裡面就要什麼欄位,(看write_only)
  2. 不過如果是新增或者修改,instance不能傳入pk了,只能傳入物件。
  3. 區域性鉤子和全域性鉤子跟之前一樣(注意層級),不要寫到Meta裡面去了,需要和Meta同級。
# views.py
def put(self, request, pk):
    obj = Book.objects.filter(pk=pk).first()  # 如果使用serializers.ModelSerializer 這裡只能傳入一個物件
    serializer = BookSerializer(instance=obj, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response({"code": 100, "msg": "修改成功!"})
    else:
        return Response({"code": 100, "msg": serializer.errors})
# models.py
from django.db import  models
from django.forms.models import model_to_dict

class Book(models.Model):
    name = models.CharField(max_length=32, verbose_name="書籍名稱")
    price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="書籍價格")
    publish_date = models.DateField(verbose_name="出版日期")
    # 這裡是一個物件,所以可以直接透過 . 獲取資料
    publish = models.ForeignKey(to='publish', on_delete=models.CASCADE, verbose_name="外來鍵,關聯出版社,設定級聯更新和級聯刪除")
    # 資料庫中不會有這個欄位。
    # 會生成第三張表 authors
    authors = models.ManyToManyField(to="Authors")
    
    class Meta:
        db_table = "book"

class Publish(models.Model):
    name = models.CharField(max_length=32, verbose_name='出版社名稱')
    addr = models.CharField(max_length=32, verbose_name="出版社地址")
    
    class Meta:
        db_table = "publish"
    
    
    def __str__(self):
        return self.name
        

class Authors(models.Model):
    name = models.CharField(max_length=32, verbose_name='作者姓名')
    age = models.IntegerField(verbose_name="作者的年齡")
    
    class Meta:
        db_table = "authors"
# 自定義序列化類.py
from rest_framework import serializers
from datetime import datetime 
from rest_framework.exceptions import ValidationError
from .models import Book
from django.forms.models import model_to_dict

# 定義子序列化類,一般是有外來鍵關係的 多對多 一對多
class PublisuSerializer(serializers.Serializer):
    # 這下面就寫要序列化給前端的欄位
    id = serializers.IntegerField()
    name = serializers.CharField()
    addr = serializers.CharField()

class AuthorsSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField()
    small_age = serializers.IntegerField(source="age")

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book # 要關聯的表
        fields = "__all__"  # 要關聯哪些欄位
        # 如果要關聯指定的欄位 加入到一個列表裡面 列表裡面放進去欄位
        # 這個欄位需要用引號包裹起來
        # fields = ["name", "publish" ...]  # 
        
        # 做控制,有的使用序列化,有的使用反序列化
        extra_kwargs = {
            "publish": {"write_only": True},
            "name": {"max_length": 8}, # 控制引數
            "authors": {"write_only": True}
        }
        
    # 如果有自己額外的子序列化欄位,需要class Meta 同級新增, 不要寫到 Meta 內部了!!!
    publish_detail = PublisuSerializer(read_only=True, source="publish")
    authors_detail = AuthorsSerializer(read_only=True, source="authors", many=True)
    

相關文章