Day3 你必需要知道的drf最佳實踐的十件事

翻譯文章,原文地址:medium.com/profil-soft…python

1. ViewSets

viewsets的好處是使得你的代碼保持一致,而且免於重複。若是你編寫的views不止去作一件事,那麼viewsets就是你想要的東西。數據庫

舉例來講,若是你有一個model叫作Tag,你須要列表、建立和詳情的功能,你能夠定義一個viewset:django

from rest_framework import mixins, permissions
from rest_framework.viewsets import GenericViewSet


class TagViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
    """ The following endpoints are fully provided by mixins: * List view * Create view """
    queryset = Tag.objects.all()
    serializer_class = TagSerializer
    permission_classes = (permissions.IsAuthenticated,)
複製代碼

viewset的mixins能夠被自由組合,你能夠定義本身的mixins或者使用ModelViewSet。json

ModelViewset能夠爲你提供如下方法:.list(),.retrieve(), .create(), .update(), .partial_update(), .destroy()api

此外,當你使用viewsets時,也會令你的路由配置更加的清晰。bash

from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter


api_router = DefaultRouter()
api_router.register(r'tag', TagViewSet, 'tag')

urlpatterns = [
   url(r'^v1/', include(api_router.urls, namespace='v1'))
]

複製代碼

如今,你的viewset能夠幫你實現如下功能:app

  • 獲取Tag列表,發送GET請求給 v1/tag/
  • 建立Tag,發送POST請求給 v1/tag/
  • 獲取特定Tag,發送GET請求給v1/tag/<tag_id>

你甚至能夠在viewset裏面經過@action裝飾器添加一些自定義的路由。ide

2. 理解不一樣類型的serializers

做爲一個DRF的使用者,你沒必要太去關心views或者路由配置,因此你可能會把絕大部分精力放在serializers上來。函數

serializers是充當Django的model及其表現形式(例如json)之間的翻譯器。每個serializer可以既被用做讀也可用做寫,初始化的方式決定了它將執行的動做。咱們能夠區分出三種不一樣類型的serializer: create, update, retrieveui

若是你想要在序列化器外部傳輸數據,下面是一個例子:

def retrieve(self, request, *args, **kwargs):
    instance = self.get_object()
    serializer = ProfileSerializer(instance=instance)
    return Response(serializer.data)
複製代碼

可是建立時,你須要另外一種寫法:

def create(self, request, *args, **kwargs):
    serializer = ProfileSerializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save()
    return Response(serializer.data)
複製代碼

最後,當你更新一個實例,你不但要提供instance,也要提供date:

def update(self, request, *args, **kwargs):
    instance = self.get_object()
    serializer = ProfileSerializer(
        instance=instance,
        data=request.data
    )
    serializer.is_valid(raise_exception=True)
    serializer.save()
    return Response(serializer.data)
複製代碼

serializer.save()會基於初始化時的參數傳遞調用適當的內部方法。

3. 使用SerializerMethodField

SerializerMethodField是一個只讀的字段,經過在其附加到的serializer classs上調用相應的方法,在請求處理時計算其值。

舉例來講,你有一個model,裏面有一個字段datetime存儲的是models.DateTimeField類型,可是你想在序列化時,得到timestamp類型的數據:

from rest_framework import serializers


class TagSerializer(serializers.ModelSerializer):
    created = serializers.SerializerMethodField()
    
    class Meta:
        model = Tag
        fields = ('label', 'created')
        
    def get_created(self, obj):
        return round(obj.created.timestamp())
複製代碼

SerializerMethodField接收method_name,可是一般使用默認的命名方法會更爲便捷,好比get_<field_name>。另外你要確保,不會爲任何繁重的操做增長方法字段的負擔。

4. 使用source參數

不少狀況下,你的model裏面定義的字段,與你想要序列化的字段不同。你可使用source參數,輕鬆解決這個問題。

舉個例子:

from rest_framework import serializers
class TaskSerializer(serializers.ModelSerializer):
    job_type = serializers.CharField(source='task_type')

    class Meta:
        model = Task
        fields = ('job_type',)
複製代碼

模型中的task_type會被轉換成job_type。這個操做不光適用於讀,還適用於寫。

另外,你還能夠藉助點語法去從關聯的模型中獲取字段。

owner_email = serializers.CharField(source='owner.email')
複製代碼

5. 序列化字段的驗證

除了在初始化serializer字段和serializer.validate()hook能夠傳遞的validators參數以外,此外還有一種字段級別的驗證,能夠幫你爲單獨的每一個字段定義它們本身的驗證方法。

我發現它有用的緣由有兩個:首先,它能夠對特別的字段進行校驗,進行解耦。其次,它能夠產生結構化的錯誤響應。

這種驗證方式的使用,和SerializerMethodField特別類似,只是這時候的函數名字形如def validate_<field_name>。舉個例子:

from rest_framework import serializers

class TransactionSerializer(serializers.ModelSerializer):
    bid = serializers.IntegerField()

    def validate_bid(self, bid: int) -> int:
        if bid > self.context['request'].user.available_balance:
            raise serializers.ValidationError(
                _('Bid is greater than your balance')
            )
        return bid
複製代碼

若是驗證錯誤,會獲得下面這樣的輸出:

{
   "bid": ["Bid is greater than your balance"]
}
複製代碼

驗證方法必需要返回一個值,以後會傳給model實例。

