让DRF的URL支持前缀的一种方式
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
,展示了三种定义方式。
''
代表一般的推荐用法,没有前缀。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
前缀。
但无论如何,这是一个可用的有效方案,可以撑到官方支持出炉。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK