Schemas是一种机器可读的文档,描述了API的端点,它们的URLs和所支持的操作。
Schemas可以用于自动生成文档,也可以用来驱动可以与API交互的动态客户端库。

Core API

为了提供Schemas支持,REST框架使用了Core API。
安装coreapi

pip install coreapi

添加schema

REST框架既支持自定义Schemas视图,也支持自动生成Schemas视图。
由于我们使用的是viewset和route,我们可以简单地自动生成Schemas视图。

现在我们需要通过在URL中配置一个自动生成的Schemas视图,为我们的API添加一个Schemas。
编辑urls.py

from rest_framework.schemas import get_schema_view

schema_view = get_schema_view(title='Pastebin API')

urlpatterns = [
    url(r'^schema/$', schema_view),
    ...
]

我们通过命令行的形式访问该接口,并指定接收格式为corejson

(django_rest_framework) [root@iZuf62kvdczytcyqlvr2pgZ django_rest_framework]# http https://127.0.0.1:80/schema/ Accept:application/coreapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Length: 1498
Content-Type: application/coreapi+json
Date: Fri, 01 Dec 2017 12:04:53 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "_meta": {
        "title": "Pastebin API",
        "url": "https://127.0.0.1/schema/"
    },
    "_type": "document",
    "snippets": {
        "highlight": {
            "_type": "link",
            "action": "get",
...

使用命令行客户端

既然我们的API提供了一个Schemas url,我们可以使用一个动态的客户端来与API进行交互。
为了演示,我们使用Core API客户端。
安装coreapi-cli

pip install coreapi-cli

使用命令行客户端访问schema接口

(django_rest_framework) [root@iZuf62kvdczytcyqlvr2pgZ django_rest_framework]# coreapi get https://127.0.0.1:80/schema/
<Pastebin API "https://127.0.0.1/schema/">
    snippets: {
        list()
        read(id)
        highlight(id)
    }
    users: {
        list()
        read(id)
    }

由于我们没有登录,所以我们现在只能看到只读的接口。

列出现有的所有snippets

(django_rest_framework) [root@iZuf62kvdczytcyqlvr2pgZ django_rest_framework]# coreapi action snippets list
[
    {
        "url": "https://127.0.0.1/snippets/1/",
        "id": 1,
        "highlight": "https://127.0.0.1/snippets/1/highlight/",
        "owner": "song",
        "title": "test_1",
        "code": "print('hello world')",
        "linenos": true,
        "language": "python",
        "style": "friendly"
    },
]

有些API需要一些命名参数

(django_rest_framework) [root@iZuf62kvdczytcyqlvr2pgZ django_rest_framework]# coreapi action snippets highlight --param id=1
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "https://www.w3.org/TR/html4/strict.dtd">

<html>
<head>
  <title>test_1</title>
  <meta http-equiv="content-type" content="text/html; charset=None">
  <style type="text/css">
td.linenos { background-color: #f0f0f0; padding-right: 10px; }
span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
pre { line-height: 125%; }
...

登录

需要写入的接口需要我们登录才能做,通过以下命令进行登录(test:qazxswedc为用户名和密码)

(django_rest_framework) [root@iZuf62kvdczytcyqlvr2pgZ django_rest_framework]# coreapi credentials add 127.0.0.1 test:qazxswedc --auth basic
Added credentials
127.0.0.1 "Basic dGVzdDpxYXp4c3dlZGM="

重载

(django_rest_framework) [root@iZuf62kvdczytcyqlvr2pgZ django_rest_framework]# coreapi reload
Pastebin API "https://127.0.0.1:80/schema/">
    snippets: {
        create(code, [title], [linenos], [language], [style])
        delete(id)
        highlight(id)
        list()
        partial_update(id, [title], [code], [linenos], [language], [style])
        read(id)
        update(id, code, [title], [linenos], [language], [style])
    }
    users: {
        list()
        read(id)
    }

我们现在可以对数据进行写操作了

coreapi action snippets delete --param id=1

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 7: Schemas & client libraries

博客更新地址

REST 框架包含了处理ViewSet的抽象,这样开发者就可以专注于API的状态和交互,而不用去管URL的构造,URL会按照
公共约定自动构造。
ViewSet类和View类差不多,不同的是ViewSet提供如read,update等方法,而不是get或是put
一个ViewSet类只绑定一组方法处理程序,当它被实例化为一组views时,通常用一个Router类来处理复杂的url。

重构代码以使用ViewSets

首先将我们现有的UserListUserDetail重构合并为UserViewSet
编辑views.py

from rest_framework import viewsets


class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    这个ViewSet提供`list`和`detail`两个功能
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

这里我们使用的ReadOnlyModelViewSet类会提供默认的只读操作。
像以前一样,我们依然定义querysetserializer_class属性,只不过之前需要在两个类里面定义,现在只需要定义一遍。

接下来我们将现有的SnippetList, SnippetDetail, SnippetHighlight重构合并为SnippetViewSet
编辑views.py

from rest_framework.decorators import detail_route
from rest_framework.response import Response


class SnippetViewSet(viewsets.ModelViewSet):
    """
    这个ViewSet自动了`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请求,我们可以在装饰器的参数内定义methods参数来使用如POST等等的其它请求。
默认情况下,这种定制的额外功能会使用和方法名同名的url,如想要使用与方法名不同的url,可以在detail_route装饰器中定义url_path参数。

将ViewSets与URLs绑定

编辑urls.py

from rest_framework import renderers
from snippets.views import api_root
from snippets.views import SnippetViewSet
from snippets.views import UserViewSet

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请求方式绑定到views的方法,从ViewSet类创建了一系列views。
接下来我们将这些views注册到url中
编辑urls.py

from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns

urlpatterns = format_suffix_patterns([
    url(r'^$', api_root),
    url(r'^snippets/$',
        snippet_list,
        name='snippet-list'),
    url(r'^snippets/(?P<pk>)[0-9]+/$',
        snippet_detail,
        name='snippet-detail'),
    url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
        snippet_highlight,
        name='snippet-highlight'),
    url(r'^users/$',
        user_list,
        name='user-list'),
    url(r'^users/(?P<pk>[0-9]+)/$',
        user_detail,
        name='user-detail')
])

使用Router

因为我们使用的是ViewSet类而不是View类,实际上我们不需要自己去设计URL。
通过使用Router类,我们只需要将合适的views注册到Router中,其它的事情就让它自动生成吧。

重写urls.py

from django.conf.urls import url
from django.conf.urls import include
from snippets import views
from rest_framework.routers import DefaultRouter


# 创建一个router并将viewsets注册上去
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)

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

DefaultRouter类自动提供了根节点的url,所以我们不再需要单独去写。

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 6: ViewSets & Routers

博客更新地址

目前我们使用主键来表示模型之间的关系。在本章,我们将提高API的凝聚性和可读性。

为我们API的根节点创建URL

之前我们给snippetsusers创建了URL接口,但我们没有一个根节点的URL。
在此我们创建一个简单的,基于函数的views,并为它加上@api_view装饰器,修改snippets/views.py

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse


@api_view(['GET'])
def api_root(request, format=None):
    return Response({
        'users': reverse('user-list', request=request, format=format),
        'snippets': reverse('snippet-list', request=request, format=format)
    })

需要注意两点:
* 我们使用REST框架中的reverse方法来返回完全符合规则的url
* url参数将会与我们之后在snippets/urls中定义的相同

编写snippets/urls.py

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

为高亮代码创建URL

接下来我们需要为高亮代码提供接口,和其它的接口不同,这个接口我们需要展现渲染好的HTML页面,而不是JSON。
REST框架给我们提供了两种HTML展现形式,一种是使用模板渲染,一种是使用已经提前渲染好的HTML,在此我们使用第二种。
另外,我们需要考虑当创建代码高亮的view时,我们没有现成的类来继承,因为我们不是返回对象,而是返回对象的一个属性,我们需要重写父类的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

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

API之间使用超链接

在Web API中处理各个API之间的关系是一件非常头疼的事情,通常有以下方法表示关系:
* 使用主键
* 在API之间使用超链接
* 在关联API之间使用唯一字段表示
* 在关联API之间使用默认字符串表示
* 将一个API嵌套在另一个API类中
* 其它

REST框架支持上述所有方法,并且可以应用于正向、反向关系或类似外键这类自定义管理项。
在这里我们使用超链接来处理之间的关系,因此我们需要修改serializers,使用HyperlinkedModelSerializer替代原先的ModelSerializer:
* 默认不包含主键
* 使用HyperlinkedRelatedField时,需要在Meta子类的fields中包含“urls字段
* 使用
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')

这里在SnippetSerializer中新增了一个highlight属性,这个属性和url字段类型相同,区别在于它指向的是snippet-highlight而非snippet-detail
另外,由于我们有.json格式的后缀,我们需要指定highlight字段使用.html来返回相应的格式

确保URL都被命名

在此之前我们创建了一些url参数,在此罗列:
* 根节点指向了user-listsnippet-list
* snippet serializer包含了一个指向snippet-highlight的url的字段
* user serializer包含了指向snippet-detail的url的字段
* snippet serializeruser serializer都包含了url字段,这个字段默认指向{model_name}-detail,这里分别是snippet-detailuser-detail

编辑snippet/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<pk>[0-9]+)/$',
        views.SnippetDetail.as_view(),
        name='snippet-detail'),
    url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
        views.SnippetHighlight.as_view(),
        name='snippet-highlight'),
    url(r'^users/$',
        views.UserList.as_view(),
        name='user-list'),
    url(r'^users/(?P<pk>[0-9]+)/$',
        views.UserDetail.as_view(),
        name='user-detail')
])

# Login and logout views for the browsable API
urlpatterns += [
    url(r'^api-auth/', include('rest_framework.urls',
                               namespace='rest_framework')),
]

添加分页

后面可能会有很多个数据产生,我们需要对返回结果进行分页,修改tutorial/settings.py

REST_FRAMEWORK = {
    'PAGE_SIZE': 10
}

注意,所有关于REST框架的settings都在一个叫做REST_FRAMEWORK的字典中,这帮助我们与其他的settings分离开来。

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 5: Relationships & Hyperlinked APIs

博客更新地址

目前为止,我们的代码没有限制谁可以编辑和删除代码片段,此节我们需要实现以下功能
* 代码片段需要与创建者关联
* 只有通过验证的用户才能创建代码片段
* 只有创建者才能修改或删除代码片段
* 没有通过验证的用户拥有只读权限

给model添加字段

我们需要添加两个字段,一个用于存储代码片段的创建者信息,一个用于存储代码的高亮信息

    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
    owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
    highlighted = models.TextField()

同时,我们需要在该模型类执行保存操作时,自动填充highlighted字段,使用pygments库。
首先,导入一些包

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

然后为Snippet重写父类的save方法

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

接下来需要迁移数据库,方便起见,删库,然后重新迁移

(django_rest_framework) [root@localhost tutorial]# rm -f tmp.db db.sqlite3 && \
> rm -rf snippets/migrations/ && \
> python manage.py makemigrations snippets && \
> python manage.py migrate
Migrations for 'snippets':
  snippets/migrations/0001_initial.py
    - Create model Snippet
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, snippets
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK
  Applying snippets.0001_initial... OK

为了测试API,我们需要创建一些用户,最快的方式就是通过createsuperuser命令

(django_rest_framework) [root@localhost tutorial]# python manage.py createsuperuser
Username (leave blank to use 'root'): song
Email address: shelmingsong@gmail.com
Password: 
Password (again): 
Superuser created successfully.
(django_rest_framework) [root@localhost tutorial]# python manage.py createsuperuser
Username (leave blank to use 'root'): user_1
Email address: user_1@gmail.com
Password: 
Password (again): 
Superuser created successfully.
(django_rest_framework) [root@localhost tutorial]# python manage.py createsuperuser
Username (leave blank to use 'root'): user_2
Email address: user_2@gmail.com
Password: 
Password (again): 
Superuser created successfully.

为用户模型添加接口

我们已经创建了三个用户,现在我们需要添加用户相关的接口,修改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')

因为snippetsuser是一种反向的关联,默认不会包含入ModelSerializer类中,所以需要我们手动添加

我们也需要对views.py进行修改,由于用户页面为只读,所以继承于ListAPIViewRetrieveAPIView

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.py

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

关联User和Snippet

此时我们创建一个代码片段,是无法与用户关联的,因为用户信息是通过request获取的。
因此我们需要重写snippet的view中perform_create()方法,这个方法允许我们在对象保存前进行相关操作,处理任何有requestrequested URL传递进来的数据

修改views.py中的SnippetList类,添加perform_create()方法

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

如此,新建代码片段时,会添加owner字段,该字段存储了request中的用户信息

更新serializer

之前我们在views中的SnippetList类中添加了perform_create方法,保存了owner信息,因而也需要在serializer中的SnippetSerializer类中添加owner信息,同时将owner添加进Meta子类的fields字段中

class SnippetSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style', 'owner')

这里我们使用了ReadOnlyField类型,这个类型是只读的,不能被更新,和Charfield(read_only=True)是一样的效果

添加权限认证

我们希望只有登录的用户能够去增加代码片段,未登录则只有查看的权限,此时我们需要用到IsAuthenticatedOrReadOnly

修改views.py,为snippet的两个类views添加permission_classes字段

from rest_framework import permissions


class SnippetList(generics.ListCreateAPIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, )


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, )

添加登陆接口

修改项目的urls.py

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

r'^api-auth/'可以自定,namespace在Django 1.9 + 的版本中可以省略

运行Django服务器,访问your.domain/snippets/,点击右上角的登陆按钮,登陆我们之前创建的用户后,就可以创建代码片段了

创建完几个代码片段后,再访问your.domain/users/时,就可以看到每个用户创建了哪几个代码片段了

对象级别的权限

现在用户都可以对所有的snippets进行增删改查,我们要确保只有创建者可以对snippets进行改动或删除。

snippetsapp中,创建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):
        # allow all user to read
        if request.method in permissions.SAFE_METHODS:
            return True

        # only allow owner to edit
        return obj.owner == request.user

views.py中添加权限

from snippets.permissions import IsOwnerOrReadOnly

class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly)

此时我们访问your.domain/snippets/1/,若用户未登录或登录用户不是该snippets的创建者,则只有读的权限,页面上表现为没有DELETE(上方中间)和PUT(右下角)按钮

通过接口进行权限认证

之前我们是通过浏览器页面进行登录的,而当我们直接使用接口去请求时,如果没有进行登录,而对某个snippet进行修改或是创建一个新的snippet,则会报错

(django_rest_framework) [root@localhost django_rest_framework]# http POST https://127.0.0.1:80/snippets/ code="hahah"
HTTP/1.0 403 Forbidden
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Tue, 28 Nov 2017 14:56:18 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

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

(django_rest_framework) [root@localhost django_rest_framework]# http POST https://127.0.0.1:80/snippets/1/ code="hahah"
HTTP/1.0 403 Forbidden
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Tue, 28 Nov 2017 14:56:26 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

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

我们在发送请求时,提供用户名和密码,就可以进行操作了

(django_rest_framework) [root@localhost django_rest_framework]# http -a your_username:your_password POST https://127.0.0.1:80/snippets/ code="hahah"
HTTP/1.0 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 104
Content-Type: application/json
Date: Tue, 28 Nov 2017 14:58:10 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "code": "hahah",
    "id": 4,
    "language": "python",
    "linenos": false,
    "owner": "song",
    "style": "friendly",
    "title": ""
}

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 4: Authentication & Permissions

博客更新地址

基于类的views

之前我们创建的views都是基于函数的,我们也可以基于类来写views

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)


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

使用mixins

相比函数型views来说,基于类的views可以很容易地通过继承来去复用代码。
刚才我们创建的API中,基本都是数据库的增删改查操作,这种常见的操作都已经在REST框架中定义好了,在mixin类中,我们只需要去继承它。
再次重构views.py

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics


class SnippetList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

SnippetList这个类,首先继承于GenericAPIView,这个类提供了核心功能,接下来是继承于ListModelMixinCreateModelMixin类,这两个类分别提供了.list().create()功能。

接下来重构下一个API

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)

RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin分别提供了retrieve(), update(), destroy()功能

继承更通用的generic类

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 3: Class-based Views

博客更新地址

Request 对象

REST 框架引入了Request对象,继承于HttpRequest,相比HttpRequest提供了更多请求解析,最核心的功能是request.data属性,类似于request.POST,以下是不同之处。
* request.POST
1. 只能处理form表单数据;
2. 只能处理POST请求。
* request.data
1. 能够处理任意一种数据;
2. 能够处理POST、PUT、PATCH请求

Response对象

REST框架也引入了Response对象,它是一个TemplateResponse类型,能够将未处理的文本转换为合适的类型返回给客户端

return Response(data)

状态码

REST框架提供了更可读的状态信息,比如HTTP_400_BAD_REQUEST

API views封装

  • 对于函数views,可以使用@api_view装饰器
  • 对于类views,可以继承于APIView

views应用

  • 修改snippets/views.py
  1. GET获取所有code snippets,与新建code snippet的接口
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 code 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)
  1. GET获取单个code snippetPUT更新单个code snippetDELETE删除单个code snippet
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
    retrieve, update or delete code snippet
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoseNotExist:
        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.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

和上一步的views明显不同的是,我们不再需要关心输入(request)输出(response)的数据类型,REST框架已经帮我们处理好了

为URLs添加可选格式后缀

如上一步所说的,REST框架已经帮我们处理好了输入(request)输出(response)的数据类型,也就意味着一个API可以去处理不同的数据类型,在URLs中使用格式后缀可以帮助我们处理类似这样的url: https://192.168.0.103/snippets.json

  • 首先我们需要在views中添加形参format=None
def snippet_list(request, format=None):

def snippet_list(request, format=None):
  • 然后我们修改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.snippet_list),
    url(r'^snippets/(?P<pk>[0-9]+)$', views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

调用接口

  • 在启动服务器前,先修改settings.py中的ALLOWED_HOSTS,方便后面通过外部浏览器请求接口
ALLOWED_HOSTS = ['*']
  • 启动服务器(别管时间,每天晚上回来,让Win10从睡眠状态恢复,虚拟机的IP和时区总会变 🙁 ,懒得每次都改了)
(django_rest_framework) [root@localhost tutorial]# python manage.py runserver 0:80
Performing system checks...

System check identified no issues (0 silenced).
November 21, 2017 - 02:47:02
Django version 1.11.7, using settings 'tutorial.settings'
Starting development server at https://0:80/
Quit the server with CONTROL-C.
  • 打开另一个shell窗口,发送请求
(django_rest_framework) [root@localhost django_rest_framework]# http https://127.0.0.1:80/snippets/
HTTP/1.0 200 OK
Allow: POST, GET, OPTIONS
Content-Length: 505
Content-Type: application/json
Date: Mon, 20 Nov 2017 18:51:07 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

[
    {
        "code": "foo = \"bar\n\"",
        "id": 1,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
    {
        "code": "print \"hello, world\"\n",
        "id": 2,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
...
]
  • 我们可以添加HTTP HEADERS来控制返回数据的数据类型
  1. json
(django_rest_framework) [root@localhost django_rest_framework]# http https://127.0.0.1:80/snippets/ Accept:application/json
HTTP/1.0 200 OK
Allow: POST, GET, OPTIONS
Content-Length: 505
Content-Type: application/json
Date: Mon, 20 Nov 2017 18:52:27 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

[
    {
        "code": "foo = \"bar\n\"",
        "id": 1,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
    {
        "code": "print \"hello, world\"\n",
        "id": 2,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
]
  1. html
(django_rest_framework) [root@localhost django_rest_framework]# http https://127.0.0.1:80/snippets/ Accept:text/html
HTTP/1.0 200 OK
Allow: POST, GET, OPTIONS
Content-Length: 8139
Content-Type: text/html; charset=utf-8
Date: Mon, 20 Nov 2017 18:53:39 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

<!DOCTYPE html>
<html>
  <head>



        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta name="robots" content="NONE,NOARCHIVE" />


      <title>Snippet List – Django REST framework</title>



          <link rel="stylesheet" type="text/css" href="/static/rest_framework/css/bootstrap.min.css"/>
...
  • 或者我们直接可以添加url后缀来控制返回数据的数据类型
  1. json
(django_rest_framework) [root@localhost django_rest_framework]# http https://127.0.0.1:80/snippets.json
HTTP/1.0 200 OK
Allow: POST, GET, OPTIONS
Content-Length: 505
Content-Type: application/json
Date: Mon, 20 Nov 2017 18:55:27 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

[
    {
        "code": "foo = \"bar\n\"",
        "id": 1,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
    {
        "code": "print \"hello, world\"\n",
        "id": 2,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
...
]
  1. html
(django_rest_framework) [root@localhost django_rest_framework]# http https://127.0.0.1:80/snippets.api
HTTP/1.0 200 OK
Allow: POST, GET, OPTIONS
Content-Length: 8160
Content-Type: text/html; charset=utf-8
Date: Mon, 20 Nov 2017 18:56:35 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

<!DOCTYPE html>
<html>
  <head>



        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta name="robots" content="NONE,NOARCHIVE" />


      <title>Snippet List – Django REST framework</title>



          <link rel="stylesheet" type="text/css" href="/static/rest_framework/css/bootstrap.min.css"/>
          <link rel="stylesheet" type="text/css" href="/static/rest_framework/css/bootstrap-tweaks.css"/>
...
  • 类似的,我们可以发送不同类型的数据给API
  1. post form data
(django_rest_framework) [root@localhost django_rest_framework]# http --form POST https://127.0.0.1:80/snippets/ code="hello world post form data"
HTTP/1.0 201 Created
Allow: POST, GET, OPTIONS
Content-Length: 110
Content-Type: application/json
Date: Mon, 20 Nov 2017 18:58:58 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "code": "hello world post form data",
    "id": 6,
    "language": "python",
    "linenos": false,
    "style": "friendly",
    "title": ""
}
  1. post json data
(django_rest_framework) [root@localhost django_rest_framework]# http --json POST https://127.0.0.1:80/snippets/ code="hello world post json data"
HTTP/1.0 201 Created
Allow: POST, GET, OPTIONS
Content-Length: 110
Content-Type: application/json
Date: Mon, 20 Nov 2017 18:59:44 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "code": "hello world post json data",
    "id": 7,
    "language": "python",
    "linenos": false,
    "style": "friendly",
    "title": ""
}
  • 在请求时添加--debug后缀可以查看请求的详细信息
(django_rest_framework) [root@localhost django_rest_framework]# http --json POST https://127.0.0.1:80/snippets/ code="hello world post json data" --debug
HTTPie 0.9.9
Requests 2.18.4
Pygments 2.2.0
Python 3.6.3 (default, Nov  4 2017, 22:19:41) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)]
/root/.pyenv/versions/3.6.3/envs/django_rest_framework/bin/python
Linux 3.10.0-693.el7.x86_64

<Environment {
    "colors": 8,
    "config": {
        "__meta__": {
            "about": "HTTPie configuration file",
            "help": "https://httpie.org/docs#config",
            "httpie": "0.9.9"
        },
        "default_options": "[]"
    },
    "config_dir": "/root/.httpie",
    "is_windows": false,
    "stderr": "<_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>",
    "stderr_isatty": true,
    "stdin": "<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>",
    "stdin_encoding": "UTF-8",
    "stdin_isatty": true,
    "stdout": "<_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>",
    "stdout_encoding": "UTF-8",
    "stdout_isatty": true
}>

>>> requests.request(**{
    "allow_redirects": false,
    "auth": "None",
    "cert": "None",
    "data": "{\"code\": \"hello world post json data\"}",
    "files": {},
    "headers": {
        "Accept": "application/json, */*",
        "Content-Type": "application/json",
        "User-Agent": "HTTPie/0.9.9"
    },
    "method": "post",
    "params": {},
    "proxies": {},
    "stream": true,
    "timeout": 30,
    "url": "https://127.0.0.1:80/snippets/",
    "verify": true
})

HTTP/1.0 201 Created
Allow: POST, GET, OPTIONS
Content-Length: 110
Content-Type: application/json
Date: Mon, 20 Nov 2017 19:00:45 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "code": "hello world post json data",
    "id": 9,
    "language": "python",
    "linenos": false,
    "style": "friendly",
    "title": ""
}
  • 在浏览器中发送请求
  1. 在浏览器中发送请求,默认会返回html类型的数据

  2. 可以像之前那样,加上url后缀,来请求json数据

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 2: Requests and Responses

博客更新地址

依赖

  • Python 3.6.3 [ Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6) 都可以 ]
  • Django 1.11.7 [ Django (1.10, 1.11, 2.0 alpha) 都可以 ]

安装、创建并配置虚拟环境

mkdir django_rest_framework && cd django_rest_framework
pyenv virtualenv 3.6.3 django_rest_framework
pyenv local django_rest_framework
  • 安装相关包
pip install django djangorestframework
pip install pygments  # 项目中会用到,提供代码高亮功能

Django项目初始化

  • 创建tutorial项目与snippets应用
django-admin startproject tutorial
cd tutorial/

python manage.py startapp snippets
  • snippets应用和rest_framework添加进settings.pyINSTALLED_APPS (如果Django版本小于1.9,则需要将snippets.apps.SnippetsConfig替换为snippets)
INSTALLED_APPS = (
    ...
    'rest_framework',
    'snippets.apps.SnippetsConfig',
)

创建Model

我们需要创建一个Snippet模型来存储代码片段。
* 修改snippets/models.py文件

from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles

# 提取出了pygments支持的所有语言的词法分析程序
LEXERS = [item for item in get_all_lexers() if item[1]]
# 提取出了pygments支持的所有语言列表
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
# 提取出了pygments支持的所有格式化风格列表
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

创建Serializer类

  • snippets文件夹内创建serializers.py文件
from rest_framework import serializers
from snippets.models import LANGUAGE_CHOICES
from snippets.models import Snippet
from snippets.models import 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):
        """
        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

使用Serializers类

  • 打开Django shell
python manage.py shell
  • 创建两个Serializers对象
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()

序列化

  • 序列化一个对象
In [11]: serializer = SnippetSerializer(snippet)

In [12]: serializer.data
Out[12]: 
ReturnDict([('id', 2),
            ('title', ''),
            ('code', 'print "hello, world"\n'),
            ('linenos', False),
            ('language', 'python'),
            ('style', 'friendly')])
  • 将data数据转换为json
In [13]: content = JSONRenderer().render(serializer.data)

In [14]: content
Out[14]: b'{"id":2,"title":"","code":"print \\"hello, world\\"\\n","linenos":false,"language":"python","style":"friendly"}'

反序列化

  • 将JSON数据转为Python原生数据类型(字典)
In [15]: from django.utils.six import BytesIO

In [16]: stream = BytesIO(content)

In [17]: data = JSONParser().parse(stream)

In [18]: data
Out[18]: 
{'code': 'print "hello, world"\n',
 'id': 2,
 'language': 'python',
 'linenos': False,
 'style': 'friendly',
 'title': ''}
  • 将Python原生数据类型(字典)转换为对象
In [19]: serializer = SnippetSerializer(data=data)

In [20]: serializer.is_valid()
Out[20]: True

In [21]: serializer.validated_data
Out[21]: 
OrderedDict([('title', ''),
             ('code', 'print "hello, world"'),
             ('linenos', False),
             ('language', 'python'),
             ('style', 'friendly')])

In [22]: serializer.save()
Out[22]: <Snippet: Snippet object>
  • 可以序列化查询集,只需要添加many=True参数
In [23]: serializer = SnippetSerializer(Snippet.objects.all(), many=True)

In [24]: serializer.data
Out[24]: [OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar\n"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', ''), ('code', 'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]

使用ModelSerializer

刚才定义的SnippetSerializer类中的类中有很多是和之前在models.py定义的内容重复,其实我们还可以使SnippetSerializer类继承于ModelSerializer,来减少这种重复定义。

  • SnippetSerializer类重新定义,为如下内容:
class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
  • 打开Django shell
python manage.py shell
  • 查看SnippetSerializer
In [1]: from snippets.serializers import SnippetSerializer

In [2]: serializer = SnippetSerializer()

In [3]: 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=[('abap', 'ABAP'), ('abnf', 'ABNF'), ('ada', 'Ada'), ('adl', 'ADL'), ('agda', 'Agda'), ('aheui', 'Aheui'), ('ahk', 'autohotkey'),
 ...

至此我们发现,继承于ModelSerializer类,有两个非常大的作用:
1. 不用重复去定义类属性
2. 自动拥有create()update()方法

Serializer在Django views内的简单运用

  • 此处未使用REST framework的其它特性,仅使用常规的Django views
  • 此处的写法有很多漏洞,仅作为教程中测试展示使用,不能直接应用于生产环境

配置views

打开snippets/views.py
* 导入相关包

from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
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
  • 创建一个返回所有对象和对象增功能的API
@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, safe=False)

    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)
  • 创建一个对象查、改、删功能的API
@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)

配置urls

  • 创建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),
]
  • 配置项目根urls.py
