title: “ django-rest-framework教程中文版\t\t”
tags:

  • django
  • rest
    url: 1175.html
    id: 1175
    categories:
  • 后端
    date: 2018-04-24 16:19:46

以下内容来源:Django-REST-Framework-Tutorial_zh-CN

Tutorial 1: 序列化 Serialization

src

1. 设置一个新的环境

在我们开始之前, 我们首先使用virtualenv要创建一个新的虚拟环境,以使我们的配置和我们的其他项目配置彻底分开。

  1. $mkdir ~/env
  2. $virtualenv ~/env/tutorial
  3. $source ~/env/tutorial/bin/avtivate

现在我们处在一个虚拟的环境中,开始安装我们的依赖包

  1. $pip install django
  2. $pip install djangorestframework
  3. $pip install pygments ////使用这个包,做代码高亮显示

需要退出虚拟环境时,运行deactivate。更多信息,virtualenv document

2. 开始

环境准备好只好,我们开始创建我们的项目

  1. $ cd ~
  2. $ django-admin.py startproject tutorial
  3. $ cd tutorial

项目创建好后,我们再创建一个简单的app

  1. $python manage.py startapp snippets

我们使用sqlite3来运行我们的项目tutorial,编辑tutorial/settings.py, 将数据库的默认引擎engine改为sqlite3, 数据库的名字NAME改为tmp.db

  1. DATABASES = {
  2. 'default': {
  3. 'ENGINE': 'django.db.backends.sqlite3',
  4. 'NAME': 'tmp.db',
  5. 'USER': '',
  6. 'PASSWORD': '',
  7. 'HOST': '',
  8. 'PORT': '',
  9. }
  10. }

同时更改settings.py文件中的INSTALLD_APPS,添加我们的APP snippetsrest_framework

  1. INSTALLED_APPS = (
  2. ...
  3. 'rest_framework',
  4. 'snippets',
  5. )

tutorial/urls.py中,将snippets app的url包含进来

  1. urlpatterns = patterns('',
  2. url(r'^', include('snippets.urls')),
  3. )

3. 创建Model

这里我们创建一个简单的snippets model,目的是用来存储代码片段。

  1. from django.db import models
  2. from pygments.lexers import get_all_lexers
  3. from pygments.styles import get_all_styles
  4. LEXERS = [item for item in get_all_lexers() if item[1]]
  5. LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
  6. STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
  7. class Snippet(models.Model):
  8. created = models.DateTimeField(auto_now_add=True)
  9. title = models.CharField(max_length=100, default='')
  10. code = models.TextField()
  11. linenos = models.BooleanField(default=False)
  12. language = models.CharField(choices=LANGUAGE_CHOICES,
  13. default='python',
  14. max_length=100)
  15. style = models.CharField(choices=STYLE_CHOICES,
  16. default='friendly',
  17. max_length=100)
  18. class Meta:
  19. ordering = ('created',)

完成model时,记得sync下数据库

  1. python manage.py syncdb

4. 创建序列化类

我们要使用我们的web api,要做的第一件事就是序列化和反序列化, 以便snippets实例能转换为可表述的内容,例如json. 我们声明一个可有效工作的串行器serializer。在snippets目录下面,该串行器与django 的表单形式很类似。创建一个serializers.py ,并将下面内容拷贝到文件中。

  1. from django.forms import widgets
  2. from rest_framework import serializers
  3. from snippets.models import Snippet
  4. class SnippetSerializer(serializers.Serializer):
  5. pk = serializers.Field() # Note: `Field` is an untyped read-only field.
  6. title = serializers.CharField(required=False,
  7. max_length=100)
  8. code = serializers.CharField(widget=widgets.Textarea,
  9. max_length=100000)
  10. linenos = serializers.BooleanField(required=False)
  11. language = serializers.ChoiceField(choices=models.LANGUAGE_CHOICES,
  12. default='python')
  13. style = serializers.ChoiceField(choices=models.STYLE_CHOICES,
  14. default='friendly')
  15. def restore_object(self, attrs, instance=None):
  16. """
  17. Create or update a new snippet instance.
  18. """
  19. if instance:
  20. # Update existing instance
  21. instance.title = attrs['title']
  22. instance.code = attrs['code']
  23. instance.linenos = attrs['linenos']
  24. instance.language = attrs['language']
  25. instance.style = attrs['style']
  26. return instance
  27. # Create new instance
  28. return Snippet(**attrs)

该序列化类的前面部分,定义了要序列化和反序列化的类型,restore_object 方法定义了如何通过反序列化数据,生成正确的对象实例。

Notice that we can also use various attributes that would typically be used on form fields, such as widget=widgets.Textarea. These can be used to control how the serializer should render when displayed as an HTML form. This is particularly useful for controlling how the browsable API should be displayed, as we’ll see later in the tutorial.

我们也可以使用ModelSerializer来快速生成,后面我们将节省如何使用它。

5. 使用 Serializers

在我们使用我们定义的SnippetsSerializers之前,我们先熟悉下Snippets.

  1. $python manage.py shell

进入shell终端后,输入以下代码:

  1. from snippets.models import Snippet
  2. from snippets.serializers import SnippetSerializer
  3. from rest_framework.renderers import JSONRenderer
  4. from rest_framework.parsers import JSONParser
  5. snippet = Snippet(code='print "hello, world"\n')
  6. snippet.save()

我们现在获得了一个Snippets的实例,现在我们对他进行以下序列化

  1. serializer = SnippetSerializer(snippet)
  2. serializer.data
  3. # {'pk': 1, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}

这时,我们将该实例转成了python原生的数据类型。下面我们将该数据转换成json格式,以完成序列化:

  1. content = JSONRenderer().render(serializer.data)
  2. content
  3. # '{"pk": 1, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'

反序列化也很简单,首先我们要将一个输入流(content),转换成python的原生数据类型

  1. import StringIO
  2. stream = StringIO.StringIO(content)
  3. data = JSONParser().parse(stream)

然后我们将该原生数据类型,转换成对象实例

  1. serializer = SnippetSerializer(data=data)
  2. serializer.is_valid()
  3. # True
  4. serializer.object
  5. # <Snippet: Snippet object>

注意这些API和django表单的相似处。这些相似点, 在我们讲述在view中使用serializers时将更加明显。

We can also serialize querysets instead of model instances. To do so we simply add a many=True flag to the serializer arguments.

  1. serializer = SnippetSerializer(Snippet.objects.all(), many=True)
  2. serializer.data
  3. # [{'pk': 1, 'title': u'', 'code': u'foo = "bar"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}, {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}]

6. 使用 ModelSerializers

SnippetSerializer使用了许多和Snippet中相同的代码。如果我们能把这部分代码去掉,看上去将更佳简洁。

类似与django提供Form类和ModelForm类,Rest Framework也包含了Serializer 类和 ModelSerializer类。

打开snippets/serializers.py ,修改SnippetSerializer类:

  1. class SnippetSerializer(serializers.ModelSerializer):
  2. class Meta:
  3. model = Snippet
  4. fields = ('id', 'title', 'code', 'linenos', 'language', 'style')

7. 通过Serializer编写Django View

让我们来看一下,如何通过我们创建的serializer类编写django view。这里我们不使用rest framework的其他特性,仅编写正常的django view。

我们创建一个HttpResponse 子类,这样我们可以将我们返回的任何数据转换成json

snippet/views.py中添加以下内容:

  1. from django.http import HttpResponse
  2. from django.views.decorators.csrf import csrf_exempt
  3. from rest_framework.renderers import JSONRenderer
  4. from rest_framework.parsers import JSONParser
  5. from snippets.models import Snippet
  6. from snippets.serializers import SnippetSerializer
  7. class JSONResponse(HttpResponse):
  8. """
  9. An HttpResponse that renders it's content into JSON.
  10. """
  11. def __init__(self, data, **kwargs):
  12. content = JSONRenderer().render(data)
  13. kwargs['content_type'] = 'application/json'
  14. super(JSONResponse, self).__init__(content, **kwargs)

我们API的目的是,可以通过view来列举全部的Snippet的内容,或者创建一个新的snippet

  1. @csrf_exempt
  2. def snippet_list(request):
  3. """
  4. List all code snippets, or create a new snippet.
  5. """
  6. if request.method == 'GET':
  7. snippets = Snippet.objects.all()
  8. serializer = SnippetSerializer(snippets)
  9. return JSONResponse(serializer.data)
  10. elif request.method == 'POST':
  11. data = JSONParser().parse(request)
  12. serializer = SnippetSerializer(data=data)
  13. if serializer.is_valid():
  14. serializer.save()
  15. return JSONResponse(serializer.data, status=201)
  16. else:
  17. return JSONResponse(serializer.errors, status=400)

注意,因为我们要通过client向该view post一个请求,所以我们要将该view 标注为csrf_exempt, 以说明不是一个CSRF事件。

Note that because we want to be able to POST to this view from clients that won’t have a CSRF token we need to mark the view as csrf_exempt. This isn’t something that you’d normally want to do, and REST framework views actually use more sensible behavior than this, but it’ll do for our purposes right now.

我们也需要一个view来操作一个单独的Snippet,以便能更新/删除该对象。

  1. @csrf_exempt
  2. def snippet_detail(request, pk):
  3. """
  4. Retrieve, update or delete a code snippet.
  5. """
  6. try:
  7. snippet = Snippet.objects.get(pk=pk)
  8. except Snippet.DoesNotExist:
  9. return HttpResponse(status=404)
  10. if request.method == 'GET':
  11. serializer = SnippetSerializer(snippet)
  12. return JSONResponse(serializer.data)
  13. elif request.method == 'PUT':
  14. data = JSONParser().parse(request)
  15. serializer = SnippetSerializer(snippet, data=data)
  16. if serializer.is_valid():
  17. serializer.save()
  18. return JSONResponse(serializer.data)
  19. else:
  20. return JSONResponse(serializer.errors, status=400)
  21. elif request.method == 'DELETE':
  22. snippet.delete()
  23. return HttpResponse(status=204)

将views.py保存,在Snippets目录下面创建urls.py,添加以下内容:

  1. urlpatterns = patterns('snippets.views',
  2. url(r'^snippets/$', 'snippet_list'),
  3. url(r'^snippets/(?P<pk>[0-9]+)/$', 'snippet_detail'),
  4. )

注意我们有些边缘事件没有处理,服务器可能会抛出500异常。

It’s worth noting that there are a couple of edge cases we’re not dealing with properly at the moment. If we send malformed json, or if a request is made with a method that the view doesn’t handle, then we’ll end up with a 500 “server error” response. Still, this’ll do for now.

8. 测试

现在我们启动server来测试我们的Snippet。

在python mange.py shell终端下执行(如果前面进入还没有退出)

quit()

执行下面的命令, 运行我们的server:

  1. python manage.py runserver
  2. Validating models...
  3. 0 errors found
  4. Django version 1.4.3, using settings 'tutorial.settings'
  5. Development server is running at http://127.0.0.1:8000/
  6. Quit the server with CONTROL-C.

