REST Framework

REST Framework

文章目录

  • REST Framework
    • 01-入门实践
      • 1.1 项目搭建
      • 1.2 Serializers
      • 1.3 Views
      • 1.4 Urls
      • 1.5 Settings
      • 1.6 测试Api
    • 02 序列化
      • 2.1 项目初始
      • 2.2 创建model
      • 2.3 序列化类
      • 2.4 查询结果集
      • 2.5 ModelSerializers
      • 2.6 Serializer编写视图*
      • 2.7 测试Api
    • 03 请求和响应
      • 3.1 请求对象
      • 3.2 响应对象
      • 3.3 状态码
      • 3.4 包装API视图
      • 3.5 组合在一起
      • 3.6 api路径后缀
      • 3.7 测试Api
    • 04 类的视图
      • 4.1 类视图重构
      • 4.2 类路由修改
      • 4.3 混合
      • 4.4 通用类视图
    • 05 认证与权限
      • 5.1 列表模型添加用户
      • 5.2 只读权限设置
      • 5.3 将Snippet和用户关联
      • 5.4 更新序列化器
      • 5.5 添加视图权限
      • 5.6 添加登陆功能
      • 5.7 设置权限
      • 5.8 通过Api验证身份
    • 06 关系和超链接API
      • 6.1 Api根路径
      • 6.2 高亮代码片段路径
      • 6.3 超链接API
      • 6.4 URL模式命名
      • 6.5 添加分页
      • 6.6 浏览Api
    • 07 视图集和路由器
      • 7.1 使用ViewSets重构
      • 7.2 将ViewSets绑定URL
      • 7.3 重写路由
      • 7.4 视图vs视图集权衡
    • 08 概要和客户端库
      • 8.1 添加概要
      • 8.2 使用命令行客户端

01-入门实践

1.1 项目搭建

# 服务器上窗户文件夹
cd tutorial

# 搭建django项目
...

# 安装djangorestframewor
pip install djangorestframewor

# 创建app应用
django-admin.py startapp quickstart

同步数据库

python manage.py migrate

创建用户

admin的初始用户,密码为password123

python manage.py createsuperuser

1.2 Serializers

创建一个名为 tutorial/quickstart/serializers.py

from django.contrib.auth.models import User,Group
from rest_framework import serializers

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

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

1.3 Views

编写tutorial/quickstart/views.py

from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from .serializers import UserSerializer, GroupSerializer


class UserViewSet(viewsets.ModelViewSet):
    """
    允许用户查看或编辑的API路径。
    """
    # queryset 数据库查询的参数
    queryset = User.objects.all().order_by('-date_joined')

    # serializer_class 列表数据过滤、序列号输出
    serializer_class = UserSerializer


class GroupViewSet(viewsets.ModelViewSet):
    """
    允许组查看或编辑的API路径。
    """
    queryset = Group.objects.all()
    serializer_class = GroupSerializer

1.4 Urls

tutorial/urls.py中开始写连接API的URLs

from django.conf.urls import 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)

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

1.5 Settings

设置一些全局设置。例:打开分页,我们希望我们的API只能由管理员使用

设置模块都在 tutorial/settings.py

INSTALLED_APPS = (
    ...
    'rest_framework',
)

REST_FRAMEWORK = {
     
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAdminUser',
    ],
    'PAGE_SIZE': 10
}

1.6 测试Api

启动服务器

python manage.py runserver

命令行访Api

curl -H 'Accept: application/json; indent=4' -u admin:password123 http://127.0.0.1:8000/users/

02 序列化

2.1 项目初始

重新创建项目

cd ~django-admin.py startproject tutorial
cd tutorial

安装插件与创建app

pip install pygments  # 代码高亮插件

python manage.py startapp snippets

配置settings.py

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

2.2 创建model

编辑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)

    class Meta:
        ordering = ('created',)

数据迁移

python manage.py makemigrations snippets
python manage.py migrate

2.3 序列化类

在snippets的目录下创建一个名为serializers.py文件,将代码片段实例序列化和反序列化为诸如json之类

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

进入Django shell

python manage.py shell

导入几个模块,然后开始创建一些代码片段来处理

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

    将模型实例转换为Python原生数据类型。要完成序列化过程,我们将数据转换成json

    content = JSONRenderer().render(serializer.data)
    content
    
    b'{"id":1,"title":"","code":"print \\"hello, world\\"\\n","linenos":false,"language":"python","style":"friendly"}'
    
  • 反序列化

    将一个流(stream)解析为Python原生数据类型

    from django.utils.six import BytesIO
    
    stream = BytesIO(content)
    data = JSONParser().parse(stream)
    

    将Python原生数据类型恢复成正常的对象实例

    serializer = SnippetSerializer(data=data)
    serializer.is_valid() # 验证传进啦的数据,True
    
    serializer.validated_data
    # OrderedDict([('title', ''), ('code', 'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
    
    serializer.save()
    # 
    

2.4 查询结果集

只需要为serializer添加一个many=True标志

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

2.5 ModelSerializers

打开snippets/serializers.py文件,并将SnippetSerializer类替换为以下内容

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

通过打印序列化器类实例的结构(representation)查看它的所有字段

from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))

2.6 Serializer编写视图*

编辑snippets/views.py文件

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

class JSONResponse(HttpResponse):
    """
    An HttpResponse that renders its content into JSON.
    """
    def __init__(self, data, **kwargs):
        content = JSONRenderer().render(data)
        kwargs['content_type'] = 'application/json'
        super(JSONResponse, self).__init__(content, **kwargs)

API的根视图支持列出所有现有的snippet或创建一个新的snippet

@csrf_exempt
def snippet_list(request):
    """
    列出所有的code 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)

一个与单个snippet对象相应的视图,并且用于获取,更新和删除这个snippet

@csrf_exempt
def snippet_detail(request, pk):
    """
    获取,更新或删除一个 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)

snippets/urls.py,配置路由

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

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

2.7 测试Api

退出shell

quit()

启动django

可以使用curl或httpie测试我们的服务器。Httpie是用Python编写的用户友好的http客户端

pip install httpie

所有snippet的列表

http http://127.0.0.1:8000/snippets/

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

通过引用其id来获取特定的snippet

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

HTTP/1.1 200 OK
...
{
     
  "id": 2,
  "title": "",
  "code": "print \"hello, world\"\n",
  "linenos": false,
  "language": "python",
  "style": "friendly"
}

03 请求和响应

3.1 请求对象

Request对象的核心功能是request.data属性,它与request.POST类似

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

# request.data可以处理传入的json请求,但它也可以处理其他格式
request.data  # 处理任意数据  适用于'POST','PUT'和'PATCH'方法

3.2 响应对象

Response对象,这是一种获取未渲染(unrendered)内容的TemplateResponse类型,

并使用内容协商来确定返回给客户端的正确内容类型

return Response(data)  # 渲染成客户端请求的内容类型

3.3 状态码

REST框架为status模块中的每个状态代码(如HTTP_400_BAD_REQUEST)提供更明确的标识符。

使用它们来代替纯数字的HTTP状态码是个很好的主意

3.4 包装API视图

REST框架提供了两个可用于编写API视图的包装器(wrappers)

  • 用于基于函数视图的@api_view装饰器
  • 用于基于类视图的APIView

这些包装器提供了一些功能,例如确保你在视图中接收到Request实例,

并将上下文添加到Response,以便可以执行内容协商

3.5 组合在一起

使用新的请求与相应修改之前的列表与详情