from django.conf.urls import url
from django.conf.urls import include
from django.contrib import admin

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

测试接口

  • 启动服务
(django_rest_framework) [root@localhost tutorial]# python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
November 20, 2017 - 23:01:29
Django version 1.11.7, using settings 'tutorial.settings'
Starting development server at https://127.0.0.1:8000/
Quit the server with CONTROL-C.
  • 打开另一个shell窗口
  • 安装httpie包,用于模拟http请求

pip install httpie
  • 发送请求,获取所有snippets对象
(django_rest_framework) [root@localhost tutorial]# http https://127.0.0.1:8000/snippets/
HTTP/1.0 200 OK
Content-Length: 352
Content-Type: application/json
Date: Mon, 20 Nov 2017 15:03:10 GMT
Server: WSGIServer/0.2 CPython/3.6.3
X-Frame-Options: SAMEORIGIN

[
    {
        "code": "foo = \"bar\n\"",
        "id": 1,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
    {
        "code": "print \"hello, world\"\n",
        "id": 2,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
]
  • 发送请求,获取主键为2的snippets对象
(django_rest_framework) [root@localhost tutorial]# http https://127.0.0.1:8000/snippets/2/
HTTP/1.0 200 OK
Content-Length: 119
Content-Type: application/json
Date: Mon, 20 Nov 2017 15:03:17 GMT
Server: WSGIServer/0.2 CPython/3.6.3
X-Frame-Options: SAMEORIGIN

{
    "code": "print \"hello, world\"\n",
    "id": 2,
    "language": "python",
    "linenos": false,
    "style": "friendly",
    "title": ""
}

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 1: Serialization

博客更新地址