Book系列十大介面
阿新 • • 發佈:2020-07-28
一. APIView版本#
1. models.py#
Copyfrom django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
# 自定義欄位Char型別
class CustomChar(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super().__init__(max_length=self.max_length, *args, **kwargs)
def db_type(self, connection):
return f'Char({self.max_length})'
# 定義所有的表共有的欄位
class CommonModel(models.Model):
is_delete = models.BooleanField(default=False, verbose_name='True標記被刪除的資料, False標記正常使用的資料')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='建立時間')
update_time = models.DateTimeField(auto_now=True , verbose_name='最後更新時間')
class Meta:
abstract = True
class Book(CommonModel):
"""
引數拓展:
blank: 指定布林值. 用來表示admin後臺管理該欄位時候可以為空.
help_text: 指定字串. 用來表示admin後臺管理的提示資訊.
外來鍵關聯:
to_field: 指定關聯的表的外來鍵欄位. 預設不寫,關聯到關聯表的主鍵值.
db_constraint: 邏輯上的關聯,實質上沒有外來鍵練習,增刪不會受外來鍵影響,以及不影響orm查詢.
ForeignKey 與 OneToOneField
由原始碼得知OneToOneField繼承ForeignKey, 並且預設制定了unique=True引數
ForeignKey(to='AuthorDetail', unique=True)
OneToOneField(to='AuthorDetail')
on_delete引數:
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
"""
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32, verbose_name='書名', help_text=''
'這裡填寫書名')
price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='書價格')
publish = models.ForeignKey(to='Publish', to_field='id', on_delete=models.DO_NOTHING, db_constraint=False)
# 提示: to_field不能指定pk, 而是需要指定對應關聯表的實際欄位. 如果指定pk, 將會丟擲如下異常:
'''
publish = models.ForeignKey(to='Publish', to_field='pk', on_delete=models.DO_NOTHING, db_constraint=False)
ERRORS:
api.Book.publish: (fields.E312) The to_field 'pk' doesn't exist on the related model 'api.Publish'.
'''
authors = models.ManyToManyField(to='Author', db_constraint=False)
# 半自動錶的建立db_constraint引數的存在就會丟擲如下異常:
'''
TypeError: __init__() got an unexpected keyword argument 'db_contraint'
'''
# authors = models.ManyToManyField(to='Author', through_fields=('book', 'author'), through='Author2Book', db_constraint=False)
class Meta:
"""
引數拓展:
db_table: 指定字串. 用來修改表名
abstract: 指定布林值. 用來建立抽象表, 該表不會在資料庫中建立
unique_together: 指定容器. 用來建立多欄位唯一
index_together: 指定容器. 用來建立多欄位之間的聯合索引
verbose_name: 指定字串. 用來admin中顯示錶名, 預設加字尾s.
verbose_name_plural: 指定字串. 用來admin中顯示錶名, 預設不加字尾s.
"""
verbose_name = '圖書表'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
# @property # 提示: property裝飾器可以不指定
# def publish_name(self):
# return self.publish.name
# @property # 提示: property裝飾器可以不指定
def authors_list(self):
return [{'name': author_obj.name, 'sex': author_obj.get_gender_display()} for author_obj in self.authors.all()]
# class Author2Book(models.Model):
# author = models.ForeignKey(to='Author', on_delete=models.DO_NOTHING, db_constraint=False)
# book = models.ForeignKey(to='Book', on_delete=models.NOT_PROVIDED, db_constraint=False)
class Publish(CommonModel):
name = models.CharField(max_length=32, verbose_name='出版社名')
addr = models.CharField(max_length=64, verbose_name='出版社地址')
class Meta:
verbose_name = '出版社表'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Author(CommonModel):
name = models.CharField(max_length=32, verbose_name='作者姓名')
gender_choices = (
(0, '女'),
(1, '男'),
(2, '保密'),
)
gender = models.IntegerField(choices=gender_choices, verbose_name='作者性別', default=2)
author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE, db_constraint=False)
class Meta:
verbose_name = '作者表'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class AuthorDetail(CommonModel):
phone = CustomChar(max_length=11, verbose_name='作者手機號碼')
class Meta:
verbose_name = '作者詳情表'
verbose_name_plural = verbose_name
def __str__(self):
return self.phone
2. ser.py 自定義序列化.py檔案
Copy
from rest_framework import serializers
from . import models
class BookListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
print('instance:', instance)
print('validated_data:', validated_data)
print('self.child:', self.child)
"""
return [
self.child.create(attrs) for attrs in validated_data
]
"""
return [self.child.update(instance[i], attrs) for i, attrs in enumerate(validated_data)]
# 提示: 序列化操作的是資料庫的表,推薦使用ModelSerializer
class BookModelSerializer(serializers.ModelSerializer):
# 第一種方式: 通過指定引數read_only=True, 在反序列化的時候不需要傳該欄位指定的值
publish_name = serializers.CharField(source='publish.name', read_only=True)
# 第二種方式: 在模型類中寫方法, 通過方法關聯到這裡的欄位. 如authors_list欄位
'''
def authors_list(self):
return [{'name': author_obj.name, 'sex': author_obj.get_gender_display()} for author_obj in self.authors.all()]
'''
class Meta:
"""
depth: 指定整數. 表示跨表查詢的深度.
如果指定2, 查詢的時候就會將本例項中Book表關聯的表, 以及關聯表的關聯的表所有的資料獲取出來.
"""
model = models.Book
fields = ('id', 'name', 'price', 'publish', 'authors', 'publish_name', 'authors_list')
depth = 0
extra_kwargs = {
'publish': {'write_only': True},
'authors': {'write_only': True},
'publish_name': {'read_only': True},
'authors_list': {'read_only': True},
}
lis
3. views.py
Copy
from rest_framework.views import APIView
from . import models
from . import ser
from utils.exception import NonentityError
from utils.response import CommonResponse
class BookAPIView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
instance = models.Book.objects.filter(pk=pk, is_delete=False).first()
serializer = ser.BookModelSerializer(instance=instance)
else:
instance = models.Book.objects.filter(is_delete=False)
serializer = ser.BookModelSerializer(instance=instance, many=True)
return CommonResponse(results=serializer.data)
def post(self, request, *args, **kwargs):
if isinstance(request.data, list):
serializer = ser.BookModelSerializer(data=request.data, many=True)
elif isinstance(request.data, dict):
serializer = ser.BookModelSerializer(data=request.data)
else:
raise NonentityError('Add data through lists or dictionaries!')
serializer.is_valid(raise_exception=True)
serializer.save()
return CommonResponse(results=serializer.data)
def put(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
instance = models.Book.objects.get(pk=pk)
serializer = ser.BookModelSerializer(instance=instance, data=request.data)
elif isinstance(request.data, list):
# request.data = [{'id': 1, 'name': 'xxx', 'price': 'xxx'}]
pks = [dic.get('id') for dic in request.data]
instance = models.Book.objects.filter(pk__in=pks)
serializer = ser.BookModelSerializer(instance=instance, data=request.data, many=True)
else:
raise NonentityError(
'Specifies that the keyword can be partially modified or that the dictionary \
format of the list can be modified multiple times!')
serializer.is_valid(raise_exception=True)
serializer.save()
return CommonResponse(results=serializer.data)
def delete(self, request, *args, **kwargs):
pk = kwargs.get('pk')
pks = []
pks = pks.append(pk) if pk else request.data.get('pks')
# {'pks': [1, 2, 3]}
affected_rows = models.Book.objects.filter(pk__in=pks).update(
is_delete=True)
return CommonResponse(results={'affected_rows': affected_rows})
4. urls.py
Copy
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'books/$', views.BookAPIView.as_view()),
url(r'books/(?P<pk>[^/.]+)', views.BookAPIView.as_view())
]
5. utils 自定義工具包
1) exception_handler.py 自定義異常處理#
Copyfrom rest_framework.views import exception_handler
from rest_framework import status
from .response import CommonResponse
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if not response:
obj = CommonResponse(code=2000, messages='失敗', error=str(exc), results=str(context),
status=status.HTTP_403_FORBIDDEN)
else:
obj = CommonResponse(code=2001, messages='失敗', error=str(response.data), results=str(context),
status=status.HTTP_403_FORBIDDEN)
return obj
2) exception.py 自定義錯誤型別
Copyclass NonentityError(Exception):
def __init__(self, value):
self.value = value
super().__init__()
def __str__(self):
return '< %s >' % self.value
3) response.py 自定義封裝response物件
Copy
from rest_framework.response import Response
class CommonResponse(Response):
def __init__(self, code=1000, messages='成功', results=None, error=None,
status=None,
template_name=None, headers=None,
exception=False, content_type=None, **kwargs):
data = {
'code': code,
'messages': messages,
}
data.update(kwargs)
if results:
data['results'] = results
if error:
data['error'] = error
super().__init__(data=data, status=status,
template_name=template_name, headers=headers,
exception=exception, content_type=content_type)
4) throttle.py 自定義頻率校驗
Copy
from rest_framework.throttling import SimpleRateThrottle
class CustomSimpleRateThrottle(SimpleRateThrottle):
scope = 'custom'
def get_cache_key(self, request, view):
# 'REMOTE_ADDR': '127.0.0.1',
# print(request.META.get('REMOTE_ADDR'))
return request.META.g
6. 總結
Copy
# 設計模型表的總結
1. 所有的表應該都有三個基本欄位: is_delete, create_time, update_time
因此新增一個類繼承Model, 之後的所有的類表繼承它即可.
注意: 新增的類表預設會在執行資料庫遷移命令以後, 會在資料庫中生成,
因此應該新建Meat類, 指定abstract=True抽象表, 這樣它就不會生成該表了.
2. 外來鍵關聯欄位需要注意如下幾種問題
1) 一對一關係要建立在查詢次數較多的一行
2) 一對多關係要建立在多的一方, 如果建立在一的一行, 那麼以後一的一行該外來鍵對應的值將會是一個'[1, 2, 3]'這種結構.
這不是我們想要的. 我們最起碼應該滿足資料的資料設計儲存規範.
3) 級聯關係: 建立外來鍵關聯的欄位要著重考慮on_delete引數.
如果一方沒了, 那麼另一方應該沒有. 那麼使用models.CASCADE(提示: 一對一關係表, 一般都是指定這個引數)
如果一方沒了, 那麼另一方應該存在. 那麼使用models.DO_NOTHING
如果一方沒了, 那麼另一行應該設定為空. 那麼使用models.SET_NULL
如果一方沒了, 那麼另一行應該轉移到設定的預設情況上. 那麼使用models.SET_DEFAULT
4) 斷關聯: 建立外來鍵關聯的欄位最好使用db_constraint=False.
使用它可以提升對資料的操作, 不用依照外來鍵之間刪除資料, 新增資料的限制, 就可以直接對資料進行操作,
使用這種名義上的關聯能最大化的提升資料庫增刪改的效率.
缺點: 如果直接操作資料庫會出現髒資料, 因此不要直接操作資料庫, 程式碼層面對資料的操作, 可以進行有效的控制即可
3. 疑問: 為什麼建立半自動錶使用db_constraint=False就會出現問題???
# 序列化器實現總結
1. 序列化操作涉及到資料庫的操作, 推薦使用ModelSerializer
2. depth: 指定整數. 表示跨表查詢的深度. 提示: 一般都不深度查詢
3. 重點: 關於外來鍵關聯, 序列化 與 反序列化 是有所不同的
1) 專門用與序列化的欄位設計: 新建一個外來鍵欄位指定只讀
2種方法:
第一種: 序列化器中使用source
第二種: 模型類中寫方法, 在fields中宣告
注意: 該欄位只讀, 可在extra_kwargs中宣告
2) 專門用與反序列化的欄位設計: 預設的外來鍵欄位指定只寫.
# 檢視方法實現總結
# get
# 如何區分請求過來是獲取單條還是多條資料?
先配置2條路由, 2條路由都指向同一個檢視類. 再通過url傳遞過來kwargs中是時候有pk值.
有: 單條資料 沒有: 多條資料
# 前提: 所有的獲取都需要在過濾is_delete=True的欄位, 獲取的只是沒有標記被刪除的is_delete=False的資料
# 獲取單條資料: 直接獲取到資料物件, 再使用自定義的序列化類序列話資料. 拿到序列化之後的結果
# 獲取多條資料: 直接獲取到queryset物件, 序列化時需要指定many=True, 即可
# post
# 如何區分請求過來是新增單條還是多條資料?
單條資料格式: {}
多條資料格式: [{}, {}]
# 新增單條資料 和 新增多條資料
資料都是從body中獲取, 反序列化時指定的引數是data, 還需要注意的就是多條資料需要指定many=True.
提示: 新增多條資料, ListSerializer中定義了create方法
本質就是通過for迴圈, 再呼叫ModelSerializer中的create方法.
def create(self, validate_data):
# self.child就是當前檢視中指行序列化類例項化得到的物件
return [self.child.create(attrs) for attrs in validate_data]
# put
# 如何區分請求過來是修改單條還是多條資料?
判斷: 通過路由的又名分組, 到kwargs中時候能獲取pk值來判斷
單條資料格式: pk -> {}
多條資料格式: [{'pk': ... }, {'pk': ... }]
# 修改單條資料
先獲取傳遞過來的pk, 過濾出需要修改的資料物件
再往序列化的類中傳遞需要修改的物件, 以及該物件修改的資料
# 修改多條資料
先將傳過來的列表套字典格式的資料中的所有字典中pk獲取, 再使用雙下劃線過濾出對應的所有物件, 返回一個queryset物件.
再往序列換的類很重傳遞需要修改的queryset物件, 以及傳遞過來的要修改成什麼樣子的資料, 注意: 需要指定many=True
提示: 上面指定了many=True, 序列化完畢以後返回的是一個由ListSerializer類. ListSerializer類中定義了create,
但是沒辦法書寫update方法, 因此需要我們重寫.
步驟:
1) 新建一個類, 繼承ListSerializer
2) 重寫create方法
def create(self, instance, validate_data):
return [self.child.update(instance[i], attrs) for i, attrs in enumerate(validate_data)]
3) 在當前檢視中執行序化類中在其, Meta中宣告處理many=True時的類. list_serializer_class = 新建類名
# delete
# 如何區分請求過來是修改單條還是多條資料?
單條資料: 判斷kwargs中是否有pk值
多條資料: {'pks': [1, 2, 3]}
# 刪除單個 和 刪除多個數據
提示: 不是真正的刪除, 而是修改對應資料中的is_delete欄位等於True
刪除單個可
二. GenericAPIView版本
Copy
from rest_framework.generics import GenericAPIView
from . import models
from . import ser
from utils.response import CommonResponse
from utils.exception import NonentityError
class BookAPIView(GenericAPIView):
queryset = models.Book.objects.all()
serializer_class = ser.BookModelSerializer
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
instance = self.get_object()
# is_delete=True表示資料是做了刪除標記的
if instance.is_delete:
raise NonentityError('The data as well does not exist anymore!')
else:
serializer = self.get_serializer(instance=instance)
else:
instance = self.get_queryset().filter(is_delete=False)
serializer = self.get_serializer(instance=instance, many=True)
return CommonResponse(results=serializer.data)
def post(self, request, *args, **kwargs):
if isinstance(request.data, list):
serializer = self.get_serializer(data=request.data, many=True)
elif isinstance(request.data, dict):
serializer = self.get_serializer(data=request.data)
else:
raise NonentityError('Add data through lists or dictionaries!')
serializer.is_valid(raise_exception=True)
serializer.save()
return CommonResponse(results=serializer.data)
def put(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
instance = self.get_object()
serializer = self.get_serializer(instance=instance, data=request.data, partial=True)
elif isinstance(request.data, list):
# request.data = [{'id': 1, 'name': 'xxx', 'price': 'xxx'}]
pks = [dic.get('id') for dic in request.data]
instance = self.get_queryset().filter(pk__in=pks)
serializer = self.get_serializer(instance=instance, data=request.data, many=True)
else:
raise NonentityError(
'Specifies that the keyword can be partially modified or that the dictionary \
format of the list can be modified multiple times!')
serializer.is_valid(raise_exception=True)
serializer.save()
return CommonResponse(results=serializer.data)
def delete(self, request, *args, **kwargs):
pk = kwargs.get('pk')
pks = []
pks = pks.append(pk) if pk else request.date.get('pks')
affected_rows = self.get_queryset().filter(pk__in=pks).update(is_delete=True)
return CommonResponse(results={'affected_rows': affected_rows})
總結
Copy
主要要明白GenericAPIView中提供的三種主要的方法的返回值
self.get_object() 返回資料物件
self.get_queryset() 返回queryset物件. 因此這裡就可以繼續有filter(), update()等連點操作
self.get_serializer() many=