简体   繁体   English

django-rest-framework:api 版本控制

[英]django-rest-framework: api versioning

so googling around it appears that the general consensus is that embedding version numbers in REST URIs is a bad practice and a bad idea.所以谷歌搜索似乎普遍的共识是在 REST URI 中嵌入版本号是一种不好的做法和坏主意。

even on SO there are strong proponents supporting this.即使在 SO 上,也有强烈的支持者支持这一点。
eg Best practices for API versioning?例如,API 版本控制的最佳实践?

My question is about how to accomplish the proposed solution of using the accept header / content negotiation in the django-rest-framework to accomplish this.我的问题是关于如何完成在 django-rest-framework 中使用接受标头/内容协商的建议解决方案来实现这一点。

It looks like content negotiation in the framework,看起来像框架中的内容协商,
http://django-rest-framework.org/api-guide/content-negotiation/ is already configured to automatically return intended values based on accepted MIME types. http://django-rest-framework.org/api-guide/content-negotiation/已经配置为根据接受的 MIME 类型自动返回预期值。 If I start using the Accept header for custom types, I'll lose this benefit of the framework.如果我开始对自定义类型使用 Accept 标头,我将失去框架的这一优势。

Is there a better way to accomplish this in the framework?在框架中是否有更好的方法来实现这一点?

UPDATE:更新:

versioning is now properly supported.现在正确支持版本控制


There are some answers from your link:您的链接中有一些答案:

We found it practical and useful to put the version in the URL.我们发现将版本放在 URL 中是实用且有用的。 It makes it easy to tell what you're using at a glance.它可以让您一目了然地知道您在使用什么。 We do alias /foo to /foo/(latest versions) for ease of use, shorter / cleaner URLs, etc, as the accepted answer suggests.正如公认的答案所暗示的那样,我们将 /foo 别名为 /foo/(最新版本)以方便使用、更短/更干净的 URL 等。 Keeping backwards compatibility forever is often cost-prohibitive and/or very difficult.永远保持向后兼容性通常成本高昂和/或非常困难。 We prefer to give advanced notice of deprecation, redirects like suggested here, docs, and other mechanisms.我们更愿意提前通知弃用、重定向(如此处建议的)、文档和其他机制。

So we took this approach, plus allowing clients to specify the version in request header (X-Version), here is how we did it:所以我们采用了这种方法,并允许客户端在请求头(X-Version)中指定版本,我们是这样做的:

Structure in side the API app: 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:项目 urls.py:

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

api app level urls.py: 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')),
)

version level urls.py版本级别 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

create a middleware to switch to the correct code by changing the path_info, please note there is a caveat that namespace ('api') defined in project level urls is not flexible and needs to be known in middleware:通过更改 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)

Sample url:示例网址:

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

One way of doing this is to have the versioning specified as part of the media type.这样做的一种方法是将版本控制指定为媒体类型的一部分。

This is what GitHub currently do for their API .这就是 GitHub目前为他们的 API 所做的

You can also include media type parameters in your accept headers, eg Accept: application/json; version=beta您还可以在接受标头中包含媒体类型参数,例如Accept: application/json; version=beta Accept: application/json; version=beta , which will successfully match against JSONRenderer . Accept: application/json; version=beta ,它将成功匹配JSONRenderer You can then code your view to behave differently depending on the accepted media type, see here .然后,您可以根据接受的媒体类型对视图进行编码,使其行为有所不同,请参见此处

There's lots of different patterns for versioning in APIs, and I wouldn't say there's any great consensus around the right approach yet, but that'd be one reasonable possibility. API 中的版本控制有很多不同的模式,我不会说对正确的方法有任何很大的共识,但这是一种合理的可能性。


Update Jan 2015 : Better versioning support will be incoming in the 3.1.0 release. 2015 年 1 月更新:3.1.0 版本将提供更好的版本支持。 See [this pull request]见[这个拉取请求]

Update March 2015 : Docs for the versioning API are now available . 2015 年 3 月更新:版本控制 API 的文档现已可用

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

@James Lin gave a great answer. @James Lin 给出了很好的答案。 In comments to the answer @Mar0ux asked what to do with broken HyperlinkedRelatedField fields.在对答案的评论中,@Mar0ux 询问如何处理损坏的HyperlinkedRelatedField字段。

I fixed this with changing HyperlinkedRelatedField to the SerializerMethodField and calling reverse with, very unobvious, passing extra parameter current_app to it.我通过将HyperlinkedRelatedField更改为SerializerMethodField并调用reverse来解决这个问题,非常不明显,将额外的参数current_app传递给它。

For example, I have an app 'fruits_app', with namespace versions 'v1', 'v2'.例如,我有一个应用程序“fruits_app”,命名空间版本为“v1”、“v2”。 And I have serializer of Fruit model.我有水果模型的序列化器。 So to serialize url I create a field所以为了序列化 url 我创建了一个字段

url = serializers.SerializerMethodField()

And corresponding method:以及相应的方法:

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

With nested namespaces you will need to change add those namespaces to current_app.对于嵌套命名空间,您需要更改将这些命名空间添加到 current_app。 For example, if you have an app 'fruits_app' with namespace versions 'v1', 'v2', and instance namespace 'bananas' inside the app, method for serializing Fruit url will look like:例如,如果您有一个名为“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))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM