1. 程式人生 > 實用技巧 >Book系列十大介面

Book系列十大介面

一. APIView版本#

1. models.py#

Copy
from 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 自定義異常處理#

Copy
from 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 自定義錯誤型別

Copy
class 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=