[转]Django REST framework 简介与中文教程

技术_编程语言 专栏收录该内容
128 篇文章 0 订阅

 

Django REST framework 简介与中文教程

 

简介

 

  1. 在序列化与反序列化时,虽然操作的数据不尽相同,但是执行的过程却是相似的,也就是说这部分代码是可以复用简化编写的。
  2. 在开发REST API的视图中,虽然每个视图具体操作的数据不同,但增、删、改、查的实现流程基本套路化,所以这部分代码也是可以复用简化编写的:
    • :校验请求数据–>执行反序列化过程–>保存数据库–>将保存的对象序列化并返回;
    • :判断要删除的数据是否存在–>执行数据库删除
    • :判断要修改的数据是否存在–>校验请求的数据–>执行反序列化过程–>保存数据库–>将保存的对象序列化并返回
    • :查询数据库–>将数据序列化并返回

 

Django REST framework可以帮助我们简化上述两部分的代码编写,大大提高REST API的开发速度

 

认识Django REST framework

 

Django REST framework 框架是一个用于构建Web API的强大而又灵活的工具。

 

通常简称为DRF框架 或 REST framework

 

DRF框架是建立在Django框架基础之上,由Tom Christie大牛二次开发的开源项目。

 

特点

 

  • 提供了定义序列化器Serializer的方法,可以快速根据Django ORM 或者其它库自动序列化/反序列化;
  • 提供了丰富的类视图、Mixin扩展类,简化视图的编写;
  • 丰富的定制层级:函数视图、类视图、视图集合到自动生成API,满足各种需要;
  • 多种身份认证和权限认证方式的支持;
  • 内置了限流系统;
  • 直观的API web界面;
  • 可扩展性,插件丰富

 

扩展知识

 

request.POST 只处理表单数据,只适用于’POST’方法;

 

request.data 处理任意数据。适用于’POST’、‘PUT’、'PATCH’方法。

 

装饰API视图

 

REST framework提供了两个用于装饰API视图的装饰器

 

  1. @api_view装饰器基于函数视图的装饰器;
  2. APIView类用于类视图;

 

这些装饰器提供了一些功能,比如确保在视图中接收请求实例,并向响应对象添加上下文,以便执行内容协商。

 

装饰器还提供了一些行为,比如在适当的时候返回不允许响应的405方法,以及处理访问请求时发生的任何ParseError异常,输入格式不正确的数据。

 

教程1-Serialization(序列化)

 

简介

 

本教程将介绍如何创建一个高亮显示的pastebin代码的Web API,在此过程中,它将介绍构成REST框架的各种组件,并让您全面了解所有内容是如何组合在一起的。

 

本教程相当深入,所以在开始之前,您应该先吃一块饼干,喝一杯您最喜欢的啤酒。如果你只是想要一个快速的概述,你应该去看一下quickstart文档。

 


 

注意:本教程的代码可以在GitHub上的tomchristie/rest-framework-tutorial知识库中找到。完成的实现也可在线作为测试的沙箱版本,可在此处获得

 


 

建立一个新的环境

 

在我们做任何其他事情之前,我们将使用**virtualenv创建一个新的虚拟环境。这将确保我们的包配置**与我们正在进行的任何其他项目保持良好的隔离。

 

virtualenv env
source env/bin/activate

 

现在我们在virtualenv环境中,可以安装我们的需求包。

 

pip install django
pip install djangorestframework
pip install pygments  # 我们将使用它来高亮显示代码

 

注意:要随时退出virtualenv环境,只需键入deactivate。有关更多信息,请参见**virtualenv documentation**。

 

准备开始

 

OK,我们准备好编码了;首先,让我们创建一个新的项目。

 

cd ~
django-admin startproject tutorial
cd tutorial

 

完成这些之后,我们就可以创建一个应用程序(app)来创建一个简单的Web API

 

python manage.py startapp snippets

 

我们需要将新的snippets应用程序rest_framework应用程序添加到INSTALLED_APPS。让我们编辑tutorial/settins.py文件。

 

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

 

OK,我们准备好了。

 

创建要使用的model(模型)

 

出于本教程的目的,我们将首先创建一个用于存储snippets(代码片段)的简单的Snippet模型。继续编辑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',)		# 通过创建时间进行排序

 

我们还需要为我们的Snippet模型创建初始迁移,并首次同步数据库。

 

python manage.py makemigrations snippets
python manage.py migrate

 

创建一个Serializer

 

首先,我们需要在Web API上提供一种将代码片段实例序列化和反序列化为json等表示形式的方法。我们可以通过声明与Django的表单(forms)非常相似的序列化器来实现这一点。在snippets目录中创建一个名为serializers.py的文件,并添加一下内容:

 

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


class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):
        """
        给定经过验证的数据,创建并返回一个新的`Snippet`实例
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
       	给定已验证的数据,更新并返回现有的`Snippet`实例
        """
        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.linenos = validated_data.get('linenos', instance.linenos)
        instance.language = validated_data.get('language', instance.language)
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance

 

序列化器类的第一部分定义得到**serialized/deserialized(序列化/反序列化)**的字段;**create()update()方法定义了在调用serializer.save()**时如何创建或修改完全成熟的实例。

 

**Serializer(序列化器)类与Django的Form(表单)**类非常相似,并且在各个字段上包含类似的验证标志,比如:required、max_length和default

 

字段表示还可以控制Serializer(序列化器)在某些情况下应该如何显示,比如呈现到HTML时;{‘base_template’: ‘textarea.html’}上面的标志相当于在Django的Form类上使用widget=widgets.Textarea。这对于控制可浏览API的显示方式特别有用,我们将在本教程后面看到。

 

我们实际上也可以通过使用ModelSerializer类来节省一些时间,我们稍后会看到,但是现在我们将保持明确的序列化器定义。

 

使用Serializers(序列化器)

 

在继续之前,我们将熟悉如何使用新的**Serializer(序列化器)**类。让我们进入Django shell:

 

python manage.py shell

 

OK,当我们完成了一些导入之后,让我们创建一些代码片段来使用。

 

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser

snippet = Snippet(code='foo = "bar"\n')
snippet.save()

snippet = Snippet(code='print("hello, world")\n')
snippet.save()

 

现在我们有了一些代码片段实例。让我们来看看序列化其中一个实例:

 

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

 

此时,我们已经将模型实例转换为Python原生数据类型。为了完成序列化过程,我们将数据呈现为json

 

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

 

反序列是相似的,首先,我们将**stream(流)**解析为Python原生数据类型…

 

import io

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

 

…然后,我们将这些原生数据类型恢复到一个完全填充的对象实例中:

 

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处理表单有多么相似;当我们开始编写使用序列化器的**views(视图)**时,这种相似性会变得更加明显。

 

我们还可以序列化querysets而不是模型实例;为此,我们只需向Serializer(序列化器)参数添加一个many=True标志:

 

serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [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')])]

 

使用ModelSerializers(模型序列化器)

 

我们的SnippetSerializer类正在复制Snippet模型中包含的大量信息;如果我们能让代码更简洁一些就好了。

 

与Django同事提供Form类和ModelForm类一样,REST框架也包含Serializer类和ModelSerializer类。

 

让我们看看如何使用ModelSerializer类重构我们的序列化器;在此打开文件snippets/serializers.py,并使用一下代码替换SnippetSerializer类:

 

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

 

序列化器有一个很好的属性,您可以通过打印它的表示形式来检查序列化器实例中的所有字段;使用python manage.py shell打开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类并没有什么特别神奇的功能,它们只是创建序列化器类的快捷方式:

 

  • 一组自动确定的字段;
  • create()update()方法的简单默认实现。

 

使用我们的序列化器编写常规的Django视图

 

让我们看看如何使用新的序列化器类来编写一些API视图;目前,我们不会使用REST框架的任何其他特性,我们只将视图编写为普通的Django视图。

 

编辑snippets/views.py文件,并添加一下内容:

 

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):
    """
    列出所有的代码片段,或者创建一个新的代码片段
    """
    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)

 

注意,由于我们希望能够从没有CSRFtoken的客户端向这个视图发送POST请求,所以需要将该视图标记为csrf_exempt;这不是您通常想要做的事情,REST框架视图实际上使用了比这更合理的行为,但是对于我们的目的,现在这样做。

 

我们还需要一个对应于单个代码片段的视图,该视图可用于检索(retrieve)更新(update)删除(delete)代码片段:

 

@csrf_exempt
def snippet_detail(request, pk):
    """
    检索、更新或删除一个代码片段
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

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

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return JsonResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

 

最后,我们需要将这些视图连接起来;创建snippets/urls.py文件:

 

from django.urls import path
from snippets import views

urlpatterns = [
    path('snippets/', views.snippet_list),
    path('snippets/<int:pk>/', views.snippet_detail),
]

 

我们还需要在tutorial/urls.py文件中连接根urlconf,以包含snippet应用程序的URLs:

 

urlpatterns = [
    path('', include('snippets.urls')),
]

 

值得注意的是,有几个边缘情况我们目前没有正确处理;如果我们发送格式不正确的json,或者使用视图无法处理的方法发出请求,那么我们将得到一个500"server error"响应;不过,现在这样就可以了。

 

测试我们对Web API的第一次尝试

 

现在,我们可以启动一个示例服务器,它为我们的snippets(代码片段)提供服务。

 

退出shell…

 

quit()

 

…并启动Django的开发服务器:

 

python manage.py runserver

Validating models...

0 errors found
Django version 1.11, using settings 'tutorial.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

 

在另一个终端窗口中,我们可以测试服务器。

 

我们可以使用curlhttpie来测试我们的API。Httpie是一个用Python编写的用户友好的http客户机。让我们安装它。

 

你可以使用pip安装httpie

 

pip install httpie

 

最后,我们可以得到所有代码片段的列表:

 

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来获取特定代码片段:

 

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浏览器中访问这些URL来显示相同的json。

 

我们现在在哪里

 

到目前为止,我们做得还不错,我们有一个序列化API,感觉非常类似于Django的表单API,以及一些常规的Django视图。

 

目前,我们的API视图除了提供json响应之外,没有做任何特别的事情,还有一些我们仍想清理的错误处理边缘情况,但它是一个正常运行的Web API。

 

我们将在本教程的第2部分中看到如何开始改进。

 

教程2-Requests(请求)和Response(响应)

 

从这一点开始,我们将真正开始涵盖REST框架的核心;让我们介绍几个基本构建块。

 

请求对象

 

REST框架引入了一个扩展常规HttpRequestRequest(请求)对象,并提供了更灵活的请求解析。请求对象的核心功能是request.data属性,类似于request.POST,但对于处理Web API更有用。

 

request.POST  # 只处理表单数据,只适用于'POST'方法;

request.data   # 处理任意数据。适用于'POST'、'PUT'、'PATCH'方法。

 

响应对象

 

REST框架还引入了一个响应对象,这是一种TemplateResponse类型,它接受未呈现的内容,并使用内容协商来确定要返回给客户端的正确内容类型。

 

return Response(data)  # 根据客户端请求呈现内容类型

 

状态代码(Status codes)

 

在视图中使用数值HTTP状态码并不总是很明显,而且如果出现错误代码,很容易就不会注意到。REST框架为每个状态代码提供了更显式的标识符,例如状态模块中的HTTP_400_BAD_REQUEST。始终使用这些标识符而不是使用数字标识符是一个好主意。

 

装饰API views(装饰API视图)

 

REST framework提供了两个用于装饰API视图的装饰器

 

  1. @api_view装饰器基于函数视图的装饰器;
  2. APIView类用于类视图;

 

这些装饰器提供了一些功能,比如确保在视图中接收请求实例,并向**Response(响应)**对象添加上下文,以便执行内容协商。

 

装饰器还提供了一些行为,比如在适当的时候返回405 Method Not Allowed,以及处理使用格式错误的输入进行ParseError访问时发生的任何异常request.data

 

把它们放在一起

 

OK,让我们继续并开始使用这些新组件来编写一些视图。

 

我们不再需要views.py中的JSONResponse类,所以删除它。完成之后,我们可以开始稍微重构视图。

 

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):
    """
    列出所有的代码片段,或者创建一个新的代码片段
    """
    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)

 

我们的实例视图是对前一个示例的改进。它更加简洁,代码现在感觉非常类似于我们使用表单(Forms)API时的感觉。我们还使用了命名状态代码,这使得响应的含义更加明显。

 

下面是views.py模块中单个代码片段的视图。

 

@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
    检索,更新或删除一个代码片段
    """
    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)

 

这应该都非常熟悉 - 与使用常规Django视图没有太大区别。

 

请注意,我们不再明确地将我们的请求或响应绑定到给定的内容类型。 request.data可以处理传入的json请求,但它也可以处理其他格式。类似地,我们返回带有数据的响应对象,但允许REST框架将响应呈现给我们正确的内容类型。

 

在我们的URL中添加可选的格式后缀

 

为了使我们的响应不再硬连接到单个内容类型这一事实,我们将API格式后缀添加到API端点。使用格式后缀为我们提供了明确引用给定格式的URL,这意味着我们的API将能够处理诸如http://example.com/api/items/4.json之类的 URL 。

 

首先向这两个视图添加一个format关键字参数,就像这样:

 

def snippet_list(request, format=None):

 

 

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

 

现在稍微更新snippets/urls.py文件,在现有URLs之外附加一组format_suffix_patterns

 

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    path('snippets/', views.snippet_list),
    path('snippets/<int:pk>', views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

 

我们不一定需要添加这些额外的url模式,但它为我们提供了一种简单,干净的方式来引用特定的格式。

 

去测试

 

继续从命令行测试API,正如我们在教程第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"
  }
]

 

我们可以控制返回的响应的格式,或者使用Accept头部(header):

 

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

 

或附加格式后缀:

 

http http://127.0.0.1:8000/snippets.json  # JSON 后缀
http http://127.0.0.1:8000/snippets.api   # 可浏览 API 后缀

 

类似地,我们可以使用Content-Type头控制发送的请求格式:

 

# POST 使用表单数据
http --form POST http://127.0.0.1:8000/snippets/ code="print(123)"

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

# POST 使用JSON
http --json POST http://127.0.0.1:8000/snippets/ code="print(456)"

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

 

如果向上面的http请求添加一个--debug开关,您将能够在请求头中看到请求类型。

 

现在,通过访问http://127.0.0.1:8000/snippets/,在web浏览器中打开API。

 

随机索得率

 

由于API根据客户端请求选择响应的内容类型,所以在web浏览器请求资源时,它将默认返回资源的html格式表示。这允许API返回一个完全可web浏览的HTML表示。

 

拥有一个web可浏览的API是一个巨大的可用性胜利,它使开发和使用您的API变得更加容易。它还极大地降低了其他希望检查和使用您的API的开发人员的进入壁垒。

 

有关可浏览api特性以及如何自定义的更多信息,请参阅browsable api

 

接下来是什么?

 

在教程第3部分中,我们将开始使用基于类的视图,并查看通用视图如何减少我们需要编写的代码量。

 

教程3-基于类的视图

 

我们还可以使用基于类的视图而不是基于函数的视图来编写API视图。我们将看到这是一个强大的模式,允许我们重用常用功能,并帮助我们保持代码使用基于类的视图重写API

 

我们首先将根视图重写为一个基于类的视图。所有这些都涉及到一点对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):
    """
    列出所有的代码片段,或者创建一个新的代码片段
    """
    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方法之间有更好的分离。我们还需要更新views.py中的实例视图。

 

class SnippetDetail(APIView):
    """
    检索,更新或删除一个代码片段实例
    """
    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)

 

OK,它还是很像基于函数的视图。

 

既然使用了基于类的视图,我们还需要稍微重构snippets/urls.py

 

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    path('snippets/', views.SnippetList.as_view()),
    path('snippets/<int:pk>/', views.SnippetDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)

 

OK,如果您运行开发服务器,那么一切都应该像以前一样工作。

 

使用mixins

 

使用基于类的视图的一大好处是,它允许我们轻松地组合可重用的行为。

 

到目前为止,我们使用的create/retrieve/update/delete操作对于我们创建的任何模型支持的API视图都非常类似。这些公共行为是在REST框架的mixin类中实现的。

 

让我们看看如何使用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)

 

我们将花一点时间研究到底发生了什么。我们使用GenericAPIView构建视图,并添加ListModelMixinCreateModelMixin

 


 

这是mixins.ListModelMixin的源码:

 

class ListModelMixin(object):
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

 

这是mixins.CreateModelMixin源码:

 

class CreateModelMixin(object):
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

 

generics.GenericAPIView类继承了views.APIView类;

 


 

基类提供核心功能,mixin类提供.list().create()操作;然后明确地将get()post()方法绑定到适当的操作;到目前为止已经足够简单了。

 

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)

 

相似地,我们再次使用GenericAPIView类来提供核心功能,并添加mixins来提供.retrieve()、.update()和.destroy()操作。

 


 

这是RetrieveModelMixin源码:

 

class RetrieveModelMixin(object):
    """
    Retrieve a model instance.
    """
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

 

这是UpdateModelMixin源码:

 

class UpdateModelMixin(object):
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

 

这是DestroyModelMixin源码:

 

class DestroyModelMixin(object):
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()

 

generics.GenericAPIView类继承了views.APIView类;

 


 

使用通用的基于类的视图

 

使用mixin类,我们重写了视图,比以前使用的代码稍微少一些,但是我们可以更进一步。REST框架提供了一组已经混合在一起的通用视图,我们可以使用这些视图来进一步简化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

 


 

这是ListCreateAPIView源码:发现它其实是继承了那两个mixins类和一个基类

 

class ListCreateAPIView(mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        GenericAPIView):
    """
    Concrete view for listing a queryset or creating a model instance.
    """
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

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

 

这是RetrieveUpdateDestroyAPIView源码:同样的,也是继承了三个mixins类和一个基类

 

class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
                                   mixins.UpdateModelMixin,
                                   mixins.DestroyModelMixin,
                                   GenericAPIView):
    """
    Concrete view for retrieving, updating or deleting a model instance.
    """
    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 patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

 


 

哇,太简洁了。我们已经免费获得了大量的资源,我们的代码看起来很好,干净,符合Django的习惯。

 

接下来,我们将进入本教程的第4部分,在这里我们将了解如何处理API的身份验证和权限。

 

教程4-Authentication(身份验证) 和 Permissions(权限)

 

目前,我们的API谁都可以编辑或删除代码段没有任何限制。我们希望有一些更高级的行为,以确保:

 

  • 代码段始终与创建者相关联。
  • 只有经过身份验证的用户才能创建摘要。
  • 只有代码段的创建者可以更新或删除它。
  • 未经身份验证的请求应具有完全只读访问权限。

 

向模型添加信息

 

我们将对Snippet模型类做一些更改。首先,让我们添加几个字段。其中一个字段将用于表示创建代码片段的用户。另一个字段将用于存储代码的高亮显示的HTML表示。

 

将以下两个字段添加到models.py中的Snippet模型中。

 

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

 

我们还需要确保在保存模型时,使用pygments代码高亮显示库填充高亮显示的字段。

 

我们需要一些额外的导入:

 

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

 

现在我们可以在模型类中添加一个.save()方法:

 

def save(self, *args, **kwargs):
    """
    使用“pygments”库创建高亮显示的HTML代码片段的表示。
    """
    lexer = get_lexer_by_name(self.language)
    linenos = 'table' if self.linenos else False
    options = {'title': self.title} if self.title else {}
    formatter = HtmlFormatter(style=self.style, linenos=linenos,
                              full=True, **options)
    self.highlighted = highlight(self.code, lexer, formatter)
    super(Snippet, self).save(*args, **kwargs)

 

完成这些之后,我们需要更新数据库表。通常我们会创建一个数据库迁移来实现这一点,但是出于本教程的目的,让我们删除数据库并重新开始。

 

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

 

您可能还想创建几个不同的用户,用于测试API。最快的方法是使用createsuperuser命令。

 

python manage.py createsuperuser

 

为我们的User模型添加端点

 

现在我们已经有了一些用户,我们最好将这些用户的表示添加到我们的API中。创建一个新的序列化器很容易。在serializers.py添加:

 

from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

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

 

因为“snippets”是User模型上的反向关系,所以在使用ModelSerializer类时,默认情况下不会包含它,所以我们需要为它添加一个显式字段。

 

我们还将向views.py添加几个视图。我们希望仅为用户表示使用只读视图,因此我们将使用ListAPIViewRetrieveAPIView通用的基于类的视图。

 

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

 

还要确保导入UserSerializer类:

 

from snippets.serializers import UserSerializer

 

最后,我们需要通过从URL conf引用这些视图,将这些视图添加到API中。

 

path('users/', views.UserList.as_view()),
path('users/<int:pk>/', views.UserDetail.as_view()),

 

将Snippets与Users关联

 

现在,如果我们创建了一个代码片段,就没有办法将创建代码片段的用户与代码片段实例关联起来。用户不是作为序列化表示的一部分发送的,而是作为传入请求的属性发送的。

 

我们处理这个问题的方法是在代码片段视图上重写.perform_create()方法,该方法允许我们修改实例保存的管理方式,并处理传入请求或请求URL中隐含的任何信息。

 

SnippetList视图类中,添加以下方法:

 

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

 

我们的序列化器的create()方法现在将传递一个附加的“owner”字段,以及来自请求的经过验证的数据。

 

更新我们的序列化器

 

现在代码片段与创建它们的用户相关联,让我们更新SnippetSerializer来反映这一点。将以下字段添加到serializer.py中的序列化器定义中:

 

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

 

注意:确保您还将“owner”添加到内部元类中的字段列表中:

 

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

 

这个字段正在做一些非常有趣的事情。source参数控制用于填充字段的属性,并可以指向序列化实例上的任何属性。它还可以使用上面所示的.(点)符号,在这种情况下,它将遍历给定的属性,方法与Django模板语言中使用的方法类似。

 

我们添加的字段是无类型ReadOnlyField类,而其他类型的字段,如CharField、BooleanField等……无类型的ReadOnlyField始终是只读的,将用于序列化表示,但在反序列化模式实例时不用于更新它们;我们也可以在这里使用CharField(read_only=True)。

 

向视图添加所需的权限

 

现在代码片段与用户相关联,我们希望确保只有经过身份验证的用户才能创建、更新和删除代码片段。

 

REST框架包含许多权限类,我们可以使用它们来限制谁可以访问给定的视图。在本例中,我们正在寻找的是IsAuthenticatedOrReadOnly,它将确保经过身份验证的请求获得读写访问,而未经身份验证的请求获得只读访问。

 

首先在views模块中添加以下导入:

 

from rest_framework import permissions

 

然后,将以下属性添加到SnippetListSnippetDetail视图类中:

 

permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

 

向可浏览API添加登录

 

如果您现在打开浏览器并导航到browsable API,您将发现您不再能够创建新的代码片段。为了做到这一点,我们需要能够以用户身份登录。

 

通过在项目级的urls.py文件中编辑URLconf,我们可以添加一个login视图,以便与可浏览的API一起使用。

 

在文件顶部添加以下导入:

 

from django.conf.urls import include

 

在文件的末尾,添加一个模式来包含可浏览API的loginlogout视图。

 

urlpatterns += [
    path('api-auth/', include('rest_framework.urls')),
]

 

模式的'api-auth/'部分实际上可以是您想使用的任何URL。

 

现在,如果您再次打开浏览器并刷新页面,您将在页面右上角看到一个“Login”链接。如果您作为前面创建的用户之一登录,您将能够再次创建代码片段。

 

创建了几个代码片段后,导航到“/users/”端点,注意,在每个用户的“snippets”字段中,表示包含与每个用户关联的代码片段(snippet) id列表。

 

对象级权限

 

实际上,我们希望所有代码片段对任何人都可见,但也要确保只有创建代码片段的用户才能更新或删除它。

 

为此,我们需要创建一个自定义权限。

 

snippets应用程序中,创建一个新文件permissions.py

 

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    自定义权限,只允许对象的所有者编辑它。
    """

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

        # 写权限只允许给代码片段的所有者。
        return obj.owner == request.user

 

现在,通过编辑SnippetDetail视图类上的permission_classes属性,我们可以将自定义权限添加到代码片段实例端点:

 

permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                      IsOwnerOrReadOnly,)

 

还要确保导入IsOwnerOrReadOnly类。

 

from snippets.permissions import IsOwnerOrReadOnly

 

现在,如果您再次打开浏览器,如果您是以创建代码片段的相同用户登录的,您会发现“DELETE”和“PUT”操作只出现在代码片段实例端点上。

 

使用API进行身份验证

 

因为我们现在对API有一组权限,如果我们想编辑任何代码片段,就需要对请求进行身份验证。我们没有设置任何身份验证类,因此当前应用的是默认值,即SessionAuthenticationBasicAuthentication

 

当我们通过web浏览器与API交互时,我们可以登录,然后浏览器会话将为请求提供所需的身份验证。

 

如果以编程方式与API交互,则需要明确地为每个请求提供身份验证凭据。

 

如果我们试图创建一个没有认证的代码片段,我们会得到一个错误:

 

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

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

 

通过包含前面创建的用户的用户名和密码,我们可以成功地发出请求。

 

http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"

{
    "id": 1,
    "owner": "admin",
    "title": "foo",
    "code": "print(789)",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}

 

总结

 

现在,我们已经在Web API上获得了一组相当细粒度的权限,以及系统用户和他们创建的代码片段的端点。

 

在本教程的第5部分中,我们将研究如何通过为高亮显示的代码段创建HTML端点来将所有内容连接在一起,并通过使用超链接处理系统中的关系来提高API的内聚性。

 

教程5-Relationships(关系) 和 Hyperlinked APIs(超链接API)

 

目前,我们的API中的关系用主键表示。在本教程的这一部分中,我们将通过使用关系超链接来改进API的内聚性和可发现性。

 

为API的根(root)创建端点

 

现在我们有了“snippets”和“users”的端点,但是我们没有API的单一入口点。要创建一个视图,我们将使用一个常规的基于函数的视图和前面介绍的@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模式由方便的名称标识,稍后我们将在snippet/url.py中声明这些名称。

 

为高亮显示的代码片段创建端点

 

我们的pastebin API中还缺少的另一个明显的东西是高亮显示代码的端点。

 

与所有其他API端点不同,我们不希望使用JSON,而是只显示HTML表示。REST framework提供了两种样式的HTML呈现器,一种用于处理使用模板呈现的HTML,另一种用于处理预呈现的HTML。第二个渲染器是我们想要为这个端点使用的。

 

在创建代码高亮显示视图时,我们需要考虑的另一件事是,没有现有的具体通用视图可供我们使用。我们返回的不是对象实例,而是对象实例的属性。

 

我们将使用基类来表示实例,并创建我们自己的.get()方法,而不是使用具体的通用视图。在你的snippets/views.py中添加:

 

from rest_framework import renderers
from rest_framework.response import Response

class SnippetHighlight(generics.GenericAPIView):
    queryset = Snippet.objects.all()
    renderer_classes = (renderers.StaticHTMLRenderer,)

    def get(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

 

与往常一样,我们需要将创建的新视图添加到URLconf中。我们将在snippets/urls.py中为我们的新API根(root)添加一个url模式:

 

path('', views.api_root),

 

然后为代码片段的高亮部分添加url模式:

 

path('snippets/<int:pk>/highlight/', views.SnippetHighlight.as_view()),

 

超链接我们的API

 

处理实体之间的关系是Web API设计中更具挑战性的方面之一。我们可以用很多不同的方式来表达一段关系:

 

  • 使用主键。
  • 使用实体之间的超链接。
  • 在相关实体上使用唯一的标识字段。
  • 使用相关实体的默认字符串表示。
  • 将相关实体嵌套在父表示形式中。
  • 其他一些自定义表示。

 

REST框架支持所有这些样式,并且可以跨正向或反向关系应用它们,或者跨自定义管理器(如通用外键)应用它们。

 

在本例中,我们希望在实体之间使用超链接样式。为此,我们将修改我们的序列化器,以扩展HyperlinkedModelSerializer,而不是现有的ModelSerializer

 

HyperlinkedModelSerializerModelSerializer有以下区别:

 

  • 默认情况下,它不包含id字段。
  • 使用HyperlinkedIdentityField包含一个url字段。
  • 关系使用HyperlinkedRelatedField,而不是PrimaryKeyRelatedField

 


 

这是HyperlinkedModelSerializer源码(部分):可以看出它继承了ModelSerializer类

 

class HyperlinkedModelSerializer(ModelSerializer):
    """
    A type of `ModelSerializer` that uses hyperlinked relationships instead
    of primary key relationships. Specifically:

    * A 'url' field is included instead of the 'id' field.
    * Relationships to other instances are hyperlinks, instead of primary keys.
    """
    serializer_related_field = HyperlinkedRelatedField

    def get_default_field_names(self, declared_fields, model_info):
    	...	

 


 

我们可以很容易地重写现有的序列化器来使用超链接。在你的snippets/serializers.py中添加:

 

class SnippetSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')

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


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

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

 

注意,我们还添加了一个新的“highlight”字段。这个字段与url字段的类型相同,只是它指向“snippet-highlight”url模式,而不是“snippet-detail”url模式。

 

因为我们已经包含了格式后缀的url,比如'.json',我们还需要在highlight字段中指出,它返回的任何格式后缀的超链接都应该使用'.html的后缀。

 

确保URL模式已命名

 

如果我们要有一个超链接API,我们需要确保为URL模式命名。让我们看看需要命名哪些URL模式。

 

  • 我们的API的根指的是user-listsnippet-list
  • 我们的代码片段序列化器包含一个引用snippet-highlight的字段。
  • 我们的用户序列化器包含一个引用snippet-detail的字段。
  • 我们的代码片段和用户序列化器包括url字段,默认情况下这些字段将引用“{model_name}-detail”,在本例中是“snippet-detail”“user-detail”

 

将所有这些名称添加到URLconf之后,最后的snippet/urls.py文件应该是这样的:

 

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

# API endpoints
urlpatterns = format_suffix_patterns([
    path('', views.api_root),
    path('snippets/',
        views.SnippetList.as_view(),
        name='snippet-list'),
    path('snippets/<int:pk>/',
        views.SnippetDetail.as_view(),
        name='snippet-detail'),
    path('snippets/<int:pk>/highlight/',
        views.SnippetHighlight.as_view(),
        name='snippet-highlight'),
    path('users/',
        views.UserList.as_view(),
        name='user-list'),
    path('users/<int:pk>/',
        views.UserDetail.as_view(),
        name='user-detail')
])

 

添加分页

 

用户的列表视图和代码片段的列表视图最终可能返回相当多的实例,因此我们希望确保对结果进行分页,并允许API客户端遍历每个单独的页面。

 

通过稍微修改我们的tutorial/settings.py文件,我们可以更改默认的列表样式来使用分页。添加以下设置:

 

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}

 

注意,REST框架中的设置都被命名为一个名为REST_FRAMEWORK的字典设置,这有助于将它们与其他项目设置很好地分离。

 

如果需要,我们还可以定制分页样式,但在本例中,我们将坚持使用默认样式。

 

浏览API

 

如果我们打开浏览器并导航到可浏览的API,您会发现现在只需按照链接即可熟悉API。

 

您还可以在snippet实例上看到“highlight”链接,这将把您带到高亮显示的代码HTML表示页面。

 

在本教程的第6部分中,我们将研究如何使用视图集(ViewSets)和路由器(Routers)来减少构建API所需的代码量。

 

教程6-ViewSets(视图集) & Routers(路由器)

 

REST框架包含一个用于处理视图集的抽象,它允许开发人员集中精力对API的状态和交互进行建模,并根据公共约定自动处理URL构造。

 

ViewSet类几乎与视图类相同,只是它们提供了诸如read或update之类的操作,而不是诸如get或put之类的方法处理程序。

 

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

 

重构以使用视图集(ViewSets)

 

让我们将当前的视图重构为视图集;

 

首先,让我们将UserList和UserDetail视图重构为一个UserViewSet。我们可以删除这两个视图,并用一个类替换它们:

 

from rest_framework import viewsets

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    这个视图集自动提供'list'和'detail'操作
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

 

这里我们使用ReadOnlyModelViewSet类自动提供默认的“只读”操作。我们仍然像使用常规视图时一样设置querysetserializer_class属性,但是我们不再需要向两个单独的类提供相同的信息。

 

ReadOnlyModelViewSet 类源码:

 

class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
                           mixins.ListModelMixin,
                           GenericViewSet):
    """
    A viewset that provides default `list()` and `retrieve()` actions.
    提供默认的'list'和'retrieve'操作的ViewSet。
    """
    pass

 

接下来,我们将替换SnippetList、SnippetDetail和SnippetHighlight视图类;我们可以删除这三个视图,并再次用一个类替换它们。

 

from rest_framework.decorators import action
from rest_framework.response import Response

class SnippetViewSet(viewsets.ModelViewSet):
    """
    这个视图集自动提供'list'、'create'、'retrieve'、'update'和'destroy'操作;
    
    此外,还提供了一个额外的'highlight'操作;
    """
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly,)

    @action(detail=True, 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类来获得完整的默认操作集;

 

ModelViewSet 类源码:

 

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    提供默认的'create()'、'retrieve()'、'update()'、'partial_update()'、'destroy()'和'list()'操作;
    """
    pass

 

注意,我们还使用**@action装饰器创建了一个名为highlight的自定义操作;此装饰器可用于添加任何不符合标准create/update/delete**样式的自定义端点。

 

默认情况下,使用**@action装饰器的自定义操作将响应GET请求;如果想要响应POST请求的操作,可以使用methods**参数。例如:

 

@action(detail=True, methods=['GET','POST'], renderer_classes=[renderers.StaticHTMLRenderer])

 

  • 如果没有methods=[‘GET’, ‘POST’]参数,向API发送POST请求:

  • 如果添加了methods=[‘GET’, ‘POST’]参数,向API发送POST请求:

 

默认情况下,自定义操作的URL依赖于方法名称本身;如果希望更改URL的构造方式,可以将url_path包含为装饰器关键字参数。

 

明确地将视图集(ViewSets)绑定到URLs

 

处理程序方法只在定义URL Conf时绑定到操作;为了了解底层发生了什么,我们首先要从ViewSets中明确地创建一组视图。

 

在snippets/urls.py文件中,我们将ViewSet类绑定到一组具体视图中。

 

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

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

 

请注意,我们如何通过将http方法绑定到每个视图所需的操作来从每个类创建多个视图;

 

现在我们已将资源绑定到具体视图中,我们可以向往常一样使用URL conf注册视图;

 

urlpatterns = format_suffix_patterns([
    path('', api_root),
    path('snippets/', snippet_list, name='snippet-list'),
    path('snippets/<int:pk>/', snippet_detail, name='snippet-detail'),
    path('snippets/<int:pk>/highlight/', snippet_highlight, name='snippet-highlight'),
    path('users/', user_list, name='user-list'),
    path('users/<int:pk>/', user_detail, name='user-detail')
])

 

此时,我们可以重新运行我们的开发服务器(python manage.py runserver)并且前往浏览器进行我们的API测试了。

 

使用Routers(路由器)

 

因为我们使用的是ViewSet类而不是View类,所以我们实际上不需要自己设计URL conf;可以使用Router类自动处理将资源连接到视图和URL的约定;我们所需要做的就是向**Router(路由器)**注册适当的视图集,然后让它来完成剩下的工作。

 

这是我们重新连接的snippets/urls.py文件:

 

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from snippets import views

# 创建一个路由器并注册我们的视图集
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)

# API的URLs现在由路由器自动确定
urlpatterns = [
    path('', include(router.urls)),
]

 

向路由器注册视图集类似于提供urlpattern;我们包括两个参数—视图的URL前缀视图集本身

 

我们使用的DefaultRouter类还自动为我们创建API根视图(api_root),所以现在我们可以从views(视图)模块中删除api_root方法。

 

视图(views)与视图集(viewsets)之间的权衡

 

使用视图集可能是一个非常有用的抽象。它有助于确保URL约定在API中保持一致,最大限度地减少需要编写的代码量,使我们可以专注于API提供的交互和表示,而不是URL conf的细节。

 

这并不意味着它始终是正确的方法。当使用基于类的视图而不是基于函数的视图时,需要考虑类似的一组权衡。使用视图集不如单独构建视图那么明确。

 

教程7-模式(Schemas) & 客户端库(client libraries)

 

模式(schema)是机器可读的文档,描述可用的API端点、它们的URLS,以及它们支持哪些操作;

 

