做者:HelloGitHub-追夢人物web
一旦咱們使用了視圖集,並實現了 HTTP 請求對應的 action 方法(對應規則的說明見 使用視圖集簡化代碼),將其在路由器中註冊後,django-restframework 自動會自動爲咱們生成對應的 API 接口。django
目前爲止,咱們只實現了 GET 請求對應的 action——list 方法,所以路由器只爲咱們生成了一個 API,這個 API 返回文章資源列表。GET 請求還能夠用於獲取單個資源,對應的 action 爲 retrieve,所以,只要咱們在視圖集中實現 retrieve 方法的邏輯,就能夠直接生成獲取單篇文章資源的 API 接口。api
貼心的是,django-rest-framework 已經幫咱們把 retrieve 的邏輯在 mixins.RetrieveModelMixin
裏寫好了,直接混入視圖集便可:編輯器
class PostViewSet( mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet ): serializer_class = PostListSerializer queryset = Post.objects.all() permission_classes = [AllowAny] 複製代碼
如今,路由會自動增長一個 /posts/:pk/ 的 URL 模式,其中 pk 爲文章的 id。訪問此 API 接口能夠得到指定文章 id 的資源。post
實際上,實現各個 action 邏輯的混入類都很是簡單,以 RetrieveModelMixin
爲例,咱們來看看它的源碼:性能
class RetrieveModelMixin:
""" Retrieve a model instance. """ def retrieve(self, request, *args, **kwargs): instance = self.get_object() serializer = self.get_serializer(instance) return Response(serializer.data) 複製代碼
retrieve 方法首先調用 get_object
方法獲取需序列化的對象。get_object
方法一般狀況下依據如下兩點來篩選出單個資源對象:url
get_queryset
方法(或者
queryset
屬性,
get_queryset
方法返回的值優先)返回的資源列表對象。
lookup_field
屬性指定的資源篩選字段(默認爲 pk)。django-rest-framework 以該字段的值從
get_queryset
返回的資源列表中篩選出單個資源對象。
lookup_field
字段的值將從請求的 URL 中捕獲,因此你看到文章接口的 url 模式爲 /posts/:pk/,假設將
lookup_field
指定爲 title,則 url 模式爲 /posts/:title/,此時將根據文章標題獲取單篇文章資源。
如今,假設咱們要獲取 id 爲 1 的文章資源,訪問獲取單篇文章資源的 API 接口 http://127.0.0.1:10000/api/posts/1/,獲得以下的返回結果:spa
能夠看到不少咱們須要在詳情頁中展現的字段值並無返回,好比文章正文(body)。緣由是視圖集中指定的文章序列化器爲 PostListSerializer,這個序列化器被用於序列化文章列表。由於展現文章列表數據時,有些字段用不上,因此出於性能考慮,只序列化了部分字段。rest
顯然,咱們須要給文章詳情寫一個新的序列化器了:code
from .models import Category, Post, Tag
class TagSerializer(serializers.ModelSerializer): class Meta: model = Tag fields = [ "id", "name", ] class PostRetrieveSerializer(serializers.ModelSerializer): category = CategorySerializer() author = UserSerializer() tags = TagSerializer(many=True) class Meta: model = Post fields = [ "id", "title", "body", "created_time", "modified_time", "excerpt", "views", "category", "author", "tags", ] 複製代碼
詳情序列化器和列表序列化器幾乎同樣,只是在 fields 中指定了更多須要序列化的字段。
同時注意,爲了序列化文章的標籤 tags,咱們新增了一個 TagSerializer
,因爲文章可能有多個標籤,由於 tags 是一個列表,要序列化一個列表資源,須要將序列化器參數 many
的值指定爲 True
。
如今新的序列化器寫好了,但是在哪裏指定呢?視圖集中 serializer_class
屬性已經被指定爲了 PostListSerializer
,那 PostRetrieveSerializer
應該指定在哪呢?
相似於視圖集類的 queryset
屬性和 get_queryset
方法的關係, serializer_class
屬性的值也能夠經過 get_serializer_class
方法返回的值覆蓋,所以咱們能夠根據不一樣的 action 動做來動態指定對應的序列化器。
那麼如何在視圖集中區分不一樣的 action 動做呢?視圖集有一個 action 屬性,專門用來記錄當前請求對應的動做。對應關係以下:
HTTP 請求 | 對應 action 屬性的值 |
---|---|
GET | list(資源列表)/ retrieve(單個資源) |
PUT | update |
PATCH | partial_update |
DELETE | destory |
所以,咱們在視圖集中重寫 get_serializer_class
方法,寫入咱們本身的邏輯,就能夠根據不一樣請求,分別獲取相應的序列化器了:
class PostViewSet( mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet ): # ... 省略其餘屬性和方法 def get_serializer_class(): if self.action == 'list': return PostListSerializer elif self.action == 'retrieve': return PostRetrieveSerializer else: return super().get_serializer_class() 複製代碼
後續對於其餘動做,能夠再加 elif 判斷,不過若是動做變多了,就會有不少的 if 判斷。更好的作好是,給視圖集加一個屬性,用於配置 action 和 serializer_class 的對應關係,經過查表法查找 action 應該使用的序列化器。
class PostDetailViewSet(viewsets.GenericViewSet):
# ... 省略其餘屬性和方法 serializer_class_table = { 'list': PostListSerializer, 'retrieve': PostRetrieveSerializer, } def get_serializer_class(): return self.serializer_class_table.get( self.action, super().get_serializer_class() ) 複製代碼
如今,再次訪問單篇文章 API 接口,能夠看到返回了更加詳細的博客文章數據了: