2

让DRF的URL支持前缀的一种方式

 2 years ago
source link: https://note.qidong.name/2022/03/drf-prefix/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

让DRF的URL支持前缀的一种方式

2022-03-08 17:55:31 +08  字数:1023  标签: Python Django

DRF(Django REST Framework)不支持带前缀的资源。 所有资源,默认都应该在URL的根路径下(/)。

这里提供了一种支持前缀的解决方案,并且支持带参数的前缀。

URL配置前缀

ROUTER = DefaultRouter()
...

urlpatterns = [
    path('', include(ROUTER.urls)),
    path(r'api/v1/app/', include(ROUTER.urls)),
    path(r'api/v2/<str:app_uid>/', include(ROUTER.urls)),
]

这里三行path,展示了三种定义方式。

  1. ''代表一般的推荐用法,没有前缀。
  2. r'api/v1/app/'代表一种常见的固定前缀。
  3. r'api/v2/<str:app_uid>/'代表在前缀中,包含动态的参数。

第1种定义,没有本文的方案,也能正常使用。 v1v2两种前缀,则需要本文的方案,自定义Field类。

但是,无论使用第2还是第3种方案,都必须加上第1种。 因为,在进行资源关联时,v1v2的API,都会去自动需要''下的资源。 如果它不存在,则会报错。 这一点固然也有办法,但都比较麻烦,不如保留'',在部署的代理层面过滤即可。

自定义带前缀的Field

默认情况下,资源中的url字段,会返回''下的那个链接。 这里通过自定义Field,为它加上了固定前缀。

class PrefixIdField(HyperlinkedIdentityField):
    """Customize `url` field with prefix.

    Example:
        Set `LEVELS = 3`, then a prefix with 3 levels is supported,
        like `/api/v1/app`.
    """

    LEVELS = 3

    def get_url(self, obj, view_name, request, *args, **kwargs):
        url = super().get_url(obj, view_name, request, *args, **kwargs)

        splits = request.path.split('/')
        prefix = '/'.join(splits[:self.LEVELS + 1])
        parsed = urlparse(url)
        path = prefix + parsed.path

        if DEBUG:
            return urljoin(url, path)
        return path

前缀的层级一般是固定的,这里示例时使用了3层,只支持静态配置。 其核心操作,就是在生成关联资源的URL(也即响应JSON中的url字段)时,额外增加前缀。

最后在return时,这里还展示了如何返回完整的URL(if DEBUG),或只返回其path部分。 在本地开发时,使用完整URL可方便点击;上线后,只保留path部分,可减少重复、降低流量,也不影响前端使用。 这部分逻辑,可按需调整。

替换原生的Field

PrefixIdField定义完成后,只需要修改Serializer.serializer_url_field,就可使用。 由于整个DefaultRouter下的所有资源都需要这个调整,因此可以考虑定义一个公共的基类,统一配置。

class BaseSrlz(HyperlinkedModelSerializer):
    serializer_url_field = PrefixIdField
    ...

获取v2前缀中的参数

对以上v2类型的前缀,还有一个示例的动态参数app_uid。 在SerializerViewSet中,均有办法获取。

class BaseSrlz(HyperlinkedModelSerializer):
    serializer_url_field = PrefixIdField

    def create(self, validated_data):
        request = self.context['request']
        app_uid = req.parser_context['kwargs'].get('app_uid')
        ...
        return super().create(validated_data)


class AViewSet(ModelViewSet):

    def retrieve(self, request, *args, **kwargs):
        app_uid = request.parser_context['kwargs'].get('app_uid')
        ...
        return super().retrieve(request, *args, **kwargs)

结论

由于DRF官方不支持,所以以上方案只能算是一种hack。 它把所有关联资源都设为''那组,并且让这组资源按URL前缀,修改关联的url前缀。 但无论如何,这是一个可用的有效方案,可以撑到官方支持出炉。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK