《django by example》實踐 educa 項目(三)

關鍵詞:django by example


點我查看本文集的說明及目錄。


本項目相關內容包括:

實現(xiàn)過程

CH10 創(chuàng)建一個在線學習平臺

CH11 緩存內容

CH12 創(chuàng)建API


CH12 創(chuàng)建一個API


上一章,我們創(chuàng)建了學生注冊和課程報讀系統(tǒng),創(chuàng)建視圖展示了課程內容,并學習了如何使用 Django 的緩存框架。本章,我們將學習如何實現(xiàn)以下功能:

  • 創(chuàng)建 RESTful API
  • 為 API 視圖處理授權和權限問題
  • 創(chuàng)建 API 的 viewsets 和 routers

創(chuàng)建一個 RESTful API


我們可能需要創(chuàng)建一個接口來使其它服務與自己的 web 應用進行交互。通過 API 可以實現(xiàn)第三方獲得信息以及操作應用程序。

我們可以通過幾種方法構建 API ,但是推薦遵守 REST 原則。REST 架構源自 Representational State Transfer 。 RESTful APIs 是基于資源的。模型代表資源,GET、POST、PUT 或 DELETE 等 HTTP 方法可以獲取、創(chuàng)建、更新或者刪除對象。內容也可以使用 HTTP 響應代碼,不同的 HTTP 響應碼表示不同的 HTTP 請求結果,比如 2XX 響應碼表示成功,4XX 響應碼表示失敗等。

RESTful API 交換數(shù)據(jù)時最常使用的格式是 JSON 和 XML 。我們使用 JSON 序列化為項目創(chuàng)建一個 REST API 。 API 將提供以下功能:

  • 獲取主題
  • 獲取可以獲得的課程
  • 獲取課程內容
  • 報讀課程

我們可以通過創(chuàng)建自定義視圖開始學習使用 Django 構建 API 。然而,一些第三方模塊可以簡化項目創(chuàng)建 API 的過程,這些第三方模塊中最受歡迎的是 Django Rest 框架。

安裝 django Rest 框架


django Rest 框架幫助用戶輕松地創(chuàng)建項目的 REST API 。我們可以從http://www.django-rest-framework.org/找到 REST 框架的所有信息。

打開 shell 并使用以下命令安裝框架:

pip install djangorestframework

編輯 educa 項目的 settings.py 文件并在 INSTALLED_APPS 中添加 rest_framework 來激活應用:

INSTALLED_APPS = ['courses', 'django.contrib.admin', 'django.contrib.auth',
                  'django.contrib.contenttypes', 'django.contrib.sessions',
                  'django.contrib.messages', 'django.contrib.staticfiles',
                  'students', 'memcache_status','rest_framework',]

然后,在 settings.py 中添加以下設置:

# REST settings

REST_FRAMEWORK = {'DEFAULT_PERMISSION_CLASSES': [
    'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly']}

我們可以使用 REST_FRAMEWORK 配置 API 。 REST 框架提供很多配置默認行為的設置。 DEFAULT_PERMISSION_CLASSES 設置指定讀取、創(chuàng)建、更新或者刪除對象的默認權限。 我們將 DjangoModelPermissionsOrAnonReadOnly 設置為唯一的權限類。這個類基于 Django 權限系統(tǒng),權限系統(tǒng)允許用戶創(chuàng)建、更新和刪除對象,但匿名用戶只能讀取對象。后續(xù)我們將學習更多的權限。

REST 框架的參數(shù)設置列表見http://www.django-rest-framework.org/api-guide/settings/

定義 serializers


設置好 REST 框架后,需要指定數(shù)據(jù)如何進行序列化。輸出數(shù)據(jù)應該序列化為特定格式,輸入數(shù)據(jù)應該進行反序列化以便于處理??蚣芴峁┮韵骂悂磉M行單一對象的序列化:

  • Serializer:為普通 Python 類實例提供序列化;

  • ModelSerializer:為模型實例提供序列化,使用主鍵表示對象關系;

  • HyperlinkedModelSerializer:與 ModelSerializer 相同,但是使用鏈接表示對象關系。

我們來創(chuàng)建第一個 Serializer 。在 courses 應用目錄下創(chuàng)建下面的文件結構:

CH12-1.png

我們在 api 目錄下創(chuàng)建所有的 API 函數(shù)。編輯 serializers.py 文件并添加以下代碼:

from rest_framework import serializers

from ..models import Subject


class SubjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subject
        fields = ('id', 'title', 'slug')

這是 Subject 模型的 serializer 。serializer 的定義方式與 Form 和 ModelForm 類的定義方式相似。使用 Meta 類來指定進行序列化的模型以及模型包含的字段。如果不設置 fields 屬性,則將包含模型所有字段。

我們來測試一下 serializer 。打開命令行并使用 python manage.py shell 打開 Django shell ,運行以下代碼:

In [1]: from courses.models import Subject

In [2]: from courses.api.serializers import SubjectSerializer

In [3]: subject = Subject.objects.latest('id')

In [4]: serializer = SubjectSerializer(subject)

In [5]: serializer.data

在這個例子中,我們獲取了一個 Subject 對象,創(chuàng)建了一個 SubjectSerializer 實例,并且訪問了序列化數(shù)據(jù)。將得到以下輸出:

{'id': 4, 'title': 'Mathematics', 'slug': 'mathematics'}

我們可以看到,模型數(shù)據(jù)變成了 Python 數(shù)據(jù)類型。

理解 pasers 和 renderers

HTTP 響應返回序列化數(shù)據(jù)之前,需要將序列化數(shù)據(jù)渲染為特定格式。接收到 HTTP 請求時,我們需要解析輸入數(shù)據(jù)并在使用之前對其進行反序列化。 REST 框架包含 renderers 和 parsers 來處理這個過程。

我們來看下如何解析輸入數(shù)據(jù)。對于一個 JSON 字符串輸入,可以使用 REST 框架提供的 JSONParser 類來將其轉換為 Python 對象。在 Python shell 中執(zhí)行以下代碼:

In [6]: from io import BytesIO

In [7]: from rest_framework.parsers import JSONParser

In [8]: data = b'{"id":4,"title":"Music","slug":"music"}'

In [9]: JSONParser().parse(BytesIO(data))

你應該得到這樣的輸出:

Out[9]: {u'id': 4, u'slug': u'music', u'title': u'Music'}

REST 框架還包括 Renderer 類來幫助用戶格式化 API 響應??蚣芨鶕?jù)內容確定使用哪個 renderer 。它檢查請求的 Accept 頭來確定預期的響應內容類型,比如可以由 URL 的格式后綴確定選用的 renderer ,例如,訪問將觸發(fā) JSONRenderer 來返回 JSON 響應。

回到 shell 并執(zhí)行以下代碼使用前面的序列化例子渲染 serializer 對象:

In [10]: from rest_framework.renderers import JSONRenderer

In [11]: JSONRenderer().render(serializer.data)

輸出應該是:

Out[11]: b'{"id":4,"title":"Mathematics","slug":"mathematics"}'

這里使用 JSONRenderer 將序列化數(shù)據(jù)渲染為 JSON 格式。默認情況下,REST 框架使用兩種不同的 renderers:JSONRenderer 和 BrowsableAPIRenderer 。BrowsableAPIRenderer 提供便于瀏覽 API 的 web 接口。我們可以通過設置 REST_FRAMEWORK 的 DEFAULT_RENDERCLASSES 來更改默認的 renderer 類。

http://www.django-rest-framework.org/api-guide/renderers/http://www.django-rest-framework.org/api-guide/parsers/中有更多關于 renderers 和 parsers 的信息。

創(chuàng)建列表和詳情視圖


REST 框架內置創(chuàng)建 API 視圖的通用視圖和 mixin 集合來實現(xiàn)獲取、創(chuàng)建、更新或者刪除模型對象的功能。http://www.django-rest-framework.org/api-guide/generic-views/中包括 REST 框架提供的所有通用 mixin 和視圖。

我們來創(chuàng)建獲取 Subject 對象的列表和詳情視圖。在 courses/api/ 目錄下新建一個 views.py 的文件,并添加以下代碼:

from rest_framework import generics

from .serializers import SubjectSerializer
from ..models import Subject


class SubjectListView(generics.ListAPIView):
    queryset = Subject.objects.all()
    serializer_class = SubjectSerializer


class SubjectDetailView(generics.RetrieveAPIView):
    queryset = Subject.objects.all()
    serializer_class = SubjectSerializer

代碼中使用了 REST 框架的通用 ListAPIView 和 RetrieveAPIView。詳細視圖中包含一個獲取給定鍵對象的 pk URL參數(shù)。兩個視圖都設置了以下屬性:

  • queryset: 獲取對象使用的基礎 QuerySet ;
  • serializer_class:序列化對象的類;

下面為視圖添加 URL模式。在 courses/api/ 目錄下新建 urls.py 的文件并添加以下代碼:

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^subjects/$', views.SubjectListView.as_view(), name='subject_list'),
    url(r'^subjects/(?P<pk>\d+)/$', views.SubjectDetailView.as_view(),
        name='subject_detail'), ]

編輯 educa 項目的 urls.py 文件并包含以下 API 模式:

url(r'^api/',include('courses.api.urls'))

使用 python manage.py runserver 運行開發(fā)服務器,打開 shell 并通過 cURL 獲取 http://127.0.0.1:8000/api/subjects/

curl http://127.0.0.1:8000/api/subjects/

你將得到類似下面的輸出:

[{"id":4,"title":"Mathematics","slug":"mathematics"},{"id":3,"title":"Music","slug":"music"},{"id":2,"title":"Physics","slug":"physics"},{"id":1,"title":"Programming","slug":"programming"}]

HTTP響應包含 JSON 格式的 Subject 對象列表。如果你的操作系統(tǒng)沒有安裝 curl,可以從http://curl.haxx.se/dlwiz/下載。除了 curl ,我們還可以使用其它工具發(fā)送 HTTP請求,比如 Postman 瀏覽器插件(可以從 https://www.getpostman.com 獲?。?。

在瀏覽器中打開 http://127.0.0.1:8000/api/subjects/ ,你將看到 REST 框架可以瀏覽的 API :

CH12-2.png

這個 HTML接口由 BrowsableAPIRenderer 提供。它展示了得到的標題和內容,并且允許用戶實現(xiàn)請求。我們可以通過在 URL 中添加 id 來訪問 Subject 對象的 API 詳情視圖。在瀏覽器中打開http://127.0.0.1:8000/api/subjects/1/,你將看到渲染為 JSON 格式的單個對象。

創(chuàng)建嵌套 serializers


我們將為 Course 模型創(chuàng)建 serializer ,編輯 api/serializers.py 文件并添加以下代碼:

from ..models import Course

class CourseSerializer(serializers.ModelSerializer):
    class Meta:
        model = Course
        fields = (
        'id', 'subject', 'title', 'slug', 'overview', 'created', 'owner',
        'modules')

我們來看下如何實現(xiàn) Course 對象序列化,打開 shell ,運行 python manage.py shell ,并運行以下代碼:

In [1]: from rest_framework.renderers import JSONRenderer

In [2]: from courses.models import Course

In [3]: from courses.api.serializers import CourseSerializer

In [4]: course = Course.objects.latest('id')

In [5]: serializer = CourseSerializer(course)

In [6]: JSONRenderer().render(serializer.data)

我們將會看到包含 CourserSerializer 設置的字段的 JSON 對象。

b'{"id":2,"subject":4,"title":"Course 2","slug":"course2","overview":"","created":"2018-04-16T15:43:35.885525Z","owner":1,"modules":[5,6]}'

結果中的 modules 管理器的相關對象序列化為主鍵列表:

"modules":[7,8]

我們希望增加更多模信息,因此需要序列化 Module 對象并進行嵌套。將剛剛添加到 api/serializers.py 中的代碼修改為:

from ..models import Course, Module


class ModuleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Module
        fields = ('order', 'title', 'description')


class CourseSerializer(serializers.ModelSerializer):
    modules = ModuleSerializer(many=True, read_only=True)

    class Meta:
        model = Course
        fields = (
            'id', 'subject', 'title', 'slug', 'overview', 'created', 'owner',
            'modules')

這里定義了 ModuleSerializer 來對 Module 模型進行序列化。然后將 modules 屬性添加到 CourseSerializer 中來嵌套 ModuleSerializer 。設置 many = True 表示對多個對象進行序列化。read_only 參數(shù)表示這個字段是只讀的,不能使用任何輸入來創(chuàng)建或者修改對象。

打開 shell 并再次創(chuàng)建 CourseSerializer 實例,使用 JSONRenderer 渲染序列化數(shù)據(jù)。這次,模塊列表使用了嵌套的 ModuleSerializer 進行了序列化:

"modules":[{"order":0,"title":"Installing Django","description":"how to install django"},{"order":1,"title":"models","description":"about django model"}]

更多序列化的相關信息見http://www.django-rest-framework.org/api-guide/serializers/

創(chuàng)建自定義視圖


REST 框架提供一個 APIView 類,可以基于 Django View 類實現(xiàn) API 功能。 APIView 與 View 的區(qū)別在于使用 REST 框架 自定義的 Request 和 Response 對象并處理 APIException 異常來返回合適的 HTTP 響應。它還包含內置的授權和權限系統(tǒng)來管理視圖訪問。

我們將為用戶報讀課程創(chuàng)建一個視圖,編輯 api/views.py 文件并添加以下代碼:

from django.shortcuts import get_object_or_404
from rest_framework.views import APIView
from rest_framework.response import Response
from ..models import Course


class CourseEnrollView(APIView):
    def post(self, request, pk, format=None):
        course = get_object_or_404(Course, pk=pk)
        course.students.add(request.user)
        return Response({'enrolled': True})

CourseEnrollView 視圖處理課程的用戶報讀事務,上面的代碼實現(xiàn)以下功能:

  • 創(chuàng)建自定義的 APIView 子類;
  • 為 POST 請求定義 post() 方法,這個視圖不允許其它方法;
  • 使用 pk URL 參數(shù)來表示課程 ID 。獲取給定 pk 參數(shù)的課程,如果沒有對象課程則引發(fā) 404 異常;
  • 將當前的用戶添加到 Course 對象的 students 多對多關系中并返回成功響應。

編輯 api/urls.py 文件并為 CourseEnrollView 添加以下 URL 模式:

url(r'^courses/(?P<pk>\d+)/enroll/$', views.CourseEnrollView.as_view(),
    name='course_enroll')

理論上,現(xiàn)在已經(jīng)可以通過 POST 請求來報讀課程,然而,我們還需要識別用戶并阻止沒有授權的用戶訪問視圖。下面來看下 API 的授權和權限如何工作。

處理授權


REST 框架通過授權類來識別請求用戶。如果授權成功,框架將 request.user 設置為授權的 User 對象,如果用戶沒有授權,Request.user 將設置為 Django AnonymousUser 實例。

REST 框架提供以下授權后端:

  • BasicAuthentication : HTTP 基本授權,客戶端在 Authorization HTTP頭中發(fā)送經(jīng)過 Base64 編碼處理的用戶名和密碼。https://en.wikipedia.org/wiki/Basic_access_authentication中有詳細介紹。

  • TokenAuthertication: 令牌授權。使用 Token 模型來保存用戶令牌。通過放在 Authorization HTTP 頭中的 token 進行授權。

  • SessionAuthertication:使用 Django 會話后端進行授權。這個后端用于從網(wǎng)站前端向 API 實現(xiàn)授權的 AJAX 請求。

我們可以通過繼承 REST 框架提供的 BaseAuthorization 類創(chuàng)建一個自定義授權后端,并覆蓋 authenticate() 方法。

每個視圖都可以進行授權,也可以使用 DEFAULT_AUTHENTICATION_CLASSES 設置全局授權。

注意:

授權僅能識別請求用戶。不能允許或者拒絕訪問視圖。我們需要使用權限來限制對視圖的訪問。

我們可以從http://www.django-rest-framework.org/api-guide/authentication/ 找到授權的所有信息。

下面將在視圖中添加 BasicAuthentication 。編輯 courses 應用的 api/views.py 并向 CourseEnrollView 添加 authentication_class 屬性:

from rest_framework.authentication import BasicAuthentication