新开一个terminal来测试我们的server

序列化:

  1. curl http://127.0.0.1:8000/snippets/
  2. >>[{"id": 1, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}]
  3. curl http://127.0.0.1:8000/snippets/1/
  4. >>{"id": 1, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}

Tutorial 2: Requests and Response

src

从本节我们开始真正接触rest framework的核心部分。首先我们学习一下一些必备知识。

1. Request Object ——Request对象

rest framework 引入了一个继承自HttpRequestRequest对象,该对象提供了对请求的更灵活解析。request对象的核心部分是request.DATA属性,类似于request.POST, 但在使用WEB API时,request.DATA更有效。

request.POST # Only handles form data. Only works for ‘POST’ method. request.DATA # Handles arbitrary data. Works any HTTP request with content.

2. Response Object ——Response对象

rest framework引入了一个Response 对象,它继承自TemplateResponse对象。它获得未渲染的内容并通过内容协商content negotiation 来决定正确的content type返回给client。

  1. return Response(data) # Renders to content type as requested by the client.

3. Status Codes

在views当中使用数字化的HTTP状态码,会使你的代码不宜阅读,且不容易发现代码中的错误。rest framework为每个状态码提供了更明确的标识。例如HTTP_400_BAD_REQUESTstatus module。相比于使用数字,在整个views中使用这类标识符将更好。

4. 封装API views

在编写API views时,REST Framework提供了两种wrappers:

  1. @api_viwedecorator for working with function based views.
  2. APIView class for working with class based views.

这两种封装器提供了许多功能,例如,确保在view当中能够接收到Request实例;往Response中增加内容以便内容协商content negotiation 机制能够执行。

封装器也提供一些行为,例如在适当的时候返回405 Methord Not Allowed响应;在访问多类型的输入request.DATA时,处理任何的ParseError异常。

5. 汇总

我们开始用这些新的组件来写一些views。

我们不在需要JESONResponse 类(在前一篇中创建),将它删除。删除后我们开始稍微重构下我们的view

  1. from rest_framework import status
  2. from rest_framework.decorators import api_view
  3. from rest_framework.response import Response
  4. from snippets.models import Snippet
  5. from snippets.serializers import SnippetSerializer
  6. @api_view(['GET', 'POST'])
  7. def snippet_list(request):
  8. """
  9. List all snippets, or create a new snippet.
  10. """
  11. if request.method == 'GET':
  12. snippets = Snippet.objects.all()
  13. serializer = SnippetSerializer(snippets)
  14. return Response(serializer.data)
  15. elif request.method == 'POST':
  16. serializer = SnippetSerializer(data=request.DATA)
  17. if serializer.is_valid():
  18. serializer.save()
  19. return Response(serializer.data, status=status.HTTP_201_CREATED)
  20. else:
  21. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

上面的代码是对我们之前代码的改进。看上去更简洁,也更类似于django的forms api形式。我们也采用了状态码,使返回值更加明确。 下面是对单个snippet操作的view更新:

  1. @api_view(['GET', 'PUT', 'DELETE'])
  2. def snippet_detail(request, pk):
  3. """
  4. Retrieve, update or delete a snippet instance.
  5. """
  6. try:
  7. snippet = Snippet.objects.get(pk=pk)
  8. except Snippet.DoesNotExist:
  9. return Response(status=status.HTTP_404_NOT_FOUND)
  10. if request.method == 'GET':
  11. serializer = SnippetSerializer(snippet)
  12. return Response(serializer.data)
  13. elif request.method == 'PUT':
  14. serializer = SnippetSerializer(snippet, data=request.DATA)
  15. if serializer.is_valid():
  16. serializer.save()
  17. return Response(serializer.data)
  18. else:
  19. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  20. elif request.method == 'DELETE':
  21. snippet.delete()
  22. return Response(status=status.HTTP_204_NO_CONTENT)

注意,我们并没有明确的要求requests或者responses给出content type。request.DATA可以处理输入的json请求,也可以输入yaml和其他格式。类似的在response返回数据时,REST Framework返回正确的content type给client。

6. 给URLs增加可选的格式后缀

利用在response时不需要指定content type这一事实,我们在API端增加格式的后缀。使用格式后缀,可以明确的指出使用某种格式,意味着我们的API可以处理类似http://example.com/api/items/4.json.的URL。

增加format参数在views中,如:

  1. def snippet_list(request, format=None):

and

  1. def snippet_detail(request, pk, format=None):

现在稍微改动urls.py文件,在现有的URLs中添加一个格式后缀pattterns (format_suffix_patterns):

  1. from django.conf.urls import patterns, url
  2. from rest_framework.urlpatterns import format_suffix_patterns
  3. urlpatterns = patterns('snippets.views',
  4. url(r'^snippets/$', 'snippet_list'),
  5. url(r'^snippets/(?P<pk>[0-9]+)$', 'snippet_detail'),
  6. )

urlpatterns = format_suffix_patterns(urlpatterns) 这些额外的url patterns并不是必须的。

7. How’s it looking?

Go ahead and test the API from the command line, as we did in tutorial part 1. Everything is working pretty similarly, although we’ve got some nicer error handling if we send invalid requests.

We can get a list of all of the snippets, as before.

  1. curl http://127.0.0.1:8000/snippets/
  2. [{"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"}]

We can control the format of the response that we get back, either by using the Accept header:

  1. curl http://127.0.0.1:8000/snippets/ -H 'Accept: application/json' # Request JSON
  2. curl http://127.0.0.1:8000/snippets/ -H 'Accept: text/html' # Request HTML

Or by appending a format suffix:

  1. curl http://127.0.0.1:8000/snippets/.json # JSON suffix
  2. curl http://127.0.0.1:8000/snippets/.api # Browsable API suffix

Similarly, we can control the format of the request that we send, using the Content-Type header.

  1. # POST using form data
  2. curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 123"
  3. {"id": 3, "title": "", "code": "123", "linenos": false, "language": "python", "style": "friendly"}
  4. # POST using JSON
  5. curl -X POST http://127.0.0.1:8000/snippets/ -d '{"code": "print 456"}' -H "Content-Type: application/json"
  6. {"id": 4, "title": "", "code": "print 456", "linenos": true, "language": "python", "style": "friendly"}

Now go and open the API in a web browser, by visiting http://127.0.0.1:8000/snippets/.

8. Browsability

Because the API chooses the content type of the response based on the client request, it will, by default, return an HTML-formatted representation of the resource when that resource is requested by a web browser. This allows for the API to return a fully web-browsable HTML representation.

Having a web-browsable API is a huge usability win, and makes developing and using your API much easier. It also dramatically lowers the barrier-to-entry for other developers wanting to inspect and work with your API.

See the browsable api topic for more information about the browsable API feature and how to customize it.

Tutorial 3: Class Based Views

src

在之前基于函数的View之外,我们还可以用基于类的view来实现我们的API view。正如我们即将看到的那样,这样的方式可以让我们重用公用功能,并使我们保持代码DRY。

用基于类的view重写我们的API

1. 我们要用基于类的view来重写刚才的根view,如下重构所示:

  1. from snippets.models import Snippet
  2. from snippets.serializers import SnippetSerializer
  3. from django.http import Http404
  4. from rest_framework.views import APIView
  5. from rest_framework.response import Response
  6. from rest_framework import status
  7. class SnippetList(APIView):
  8. """
  9. List all snippets, or create a new snippet.
  10. """
  11. def get(self, request, format=None):
  12. snippets = Snippet.objects.all()
  13. serializer = SnippetSerializer(snippets, many=True)
  14. return Response(serializer.data)
  15. def post(self, request, format=None):
  16. serializer = SnippetSerializer(data=request.DATA)
  17. if serializer.is_valid():
  18. serializer.save()
  19. return Response(serializer.data, status=status.HTTP_201_CREATED)
  20. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

目前看上去不错。它看起来和我们之前写的很相似,但我们在不同的HTTP方法见有了更好的分隔方式,我们还需要把示例的view也重构一下:

  1. class SnippetDetail(APIView):
  2. """
  3. Retrieve, update or delete a snippet instance.
  4. """
  5. def get_object(self, pk):
  6. try:
  7. return Snippet.objects.get(pk=pk)
  8. except Snippet.DoesNotExist:
  9. raise Http404
  10. def get(self, request, pk, format=None):
  11. snippet = self.get_object(pk)
  12. serializer = SnippetSerializer(snippet)
  13. return Response(serializer.data)
  14. def put(self, request, pk, format=None):
  15. snippet = self.get_object(pk)
  16. serializer = SnippetSerializer(snippet, data=request.DATA)
  17. if serializer.is_valid():
  18. serializer.save()
  19. return Response(serializer.data)
  20. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  21. def delete(self, request, pk, format=None):
  22. snippet = self.get_object(pk)
  23. snippet.delete()
  24. return Response(status=status.HTTP_204_NO_CONTENT)

做的不错。它和我们之前写的基于函数的view还是有些相像。

我们还需要对URLconf做一些小小的改动:

  1. from django.conf.urls import patterns, url
  2. from rest_framework.urlpatterns import format_suffix_patterns
  3. from snippets import views
  4. urlpatterns = patterns('',
  5. url(r'^snippets/$', views.SnippetList.as_view()),
  6. url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
  7. )
  8. urlpatterns = format_suffix_patterns(urlpatterns)

到目前为止,已经全部完成。你可以运行开发服务器,一切应该表现如初。

2. 使用mixins

使用基于类的view的最大好处就是可以让我们方便的组合与重用。

刚才我们的create/retrieve/update/delete等函数实现在模型支撑API view下会很类似。其中的公共行为在REST framework’s mixin类中实现了。

我们来看看,我们可以用mixin类来吧我们的view组合起来:

  1. from snippets.models import Snippet
  2. from snippets.serializers import SnippetSerializer
  3. from rest_framework import mixins
  4. from rest_framework import generics
  5. class SnippetList(mixins.ListModelMixin,
  6. mixins.CreateModelMixin,
  7. generics.GenericAPIView):
  8. model = Snippet
  9. serializer_class = SnippetSerializer
  10. def get(self, request, *args, **kwargs):
  11. return self.list(request, *args, **kwargs)
  12. def post(self, request, *args, **kwargs):
  13. return self.create(request, *args, **kwargs)

我们将花点时间来解释下这里到底发生了什么。我们用GenericAPIView构建了我们的view, 然后加上了ListModelMixinCreateModelMixin.

基类提供了核心功能,mixin类提供了 .list().create() 动作。我们然后显式的把 getpost 方法与合适的动作绑定在一起,非常简单。

  1. class SnippetDetail(mixins.RetrieveModelMixin,
  2. mixins.UpdateModelMixin,
  3. mixins.DestroyModelMixin,
  4. generics.GenericAPIView):
  5. model = Snippet
  6. serializer_class = SnippetSerializer
  7. def get(self, request, *args, **kwargs):
  8. return self.retrieve(request, *args, **kwargs)
  9. def put(self, request, *args, **kwargs):
  10. return self.update(request, *args, **kwargs)
  11. def delete(self, request, *args, **kwargs):
  12. return self.destroy(request, *args, **kwargs)

示例部分的实现也非常类似。这次我们用GenericAPIView来提供核心功能,然后用mixins来提供.retrieve(), .update().destroy() actions.

3. 使用基于泛型类的view

使用mixin类可以让我们重写view时写更少的代码,但我们还可以更进一步,REST framework提供了一系列已经mixed-in的泛型view供我们使用。

  1. from snippets.models import Snippet
  2. from snippets.serializers import SnippetSerializer
  3. from rest_framework import generics
  4. class SnippetList(generics.ListCreateAPIView):
  5. model = Snippet
  6. serializer_class = SnippetSerializer
  7. class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
  8. model = Snippet
  9. serializer_class = SnippetSerializer

Wow, 非常简洁. 我们轻松了不少,而且代码看起来优美,干净和符合Django的习惯。

在第四部分 part 4 of the tutorial, 我们将看看我们的API如何处理认证和权限。

Tutorial 4: Authentication & Permissions

src

目前为止,我们的API对谁能编辑或删除snippet(代码片段)还没有任何限制。我们将增加一些扩展功能来确保以下:

  • snippets总关联一个创建者;
  • 只有认证后的用户才能创建一个snippets.
  • 只有创建者才能更新或删除snippet;
  • 非认证请求只拥有只读权限。

1. 为model增加信息

我们先需要对Snippet的model做些修改。首先,增加一些fields. 其中一个用来表示创建者,另一个用来存储代码中的HTML高亮。

在模型中增加这两个字段。

  1. owner = models.ForeignKey('auth.User', related_name='snippets')
  2. highlighted = models.TextField()

我们还需要确保model存储时,我们能生成高亮字段内容,这里使用 pygments 代码高亮库。

首先需要一些额外的imports:

  1. from pygments.lexers import get_lexer_by_name
  2. from pygments.formatters.html import HtmlFormatter
  3. from pygments import highlight

我们现在为模型增加一个 .save() 方法:

  1. def save(self, *args, **kwargs):
  2. """
  3. Use the `pygments` library to create a highlighted HTML
  4. representation of the code snippet.
  5. """
  6. lexer = get_lexer_by_name(self.language)
  7. linenos = self.linenos and 'table' or False
  8. options = self.title and {'title': self.title} or {}
  9. formatter = HtmlFormatter(style=self.style, linenos=linenos,
  10. full=True, **options)
  11. self.highlighted = highlight(self.code, lexer, formatter)
  12. super(Snippet, self).save(*args, **kwargs)

这些完成后还需要更新一下数据库里面的表。通常我们要创建一个数据库迁移(database migration)来完成,但教程中,我们就只是删除数据库然后重新创建:

  1. rm tmp.db
  2. python ./manage.py syncdb

你可能想创建一些其他用户来测试这些API,最快捷的方式是利用 createsuperuser 命令。

  1. python ./manage.py createsuperuser

2. 为用户模型增加endpoints

现在我们需要一些用户,我们最好把用户呈现也增加到API上,创建一个新的serializer很容易:

  1. from django.contrib.auth.models import User
  2. class UserSerializer(serializers.ModelSerializer):
  3. snippets = serializers.PrimaryKeyRelatedField(many=True)
  4. class Meta:
  5. model = User
  6. fields = ('id', 'username', 'snippets')

因为 'snippets' 和用户model是反向关联(reverse relationship,即多对一),所以在使用 ModelSerializer时并不会缺省加入,所以我们需要显式的来实现。

我们还需要创建一些views,对用户呈现而言,我们最好使用只读的view,所以使用 ListAPIViewRetrieveAPIView 泛型类Views。

  1. class UserList(generics.ListAPIView):
  2. model = User
  3. serializer_class = UserSerializer
  4. class UserDetail(generics.RetrieveAPIView):
  5. model = User
  6. serializer_class = UserSerializer

最后,我们需要修改URL conf:

  1. url(r'^users/$', views.UserList.as_view()),
  2. url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),

3. 把Snippets与Users关联

现在,如果我们创建一个code snippet,还没有方法指定其创建者。User并没有作为序列化内容的一部分发送,而是作为request的一个属性。

这里的处理方法是重载snippet view中的 .pre_save() 方法,它可以让我们处理request中隐式的信息。

SnippetListSnippetDetail 的view类中,都需要添加如下的方法:

  1. def pre_save(self, obj):
  2. obj.owner = self.request.user

4. 更新 serializer

现在snippets已经和创建者关联起来了,我们接下来还需要更新SnippetSerializer,在其定义中增加一个新的字段:

  1. owner = serializers.Field(source='owner.username')

Note: 确定你在嵌入类Meta的字段列表中也加入了 'owner'

这个字段所做的十分有趣。source 参数表明增加一个新字段,可以指定序列化实例任何属性。它可以采用如上的点式表示(dotted notation),这时他可以直接遍历到指定的属性。在Django’s template中使用时,也可以采用类似的方式。

我们增加字段是一个无类型 Field 类,而我们之前的字段都是有类型的,例如 CharField, BooleanField etc… 无类型字段总是只读的,它们只用在序列化表示中,而在反序列化时(修改model)不被使用。

5. 给view增加权限控制

现在代码片段 snippets 已经关联了用户,我们需要确保只有认证用户才能增、删、改snippets.

REST framework 包括许多权限类可用于view的控制。这里我们使用 IsAuthenticatedOrReadOnly, 它可确保认证的request获取read-write权限,而非认证的request只有read-only 权限.

