django-rest-framework:api版本控制

时间:2013-01-11 00:24:19

标签: django api rest django-rest-framework

所以谷歌搜索似乎普遍的共识是在REST URI中嵌入版本号是一种不好的做法,也是一个坏主意。

甚至在SO上也有强有力的支持者支持这一点 例如Best practices for API versioning?

我的问题是如何在django-rest-framework中完成使用accept header / content negotiation的提议解决方案来完成此任务。

看起来像框架中的内容协商,
http://django-rest-framework.org/api-guide/content-negotiation/ 已配置为根据接受的MIME类型自动返回预期值。如果我开始使用自定义类型的Accept标头,我将失去框架的这种好处。

在框架中有更好的方法来实现这一目标吗?

3 个答案:

答案 0 :(得分:38)

<强>更新

versioning现已得到适当支持。


您的链接有一些答案:

  

我们发现将版本放入URL非常实用且有用。它   让您轻松了解您正在使用的内容。我们做别名/ foo   to / foo /(最新版本)易于使用,更短/更清晰的URL,   等,正如公认的答案所暗示的那样。       永远保持向后兼容通常是成本过高和/或非常困难的。我们更愿意提前通知   弃用,重定向,如此处建议,文档和其他   机制。

所以我们采用了这种方法,并允许客户在请求标题(X-Version)中指定版本,以下是我们的方法:

API应用程序旁边的结构:

.
├── __init__.py
├── middlewares.py
├── urls.py
├── v1
│   ├── __init__.py
│   ├── account
│   │   ├── __init__.py
│   │   ├── serializers.py
│   │   └── views.py
│   └── urls.py
└── v2
    ├── __init__.py
    ├── account
    │   ├── __init__.py
    │   ├── serializers.py
    │   └── views.py
    └── urls.py

project urls.py:

url(r'^api/', include('project.api.urls', namespace='api')),

api app level urls.py:

from django.conf.urls import *

urlpatterns = patterns('',
    url(r'', include('project.api.v2.urls', namespace='default')),
    url(r'^v1/', include('project.api.v1.urls', namespace='v1')),
)

版本级别urls.py

from django.conf.urls import *
from .account import views as account_views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('account', account_views.AccountView)
router.register('myaccount', account_views.MyAccountView)
urlpatterns = router.urls

创建一个中间件,通过更改path_info切换到正确的代码,请注意,有一点需要注意,项目级网址中定义的命名空间('api')不灵活,需要在中间件中知道:

from django.core.urlresolvers import resolve
from django.core.urlresolvers import reverse


class VersionSwitch(object):

    def process_request(self, request):
        r = resolve(request.path_info)
        version = request.META.get('HTTP_X_VERSION', False)
        if r.namespace.startswith('api:') and version:
            old_version = r.namespace.split(':')[-1]
            request.path_info = reverse('{}:{}'.format(r.namespace.replace(old_version, version), r.url_name), args=r.args, kwargs=r.kwargs)

示例网址:

curl -H "X-Version: v1" http://your.domain:8000/api/myaccount/

答案 1 :(得分:30)

执行此操作的一种方法是将版本控制指定为媒体类型的一部分。

这就是GitHub currently do for their API

您还可以在接受标头中包含媒体类型参数,例如Accept: application/json; version=beta,它将成功与JSONRenderer匹配。然后,您可以根据所接受的媒体类型对视图进行编码,使其行为有所不同,请参阅here

在API中进行版本控制有很多不同的模式,我不会说对于正确的方法还有很多共识,但这是一种合理的可能性。


2015年1月更新:3.1.0版本中将提供更好的版本控制支持。见[此拉请求]

2015年3月更新:版本控制API are now available的文档。

https://github.com/tomchristie/django-rest-framework/pull/2285)了解更多详情。

答案 2 :(得分:0)

@James Lin 给出了很好的答案。在对答案的评论中,@Mar0ux 询问如何处理损坏的 HyperlinkedRelatedField 字段。

我通过将 HyperlinkedRelatedField 更改为 SerializerMethodField 并调用 reverse 来解决这个问题,非常不明显,将额外的参数 current_app 传递给它。

例如,我有一个应用程序“fruits_app”,命名空间版本为“v1”、“v2”。我有水果模型的序列化器。所以为了序列化 url 我创建了一个字段

url = serializers.SerializerMethodField()

以及对应的方法:

def get_url(self, instance):
    reverse.reverse('fruits_app:fruit-detail',
        args=[instance.pk],
        request=request,
        current_app=request.version)

对于嵌套命名空间,您需要更改将这些命名空间添加到 current_app。例如,如果您有一个名为“fruits_app”的应用,其命名空间版本为“v1”、“v2”和实例命名空间“bananas”,那么序列化 Fruit url 的方法将如下所示:

def get_url(self, instance):
    reverse.reverse('fruits_app:fruit-detail',
        args=[instance.pk],
        request=request,
        current_app='bananas:{}'.format(request.version))