django-rest-framework/api-guide/versioning/index.html
2021-12-13 13:11:32 +00:00

690 lines
31 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>Versioning - Django REST framework</title>
<link href="../../img/favicon.ico" rel="icon" type="image/x-icon">
<link rel="canonical" href="https://www.django-rest-framework.org/api-guide/versioning/" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Django, API, REST, Versioning">
<meta name="author" content="Tom Christie">
<!-- Le styles -->
<link href="../../css/prettify.css" rel="stylesheet">
<link href="../../css/bootstrap.css" rel="stylesheet">
<link href="../../css/bootstrap-responsive.css" rel="stylesheet">
<link href="../../css/default.css" rel="stylesheet">
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-18852272-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script>
<style>
#sidebarInclude img {
margin-bottom: 10px;
}
#sidebarInclude a.promo {
color: black;
}
@media (max-width: 767px) {
div.promo {
display: none;
}
}
</style>
</head>
<body onload="prettyPrint()" class="-page">
<div class="wrapper">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container-fluid">
<a class="repo-link btn btn-primary btn-small" href="https://github.com/encode/django-rest-framework/tree/master">GitHub</a>
<a class="repo-link btn btn-inverse btn-small " rel="next" href="../content-negotiation/">
Next <i class="icon-arrow-right icon-white"></i>
</a>
<a class="repo-link btn btn-inverse btn-small " rel="prev" href="../pagination/">
<i class="icon-arrow-left icon-white"></i> Previous
</a>
<a id="search_modal_show" class="repo-link btn btn-inverse btn-small" href="#mkdocs_search_modal" data-toggle="modal" data-target="#mkdocs_search_modal"><i class="icon-search icon-white"></i> Search</a>
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="https://www.django-rest-framework.org/">Django REST framework</a>
<div class="nav-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Tutorial <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../tutorial/quickstart/">Quickstart</a>
</li>
<li >
<a href="../../tutorial/1-serialization/">1 - Serialization</a>
</li>
<li >
<a href="../../tutorial/2-requests-and-responses/">2 - Requests and responses</a>
</li>
<li >
<a href="../../tutorial/3-class-based-views/">3 - Class based views</a>
</li>
<li >
<a href="../../tutorial/4-authentication-and-permissions/">4 - Authentication and permissions</a>
</li>
<li >
<a href="../../tutorial/5-relationships-and-hyperlinked-apis/">5 - Relationships and hyperlinked APIs</a>
</li>
<li >
<a href="../../tutorial/6-viewsets-and-routers/">6 - Viewsets and routers</a>
</li>
</ul>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">API Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../requests/">Requests</a>
</li>
<li >
<a href="../responses/">Responses</a>
</li>
<li >
<a href="../views/">Views</a>
</li>
<li >
<a href="../generic-views/">Generic views</a>
</li>
<li >
<a href="../viewsets/">Viewsets</a>
</li>
<li >
<a href="../routers/">Routers</a>
</li>
<li >
<a href="../parsers/">Parsers</a>
</li>
<li >
<a href="../renderers/">Renderers</a>
</li>
<li >
<a href="../serializers/">Serializers</a>
</li>
<li >
<a href="../fields/">Serializer fields</a>
</li>
<li >
<a href="../relations/">Serializer relations</a>
</li>
<li >
<a href="../validators/">Validators</a>
</li>
<li >
<a href="../authentication/">Authentication</a>
</li>
<li >
<a href="../permissions/">Permissions</a>
</li>
<li >
<a href="../caching/">Caching</a>
</li>
<li >
<a href="../throttling/">Throttling</a>
</li>
<li >
<a href="../filtering/">Filtering</a>
</li>
<li >
<a href="../pagination/">Pagination</a>
</li>
<li class="active" >
<a href="./">Versioning</a>
</li>
<li >
<a href="../content-negotiation/">Content negotiation</a>
</li>
<li >
<a href="../metadata/">Metadata</a>
</li>
<li >
<a href="../schemas/">Schemas</a>
</li>
<li >
<a href="../format-suffixes/">Format suffixes</a>
</li>
<li >
<a href="../reverse/">Returning URLs</a>
</li>
<li >
<a href="../exceptions/">Exceptions</a>
</li>
<li >
<a href="../status-codes/">Status codes</a>
</li>
<li >
<a href="../testing/">Testing</a>
</li>
<li >
<a href="../settings/">Settings</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Topics <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../topics/documenting-your-api/">Documenting your API</a>
</li>
<li >
<a href="../../topics/api-clients/">API Clients</a>
</li>
<li >
<a href="../../topics/internationalization/">Internationalization</a>
</li>
<li >
<a href="../../topics/ajax-csrf-cors/">AJAX, CSRF & CORS</a>
</li>
<li >
<a href="../../topics/html-and-forms/">HTML & Forms</a>
</li>
<li >
<a href="../../topics/browser-enhancements/">Browser Enhancements</a>
</li>
<li >
<a href="../../topics/browsable-api/">The Browsable API</a>
</li>
<li >
<a href="../../topics/rest-hypermedia-hateoas/">REST, Hypermedia & HATEOAS</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Community <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../community/tutorials-and-resources/">Tutorials and Resources</a>
</li>
<li >
<a href="../../community/third-party-packages/">Third Party Packages</a>
</li>
<li >
<a href="../../community/contributing/">Contributing to REST framework</a>
</li>
<li >
<a href="../../community/project-management/">Project management</a>
</li>
<li >
<a href="../../community/release-notes/">Release Notes</a>
</li>
<li >
<a href="../../community/3.13-announcement/">3.13 Announcement</a>
</li>
<li >
<a href="../../community/3.12-announcement/">3.12 Announcement</a>
</li>
<li >
<a href="../../community/3.11-announcement/">3.11 Announcement</a>
</li>
<li >
<a href="../../community/3.10-announcement/">3.10 Announcement</a>
</li>
<li >
<a href="../../community/3.9-announcement/">3.9 Announcement</a>
</li>
<li >
<a href="../../community/3.8-announcement/">3.8 Announcement</a>
</li>
<li >
<a href="../../community/3.7-announcement/">3.7 Announcement</a>
</li>
<li >
<a href="../../community/3.6-announcement/">3.6 Announcement</a>
</li>
<li >
<a href="../../community/3.5-announcement/">3.5 Announcement</a>
</li>
<li >
<a href="../../community/3.4-announcement/">3.4 Announcement</a>
</li>
<li >
<a href="../../community/3.3-announcement/">3.3 Announcement</a>
</li>
<li >
<a href="../../community/3.2-announcement/">3.2 Announcement</a>
</li>
<li >
<a href="../../community/3.1-announcement/">3.1 Announcement</a>
</li>
<li >
<a href="../../community/3.0-announcement/">3.0 Announcement</a>
</li>
<li >
<a href="../../community/kickstarter-announcement/">Kickstarter Announcement</a>
</li>
<li >
<a href="../../community/mozilla-grant/">Mozilla Grant</a>
</li>
<li >
<a href="../../community/funding/">Funding</a>
</li>
<li >
<a href="../../community/jobs/">Jobs</a>
</li>
</ul>
</li>
</ul>
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>
<div class="body-content">
<div class="container-fluid">
<!-- Search Modal -->
<div id="mkdocs_search_modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 id="myModalLabel">Documentation search</h3>
</div>
<div class="modal-body">
<form role="form" autocomplete="off">
<div class="form-group">
<input type="text" name="q" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
</div>
</div>
<div class="row-fluid">
<div class="span3">
<div id="table-of-contents">
<ul class="nav nav-list side-nav well sidebar-nav-fixed">
<li class="main">
<a href="#versioning">Versioning</a>
</li>
<li>
<a href="#versioning-with-rest-framework">Versioning with REST framework</a>
</li>
<li>
<a href="#configuring-the-versioning-scheme">Configuring the versioning scheme</a>
</li>
<li class="main">
<a href="#api-reference">API Reference</a>
</li>
<li>
<a href="#acceptheaderversioning">AcceptHeaderVersioning</a>
</li>
<li>
<a href="#urlpathversioning">URLPathVersioning</a>
</li>
<li>
<a href="#namespaceversioning">NamespaceVersioning</a>
</li>
<li>
<a href="#hostnameversioning">HostNameVersioning</a>
</li>
<li>
<a href="#queryparameterversioning">QueryParameterVersioning</a>
</li>
<li class="main">
<a href="#custom-versioning-schemes">Custom versioning schemes</a>
</li>
<li>
<a href="#example">Example</a>
</li>
<div class="promo">
<hr/>
<div id="sidebarInclude">
</div>
</ul>
</div>
</div>
<div id="main-content" class="span9">
<a class="github" href="https://github.com/encode/django-rest-framework/tree/master/rest_framework/versioning.py">
<span class="label label-info">versioning.py</span>
</a>
<h1 id="versioning"><a class="toclink" href="#versioning">Versioning</a></h1>
<blockquote>
<p>Versioning an interface is just a "polite" way to kill deployed clients.</p>
<p>&mdash; <a href="https://www.slideshare.net/evolve_conference/201308-fielding-evolve/31">Roy Fielding</a>.</p>
</blockquote>
<p>API versioning allows you to alter behavior between different clients. REST framework provides for a number of different versioning schemes.</p>
<p>Versioning is determined by the incoming client request, and may either be based on the request URL, or based on the request headers.</p>
<p>There are a number of valid approaches to approaching versioning. <a href="https://www.infoq.com/articles/roy-fielding-on-versioning">Non-versioned systems can also be appropriate</a>, particularly if you're engineering for very long-term systems with multiple clients outside of your control.</p>
<h2 id="versioning-with-rest-framework"><a class="toclink" href="#versioning-with-rest-framework">Versioning with REST framework</a></h2>
<p>When API versioning is enabled, the <code>request.version</code> attribute will contain a string that corresponds to the version requested in the incoming client request.</p>
<p>By default, versioning is not enabled, and <code>request.version</code> will always return <code>None</code>.</p>
<h4 id="varying-behavior-based-on-the-version"><a class="toclink" href="#varying-behavior-based-on-the-version">Varying behavior based on the version</a></h4>
<p>How you vary the API behavior is up to you, but one example you might typically want is to switch to a different serialization style in a newer version. For example:</p>
<pre><code>def get_serializer_class(self):
if self.request.version == 'v1':
return AccountSerializerVersion1
return AccountSerializer
</code></pre>
<h4 id="reversing-urls-for-versioned-apis"><a class="toclink" href="#reversing-urls-for-versioned-apis">Reversing URLs for versioned APIs</a></h4>
<p>The <code>reverse</code> function included by REST framework ties in with the versioning scheme. You need to make sure to include the current <code>request</code> as a keyword argument, like so.</p>
<pre><code>from rest_framework.reverse import reverse
reverse('bookings-list', request=request)
</code></pre>
<p>The above function will apply any URL transformations appropriate to the request version. For example:</p>
<ul>
<li>If <code>NamespaceVersioning</code> was being used, and the API version was 'v1', then the URL lookup used would be <code>'v1:bookings-list'</code>, which might resolve to a URL like <code>http://example.org/v1/bookings/</code>.</li>
<li>If <code>QueryParameterVersioning</code> was being used, and the API version was <code>1.0</code>, then the returned URL might be something like <code>http://example.org/bookings/?version=1.0</code></li>
</ul>
<h4 id="versioned-apis-and-hyperlinked-serializers"><a class="toclink" href="#versioned-apis-and-hyperlinked-serializers">Versioned APIs and hyperlinked serializers</a></h4>
<p>When using hyperlinked serialization styles together with a URL based versioning scheme make sure to include the request as context to the serializer.</p>
<pre><code>def get(self, request):
queryset = Booking.objects.all()
serializer = BookingsSerializer(queryset, many=True, context={'request': request})
return Response({'all_bookings': serializer.data})
</code></pre>
<p>Doing so will allow any returned URLs to include the appropriate versioning.</p>
<h2 id="configuring-the-versioning-scheme"><a class="toclink" href="#configuring-the-versioning-scheme">Configuring the versioning scheme</a></h2>
<p>The versioning scheme is defined by the <code>DEFAULT_VERSIONING_CLASS</code> settings key.</p>
<pre><code>REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning'
}
</code></pre>
<p>Unless it is explicitly set, the value for <code>DEFAULT_VERSIONING_CLASS</code> will be <code>None</code>. In this case the <code>request.version</code> attribute will always return <code>None</code>.</p>
<p>You can also set the versioning scheme on an individual view. Typically you won't need to do this, as it makes more sense to have a single versioning scheme used globally. If you do need to do so, use the <code>versioning_class</code> attribute.</p>
<pre><code>class ProfileList(APIView):
versioning_class = versioning.QueryParameterVersioning
</code></pre>
<h4 id="other-versioning-settings"><a class="toclink" href="#other-versioning-settings">Other versioning settings</a></h4>
<p>The following settings keys are also used to control versioning:</p>
<ul>
<li><code>DEFAULT_VERSION</code>. The value that should be used for <code>request.version</code> when no versioning information is present. Defaults to <code>None</code>.</li>
<li><code>ALLOWED_VERSIONS</code>. If set, this value will restrict the set of versions that may be returned by the versioning scheme, and will raise an error if the provided version is not in this set. Note that the value used for the <code>DEFAULT_VERSION</code> setting is always considered to be part of the <code>ALLOWED_VERSIONS</code> set (unless it is <code>None</code>). Defaults to <code>None</code>.</li>
<li><code>VERSION_PARAM</code>. The string that should be used for any versioning parameters, such as in the media type or URL query parameters. Defaults to <code>'version'</code>.</li>
</ul>
<p>You can also set your versioning class plus those three values on a per-view or a per-viewset basis by defining your own versioning scheme and using the <code>default_version</code>, <code>allowed_versions</code> and <code>version_param</code> class variables. For example, if you want to use <code>URLPathVersioning</code>:</p>
<pre><code>from rest_framework.versioning import URLPathVersioning
from rest_framework.views import APIView
class ExampleVersioning(URLPathVersioning):
default_version = ...
allowed_versions = ...
version_param = ...
class ExampleView(APIVIew):
versioning_class = ExampleVersioning
</code></pre>
<hr />
<h1 id="api-reference"><a class="toclink" href="#api-reference">API Reference</a></h1>
<h2 id="acceptheaderversioning"><a class="toclink" href="#acceptheaderversioning">AcceptHeaderVersioning</a></h2>
<p>This scheme requires the client to specify the version as part of the media type in the <code>Accept</code> header. The version is included as a media type parameter, that supplements the main media type.</p>
<p>Here's an example HTTP request using the accept header versioning style.</p>
<pre><code>GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/json; version=1.0
</code></pre>
<p>In the example request above <code>request.version</code> attribute would return the string <code>'1.0'</code>.</p>
<p>Versioning based on accept headers is <a href="http://blog.steveklabnik.com/posts/2011-07-03-nobody-understands-rest-or-http#i_want_my_api_to_be_versioned">generally considered</a> as <a href="https://github.com/interagent/http-api-design/blob/master/en/foundations/require-versioning-in-the-accepts-header.md">best practice</a>, although other styles may be suitable depending on your client requirements.</p>
<h4 id="using-accept-headers-with-vendor-media-types"><a class="toclink" href="#using-accept-headers-with-vendor-media-types">Using accept headers with vendor media types</a></h4>
<p>Strictly speaking the <code>json</code> media type is not specified as <a href="https://tools.ietf.org/html/rfc4627#section-6">including additional parameters</a>. If you are building a well-specified public API you might consider using a <a href="https://en.wikipedia.org/wiki/Internet_media_type#Vendor_tree">vendor media type</a>. To do so, configure your renderers to use a JSON based renderer with a custom media type:</p>
<pre><code>class BookingsAPIRenderer(JSONRenderer):
media_type = 'application/vnd.megacorp.bookings+json'
</code></pre>
<p>Your client requests would now look like this:</p>
<pre><code>GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/vnd.megacorp.bookings+json; version=1.0
</code></pre>
<h2 id="urlpathversioning"><a class="toclink" href="#urlpathversioning">URLPathVersioning</a></h2>
<p>This scheme requires the client to specify the version as part of the URL path.</p>
<pre><code>GET /v1/bookings/ HTTP/1.1
Host: example.com
Accept: application/json
</code></pre>
<p>Your URL conf must include a pattern that matches the version with a <code>'version'</code> keyword argument, so that this information is available to the versioning scheme.</p>
<pre><code>urlpatterns = [
re_path(
r'^(?P&lt;version&gt;(v1|v2))/bookings/$',
bookings_list,
name='bookings-list'
),
re_path(
r'^(?P&lt;version&gt;(v1|v2))/bookings/(?P&lt;pk&gt;[0-9]+)/$',
bookings_detail,
name='bookings-detail'
)
]
</code></pre>
<h2 id="namespaceversioning"><a class="toclink" href="#namespaceversioning">NamespaceVersioning</a></h2>
<p>To the client, this scheme is the same as <code>URLPathVersioning</code>. The only difference is how it is configured in your Django application, as it uses URL namespacing, instead of URL keyword arguments.</p>
<pre><code>GET /v1/something/ HTTP/1.1
Host: example.com
Accept: application/json
</code></pre>
<p>With this scheme the <code>request.version</code> attribute is determined based on the <code>namespace</code> that matches the incoming request path.</p>
<p>In the following example we're giving a set of views two different possible URL prefixes, each under a different namespace:</p>
<pre><code># bookings/urls.py
urlpatterns = [
re_path(r'^$', bookings_list, name='bookings-list'),
re_path(r'^(?P&lt;pk&gt;[0-9]+)/$', bookings_detail, name='bookings-detail')
]
# urls.py
urlpatterns = [
re_path(r'^v1/bookings/', include('bookings.urls', namespace='v1')),
re_path(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
]
</code></pre>
<p>Both <code>URLPathVersioning</code> and <code>NamespaceVersioning</code> are reasonable if you just need a simple versioning scheme. The <code>URLPathVersioning</code> approach might be better suitable for small ad-hoc projects, and the <code>NamespaceVersioning</code> is probably easier to manage for larger projects.</p>
<h2 id="hostnameversioning"><a class="toclink" href="#hostnameversioning">HostNameVersioning</a></h2>
<p>The hostname versioning scheme requires the client to specify the requested version as part of the hostname in the URL.</p>
<p>For example the following is an HTTP request to the <code>http://v1.example.com/bookings/</code> URL:</p>
<pre><code>GET /bookings/ HTTP/1.1
Host: v1.example.com
Accept: application/json
</code></pre>
<p>By default this implementation expects the hostname to match this simple regular expression:</p>
<pre><code>^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$
</code></pre>
<p>Note that the first group is enclosed in brackets, indicating that this is the matched portion of the hostname.</p>
<p>The <code>HostNameVersioning</code> scheme can be awkward to use in debug mode as you will typically be accessing a raw IP address such as <code>127.0.0.1</code>. There are various online tutorials on how to <a href="https://reinteractive.net/posts/199-developing-and-testing-rails-applications-with-subdomains">access localhost with a custom subdomain</a> which you may find helpful in this case.</p>
<p>Hostname based versioning can be particularly useful if you have requirements to route incoming requests to different servers based on the version, as you can configure different DNS records for different API versions.</p>
<h2 id="queryparameterversioning"><a class="toclink" href="#queryparameterversioning">QueryParameterVersioning</a></h2>
<p>This scheme is a simple style that includes the version as a query parameter in the URL. For example:</p>
<pre><code>GET /something/?version=0.1 HTTP/1.1
Host: example.com
Accept: application/json
</code></pre>
<hr />
<h1 id="custom-versioning-schemes"><a class="toclink" href="#custom-versioning-schemes">Custom versioning schemes</a></h1>
<p>To implement a custom versioning scheme, subclass <code>BaseVersioning</code> and override the <code>.determine_version</code> method.</p>
<h2 id="example"><a class="toclink" href="#example">Example</a></h2>
<p>The following example uses a custom <code>X-API-Version</code> header to determine the requested version.</p>
<pre><code>class XAPIVersionScheme(versioning.BaseVersioning):
def determine_version(self, request, *args, **kwargs):
return request.META.get('HTTP_X_API_VERSION', None)
</code></pre>
<p>If your versioning scheme is based on the request URL, you will also want to alter how versioned URLs are determined. In order to do so you should override the <code>.reverse()</code> method on the class. See the source code for examples.</p>
</div> <!--/span-->
</div> <!--/row-->
</div> <!--/.fluid-container-->
</div> <!--/.body content-->
<div id="push"></div>
</div> <!--/.wrapper -->
<footer class="span12">
<p>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.
</p>
</footer>
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script async src="https://fund.django-rest-framework.org/sidebar_include.js"></script>
<script src="../../js/jquery-1.8.1-min.js"></script>
<script src="../../js/prettify-1.0.js"></script>
<script src="../../js/bootstrap-2.1.1-min.js"></script>
<script src="../../js/theme.js"></script>
<script>var base_url = '../..';</script>
<script src="../../search/main.js" defer></script>
<script>
var shiftWindow = function() {
scrollBy(0, -50)
};
if (location.hash) shiftWindow();
window.addEventListener("hashchange", shiftWindow);
$('.dropdown-menu').on('click touchstart', function(event) {
event.stopPropagation();
});
// Dynamically force sidenav/dropdown to no higher than browser window
$('.side-nav, .dropdown-menu').css('max-height', window.innerHeight - 130);
$(function() {
$(window).resize(function() {
$('.side-nav, .dropdown-menu').css('max-height', window.innerHeight - 130);
});
});
</script>
</body>
</html>