django rest framework

翻译http://www.django-rest-framework.org/

Django REST framework

本篇文档适用于version 3的rest framework,version 2也同样使用。

Django REST framework用来建立Web APIs是很强大和灵活的。

使用Django REST framework的原因如下:

Requirements

REST framework 以下是必须安装的:

  • Python (2.7, 3.2, 3.3, 3.4, 3.5)
  • Django (1.7+, 1.8, 1.9)

下面的包是可选的:

Installation

使用pip安装,包括你需要的任何其他的包:

pip install djangorestframework
pip install markdown       # Markdown support for the browsable API.
pip install django-filter  # Filtering support

或者从github中克隆:

git clone git@github.com:tomchristie/django-rest-framework.git

添加'rest_framework'INSTALLED_APPS 配置中。

INSTALLED_APPS = (
    ...
    'rest_framework',
)

若你想使用browsable API,你需要添加REST framework的登录和登出views。添加如下代码到你的urls.py文件中

urlpatterns = [
    ...
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

注意:URL路径可以是任何字符串,但是必须从rest_framework中导入rest_framework.urls,django 1.9+版本可以省略导入,REST framework会自动设置。

Example

看一个使用REST framework创建简单API的例子,我们将会创建一个访问这个项目用户信息的读写API。

REST framework API的任何全局设置都被保存在一个称为REST_FRAMEWORK的单独配置字段中,添加如下代码到你的setting.py中:

REST_FRAMEWORK = {
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    ]
}

确认rest_framework已经添加到setting.py的INSTALLED_APPS中了。

现在我们开始创建API,这是该项目的urls.py:

from django.conf.urls import url, include
from django.contrib.auth.models import User
from rest_framework import routers, serializers, viewsets

# Serializers define the API representation.
class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ('url', 'username', 'email', 'is_staff')

# ViewSets define the view behavior.
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)

# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
    url(r'^', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

浏览器访问http://127.0.0.1:8000/打开API,查看你刚刚创建的‘users’API,如果你在右上角有登陆管理也可以添加进来,创建或者删除这个系统的用户。

Quickstart

quickstart guide是最快的开始并运行的方法。

Tutorial

这个会花费不小的时间来立即,但是通过这次学习会对rest framework各个部件如何组合在一起有深刻的理解,是强烈推荐阅读的

available here.

API Guide

API向导是REST framework提供的功能的手册参考。

Topics

使用REST framework的通用指南:

 



Quickstart

我们会创建一个简单的API来运行admin 用户查看和编辑这个系统的用户和组。

Project setup

创建一个命名为tutorial的项目,启动一个名为quickstart的app。

# Create the project directory
mkdir tutorial
cd tutorial

# Create a virtualenv to isolate our package dependencies locally
virtualenv env
source env/bin/activate  # On Windows use `env\Scripts\activate`

# Install Django and Django REST framework into the virtualenv
pip install django
pip install djangorestframework

# Set up a new project with a single application
django-admin.py startproject tutorial .  # Note the trailing '.' character
cd tutorial
django-admin.py startapp quickstart
cd ..

先同步数据库:

python manage.py migrate

创建一个初始用户名admin使用密码password123,稍后在我们的例子中将会以该用户进行认证。

python manage.py createsuperuser

一旦你安装了数据库以及初始用户,打开app目录,我们要开始coding...

Serializers

首先定义一些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')

注意这里使用hyperlinked relations ,withHyperlinkedModelSerializer。也可以使用主键以及其他的关系,但是hyperlinked relations 是一个good RESTful设计。

Views

编写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 endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all().order_by('date_joined')
    serializer_class = UserSerializer


class GroupViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows groups to be viewed or edited.
    """
    queryset = Group.objects.all()
    serializer_class = GroupSerializer

在django_rest_framework中, 所有常见的行为都被归到了ViewSets中

如果需要,可以很容易的将写着分成各个views,但是一定要使用viewsets来保持view具有逻辑性以及简洁的组织在一起。

URLs

ok,现在来书写API URLS在tutorial/urls.py

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)

# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
    url(r'^', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

因为使用viewsets 代替views,能自动为我们的API生成URL配置,通过router 类注册viewsets。

另外,如果想对API URLS更加多的控制改造,可以使用regular class based views以及编写URL conf。

最后,使用browsable API以及包括默认的login和logout views。这不是必须的,但是如果API需要权限验证或者需要使用browsable API这是很有用的。

Settings

需要设置一些全局设置,想要设置API只能admin用户访问,配置模块看tutorial/settings.py

INSTALLED_APPS = (
    ...
    'rest_framework',
)

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
    'PAGE_SIZE': 10
}

ok,完成了。

Testing our 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"
        }
    ]
}

或者直接通过浏览器...

如果通过浏览器,确保login using the control in the top right corner.

如果想要更深入的理解REST framework各个组件如何组合在一起的,看the tutorial, 或者开始浏览API guide

 



Tutorial 1: Serialization

Introduction

会介绍组成REST framework的各个组件,你会对他们如何组合在一起有更加综合的理解。代码可以在GitHub的 tomchristie/rest-framework-tutorial找到,且已经在线上成功运行,点击 available here

Setting up a new environment

创建一个新的虚拟环境,使用virtualenv. 它能确保我们的包配置和其它项目保持隔离。

virtualenv env
source env/bin/activate

在虚拟环境里,安装我们需要的包

pip install django
pip install djangorestframework
pip install pygments  # We'll be using this for the code highlighting

注意:为了能任何时间退出虚拟环境,just type deactivate。更多信息查看 virtualenv documentation

Getting started

创建工作项目

cd ~
django-admin.py startproject tutorial
cd tutorial

创建app,将在这个app里面创建我们的Web API.

python manage.py startapp snippets

添加snippets app以及rest_framework app 到 INSTALLED_APPS,编辑tutorial/settings.py文件:

INSTALLED_APPS = (
    ...
    'rest_framework',
    'snippets.apps.SnippetsConfig',
)

ok

Creating a model to work with

编辑 snippets/models.py文件以创建一个简单的sinppet model开始,这个model被用来存储code snippets。

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',)

需要创建初始迁移为snippet model,首先同步数据库:

python manage.py makemigrations snippets
python manage.py migrate

Creating a Serializer class

第一:通过定义serializers提供一种serializing 和deserializing  snippet实例到另一种表示形式比如json的方式。它和Django's forms工作方式类似。
在snippets目录下创建名为serializers.py 的文件,然后添加如下代码:

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


class SnippetSerializer(serializers.Serializer):
    pk = 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):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Snippet` instance, given the validated data.
        """
        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

serializer 类的第一部分定义了被序列化和反序列化的字段。create()和update()方法定义了实例如何被创建以及更改当调用serializer.save()的时候

serializer 类和Django Form class是很类似的,在各个字段上包含相同的校验标志,比如required, max_length 以及default。

字段标记可以控制在特定的环境下序列化如何被展示,比如渲染到HTML,{'base_template': 'textarea.html'}标记和Django Form class的是widget=widgets.Textarea等价的,对于browsable API 如何被展示是相当有用的。

使用ModelSerializer class也节省了我们的时间,但是我们要保持序列化定义是清楚明确的。

Working with Serializers

通过使用Serializer 类我们会更加熟悉,进入Django shell。

python manage.py shell

创建一些代码工作于snippets。

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
# {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}

此刻,已经讲model实例转换成python原有类型,接下来将data转变成json格式。

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

反序列化是类似的,首先将数据流解析成python原有类型...

from django.utils.six import BytesIO

stream = BytesIO(content)
data = JSONParser().parse(stream)

然后将这些类型的数据恢复成完整的object 实例。

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()
# <Snippet: Snippet object>

注意到API和working with forms是怎么相似的,相似点会体现的更加明显当用serializer编写views的时候。

我们也可以序列化querysets 而不是model instances。为了实现这点需要添加many=True标记到serializer 参数中。

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

Using ModelSerializers

SnippetSerializer 类复制了很多Snippet model里面的内容,若保持代码简洁岂不更好。

如同Django 提供Form classes 和ModelForm classes,REST framework也包括Serializer classes, 和ModelSerializer classes。

看下如何使用Serializer classes, and ModelSerializer classes来重构我们的serializer,打开snippets/serializers.py,通过如下代码代替上面的代码:

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

serializers 拥有的一个比较好的属性就是能通过打印检查serializer instance的所有字段。打开Django shell,试试如下代码:

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 classes并不能做什么神奇的东西,它仅仅是一个创建serializer classes的快捷方式。

  • 自动设置字段【An automatically determined set of fields.】
  • 默认实现了create()和update()方法

Writing regular Django views using our Serializer

看下如何使用我们新创建的Serializer class来编写API,先暂时不使用REST framework的其他属性,仅仅编写views作为常规的Django views。

创建HttpResponse 的子类,用来渲染任何数据成json格式。编辑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 =     ().render(data)
        kwargs['content_type'] = 'application/json'
        super(JSONResponse, self).__init__(content, **kwargs)

API的root目录下会有一个view支持列出所有存在的snippets或者创建一个新的snippets

@csrf_exempt
def snippet_list(request):
    """
    List all code snippets, or create a new 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到view,因此需要标记view as csrf_exempt. 这并不是必须要做的事情,REST framework views 会用更加合理的方式。但是现在为了达到我们的目的这是必须的。

也需要一个view来对应一个特定的snippet,能被用来检索、更新、删除snippet。

@csrf_exempt
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a 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)

最后我们需要vew上面的views,创建snippets/urls.py文件:

from django.conf.urls import url
from snippets import views

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

We also need to wire up the root urlconf, in the tutorial/urls.py file, to include our snippet app's URLs.

from django.conf.urls import url, include

urlpatterns = [
    url(r'^', include('snippets.urls')),
]

有几个边缘case现在不能很好的解决,如果我们发送不合法的json,或者有view不能处理的方法,将会显示500 “server error”response。

Testing our first attempt at a Web API

现在开启服务来服务snippets

从shell中退出

quit()

启动Django的development服务

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 or httpie  测试API,Httpie 是一个python开发的比较优化的http client。安装:

pip install httpie

最后,我们可以获取一系列的snippets:

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"
}

同样,通过在web浏览器访问URLS来展示与上面相同的json数据。

Where are we now

目前为止,已经创建好了一个和Django's Forms API相当类似的serialization API,以及一些Django views。

但是仍然有一些异常处理边缘case需要我们清理。

做这些改进可以看part 2 of the tutorial



Tutorial 2: Requests and Responses

接下来我们将开始接触REST framework的核心,先介绍必要的几点

Request objects

REST framework引进了扩展普通HttpRequestRequest  object,提供了更多灵活的request解析。Request  object的核心功能点是reqeust.data属性,和request.POST类似,但是对Web API更有用。

request.POST  # Only handles form data.  Only works for 'POST' method.
request.data  # Handles arbitrary data.  Works for 'POST', 'PUT' and 'PATCH' methods.

Response objects

REST framework同样引进了Response object是TemplateResponse 类型,它将数据转换成合适的格式返回给client。

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

Status codes

使用HTTP status码并不是特别好理解,REST framework对每个status code提供了更加明确的说明,比如在status 模块的HTTP_400_BAD_REQUEST

Wrapping API views

REST framework提供了两个可以编写API views的封装:

  1.   @api_view装饰器为function based views
  2. APIView class为class based views

上述封装可以确保你接收到了Request instances在你的view,以及添加上下文到Response object,以便内容实现转变。

这些封装也提供了针对“错误输入的导致访问request.data出现的解析异常”以及“适当的时候返回405 Method Not Allowed”的行为。

Pulling it all together

现在,让我们开始使用这些新的组件来编写views。

在views.py我们不再需要JSONResponse class,可以删除了。开始重构我们的views把。

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):
    """
    List all snippets, or create a new 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)

 

view相比之前的例子是个很大的改善,代码更加简洁,如果我们使用过 Forms API感觉更加相似。我们也会使用status codes,这会使response的含义更加清晰可理解。

这是一个特定的snippet的view。

@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a snippet instance.
    """
    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)

这段代码应该比较熟悉,它和regular Django views没有什么不同。

我们不再明确的让requests或者responses转换成一个给定的content type,reqeust.data能处理json reqeusts或者其他格式的请求。同样我们返回response objects,但允许REST framework渲染response到合适content type。

Adding optional format suffixes to our URLs

response不再仅仅是一个单一content type,可以为API endpoints后添加支持的格式后缀,使用格式后缀相当于明确得告诉URLS将数据渲染成给出的固定格式,这意味着API能够出项类似这样的URLshttp://example.com/api/items/4/.json.

添加一个格式参数到两个views,像下面这样:

def snippet_list(request, format=None):
def snippet_detail(request, pk, format=None):

稍微更新urls.py,在URLs后面添加一些 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<pk>[0-9]+)$', views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

We don't necessarily need to add these extra url patterns in, but it gives us a simple, clean way of referring to a specific format.

How's it looking?

tutorial part 1一样从命令进行测试,我们还提供了一些错误处理如果不合法的请求。

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"
  }
]

能控制返回的response格式,通过使用Accept header:

http http://127.0.0.1:8000/snippets/ Accept:application/json  # Request JSON
http http://127.0.0.1:8000/snippets/ Accept:text/html         # Request HTML

或者附加格式后缀:

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

同样,我们能控制我们发送的reqeust格式,通过使用Content-Type header。

# POST using form data
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 using 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"
}

通过web浏览器访问 http://127.0.0.1:8000/snippets/.

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.

What's next?

tutorial part 3,我们会使用 class based views,以及学习通用的views是如何减少代码量的。



Tutorial 3: Class Based Views

也可以使用class based views来编写我们的API veiws,而不是function based views。这是一个强大的模式允许我们重用公共函数以及帮我们保持代码的DRY。

Rewriting our API using class based views

首先以class based view重写root views,涉及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):
    """
    List all snippets, or create a new 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请求methods中进行很好的分离。

class SnippetDetail(APIView):
    """
    Retrieve, update or delete a snippet instance.
    """
    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<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)

ok,完成了,运行server一切和之前都是一模一样的。

Using mixins

使用class based views最大的好处是:允许我们组合可复用的behaviour

目前为止,我们使用的create/retrieve/update/delete 操作和任何创建的model-backed API views是相当类似的。相同点是implemented in REST framework's mixin classes

看下如何使用mixin classes来编写views,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创建我们的views,以及添加 ListModelMixin and CreateModelMixin.

The base class提供了核心功能,The base class提供了list() and .create() actions,明确将get post方法绑定到合适的actions。

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)

Using generic class based views

使用mixin classes我们已经重写了view相比以前使用了更少的代码,但是我们还可以进一步深入。REST framework提供了一组already mixed-in的通用views我们可以用来继续减少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

看,是不是非常简洁,可以继续看part 4 of the tutorial,怎么针对API解决authentication and permissions的问题



Tutorial 4: Authentication & Permissions

当前我们的API没有任何限制,所有人可以编辑和删除code snippets,我们需要采取一些方法来保证:

  • Code snippets和creator联系在一起的
  • 仅仅已经验证的用户才可以创建snippets
  • 仅仅某个snippets的创建者才可以更新或者删除它
  • 未通过验证的请求只有只读权限

Adding information to our model

Snippet model class我们会做一些改变,首先,添加一些字段。这些字段的其中一个被用来展示用户是谁创建了这个code snippets。

其他的字段被用来存储code的高亮HTML展示。

添加这两个字段到moles.py的Snippet model:

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

我们需要确认当model保存后,我们能使用核心模块code highlighting library填充highlighted 字段,先进行一些导入:

from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight

添加一个save方法到model class:

def save(self, *args, **kwargs):
    """
    Use the `pygments` library to create a highlighted HTML
    representation of the code snippet.
    """
    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)

When that's all done we'll need to update our database tables. Normally we'd create a database migration in order to do that, but for the purposes of this tutorial, let's just delete the database and start again.

rm -f tmp.db db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate

You might also want to create a few different users, to use for testing the API. The quickest way to do this will be with the createsuperuser command.

python manage.py createsuperuser

Adding endpoints for our User models

Now that we've got some users to work with, we'd better add representations of those users to our API. Creating a new serializer is easy. In serializers.py add:

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')

Because 'snippets' is a reverse relationship on the User model, it will not be included by default when using the ModelSerializer class, so we needed to add an explicit field for it.

We'll also add a couple of views to views.py. We'd like to just use read-only views for the user representations, so we'll use the ListAPIView and RetrieveAPIView generic class based views.

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

Make sure to also import the UserSerializer class

from snippets.serializers import UserSerializer

Finally we need to add those views into the API, by referencing them from the URL conf. Add the following to the patterns in urls.py.

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

Associating Snippets with Users

Right now, if we created a code snippet, there'd be no way of associating the user that created the snippet, with the snippet instance. The user isn't sent as part of the serialized representation, but is instead a property of the incoming request.

The way we deal with that is by overriding a .perform_create() method on our snippet views, that allows us to modify how the instance save is managed, and handle any information that is implicit in the incoming request or requested URL.

On the SnippetList view class, add the following method:

def perform_create(self, serializer):
    serializer.save(owner=self.request.user)

The create() method of our serializer will now be passed an additional 'owner' field, along with the validated data from the request.

Updating our serializer

Now that snippets are associated with the user that created them, let's update our SnippetSerializer to reflect that. Add the following field to the serializer definition in serializers.py:

owner = serializers.ReadOnlyField(source='owner.username')

Note: Make sure you also add 'owner', to the list of fields in the inner Meta class.

This field is doing something quite interesting. The source argument controls which attribute is used to populate a field, and can point at any attribute on the serialized instance. It can also take the dotted notation shown above, in which case it will traverse the given attributes, in a similar way as it is used with Django's template language.

The field we've added is the untyped ReadOnlyField class, in contrast to the other typed fields, such as CharField, BooleanField etc... The untyped ReadOnlyField is always read-only, and will be used for serialized representations, but will not be used for updating model instances when they are deserialized. We could have also used CharField(read_only=True) here.

Adding required permissions to views

Now that code snippets are associated with users, we want to make sure that only authenticated users are able to create, update and delete code snippets.

REST framework includes a number of permission classes that we can use to restrict who can access a given view. In this case the one we're looking for is IsAuthenticatedOrReadOnly, which will ensure that authenticated requests get read-write access, and unauthenticated requests get read-only access.

First add the following import in the views module

from rest_framework import permissions

Then, add the following property to both the SnippetList and SnippetDetail view classes.

permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

Adding login to the Browsable API

If you open a browser and navigate to the browsable API at the moment, you'll find that you're no longer able to create new code snippets. In order to do so we'd need to be able to login as a user.

We can add a login view for use with the browsable API, by editing the URLconf in our project-level urls.py file.

Add the following import at the top of the file:

from django.conf.urls import include

And, at the end of the file, add a pattern to include the login and logout views for the browsable API.

urlpatterns += [
    url(r'^api-auth/', include('rest_framework.urls',
                               namespace='rest_framework')),
]

The r'^api-auth/' part of pattern can actually be whatever URL you want to use. The only restriction is that the included urls must use the 'rest_framework' namespace. In Django 1.9+, REST framework will set the namespace, so you may leave it out.

Now if you open up the browser again and refresh the page you'll see a 'Login' link in the top right of the page. If you log in as one of the users you created earlier, you'll be able to create code snippets again.

Once you've created a few code snippets, navigate to the '/users/' endpoint, and notice that the representation includes a list of the snippet pks that are associated with each user, in each user's 'snippets' field.

Object level permissions

Really we'd like all code snippets to be visible to anyone, but also make sure that only the user that created a code snippet is able to update or delete it.

To do that we're going to need to create a custom permission.

In the snippets app, create a new file, permissions.py

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow owners of an object to edit it.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the snippet.
        return obj.owner == request.user

Now we can add that custom permission to our snippet instance endpoint, by editing the permission_classes property on the SnippetDetail view class:

permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                      IsOwnerOrReadOnly,)

Make sure to also import the IsOwnerOrReadOnly class.

from snippets.permissions import IsOwnerOrReadOnly

Now, if you open a browser again, you find that the 'DELETE' and 'PUT' actions only appear on a snippet instance endpoint if you're logged in as the same user that created the code snippet.

Authenticating with the API

Because we now have a set of permissions on the API, we need to authenticate our requests to it if we want to edit any snippets. We haven't set up any authentication classes, so the defaults are currently applied, which are SessionAuthentication and BasicAuthentication.

When we interact with the API through the web browser, we can login, and the browser session will then provide the required authentication for the requests.

If we're interacting with the API programmatically we need to explicitly provide the authentication credentials on each request.

If we try to create a snippet without authenticating, we'll get an error:

http POST http://127.0.0.1:8000/snippets/ code="print 123"

{
    "detail": "Authentication credentials were not provided."
}

We can make a successful request by including the username and password of one of the users we created earlier.

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"
}

Summary

We've now got a fairly fine-grained set of permissions on our Web API, and end points for users of the system and for the code snippets that they have created.

In part 5 of the tutorial we'll look at how we can tie everything together by creating an HTML endpoint for our highlighted snippets, and improve the cohesion of our API by using hyperlinking for the relationships within the system.

转载于:https://my.oschina.net/cqlcql/blog/680688

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值