【问题标题】:django-rest-framework: api versioningdjango-rest-framework: api 版本控制
【发布时间】:2012-12-25 12:52:55
【问题描述】:

谷歌搜索似乎普遍认为,在 REST URI 中嵌入版本号是一种不好的做法和一个坏主意。

即使在 SO 上,也有强烈的支持者支持这一点。
例如Best practices for API versioning?

我的问题是关于如何完成建议的解决方案,即在 django-rest-framework 中使用接受标头/内容协商来完成此任务。

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

在框架中是否有更好的方法来实现这一点?

【问题讨论】:

    标签: python django rest api django-rest-framework


    【解决方案1】:

    @James Lin 给出了很好的答案。在 cmets 中回答 @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))
    

    【讨论】:

      【解决方案2】:

      更新:

      versioning 现已得到适当支持。


      您的链接中有一些答案:

      我们发现将版本放在 URL 中既实用又有用。它 让您一目了然地知道您正在使用什么。我们做别名 /foo 到 /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
      

      项目 urls.py:

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

      api 应用级 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 来切换到正确的代码,请注意项目级 url 中定义的命名空间 ('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/
      

      【讨论】:

      • 这种方法很好,除了它会破坏超链接字段(HyperlinkedRelatedField 等)。有什么想法吗?
      • 我还没有项目设置可以使用HyperlinkedRelatedField 我猜你的问题是如果你指定了不同的版本,链接会转到默认版本?
      • 没错。我倾向于使用 Accept 标头版本控制方法,这样 URL 根本不会改变。
      • 恐怕没有简单的方法可以解决,直到版本控制被烘焙到包中,所以它会生成HyperlinkedRelatedField的版本感知网址
      • 那么我们把模型放在哪里呢?
      【解决方案3】:

      这样做的一种方法是将版本控制指定为媒体类型的一部分。

      这是 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) 了解更多详情。

      【讨论】:

      • 显然这个问题已经赢得了一个受欢迎的问题徽章,只是意识到我从未接受过答案。感谢您在 Tom 框架上的所有辛勤工作!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-05-31
      • 2018-09-30
      • 2014-06-23
      • 2014-08-29
      • 2017-03-18
      • 2015-03-10
      • 1970-01-01
      相关资源
      最近更新 更多