django by example 实践Python

《django by example》实践 educa 项目(三

2018-04-21  本文已影响54人  学以致用123

关键词:django by example


点我查看本文集的说明及目录。


本项目相关内容包括:

实现过程

CH10 创建一个在线学习平台

CH11 缓存内容

CH12 创建API


CH12 创建一个API


上一章,我们创建了学生注册和课程报读系统,创建视图展示了课程内容,并学习了如何使用 Django 的缓存框架。本章,我们将学习如何实现以下功能:

创建一个 RESTful API


我们可能需要创建一个接口来使其它服务与自己的 web 应用进行交互。通过 API 可以实现第三方获得信息以及操作应用程序。

我们可以通过几种方法构建 API ,但是推荐遵守 REST 原则。REST 架构源自 Representational State Transfer 。 RESTful APIs 是基于资源的。模型代表资源,GET、POST、PUT 或 DELETE 等 HTTP 方法可以获取、创建、更新或者删除对象。内容也可以使用 HTTP 响应代码,不同的 HTTP 响应码表示不同的 HTTP 请求结果,比如 2XX 响应码表示成功,4XX 响应码表示失败等。

RESTful API 交换数据时最常使用的格式是 JSON 和 XML 。我们使用 JSON 序列化为项目创建一个 REST API 。 API 将提供以下功能:

我们可以通过创建自定义视图开始学习使用 Django 构建 API 。然而,一些第三方模块可以简化项目创建 API 的过程,这些第三方模块中最受欢迎的是 Django Rest 框架。

安装 django Rest 框架


django Rest 框架帮助用户轻松地创建项目的 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 设置指定读取、创建、更新或者删除对象的默认权限。 我们将 DjangoModelPermissionsOrAnonReadOnly 设置为唯一的权限类。这个类基于 Django 权限系统,权限系统允许用户创建、更新和删除对象,但匿名用户只能读取对象。后续我们将学习更多的权限。

REST 框架的参数设置列表见http://www.django-rest-framework.org/api-guide/settings/

定义 serializers


设置好 REST 框架后,需要指定数据如何进行序列化。输出数据应该序列化为特定格式,输入数据应该进行反序列化以便于处理。框架提供以下类来进行单一对象的序列化:

我们来创建第一个 Serializer 。在 courses 应用目录下创建下面的文件结构:

CH12-1.png

我们在 api 目录下创建所有的 API 函数。编辑 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 对象,创建了一个 SubjectSerializer 实例,并且访问了序列化数据。将得到以下输出:

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

我们可以看到,模型数据变成了 Python 数据类型。

理解 pasers 和 renderers

HTTP 响应返回序列化数据之前,需要将序列化数据渲染为特定格式。接收到 HTTP 请求时,我们需要解析输入数据并在使用之前对其进行反序列化。 REST 框架包含 renderers 和 parsers 来处理这个过程。

我们来看下如何解析输入数据。对于一个 JSON 字符串输入,可以使用 REST 框架提供的 JSONParser 类来将其转换为 Python 对象。在 Python shell 中执行以下代码:

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 响应。框架根据内容确定使用哪个 renderer 。它检查请求的 Accept 头来确定预期的响应内容类型,比如可以由 URL 的格式后缀确定选用的 renderer ,例如,访问将触发 JSONRenderer 来返回 JSON 响应。

回到 shell 并执行以下代码使用前面的序列化例子渲染 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 将序列化数据渲染为 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 的信息。

创建列表和详情视图


REST 框架内置创建 API 视图的通用视图和 mixin 集合来实现获取、创建、更新或者删除模型对象的功能。http://www.django-rest-framework.org/api-guide/generic-views/中包括 REST 框架提供的所有通用 mixin 和视图。

我们来创建获取 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参数。两个视图都设置了以下属性:

下面为视图添加 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 运行开发服务器,打开 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 对象列表。如果你的操作系统没有安装 curl,可以从http://curl.haxx.se/dlwiz/下载。除了 curl ,我们还可以使用其它工具发送 HTTP请求,比如 Postman 浏览器插件(可以从 https://www.getpostman.com 获取)。

在浏览器中打开 http://127.0.0.1:8000/api/subjects/ ,你将看到 REST 框架可以浏览的 API :

CH12-2.png

这个 HTML接口由 BrowsableAPIRenderer 提供。它展示了得到的标题和内容,并且允许用户实现请求。我们可以通过在 URL 中添加 id 来访问 Subject 对象的 API 详情视图。在浏览器中打开http://127.0.0.1:8000/api/subjects/1/,你将看到渲染为 JSON 格式的单个对象。

创建嵌套 serializers


我们将为 Course 模型创建 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')

我们来看下如何实现 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 参数表示这个字段是只读的,不能使用任何输入来创建或者修改对象。

打开 shell 并再次创建 CourseSerializer 实例,使用 JSONRenderer 渲染序列化数据。这次,模块列表使用了嵌套的 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/

创建自定义视图


REST 框架提供一个 APIView 类,可以基于 Django View 类实现 API 功能。 APIView 与 View 的区别在于使用 REST 框架 自定义的 Request 和 Response 对象并处理 APIException 异常来返回合适的 HTTP 响应。它还包含内置的授权和权限系统来管理视图访问。

我们将为用户报读课程创建一个视图,编辑 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 视图处理课程的用户报读事务,上面的代码实现以下功能:

编辑 api/urls.py 文件并为 CourseEnrollView 添加以下 URL 模式:

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

理论上,现在已经可以通过 POST 请求来报读课程,然而,我们还需要识别用户并阻止没有授权的用户访问视图。下面来看下 API 的授权和权限如何工作。

处理授权


REST 框架通过授权类来识别请求用户。如果授权成功,框架将 request.user 设置为授权的 User 对象,如果用户没有授权,Request.user 将设置为 Django AnonymousUser 实例。

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

我们可以通过继承 REST 框架提供的 BaseAuthorization 类创建一个自定义授权后端,并覆盖 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 框架使用权限系统来限制视图访问。REST 框架内置的权限包括:

如果拒绝用户访问,我们通常获得以下 HTTP 错误码:

我们可以从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 权限,将阻止匿名用户访问视图,现在可以向 API 发送 POST 请求了。

保证开发服务器正在运行,打开 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 网站并检查用户是否报读了课程。

创建视图集合和routers

ViewSets 帮助我们定义 API 与 REST 框架(通过 Router 对象)动态创建的 URLs 的交互。使用视图集合,可以避免多个视图使用重复逻辑。视图集合包含 list()、create()、retrieve()、update()、partial_update() 和 destroy() 等实现获取、更新、删除等操作动作的方法。

下面为 Course 模型创建一个视图集合,编辑 api/views.py 并添加以下代码:

from rest_framework import viewsets
from .serializers import CourseSerializer


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

创建 ReadOnlyModelViewSet 子类,ReadOnlyModelViewSet 提供只读权限的 list() 和 retrieve() 操作来列出对象集合或获取单个对象。编辑 api/urls.py 并为视图集合创建 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)), ]