class CourseEnrollView(APIView):
    authentication_classes = (BasicAuthentication,)

    def post(self, request, pk, format=None):
        course = get_object_or_404(Course, pk=pk)
        course.students.add(request.user)
        return Response({'enrolled': True})

這里將通過 HTTP 請求頭中的 Authorization 標頭憑證來識別用戶。

為視圖添加權限


REST 框架使用權限系統(tǒng)來限制視圖訪問。REST 框架內置的權限包括:

  • AllowAny : 沒有訪問限制,用戶授權與否都可以訪問;
  • IsAuthenticated : 只允許授權的用戶訪問;
  • IsAuthenticatedOrReadOnly : 授權用戶可以進行完全訪問,匿名用戶只能進行讀操作(如 GET 、 HEAD、或 OPTIONS 操作);
  • DjangoModelPermissions : django.contrib.auth 綁定的權限,視圖需要設置 queryset 屬性。 只有有模型訪問權限的用戶才能訪問。
  • DjangoObjectPermissions : 基于對象的 Django 權限。

如果拒絕用戶訪問,我們通常獲得以下 HTTP 錯誤碼:

  • HTTP 401 :沒授權
  • HTTP 403:權限拒絕

我們可以從http://www.django-rest-framework.org/api-guide/permissions/看到更多關于權限的介紹。

編輯 courses 應用的 api/views.py 文件并為 CourseEnrollView 添加 permission_class 屬性:

from rest_framework.permissions import IsAuthenticated


class CourseEnrollView(APIView):
    authentication_classes = (BasicAuthentication,)
    permission_classes = (IsAuthenticated,)

這里添加了 IsAuthorization 權限,將阻止匿名用戶訪問視圖,現(xiàn)在可以向 API 發(fā)送 POST 請求了。

保證開發(fā)服務器正在運行,打開 shell 并運行以下命令:

curl -i -X POST http://127.0.0.1:8000/api/courses/1/enroll/

你將得到這樣的響應:

HTTP/1.0 401 Unauthorized
Date: Mon, 05 Mar 2018 07:00:42 GMT
Server: WSGIServer/0.1 Python/2.7.10
Content-Length: 58
Vary: Accept
Allow: POST, OPTIONS
X-Frame-Options: SAMEORIGIN
Content-Type: application/json
WWW-Authenticate: Basic realm="api"

{"detail":"Authentication credentials were not provided."}

這里接收到了預期的 401 HTTP 碼,這是由于我們沒有授權。使用一個有基本權限的用戶,運行以下命令:

curl -i -X POST -u student:password http://127.0.0.1:8000/api/courses/1/enroll/

使用注冊過的用戶的憑證代替 student:password ,將會看到下面的輸出:

HTTP/1.0 200 OK
Date: Mon, 05 Mar 2018 07:07:50 GMT
Server: WSGIServer/0.1 Python/2.7.10
Vary: Accept
X-Frame-Options: SAMEORIGIN
Content-Type: application/json
Content-Length: 17
Allow: POST, OPTIONS

{"enrolled":true}

我們可以訪問 admin 網(wǎng)站并檢查用戶是否報讀了課程。

創(chuàng)建視圖集合和routers

ViewSets 幫助我們定義 API 與 REST 框架(通過 Router 對象)動態(tài)創(chuàng)建的 URLs 的交互。使用視圖集合,可以避免多個視圖使用重復邏輯。視圖集合包含 list()、create()、retrieve()、update()、partial_update() 和 destroy() 等實現(xiàn)獲取、更新、刪除等操作動作的方法。

下面為 Course 模型創(chuàng)建一個視圖集合,編輯 api/views.py 并添加以下代碼:

from rest_framework import viewsets
from .serializers import CourseSerializer


class CourseViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Course.objects.all()
    serializer_class = CourseSerializer

創(chuàng)建 ReadOnlyModelViewSet 子類,ReadOnlyModelViewSet 提供只讀權限的 list() 和 retrieve() 操作來列出對象集合或獲取單個對象。編輯 api/urls.py 并為視圖集合創(chuàng)建 router :

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

from . import views

router = routers.DefaultRouter()
router.register('courses', views.CourseViewSet)

urlpatterns = [
    url(r'^subjects/$', views.SubjectListView.as_view(), name='subject_list'),
    url(r'^subjects/(?P<pk>\d+)/$', views.SubjectDetailView.as_view(),
        name='subject_detail'),
    url(r'^courses/(?P<pk>\d+)/enroll/$', views.CourseEnrollView.as_view(),
        name='course_enroll'), 
    url(r'^', include(router.urls)), ]

這里創(chuàng)建了 DefaultRouter 對象并使用 courses 前綴注冊視圖集合,router 負責為視圖集合動態(tài)生成 URLs 。

在瀏覽器中打開 http://127.0.0.1:8000/api/,將會看到 router 在 URL 中列出所有視圖集合,如下圖所示:

CH12-3.png

可以訪問 http://127.0.0.1:8000/api/courses/ 來獲得課程列表。

可以從 http://www.django-rest-framework.org/api-guide/viewsets/了解更多關于視圖集合的消息??梢詮?a target="_blank" rel="nofollow">http://www.django-rest-framework.org/api-guide/routers/找到更多關于 router 的消息。

為視圖集合添加額外動作


Viewsets 還可以添加額外動作。我們來將 CourseEnrollView 視圖更改為自定義視圖集合動作。編輯

api/views.py 文件并更改 CourseViewSet :

from rest_framework.decorators import action

class CourseViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Course.objects.all()
    serializer_class = CourseSerializer

    @action(methods=['post'], detail=True,
            authentication_classes=[BasicAuthentication],
            permission_classes=[IsAuthenticated])
    def enroll(self, request, *args, **kwargs):
        course = self.get_object()
        course.students.add(request.user)
        return Response({'enrolled': True})

筆者注:

原文代碼為:

from rest_framework.decorators import detail_route


class CourseViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Course.objects.all()
    serializer_class = CourseSerializer

    @detail_route(methods=['post'],
                  authentication_classes=[BasicAuthentication],
                  permission_classes=[IsAuthenticated])
    def enroll(self, request, *args, **kwargs):
        course = self.get_object()
        course.students.add(request.user)
        return Response({'enrolled': True})

由于 rest_framework 3.10 版本之后將棄用 detail_route,這里使用 action( detail=True )代替了原文中的 detail_route。

這里添加了自定義 enroll() 方法實現(xiàn)視圖集合的額外動作,上面的代碼功能為:

  • 使用框架的 action 裝飾器指定這是對單個對象進行操作的動作;

  • 可以使用裝飾器為動作添加自定義屬性,這里指定只有 POST 方法可以訪問視圖,并且設置了授權類和權限類;

  • 使用 self.get_object() 來獲得 Course 對象;

  • 將當前用戶添加到 students 多對多關系中并返回自定義成功響應。

編輯 api/urls.py 文件并刪除下面的 URL ,因為不再需要這個鏈接:

url(r'^courses/(?P<pk>\d+)/enroll/$', views.CourseEnrollView.as_view(),
    name='course_enroll'), 

然后編輯 api/views.py 文件并刪除 CourseEnrollView 類。

現(xiàn)在,報讀課程的鏈接可以通過 router 動態(tài)生成,由于使用名為 enroll 的動作自動生成,生成的 URL 與之前的一樣。

創(chuàng)建自定義權限


我們期望學生能夠訪問報讀了的課程內容。只有報讀了課程的學生才能訪問內容。實現(xiàn)這個功能的最好方法是自定義權限類。rest_framework 提供 BasePermission 類來幫助用戶定義以下方法:

  • has_permission() :視圖級別的權限檢查;
  • has_object_permission() :實例級別的權限檢查;

這些方法返回 True 表示成功,返回 False 表示失敗。在 courses/api/ 目錄下新建 permissions.py 的文件,并添加以下代碼:

from rest_framework.permissions import BasePermission


