@list_route 和@detail_route 嵌套路由不会出现在 Browseable API 中

@list_route and @detail_route nested routes do not appear in Browseable API

我正在尝试通过 @list_route@detail_route 装饰器使用嵌套路由。路线和 return 数据有效,但我必须在地址栏中手动导航到它们。它们不会出现在 DefaultRouter 生成的 ApiRoot 中可浏览 API。我正在使用 Django 1.8 和 Rest Framework 3.1.1。

urls.py:

router = DefaultRouter()
router.register(r'aggregates', viewsets.AggregateViewSet, base_name='aggregate')

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

viewsets.py:

class AggregateViewSet(viewsets.GenericViewSet):
    queryset = models.DataAggregate.objects.order_by('id')
    serializer_class = serializers.AggregateSerializer

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        page = self.paginate_queryset(queryset)

        if page:
            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)

    @list_route(url_path='recent')
    def recent_aggregates(self, request):
        return Response({'message': 'herp a derp'})

当我导航到 /myapp/api 时,可浏览的 API 只显示:

HTTP 200 OK
Content-Type: application/json
Vary: Accept
Allow: GET, HEAD, OPTIONS

{
    "aggregates": "http://localhost:8000/myapp/api/aggregates/"
}

期待这个:

{
    "aggregates": "http://localhost:8000/myapp/api/aggregates/"
    "aggregates-recent": "http://localhost:8000/myapp/api/aggregates/recent"
}

我试过各种修改版本,看能不能让步,都无济于事。同样,如果我手动导航到这些路由 do 函数和可浏览的 API 将显示它们的页面......但是违背了可浏览的目的 api.

我看了一下 DefaultRouter(和 SimpleRouter)的代码,它似乎发现了动态路由...

没错,默认情况下它不会出现在 API 中。

我自己使用该代码段来提供遵循 HAL 样式(py3 代码)的嵌套路由。

import urllib
from collections import OrderedDict

from rest_framework import serializers, relations

class SubNamespaceURLField(relations.HyperlinkedIdentityField):
    """Refers to a child namespace of the object, as pointed by view_name
    """
    def __init__(self, namespace, *args, **kwargs):
        self.namespace = namespace.strip('/')
        super().__init__(*args, **kwargs)

    def field_to_native(self, obj, field_name):
        base = super().field_to_native(obj, field_name)
        return urllib.parse.urljoin(base, self.namespace) + '/'

class HALNestedLinksField(relations.HyperlinkedIdentityField):
    """ Tries to represent a list of nested links on the resource a
    HAL-compliant way.

    See http://stateless.co/hal_specification.html
    """
    def __init__(self, endpoints, *args, **kwargs):
        """
        :param endpoints list of url suffixes leading to nested operations on
                         the resource (ex: ['preview', 'check'])
        """
        self.endpoints = endpoints
        super().__init__(*args, **kwargs)

    def get_attribute(self, obj):
        return obj

    def to_representation(self, value):
        links = OrderedDict()
        prefix = super().to_representation(self.get_attribute(value))
        for i in self.endpoints:
            # We consider if it contains a dot its a content-type indication,
            # so no trailing slash
            if '.' in i:
                suffix = ''
            else:
                suffix = '/'
            links[i] = {'href':
                        urllib.parse.urljoin(prefix, i) + suffix}
        return links

然后在你的序列化器中使用它:

class SomeModelSerializer(serializers.HyperlinkedModelSerializer):
    _links = HALNestedLinksField(['revalidate'], # you detail_route names
                                 view_name='somemodel-detail')
    class Meta:
        model = SomeModel
        fields = ('url',
                  'date',
                  '_links') # do not forget it

并且您将获得一个 _links 属性,其中包含您在 HALNestedLinksField 中声明的所有相关路由。