另外要記住,字段級別的驗證將會在serializer.validate()以前被serializer.to_internal_value()調用。

6. 把值直接傳給save方法

某些狀況下,將值從序列化器外部直接傳遞到其save()方法很方便。

此方法將採用能夠等同於序列化對象的參數。以這種方式傳遞的值將不會獲得驗證。它可用於強制覆蓋初始數據。

serializer = EmailSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(owner_id=request.user.id)
複製代碼

7. 使用CurrentUserDefault

若是須要設置用戶,比上面的例子更好的是,使用CurrentUserDefault,這時候沒必要去重寫view了。

from rest_framework import serializers

class EmailSerializer(serializers.ModelSerializer):
    owner = serializers.HiddenField(
        default=serializers.CurrentUserDefault()
    )
複製代碼

這將會作兩件事。首先,將在請求對象中認證的用戶設置爲默認用戶。其次,由於使用了HiddenField,所以不會考慮任何傳入的數據,因此不可能會設置成別的用戶。

8. serializers的初始數據

有時候你須要去獲取serializer的最原始的數據。這是由於數據已經經過運行serializer.is_valid()進行了修改,或者須要在validated_data尚不可用時比較驗證方法中另外一個字段的值。

數據可以經過serializer.initial_data被獲取到,格式是dict,舉個例子:

from rest_framework import serializers


class SignupSerializer(serializers.ModelSerializer):
    password1 = serializers.CharField()
    password2 = serializers.CharField()

    def validate_password1(self, password1):
        if password1 != self.initial_data['password2']:
            raise serializers.ValidationError(
                'Passwords do not match'
            )
複製代碼

9. 在嵌套序列化程序中處理多個建立/更新/刪除

大多數時候,序列化器是徹底簡單的,而且有必定的經驗,沒有什麼可能出錯。可是,有一些限制。當您必須在一個高級序列化程序中支持嵌套序列化程序中的多個建立,更新和刪除操做時,事情可能會有些棘手。

這須要權衡:要選擇處理較多的請求數量,仍是在一個請求裏處理較長的時間。

默認狀況下,DRF根本不支持多個更新。很難想象它如何支持全部可能的嵌套插入和刪除類型。這就是DRF的建立者選擇靈活性而非現成的「萬能」解決方案的緣由,並把特權留給了咱們。

在這種狀況下,能夠遵循兩種路徑:

我建議至少選擇一次第二個選項,這樣您就會知道其中的含義。

在分析傳入數據以後,在大多數狀況下,咱們能夠作出如下假設:

  • 全部應更新的實例都有ID,
  • 全部應建立的實例都沒有ID,
  • 全部應刪除的實例都都存在於數據存儲(例如數據庫)中,但不會出如今傳入的request.data中。

基於此,咱們知道如何處理列表中的特定實例。如下是詳細顯示此過程的代碼段:

class CUDNestedMixin(object):
 @staticmethod
    def cud_nested(queryset: QuerySet, data: List[Dict], serializer: Type[Serializer], context: Dict):
        """ Logic for handling multiple updates, creates and deletes on nested resources. :param queryset: queryset for objects existing in DB :param data: initial data to validate passed from higher level serializer to nested serializer :param serializer: nested serializer to use :param context: context passed from higher level serializer :return: N/A """
        updated_ids = list()
        for_create = list()
        for item in data:
            item_id = item.get('id')
            if item_id:
                instance = queryset.get(id=item_id)
                update_serializer = serializer(
                    instance=instance,
                    data=item,
                    context=context
                )
                update_serializer.is_valid(raise_exception=True)
                update_serializer.save()
                updated_ids.append(instance.id)
            else:
                for_create.append(item)

        delete_queryset = queryset.exclude(id__in=updated_ids)
        delete_queryset.delete()

        create_serializer = serializer(
            data=for_create,
            many=True,
            context=context
        )
        create_serializer.is_valid(raise_exception=True)
        create_serializer.save()
複製代碼

這是高級序列化程序如何利用此mixin的簡化版本:

from rest_framework import serializers

class AccountSerializer(serializers.ModelSerializer, CUDNestedMixin):
    phone_numbers = PhoneSerializer(
        many=True,
        source='phone_set',
    )

    class Meta:
        model = User
        fields = ('first_name', 'last_name', 'phone_numbers')

    def update(self, instance, validated_data):
        self.cud_nested(
            queryset=instance.phone_set.all(),
            data=self.initial_data['phone_numbers'],
            serializer=PhoneSerializer,
            context=self.context
        )
        ...
        return instance
複製代碼

請記住,嵌套對象應使用initial_data而不是validated_data

那是由於運行驗證會在序列化器的每一個字段上調用field.to_internal_value(),這可能會修改特定字段存儲的數據(例如,經過將主鍵更改成模型實例)。

10. 覆蓋數據以強制排序

經過在view上的queryset添加排序,能夠輕鬆地實現對列表視圖的排序,可是在還應該對嵌套資源進行排序的狀況下,並非那麼簡單。

對於只讀字段,能夠在SerializerMethodField中完成,可是在必須寫字段的狀況下該怎麼辦?

在這種狀況下,能夠覆蓋序列化程序的data屬性,如如下示例所示:

@property
def data(self):
    data = super().data
    data['phone_numbers'].sort(key=lambda p: p['id'])
    return data
複製代碼

結論:

但願您在本文中找到了一些有趣的新技術。有新的drf使用技巧或想法,歡迎分享!

相關文章
相關標籤/搜索