class IsEnrolled(BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj.students.filter(id=request.user.id).exists()

創(chuàng)建 BasePermission 的子類并重寫 has_object_permission() 方法。檢查請求的用戶是否在 Course 對象的 students 關系中,下一步我們將使用 IsEnrolled 權限。

序列化課程內容


我們需要序列化課程內容, Content 模型內置通用外鍵來訪問不同類型內容模型。前一章,我們還為所有方法模型添加了自定義 render() 方法。這個方法可以為 API 提供渲染的內容。

編輯 courses 應用的 api/serializers.py 文件并添加以下代碼:

from ..models import Content


class ItemRelatedField(serializers.RelatedField):
    def to_representation(self, value):
        return value.render()


class ContentSerializer(serializers.ModelSerializer):
    item = ItemRelatedField(read_only=True)

    class Meta:
        model = Content
        fields = ('order', 'item')

上面的代碼,繼承 REST 框架提供的 RelatedField 序列化字段創(chuàng)建自定義字段,并重寫 to_representation() 方法。為 Content 模型定義 ContentSerializer 并使用自定義字段表示通用外鍵 item 。

Module 模型還需要創(chuàng)建序列化器來包含課程內容并擴展的 Course 序列化器。編輯 api/serializers.py 文件并添加以下代碼:

class ModuleWithContentsSerializer(serializers.ModelSerializer):
    contents = ContentSerializer(many=True)

    class Meta:
        model = Module
        fields = ('order', 'title', 'description', 'contents')


class CourseWithContentsSerializer(serializers.ModelSerializer):
    modules = ModuleWithContentsSerializer(many=True)

    class Meta:
        model = Course
        fields = (
        'id', 'subject', 'title', 'slug', 'overview', 'created', 'owner',
        'modules')

我們來創(chuàng)建一個模仿 retrieve() 課程內容動作的視圖。編輯 api/views.py 文件并在 CourseViewSet 類中添加以下方法:

from .permissions import IsEnrolled
from .serializers import CourseWithContentsSerializer


class CourseViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Course.objects.all()
    serializer_class = CourseSerializer

    @action(methods=['post'], detail=True,
            authentication_classes=[BasicAuthentication],
            permission_classes=[IsAuthenticated])
    def enroll(self, request, *args, **kwargs):
        course = self.get_object()
        course.students.add(request.user)
        return Response({'enrolled': True})

    @action(methods=['get'], serializer_class=CourseWithContentsSerializer,
            authentication_classes=[BasicAuthentication],
            permission_classes=[IsAuthenticated])
    def contents(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

這個方法的描述如下:

  • 使用 detail_route 裝飾器指定這個動作應用于單個對象;

  • 指定只有 GET 方法可以訪問這個動作;

  • 使用 CourseWithContentsSerializer 序列化類包含渲染的課程內容;

  • 使用 IsAutheticated 和自定義 IsEnrolled 權限指定動作權限,這樣可以保證只有訂閱了課程的用戶才能訪問內容。

  • 使用存在的 retrieve() 動作返回課程對象。

在瀏覽器中打開 http://127.0.0.1:8000/api/courses/1/contents/,如果使用具有權限的用戶訪問視圖,將可以看到課程的每個模塊,模塊包含渲染的 HTML 內容:

CH12-4.png

我們已經(jīng)創(chuàng)建了一個幫助其它服務程序訪問課程應用的簡單 API 。REST 框架可以使用 ModelViewSet 視圖集來管理創(chuàng)建和編輯對象。我們已經(jīng)介紹了 Django Rest 框架的主要內容,更多擴展內容詳見http://www.django-rest-framework.org/

總結


本章,我們創(chuàng)建了一個 RESTful API 供其它服務與 web 應用交互。

第 13章 上線運行 可以從https://www.packtpub.com/sites/default/files/downloads/Django_By_Example_GoingLive.pdf下載。它將教我們如何通過 uWSGI 和 NGINX 創(chuàng)建生產(chǎn)環(huán)境,以及如何實現(xiàn)自定義 middleware 和創(chuàng)建自定義管理命令。

你已經(jīng)到達了本書的結尾。恭喜你!已經(jīng)學習了使用 Django 成功地創(chuàng)建 web 應用的技能。這本書引導你開發(fā)實際生活中需要的項目以及使用 Django 集成其它技術。現(xiàn)在,你已經(jīng)為創(chuàng)建自己的 Django 項目做好了準備,不管它是簡單的原型還是大型的 web應用程序。

祝下一次的 Django 冒險成功。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容