目前我们的API中的关系是用主键表示的。下面我们将通过使用超链接来提高我们API的内聚力 和可发现性。

一、为我们的API创建一个根路径

现在我们有snippetsusers的这两个url,但是我们的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模式是通过简单方便易懂的名称来标识的,我们稍后将在 snippets/urls.py 中声明。

二、为高亮显示的代码片段创建路径

我们的API中另一个明显缺少的是高亮代码的路径。

与所有其他API路径不同,我们不想使用JSON,而只是需要HTML表示。REST框架提供了两种 HTML渲染器,一种用于处理使用模板渲染的HTML,另一种用于处理预渲染的HTML。教程里, 我们选择第二个渲染器。

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

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

像往常一样,我们需要在URLconf中添加新视图的路由。在 snippets/urls.py 中为 api_root 视图添加下面的url路由:

path('', views.api_root),

以及为高亮代码片段添加一个url模式:

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

到目前,我们的views.py内容如下:

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, UserSerializer
from rest_framework import generics
from django.contrib.auth.models import User
from rest_framework import permissions
from snippets.permissions import IsOwnerOrReadOnly
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework import renderers
from rest_framework.response import Response


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


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)


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

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


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)


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


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

重启服务器,访问 127.0.0.1:8000 ,你可能会看到如下报错:

image-20210120110038398

这是因为rest_framework.reverse.reverse方法中必须传入视图viewname,可是我们已经传入user-list了,为什么还是报错呢,是因为我们没有在url中指定,我们只需要在需要的url中指定viewname就行了,这是一个必须要养成的习惯。

截止到目前我们的snippets/url.py内容如下:

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

urlpatterns = [
    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('users/', views.UserList.as_view(), name='user-list'),
    path('users/<int:pk>/', views.UserDetail.as_view(), name='user-detail'),
    path('snippets/<int:pk>/highlight/', views.SnippetHighlight.as_view(), name='snippet-highlight'),
]
urlpatterns = format_suffix_patterns(urlpatterns)

这是在打开浏览器运行看看

image-20210120110444361

点击链接会跳转到对应的页面。

三、使用链接形式的API

处理好实体之间的关系是Web API设计中比较有挑战性的工作。我们可以选择几种不同的方式来 代表一种关联关系,比如:

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

REST框架支持所有这些方式,并且可以将它们应用于正向或反向关联,也可以在诸如通用外键 之类的自定义管理器上应用。

本教程中,我们采用超链接的方式。这样的话,我们需要修改我们的序列化类来,改为继承 HyperlinkedModelSerializer 类而不是现有的 ModelSerializer 类。

HyperlinkedModelSerializer 类是DRF为我们提供的用于实现超级链接模型序列化器的父 类。也是常用选择之一。

HyperlinkedModelSerializerModelSerializer 有以下区别:

  • 默认情况下不包括 id 字段。
  • 自带一个url字段,使用HyperlinkedIdentityField
  • 关联关系使用HyperlinkedRelatedField字段类型,而不是PrimaryKeyRelatedField字段类型。

修改你的 snippets/serializers.py ,如下所示:

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
from django.contrib.auth.models import User


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', 'title', 'code', 'linenos', 'language', 'style', 'owner')


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,我们还需要在higjlight字段上之处任何格式后缀的超链接,它应该使用.html后缀

五、添加分页功能

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

稍微修改下我们的 tutorial/settings.py 文件,添加以下设置:

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

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

如果需要的话,我们也可以自定义分页风格,但在这个教程中,我们将一直使用默认设置。

六、浏览API

好了,可以打开浏览器并浏览我们的API了,你可以简单的通过页面上的超链接来了解API。 你还可以看到代码片段实例上的'highlight'链接,它能带你跳转到高亮显示的代码HTML表示。 先看snippets_list页面:

image-20210120112011873

通过页面上的url链接可以跳转到对应的页面,比如高亮页面:

image-20210120112101039

总结

关系带给我们的是可跳转;超链接序列化器带给我们的是可点击的对象链接,而不是冰冷的主 键数字。也更利于前端发现后端的API。

本文作者:博主:
文章标题:DRF-快速入门--关系和超链接API
本文地址:https://wouldmissyou.com/archives/44/     
版权说明:若无注明,本文皆为“多点部落”原创,转载请保留文章出处。
最后修改:2021 年 01 月 20 日 01 : 40 PM
如果觉得我的文章对你有用,请随意赞赏