我们还使用了命名状态代码,这使得响应意义更加明显

  • 列表

    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):
        """
        列出所有的snippets,或者创建一个新的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)
    
  • 详情

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

3.6 api路径后缀

为了充分利用我们的响应不再与单一内容类型连接,我们可以为API路径添加对格式后缀的支持。使用格式后缀给我们明确指定了给定格式的URL,这意味着我们的API将能够处理诸如http://example.com/api/items/4.json

像下面这样在这两个视图中添加一个format关键字参数

def snippet_list(request, format=None):

# 详情传入文章id,pk为文章id
def snippet_detail(request, pk, format=None):

更新urls.py文件,给现有的URL后面添加一组format_suffix_patterns

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

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

urlpatterns = format_suffix_patterns(urlpatterns)

3.7 测试Api

我们可以像以前一样获取所有snippet的列表

http http://127.0.0.1:8000/snippets/

可以通过使用Accept标头来控制我们回复的响应格式

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,则可以在请求标头中查看请求类型

04 类的视图

4.1 类视图重构

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):
    """
    列出所有的snippets或者创建一个新的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):
    """
    检索,更新或删除一个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)

4.2 类路由修改

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

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

urlpatterns = format_suffix_patterns(urlpatterns)

4.3 混合

到目前为止,我们使用的创建/获取/更新/删除操作和我们创建的任何基于模型的API视图非常相似。

这些常见的行为是在REST框架的mixin类中实现的

编辑views.py模块

generics

  • GenericAPIView 构建视图

mixins

  • ListModelMixin 获取列表数据

  • RetrieveModelMixin 获取详情数据

  • CreateModelMixin 创建数据

  • UpdateModelMixin 修改数据

  • DestroyModelMixin 删除数据

self操作数据

  • list 列表
  • retrieve 详情
  • create 创建
  • update 修改
  • destroy 删除
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)
    
  
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)
    

4.4 通用类视图

REST框架提供了一组已经混合好(mixed-in)的通用视图,我们可以使用它来简化我们的views.py模块

  • ListCreateAPIView 查看与创建

  • RetrieveUpdateDestroyAPIView 增删改

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


class SnippetList(generics.ListCreateAPIView):
    """"
    列出所有的snippets或者创建一个新的snippet。
    """
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    """
      检索,更新或删除一个snippet示例。
      """

    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

05 认证与权限

5.1 列表模型添加用户

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

# 创建代码段的用户
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)

# 存储代码的高亮显示的HTML内容
highlighted = models.TextField()

导入额外的模块

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

通常这种情况我们会创建一个数据库迁移(migration)来实现这一点,

但现在我们只是个教程示例,所以我们选择直接删除数据库并重新开始

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

需要创建几个不同的用户

python manage.py createsuperuser

zzw / 123
baba / 123
mama / 123

5.2 只读权限设置

创建一个新的序列化器非常简单,在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' 在用户模型中是一个反向关联关系。

在使用 ModelSerializer 类时它默认不会被包含,所以我们需要为它添加一个显式字段

views.py中添加几个视图。我们只想将用户展示为只读视图

  • ListAPIView 查看列表
  • RetrieveAPIView 查看详情
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(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P[0-9]+)/$', views.UserDetail.as_view()),

5.3 将Snippet和用户关联

如果我们创建了一个代码片段,并不能将创建该代码片段的用户与代码段实例相关联。

用户不是作为序列化表示的一部分发送的,而是作为传入请求的属性。

(译者注:user不在传过来的数据中,而是通过request.user获得)

我们处理的方式是在我们的代码片段视图中重写一个.perform_create()方法,这样我们可以修改实例保存的方法,并处理传入请求或请求URL中隐含的任何信息

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

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

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

5.4 更新序列化器

将以下字段添加到serializers.py中的序列化器定义:

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

注意:确保你还将'owner',添加到内部Meta类的字段列表中。

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

    class Meta:
        model = Snippet
		# owner 添加字段
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style','owner')

5.5 添加视图权限

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

视图导入模块

from rest_framework import permissions

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

# 仅可读
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

5.6 添加登陆功能

项目urls.py文件中的URLconf来添加可浏览的API使用的登录视图

from django.conf.urls import include

而且,在文件末尾添加一个模式(pattern)以包括可浏览的API的登录和注销视图。

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

模式的r'^api-auth/'部分实际上可以是你要使用的任何URL。

唯一的限制是包含的URL必须使用'rest_framework'命名空间

5.7 设置权限

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

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

在snippets app中,创建一个新文件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

        # 只有该snippet的所有者才允许写权限
        return obj.owner == request.user

SnippetDetail视图类中编辑permission_classes属性将该自定义权限添加到我们的代码片段实例路径

from .permissions import IsOwnerOrReadOnly

#...
permission_classes = (permissions.IsAuthenticatedOrReadOnly,IsOwnerOrReadOnly,)

5.8 通过Api验证身份

未登录时请求

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

HTTP/1.1 403 Forbidden
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 43
Content-Type: application/json
Date: Fri, 17 Jan 2020 09:03:33 GMT
Server: WSGIServer/0.2 CPython/3.6.6
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
     
    "detail": "身份认证信息未提供。"
}

请求加上用户名与密码请求创建

http -a zzw:123 POST http://127.0.0.1:8000/snippets/ code="print 789"
            
HTTP/1.1 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 107
Content-Type: application/json
Date: Sat, 18 Jan 2020 13:27:06 GMT
Server: WSGIServer/0.2 CPython/3.6.6
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
     
    "code": "print 789",
    "id": 1,
    "language": "python",
    "linenos": false,
    "owner": "zzw",
    "style": "friendly",
    "title": ""
}

06 关系和超链接API

6.1 Api根路径

为’snippets’和’users’创建Api入口点,在你的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.py中声明

6.2 高亮代码片段路径

REST框架提供了两种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)

snippets/urls.py中为我们的新API根路径添加一个url模式

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

然后为高亮代码片段添加一个url模式:

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

6.3 超链接API

我们希望在实体之间使用超链接方式。这样的话,

我们需要修改我们的序列化程序来扩展HyperlinkedModelSerializer而不是现有的ModelSerializer

HyperlinkedModelSerializerModelSerializer有以下区别:

  • 默认情况下不包括id字段。
  • 它包含一个url字段,使用HyperlinkedIdentityField
  • 关联关系使用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')

请注意,我们还添加了一个新的'highlight'字段。该字段与url字段的类型相同,不同之处在于它指向'snippet-highlight'url模式,而不是'snippet-detail'url模式。

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

6.4 URL模式命名

如果我们要使用超链接的API,那么需要确保为我们的URL模式命名。我们来看看我们需要命名的URL模式。

  • 我们API的根路径是指'user-list''snippet-list'
  • 我们的代码片段序列化器包含一个指向'snippet-highlight'的字段。
  • 我们的用户序列化器包含一个指向'snippet-detail'的字段。
  • 我们的代码片段和用户序列化程序包括'url'字段,默认情况下将指向'{model_name}-detail',在这个例子中就是'snippet-detail''user-detail'

将所有这些名称添加到我们的URLconf中后,最终我们的snippets/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[0-9]+)/$', views.SnippetDetail.as_view(), name='snippet-detail'),
    url(r'^snippets/(?P[0-9]+)/highlight/$', views.SnippetHighlight.as_view(), name='snippet-highlight'),
    url(r'^users/$', views.UserList.as_view(), name='user-list'),
    url(r'^users/(?P[0-9]+)/$', views.UserDetail.as_view(), name='user-detail')
])

# 可浏览API的登录和注销视图
urlpatterns += [
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]

6.5 添加分页

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

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

REST_FRAMEWORK = {
      'PAGE_SIZE': 10}

请注意,REST框架中的所有设置都放在一个名为“REST_FRAMEWORK”的字典中,这有助于区分项目中的其他设置。

6.6 浏览Api

http -a zzw:123 POST http://127.0.0.1:8000/snippets/ code="print 789"
            
HTTP/1.1 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 207
Content-Type: application/json
Date: Sat, 18 Jan 2020 14:14:04 GMT
Location: http://127.0.0.1:8000/snippets/3/
Server: WSGIServer/0.2 CPython/3.6.6
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
     
    "code": "print 789",
    "highlight": "http://127.0.0.1:8000/snippets/3/highlight/",
    "id": 3,
    "language": "python",
    "linenos": false,
    "owner": "zzw",
    "style": "friendly",
    "title": "",
    "url": "http://127.0.0.1:8000/snippets/3/"
}     

07 视图集和路由器

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

ViewSet类与View类几乎相同,不同之处在于它们提供诸如readupdate之类的操作,而不是getput等方法处理程序

7.1 使用ViewSets重构

UserListUserDetail视图重构为一个UserViewSet。我们可以删除这两个视图,并用一个类替换它们

from rest_framework import viewsets

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    此视图自动提供`list`和`detail`操作。
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

使用ReadOnlyModelViewSet类来自动提供默认的“只读”操作。

仍然像使用常规视图那样设置querysetserializer_class属性,但我们不再需要向两个不同的类提供相同的信息。

将替换SnippetListSnippetDetailSnippetHighlight视图类。可以删除三个视图,并再次用一个类替换它们

from rest_framework.decorators import detail_route

class SnippetViewSet(viewsets.ModelViewSet):
    """
    此视图自动提供`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请求。如果我们想要一个响应POST请求的动作,我们可以使用methods参数。

默认情况下,自定义操作的URL取决于方法名称本身。如果要更改URL的构造方式,可以为装饰器设置url_path关键字参数。

cannot import name detail_route错误

执行from rest_framework.decorators import detail_route时,报cannot import name detail_route错误

查看decorators.py源码,发现原因是从2.4.0才有这个方法,而公司用的是2.3.14,所以没有。

在之前旧的版本使用的是detail_route,list_route连个装饰器来添加额外的路由,用来区别url中是否有pk字段。action装饰器则是通过detail字段来区别

from rest_framework.decorators import action

@action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])

7.2 将ViewSets绑定URL

当我们定义URLConf时,处理程序方法只能绑定到那些动作上。 要了解在幕后发生的事情,首先显式地从我们的视图集中创建一组视图

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方法绑定到每个视图所需的操作,从每个ViewSet类创建多个视图的。

现在我们将资源绑定到具体的视图中,我们可以像往常一样在URL conf中注册视图。

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

7.3 重写路由

因为我们使用的是ViewSet类而不是View类,我们实际上不需要自己设计URL。将资源连接到视图和url的约定可以使用Router类自动处理。我们需要做的就是使用路由器注册相应的视图集,然后让它执行其余操作。

这是我们重写的urls.py文件。

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

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

# API URL现在由路由器自动确定。
# 另外,我们还要包含可浏览的API的登录URL。
urlpatterns = [
    url(r'^', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

使用路由器注册viewsets类似于提供urlpattern。我们包含两个参数 - 视图的URL前缀和视图本身。

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

7.4 视图vs视图集权衡

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

这并不意味着采用视图集总是正确的方法。在使用基于类的视图而不是基于函数的视图时,有一个类似的权衡要考虑。使用视图集不像单独构建视图那样明确

08 概要和客户端库

8.1 添加概要

REST框架支持明确定义的概要视图或自动生成的概要。由于我们使用的是视图集和路由器,我们可以简单地使用自动概要生成。

你需要安装coreapi python包才能包含API概要。

$ pip install coreapi

现在我们可以通过在URL配置中包含一个自动生成的概要视图来为API添加概要。

from rest_framework.schemas import get_schema_view

schema_view = get_schema_view(title='Pastebin API')

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

如果你在浏览器中访问API根路径,那么你现在应该可以看到corejson表示形式是一个可用选项。

我们也可以通过在Accept标头中指定所需的内容类型从命令行请求概要

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

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

8.2 使用命令行客户端

现在我们的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 http://www.coreapi.org for more information.

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

Commands:
...

你可能感兴趣的:(python)