# Django_restframework
**Repository Path**: top2mei/Django_restframework
## Basic Information
- **Project Name**: Django_restframework
- **Description**: Django REST framework
- **Primary Language**: Python
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 2
- **Created**: 2019-07-10
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
djangorestframework 快速入门
我们将创建一个简单的允许管理员用户查看和编辑系统中的用户和组的API。
项目设置
创建一个名为 tutorial 的新django项目,然后启动一个名为 quickstart 的新app。
# 创建项目目录
mkdir tutorial
cd tutorial
# 创建一个virtualenv来隔离我们本地的包依赖关系
virtualenv env
source env/bin/activate # 在Windows下使用 `env\Scripts\activate`
# 在创建的虚拟环境中安装 Django 和 Django REST framework
pip install django
pip install djangorestframework
# 创建一个新项目和一个单个应用
django-admin.py startproject tutorial . # 注意结尾的'.'符号
cd tutorial
django-admin.py startapp quickstart
cd ..
现在第一次同步你的数据库:
python manage.py migrate
我们还要创建一个名为 admin 的初始用户,密码为 password123。我们稍后将在该示例中验证该用户。
python manage.py createsuperuser
等你建立好一个数据库和初始用户,并准备好开始。打开应用程序的目录,我们就要开始编码了...
Serializers
首先我们要定义一些序列化程序。我们创建一个名为 tutorial/quickstart/serializers.py的文件,来用作我们的数据表示。
from django.contrib.auth.models import User, Group
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ('url', 'name')
请注意,在这个例子中我们用到了超链接关系,使用 HyperlinkedModelSerializer。你还可以使用主键和各种其他关系,但超链接是好的RESTful设计。
Views
好了,我们接下来再写一些视图。打开 tutorial/quickstart/views.py 文件开始写代码了。
from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from tutorial.quickstart.serializers import UserSerializer, GroupSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
允许用户查看或编辑的API路径。
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
class GroupViewSet(viewsets.ModelViewSet):
"""
允许组查看或编辑的API路径。
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
不再写多个视图,我们将所有常见行为分组写到叫 ViewSets 的类中。
如果我们需要,我们可以轻松地将这些细节分解为单个视图,但是使用viewsets可以使视图逻辑组织良好,并且非常简洁。
URLs
好的,现在我们在tutorial/urls.py中开始写连接API的URLs。
from django.conf.urls import url, include
from rest_framework import routers
from tutorial.quickstart import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
使用自动URL路由连接我们的API。
另外,我们还包括支持浏览器浏览API的登录URL。
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
因为我们使用的是viewsets而不是views,所以我们可以通过简单地使用路由器类注册视图来自动生成API的URL conf。
再次,如果我们需要对API URL进行更多的控制,我们可以简单地将其拉出来使用常规基于类的视图,并明确地编写URL conf。
最后,我们将包括用于支持浏览器浏览的API的默认登录和注销视图。这是可选的,但如果您的API需要身份验证,并且你想要使用支持浏览器浏览的API,那么它们很有用。
Settings
我们也想设置一些全局设置。我们想打开分页,我们希望我们的API只能由管理员使用。设置模块都在 tutorial/settings.py 中。
INSTALLED_APPS = (
...
'rest_framework',
)
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAdminUser',
],
'PAGE_SIZE': 10
}
好了,我们完成了。
测试我们的API
我们现在可以测试我们构建的API。我们从命令行启动服务器。
python manage.py runserver
我们现在可以从命令行访问我们的API,使用诸如 curl ...
bash: curl -H 'Accept: application/json; indent=4' -u admin:password123 http://127.0.0.1:8000/users/
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"email": "admin@example.com",
"groups": [],
"url": "http://127.0.0.1:8000/users/1/",
"username": "admin"
},
{
"email": "tom@example.com",
"groups": [ ],
"url": "http://127.0.0.1:8000/users/2/",
"username": "tom"
}
]
}
或者使用 httpie,命令行工具...
bash: http -a admin:password123 http://127.0.0.1:8000/users/
HTTP/1.1 200 OK
...
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"email": "admin@example.com",
"groups": [],
"url": "http://localhost:8000/users/1/",
"username": "paul"
},
{
"email": "tom@example.com",
"groups": [ ],
"url": "http://127.0.0.1:8000/users/2/",
"username": "tom"
}
]
}
或直接通过浏览器,转到URL http://127.0.0.1:8000/users/...
Quick start image
如果您正在使用浏览器,请确保使用右上角进行登录。
非常棒!就是这么简单!
如果您想更深入地了解REST framework各部分是如何配合在一起的,
请前往教程: (https://q1mi.github.io/Django-REST-framework-documentation/tutorial/1-serialization_zh/ ),
或者开始浏览 API指南:(https://q1mi.github.io/Django-REST-framework-documentation/#api-guide)。
#Tutorial 1: 序列化
介绍
本教程将介绍如何创建一个简单的收录代码高亮展示的Web API。这个过程中, 将会介绍组成Rest框架的各个组件,并让你全面了解各个组件是如何一起工作的。
这个教程是相当深入的,所以你应该在开始之前准备些啤酒和花生毛豆什么的。如果你只是想来个快速预览,那你应该查看quickstart文档。
注意:本教程的代码可以在Github的tomchristie/rest-framework-tutorial库中找到。完整的实现版本可以在线查看作为一个沙盒版本进行测试。
创建一个新环境
在做其他事情之前,我们要用virtualenv创建一个新的虚拟环境。这样就能确保我们的包配置与我们正在开展的任何其他项目保持良好的隔离。
virtualenv env
source env/bin/activate
现在我们在一个virtualenv环境中,我们可以安装我们需要的包了。
pip install django
pip install djangorestframework
pip install pygments # 代码高亮插件
注意: 要随时退出virtualenv环境,只需输入deactivate。有关更多信息,请参阅virtualenv documentation文档。
正式开始
好了,我们现在要开始写代码了。 首先,我们来创建一个新的项目。
cd ~
django-admin.py startproject tutorial
cd tutorial
完成上面的步骤以后我们要创建一个用于创建简单Web API的app。
python manage.py startapp snippets
我们需要将新建的snippetsapp和rest_frameworkapp添加到INSTALLED_APPS。让我们编辑tutorial/settings.py文件:
INSTALLED_APPS = (
...
'rest_framework',
'snippets.apps.SnippetsConfig',
)
请注意如果你使用的Django版本低于1.9,你需要使用snippets.apps.SnippetsConfig替换snippets。
好了,我们的准备工作做完了。
创建一个model
为了实现本教程的目的,我们将开始创建一个用于存储代码片段的简单的Snippet model。然后继续编辑snippets/models.py文件。注意:优秀的编程实践都会添加注释。虽然你将在本教程代码的存储库版本中找到它们,但我们在此忽略了它们,专注于代码本身。
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
ordering = ('created',)
我们还需要为我们的代码段模型创建初始迁移(initial migration),并首次同步数据库(migrate)。
python manage.py makemigrations snippets
python manage.py migrate
创建一个序列化类
开发我们的Web API的第一件事是为我们的Web API提供一种将代码片段实例序列化和反序列化为诸如json之类的表示形式的方式。我们可以通过声明与Django forms非常相似的序列化器(serializers)来实现。 在snippets的目录下创建一个名为serializers.py文件,并添加以下内容。
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
def create(self, validated_data):
"""
根据提供的验证过的数据创建并返回一个新的`Snippet`实例。
"""
return Snippet.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
根据提供的验证过的数据更新和返回一个已经存在的`Snippet`实例。
"""
instance.title = validated_data.get('title', instance.title)
instance.code = validated_data.get('code', instance.code)
instance.linenos = validated_data.get('linenos', instance.linenos)
instance.language = validated_data.get('language', instance.language)
instance.style = validated_data.get('style', instance.style)
instance.save()
return instance
序列化器类的第一部分定义了序列化/反序列化的字段。create()和update()方法定义了在调用serializer.save()时如何创建和修改完整的实例。
序列化器类与Django Form类非常相似,并在各种字段中包含类似的验证标志,例如required,max_length和default。
字段标志还可以控制serializer在某些情况下如何显示,比如渲染HTML的时候。上面的{'base_template': 'textarea.html'}标志等同于在Django Form类中使用widget=widgets.Textarea。这对于控制如何显示可浏览器浏览的API特别有用,我们将在本教程的后面看到。
我们实际上也可以通过使用ModelSerializer类来节省时间,就像我们后面会用到的那样。但是现在我们还继续使用我们明确定义的serializer。
使用序列化类
在我们进一步了解之前,我们先来熟悉使用我们新的Serializer类。输入下面的命令进入Django shell。
python manage.py shell
好的,像下面一样导入几个模块,然后开始创建一些代码片段来处理。
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
snippet = Snippet(code='foo = "bar"\n')
snippet.save()
snippet = Snippet(code='print "hello, world"\n')
snippet.save()
我们现在已经有几个片段实例了,让我们看一下将其中一个实例序列化。
serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
此时,我们将模型实例转换为Python原生数据类型。要完成序列化过程,我们将数据转换成json。
content = JSONRenderer().render(serializer.data)
content
# '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
反序列化是类似的。首先我们将一个流(stream)解析为Python原生数据类型...
from django.utils.six import BytesIO
stream = BytesIO(content)
data = JSONParser().parse(stream)
...然后我们要将Python原生数据类型恢复成正常的对象实例。
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
#
可以看到API和表单(forms)是多么相似。当我们开始使用我们的序列化类编写视图的时候,相似性会变得更加明显。
我们也可以序列化查询结果集(querysets)而不是模型实例。我们只需要为serializer添加一个many=True标志。
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
使用ModelSerializers
我们的SnippetSerializer类中重复了很多包含在Snippet模型类(model)中的信息。如果能保证我们的代码整洁,那就更好了。
就像Django提供了Form类和ModelForm类一样,REST framework包括Serializer类和ModelSerializer类。
我们来看看使用ModelSerializer类重构我们的序列化类。再次打开snippets/serializers.py文件,并将SnippetSerializer类替换为以下内容。
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
序列一个非常棒的属性就是可以通过打印序列化器类实例的结构(representation)查看它的所有字段。
from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))
# SnippetSerializer():
# id = IntegerField(label='ID', read_only=True)
# title = CharField(allow_blank=True, max_length=100, required=False)
# code = CharField(style={'base_template': 'textarea.html'})
# linenos = BooleanField(required=False)
# language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
# style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
重要的是要记住,ModelSerializer类并不会做任何特别神奇的事情,它们只是创建序列化器类的快捷方式:
一组自动确定的字段。
默认简单实现的create()和update()方法。
使用我们的序列化(Serializer)来编写常规的Django视图(views)
让我们看看如何使用我们新的Serializer类编写一些API视图。目前我们不会使用任何REST框架的其他功能,我们只需将视图作为常规Django视图编写。
编辑snippets/views.py文件,并且添加以下内容。
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
class JSONResponse(HttpResponse):
"""
An HttpResponse that renders its content into JSON.
"""
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
我们API的根视图支持列出所有现有的snippet或创建一个新的snippet。
@csrf_exempt
def snippet_list(request):
"""
列出所有的code snippet,或创建一个新的snippet。
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return JSONResponse(serializer.data)
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = SnippetSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data, status=201)
return JSONResponse(serializer.errors, status=400)
请注意,因为我们希望能够从不具有CSRF令牌的客户端对此视图进行POST,因此我们需要将视图标记为csrf_exempt。这不是你通常想要做的事情,并且REST框架视图实际上比这更实用的行为,但它现在足够达到我们的目的。
我们也需要一个与单个snippet对象相应的视图,并且用于获取,更新和删除这个snippet。
@csrf_exempt
def snippet_detail(request, pk):
"""
获取,更新或删除一个 code snippet。
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return JSONResponse(serializer.data)
elif request.method == 'PUT':
data = JSONParser().parse(request)
serializer = SnippetSerializer(snippet, data=data)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data)
return JSONResponse(serializer.errors, status=400)
elif request.method == 'DELETE':
snippet.delete()
return HttpResponse(status=204)
最终,我们需要把这些视图连起来。创建一个snippets/urls.py文件:
from django.conf.urls import url
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P[0-9]+)/$', views.snippet_detail),
]
我们也需要在根URL配置tutorial/urls.py文件中,添加我们的snippet应用的URL。
from django.conf.urls import url, include
urlpatterns = [
url(r'^', include('snippets.urls')),
]
值得注意的是,目前我们还没有正确处理好几个边缘事项。如果我们发送格式错误的json,或者如果使用视图不处理的方法发出请求,那么我们最终会出现一个500“服务器错误”响应。不过,现在这样做。
测试在我们Web API进行第一次尝试
现在为我们的snippets启动一个服务器。
退出所有的shell...
quit()
...启动Django的开发服务器。
python manage.py runserver
Validating models...
0 errors found
Django version 1.8.3, using settings 'tutorial.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
在另一个终端窗口中,我们可以测试服务器。
我们可以使用curl或httpie测试我们的服务器。Httpie是用Python编写的用户友好的http客户端,我们安装它。
你可以使用pip来安装httpie:
pip install httpie
最后,我们可以得到所有snippet的列表:
http http://127.0.0.1:8000/snippets/
HTTP/1.1 200 OK
...
[
{
"id": 1,
"title": "",
"code": "foo = \"bar\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
或者我们可以通过引用其id来获取特定的snippet:
http http://127.0.0.1:8000/snippets/2/
HTTP/1.1 200 OK
...
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
同样,你可以通过在网络浏览器中访问这些URL来显示相同的json。
我们现在的位置
我们到目前为止做得不错,我们有一个与Django的Forms API非常相似序列化API,和一些常规的Django视图。
现在,我们的API视图除了服务json响应外,不会做任何其他特别的东西,并且有一些错误我们仍然需要清处理,但是它仍是一个可用的Web API。
我们将在本教程的第2部分(https://q1mi.github.io/Django-REST-framework-documentation/tutorial/2-requests-and-responses_zh/ )来完善这些东西。
#Tutorial 2: 请求和响应
从现在开始,我们将真正开始接触REST框架的核心。 我们来介绍几个基本的构建模块。
请求对象(Request objects)
REST框架引入了一个扩展了常规HttpRequest的Request对象,并提供了更灵活的请求解析。Request对象的核心功能是request.data属性,它与request.POST类似,但对于使用Web API更为有用。
request.POST # 只处理表单数据 只适用于'POST'方法
request.data # 处理任意数据 适用于'POST','PUT'和'PATCH'方法
响应对象(Response objects)
REST框架还引入了一个Response对象,这是一种获取未渲染(unrendered)内容的TemplateResponse类型,并使用内容协商来确定返回给客户端的正确内容类型。
return Response(data) # 渲染成客户端请求的内容类型。
状态码(Status codes)
在你的视图(views)中使用纯数字的HTTP 状态码并不总是那么容易被理解。而且如果错误代码出错,很容易被忽略。REST框架为status模块中的每个状态代码(如HTTP_400_BAD_REQUEST)提供更明确的标识符。使用它们来代替纯数字的HTTP状态码是个很好的主意。
包装(wrapping)API视图
REST框架提供了两个可用于编写API视图的包装器(wrappers)。
用于基于函数视图的@api_view装饰器。
用于基于类视图的APIView类。
这些包装器提供了一些功能,例如确保你在视图中接收到Request实例,并将上下文添加到Response,以便可以执行内容协商。
包装器还提供了诸如在适当时候返回405 Method Not Allowed响应,并处理在使用格式错误的输入来访问request.data时发生的任何ParseError异常。
组合在一起
好的,我们开始使用这些新的组件来写几个视图。
我们在views.py中不再需要JSONResponse类了,所以把它删除掉。删除之后,我们就可以开始重构我们的视图了。
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@api_view(['GET', 'POST'])
def snippet_list(request):
"""
列出所有的snippets,或者创建一个新的snippet。
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
我们的实例视图比前面的示例有所改进。它稍微简洁一点,现在的代码与我们使用Forms API时非常相似。我们还使用了命名状态代码,这使得响应意义更加明显。
以下是views.py模块中单个snippet的视图。
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
"""
获取,更新或删除一个snippet实例。
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
这对我们来说应该都是非常熟悉的,-它和正常Django视图并没有什么不同。
注意,我们不再显式地将请求或响应绑定到给定的内容类型。request.data可以处理传入的json请求,但它也可以处理其他格式。同样,我们返回带有数据的响应对象,但允许REST框架将响应给我们渲染成正确的内容类型。
给我们的网址添加可选的格式后缀
为了充分利用我们的响应不再与单一内容类型连接,我们可以为API路径添加对格式后缀的支持。使用格式后缀给我们明确指定了给定格式的URL,这意味着我们的API将能够处理诸如http://example.com/api/items/4.json之类的URL。
像下面这样在这两个视图中添加一个format关键字参数。
def snippet_list(request, format=None):
和
def snippet_detail(request, pk, format=None):
现在更新urls.py文件,给现有的URL后面添加一组format_suffix_patterns。
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P[0-9]+)$', views.snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns)
我们不一定需要添加这些额外的url模式,但它给了我们一个简单,清晰的方式来引用特定的格式。
怎么查看结果?
从命令行开始测试API,就像我们在教程第一部分中所做的那样。一切操作都很相似,尽管我们发送无效的请求也会有一些更好的错误处理了。
我们可以像以前一样获取所有snippet的列表。
http http://127.0.0.1:8000/snippets/
HTTP/1.1 200 OK
...
[
{
"id": 1,
"title": "",
"code": "foo = \"bar\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
我们可以通过使用Accept标头来控制我们回复的响应格式:
http http://127.0.0.1:8000/snippets/ Accept:application/json # 请求JSON
http http://127.0.0.1:8000/snippets/ Accept:text/html # 请求HTML
或者通过附加格式后缀:
http http://127.0.0.1:8000/snippets.json # JSON后缀
http http://127.0.0.1:8000/snippets.api # 浏览器可浏览API后缀
类似地,我们可以使用Content-Type头控制我们发送的请求的格式。
# POST表单数据
http --form POST http://127.0.0.1:8000/snippets/ code="print 123"
{
"id": 3,
"title": "",
"code": "print 123",
"linenos": false,
"language": "python",
"style": "friendly"
}
# POST JSON数据
http --json POST http://127.0.0.1:8000/snippets/ code="print 456"
{
"id": 4,
"title": "",
"code": "print 456",
"linenos": false,
"language": "python",
"style": "friendly"
}
如果你向上述http请求添加了--debug,则可以在请求标头中查看请求类型。
现在可以在浏览器中访问http://127.0.0.1:8000/snippets/查看API。
浏览功能
由于API根据客户端请求选择响应的内容类型,因此默认情况下,当Web浏览器请求该资源时,它将返回资源的HTML格式表示。这允许API返回完全浏览器可浏览(web-browsable)的HTML表示。
拥有支持浏览器可浏览的API在可用性方面完胜并使开发和使用你的API更容易。它也大大降低了其他开发人员要检查和使用API的障碍。
有关支持浏览器可浏览的API功能以及如何自定义API的更多信息,请参阅可浏览的api(https://q1mi.github.io/Django-REST-framework-documentation/topics/browsable-api/ )主题。
#Tutorial 3: 基于类的视图(CBV)
我们也可以使用基于类的视图编写我们的API视图,而不是基于函数的视图。我们将看到这是一个强大的模式,允许我们重用常用功能,并帮助我们保持代码DRY。
使用基于类的视图重写我们的API
我们将首先将根视图重写为基于类的视图。所有这些都是对views.py文件的重构。
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class SnippetList(APIView):
"""
列出所有的snippets或者创建一个新的snippet。
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
到现在为止还挺好。它看起来与以前的情况非常相似,但是我们在不同的HTTP方法之间有更好的分离。我们还需要更新views.py中的实例视图。
class SnippetDetail(APIView):
"""
检索,更新或删除一个snippet示例。
"""
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
看起来不错,它现在仍然非常类似于基于功能的视图。
我们还需要重构我们的urls.py,现在我们使用基于类的视图。
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view()),
url(r'^snippets/(?P[0-9]+)/$', views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
好了,我们完成了。如果你启动服务,那么它应该像之前一样可以运行。
使用混合(mixins)
使用基于类视图的最大优势之一是它可以轻松地创建可复用的行为。
到目前为止,我们使用的创建/获取/更新/删除操作和我们创建的任何基于模型的API视图非常相似。这些常见的行为是在REST框架的mixin类中实现的。
让我们来看看我们是如何通过使用mixin类编写视图的。这是我们的views.py模块。
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics
class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
我们将花点时间好好看下这里的具体实现方式。我们使用GenericAPIView构建了我们的视图,并且用上了ListModelMixin和CreateModelMixin。
基类提供核心功能,而mixin类提供.list()和.create()操作。然后我们明确地将get和post方法绑定到适当的操作。都目前为止都是显而易见的。
class SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
非常相似。这一次我们使用GenericAPIView类来提供核心功能,并添加mixins来提供.retrieve()),.update()和.destroy()操作。
使用通用的基于类的视图
通过使用mixin类,我们使用更少的代码重写了这些视图,但我们还可以再进一步。REST框架提供了一组已经混合好(mixed-in)的通用视图,我们可以使用它来简化我们的views.py模块。
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
哇,如此简洁。我们可以使用很多现成的代码,让我们的代码看起来非常清晰、简洁,地道的Django。
#Tutorial 4: 认证和权限
目前,我们的API对谁可以编辑或删除代码段没有任何限制。我们希望有更高级的行为,以确保:
代码片段始终与创建者相关联。
只有通过身份验证的用户可以创建片段。
只有代码片段的创建者可以更新或删除它。
未经身份验证的请求应具有完全只读访问权限。
在我们的模型(model)中添加信息
我们将对我们的Snippet模型类进行几次更改。首先,我们添加几个字段。其中一个字段将用于表示创建代码段的用户,另一个字段将用于存储代码的高亮显示的HTML内容。
将以下两个字段添加到models.py文件中的Snippet模型中。
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
我们还需要确保在保存模型时,使用pygments代码高亮显示库填充要高亮显示的字段。
我们需要导入额外的模块:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
现在我们可以在我们的模型类中添加一个.save()方法:
def save(self, *args, **kwargs):
"""
使用`pygments`库创建一个高亮显示的HTML表示代码段。
"""
lexer = get_lexer_by_name(self.language)
linenos = self.linenos and 'table' or False
options = self.title and {'title': self.title} or {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
完成这些工作后,我们需要更新我们的数据库表。 通常这种情况我们会创建一个数据库迁移(migration)来实现这一点,但现在我们只是个教程示例,所以我们选择直接删除数据库并重新开始。
rm -f tmp.db db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
你可能还需要创建几个不同的用户,以用于测试API。最快的方法是使用createsuperuser命令。
python manage.py createsuperuser
为我们的用户模型添加路径
现在我们已经创建了一些用户,我们最好在API中添加这些用户的表示。创建一个新的序列化器非常简单,在serializers.py文件中添加:
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'snippets')
因为'snippets' 在用户模型中是一个反向关联关系。在使用 ModelSerializer 类时它默认不会被包含,所以我们需要为它添加一个显式字段。
我们还会在views.py中添加几个视图。我们只想将用户展示为只读视图,因此我们将使用ListAPIView和RetrieveAPIView通用的基于类的视图。
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
确保导入了UserSerializer类
from snippets.serializers import UserSerializer
最后,我们需要通过在URL conf中引用它们来将这些视图添加到API中。将以下内容添加到urls.py文件的patterns中。
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P[0-9]+)/$', views.UserDetail.as_view()),
将Snippet和用户关联
现在,如果我们创建了一个代码片段,并不能将创建该代码片段的用户与代码段实例相关联。用户不是作为序列化表示的一部分发送的,而是作为传入请求的属性。(译者注:user不在传过来的数据中,而是通过request.user获得)
我们处理的方式是在我们的代码片段视图中重写一个.perform_create()方法,这样我们可以修改实例保存的方法,并处理传入请求或请求URL中隐含的任何信息。
在SnippetList视图类中,添加以下方法:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
我们的序列化器的create()方法现在将被传递一个附加的'owner'字段以及来自请求的验证数据。
更新我们的序列化器
现在,这些代码片段和创建它们的用户相关联,让我们更新我们的SnippetSerializer来体现这个关联关系。将以下字段添加到serializers.py中的序列化器定义: Add the following field to the serializer definition in serializers.py:
owner = serializers.ReadOnlyField(source='owner.username')
注意:确保你还将'owner',添加到内部Meta类的字段列表中。
这个字段非常有趣。source参数控制哪个属性用于填充字段,并且可以指向序列化实例上的任何属性。它也可以采用如上所示点加下划线的方式,在这种情况下,它将以与Django模板语言一起使用的相似方式遍历给定的属性。
我们添加的字段是无类型的ReadOnlyField类,区别于其他类型的字段(如CharField,BooleanField等)。无类型的ReadOnlyField始终是只读的,只能用于序列化表示,不能用于在反序列化时更新模型实例。我们也可以在这里使用CharField(read_only=True)。
添加视图所需的权限
现在,代码片段与用户是相关联的,我们希望确保只有经过身份验证的用户才能创建,更新和删除代码片段。
REST框架包括许多权限类,我们可以使用这些权限类来限制谁可以访问给定的视图。 在这种情况下,我们需要的是IsAuthenticatedOrReadOnly类,这将确保经过身份验证的请求获得读写访问权限,未经身份验证的请求将获得只读访问权限。
首先要在视图模块中导入以下内容
from rest_framework import permissions
然后,将以下属性添加到SnippetList和SnippetDetail视图类中。
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
给Browsable API添加登陆
如果你打开浏览器并浏览我们的API,那么你会发现不能创建新的代码片段。只有登陆用户才能创建新的代码片段。
我们可以通过编辑项目级别的urls.py文件中的URLconf来添加可浏览的API使用的登录视图。
在文件顶部添加以下导入:
from django.conf.urls import include
而且,在文件末尾添加一个模式(pattern)以包括可浏览的API的登录和注销视图。
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
模式的r'^api-auth/'部分实际上可以是你要使用的任何URL。唯一的限制是包含的URL必须使用'rest_framework'命名空间。在Django 1.9以上的版本中,REST框架将设置命名空间,因此你可以将其删除。
现在,如果你再次打开浏览器并刷新页面,你将在页面右上角看到一个“登录”链接。如果你用早期创建的用户登录,就可以再次创建代码片段。
一旦你创建了一些代码片段后,在'/users/'路径下你会注意到每个用户的'snippets'字段都包含与每个用户相关联的代码片段的列表。
对象级别的权限
我们希望所有的代码片段都可以被任何人看到,但也要确保只有创建代码片段的用户才能更新或删除它。
为此,我们将需要创建一个自定义权限。
在snippets app中,创建一个新文件permissions.py。
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
自定义权限只允许对象的所有者编辑它。
"""
def has_object_permission(self, request, view, obj):
# 读取权限允许任何请求,
# 所以我们总是允许GET,HEAD或OPTIONS请求。
if request.method in permissions.SAFE_METHODS:
return True
# 只有该snippet的所有者才允许写权限。
return obj.owner == request.user
现在,我们可以通过在SnippetDetail视图类中编辑permission_classes属性将该自定义权限添加到我们的代码片段实例路径:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
确保要先导入IsOwnerOrReadOnly类。
from snippets.permissions import IsOwnerOrReadOnly
现在,如果再次打开浏览器,你会发现如果你以代码片段创建者的身份登录的话,“DELETE”和“PUT”操作才会显示在代码片段实例路径上。
使用API进行身份验证
现在因为我们在API上有一组权限,如果我们要编辑任何代码片段,我们都需要验证我们的请求。我们还没有设置任何身份验证类,所以应用的是默认的SessionAuthentication和BasicAuthentication。
当我们通过Web浏览器与API进行交互时,我们可以登录,然后浏览器会话将为请求提供所需的身份验证。
如果我们在代码中与API交互,我们需要在每次请求上显式提供身份验证凭据。
如果我们通过没有验证就尝试创建一个代码片段,我们会像下面展示的那样收到报错:
http POST http://127.0.0.1:8000/snippets/ code="print 123"
{
"detail": "Authentication credentials were not provided."
}
我们可以通过加上我们之前创建的一个用户的用户名和密码来成功创建:
http -a tom:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
{
"id": 5,
"owner": "tom",
"title": "foo",
"code": "print 789",
"linenos": false,
"language": "python",
"style": "friendly"
}
总结
我们现在已经在我们的Web API上获得了相当精细的一组权限控制,并为系统的用户和他们创建的代码片段提供了API路径。
#Tutorial 5: 关系和超链接API
目前我们的API中的关系是用主键表示的。我们将通过使用超链接来提高我们API的内部联系。
为我们的API创建一个根路径
现在我们有'snippets'和'users'的路径,但是我们的API没有一个入口点。我们将使用一个常规的基于函数的视图和我们前面介绍的@api_view装饰器创建一个。在你的snippets/views.py中添加:
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
@api_view(['GET'])
def api_root(request, format=None):
return Response({
'users': reverse('user-list', request=request, format=format),
'snippets': reverse('snippet-list', request=request, format=format)
})
这里应该注意两件事。首先,我们使用REST框架的reverse功能来返回完全限定的URL;第二,URL模式是通过方便的名称来标识的,我们稍后将在snippets/urls.py中声明。
为高亮显示的代码片段创建路径
我们的API中另一个明显缺少的是代码高亮显示路径。
与所有其他API路径不同,我们不想使用JSON,而只是需要HTML表示。REST框架提供了两种HTML渲染器,一种用于处理使用模板渲染的HTML,另一种用于处理预渲染的HTML。第二个渲染器是我们要用于此路径的渲染器。
创建代码高亮视图时需要考虑的另一件事是,我们没有可用的具体通用视图。我们不是返回对象实例,而是返回对象实例的属性。
不是使用具体的通用视图,我们将使用基类来表示实例,并创建我们自己的.get()方法。在你的snippets/views.py中添加:
from rest_framework import renderers
from rest_framework.response import Response
class SnippetHighlight(generics.GenericAPIView):
queryset = Snippet.objects.all()
renderer_classes = (renderers.StaticHTMLRenderer,)
def get(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
像往常一样,我们需要在URLconf中添加我们创建的新视图。我们将在snippets/urls.py中为我们的新API根路径添加一个url模式:
url(r'^$', views.api_root),
然后为高亮代码片段添加一个url模式:
url(r'^snippets/(?P[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
超链接我们的API
处理好实体之间的关系是Web API设计中更具挑战性的方面。我们可以选择几种不同的方式来代表一种关系:
使用主键。
在实体之间使用超链接。
在相关实体上使用唯一的标识字段。
使用相关实体的默认字符串表示形式。
将相关实体嵌套在父表示中。
一些其他自定义表示。
REST框架支持所有这些方式,并且可以将它们应用于正向或反向关系,也可以在诸如通用外键之类的自定义管理器上应用。
在这种情况下,我们希望在实体之间使用超链接方式。这样的话,我们需要修改我们的序列化程序来扩展HyperlinkedModelSerializer而不是现有的ModelSerializer。
HyperlinkedModelSerializer与ModelSerializer有以下区别:
默认情况下不包括id字段。
它包含一个url字段,使用HyperlinkedIdentityField。
关联关系使用HyperlinkedRelatedField,而不是PrimaryKeyRelatedField。
我们可以轻松地重写我们现有的序列化程序以使用超链接。在你的snippets/serializers.py中添加:
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
class Meta:
model = Snippet
fields = ('url', 'id', 'highlight', 'owner',
'title', 'code', 'linenos', 'language', 'style')
class UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)
class Meta:
model = User
fields = ('url', 'id', 'username', 'snippets')
请注意,我们还添加了一个新的'highlight'字段。该字段与url字段的类型相同,不同之处在于它指向'snippet-highlight'url模式,而不是'snippet-detail'url模式。
因为我们已经包含了格式后缀的URL,例如'.json',我们还需要在highlight字段上指出任何格式后缀的超链接,它应该使用'.html'后缀。
确保我们的URL模式被命名
如果我们要使用超链接的API,那么需要确保为我们的URL模式命名。我们来看看我们需要命名的URL模式。
我们API的根路径是指'user-list'和'snippet-list'。
我们的代码片段序列化器包含一个指向'snippet-highlight'的字段。
我们的用户序列化器包含一个指向'snippet-detail'的字段。
我们的代码片段和用户序列化程序包括'url'字段,默认情况下将指向'{model_name}-detail',在这个例子中就是'snippet-detail'和'user-detail'。
将所有这些名称添加到我们的URLconf中后,最终我们的snippets/urls.py文件应该如下所示:
from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
# API endpoints
urlpatterns = format_suffix_patterns([
url(r'^$', views.api_root),
url(r'^snippets/$',
views.SnippetList.as_view(),
name='snippet-list'),
url(r'^snippets/(?P[0-9]+)/$',
views.SnippetDetail.as_view(),
name='snippet-detail'),
url(r'^snippets/(?P[0-9]+)/highlight/$',
views.SnippetHighlight.as_view(),
name='snippet-highlight'),
url(r'^users/$',
views.UserList.as_view(),
name='user-list'),
url(r'^users/(?P[0-9]+)/$',
views.UserDetail.as_view(),
name='user-detail')
])
# 可浏览API的登录和注销视图
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
添加分页
用户和代码片段的列表视图可能会返回相当多的实例,因此我们希望确保对结果分页,并允许API客户端依次获取每个单独的页面。
我们可以通过稍微修改我们的tutorial/settings.py文件来更改默认列表展示样式来使用分页。添加以下设置:
REST_FRAMEWORK = {
'PAGE_SIZE': 10
}
请注意,REST框架中的所有设置都放在一个名为“REST_FRAMEWORK”的字典中,这有助于区分项目中的其他设置。
如果需要的话,我们也可以自定义分页风格,但在这个例子中,我们将一直使用默认设置。
浏览API
如果我们打开浏览器并浏览我们的API,那么你可以简单的通过页面上的超链接来了解API。
你还可以看到代码片段实例上的'highlight'链接,它能带你跳转到高亮显示的代码HTML表示。
#Tutorial 6: 视图集和路由器
REST框架包括一个用于处理ViewSets的抽象,它允许开发人员集中精力对API的状态和交互进行建模,并根据常规约定自动处理URL构造。
ViewSet类与View类几乎相同,不同之处在于它们提供诸如read或update之类的操作,而不是get或put等方法处理程序。
最后一个ViewSet类只绑定到一组方法处理程序,当它被实例化成一组视图的时候,通常通过使用一个Router类来处理自己定义URL conf的复杂性。
使用ViewSets重构
我们看一下目前的视图,把它们重构成视图集。
首先让我们将UserList和UserDetail视图重构为一个UserViewSet。我们可以删除这两个视图,并用一个类替换它们:
from rest_framework import viewsets
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
此视图自动提供`list`和`detail`操作。
"""
queryset = User.objects.all()
serializer_class = UserSerializer
这里,我们使用ReadOnlyModelViewSet类来自动提供默认的“只读”操作。我们仍然像使用常规视图那样设置queryset和serializer_class属性,但我们不再需要向两个不同的类提供相同的信息。
接下来,我们将替换SnippetList,SnippetDetail和SnippetHighlight视图类。我们可以删除三个视图,并再次用一个类替换它们。
from rest_framework.decorators import detail_route
class SnippetViewSet(viewsets.ModelViewSet):
"""
此视图自动提供`list`,`create`,`retrieve`,`update`和`destroy`操作。
另外我们还提供了一个额外的`highlight`操作。
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
@detail_route(renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
这次我们使用了ModelViewSet类来获取完整的默认读写操作。
请注意,我们还使用@detail_route装饰器创建一个名为highlight的自定义操作。这个装饰器可用于添加不符合标准create/update/delete样式的任何自定义路径。
默认情况下,使用@detail_route装饰器的自定义操作将响应GET请求。如果我们想要一个响应POST请求的动作,我们可以使用methods参数。
默认情况下,自定义操作的URL取决于方法名称本身。如果要更改URL的构造方式,可以为装饰器设置url_path关键字参数。
明确地将ViewSets绑定到URL
当我们定义URLConf时,处理程序方法只能绑定到那些动作上。 要了解在幕后发生的事情,首先显式地从我们的视图集中创建一组视图。
在urls.py文件中,我们将ViewSet类绑定到一组具体视图中。
from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers
snippet_list = SnippetViewSet.as_view({
'get': 'list',
'post': 'create'
})
snippet_detail = SnippetViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
})
snippet_highlight = SnippetViewSet.as_view({
'get': 'highlight'
}, renderer_classes=[renderers.StaticHTMLRenderer])
user_list = UserViewSet.as_view({
'get': 'list'
})
user_detail = UserViewSet.as_view({
'get': 'retrieve'
})
请注意,我们是如何通过将http方法绑定到每个视图所需的操作,从每个ViewSet类创建多个视图的。
现在我们将资源绑定到具体的视图中,我们可以像往常一样在URL conf中注册视图。
urlpatterns = format_suffix_patterns([
url(r'^$', api_root),
url(r'^snippets/$', snippet_list, name='snippet-list'),
url(r'^snippets/(?P[0-9]+)/$', snippet_detail, name='snippet-detail'),
url(r'^snippets/(?P[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
url(r'^users/$', user_list, name='user-list'),
url(r'^users/(?P[0-9]+)/$', user_detail, name='user-detail')
])
使用路由器
因为我们使用的是ViewSet类而不是View类,我们实际上不需要自己设计URL。将资源连接到视图和url的约定可以使用Router类自动处理。我们需要做的就是使用路由器注册相应的视图集,然后让它执行其余操作。
这是我们重写的urls.py文件。
from django.conf.urls import url, include
from snippets import views
from rest_framework.routers import DefaultRouter
# 创建路由器并注册我们的视图。
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)
# API URL现在由路由器自动确定。
# 另外,我们还要包含可浏览的API的登录URL。
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
使用路由器注册viewsets类似于提供urlpattern。我们包含两个参数 - 视图的URL前缀和视图本身。
我们使用的DefaultRouter类也会自动为我们创建API根视图,因此我们现在可以从我们的views模块中删除api_root方法。
视图(views)vs视图集(viewsets)之间的权衡
使用视图集可以是一个非常有用的抽象。它有助于确保URL约定在你的API中保持一致,最大限度地减少编写所需的代码量,让你能够专注于API提供的交互和表示,而不是URLconf的细节。
这并不意味着采用视图集总是正确的方法。在使用基于类的视图而不是基于函数的视图时,有一个类似的权衡要考虑。使用视图集不像单独构建视图那样明确。
#Tutorial 7: 概要和客户端库
概要是一种机器可读文档,用于描述可用的API路径,其URLS以及它们支持的操作。
概要可以是自动生成文档的有用工具,也可以用于驱动可以与API进行交互的动态客户端库。
Core API
为了提供概要支持REST框架使用Core API。
Core API是用于描述API的文档规范。它用于提供可用路径的内部表示形式和API公开的可能的交互。它可以用于服务器端或客户端。
当使用服务器端时,coreAPI允许API支持呈现范围广泛的概要或超媒体格式。
当使用客户端时,核心API允许动态驱动的客户端库,它可以与任何公开受支持的概要或超媒体格式的API交互。
添加概要
REST框架支持明确定义的概要视图或自动生成的概要。由于我们使用的是视图集和路由器,我们可以简单地使用自动概要生成。
你需要安装coreapi python包才能包含API概要。
$ pip install coreapi
现在我们可以通过在URL配置中包含一个自动生成的概要视图来为API添加概要。
from rest_framework.schemas import get_schema_view
schema_view = get_schema_view(title='Pastebin API')
urlpatterns = [
url('^schema/$', schema_view),
...
]
如果你在浏览器中访问API根路径,那么你现在应该可以看到corejson表示形式是一个可用选项。
Schema format
我们也可以通过在Accept标头中指定所需的内容类型从命令行请求概要。
$ http http://127.0.0.1:8000/schema/ Accept:application/coreapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/coreapi+json
{
"_meta": {
"title": "Pastebin API"
},
"_type": "document",
...
默认输出样式是使用Core JSON编码。
还支持其他概要格式,如Open API(以前叫Swagger)。
使用命令行客户端
现在我们的API暴露了一个概要路径,我们可以使用一个动态的客户端库与API进行交互。为了演示这个,我们来使用Core API命令行客户端。
命令行客户端作为一个coreapi-cli包提供:
$ pip install coreapi-cli
现在检查它在命令行上是否可用...
$ coreapi
Usage: coreapi [OPTIONS] COMMAND [ARGS]...
Command line client for interacting with CoreAPI services.
Visit http://www.coreapi.org for more information.
Options:
--version Display the package version number.
--help Show this message and exit.
Commands:
...
首先,我们将使用命令行客户端加载API概要。
$ coreapi get http://127.0.0.1:8000/schema/
snippets: {
highlight(id)
list()
read(id)
}
users: {
list()
read(id)
}
我们还没有认证,所以现在我们只能看到只读路径,这与我们设置的API权限是一致的。
我们使用命令行客户端,尝试列出现有的代码片段:
$ coreapi action snippets list
[
{
"url": "http://127.0.0.1:8000/snippets/1/",
"id": 1,
"highlight": "http://127.0.0.1:8000/snippets/1/highlight/",
"owner": "lucy",
"title": "Example",
"code": "print('hello, world!')",
"linenos": true,
"language": "python",
"style": "friendly"
},
...
一些API路径需要命名参数。例如,要获取特定代码片段的高亮HTML表示,我们需要提供一个id。
$ coreapi action snippets highlight --param id=1
Example
...
验证我们的客户端
如果我们想要创建,编辑和删除代码片段,我们需要进行有效性用户身份验证。在这种情况下,我们只需使用基本的auth。
请确保使用实际的用户名和密码替换下面的和。
$ coreapi credentials add 127.0.0.1 : --auth basic
Added credentials
127.0.0.1 "Basic <...>"
现在,如果我们再次提取概要,我们应该能够看到一组可用的交互。
$ coreapi reload
Pastebin API "http://127.0.0.1:8000/schema/">
snippets: {
create(code, [title], [linenos], [language], [style])
delete(id)
highlight(id)
list()
partial_update(id, [title], [code], [linenos], [language], [style])
read(id)
update(id, code, [title], [linenos], [language], [style])
}
users: {
list()
read(id)
}
我们现在能够与这些路径行交互。例如,要创建一个新的代码片段:
$ coreapi action snippets create --param title="Example" --param code="print('hello, world')"
{
"url": "http://127.0.0.1:8000/snippets/7/",
"id": 7,
"highlight": "http://127.0.0.1:8000/snippets/7/highlight/",
"owner": "lucy",
"title": "Example",
"code": "print('hello, world')",
"linenos": false,
"language": "python",
"style": "friendly"
}
然后删除一个代码片段:
$ coreapi action snippets delete --param id=7
除了命令行客户端,开发人员还可以使用客户端库与你的API进行交互。Python客户端库是第一个可用的库,并且计划即将发布一个Javascript客户端库。
有关定制模式生成和使用Core API客户端库的更多详细信息,您需要参考完整的文档。
回顾我们所做的
使用非常少的代码,我们现在有一个代码收集系统的webAPI,它是完全的浏览器可浏览的,包括一个概要驱动的客户端库和带有身份验证、每个对象的权限控制和多个渲染器格式支持。
我们已经走过了设计过程的每个步骤,并且看到如果我们需要自定义任何东西,我们都可以按部就班的简单地使用常规的Django视图实现。