前言
ModelSerializer
中還具有一些高階用法,如批量更新、批量刪除、批量建立等。
但是批量過來的資料格式都需要與前端做好協商,什麼樣的資料格式是單條操作,什麼樣的資料格式是批量操作。
如下,對於單條操作而言,只需要傳入編號即可,而批量操作則需要在請求體中傳入[]
以及被操作的主鍵編號。
模型表
書籍表、出版社表、作者表、作者詳情表
作者和作者詳情表是一對一關係
書籍和出版社表是一對多關係
書籍和作者是多對多關係
以上關係均是為ORM查詢方便所提供,但是在物理表中並沒有確切關聯
由於這些表中的資料具有商業價值,所以我們要給它設定一個欄位名為delete_status
,代表是否邏輯刪除(真實並不會刪除)。
初此之外還有create_time
代表該記錄的建立時間,以及last_update_time
代表這張表的最後更新時間。
所以我們可以給這三張欄位抽出一張抽象表,用於被其他表繼承:
from django.db import models
# Create your models here.
class BaseModel(models.Model):
delete_status = models.BooleanField(default=False) # 預設不刪除
create_time = models.DateTimeField(auto_now_add=True) # 新增記錄時自動插入
last_update_time = models.DateTimeField(auto_now=True) # 更新記錄時自動插入
class Meta:
abstract = True # 抽象類,不會建立真實表,用於繼承
class Book(BaseModel):
book_id = models.AutoField(primary_key=True)
book_name = models.CharField(max_length=32,verbose_name="書籍名稱")
book_price = models.DecimalField(max_digits=5,decimal_places=2,verbose_name="書籍價格")
publish = models.ForeignKey(to="Publish",on_delete=models.DO_NOTHING,db_constraint=False)
# 邏輯一對多,實際表沒有關聯,刪除出版社書不受影響
authors = models.ManyToManyField(to="Author",db_constraint=False)
# 邏輯多對多,實際表沒有任何關聯
class Meta:
verbose_name_plural = "書籍表"
def __str__(self):
return self.book_name
@property
def publish_name(self): # 模擬欄位,用於序列化時顯示出版社名稱
return self.publish.publish_name
@property
def author_list(self): # 模擬欄位,用於序列化時顯示多個作者的名字和性別
author_list = self.authors.all()
return [
{"author_name":author.author_name,"author_gender":author.get_author_gender_display()} for author in author_list
]
class Publish(BaseModel):
publish_id = models.AutoField(primary_key=True)
publish_name = models.CharField(max_length=32,verbose_name="出版社名稱")
publish_addr = models.CharField(max_length=32,verbose_name="出版社地址")
def __str__(self):
return self.publish_name
class Author(BaseModel):
author_id = models.AutoField(primary_key=True)
author_name = models.CharField(max_length=32,verbose_name="作者姓名")
author_gender = models.IntegerField(choices=[(1,"男"),(2,"女")])
author_detail = models.OneToOneField(to="AuthorDetail",db_constraint=False,on_delete=models.CASCADE)
# 邏輯一對一,實際上沒有什麼聯絡
class AuthorDetail(BaseModel):
author_phone = models.CharField(max_length=11)
# 二、表斷關聯
# 1、表之間沒有外來鍵關聯,但是有外來鍵邏輯關聯(有充當外來鍵的欄位)
# 2、斷關聯後不會影響資料庫查詢效率,但是會極大提高資料庫增刪改效率(不影響增刪改查操作)
# 3、斷關聯一定要通過邏輯保證表之間資料的安全,不要出現髒資料,程式碼控制
# 4、斷關聯
# 5、級聯關係
# 作者沒了,詳情也沒:on_delete=models.CASCADE
# 出版社沒了,書還是那個出版社出版:on_delete=models.DO_NOTHING
# 部門沒了,員工沒有部門(空不能):null=True, on_delete=models.SET_NULL
# 部門沒了,員工進入預設部門(預設值):default=0, on_delete=models.SET_DEFAULT
詳解序列器
進行序列化時,如何區分是建立、更新以及查詢單條或多條呢?這其實涉及到ModelSerializer
的引數問題。
def __init__(self, instance=None, data=empty, **kwargs):
如果只傳入data
引數,代表這是新增一條,則情況如下:
方法\屬性\鉤子 | 狀態 |
---|---|
is_valid() | 可用 |
initial_data() | 可用 |
validated_data | 僅在呼叫is_valid()之後可用 |
errors | 僅在呼叫is_valid()之後可用 |
data | 僅在呼叫is_valid()之後可用 |
如果沒有傳遞data
引數,則會發生如下情況:
方法\屬性\鉤子 | 狀態 |
---|---|
is_valid() | 不可用 |
initial_data() | 不可用 |
validated_data | 不可用 |
errors | 不可用 |
data | 可用 |
我們要注意傳遞引數的情況如下:
查詢單條,只會傳入instance
建立一條,只會傳入data
更新一條,會傳入instance以及data
上面沒有涉及到查詢多條,那麼在查詢多條時我們會傳遞進many
引數,在內部會執行這樣一段程式碼:
def __new__(cls, *args, **kwargs):
# We override this method in order to automatically create
# `ListSerializer` classes instead when `many=True` is set.
if kwargs.pop('many', False):
return cls.many_init(*args, **kwargs)
return super().__new__(cls, *args, **kwargs)
這句程式碼的意思是,當有many
為True
時代表這是多條操作,它將不會例項化你的自定義序列化器,因為你的自定義序列化器都是針對單條記錄的,轉而它會例項化一個叫做ListSerializer
的類,該類是內建的,並且該類支援批量建立。
下面是ListSerializer
的部分原始碼,其中child
代表你自定義的序列化器:
class ListSerializer(BaseSerializer):
child = None # 代表你自己寫的序列化器
many = True
default_error_messages = {
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
'empty': _('This list may not be empty.')
}
def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child)) # 自己寫的序列化器
self.allow_empty = kwargs.pop('allow_empty', True)
assert self.child is not None, '`child` is a required argument.'
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
super().__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
繼續向下看它的原始碼,可以發現它是支援批量建立的,但是不支援批量更新。
def update(self, instance, validated_data):
raise NotImplementedError(
"Serializers with many=True do not support multiple update by "
"default, only multiple create. For updates it is unclear how to "
"deal with insertions and deletions. If you need to support "
"multiple update, use a `ListSerializer` class and override "
"`.update()` so you can specify the behavior exactly."
)
def create(self, validated_data):
return [
self.child.create(attrs) for attrs in validated_data
]
如果我們對自己的序列化器做一個ListSerializer
,則可以繼承原生的ListSerializer
,並且指定好當有many
引數傳遞時,例項化的是我們自己寫的ListSerializer
即可。如下所示:
from rest_framework.serializers import ModelSerializer
from rest_framework.serializers import ListSerializer
from app01 import models
# 寫一個類,繼承ListSerializer,重寫update方法實現批量更新
class BookListSerializer(ListSerializer):
def update(self,instance,validated_data):
return [
self.child.update(instance[i],attrs) for i,attrs in enumerate(validated_data)
]
class BookModelSerializer(ModelSerializer):
class Meta:
list_serializer_class = BookListSerializer # 使用many引數後後用指定的類進行例項化
model = models.Book
# depth=0 # 序列化的關聯層級
fields = ("pk","book_name","book_price","authors","publish","publish_name","author_list")
# 使用property來拿到展示的資料
extra_kwargs = {
"publish":{"write_only":True}, # 不展示,但是新增或更新需要指定
"publish_name":{"read_only":True}, # 僅用於展示
"authors":{"write_only":True},
"author_list":{"read_only":True},
}
總結如下:
1.我們自己寫的序列化器都只能對單條進行操作
2.如果傳入many為True時,則會自動序列化一個內部的ListSerializer類,它支援批量建立多條,但是不支援批量更新
3.在自定義序列器中使用list_serializer_class類屬性即可指定在進行批量操縱時用哪一個類進行例項化
序列化與反序列化
檢視上面的程式碼,你可以發現在model
中的很多地方都用了property
來對例項方法進行裝飾。
它其實是為了配合序列化做的,因為我們在自定以序列化器中的fields
屬性裡將他們進行加入了,如下所示:
@property
def publish_name(self): # 模擬欄位,用於序列化時顯示出版社名稱
return self.publish.publish_name
@property
def author_list(self): # 模擬欄位,用於序列化時顯示多個作者的名字和性別
author_list = self.authors.all()
return [
{"author_name":author.author_name,"author_gender":author.get_author_gender_display()} for author in author_list
]
fields = ("pk","book_name","book_price","authors","publish","publish_name","author_list") # 返回外來鍵,出版社的名字,返回多對多外來鍵,作者的列表
並且,還指定了extra_kwargs
引數,用於指定哪些引數是序列化時用,哪些引數是反序列化時用:
extra_kwargs = {
"publish":{"write_only":True}, # 不展示,但是新增或更新需要指定
"publish_name":{"read_only":True}, # 僅用於展示
"authors":{"write_only":True},
"author_list":{"read_only":True},
}
這其實也要與前端溝通好,我序列化丟給你的資料是字串,但是你要建立時必須給我把諸如出版社、作者等資訊的pk
放進來才行。
大概意思就是,我丟給你字串讓使用者看,你建立或更新時我不要字串,我要pk
主鍵。
此外,還有一個引數叫做depth
,它規定了在序列化時是否連同外來鍵一起進行序列化,並且序列化的層級是多少,一般看一看就行了。
介面書寫
規定,對於單條操作,直接放在?
請求地址後面。放入pk
即可。
多條操作,你需要放入請求body
中,以[]
形式進行傳遞。
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import CreateModelMixin,ListModelMixin,RetrieveModelMixin,UpdateModelMixin
from rest_framework.response import Response
from app01.serializer import *
from app01 import models
class BookAPI(GenericAPIView,CreateModelMixin,ListModelMixin,RetrieveModelMixin,UpdateModelMixin):
queryset = models.Book.objects.filter(delete_status=False) # 查詢未刪除的資料
serializer_class = BookModelSerializer
def get(self,request,*args,**kwargs):
pk = kwargs.get("pk")
if not pk: # 獲取所有
return self.list(request)
# 獲取單條
return self.retrieve(request,pk)
def post(self,request,*args,**kwargs):
# 新增一條
if isinstance(request.data,dict):
return self.create(request) # 自動返回
# 新增多條
elif isinstance(request.data,list):
# 現在執行我們自己定義的ListSerializer,因為傳入many=True.由於繼承原生的ListSerializer,它自己有create方法
book_ser = self.get_serializer(data=request.data,many=True)
book_ser.is_valid(raise_exception=True) # 序列化失敗直接丟擲異常
book_ser.save()
return Response(data=book_ser.data)
def patch(self, request, *args, **kwargs):
pk = kwargs.get("pk")
print(pk)
if pk:
return self.update(request,pk)
# 改多個
# 前端傳遞資料格式[{book_id:1,book_name:xx,book_price:xx},{book_id:1,book_name:xx,book_price:xx}]
# 處理傳入的資料 物件列表[book1,book2] 修改的資料列表[{book_name:xx,book_price:xx},{book_name:xx,book_price:xx}]
book_list = []
modify_data = []
for item in request.data:
# {book_id:1,book_name:xx,book_price:xx} 取出pk,不允許修改pk
pk = item.pop("pk")
book_obj = models.Book.objects.get(pk=pk)
book_list.append(book_obj)
modify_data.append(item)
book_ser = BookModelSerializer(instance=book_list,data=modify_data,many=True,partial=True) # parital允許區域性修改,這個主要針對put,patch本身就是True
# 處理時:
# self.child.update(instance[i],attrs) for i,attrs in enumerate(validated_data) 傳入id,和要修改的資料
book_ser.is_valid(raise_exception=True)
book_ser.save()
return Response(book_ser.data)
def delete(self,request,*args,**kwargs):
pk = kwargs.get("pk")
pks = [] # 用於獲取要刪除的id,全部放入列表中
if pk:
# 刪一個
pks.append(pk)
else:
pks = request.data.get("pks")
result = models.Book.objects.filter(pk__in=pks,delete_status=False).update(delete_status=True)
if result:
return Response(data="刪除%s條記錄成功"%len(pks))
else:
return Response(data="刪除失敗,沒有要刪除的資料")
資料格式
新增資料:
# 新增一條 http://127.0.0.1:8000/api/books/ POST請求
# 請求的資料格式:
{
"book_name":"新書,單條",
"book_price": "123.00",
"publish": 1,
"authors": [
1,2
]
}
# 返回格式:
{
"pk": 12,
"book_name": "新書,單條",
"book_price": "123.00",
"publish_name": "北京出版社",
"author_list": [
{
"author_name": "雲崖",
"author_gender": "男"
},
{
"author_name": "小屁孩",
"author_gender": "女"
}
]
}
# 新增多條 http://127.0.0.1:8000/api/books/ POST請求
# 請求的資料格式:
[
{
"book_name":"新書1,多條",
"book_price": "88.00",
"publish": 1,
"authors": [
1
]
},
{
"book_name":"新書2,多條",
"book_price": "13.00",
"publish": 1,
"authors": [
2,3
]
},
{
"book_name":"新書3,多條",
"book_price": "63.00",
"publish": 1,
"authors": [
3,4
]
}
]
# 返回格式:
[
{
"pk": 18,
"book_name": "新書1,多條",
"book_price": "88.00",
"publish_name": "北京出版社",
"author_list": [
{
"author_name": "雲崖",
"author_gender": "男"
}
]
},
{
"pk": 19,
"book_name": "新書2,多條",
"book_price": "13.00",
"publish_name": "北京出版社",
"author_list": [
{
"author_name": "小屁孩",
"author_gender": "女"
},
{
"author_name": "東仙人",
"author_gender": "男"
}
]
},
{
"pk": 20,
"book_name": "新書3,多條",
"book_price": "63.00",
"publish_name": "北京出版社",
"author_list": [
{
"author_name": "東仙人",
"author_gender": "男"
},
{
"author_name": "雲散人",
"author_gender": "女"
}
]
}
]
修改資料:
# 修改一條 http://127.0.0.1:8000/api/books/1/ patch請求
# 請求的資料格式:
{
"book_name":"修改新書",
"book_price": "123.00",
"publish": 3,
"authors": [
2,3
]
}
# 返回格式:
{
"pk": 1,
"book_name": "修改新書",
"book_price": "123.00",
"publish_name": "西京出版社",
"author_list": [
{
"author_name": "小屁孩",
"author_gender": "女"
},
{
"author_name": "東仙人",
"author_gender": "男"
}
]
}
# 修改多條 http://127.0.0.1:8000/api/books/ patch請求
# 請求的資料格式:
[
{
"pk": 1,
"book_name":"修改新書",
"book_price": "123.00",
"publish": 1,
"authors": [
3,4
]
},
{
"pk": 2,
"book_name":"修改新書2",
"book_price": "123.00",
"publish": 1,
"authors": [
1,2
]
}
]
# 返回格式:
[
{
"pk": 1,
"book_name": "修改新書",
"book_price": "123.00",
"publish_name": "北京出版社",
"author_list": [
{
"author_name": "東仙人",
"author_gender": "男"
},
{
"author_name": "雲散人",
"author_gender": "女"
}
]
},
{
"pk": 2,
"book_name": "修改新書2",
"book_price": "123.00",
"publish_name": "北京出版社",
"author_list": [
{
"author_name": "雲崖",
"author_gender": "男"
},
{
"author_name": "小屁孩",
"author_gender": "女"
}
]
}
]
刪除資料:
# 刪除一條 http://127.0.0.1:8000/api/books/1/ delete請求
# 刪除多條 http://127.0.0.1:8000/api/books/ delete請求
# 請求資料格式:
{
"pks":
[1,2,3]
}