12 构建API
在上一章中,你构建了一个学生注册和课程报名系统。你创建了显示课程内容的视图,并学习了如何使用Django的缓存框架。在本章中,你会学习以下知识点:
- 构建一个RESTful API
- 为API视图处理认证和权限
- 创建API视图集和路由
12.1 构建RESTful API
你可能想要创建一个接口,让其它服务可以与你的web应用交互。通过构建一个API,你可以允许第三方以编程方式使用信息和操作你的应用。
你可以通过很多方式构建API,但最好是遵循REST原则。REST架构是表述性状态传递(Representational State Transfer
)的缩写。RESTful API是基于资源的。你的模型代表资源,HTTP方法(比如GET,POST,PUT或DELETE)用于检索,创建,更新或者删除对象。HTTP响应代码也可以在这个上下文中使用。返回的不同HTTP响应代码表示HTTP请求的结果,比如2XX响应代码表示成功,4XX表示错误等等。
RESTful API最常用的交互数据的格式是JSON和XML。我们将为项目构建一个JSON序列化的REST API。我们的API会提供以下功能:
- 检索主题
- 检索可用的课程
- 检索课程内容
- 报名参加课程
我们可以通过Django创建自定义视图,从头开始构建API。但是有很多第三方模块可以简化创建API,其中最流行的是Django Rest Framework
。
12.1.1 安装Django Rest Framework
Django Rest Framework
可以很容易的为项目构建REST API。你可以在这里查看所有文档。
打开终端,使用以下命令安装框架:
pip install djangorestframework
编辑educa
项目的settings.py
文件,在INSTALLED_APPS
设置中添加rest_framework
:
INSTALLED_APPS = [
# ...
'rest_framework',
]
然后在settings.py
文件中添加以下代码:
REST_FRAMEWORK = {
'DEFAULT_PREMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
你可以使用REST_FRAMEWORK
设置为API提供一个特定配置。REST Framework提供了大量设置来配置默认行为。DEFAULT_PREMISSION_CLASSES
设置指定读取,创建,更新或者删除对象的默认权限。我们设置DjangoModelPermissionsOrAnonReadOnly
是唯一的默认权限类。这个类依赖Django的权限系统,允许用户创建,更新或删除对象,同时为匿名用户提供只读访问。之后你会学习更多关于权限的内容。
你可以访问这里查看完整的REST Framework可用设置列表。
12.1.2 定义序列化器
设置REST Framework之后,我们需要指定如何序列化我们的数据。输出数据必须序列化为指定格式,输入数据会反序列化处理。框架为单个类构建序列化器提供了以下类:
-
Serializer
:为普通Python类实例提供序列化 -
ModelSerializer
:为模型实例提供序列化 -
HyperlinkedModelSerializer
:与ModelSerializer
一样,但使用链接而不是主键表示对象关系
让我们构建第一个序列化器。在courses
应用目录中创建以下文件结构:
api/
__init__.py
serializers.py
我们会在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
模型的序列化器。序列化器的定义类似于Django的Form
和ModelForm
类。Meta
类允许你指定序列化的模型和序列化中包括的字段。如果没有设置fields
属性,则会包括所有模型字段。
让我们试试序列化器。打开终端执行python manage.py shell
命令,然后执行以下代码:
>>> from courses.models import Subject
>>> from courses.api.serializers import SubjectSerializer
>>> subject = Subject.objects.latest('id')
>>> serializer = SubjectSerializer(subject)
>>> serializer.data
在这个例子中,我们获得一个Subject
对象,创建一个SubjectSerializer
实例,然后访问序列化的数据。你会看到以下输出:
{'id': 4, 'slug': 'mathematics', 'title': 'Mathematics'}
正如你所看到的,模型数据转换为Python的原生数据类型。
12.1.3 理解解析器和渲染器
在HTTP响应中返回序列化的数据之前,需要把它渲染为特定格式。同样的,当你获得HTTP请求时,在你操作它之前,需要解析传入的数据并反序列化数据。REST Framework包括渲染器和解析器来处理这些操作。
让我们看看如何解析收到的数据。给定一个JSON字符串输入,你可以使用REST Framework提供的JSONParser
类转换为Python对象。在Python终端中执行以下代码:
from io import BytesIO
from rest_framework.parsers import JSONParser
data = b'{"id":4,"title":"Music","slug":"music"}'
JSONParser().parse(BytesIO(data))
你会看到以下输出:
{'id': 4, 'title': 'Music', 'slug': 'music'}
REST Framework还包括Renderer
类,允许你格式化API响应。框架通过内容协商决定使用哪个渲染器。它检查请求的Accept
头,决定响应期望的内容类型。根据情况,渲染器由URL格式后缀确定。例如,触发JSONRenderer
的访问会返回JSON响应。
回到终端执行以下代码,从上一个序列化器例子中渲染serializer
对象:
>>> from rest_framework.renderers import JSONRenderer
>>> JSONRenderer().render(serializer.data)
你会看到以下输出:
b'{"id":4,"title":"Mathematics","slug":"mathematics"}'
我们使用JSONRenderer
渲染序列化的数据位JSON。默认情况下,REST Framework使用两个不同的渲染器:JSONRenderer
和BrowsableAPIRenderer
。后者提供一个web接口,可以很容易的浏览你的API。你可以在REST_FRAMEWORK
设置的DEFAULT_RENDERER_CLASSES
选项中修改默认的渲染器类。
12.1.4 构建列表和详情视图
REST Framework自带一组构建API的通用视图和mixins。它们提供了检索,创建,更新或删除模型对象的功能。你可以在这里查看REST Framework提供的所有通用的mixins和视图。
让我们创建检索Subject
对象的列表和详情视图。在courses/api/
目录中创建views.py
文件,并添加以下代码:
from rest_framework import generics
from ..models import Subject
from .serializers import SubjectSerializer
class SubjectListView(generics.ListAPIView):
queryset = Subject.objects.all()
serializer_class = SubjectSerializer
class SubjectDetailView(generics.RetrieveAPIView):
queryset = Subject.objects.all()
serializer_class = SubjectSerializer
在这段代码中,我们使用了REST Framework的通用ListAPIView
和RetrieveAPIView
。我们在详情视图中包括一个pk
URL参数,来检索给定主键的对象。两个视图都包括以下属性:
-
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模式:
urlpatterns = [
# ...
url(r'^api/', include('courses.api.urls', namespace='api')),
]
我们为API的URL使用api
命名空间。使用python manage.py runserver
启动开发服务器。打开终端,并使用curl
获取http://127.0.0.1:8000/api/subjects/
:
bogon:educa lakerszhy$ 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
,请在这里下载。除了curl
,你还可以使用其它工具发送自定义HTTP请求,比如浏览器扩展Postman
,你可以在这里下载Postman
。
在浏览器中打开http://127.0.0.1:8000/api/subjects/
。你会看到REST Framework的可浏览API,如下图所示:
这个HTML界面由BrowsableAPIRenderer
渲染器提供。你还可以在URL中包括id
来访问一个Subject
对象的API详情视图。在浏览器中打开http://127.0.0.1:8000/api/subjects/1/
。你会看到单个Subject
对象以JSON格式渲染。
12.1.5 创建嵌套的序列化器
我们将为Course
模型创建一个序列化器。编辑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
对象是如何被序列化的。在终端执行python manage.py shell
,然后执行以下代码:
>>> from rest_framework.renderers import JSONRenderer
>>> from courses.models import Course
>>> from courses.api.serializers import CourseSerializer
>>> course = Course.objects.latest('id')
>>> serializer = CourseSerializer(course)
>>> JSONRenderer().render(serializer.data)
你获得的JSON对象包括我们在CourseSerializer
中指定的字段。你会看到modules
管理器的关联对象被序列化为主键列表,如下所示:
"modules": [17, 18, 19, 20, 21, 22]
我们想包括每个单元的更多信息,所以我们需要序列化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
参数表示该字段是可读的,并且不应该包括在任何输入中来创建或更新对象。
打开终端,并再创建一个CourseSerializer
实例。使用JSONRenderer
渲染序列化器的data
属性。这次,单元列表被嵌套的ModuleSerializer
序列化器序列化,如下所示:
"modules": [
{
"order": 0,
"title": "Django overview",
"description": "A brief overview about the Web Framework."
},
{
"order": 1,
"title": "Installing Django",
"description": "How to install Django."
},
...
你可以在这里阅读更多关于序列化器的信息。
12.1.6 构建自定义视图
REST Framework提供了一个APIView
类,可以在Django的View
类之上构建API功能。APIView
类与View
类不同,它使用REST Framework的自定义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
视图处理用户报名参加课程。上面的代码完成以下任务:
- 我们创建了一个继承自
APIView
的自定义视图。 - 我们为POST操作定义了
post()
方法。这个视图不允许其它HTTP方法。 - 我们期望URL参数
pk
包含课程ID。我们用给定的pk
参数检索课程,如果没有找到则抛出404异常。 - 我们添加当前对象到
Course
对象的多对多关系students
中,并返回成功的响应。
编辑api/urls.py
文件,并为CourseEnrollView
视图添加URL模式:
url(r'^courses/(?P<pk>\d+)/enroll/$', views.CourseEnrollView.as_view(), name='course_enroll'),
理论上,我们现在可以执行一个POST请求,为当前用户报名参加一个课程。但是,我们需要识别用户,并阻止未认证用户访问这个视图。让我们看看API认证和权限是如何工作的。
12.1.7 处理认证
REST Framework提供了识别执行请求用户的认证类。如果认证成功,框架会在request.user
中设置认证的User
对象。否则设置为Django的AnonymousUser
实例。
REST Framework提供以下认证后台:
-
BasicAuthentication
:HTTP基础认证。客户端用Base64在Authorization
HTTP头中发送用户和密码。你可以在这里进一步学习。 -
TokenAuthentication
:基于令牌的认证。一个Token
模型用于存储用户令牌。用户在Authorization
HTTP头中包括用于认证的令牌。 -
SessionAuthentication
:使用Django的会话后台用于认证。当执行从你的网站前端到API的AJAX请求时,这个后台非常有用。
你可以通过继承REST Framework提供的BaseAuthentication
类,并覆写authenticate()
方法来构建自定义认证后台。
你可以基于单个视图设置认证,或者用DEFAULT_AUTHENTICATION_CLASSES
设置为全局认证。
认证只识别执行请求的用户。它不会允许或阻止访问视图。你必须使用权限来显示访问视图。
你可以在这里查看所有关于认证的信息。
让我们添加BasicAuthentication
到我们的视图。编辑courses
应用的api/views.py
文件,并添加authentication_classes
属性到CourseEnrollView
:
from rest_framework.authentication import BasicAuthentication
class CourseEnrollView(APIView):
authentication_classes = (BasicAuthentication, )
# ...
用户将通过设置在HTTP请求中的Authorization
头的证书识别。
12.1.8 添加权限到视图
REST Framework包括一个权限系统,用于限制视图的访问。REST Framework的一些内置权限是:
-
AllowAny
:不限制访问,不管用户是否认证。 -
IsAuthenticated
:只允许认证的用户访问。 -
IsAuthenticatedOrReadOnly
:认证用户可以完全访问。匿名用户只允许执行读取方法,比如GET,HEAD或OPTIONS。 -
DjangoModelPermissions
:捆绑到django.contrib.auth
的权限。视图需要一个queryset
属性。只有分配了模型权限的认证用户才能获得权限。 -
DjangoObjectPermissions
:基于单个对象的Django权限。
如果用户被拒绝访问,他们通常会获得以下某个HTTP错误代码:
-
HTTP 401
:未认证 -
HTTP 403
:没有权限
你可以在这里阅读更多关于权限的信息。
编辑courses
应用的api/views.py
文件,并在CourseEnrollView
中添加permission_classes
属性:
from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated
class CourseEnrollView(APIView):
authentication_classes = (BasicAuthentication, )
permission_classes = (IsAuthenticated, )
# ..
我们引入了IsAuthenticated
权限。这会阻止匿名用户访问这个视图。现在我们可以执行POST请求到新的API方法。
确保开发服务器正在运行。打开终端并执行以下命令:
curl -i -X POST http://127.0.0.1:8000/api/courses/1/enroll/
你会获得以下响应:
HTTP/1.0 401 UNAUTHORIZED
...
{"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
...
{"enrolled": true}
你可以访问管理站点,检查用户是否报名参加课程。
12.1.9 创建视图集和路由
ViewSets
允许你定义你的API交互,并让REST Framework用Router
对象动态构建URL。通过视图集,你可以避免多个视图的重复逻辑。视图集包括典型的创建,检索,更新,删除操作,分别是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
继承,它提供了只读操作list()
和retrieve()
,用于列出对象或检索单个对象。编辑api/urls.py
文件,并为我们的视图集创建一个路由:
from django.conf.urls import url, include
from . import views
from rest_framework import routers
router = routers.DefaultRouter()
router.register('courses', views.CourseViewSet)
urlpatterns = [
# ...
url(r'^', include(router.urls)),
]
我们创建了一个DefaultRouter
对象,并用courses
前缀注册我们的视图集。路由负责为我们的视图集自动生成URL。
在浏览器中打开http://127.0.0.1:8000/api/
。你会看到路由在它的基础URL中列出所有视图集,如下图所示:
你现在可以访问http://127.0.0.1:8000/api/courses/
检索课程列表。
你可以在这里进一步学习视图集。你还可以在这里查看更多关于路由的信息。
12.1.10 添加额外操作到视图集
你可以添加额外操作到视图集中。让我们把之前的CourseEnrollView
视图为一个自定义视图集操作。编辑api/views.py
文件,并修改CourseViewSet
类:
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})
我们添加了一个自定义的enroll()
方法,它代表这个视图集的一个额外操作。上面的代码执行以下任务:
- 我们使用框架的
detail_route
装饰器,指定这是在单个对象上执行的操作。 - 装饰器允许我们为操作添加自定义属性。我们指定这个视图只允许POST方法,并设置了认证和权限类。
- 我们使用
self.get_object()
检索Courses
对象。 - 我们把当前用户添加到
students
多对多关系中,并返回一个自定义的成功响应。
编辑api/urls.py
文件,移除以下URL,因为我们不再需要它:
url(r'^courses/(?P<pk>\d+)/enroll/$', views.CourseEnrollView.as_view(), name='course_enroll'),
然后编辑api/views.py
文件,移除CourseEnrollView
类。
现在,报名参加课程的URL由路由自动生成。因为它使用操作名enroll
,所以URL保持不变。
12.1.11 创建自定义权限
我们希望学生可以访问它们报名的课程内容。只有报名的学生才可以访问课程内容。最好的实现方式是使用一个自定义权限类。Django提供的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()
我们从BasePermission
类继承,并覆写has_object_permission()
。我们检查执行请求的用户是否存在Course
对象的students
关系中。我们下一步会使用IsEnrolled
权限。
12.1.12 序列化课程内容
我们需要序列化课程内容。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 Framework提供的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):
# ...
@detail_route(
methods=['get'],
serializer_class=CourseWithContentsSerializer,
authentication_classes=[BasicAuthentication],
permission_classes=[IsAuthenticated, IsEnrolled]
)
def contents(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
这个方法执行以下任务:
- 我们使用
detail_route
装饰器指定该操作在单个对象上执行。 - 我们指定该操作只允许GET方法。
- 我们使用新的
CourseWithContentsSerializer
序列化器类,它包括渲染的课程内容。 - 我们使用
IsAuthenticated
和自定义的IsEnrolled
权限。这样可以确保只有报名的用户可以访问课程内容。 - 我们使用存在的
retrieve()
操作返回课程对象。
在浏览器中打开http://127.0.0.1:8000/api/courses/1/contents/
。如果你用正确证书访问视图,你会看到课程的每个单元,包括渲染后的课程内容的HTML,如下所示:
{
"order": 0,
"title": "Installing Django",
"description": "",
"contents": [
{
"order": 0,
"item": "<p>Take a look at the following video for installing Django:</p>\n"
},
{
"order": 1,
"item": "\n<iframe width=\"480\" height=\"360\" src=\"http://www.youtube.com/embed/bgV39DlmZ2U?wmode=opaque\" frameborder=\"0\" allowfullscreen></iframe>\n\n"
}
]
}
你已经构建了一个简单的API,允许其它服务通过编程方式访问course
应用。REST Framework还允许你用ModelViewSet
视图集管理创建和编辑对象。我们已经学习了Django Rest Framework的主要部分,但你仍可以在这里进一步学习它的特性。
12.2 总结
在这一章中,你创建了一个RESTful API,可以让其它服务与你的web应用交互。
额外的第十三章可以在这里下载。它教你如何使用uWSGI
和NGINX
构建一个生产环境。你还会学习如何实现一个自定义的中间件和创建自定义的管理命令。
你已经到达了本书的结尾。恭喜你!你已经学会了用Django构建一个成功的web应用所需要的技巧。本书指导你完成开发实际项目,以及将Django与其它技术结合。现在你已经准备好创建自己的Django项目,不管是一个简单的原型还是一个大型的web应用。
祝你下一次Django冒险活动好运!