让DRF的URL支持前缀的一种方式
2022-03-08 17:55:31 +08 字数:1023 标签: Python DjangoDRF(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
,展示了三种定义方式。
''
代表一般的推荐用法,没有前缀。r'api/v1/app/'
代表一种常见的固定前缀。r'api/v2/<str:app_uid>/'
代表在前缀中,包含动态的参数。
第1种定义,没有本文的方案,也能正常使用。
v1
和v2
两种前缀,则需要本文的方案,自定义Field类。
但是,无论使用第2还是第3种方案,都必须加上第1种。
因为,在进行资源关联时,v1
和v2
的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
。
在Serializer
或ViewSet
中,均有办法获取。
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
前缀。
但无论如何,这是一个可用的有效方案,可以撑到官方支持出炉。