Django-Rest-Framework 教程

原文转自:http://www.weiguda.com/blog/categories/12/

快速入门

本篇中, 我们会创建一个简单的API, 用来查看和编辑django默认的user和group数据.


1. 设置

我们创建django项目tutorial, 和app quickstart:

    # 创建新Django项目
    django-admin.py startproject tutorial
    cd tutorial

    # 使用virtualenvwrapper创建Virtualenv
    mkvirtualenv env
    workon env

    # 在env中安装Django 和 Django REST framework
    pip install django
    pip install djangorestframework

    # 创建新app
    python manage.py startapp quickstart

然后根据自己的数据库配置设置数据库:

    # tutorial/settings.py
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql_psycopg2',
            'NAME': 'database_name',
            'USER': 'database_user',
            'PASSWORD': 'database_password',
            'HOST': '',
            'PORT': ''
        }
    }
    ...
    INSTALLED_APPS = (
        ...
        'quickstart',
        'rest_framework',
    )

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

最后通过syncdb创建数据库

    python manage.py syncdb

2. 序列化

接下来我们创建用于数据序列化的代码:

    # quickstart/serializers.py
    from django.contrib.auth.models import User, Group
    from rest_framework import serializers


    class UserSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = User
            fields = ('url', 'username', 'email', 'groups')


    class GroupSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = Group
            fields = ('url', 'name')

值得注意的是, 我们使用的是HyperlinkedModelSerializer. 你可以使用主键或者其他关系, 但使用HyperlinkedModelSerializer是一个好的 RESTful 设计.

3. Views

    # quickstart/views.py
    from django.contrib.auth.models import User, Group
    from rest_framework import viewsets
    from quickstart.serializers import UserSerializer, GroupSerializer


    class UserViewSet(viewsets.ModelViewSet):
        """
        允许查看和编辑user 的 API endpoint
        """
        queryset = User.objects.all()
        serializer_class = UserSerializer


    class GroupViewSet(viewsets.ModelViewSet):
        """
        允许查看和编辑group的 API endpoint
        """
        queryset = Group.objects.all()
        serializer_class = GroupSerializer

在django_rest_framework中, 所有常见的行为都被归到了ViewSets中. 当然我们可以将这些行为分拆出来, 但使用ViewSets, 使view的逻辑更为清楚.

使用queryset和serializer_class代替model变量, 使我们能更加好的控制API行为, 这也是我们推荐的使用方式.

4. URLs

    # tutorial/urls.py
    from django.conf.urls import patterns, url, include
    from rest_framework import routers
    from quickstart import views

    router = routers.DefaultRouter()
    router.register(r'users', views.UserViewSet)
    router.register(r'groups', views.GroupViewSet)

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

因为我们使用的是viewset, 所以我们可以使用route class自动生成url conf.

5. 测试

至此我们的设置完成, 可以测试我们的REST API了, 首先启动django服务器:

    python ./manage.py runserver

我们可以使用curl命令:

    bash: curl -H 'Accept: application/json; indent=4' -u admin:password 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"
            }
        ]
    }

或者直接使用浏览器也可以:

Django-Rest-Framework 教程: 1. 序列化 (Serialization)

在本篇中, 我们将通过建立一个代码黏贴板(pastebin), 来熟悉组成REST framework的各组成部分, 并了解这些部件是如何相互协调工作的.

1. 环境设置

首先我们使用virtualenvwrapper创建新的virtualenv, 并安装需要的代码库:

    mkvirtualenv env
    pip install django
    pip install djangorestframework
    pip install pygments # 用于代码高亮

2. Django项目设置

我们建立Django项目tutorial, 和app snippets

    django-admin.py startproject tutorial
    cd tutorial
    python manage.py startapp snippets

设置settings.py:

    # tutorial/settings.py
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql_psycopg2',
            'NAME': 'database_name',
            'USER': 'database_user',
            'PASSWORD': 'database_password',
            'HOST': '',
            'PORT': ''
        }
    }

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

设置urls.py, 将新建的snippet app中的urls.py加入到其中:

    # tutorial/urls.py
    urlpatterns = patterns('',
        url(r'^', include('snippets.urls')),
    )

3. 创建Model

