# 服务器上窗户文件夹
cd tutorial
# 搭建django项目
...
# 安装djangorestframewor
pip install djangorestframewor
# 创建app应用
django-admin.py startapp quickstart
同步数据库
python manage.py migrate
创建用户
admin的初始用户,密码为
password123
python manage.py createsuperuser
创建一个名为 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')
编写tutorial/quickstart/views.py
from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from .serializers import UserSerializer, GroupSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
允许用户查看或编辑的API路径。
"""
# queryset 数据库查询的参数
queryset = User.objects.all().order_by('-date_joined')
# serializer_class 列表数据过滤、序列号输出
serializer_class = UserSerializer
class GroupViewSet(viewsets.ModelViewSet):
"""
允许组查看或编辑的API路径。
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
在tutorial/urls.py
中开始写连接API的URLs
from django.conf.urls import url, include
from rest_framework import routers
from quickstart import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
urlpatterns = [
url(r'', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
设置一些全局设置。例:打开分页,我们希望我们的API只能由管理员使用
设置模块都在 tutorial/settings.py
中
INSTALLED_APPS = (
...
'rest_framework',
)
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAdminUser',
],
'PAGE_SIZE': 10
}
启动服务器
python manage.py runserver
命令行访Api
curl -H 'Accept: application/json; indent=4' -u admin:password123 http://127.0.0.1:8000/users/
重新创建项目
cd ~django-admin.py startproject tutorial
cd tutorial
安装插件与创建app
pip install pygments # 代码高亮插件
python manage.py startapp snippets
配置settings.py
INSTALLED_APPS = (
...
'rest_framework',
'snippets.apps.SnippetsConfig',
)
编辑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',)
数据迁移
python manage.py makemigrations snippets
python manage.py migrate
在snippets的目录下创建一个名为serializers.py文件,将代码片段实例序列化和反序列化为诸如json之类
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
进入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': 1, 'title': '', 'code': 'print "hello, world"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
将模型实例转换为Python原生数据类型。要完成序列化过程,我们将数据转换成json
content = JSONRenderer().render(serializer.data)
content
b'{"id":1,"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"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
#
只需要为serializer添加一个many=True
标志
>>> serializer = SnippetSerializer(Snippet.objects.all(), many=True)
>>> serializer.data
# [OrderedDict([('id', 1), ('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
打开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))
编辑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)
一个与单个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),
]
退出shell
quit()
启动django
可以使用curl或httpie测试我们的服务器。Httpie是用Python编写的用户友好的http客户端
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"
}
Request
对象的核心功能是request.data
属性,它与request.POST
类似
request.POST # 只处理表单数据 只适用于'POST'方法
# request.data可以处理传入的json请求,但它也可以处理其他格式
request.data # 处理任意数据 适用于'POST','PUT'和'PATCH'方法
Response
对象,这是一种获取未渲染(unrendered)内容的TemplateResponse
类型,
并使用内容协商来确定返回给客户端的正确内容类型
return Response(data) # 渲染成客户端请求的内容类型
REST框架为status
模块中的每个状态代码(如HTTP_400_BAD_REQUEST
)提供更明确的标识符。
使用它们来代替纯数字的HTTP状态码是个很好的主意
REST框架提供了两个可用于编写API视图的包装器(wrappers)
@api_view
装饰器APIView
类这些包装器提供了一些功能,例如确保你在视图中接收到Request
实例,
并将上下文添加到Response
,以便可以执行内容协商
使用新的请求与相应修改之前的列表与详情
我们还使用了命名状态代码,这使得响应意义更加明显
列表
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)
详情
@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)
为了充分利用我们的响应不再与单一内容类型连接,我们可以为API路径添加对格式后缀的支持。使用格式后缀给我们明确指定了给定格式的URL,这意味着我们的API将能够处理诸如http://example.com/api/items/4.json
像下面这样在这两个视图中添加一个format
关键字参数
def snippet_list(request, format=None):
和
# 详情传入文章id,pk为文章id
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)
我们可以像以前一样获取所有snippet的列表
http http://127.0.0.1:8000/snippets/
可以通过使用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
,则可以在请求标头中查看请求类型
对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)
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)
from django.conf.urls import url
from snippets import views
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view()),
url(r'^snippets/(?P[0-9]+)/$' , views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
到目前为止,我们使用的创建/获取/更新/删除操作和我们创建的任何基于模型的API视图非常相似。
这些常见的行为是在REST框架的mixin类中实现的
编辑views.py
模块
generics
mixins
ListModelMixin 获取列表数据
RetrieveModelMixin 获取详情数据
CreateModelMixin 创建数据
UpdateModelMixin 修改数据
DestroyModelMixin 删除数据
self操作数据
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)
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)
REST框架提供了一组已经混合好(mixed-in)的通用视图,我们可以使用它来简化我们的views.py
模块
ListCreateAPIView 查看与创建
RetrieveUpdateDestroyAPIView 增删改
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics
class SnippetList(generics.ListCreateAPIView):
""""
列出所有的snippets或者创建一个新的snippet。
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
"""
检索,更新或删除一个snippet示例。
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
将以下两个字段添加到models.py
文件中的Snippet
模型中
# 创建代码段的用户
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
# 存储代码的高亮显示的HTML内容
highlighted = models.TextField()
导入额外的模块
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
需要创建几个不同的用户
python manage.py createsuperuser
zzw / 123
baba / 123
mama / 123
创建一个新的序列化器非常简单,在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
中添加几个视图。我们只想将用户展示为只读视图
from django.contrib.auth.models import User
from snippets.serializers import UserSerializer
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
添加路由
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P[0-9]+)/$' , views.UserDetail.as_view()),
如果我们创建了一个代码片段,并不能将创建该代码片段的用户与代码段实例相关联。
用户不是作为序列化表示的一部分发送的,而是作为传入请求的属性。
(译者注:user不在传过来的数据中,而是通过request.user获得)
我们处理的方式是在我们的代码片段视图中重写一个.perform_create()
方法,这样我们可以修改实例保存的方法,并处理传入请求或请求URL中隐含的任何信息
在SnippetList
视图类中,添加以下方法:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
我们的序列化器的create()
方法现在将被传递一个附加的'owner'
字段以及来自请求的验证数据
将以下字段添加到serializers.py
中的序列化器定义:
owner = serializers.ReadOnlyField(source='owner.username')
注意:确保你还将'owner',
添加到内部Meta
类的字段列表中。
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Snippet
# owner 添加字段
fields = ('id', 'title', 'code', 'linenos', 'language', 'style','owner')
代码片段与用户是相关联的,我们希望确保只有经过身份验证的用户才能创建,更新和删除代码片段
视图导入模块
from rest_framework import permissions
然后,将以下属性添加到SnippetList
和SnippetDetail
视图类中
# 仅可读
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
项目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'
命名空间
我们希望所有的代码片段都可以被任何人看到,但也要确保只有创建代码片段的用户才能更新或删除它。
为此,我们将需要创建一个自定义权限
在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
属性将该自定义权限添加到我们的代码片段实例路径
from .permissions import IsOwnerOrReadOnly
#...
permission_classes = (permissions.IsAuthenticatedOrReadOnly,IsOwnerOrReadOnly,)
未登录时请求
http POST http://127.0.0.1:8000/snippets/ code="print 123"
HTTP/1.1 403 Forbidden
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 43
Content-Type: application/json
Date: Fri, 17 Jan 2020 09:03:33 GMT
Server: WSGIServer/0.2 CPython/3.6.6
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN
{
"detail": "身份认证信息未提供。"
}
请求加上用户名与密码请求创建
http -a zzw:123 POST http://127.0.0.1:8000/snippets/ code="print 789"
HTTP/1.1 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 107
Content-Type: application/json
Date: Sat, 18 Jan 2020 13:27:06 GMT
Server: WSGIServer/0.2 CPython/3.6.6
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN
{
"code": "print 789",
"id": 1,
"language": "python",
"linenos": false,
"owner": "zzw",
"style": "friendly",
"title": ""
}
为’snippets’和’users’创建Api入口点,在你的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)
})
这里应该注意两件事。
reverse
功能来返回完全限定的URLsnippets/urls.py
中声明REST框架提供了两种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)
在snippets/urls.py
中为我们的新API根路径添加一个url模式
url(r'^$', views.api_root),
然后为高亮代码片段添加一个url模式:
url(r'^snippets/(?P[0-9]+)/highlight/$' , views.SnippetHighlight.as_view()),
我们希望在实体之间使用超链接方式。这样的话,
我们需要修改我们的序列化程序来扩展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'
后缀。
如果我们要使用超链接的API,那么需要确保为我们的URL模式命名。我们来看看我们需要命名的URL模式。
'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”的字典中,这有助于区分项目中的其他设置。
http -a zzw:123 POST http://127.0.0.1:8000/snippets/ code="print 789"
HTTP/1.1 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 207
Content-Type: application/json
Date: Sat, 18 Jan 2020 14:14:04 GMT
Location: http://127.0.0.1:8000/snippets/3/
Server: WSGIServer/0.2 CPython/3.6.6
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN
{
"code": "print 789",
"highlight": "http://127.0.0.1:8000/snippets/3/highlight/",
"id": 3,
"language": "python",
"linenos": false,
"owner": "zzw",
"style": "friendly",
"title": "",
"url": "http://127.0.0.1:8000/snippets/3/"
}
REST框架包括一个用于处理ViewSets
的抽象,它允许开发人员集中精力对API的状态和交互进行建模,并根据常规约定自动处理URL构造
ViewSet
类与View
类几乎相同,不同之处在于它们提供诸如read
或update
之类的操作,而不是get
或put
等方法处理程序
将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关键字参数。
cannot import name detail_route错误
执行from rest_framework.decorators import detail_route
时,报cannot import name detail_route错误
查看decorators.py源码,发现原因是从2.4.0才有这个方法,而公司用的是2.3.14,所以没有。
在之前旧的版本使用的是detail_route,list_route连个装饰器来添加额外的路由,用来区别url中是否有pk字段。action装饰器则是通过detail字段来区别
from rest_framework.decorators import action
@action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
当我们定义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
方法。
使用视图集可以是一个非常有用的抽象。它有助于确保URL约定在你的API中保持一致,最大限度地减少编写所需的代码量,让你能够专注于API提供的交互和表示,而不是URLconf的细节。
这并不意味着采用视图集总是正确的方法。在使用基于类的视图而不是基于函数的视图时,有一个类似的权衡要考虑。使用视图集不像单独构建视图那样明确
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
表示形式是一个可用选项。
我们也可以通过在Accept
标头中指定所需的内容类型从命令行请求概要
$ http http://127.0.0.1:8000/schema/ Accept:vnd.oai.openapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/coreapi+json
{
"_meta": {
"title": "Pastebin API"
},
"_type": "document",
...
现在我们的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:
...