这里创建了 DefaultRouter 对象并使用 courses 前缀注册视图集合,router 负责为视图集合动态生成 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/了解更多关于视图集合的消息。可以从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() 方法实现视图集合的额外动作,上面的代码功能为:

编辑 api/urls.py 文件并删除下面的 URL ,因为不再需要这个链接:

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

然后编辑 api/views.py 文件并删除 CourseEnrollView 类。

现在,报读课程的链接可以通过 router 动态生成,由于使用名为 enroll 的动作自动生成,生成的 URL 与之前的一样。

创建自定义权限


我们期望学生能够访问报读了的课程内容。只有报读了课程的学生才能访问内容。实现这个功能的最好方法是自定义权限类。rest_framework 提供 BasePermission 类来帮助用户定义以下方法:

这些方法返回 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()

创建 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 序列化字段创建自定义字段,并重写 to_representation() 方法。为 Content 模型定义 ContentSerializer 并使用自定义字段表示通用外键 item 。

Module 模型还需要创建序列化器来包含课程内容并扩展的 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')

我们来创建一个模仿 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)

这个方法的描述如下:

在浏览器中打开 http://127.0.0.1:8000/api/courses/1/contents/,如果使用具有权限的用户访问视图,将可以看到课程的每个模块,模块包含渲染的 HTML 内容:

CH12-4.png

我们已经创建了一个帮助其它服务程序访问课程应用的简单 API 。REST 框架可以使用 ModelViewSet 视图集来管理创建和编辑对象。我们已经介绍了 Django Rest 框架的主要内容,更多扩展内容详见http://www.django-rest-framework.org/

总结


本章,我们创建了一个 RESTful API 供其它服务与 web 应用交互。

第 13章 上线运行 可以从https://www.packtpub.com/sites/default/files/downloads/Django_By_Example_GoingLive.pdf下载。它将教我们如何通过 uWSGI 和 NGINX 创建生产环境,以及如何实现自定义 middleware 和创建自定义管理命令。

你已经到达了本书的结尾。恭喜你!已经学习了使用 Django 成功地创建 web 应用的技能。这本书引导你开发实际生活中需要的项目以及使用 Django 集成其它技术。现在,你已经为创建自己的 Django 项目做好了准备,不管它是简单的原型还是大型的 web应用程序。

祝下一次的 Django 冒险成功。

上一篇下一篇

猜你喜欢

热点阅读