现需要在views模块中增加 import。

  1. from rest_framework import permissions

然后需要在 SnippetListSnippetDetail view类中都增加如下属性:

  1. permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

6. 为可浏览API(Browseable API)增加login

如果你打开浏览器,访问可浏览API,你会发现只有登录后才能创建新的snippet了。

我们可以编辑URLconf来增加一个登录view。首先增加新的import:

  1. from django.conf.urls import include

然后,在文件末尾增加一个pattern来为browsable API增加 login 和 logout views.

  1. urlpatterns += patterns('',
  2. url(r'^api-auth/', include('rest_framework.urls',
  3. namespace='rest_framework')),
  4. )

具体的, r'^api-auth/' 部分可以用任何你想用的URL来替代。这里唯一的限制就是 urls 必须使用'rest_framework' 命名空间。

现在如果你打开浏览器,刷新页面会看到页面右上方的 ‘Login’ 链接。如果你用之前的用户登录后,你就又可以创建 snippets了。

一旦你创建了一些snippets,当导航至’/users/‘时,你会看到在每个user的snippets字段都包含了一系列snippet的pk。

7. 对象级别的权限

我们希望任何人都可以浏览snippets,但只有创建snippet的用户才能编辑或删除它。

为了实现这个需求,我们需要创建定制的权限(custom permission)。

在 snippets 应用中,创建一个新文件: permissions.py

  1. from rest_framework import permissions
  2. class IsOwnerOrReadOnly(permissions.BasePermission):
  3. """
  4. Custom permission to only allow owners of an object to edit it.
  5. """
  6. def has_object_permission(self, request, view, obj):
  7. # Read permissions are allowed to any request,
  8. # so we'll always allow GET, HEAD or OPTIONS requests.
  9. if request.method in permissions.SAFE_METHODS:
  10. return True
  11. # Write permissions are only allowed to the owner of the snippet
  12. return obj.owner == request.user

现在我们可以为snippet实例增加定制权限了,需要编辑 SnippetDetail 类的 permission_classes 属性:

  1. permission_classes = (permissions.IsAuthenticatedOrReadOnly,
  2. IsOwnerOrReadOnly,)

别忘了import 这个IsOwnerOrReadOnly 类。

  1. from snippets.permissions importIsOwnerOrReadOnly

现在打开浏览器,你可以看见 ‘DELETE’ 和 ‘PUT’ 动作只会出现在那些你的登录用户创建的snippet页面上了.

8. 通过API认证

我们已经有了一系列的权限,如果我们需要编辑任何snippet,我们需要认证我们的request。因为我们还没有建立任何 authentication classes, 所以目前是默认的SessionAuthenticationBasicAuthentication在起作用。

当我们通过Web浏览器与API互动时,我们登录后、然后浏览器session可以为所有的request提供所需的验证。

如果我们使用程序访问这些API,我们则需要显式的为每个request提供认证凭证(authentication credentials)。

如果我们试图未认证就创建一个snippet,将得到错误如下:

  1. curl -i -X POST http://127.0.0.1:8000/snippets/ -d "code=print 123"
  2. {"detail": "Authentication credentials were not provided."}

如果我们带着用户名和密码来请求时则可以成功创建:

  1. curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 789" -u tom:password
  2. {"id": 5, "owner": "tom", "title": "foo", "code": "print 789", "linenos": false, "language": "python", "style": "friendly"}

9. 小结

我们已经为我们的Web API创建了相当细粒度的权限控制和相应的系统用户。

在教程第5部分 part 5 ,我们将把所有的内容串联起来,为我们的高亮代码片段创建HTML节点,并利用系统内的超链接关联来提升API的一致性表现。

Tutorial 5: Relationships & Hyperlinked APIs

src

到目前为止,在我们的API中关系(relationship)还是通过主键来表示的。在这部分的教程中,我们将用超链接方式来表示关系,从而提升API的统一性和可发现性。

1. 为API根创建一个endpoint

到目前为止,我们已经有了’snippets’和’users’的endpoint, 但是我们还没有为我们的API单独创立一个端点入口。我们可以用常规的基于函数的view和之前介绍的 @api_view 修饰符来创建。

  1. from rest_framework import renderers
  2. from rest_framework.decorators import api_view
  3. from rest_framework.response import Response
  4. from rest_framework.reverse import reverse
  5. @api_view(('GET',))
  6. def api_root(request, format=None):
  7. return Response({
  8. 'users': reverse('user-list', request=request, format=format),
  9. 'snippets': reverse('snippet-list', request=request, format=format)
  10. })

请注意,我们用 REST framework 的 reverse 函数来返回完全合规的URLs.

2. 为高亮的Snippet创建一个endpoint

我们目前还没有为支持代码高亮的Snippet创建一个endpoints.

与之前的API endpoints不同, 我们将直接使用HTML呈现,而非JSON。在 REST framework中有两种风格的HTML render, 一种使用模板来处理HTML,一种则使用预先处理的方式。在这里我们使用后者。

另一个需要我们考虑的是,对于高亮代码的view并没有具体的泛型view可以直接利用。我们将只返回实例的一个属性而不是对象实例本身。

没有具体泛型view的支持,我们使用基类来表示实例,并创建我们自己的 .get() 方法。在你的 snippets.views 中增加:

  1. from rest_framework import renderers
  2. from rest_framework.response import Response
  3. class SnippetHighlight(generics.SingleObjectAPIView):
  4. model = Snippet
  5. renderer_classes = (renderers.StaticHTMLRenderer,)
  6. def get(self, request, *args, **kwargs):
  7. snippet = self.get_object()
  8. return Response(snippet.highlighted)

和以往一样,我们需要为新的view增加新的URLconf,如下增加urlpatterns:

  1. url(r'^$','api_root'),

还需要为代码高亮增加一个urlpatterns:

  1. url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),

3. API超链接化

在Web API设计中,处理实体间关系是一个有挑战性的工作。我们有许多方式来表示关系:

  • 使用主键;
  • 使用超链接;
  • 使用相关实体唯一标识的字段;
  • 使用相关实体的默认字符串表示;
  • 在父级表示中嵌入子级实体;
  • 其他自定义的表示。

REST framework支持所有这些方式,包括正向或者反向的关系,或者将其应用到自定义的管理类中,例如泛型外键。

在这部分,我们使用超链接方式。为了做到这一点,我们需要在序列化器中用 HyperlinkedModelSerializer 来替代之前的 ModelSerializer.

HyperlinkedModelSerializerModelSerializer 有如下的区别:

  • 缺省状态下不包含 pk 字段;

  • 具有一个 url 字段,即HyperlinkedIdentityField类型.

  • HyperlinkedRelatedField表示关系,而非PrimaryKeyRelatedField.

  • 我们可以很方便的改写现有代码来使用超连接方式: class SnippetSerializer(serializers.HyperlinkedModelSerializer): owner = serializers.Field(source=’owner.username’) highlight = serializers.HyperlinkedIdentityField(view_name=’snippet-highlight’, format=’html’)

    1. class Meta:
    2. model = models.Snippet
    3. fields = ('url', 'highlight', 'owner',
    4. 'title', 'code', 'linenos', 'language', 'style')


class UserSerializer(serializers.HyperlinkedModelSerializer): snippets = serializers.HyperlinkedRelatedField(many=True, view_name=’snippet-detail’)

  1. class Meta:
  2. model = User
  3. fields = ('url', 'username', 'snippets')

注意:我们也增加了一个新的 'highlight' 字段。该字段与 url 字段相同类型。不过它指向了 'snippet-highlight'的 url pattern, 而非'snippet-detail'的url pattern.

因为我们已经有一个 '.json'的后缀,为了更好的表明highlight字段链接的区别,使用一个 '.html' 的后缀。

4. 正确使用URL patterns

如果要使用超链接API,就必须确保正确的命名和使用 URL patterns. 我们来看看我们需要命名的 URL patterns:

  • 指向 'user-list''snippet-list' 的API根.
  • snippet的序列化器,包括一个 'snippet-highlight'字段.
  • user序列化器,包含一个 'snippet-detail'字段.
  • snippet 和user的序列化器,包含 'url' 字段(会缺省指向'snippet-detail''user-detail'.

一番工作之后,最终的 'urls.py' 文件应该如下所示:

  1. # API endpoints
  2. urlpatterns = format_suffix_patterns(patterns('snippets.views',
  3. url(r'^$', 'api_root'),
  4. url(r'^snippets/$',
  5. views.SnippetList.as_view(),
  6. name='snippet-list'),
  7. url(r'^snippets/(?P<pk>[0-9]+)/$',
  8. views.SnippetDetail.as_view(),
  9. name='snippet-detail'),
  10. url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
  11. views.SnippetHighlight.as_view(),
  12. name='snippet-highlight'),
  13. url(r'^users/$',
  14. views.UserList.as_view(),
  15. name='user-list'),
  16. url(r'^users/(?P<pk>[0-9]+)/$',
  17. views.UserDetail.as_view(),
  18. name='user-detail')
  19. ))
  20. # Login and logout views for the browsable API
  21. urlpatterns += patterns('',
  22. url(r'^api-auth/', include('rest_framework.urls',
  23. namespace='rest_framework')),
  24. )

5. 添加分页

列表view有时会返回大量的实例结果,所以我们应该把结果分页显示,以便用户使用。

通过在 settings.py 中添加如下配置,我们就能在结果列表中增加分页的功能:

  1. REST_FRAMEWORK ={'PAGINATE_BY':10}

请注意REST framework的所有配置信息都是存放在一个叫做 ‘REST_FRAMEWORK’的dictionary中,以便于其他配置区分。

如有必要,你也可以自定义分页的方式,这里不再赘述。

6. Browsing the API

If we open a browser and navigate to the browsable API, you’ll find that you can now work your way around the API simply by following links.

You’ll also be able to see the ‘highlight’ links on the snippet instances, that will take you to the highlighted code HTML representations.

In part 6 of the tutorial we’ll look at how we can use ViewSets and Routers to reduce the amount of code we need to build our API.

Tutorial 6:ViewSets & Routers

REST framework包含一个抽象概念来处理ViewSets,它使得开发者可以集中精力对 API的state和interactions建模,留下URL构造被自动处理,基于共同约定。

ViewSet类几乎和View类一样,除了它们提供像readupdate操作,但没有处理getput的方法。

一个ViewSet类只是绑定到一组方法处理程序在最后一刻,在它被实例化到一组视图的时候,通常是使用一个Router类——为你处理定义URL conf的复杂性。

使用ViewSets来重构

让我们取出当前集合的views,使用view sets将它们重构。

首先让我们重构我们的UserListViewUserDetailViewviews成一个UserViewSet。我们可以移除两个views,用一个类来替换它们。

  1. class UserViewSet(viewsets.ReadOnlyModelViewSet):
  2. """
  3. This viewset automatically provides `list` and `detail` actions.
  4. """
  5. queryset = User.objects.all()
  6. serializer_class = UserSerializer

在这里我们将使用ReadOnlyModelViewSet类来自动地提供默认的’read-only’操作。正如我们在使用常规的views做的,我们还是会设置querysetserializer_class属性,但我们不再需要提供相同的信息给两个独立的类。

下一步我们将替换SnippetList,SnippetDetialSnippetHighlightview类。我们可以移除这三个views,再次用一个类来替换它们。

  1. from rest_framework import viewsets
  2. from rest_framework.decorators import link
  3. class SnippetViewSet(viewsets.ModelViewSet):
  4. """
  5. This viewset automatically provides `list`, `create`, `retrieve`,
  6. `update` and `destroy` actions.
  7. Additionally we also provide an extra `highlight` action.
  8. """
  9. queryset = Snippet.objects.all()
  10. serializer_class = SnippetSerializer
  11. permission_classes = (permissions.IsAuthenticatedOrReadOnly,
  12. IsOwnerOrReadOnly,)
  13. @link(renderer_classes=[renderers.StaticHTMLRenderer])
  14. def highlight(self, request, *args, **kwargs):
  15. snippet = self.get_object()
  16. return Response(snippet.highlighted)
  17. def pre_save(self, obj):
  18. obj.owner = self.request.user

这次我们将使用ModelViewSet类为了得到默认read和write操作的完整集合。

注意我们还使用@link修饰符来创建一个自定义动作名为highlight。这个修饰符可以用来添加任何自定义endpoints,不用符合标准的create/update/delete样式。

@link修饰符创建的自定义动作将会对GET请亲做出响应。我们也可以使用@action修饰符代替如果我们想要一个对POST请求做出响应的动作。

明确地绑定ViewSets到URLs

handler method仅仅在我们定义URLConf的时候绑定到动作(actions)上。去看看盖子下发生了什么首先从我们的ViewSets明确地创建一个views集合。

在urls.py文件中我们绑定了我们的ViewSet类到一个具体views的集合。

  1. from snippets.views import SnippetViewSet, UserViewSet
  2. snippet_list = SnippetViewSet.as_view({
  3. 'get': 'list',
  4. 'post': 'create'
  5. })
  6. snippet_detail = SnippetViewSet.as_view({
  7. 'get': 'retrieve',
  8. 'put': 'update',
  9. 'patch': 'partial_update',
  10. 'delete': 'destroy'
  11. })
  12. snippet_highlight = SnippetViewSet.as_view({
  13. 'get': 'highlight'
  14. })
  15. user_list = UserViewSet.as_view({
  16. 'get': 'list'
  17. })
  18. user_detail = UserViewSet.as_view({
  19. 'get': 'retrieve'
  20. })

注意我们怎么样从每个ViewSet类创建多样的views,通过绑定http methods到每个view所需的动作(by binding the http methods to the required aciotn for each view)

现在我们已经绑定我们的资源到具体的views,我们可以像往常一样注册views和URL conf。

  1. urlpatterns = format_suffix_patterns(patterns('snippets.views',
  2. url(r'^$', 'api_root'),
  3. url(r'^snippets/$', snippet_list, name='snippet-list'),
  4. url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
  5. url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
  6. url(r'^users/$', user_list, name='user-list'),
  7. url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
  8. ))

使用Routers

因为我们使用ViewSet类而不是View类,我们实际上不用自己设计URL conf。连接resources到views和urls的约定可以使用Router类自动处理。我们要做的仅仅是用一个router注册适当的view集合,and let it do the rest

这里我们重连接urls.py文件

  1. from snippets import views
  2. from rest_framework.routers import DefaultRouter
  3. # Create a router and register our viewsets with it.
  4. router = DefaultRouter()
  5. router.register(r'snippets', views.SnippetViewSet)
  6. router.register(r'users', views.UserViewSet)
  7. # The API URLs are now determined automatically by the router.
  8. # Additionally, we include the login URLs for the browseable API.
  9. urlpatterns = patterns('',
  10. url(r'^', include(router.urls)),
  11. url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
  12. )

用router注册viewsets和提供一个urlpattern很像。我们有两个参数——给views的URL前缀和viewset自身。

DefaultRouter类自动为我们创建API root vies,所以我们现在可以从我们的views方法中删除api_root方法。

权衡views VS viewsets

使用viewset可以是一个真正有用的抽象。它帮助确保URL约定可以对你的API始终如一,使你需要编写的代码数量最小化,使得你可以集中精力在你API的交互和表现上,而不是URL conf的细节上。

这并不意味着它总是正确的方法。有一个类似的权衡在使用class-based views代替function-based views上。单独构建views比使用viewsets更加清楚。

回顾我们的工作 如果我们打开浏览器来看看之前的API,会发现它们可以用链接的方式工作了。

当你查看snippet实例的 ‘highlight’ 链接时,你会直接看到代码高亮的HTML呈现。

我们目前已经完成了全套的Web API。可以浏览,支持认证,对象粒度的权限,以及多种返回格式。

我们已经完成了流程中的所有步骤,了解了如何从基本的Django View出发,根据需求逐步定义我们的工作方式。

你能从GitHub上得到教程的最终代码: tutorial code ,或者直接访问在线示例: the sandbox.

总结与后续工作

我们的教程已经到此结束,如果你还想更多的了解 REST framework,可以从下面这些地方开始:

为 GitHub 做贡献,审查、提交内容或提出新要求。 参加讨论组 REST framework discussion group, 帮助创建更好的线上社区. Follow the author 作者的 Twitter,打打招呼。 让我们去大展身手吧.