模式(schema)可以是自动生成文档的有用工具,也可以用来驱动可以与API交互的动态客户端库。

 

核心API(Core API)

 

为了提供模式支持,REST 框架使用核心API(Core API);

 

核心API(Core API)是描述API的文档规范。它用于提供API公开的可用端点和可能的交互的内部表示格式,它既可以用于服务器端,也可以用于客户端;

 

在服务器端使用时,核心API(Core API)允许API支持呈现多种模式或超媒体格式;

 

在客户端使用时,核心API(Core API)允许动态驱动的客户端库可以与任何公开支持的模式或超媒体格式的API进行交互。

 

添加模式(schema)

 

REST框架既支持明确定义的模式视图或自动生成的模式;由于我们使用的是视图集(viewsets)路由器(routers),因此我们可以简单地使用自动模式生成;

 

为了包含API模式,需要安装Python的coreapi包,并使用pyyaml将模式呈现为常用的基于YAML的OpenAPI格式。

 

$ pip install coreapi pyyaml

 

现在,通过在URL配置中包含一个自动生成的模式视图,我们可以为API包含一个模式。

 

from rest_framework.schemas import get_schema_view

schema_view = get_schema_view(title='Pastebin API')

urlpatterns = [
    path('schema/', schema_view),
    ...
]

 

如果在浏览器中访问/schema/端点,现在应该可以看到corejson表示形式作为一个选项可用。

 

 

我们还可以通过在Accept头中指定所需的内容类型,从命令行请求模式。

 

$ http http://127.0.0.1:8000/schema/ Accept:application/coreapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/coreapi+json

{
    "_meta": {
        "title": "Pastebin API"
    },
    "_type": "document",
    ...

 

默认的输出样式是使用Core JSON 编码;

 

还支持其他模式格式,比如Open API(以前称为Swagger)。

 

使用命令行客户端

 

现在我们的API公开了一个模式端点,我们可以使用一个动态客户端库与API进行交互;为了演示这一点,让我们使用Core API命令行客户端。

 

命令行客户端作为coreapi-cli包提供:

 

$ pip install coreapi-cli

 

现在检查它是否在命令行上可用…

 

$ coreapi
Usage: coreapi [OPTIONS] COMMAND [ARGS]...

  Command line client for interacting with CoreAPI services.

  Visit https://www.coreapi.org/ for more information.

Options:
  --version  Display the package version number.
  --help     Show this message and exit.

Commands:
...

 

首先,我们将使用命令行客户端加载API模式:

 

$ coreapi get http://127.0.0.1:8000/schema/
<Pastebin API "http://127.0.0.1:8000/schema/">
    snippets: {
        highlight(id)
        list()
        read(id)
    }
    users: {
        list()
        read(id)
    }

 

我们尚未进行身份验证,因此现在我们只能看到只读端点,这与我们在API上设置权限的方式一致。

 

让我们试着列出现有的代码片段(snippets),使用命令行客户端。

 

$ coreapi action snippets list
[
    {
        "url": "http://127.0.0.1:8000/snippets/1/",
        "id": 1,
        "highlight": "http://127.0.0.1:8000/snippets/1/highlight/",
        "owner": "lucy",
        "title": "Example",
        "code": "print('hello, world!')",
        "linenos": true,
        "language": "python",
        "style": "friendly"
    },
    ...

 

一些API端点需要制定的参数;例如:要获取特定代码段的高亮HTML,我们需要提供ID。

 

$ coreapi action snippets highlight --param id=1
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html>
<head>
  <title>Example</title>
  ...

 

验证我们的客户端

 

如果我们希望能够创建(create)、编辑(edit)和删除(delete)代码片段,就需要验证为有效用户;在本例中,我们只使用基本的auth。

 

确保用实际的用户名和密码替换下面的****。

 

$ coreapi credentials add 127.0.0.1 <username>:<password> --auth basic
Added credentials
127.0.0.1 "Basic <...>"

 

现在,如果我们再次获取模式(schema),我们应该能够看到所有可用的交互:

 

$ coreapi reload
Pastebin API "http://127.0.0.1:8000/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)
    }

 

现在我们可以和这些端点交互了;例如:要创建一个新的代码段(snippet)

 

$ coreapi action snippets create --param title="Example" --param code="print('hello, world')"
{
    "url": "http://127.0.0.1:8000/snippets/7/",
    "id": 7,
    "highlight": "http://127.0.0.1:8000/snippets/7/highlight/",
    "owner": "lucy",
    "title": "Example",
    "code": "print('hello, world')",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}

 

删除一个代码片段(snippet)

 

$ coreapi action snippets delete --param id=7

 

除了命令行客户端,开发人员还可以使用客户端库与您的API进行交互;Python客户端库是第一个可用的,并且计划很快发布JavaS客户端库。

 

有关自定义模式生成和使用Core API客户端库的更多详细信息,您需要参考完整的文档。

 

回顾我们的工作

 

凭借极少量的代码,我们现在拥有了一个完整的pastebin Web API,它完全是Web可浏览的,包括一个模式驱动的客户端库,并具有完整的身份验证、每个对象的权限和多个渲染器格式。

 

我们已经完成了设计过程的每一步,并了解了如果我们需要定制任何我们可以逐步使用的方法,只需使用常规的Django视图。

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 程序猿惹谁了 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值