django-rest-framework/topics/2.3-announcement/index.html
2014-11-25 16:31:00 +00:00

666 lines
30 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>2.3 Announcement - Django REST framework</title>
<link href="../../img/favicon.ico" rel="icon" type="image/x-icon">
<link rel="canonical" href="http://www.django-rest-framework.org/topics/2.3-announcement/" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Django, API, REST, 2.3 Announcement">
<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">
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<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>
span.fusion-wrap a {
display: block;
margin-top: 10px;
color: black;
}
a.fusion-poweredby {
display: block;
margin-top: 10px;
}
@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/tomchristie/django-rest-framework/tree/master">GitHub</a>
<a class="repo-link btn btn-inverse btn-small " rel="prev" href="../2.4-announcement">
Next <i class="icon-arrow-right icon-white"></i>
</a>
<a class="repo-link btn btn-inverse btn-small " rel="next" href="../2.2-announcement">
<i class="icon-arrow-left icon-white"></i> Previous
</a>
<a class="repo-link btn btn-inverse btn-small" href="#searchModal" data-toggle="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="http://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">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">API Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../api-guide/requests">Requests</a>
</li>
<li >
<a href="../../api-guide/responses">Responses</a>
</li>
<li >
<a href="../../api-guide/views">Views</a>
</li>
<li >
<a href="../../api-guide/generic-views">Generic views</a>
</li>
<li >
<a href="../../api-guide/viewsets">Viewsets</a>
</li>
<li >
<a href="../../api-guide/routers">Routers</a>
</li>
<li >
<a href="../../api-guide/parsers">Parsers</a>
</li>
<li >
<a href="../../api-guide/renderers">Renderers</a>
</li>
<li >
<a href="../../api-guide/serializers">Serializers</a>
</li>
<li >
<a href="../../api-guide/fields">Serializer fields</a>
</li>
<li >
<a href="../../api-guide/relations">Serializer relations</a>
</li>
<li >
<a href="../../api-guide/authentication">Authentication</a>
</li>
<li >
<a href="../../api-guide/permissions">Permissions</a>
</li>
<li >
<a href="../../api-guide/throttling">Throttling</a>
</li>
<li >
<a href="../../api-guide/filtering">Filtering</a>
</li>
<li >
<a href="../../api-guide/pagination">Pagination</a>
</li>
<li >
<a href="../../api-guide/content-negotiation">Content negotiation</a>
</li>
<li >
<a href="../../api-guide/format-suffixes">Format suffixes</a>
</li>
<li >
<a href="../../api-guide/reverse">Returning URLs</a>
</li>
<li >
<a href="../../api-guide/exceptions">Exceptions</a>
</li>
<li >
<a href="../../api-guide/status-codes">Status codes</a>
</li>
<li >
<a href="../../api-guide/testing">Testing</a>
</li>
<li >
<a href="../../api-guide/settings">Settings</a>
</li>
</ul>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Topics <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../documenting-your-api">Documenting your API</a>
</li>
<li >
<a href="../ajax-csrf-cors">AJAX, CSRF & CORS</a>
</li>
<li >
<a href="../browser-enhancements">Browser enhancements</a>
</li>
<li >
<a href="../browsable-api">The Browsable API</a>
</li>
<li >
<a href="../rest-hypermedia-hateoas">REST, Hypermedia & HATEOAS</a>
</li>
<li >
<a href="../third-party-resources">Third Party Resources</a>
</li>
<li >
<a href="../contributing">Contributing to REST framework</a>
</li>
<li >
<a href="../rest-framework-2-announcement">2.0 Announcement</a>
</li>
<li >
<a href="../2.2-announcement">2.2 Announcement</a>
</li>
<li class="active" >
<a href=".">2.3 Announcement</a>
</li>
<li >
<a href="../2.4-announcement">2.4 Announcement</a>
</li>
<li >
<a href="../kickstarter-announcement">Kickstarter Announcement</a>
</li>
<li >
<a href="../release-notes">Release Notes</a>
</li>
<li >
<a href="../credits">Credits</a>
</li>
</ul>
</li>
</ul>
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>
<div class="body-content">
<div class="container-fluid">
<!-- Search Modal -->
<div id="searchModal" 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">
<!-- Custom google search -->
<script>
(function() {
var cx = '015016005043623903336:rxraeohqk6w';
var gcse = document.createElement('script');
gcse.type = 'text/javascript';
gcse.async = true;
gcse.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') +
'//www.google.com/cse/cse.js?cx=' + cx;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
})();
</script>
<gcse:search></gcse:search>
</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">
<!-- TODO
<p style="margin-top: -12px">
<a class="btn btn-mini btn-primary" style="width: 60px">&laquo; previous</a>
<a class="btn btn-mini btn-primary" style="float: right; margin-right: 8px; width: 60px;">next &raquo;</a>
</p>
-->
<div id="table-of-contents">
<ul class="nav nav-list side-nav well sidebar-nav-fixed">
<li class="main">
<a href="#rest-framework-23-announcement">REST framework 2.3 announcement</a>
</li>
<li>
<a href="#viewsets-and-routers">ViewSets and Routers</a>
</li>
<li>
<a href="#simpler-views">Simpler views</a>
</li>
<li>
<a href="#easier-serializers">Easier Serializers</a>
</li>
<li>
<a href="#more-flexible-filtering">More flexible filtering</a>
</li>
<li class="main">
<a href="#api-changes">API Changes</a>
</li>
<li>
<a href="#simplified-generic-view-classes">Simplified generic view classes</a>
</li>
<li>
<a href="#simpler-url-lookups">Simpler URL lookups</a>
</li>
<li>
<a href="#fileuploadparser">FileUploadParser</a>
</li>
<li>
<a href="#decimalfield">DecimalField</a>
</li>
<li>
<a href="#modelserializers-and-reverse-relationships">ModelSerializers and reverse relationships</a>
</li>
<li>
<a href="#view-names-and-descriptions">View names and descriptions</a>
</li>
<li class="main">
<a href="#other-notes">Other notes</a>
</li>
<li>
<a href="#more-explicit-style">More explicit style</a>
</li>
<li>
<a href="#django-13-support">Django 1.3 support</a>
</li>
<li>
<a href="#version-22-api-changes">Version 2.2 API changes</a>
</li>
<li>
<a href="#what-comes-next">What comes next?</a>
</li>
</ul>
</div>
</div>
<div id="main-content" class="span9">
<h1 id="rest-framework-23-announcement">REST framework 2.3 announcement</h1>
<p>REST framework 2.3 makes it even quicker and easier to build your Web APIs.</p>
<h2 id="viewsets-and-routers">ViewSets and Routers</h2>
<p>The 2.3 release introduces the <a href="../../api-guide/viewsets">ViewSet</a> and <a href="../../api-guide/routers">Router</a> classes.</p>
<p>A viewset is simply a type of class based view that allows you to group multiple views into a single common class.</p>
<p>Routers allow you to automatically determine the URLconf for your viewset classes.</p>
<p>As an example of just how simple REST framework APIs can now be, here's an API written in a single <code>urls.py</code> module:</p>
<pre><code>"""
A REST framework API for viewing and editing users and groups.
"""
from django.conf.urls.defaults import url, include
from django.contrib.auth.models import User, Group
from rest_framework import viewsets, routers
# ViewSets define the view behavior.
class UserViewSet(viewsets.ModelViewSet):
model = User
class GroupViewSet(viewsets.ModelViewSet):
model = Group
# Routers provide an easy way of automatically determining the URL conf
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'groups', GroupViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browseable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
</code></pre>
<p>The best place to get started with ViewSets and Routers is to take a look at the <a href="../../tutorial/6-viewsets-and-routers">newest section in the tutorial</a>, which demonstrates their usage.</p>
<h2 id="simpler-views">Simpler views</h2>
<p>This release rationalises the API and implementation of the generic views, dropping the dependency on Django's <code>SingleObjectMixin</code> and <code>MultipleObjectMixin</code> classes, removing a number of unneeded attributes, and generally making the implementation more obvious and easy to work with.</p>
<p>This improvement is reflected in improved documentation for the <code>GenericAPIView</code> base class, and should make it easier to determine how to override methods on the base class if you need to write customized subclasses.</p>
<h2 id="easier-serializers">Easier Serializers</h2>
<p>REST framework lets you be totally explicit regarding how you want to represent relationships, allowing you to choose between styles such as hyperlinking or primary key relationships.</p>
<p>The ability to specify exactly how you want to represent relationships is powerful, but it also introduces complexity. In order to keep things more simple, REST framework now allows you to include reverse relationships simply by including the field name in the <code>fields</code> metadata of the serializer class.</p>
<p>For example, in REST framework 2.2, reverse relationships needed to be included explicitly on a serializer class.</p>
<pre><code>class BlogSerializer(serializers.ModelSerializer):
comments = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = Blog
fields = ('id', 'title', 'created', 'comments')
</code></pre>
<p>As of 2.3, you can simply include the field name, and the appropriate serializer field will automatically be used for the relationship.</p>
<pre><code>class BlogSerializer(serializers.ModelSerializer):
"""
Don't need to specify the 'comments' field explicitly anymore.
"""
class Meta:
model = Blog
fields = ('id', 'title', 'created', 'comments')
</code></pre>
<p>Similarly, you can now easily include the primary key in hyperlinked relationships, simply by adding the field name to the metadata.</p>
<pre><code>class BlogSerializer(serializers.HyperlinkedModelSerializer):
"""
This is a hyperlinked serializer, which default to using
a field named 'url' as the primary identifier.
Note that we can now easily also add in the 'id' field.
"""
class Meta:
model = Blog
fields = ('url', 'id', 'title', 'created', 'comments')
</code></pre>
<h2 id="more-flexible-filtering">More flexible filtering</h2>
<p>The <code>FILTER_BACKEND</code> setting has moved to pending deprecation, in favor of a <code>DEFAULT_FILTER_BACKENDS</code> setting that takes a <em>list</em> of filter backend classes, instead of a single filter backend class.</p>
<p>The generic view <code>filter_backend</code> attribute has also been moved to pending deprecation in favor of a <code>filter_backends</code> setting.</p>
<p>Being able to specify multiple filters will allow for more flexible, powerful behavior. New filter classes to handle searching and ordering of results are planned to be released shortly.</p>
<hr />
<h1 id="api-changes">API Changes</h1>
<h2 id="simplified-generic-view-classes">Simplified generic view classes</h2>
<p>The functionality provided by <code>SingleObjectAPIView</code> and <code>MultipleObjectAPIView</code> base classes has now been moved into the base class <code>GenericAPIView</code>. The implementation of this base class is simple enough that providing subclasses for the base classes of detail and list views is somewhat unnecessary.</p>
<p>Additionally the base generic view no longer inherits from Django's <code>SingleObjectMixin</code> or <code>MultipleObjectMixin</code> classes, simplifying the implementation, and meaning you don't need to cross-reference across to Django's codebase.</p>
<p>Using the <code>SingleObjectAPIView</code> and <code>MultipleObjectAPIView</code> base classes continues to be supported, but will raise a <code>PendingDeprecationWarning</code>. You should instead simply use <code>GenericAPIView</code> as the base for any generic view subclasses.</p>
<h3 id="removed-attributes">Removed attributes</h3>
<p>The following attributes and methods, were previously present as part of Django's generic view implementations, but were unneeded and unused and have now been entirely removed.</p>
<ul>
<li>context_object_name</li>
<li>get_context_data()</li>
<li>get_context_object_name()</li>
</ul>
<p>The following attributes and methods, which were previously present as part of Django's generic view implementations have also been entirely removed.</p>
<ul>
<li>paginator_class</li>
<li>get_paginator()</li>
<li>get_allow_empty()</li>
<li>get_slug_field()</li>
</ul>
<p>There may be cases when removing these bits of API might mean you need to write a little more code if your view has highly customized behavior, but generally we believe that providing a coarser-grained API will make the views easier to work with, and is the right trade-off to make for the vast majority of cases.</p>
<p>Note that the listed attributes and methods have never been a documented part of the REST framework API, and as such are not covered by the deprecation policy.</p>
<h3 id="simplified-methods">Simplified methods</h3>
<p>The <code>get_object</code> and <code>get_paginate_by</code> methods no longer take an optional queryset argument. This makes overridden these methods more obvious, and a little more simple.</p>
<p>Using an optional queryset with these methods continues to be supported, but will raise a <code>PendingDeprecationWarning</code>.</p>
<p>The <code>paginate_queryset</code> method no longer takes a <code>page_size</code> argument, or returns a four-tuple of pagination information. Instead it simply takes a queryset argument, and either returns a <code>page</code> object with an appropriate page size, or returns <code>None</code>, if pagination is not configured for the view.</p>
<p>Using the <code>page_size</code> argument is still supported and will trigger the old-style return type, but will raise a <code>PendingDeprecationWarning</code>.</p>
<h3 id="deprecated-attributes">Deprecated attributes</h3>
<p>The following attributes are used to control queryset lookup, and have all been moved into a pending deprecation state.</p>
<ul>
<li>pk_url_kwarg = 'pk'</li>
<li>slug_url_kwarg = 'slug'</li>
<li>slug_field = 'slug'</li>
</ul>
<p>Their usage is replaced with a single attribute:</p>
<ul>
<li>lookup_field = 'pk'</li>
</ul>
<p>This attribute is used both as the regex keyword argument in the URL conf, and as the model field to filter against when looking up a model instance. To use non-pk based lookup, simply set the <code>lookup_field</code> argument to an alternative field, and ensure that the keyword argument in the url conf matches the field name.</p>
<p>For example, a view with 'username' based lookup might look like this:</p>
<pre><code>class UserDetail(generics.RetrieveAPIView):
lookup_field = 'username'
queryset = User.objects.all()
serializer_class = UserSerializer
</code></pre>
<p>And would have the following entry in the urlconf:</p>
<pre><code> url(r'^users/(?P&lt;username&gt;\w+)/$', UserDetail.as_view()),
</code></pre>
<p>Usage of the old-style attributes continues to be supported, but will raise a <code>PendingDeprecationWarning</code>.</p>
<p>The <code>allow_empty</code> attribute is also deprecated. To use <code>allow_empty=False</code> style behavior you should explicitly override <code>get_queryset</code> and raise an <code>Http404</code> on empty querysets.</p>
<p>For example:</p>
<pre><code>class DisallowEmptyQuerysetMixin(object):
def get_queryset(self):
queryset = super(DisallowEmptyQuerysetMixin, self).get_queryset()
if not queryset.exists():
raise Http404
return queryset
</code></pre>
<p>In our opinion removing lesser-used attributes like <code>allow_empty</code> helps us move towards simpler generic view implementations, making them more obvious to use and override, and re-enforcing the preferred style of developers writing their own base classes and mixins for custom behavior rather than relying on the configurability of the generic views.</p>
<h2 id="simpler-url-lookups">Simpler URL lookups</h2>
<p>The <code>HyperlinkedRelatedField</code> class now takes a single optional <code>lookup_field</code> argument, that replaces the <code>pk_url_kwarg</code>, <code>slug_url_kwarg</code>, and <code>slug_field</code> arguments.</p>
<p>For example, you might have a field that references it's relationship by a hyperlink based on a slug field:</p>
<pre><code> account = HyperlinkedRelatedField(read_only=True,
lookup_field='slug',
view_name='account-detail')
</code></pre>
<p>Usage of the old-style attributes continues to be supported, but will raise a <code>PendingDeprecationWarning</code>.</p>
<h2 id="fileuploadparser">FileUploadParser</h2>
<p>2.3 adds a <code>FileUploadParser</code> parser class, that supports raw file uploads, in addition to the existing multipart upload support.</p>
<h2 id="decimalfield">DecimalField</h2>
<p>2.3 introduces a <code>DecimalField</code> serializer field, which returns <code>Decimal</code> instances.</p>
<p>For most cases APIs using model fields will behave as previously, however if you are using a custom renderer, not provided by REST framework, then you may now need to add support for rendering <code>Decimal</code> instances to your renderer implementation.</p>
<h2 id="modelserializers-and-reverse-relationships">ModelSerializers and reverse relationships</h2>
<p>The support for adding reverse relationships to the <code>fields</code> option on a <code>ModelSerializer</code> class means that the <code>get_related_field</code> and <code>get_nested_field</code> method signatures have now changed.</p>
<p>In the unlikely event that you're providing a custom serializer class, and implementing these methods you should note the new call signature for both methods is now <code>(self, model_field, related_model, to_many)</code>. For reverse relationships <code>model_field</code> will be <code>None</code>.</p>
<p>The old-style signature will continue to function but will raise a <code>PendingDeprecationWarning</code>.</p>
<h2 id="view-names-and-descriptions">View names and descriptions</h2>
<p>The mechanics of how the names and descriptions used in the browseable API are generated has been modified and cleaned up somewhat.</p>
<p>If you've been customizing this behavior, for example perhaps to use <code>rst</code> markup for the browseable API, then you'll need to take a look at the implementation to see what updates you need to make.</p>
<p>Note that the relevant methods have always been private APIs, and the docstrings called them out as intended to be deprecated.</p>
<hr />
<h1 id="other-notes">Other notes</h1>
<h2 id="more-explicit-style">More explicit style</h2>
<p>The usage of <code>model</code> attribute in generic Views is still supported, but it's usage is generally being discouraged throughout the documentation, in favour of the setting the more explicit <code>queryset</code> and <code>serializer_class</code> attributes.</p>
<p>For example, the following is now the recommended style for using generic views:</p>
<pre><code>class AccountListView(generics.RetrieveAPIView):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
</code></pre>
<p>Using an explicit <code>queryset</code> and <code>serializer_class</code> attributes makes the functioning of the view more clear than using the shortcut <code>model</code> attribute.</p>
<p>It also makes the usage of the <code>get_queryset()</code> or <code>get_serializer_class()</code> methods more obvious.</p>
<pre><code>class AccountListView(generics.RetrieveAPIView):
serializer_class = MyModelSerializer
def get_queryset(self):
"""
Determine the queryset dynamically, depending on the
user making the request.
Note that overriding this method follows on more obviously now
that an explicit `queryset` attribute is the usual view style.
"""
return self.user.accounts
</code></pre>
<h2 id="django-13-support">Django 1.3 support</h2>
<p>The 2.3.x release series will be the last series to provide compatibility with Django 1.3.</p>
<h2 id="version-22-api-changes">Version 2.2 API changes</h2>
<p>All API changes in 2.2 that previously raised <code>PendingDeprecationWarning</code> will now raise a <code>DeprecationWarning</code>, which is loud by default.</p>
<h2 id="what-comes-next">What comes next?</h2>
<ul>
<li>Support for read-write nested serializers is almost complete, and due to be released in the next few weeks.</li>
<li>Extra filter backends for searching and ordering of results are planned to be added shortly.</li>
</ul>
<p>The next few months should see a renewed focus on addressing outstanding tickets. The 2.4 release is currently planned for around August-September.</p>
</div>
<!--/span-->
</div>
<!--/row-->
</div>
<!--/.fluid-container-->
</div>
<!--/.body content-->
<div id="push"></div>
</div>
<!--/.wrapper -->
<footer class="span12">
<p>Sponsored by <a href="http://dabapps.com/">DabApps</a>.</a>
</p>
</footer>
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<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>
//$('.side-nav').scrollspy()
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 to no higher than browser window
$('.side-nav').css('max-height', window.innerHeight - 130);
$(function() {
$(window).resize(function() {
$('.side-nav').css('max-height', window.innerHeight - 130);
});
});
</script>
</body>
</html>