我们建立Snippet Model用于储存代码:

    # snippet/models.py
    from django.db import models
    from pygments.lexers import get_all_lexers
    from pygments.styles import get_all_styles

    LEXERS = [item for item in get_all_lexers() if item[1]]
    LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
    STYLE_CHOICES = sorted((item, item) for item in get_all_styles())


    class Snippet(models.Model):
        created = models.DateTimeField(auto_now_add=True)
        title = models.CharField(max_length=100, blank=True, default='')
        code = models.TextField()
        linenos = models.BooleanField(default=False)
        language = models.CharField(choices=LANGUAGE_CHOICES,
                                    default='python',
                                    max_length=100)
        style = models.CharField(choices=STYLE_CHOICES,
                                 default='friendly',
                                 max_length=100)

        class Meta:
            ordering = ('created',)

启动django服务器:

    python manage.py syncdb

4. 创建Serializer

第一步我们需要为我们的API提供序列化和反序列化的方法, 将snippet实例转为json等方式呈现数据. 我们可以使用Serializer达到这一目的, Serializer和django forms十分相似. 我们建立snippet/serializers.py文件:

    # snippets/serializers.py
    from django.forms import widgets
    from rest_framework import serializers
    from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


    class SnippetSerializer(serializers.Serializer):
        pk = serializers.Field()  # `Field` 是无类型, 只读的.
        title = serializers.CharField(required=False,
                                      max_length=100)
        code = serializers.CharField(widget=widgets.Textarea,
                                     max_length=100000)
        linenos = serializers.BooleanField(required=False)
        language = serializers.ChoiceField(choices=LANGUAGE_CHOICES,
                                           default='python')
        style = serializers.ChoiceField(choices=STYLE_CHOICES,
                                        default='friendly')

        def restore_object(self, attrs, instance=None):
            """
            创建或更新一个snippet实例, 返回该snippet实例

            如果不定义该function, 则反序列化时将返回一个包括所有field的dict
            """
            if instance:
                # 更新已存在的snippet实例
                instance.title = attrs.get('title', instance.title)
                instance.code = attrs.get('code', instance.code)
                instance.linenos = attrs.get('linenos', instance.linenos)
                instance.language = attrs.get('language', instance.language)
                instance.style = attrs.get('style', instance.style)
                return instance

            # Create new instance
            return Snippet(**attrs)

以上代码第一部分定义了序列化和反序列化的项, 第二部分restore_object function则定义了符合要求的已序列化snippet实例如何反序列化.

注意, 我们也可以使用在django form中使用的参数, 比如widget=widgets.Textarea. 这些参数可以控制serializer如何显示为HTML form, 尤其是在构建可浏览的API时, 十分有用, 我们也会在今后的博文中详细介绍.

如果想快速的构建serializer, 我们也可以使用ModelSerializer, 我们会在稍后介绍:

5. 使用serializer

在继续完成该项目前, 我们先熟悉一下serializer, 是有manage.py shell启动django shell:

    python manage.py shell

import必要的代码库并创建2个snippet实例:

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

以上代码已将snippet实例转化为Python基本数据类型, 接下来我们完成序列化:

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

反序列化也是类似的, 首先将stream转为python基本类型:

    # 根据我们使用的是 python 2 或是 python 3
    # 这一import会自动引入 `StringIO.StringIO` 或 `io.BytesIO`
    from rest_framework.compat import BytesIO

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

然后我们将它转化为snippet实例:

    serializer = SnippetSerializer(data=data)
    serializer.is_valid()
    # True
    serializer.object
    # <Snippet: Snippet object>

可见, serializer和django form 有多么相似, 当我们写view时, 这一相似性会更加明显.

当我们输入参数many=True时, serializer还能序列化queryset:

    serializer = SnippetSerializer(Snippet.objects.all(), many=True)
    serializer.data
    # [{'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中, 包含了许多与model重复的field, 那么是否能将这些代码变得更紧凑呢? 当然可以!

就像Django提供ModelForm一样, django_rest_framework提供了ModelSerializer.

我们修改之前的SnippetSerializer:

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

7. 在Views中使用Serializer

以下代码中, 为了更好的理解Serializer, 我们只使用django_rest_framework的Serializer部件和Django最基本的function_base_view.

首先创建能返回json数据的HttpResponse:

    # 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):
        """
        将内容转为JSON格式的HttpResponse
        """
        def __init__(self, data, **kwargs):
            content = JSONRenderer().render(data)
            kwargs['content_type'] = 'application/json'
            super(JSONResponse, self).__init__(content, **kwargs)

我们API的根目录是一个list view, 用于展示所有存在的snippet, 或建立新的snippet:

    # snippets/views.py
    @csrf_exempt
    def snippet_list(request):
        """
        展示所有存在的snippet, 或建立新的snippet
        """
        if request.method == 'GET':
            snippets = Snippet.objects.all()
            serializer = SnippetSerializer(snippets, many=True)
            return JSONResponse(serializer.data)

        elif request.method == 'POST':
            data = JSONParser().parse(request)
            serializer = SnippetSerializer(data=data)
            if serializer.is_valid():
                serializer.save()
                return JSONResponse(serializer.data, status=201)
            return JSONResponse(serializer.errors, status=400)

注意, 为了简便, 我们希望在POST时不使用csrf, 因此使用了csrf_exempt. 这不是通常应该做的, 而且django_rest_framework默认使用了更为安全的方式.

用于展示, 更新或删除的view:

    # snippets/views.py
    @csrf_exempt
    def snippet_detail(request, pk):
        """
        展示, 更新或删除一个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.py, 使这些view通电

    # snippets/urls.py
    from django.conf.urls import patterns, url

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

需要注意的是, 我们还有许多错误处理没有涉及, 例如提交错误的json, 使用view不支持的http method等. 同样处于更好的理解serializer的目的, 这些错误都会返回500错误页.

8. 测试

首先我们推出shell:

    quit()

启动django server:

    python manage.py runserver

在另外的终端中:

curl http://127.0.0.1:8000/snippets/

[{"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:

    curl http://127.0.0.1:8000/snippets/2/

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

Django-Rest-Framework 教程: 2. Requests 和 Responses

接着上篇的内容, 本篇中, 我们进一步介绍Django-rest-framework的其他核心部件. 首先我们来做准备工作:

Request

django-rest-framework中引入了新的Request类, 该Request继承自Django的HttpRequest, 并提供了更多灵活的request处理功能. 比如request.DATA属性便是专门用作Web API的属性, 类似于request.POST.

    request.POST  # 只处理form数据; 只接受HTML 'POST'动作.
    request.DATA  # 处理特定数据; 接受HTML 'POST', 'PUT' 和 'PATCH' 动作.
状态码 (status codes)

django-rest-framework为每一个状态码都提供了打包好的, 更为精确的状态码完整描述供我们使用, 比如HTTP_400_BAD_REQUEST.

API views

django-rest-framework为function_based_view和class_based_view分别提供了@api_view修饰器和APIView类. 这些wrapper代码, 保证了view接收Request实例, 并会自动添加context至Response中. 这些wrapper还能自动返回正确的状态码和详细信息.

1. 开始

接着之前的views.py, 在这里, 我们已经不需要JSONResponse了, 因此, 首先将其删除. 然后修改views.py中的snippet_list view:

    snippets/views.py
    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):
        """
        展示所有存在的snippet, 或建立新的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中的代码更为简洁. 可以看到代码与form API的使用十分相似, 我们还使用了内置的状态码, 使返回的response意义更为清晰.

对于单独的snippet编辑:

    @api_view(['GET', 'PUT', 'DELETE'])
    def snippet_detail(request, pk):
        """
        展示, 更新或删除一个snippet
        """
        try:
            snippet = Snippet.objects.get(pk=pk)
        except Snippet.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)

        if request.method == 'GET':
            serializer = SnippetSerializer(snippet)
            return Response(serializer.data)

        elif request.method == 'PUT':
            serializer = SnippetSerializer(snippet, data=request.DATA)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

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

从以上代码中可以看到, 我们不再指明request和response中的内容类型. request.DATA即可用来处理json数据类型类型, 也可以处理yaml或其他数据类型. 同时, django-rest-framework也能根据不同情况, 再response呈现正确的数据类型.

2. 使用其他数据格式

为了展示如何多数据格式的支持, 接下来, 我们为url添加后缀: http://example.com/api/items/4.json

为snippets/view.py中的snippet_list和snippet_detail增加format参数

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

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

更新urls.py:

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

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

    urlpatterns = format_suffix_patterns(urlpatterns)

以上代码是可选的, 实际上我们并不需要添加这些url pattern就能实现多数据格式的支持. 但添加之后使得使用时更加直观.

3. 测试

我们可以通过前篇教程中的方式测试, 获取所有snippet:

curl http://127.0.0.1:8000/snippets/

[{"id": 1, "title": "", "code": "foo = \"bar\"\n", "linenos": false, "language": "python", "style":
    "friendly"}, {"id": 2, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language":
    "python", "style": "friendly"}]

使用Accept头部信息控制response返回格式:

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

或者使用后缀控制返回格式:

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

通过修改request的Content-Type头部, 控制格式:

    # 使用form data POST
    curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 123"

    {"id": 3, "title": "", "code": "print 123", "linenos": false, "language": "python", "style": "friendly"}

    # 使用JSON POST
    curl -X POST http://127.0.0.1:8000/snippets/ -d '{"code": "print 456"}' -H "Content-Type: application/json"

    {"id": 4, "title": "", "code": "print 456", "linenos": true, "language": "python", "style": "friendly"}

或者直接在浏览器中打开:

    http://127.0.0.1:8000/snippets/

Django-Rest-Framework 教程: 3. 使用 class based views

在上一篇Django-Rest-Framework 教程: 2. Requests 和 Responses中, 使用的是function based views. 在本篇中, 主要介绍怎样使用class based views.

1. 修改views.py

首先修改snippet_list view:

    # snippets/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):
        """
        展示所有存在的snippet, 或建立新的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 based view代替 snippet_list后, 两者的代码非常相似, 但对于不同Http动作, 分离效果更为优秀. 对于snippet_detail:

    # snippets/views.py
    class SnippetDetail(APIView):
        """
        展示, 更新或删除一个snippet
        """
        def get_object(self, pk):
            try:
                return Snippet.objects.get(pk=pk)
            except Snippet.DoesNotExist:
                raise Http404

        def get(self, request, pk, format=None):
            snippet = self.get_object(pk)
            serializer = SnippetSerializer(snippet)
            return Response(serializer.data)

        def put(self, request, pk, format=None):
            snippet = self.get_object(pk)
            serializer = SnippetSerializer(snippet, data=request.DATA)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        def delete(self, request, pk, format=None):
            snippet = self.get_object(pk)
            snippet.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)

更新urls.py:

    # snippets/urls.py
    from django.conf.urls import patterns, url
    from rest_framework.urlpatterns import format_suffix_patterns
    from snippets import views

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

    urlpatterns = format_suffix_patterns(urlpatterns)

class based view改造完成, 现在可以使用之前的测试方法进行测试了.

2. 使用 Mixins

使用class based views的一大好处是, 我们可以使用各种mixin.

Django-rest-framework为我们提供了许多现成的mixins, 方便我们像使用model-backed API一样构建 "创建/获取/更新/删除" API. 我们试着使用Mixins改写原先的view.

GenericAPIView为我们提供了view核心的功能, 而ListModelMixin和CreateModelMixin为我们提供了.list()和.create()功能. 我们将这些功能与http动作绑定:

    # snippets/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, RetrieveModelMixin, UpdateModelMixin和DestroyModelMixin改写views.py:

    # snippets/views.py
    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)

3. 使用generic class based views

同Django一样, django-rest-framework为我们提供了现成的generic class based views. 接下来我们使用这些GCBVs再一次修改原有的views.py:

    # snippets/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

可以发现, 使用GCBVs后, 代码变得的更为精简易懂.

Django-Rest-Framework 教程: 4. 验证和权限

到目前为止, 我们的API并未指明哪些人有权限编辑或删除snippet, 接下来我们要实现:

  • 为snippet增加创建者
  • 特定用户才能创建snippet
  • snippet创建者才能更新或删除该snippet
  • 未授权用户只能查看

1. 为snippet model增加field

我们现为snippet model增加两个field, 一个用于储存创建者信息, 另一个则用作储存高亮HTML信息:

    # snippets/models.py
    from django.db import models
    from pygments.lexers import get_all_lexers
    from pygments.styles import get_all_styles

    LEXERS = [item for item in get_all_lexers() if item[1]]
    LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
    STYLE_CHOICES = sorted((item, item) for item in get_all_styles())


    class Snippet(models.Model):
        created = models.DateTimeField(auto_now_add=True)
        title = models.CharField(max_length=100, blank=True, default='')
        code = models.TextField()
        linenos = models.BooleanField(default=False)
        language = models.CharField(choices=LANGUAGE_CHOICES,
                                    default='python',
                                    max_length=100)
        style = models.CharField(choices=STYLE_CHOICES,
                                 default='friendly',
                                 max_length=100)
        owner = models.ForeignKey('auth.User', related_name='snippets')
        highlighted = models.TextField()

        class Meta:
            ordering = ('created',)

当执行save()时, 我们使用pygments生成高亮后的HTML:

    # snippets/models.py
    from pygments.lexers import get_lexer_by_name
    from pygments.formatters.html import HtmlFormatter
    from pygments import highlight

    class Snippet(models.Model):

        ...

        def save(self, *args, **kwargs):
            """
            使用pygments创建高亮的HTML文本
            """
            lexer = get_lexer_by_name(self.language)
            linenos = self.linenos and 'table' or False
            options = self.title and {'title': self.title} or {}
            formatter = HtmlFormatter(style=self.style, linenos=linenos,
                                      full=True, **options)
            self.highlighted = highlight(self.code, lexer, formatter)
            super(Snippet, self).save(*args, **kwargs)

修改完毕之后我们删除原来的数据库, 然后重新创建数据库:

    python ./manage.py syncdb

你可能需要再创建几个用户, 可以通过以下命令创建:

    python ./manage.py createsuperuser

2. 为User model增加endpoints

在serializer.py中增加UserSerializer:

    # snippets/serializers.py
    from django.contrib.auth.models import User

    class UserSerializer(serializers.ModelSerializer):
        snippets = serializers.PrimaryKeyRelatedField(many=True)

        class Meta:
            model = User
            fields = ('id', 'username', 'snippets')

由于"snippets"是User的一个反向关系, 因此我们需要用field明确指明.

我们只需要为user model添加只读的API, 因此使用ListAPIView和RetrieveAPIView即可:

    # snippets/views.py
    from django.contrib.auth.models import User
    from snippets.serializers import UserSerializer


    class UserList(generics.ListAPIView):
        queryset = User.objects.all()
        serializer_class = UserSerializer


    class UserDetail(generics.RetrieveAPIView):
        queryset = User.objects.all()
        serializer_class = UserSerializer

最后修改urls.py, 添加users:

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

3. 关联user和snippet

如果现在我们通过API创建snippet, 我们无法将创建者将其关联起来. 因为创建者信息并不是以serialized数据传进来的, 而是通过request获取的.

我们的解决方法是重写SnippetList和SnippetDetail view的pre_save()方法. 该方法允许我们处理request或request URL中的任何信息.

    # snippets/views.py
    class SnippetList(APIView):

        ...

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


    class SnippetDetail(APIView):

        ...

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

4. 更新 serializer

现在snippet已经与创建者关联, 接下来我们修改serializer来反映该变化:

# snippets/serializers.py
class SnippetSerializer(serializers.ModelSerializer):
    owner = serializers.Field(source='owner.username')

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

ower field十分有趣, source参数控制着使用snippet哪个attribute作为来源填充该field, 并且能使用"."语法.

Field类, 相对于其他field类(CharField, BooleanField等), 是只读field. 它能用作呈现序列化的数据, 但不会在凡序列化时被用做更新数据.

5. 添加权限

到此为止, snippet已经与user关联了. 接下来我们实现只有授权用户才能创建, 更新和删除snippet.

Django REST Framework为我们提供了许多permission类. 在此, 我们使用IsAuthenticatedOrReadOnly, 它能保证只有授权用户才有读写权限, 而一般用户只有读得权限:

    # snippets/views.py
    from rest_framework import permissions
    class SnippetList(APIView):

        ...

        permission_classes = (permissions.IsAuthenticatedOrReadOnly,)


    class SnippetDetail(APIView):

        ...

        permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

6. 增加可浏览的授权API

在tutorial/urls.py中:

    # 在tutorial/urls.py

    urlpatterns = patterns('',
                       ...
    )

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

r'^api-auth/'可以是你能想得到的所有pattern. 到此你可以使用/api-auth/进行登录了. 登录成功之后你才能创建snippet.

添加好snippet之后, 在浏览/users/ endpoint, 你可以发现每个用户下都有对应的snippet的pk.

7. 添加对象权限

接下来, 我们实现只有snippet的创建者才能更新, 编辑或删除snippet, 创建snippets/permissions.py:

    # snippets/permissions.py
    from rest_framework import permissions


    class IsOwnerOrReadOnly(permissions.BasePermission):
        """
        允许创建者编辑的自定义权限
        """

        def has_object_permission(self, request, view, obj):
            # 任何request都有只读权限, 所以总是允许GET, HEAD 或 OPTIONS
            if request.method in permissions.SAFE_METHODS:
                return True

            # 只有snippet的创建者有写的权限
            return obj.owner == request.user

修改SnippetDetail view:

    # snippets/views.py
    from snippets.permissions import IsOwnerOrReadOnly


    class SnippetDetail(APIView):

        ...

        permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                      IsOwnerOrReadOnly,)

8. 使用API授权

目前为止, 我们没有设置 authentication classes, 因此 authentication classes 还是默认的SessionAuthentication和BasicAuthentication.

当我们使用浏览器时, 我们可以通过浏览器登录并使用session授权接下来的request.

但当我们使用程序调用API时, 我们必须为每次request提供授权信息:

    curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 789" -u tom:password

    {"id": 5, "owner": "tom", "title": "foo", "code": "print 789", "linenos": false, "language": "python", "style": "friendly"}

Django-Rest-Framework 教程: 5. 提高关联性和超链接API

到目前为止, API之间的关系是以主键形式体现的, (比如打开/users/1/, 可以看到snippets中为snippet的主键). 在本篇中, 我们将使用超链接的形式, 进一步提高API的关联程度和可发现性. 首先我们补充一些路径, 是整个API结构更为完整.

1. 根路径

现在, 我们已经有了users和snippets的路径, 但对于API本身却没有一个根路径. 我们使用@api_view修饰器来创建一个function based view作为根路径:

    # snippets/views.py
    from rest_framework import renderers
    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)
        })

注意, 我们使用了django-rest-framework的reverse(), 而不是django自带的reverse(), 此时如果打开http://127.0.0.1/8000, 会报错, 因为我们还没有为url添加name, 稍后我们会添加.

在urls.py中添加对应的路径:

    # snippets/urls.py
    url(r'^$', views.api_root),

2. 高亮snippet路径

我们还需要提供高亮snippet的路径. 当然这一路径与其他不同, 我们希望使用HTML而不是JSON来呈现. Django-rest_framework为我们提供了两种方式呈现HTML, 一种是使用模板, 另一种则是已构建好的HTML文本. 由于在创建snippet时, 我们已经使用pygments将高亮的snippet转化为HTML文本储存在数据库中, 我们使用第二种方式.

由于我们返回的并不是一个object实例, 而是一个实例的某个属性, django-rest-framework没有提供该generic class based view. 因此我们需要使用基本的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)

在urls.py中添加对应的路径:

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

3. 使用超链接

处理各部件API之间的关系是一件头痛的事. django-rest_framework为我们提供了以下这些方法来表现关系:

  • 使用主键
  • 使用超链接
  • 使用相关项的slug field
  • 使用相关项的默认文本信息
  • 将子项显示在母项中
  • 其他表现方式

这次, 我们使用超链接的方式来体现user和snippet的关系. 为了实现这一方式, 我们需要改写序列器(serializers), 使用HyperlinkedModelSerializer代替原本的ModelSerializer. HyperlinkedModelSerializer相对于ModelSerializer具有以下不同:

  • HyperlinkedModelSerializer不会自动包含pk field
  • HyperlinkedModelSerializer会自动包括url field
  • 关系使用的是HyperlinkedRelatedField而不是PrimaryKeyRelatedField
    # snippets/serializers.py
    class SnippetSerializer(serializers.HyperlinkedModelSerializer):
        owner = serializers.Field(source='owner.username')
        highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')

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


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

        class Meta:
            model = User
            fields = ('url', 'username', 'snippets')

注意, 我们同时添加了 "highlight" field, 它与url field使用的一样, 是HyperlinkedRelatedField, 但指向的是snippet-highlight url而不是snippet-detail url.

由于我们在url中包含了格式信息, 我们使用format='html'参数为highlight指定.html后缀.

4. 为url添加name

编辑snippets/urls.py如下:

    # snippets/urls.py
    # API 路径
    urlpatterns = format_suffix_patterns(patterns('snippets.views',
        url(r'^$', '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')
    ))

    # 可浏览式登录API
    urlpatterns += patterns('',
        url(r'^api-auth/', include('rest_framework.urls',
                                   namespace='rest_framework')),
    )

5. 分页

如果数据库中的数据达到一定程度, 那么用户使用api时可能会返回大量数据, 因此, 我们最好使用分页功能:

我们可以使用django-rest-framework自带的设置选项, 使list自动使用分页:

    # tutorial/settings.py
    REST_FRAMEWORK = {
        'PAGINATE_BY': 10
    }

6. 测试

现在可以使用之前提到的方法测试我们的API了, 可以在/snippets/中看到, 原先的id已经被url代替, url和highlight field都采用超链接的形式.

Django-Rest-Framework 教程: 6. ViewSets 和 Routers

django-rst-framework为我们提供了ViewSet类, ViewSet为我们提供了默认的URL结构, 使得我们能更专注于API本身. ViewSet类与View类几乎是相同的, 但提供的是read或update, 而不是http动作get或put.

ViewSet类在实例化后, 通过Router类, 最终将URL与ViewSet方法绑定. 接下来我们使用ViewSet代替之前的View.

1. 使用ViewSet

将UserList和UserDetail移除, 并替换为UserViewSet:

    # snippets/views.py
    class UserViewSet(viewsets.ReadOnlyModelViewSet):
        """
        这一viewset提供了`list`和`detail`
        """
        queryset = User.objects.all()
        serializer_class = UserSerializer

我们使用ReadOnlyModelViewSet提供默认的只读权限, queryset和serializer_class设置还是未变.

将SnippetList, SnippetDetail和SnippetHighlight移除, 替换为SnippetViewSet:

    # snippets/views.py
    from rest_framework.decorators import link

    class SnippetViewSet(viewsets.ModelViewSet):
        """
        这一viewset提供了`list`, `create`, `retrieve`, `update` 和 `destroy`

        但我们需要自己设置 `highlight`.
        """
        queryset = Snippet.objects.all()
        serializer_class = SnippetSerializer
        permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                              IsOwnerOrReadOnly,)

        @link(renderer_classes=[renderers.StaticHTMLRenderer])
        def highlight(self, request, *args, **kwargs):
            snippet = self.get_object()
            return Response(snippet.highlighted)

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

我们使用ModelViewSet类实现读写操作API. 我们使用了@link修饰器来创建自定义的highlight动作, 这一修饰器可以用于创建非标准路径. @link修饰器对应的是get request, 如果想对应post request, 可以使用@action修饰器.

2. 明确关联URL

为了更清楚ViewSet内部发生的事情, 我们首先在urls.py中使用明确指明的关联:

    # snippets/urls.py
    from snippets.views import SnippetViewSet, UserViewSet
    from rest_framework import renderers

    snippet_list = SnippetViewSet.as_view({
        'get': 'list',
        'post': 'create'
    })
    snippet_detail = SnippetViewSet.as_view({
        'get': 'retrieve',
        'put': 'update',
        'patch': 'partial_update',
        'delete': 'destroy'
    })
    snippet_highlight = SnippetViewSet.as_view({
        'get': 'highlight'
    }, renderer_classes=[renderers.StaticHTMLRenderer])
    user_list = UserViewSet.as_view({
        'get': 'list'
    })
    user_detail = UserViewSet.as_view({
        'get': 'retrieve'
    })

    urlpatterns = format_suffix_patterns(patterns('snippets.views',
    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')
))

3. 使用Router类自动关联URL

Router类可以轻松的帮我们实现URL和ViewSet之间的关联, 我们先注释掉snippets/urls.py中所有内容, 然后重新改写tutorial/urls.py:

    # tutorial/urls.py
    from django.conf.urls import patterns, url, include
    from snippets import views
    from rest_framework.routers import DefaultRouter

    # 黄建router并注册viewset.
    router = DefaultRouter()
    router.register(r'snippets', views.SnippetViewSet)
    router.register(r'users', views.UserViewSet)

    # router会自动生成url
    # 我们只需要额外提供可浏览性登入API
    urlpatterns = patterns('',
        url(r'^', include(router.urls)),
        url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
    )

4. View类和ViewSet类

使用ViewSet类为我们提供了统一的URL地址, 减少了代码的量, 是我们更专注于API本身.

但这不代表一定要使用ViewSet类, ViewSet相对于View更为不明确. 所以在使用中需要特别注意

5. 测试

接下来可以使用之前的方式测试我们的API了.


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值