mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-26 11:33:59 +03:00
Latest docs release
This commit is contained in:
parent
ce16580548
commit
dd14c6c88b
|
@ -206,6 +206,7 @@ a.fusion-poweredby {
|
|||
<li><a href="#json-web-token-authentication">JSON Web Token Authentication</a></li>
|
||||
<li><a href="#hawk-http-authentication">Hawk HTTP Authentication</a></li>
|
||||
<li><a href="#http-signature-authentication">HTTP Signature Authentication</a></li>
|
||||
<li><a href="#djoser">Djoser</a></li>
|
||||
|
||||
<div class="promo">
|
||||
|
||||
|
@ -337,12 +338,13 @@ print token.key
|
|||
<hr />
|
||||
<h4 id="generating-tokens">Generating Tokens</h4>
|
||||
<p>If you want every user to have an automatically generated Token, you can simply catch the User's <code>post_save</code> signal.</p>
|
||||
<pre class="prettyprint lang-py"><code>from django.contrib.auth import get_user_model
|
||||
<pre class="prettyprint lang-py"><code>from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
@receiver(post_save, sender=get_user_model())
|
||||
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
|
||||
def create_auth_token(sender, instance=None, created=False, **kwargs):
|
||||
if created:
|
||||
Token.objects.create(user=instance)
|
||||
|
@ -356,9 +358,10 @@ for user in User.objects.all():
|
|||
Token.objects.get_or_create(user=user)
|
||||
</code></pre>
|
||||
<p>When using <code>TokenAuthentication</code>, you may want to provide a mechanism for clients to obtain a token given the username and password. REST framework provides a built-in view to provide this behavior. To use it, add the <code>obtain_auth_token</code> view to your URLconf:</p>
|
||||
<pre class="prettyprint lang-py"><code>urlpatterns += patterns('',
|
||||
url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token')
|
||||
)
|
||||
<pre class="prettyprint lang-py"><code>from rest_framework.authtoken import views
|
||||
urlpatterns += [
|
||||
url(r'^api-token-auth/', views.obtain_auth_token)
|
||||
]
|
||||
</code></pre>
|
||||
<p>Note that the URL part of the pattern can be whatever you want to use.</p>
|
||||
<p>The <code>obtain_auth_token</code> view will return a JSON response when valid <code>username</code> and <code>password</code> fields are POSTed to the view using form data or JSON:</p>
|
||||
|
@ -508,6 +511,8 @@ class ExampleAuthentication(authentication.BaseAuthentication):
|
|||
<p>The <a href="http://hawkrest.readthedocs.org/en/latest/">HawkREST</a> library builds on the <a href="http://mohawk.readthedocs.org/en/latest/">Mohawk</a> library to let you work with <a href="https://github.com/hueniverse/hawk">Hawk</a> signed requests and responses in your API. <a href="https://github.com/hueniverse/hawk">Hawk</a> lets two parties securely communicate with each other using messages signed by a shared key. It is based on <a href="http://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05">HTTP MAC access authentication</a> (which was based on parts of <a href="http://oauth.net/core/1.0a">OAuth 1.0</a>).</p>
|
||||
<h2 id="http-signature-authentication">HTTP Signature Authentication</h2>
|
||||
<p>HTTP Signature (currently a <a href="https://datatracker.ietf.org/doc/draft-cavage-http-signatures/">IETF draft</a>) provides a way to achieve origin authentication and message integrity for HTTP messages. Similar to <a href="http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html">Amazon's HTTP Signature scheme</a>, used by many of its services, it permits stateless, per-request authentication. <a href="https://github.com/etoccalino/">Elvio Toccalino</a> maintains the <a href="https://github.com/etoccalino/django-rest-framework-httpsignature">djangorestframework-httpsignature</a> package which provides an easy to use HTTP Signature Authentication mechanism.</p>
|
||||
<h2 id="djoser">Djoser</h2>
|
||||
<p><a href="https://github.com/sunscrapers/djoser">Djoser</a> library provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation. The package works with a custom user model and it uses token based authentication. This is a ready to use REST implementation of Django authentication system.</p>
|
||||
</div><!--/span-->
|
||||
</div><!--/row-->
|
||||
</div><!--/.fluid-container-->
|
||||
|
|
|
@ -219,12 +219,13 @@ used all the time.</p>
|
|||
</ul>
|
||||
<p>Example:</p>
|
||||
<pre class="prettyprint lang-py"><code>from rest_framework.urlpatterns import format_suffix_patterns
|
||||
from blog import views
|
||||
|
||||
urlpatterns = patterns('blog.views',
|
||||
url(r'^/$', 'api_root'),
|
||||
url(r'^comments/$', 'comment_list'),
|
||||
url(r'^comments/(?P<pk>[0-9]+)/$', 'comment_detail')
|
||||
)
|
||||
urlpatterns = [
|
||||
url(r'^/$', views.apt_root),
|
||||
url(r'^comments/$', views.comment_list),
|
||||
url(r'^comments/(?P<pk>[0-9]+)/$', views.comment_detail)
|
||||
]
|
||||
|
||||
urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'html'])
|
||||
</code></pre>
|
||||
|
|
324
api-guide/metadata.html
Normal file
324
api-guide/metadata.html
Normal file
|
@ -0,0 +1,324 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta charset="utf-8">
|
||||
<title>Metadata - Django REST framework</title>
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//img/favicon.ico" rel="icon" type="image/x-icon">
|
||||
<link rel="canonical" href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/metadata.html"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Django, API, REST, Metadata, Custom metadata classes">
|
||||
<meta name="author" content="Tom Christie">
|
||||
|
||||
<!-- Le styles -->
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/prettify.css" rel="stylesheet">
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/bootstrap.css" rel="stylesheet">
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/bootstrap-responsive.css" rel="stylesheet">
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//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="metadata-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 disabled" href="#">Next <i class="icon-arrow-right icon-white"></i></a>
|
||||
<a class="repo-link btn btn-inverse btn-small disabled" href="#"><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="file:///Users/tomchristie/GitHub/django-rest-framework/html/index.html">Django REST framework</a>
|
||||
<div class="nav-collapse collapse">
|
||||
<ul class="nav">
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html/index.html">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="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/quickstart.html">Quickstart</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/1-serialization.html">1 - Serialization</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/2-requests-and-responses.html">2 - Requests and responses</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/3-class-based-views.html">3 - Class based views</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/4-authentication-and-permissions.html">4 - Authentication and permissions</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/5-relationships-and-hyperlinked-apis.html">5 - Relationships and hyperlinked APIs</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/6-viewsets-and-routers.html">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="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/requests.html">Requests</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/responses.html">Responses</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/views.html">Views</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/generic-views.html">Generic views</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/viewsets.html">Viewsets</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/routers.html">Routers</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/parsers.html">Parsers</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/renderers.html">Renderers</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/serializers.html">Serializers</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/fields.html">Serializer fields</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/relations.html">Serializer relations</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/validators.html">Validators</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/authentication.html">Authentication</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/permissions.html">Permissions</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/throttling.html">Throttling</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/filtering.html">Filtering</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/pagination.html">Pagination</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/content-negotiation.html">Content negotiation</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/format-suffixes.html">Format suffixes</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/reverse.html">Returning URLs</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/exceptions.html">Exceptions</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/status-codes.html">Status codes</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/testing.html">Testing</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/settings.html">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="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/documenting-your-api.html">Documenting your API</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/ajax-csrf-cors.html">AJAX, CSRF & CORS</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/browser-enhancements.html">Browser enhancements</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/browsable-api.html">The Browsable API</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/rest-hypermedia-hateoas.html">REST, Hypermedia & HATEOAS</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/third-party-resources.html">Third Party Resources</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/contributing.html">Contributing to REST framework</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/rest-framework-2-announcement.html">2.0 Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/2.2-announcement.html">2.2 Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/2.3-announcement.html">2.3 Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/2.4-announcement.html">2.4 Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/kickstarter-announcement.html">Kickstarter Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/release-notes.html">Release Notes</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/credits.html">Credits</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav pull-right">
|
||||
<!-- TODO
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Version: 2.0.0 <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#">Trunk</a></li>
|
||||
<li><a href="#">2.0.0</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">×</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">« previous</a>
|
||||
<a class="btn btn-mini btn-primary" style="float: right; margin-right: 8px; width: 60px;">next »</a>
|
||||
</p>
|
||||
-->
|
||||
<div id="table-of-contents">
|
||||
<ul class="nav nav-list side-nav well sidebar-nav-fixed">
|
||||
<li class="main"><a href="#metadata">Metadata</a></li>
|
||||
<li><a href="#setting-the-metadata-scheme">Setting the metadata scheme</a></li>
|
||||
<li><a href="#creating-schema-endpoints">Creating schema endpoints</a></li>
|
||||
<li class="main"><a href="#custom-metadata-classes">Custom metadata classes</a></li>
|
||||
<li><a href="#example">Example</a></li>
|
||||
|
||||
<div class="promo">
|
||||
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main-content" class="span9">
|
||||
<p><a class="github" href="https://github.com/tomchristie/django-rest-framework/tree/master/rest_framework/metadata.py"><span class="label label-info">metadata.py</span></a></p>
|
||||
<h1 id="metadata">Metadata</h1>
|
||||
<blockquote>
|
||||
<p>[The <code>OPTIONS</code>] method allows a client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval.</p>
|
||||
<p>— <a href="http://tools.ietf.org/html/rfc7231#section-4.3.7">RFC7231, Section 4.3.7.</a></p>
|
||||
</blockquote>
|
||||
<p>REST framework includes a configurable mechanism for determining how your API should respond to <code>OPTIONS</code> requests. This allows you to return API schema or other resource information.</p>
|
||||
<p>There are not currently any widely adopted conventions for exactly what style of response should be returned for HTTP <code>OPTIONS</code> requests, so we provide an ad-hoc style that returns some useful information.</p>
|
||||
<p>Here's an example response that demonstrates the information that is returned by default.</p>
|
||||
<pre class="prettyprint lang-py"><code>HTTP 200 OK
|
||||
Allow: GET, POST, HEAD, OPTIONS
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "To Do List",
|
||||
"description": "List existing 'To Do' items, or create a new item.",
|
||||
"renders": [
|
||||
"application/json",
|
||||
"text/html"
|
||||
],
|
||||
"parses": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data"
|
||||
],
|
||||
"actions": {
|
||||
"POST": {
|
||||
"note": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"read_only": false,
|
||||
"label": "title",
|
||||
"max_length": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="setting-the-metadata-scheme">Setting the metadata scheme</h2>
|
||||
<p>You can set the metadata class globally using the <code>'DEFAULT_METADATA_CLASS'</code> settings key:</p>
|
||||
<pre class="prettyprint lang-py"><code>REST_FRAMEWORK = {
|
||||
'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Or you can set the metadata class individually for a view:</p>
|
||||
<pre class="prettyprint lang-py"><code>class APIRoot(APIView):
|
||||
metadata_class = APIRootMetadata
|
||||
|
||||
def get(self, request, format=None):
|
||||
return Response({
|
||||
...
|
||||
})
|
||||
</code></pre>
|
||||
<p>The REST framework package only includes a single metadata class implementation, named <code>SimpleMetadata</code>. If you want to use an alternative style you'll need to implement a custom metadata class.</p>
|
||||
<h2 id="creating-schema-endpoints">Creating schema endpoints</h2>
|
||||
<p>If you have specific requirements for creating schema endpoints that are accessed with regular <code>GET</code> requests, you might consider re-using the metadata API for doing so.</p>
|
||||
<p>For example, the following additional route could be used on a viewset to provide a linkable schema endpoint.</p>
|
||||
<pre class="prettyprint lang-py"><code>@list_route(methods=['GET'])
|
||||
def schema(self, request):
|
||||
meta = self.metadata_class()
|
||||
data = meta.determine_metadata(request, self)
|
||||
return Response(data)
|
||||
</code></pre>
|
||||
<p>There are a couple of reasons that you might choose to take this approach, including that <code>OPTIONS</code> responses <a href="https://www.mnot.net/blog/2012/10/29/NO_OPTIONS">are not cacheable</a>.</p>
|
||||
<hr />
|
||||
<h1 id="custom-metadata-classes">Custom metadata classes</h1>
|
||||
<p>If you want to provide a custom metadata class you should override <code>BaseMetadata</code> and implement the <code>determine_metadata(self, request, view)</code> method.</p>
|
||||
<p>Useful things that you might want to do could include returning schema information, using a format such as <a href="http://json-schema.org/">JSON schema</a>, or returning debug information to admin users.</p>
|
||||
<h2 id="example">Example</h2>
|
||||
<p>The following class could be used to limit the information that is returned to <code>OPTIONS</code> requests.</p>
|
||||
<pre class="prettyprint lang-py"><code>class MinimalMetadata(BaseMetadata):
|
||||
"""
|
||||
Don't include field and other information for `OPTIONS` requests.
|
||||
Just return the name and description.
|
||||
"""
|
||||
def determine_metadata(self, request, view):
|
||||
return {
|
||||
'name': view.get_view_name(),
|
||||
'description': view.get_view_description()
|
||||
}
|
||||
</code></pre>
|
||||
</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="file:///Users/tomchristie/GitHub/django-rest-framework/html//js/jquery-1.8.1-min.js"></script>
|
||||
<script src="file:///Users/tomchristie/GitHub/django-rest-framework/html//js/prettify-1.0.js"></script>
|
||||
<script src="file:///Users/tomchristie/GitHub/django-rest-framework/html//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>
|
388
api-guide/validators.html
Normal file
388
api-guide/validators.html
Normal file
|
@ -0,0 +1,388 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta charset="utf-8">
|
||||
<title>Validators - Django REST framework</title>
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//img/favicon.ico" rel="icon" type="image/x-icon">
|
||||
<link rel="canonical" href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/validators.html"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Django, API, REST, Validators, Writing custom validators">
|
||||
<meta name="author" content="Tom Christie">
|
||||
|
||||
<!-- Le styles -->
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/prettify.css" rel="stylesheet">
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/bootstrap.css" rel="stylesheet">
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/bootstrap-responsive.css" rel="stylesheet">
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//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="validators-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 " href="../api-guide/authentication.html">Next <i class="icon-arrow-right icon-white"></i></a>
|
||||
<a class="repo-link btn btn-inverse btn-small " href="../api-guide/relations.html"><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="file:///Users/tomchristie/GitHub/django-rest-framework/html/index.html">Django REST framework</a>
|
||||
<div class="nav-collapse collapse">
|
||||
<ul class="nav">
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html/index.html">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="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/quickstart.html">Quickstart</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/1-serialization.html">1 - Serialization</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/2-requests-and-responses.html">2 - Requests and responses</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/3-class-based-views.html">3 - Class based views</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/4-authentication-and-permissions.html">4 - Authentication and permissions</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/5-relationships-and-hyperlinked-apis.html">5 - Relationships and hyperlinked APIs</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/6-viewsets-and-routers.html">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="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/requests.html">Requests</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/responses.html">Responses</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/views.html">Views</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/generic-views.html">Generic views</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/viewsets.html">Viewsets</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/routers.html">Routers</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/parsers.html">Parsers</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/renderers.html">Renderers</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/serializers.html">Serializers</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/fields.html">Serializer fields</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/relations.html">Serializer relations</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/validators.html">Validators</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/authentication.html">Authentication</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/permissions.html">Permissions</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/throttling.html">Throttling</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/filtering.html">Filtering</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/pagination.html">Pagination</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/content-negotiation.html">Content negotiation</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/format-suffixes.html">Format suffixes</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/reverse.html">Returning URLs</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/exceptions.html">Exceptions</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/status-codes.html">Status codes</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/testing.html">Testing</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/settings.html">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="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/documenting-your-api.html">Documenting your API</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/ajax-csrf-cors.html">AJAX, CSRF & CORS</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/browser-enhancements.html">Browser enhancements</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/browsable-api.html">The Browsable API</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/rest-hypermedia-hateoas.html">REST, Hypermedia & HATEOAS</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/third-party-resources.html">Third Party Resources</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/contributing.html">Contributing to REST framework</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/rest-framework-2-announcement.html">2.0 Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/2.2-announcement.html">2.2 Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/2.3-announcement.html">2.3 Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/2.4-announcement.html">2.4 Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/kickstarter-announcement.html">Kickstarter Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/release-notes.html">Release Notes</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/credits.html">Credits</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav pull-right">
|
||||
<!-- TODO
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Version: 2.0.0 <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#">Trunk</a></li>
|
||||
<li><a href="#">2.0.0</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">×</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">« previous</a>
|
||||
<a class="btn btn-mini btn-primary" style="float: right; margin-right: 8px; width: 60px;">next »</a>
|
||||
</p>
|
||||
-->
|
||||
<div id="table-of-contents">
|
||||
<ul class="nav nav-list side-nav well sidebar-nav-fixed">
|
||||
<li class="main"><a href="#validators">Validators</a></li>
|
||||
<li><a href="#uniquevalidator">UniqueValidator</a></li>
|
||||
<li><a href="#uniquefordatevalidator">UniqueForDateValidator</a></li>
|
||||
<li><a href="#uniqueformonthvalidator">UniqueForMonthValidator</a></li>
|
||||
<li><a href="#uniqueforyearvalidator">UniqueForYearValidator</a></li>
|
||||
<li class="main"><a href="#writing-custom-validators">Writing custom validators</a></li>
|
||||
<li><a href="#function-based">Function based</a></li>
|
||||
<li><a href="#class-based">Class based</a></li>
|
||||
|
||||
<div class="promo">
|
||||
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main-content" class="span9">
|
||||
<p><a class="github" href="https://github.com/tomchristie/django-rest-framework/tree/master/rest_framework/validators.py"><span class="label label-info">validators.py</span></a></p>
|
||||
<h1 id="validators">Validators</h1>
|
||||
<blockquote>
|
||||
<p>Validators can be useful for re-using validation logic between different types of fields.</p>
|
||||
<p>— <a href="https://docs.djangoproject.com/en/dev/ref/validators/">Django documentation</a></p>
|
||||
</blockquote>
|
||||
<p>Most of the time you're dealing with validation in REST framework you'll simply be relying on the default field validation, or writing explicit validation methods on serializer or field classes.</p>
|
||||
<p>Sometimes you'll want to place your validation logic into reusable components, so that it can easily be reused throughout your codebase. This can be achieved by using validator functions and validator classes.</p>
|
||||
<h2 id="validation-in-rest-framework">Validation in REST framework</h2>
|
||||
<p>Validation in Django REST framework serializers is handled a little differently to how validation works in Django's <code>ModelForm</code> class.</p>
|
||||
<p>With <code>ModelForm</code> the validation is performed partially on the form, and partially on the model instance. With REST framework the validation is performed entirely on the serializer class. This is advantageous for the following reasons:</p>
|
||||
<ul>
|
||||
<li>It introduces a proper separation of concerns, making your code behaviour more obvious.</li>
|
||||
<li>It is easy to switch between using shortcut <code>ModelSerializer</code> classes and using explicit <code>Serializer</code> classes. Any validation behaviour being used for <code>ModelSerializer</code> is simple to replicate.</li>
|
||||
<li>Printing the <code>repr</code> of a serializer instance will show you exactly what validation rules it applies. There's no extra hidden validation behaviour being called on the model instance.</li>
|
||||
</ul>
|
||||
<p>When you're using <code>ModelSerializer</code> all of this is handled automatically for you. If you want to drop down to using a <code>Serializer</code> classes instead, then you need to define the validation rules explicitly.</p>
|
||||
<h4 id="example">Example</h4>
|
||||
<p>As an example of how REST framework uses explicit validation, we'll take a simple model class that has a field with a uniqueness constraint.</p>
|
||||
<pre class="prettyprint lang-py"><code>class CustomerReportRecord(models.Model):
|
||||
time_raised = models.DateTimeField(default=timezone.now, editable=False)
|
||||
reference = models.CharField(unique=True, max_length=20)
|
||||
description = models.TextField()
|
||||
</code></pre>
|
||||
<p>Here's a basic <code>ModelSerializer</code> that we can use for creating or updating instances of <code>CustomerReportRecord</code>:</p>
|
||||
<pre class="prettyprint lang-py"><code>class CustomerReportSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = CustomerReportRecord
|
||||
</code></pre>
|
||||
<p>If we open up the Django shell using <code>manage.py shell</code> we can now </p>
|
||||
<pre class="prettyprint lang-py"><code>>>> from project.example.serializers import CustomerReportSerializer
|
||||
>>> serializer = CustomerReportSerializer()
|
||||
>>> print(repr(serializer))
|
||||
CustomerReportSerializer():
|
||||
id = IntegerField(label='ID', read_only=True)
|
||||
time_raised = DateTimeField(read_only=True)
|
||||
reference = CharField(max_length=20, validators=[<UniqueValidator(queryset=CustomerReportRecord.objects.all())>])
|
||||
description = CharField(style={'type': 'textarea'})
|
||||
</code></pre>
|
||||
<p>The interesting bit here is the <code>reference</code> field. We can see that the uniqueness constraint is being explicitly enforced by a validator on the serializer field.</p>
|
||||
<p>Because of this more explicit style REST framework includes a few validator classes that are not available in core Django. These classes are detailed below.</p>
|
||||
<hr />
|
||||
<h2 id="uniquevalidator">UniqueValidator</h2>
|
||||
<p>This validator can be used to enforce the <code>unique=True</code> constraint on model fields.
|
||||
It takes a single required argument, and an optional <code>messages</code> argument:</p>
|
||||
<ul>
|
||||
<li><code>queryset</code> <em>required</em> - This is the queryset against which uniqueness should be enforced.</li>
|
||||
<li><code>message</code> - The error message that should be used when validation fails.</li>
|
||||
</ul>
|
||||
<p>This validator should be applied to <em>serializer fields</em>, like so:</p>
|
||||
<pre class="prettyprint lang-py"><code>slug = SlugField(
|
||||
max_length=100,
|
||||
validators=[UniqueValidator(queryset=BlogPost.objects.all())]
|
||||
)
|
||||
</code></pre>
|
||||
<h2 id="uniquetogethervalidator">UniqueTogetherValidator</h2>
|
||||
<p>This validator can be used to enforce <code>unique_together</code> constraints on model instances.
|
||||
It has two required arguments, and a single optional <code>messages</code> argument:</p>
|
||||
<ul>
|
||||
<li><code>queryset</code> <em>required</em> - This is the queryset against which uniqueness should be enforced.</li>
|
||||
<li><code>fields</code> <em>required</em> - A list or tuple of field names which should make a unique set. These must exist as fields on the serializer class.</li>
|
||||
<li><code>message</code> - The error message that should be used when validation fails.</li>
|
||||
</ul>
|
||||
<p>The validator should be applied to <em>serializer classes</em>, like so:</p>
|
||||
<pre class="prettyprint lang-py"><code>class ExampleSerializer(serializers.Serializer):
|
||||
# ...
|
||||
class Meta:
|
||||
# ToDo items belong to a parent list, and have an ordering defined
|
||||
# by the 'position' field. No two items in a given list may share
|
||||
# the same position.
|
||||
validators = [
|
||||
UniqueTogetherValidator(
|
||||
queryset=ToDoItem.objects.all(),
|
||||
fields=('list', 'position')
|
||||
)
|
||||
]
|
||||
</code></pre>
|
||||
<h2 id="uniquefordatevalidator">UniqueForDateValidator</h2>
|
||||
<h2 id="uniqueformonthvalidator">UniqueForMonthValidator</h2>
|
||||
<h2 id="uniqueforyearvalidator">UniqueForYearValidator</h2>
|
||||
<p>These validators can be used to enforce the <code>unique_for_date</code>, <code>unique_for_month</code> and <code>unique_for_year</code> constraints on model instances. They take the following arguments:</p>
|
||||
<ul>
|
||||
<li><code>queryset</code> <em>required</em> - This is the queryset against which uniqueness should be enforced.</li>
|
||||
<li><code>field</code> <em>required</em> - A field name against which uniqueness in the given date range will be validated. This must exist as a field on the serializer class.</li>
|
||||
<li><code>date_field</code> <em>required</em> - A field name which will be used to determine date range for the uniqueness constrain. This must exist as a field on the serializer class.</li>
|
||||
<li><code>message</code> - The error message that should be used when validation fails.</li>
|
||||
</ul>
|
||||
<p>The validator should be applied to <em>serializer classes</em>, like so:</p>
|
||||
<pre class="prettyprint lang-py"><code>class ExampleSerializer(serializers.Serializer):
|
||||
# ...
|
||||
class Meta:
|
||||
# Blog posts should have a slug that is unique for the current year.
|
||||
validators = [
|
||||
UniqueForYearValidator(
|
||||
queryset=BlogPostItem.objects.all(),
|
||||
field='slug',
|
||||
date_field='published'
|
||||
)
|
||||
]
|
||||
</code></pre>
|
||||
<p>The date field that is used for the validation is always required to be present on the serializer class. You can't simply rely on a model class <code>default=...</code>, because the value being used for the default wouldn't be generated until after the validation has run.</p>
|
||||
<p>There are a couple of styles you may want to use for this depending on how you want your API to behave. If you're using <code>ModelSerializer</code> you'll probably simply rely on the defaults that REST framework generates for you, but if you are using <code>Serializer</code> or simply want more explicit control, use on of the styles demonstrated below.</p>
|
||||
<h4 id="using-with-a-writable-date-field">Using with a writable date field.</h4>
|
||||
<p>If you want the date field to be writable the only thing worth noting is that you should ensure that it is always available in the input data, either by setting a <code>default</code> argument, or by setting <code>required=True</code>.</p>
|
||||
<pre class="prettyprint lang-py"><code>published = serializers.DateTimeField(required=True)
|
||||
</code></pre>
|
||||
<h4 id="using-with-a-read-only-date-field">Using with a read-only date field.</h4>
|
||||
<p>If you want the date field to be visible, but not editable by the user, then set <code>read_only=True</code> and additionally set a <code>default=...</code> argument.</p>
|
||||
<pre class="prettyprint lang-py"><code>published = serializers.DateTimeField(read_only=True, default=timezone.now)
|
||||
</code></pre>
|
||||
<p>The field will not be writable to the user, but the default value will still be passed through to the <code>validated_data</code>.</p>
|
||||
<h4 id="using-with-a-hidden-date-field">Using with a hidden date field.</h4>
|
||||
<p>If you want the date field to be entirely hidden from the user, then use <code>HiddenField</code>. This field type does not accept user input, but instead always returns it's default value to the <code>validated_data</code> in the serializer.</p>
|
||||
<pre class="prettyprint lang-py"><code>published = serializers.HiddenField(default=timezone.now)
|
||||
</code></pre>
|
||||
<hr />
|
||||
<h1 id="writing-custom-validators">Writing custom validators</h1>
|
||||
<p>You can use any of Django's existing validators, or write your own custom validators.</p>
|
||||
<h2 id="function-based">Function based</h2>
|
||||
<p>A validator may be any callable that raises a <code>serializers.ValidationError</code> on failure.</p>
|
||||
<pre class="prettyprint lang-py"><code>def even_number(value):
|
||||
if value % 2 != 0:
|
||||
raise serializers.ValidationError('This field must be an even number.')
|
||||
</code></pre>
|
||||
<h2 id="class-based">Class based</h2>
|
||||
<p>To write a class based validator, use the <code>__call__</code> method. Class based validators are useful as they allow you to parameterize and reuse behavior.</p>
|
||||
<pre class="prettyprint lang-py"><code>class MultipleOf:
|
||||
def __init__(self, base):
|
||||
self.base = base
|
||||
|
||||
def __call__(self, value):
|
||||
if value % self.base != 0
|
||||
message = 'This field must be a multiple of %d.' % self.base
|
||||
raise serializers.ValidationError(message)
|
||||
</code></pre>
|
||||
<h4 id="using-set_context">Using <code>set_context()</code></h4>
|
||||
<p>In some advanced cases you might want a validator to be passed the serializer field it is being used with as additional context. You can do so by declaring a <code>set_context</code> method on a class based validator.</p>
|
||||
<pre class="prettyprint lang-py"><code>def set_context(self, serializer_field):
|
||||
# Determine if this is an update or a create operation.
|
||||
# In `__call__` we can then use that information to modify the validation behavior.
|
||||
self.is_update = serializer_field.parent.instance is not None
|
||||
</code></pre>
|
||||
</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="file:///Users/tomchristie/GitHub/django-rest-framework/html//js/jquery-1.8.1-min.js"></script>
|
||||
<script src="file:///Users/tomchristie/GitHub/django-rest-framework/html//js/prettify-1.0.js"></script>
|
||||
<script src="file:///Users/tomchristie/GitHub/django-rest-framework/html//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>
|
|
@ -253,7 +253,7 @@ a.fusion-poweredby {
|
|||
<h2 id="requirements">Requirements</h2>
|
||||
<p>REST framework requires the following:</p>
|
||||
<ul>
|
||||
<li>Python (2.6.5+, 2.7, 3.2, 3.3)</li>
|
||||
<li>Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)</li>
|
||||
<li>Django (1.4.2+, 1.5, 1.6, 1.7)</li>
|
||||
</ul>
|
||||
<p>The following packages are optional:</p>
|
||||
|
@ -283,10 +283,10 @@ pip install django-filter # Filtering support
|
|||
)
|
||||
</code></pre>
|
||||
<p>If you're intending to use the browsable API you'll probably also want to add REST framework's login and logout views. Add the following to your root <code>urls.py</code> file.</p>
|
||||
<pre class="prettyprint lang-py"><code>urlpatterns = patterns('',
|
||||
<pre class="prettyprint lang-py"><code>urlpatterns = [
|
||||
...
|
||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
|
||||
)
|
||||
]
|
||||
</code></pre>
|
||||
<p>Note that the URL path can be whatever you want, but you must include <code>'rest_framework.urls'</code> with the <code>'rest_framework'</code> namespace.</p>
|
||||
<h2 id="example">Example</h2>
|
||||
|
|
|
@ -223,7 +223,7 @@ a.fusion-poweredby {
|
|||
<pre class="prettyprint lang-py"><code>"""
|
||||
A REST framework API for viewing and editing users and groups.
|
||||
"""
|
||||
from django.conf.urls.defaults import url, patterns, include
|
||||
from django.conf.urls.defaults import url, include
|
||||
from django.contrib.auth.models import User, Group
|
||||
from rest_framework import viewsets, routers
|
||||
|
||||
|
@ -244,10 +244,10 @@ router.register(r'groups', GroupViewSet)
|
|||
|
||||
# Wire up our API using automatic URL routing.
|
||||
# Additionally, we include login URLs for the browseable API.
|
||||
urlpatterns = patterns('',
|
||||
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>
|
||||
|
|
|
@ -295,8 +295,8 @@ The lowest supported version of Django is now 1.4.2.</p>
|
|||
</ul>
|
||||
<h2 id="other-features">Other features</h2>
|
||||
<p>There are also a number of other features and bugfixes as <a href="release-notes#240">listed in the release notes</a>. In particular these include:</p>
|
||||
<p><a href="../api-guide/settings/#view-names-and-descriptions">Customizable view name and description functions</a> for use with the browsable API, by using the <code>VIEW_NAME_FUNCTION</code> and <code>VIEW_DESCRIPTION_FUNCTION</code> settings.</p>
|
||||
<p>Smarter <a href="../api-guide/throttling/#how-clients-are-identified">client IP identification for throttling</a>, with the addition of the <code>NUM_PROXIES</code> setting.</p>
|
||||
<p><a href="../api-guide/settings#view-names-and-descriptions">Customizable view name and description functions</a> for use with the browsable API, by using the <code>VIEW_NAME_FUNCTION</code> and <code>VIEW_DESCRIPTION_FUNCTION</code> settings.</p>
|
||||
<p>Smarter <a href="../api-guide/throttling#how-clients-are-identified">client IP identification for throttling</a>, with the addition of the <code>NUM_PROXIES</code> setting.</p>
|
||||
<p>Added the standardized <code>Retry-After</code> header to throttled responses, as per <a href="http://tools.ietf.org/html/rfc6585">RFC 6585</a>. This should now be used in preference to the custom <code>X-Throttle-Wait-Seconds</code> header which will be fully deprecated in 3.0.</p>
|
||||
<h2 id="deprecations">Deprecations</h2>
|
||||
<p>All API changes in 2.3 that previously raised <code>PendingDeprecationWarning</code> will now raise a <code>DeprecationWarning</code>, which is loud by default.</p>
|
||||
|
|
886
topics/3.0-announcement.html
Normal file
886
topics/3.0-announcement.html
Normal file
|
@ -0,0 +1,886 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta charset="utf-8">
|
||||
<title>Pre-release notes: - Django REST framework</title>
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//img/favicon.ico" rel="icon" type="image/x-icon">
|
||||
<link rel="canonical" href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/3.0-announcement.html"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Django, API, REST, REST framework 3.0">
|
||||
<meta name="author" content="Tom Christie">
|
||||
|
||||
<!-- Le styles -->
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/prettify.css" rel="stylesheet">
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/bootstrap.css" rel="stylesheet">
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//css/bootstrap-responsive.css" rel="stylesheet">
|
||||
<link href="file:///Users/tomchristie/GitHub/django-rest-framework/html//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="3.0-announcement-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 disabled" href="#">Next <i class="icon-arrow-right icon-white"></i></a>
|
||||
<a class="repo-link btn btn-inverse btn-small disabled" href="#"><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="file:///Users/tomchristie/GitHub/django-rest-framework/html/index.html">Django REST framework</a>
|
||||
<div class="nav-collapse collapse">
|
||||
<ul class="nav">
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html/index.html">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="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/quickstart.html">Quickstart</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/1-serialization.html">1 - Serialization</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/2-requests-and-responses.html">2 - Requests and responses</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/3-class-based-views.html">3 - Class based views</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/4-authentication-and-permissions.html">4 - Authentication and permissions</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/5-relationships-and-hyperlinked-apis.html">5 - Relationships and hyperlinked APIs</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//tutorial/6-viewsets-and-routers.html">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="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/requests.html">Requests</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/responses.html">Responses</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/views.html">Views</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/generic-views.html">Generic views</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/viewsets.html">Viewsets</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/routers.html">Routers</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/parsers.html">Parsers</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/renderers.html">Renderers</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/serializers.html">Serializers</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/fields.html">Serializer fields</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/relations.html">Serializer relations</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/validators.html">Validators</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/authentication.html">Authentication</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/permissions.html">Permissions</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/throttling.html">Throttling</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/filtering.html">Filtering</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/pagination.html">Pagination</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/content-negotiation.html">Content negotiation</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/format-suffixes.html">Format suffixes</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/reverse.html">Returning URLs</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/exceptions.html">Exceptions</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/status-codes.html">Status codes</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/testing.html">Testing</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//api-guide/settings.html">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="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/documenting-your-api.html">Documenting your API</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/ajax-csrf-cors.html">AJAX, CSRF & CORS</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/browser-enhancements.html">Browser enhancements</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/browsable-api.html">The Browsable API</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/rest-hypermedia-hateoas.html">REST, Hypermedia & HATEOAS</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/third-party-resources.html">Third Party Resources</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/contributing.html">Contributing to REST framework</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/rest-framework-2-announcement.html">2.0 Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/2.2-announcement.html">2.2 Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/2.3-announcement.html">2.3 Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/2.4-announcement.html">2.4 Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/kickstarter-announcement.html">Kickstarter Announcement</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/release-notes.html">Release Notes</a></li>
|
||||
<li><a href="file:///Users/tomchristie/GitHub/django-rest-framework/html//topics/credits.html">Credits</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav pull-right">
|
||||
<!-- TODO
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Version: 2.0.0 <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#">Trunk</a></li>
|
||||
<li><a href="#">2.0.0</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">×</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">« previous</a>
|
||||
<a class="btn btn-mini btn-primary" style="float: right; margin-right: 8px; width: 60px;">next »</a>
|
||||
</p>
|
||||
-->
|
||||
<div id="table-of-contents">
|
||||
<ul class="nav nav-list side-nav well sidebar-nav-fixed">
|
||||
<li><a href="#pre-release-notes:">Pre-release notes:</a></li>
|
||||
<li class="main"><a href="#rest-framework-30">REST framework 3.0</a></li>
|
||||
<li><a href="#new-features">New features</a></li>
|
||||
<li><a href="#request-objects">Request objects</a></li>
|
||||
<li><a href="#serializers">Serializers</a></li>
|
||||
<li><a href="#serializer-fields">Serializer fields</a></li>
|
||||
<li><a href="#generic-views">Generic views</a></li>
|
||||
<li><a href="#the-metadata-api">The metadata API</a></li>
|
||||
<li><a href="#api-style">API style</a></li>
|
||||
<li><a href="#whats-coming-next">What's coming next.</a></li>
|
||||
|
||||
<div class="promo">
|
||||
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main-content" class="span9">
|
||||
<h2 id="pre-release-notes">Pre-release notes:</h2>
|
||||
<p>The 3.0 release is now ready for some tentative testing and upgrades for super keen early adopters. You can install the development version directly from GitHub like so:</p>
|
||||
<pre class="prettyprint lang-py"><code>pip install https://github.com/tomchristie/django-rest-framework/archive/version-3.0.zip
|
||||
</code></pre>
|
||||
<p>See the <a href="https://github.com/tomchristie/django-rest-framework/pull/1800">Version 3.0 GitHub issue</a> for more details on remaining work.</p>
|
||||
<p>The most notable outstanding issues still to be resolved on the <code>version-3.0</code> branch are as follows:</p>
|
||||
<ul>
|
||||
<li>Finish forms support for serializers and in the browsable API.</li>
|
||||
<li>Optimisations for serializing primary keys.</li>
|
||||
<li>Refine style of validation errors in some cases, such as validation errors in <code>ListField</code>.</li>
|
||||
</ul>
|
||||
<p><strong>Your feedback on the upgrade process and 3.0 changes is hugely important!</strong></p>
|
||||
<p>Please do get in touch via twitter, IRC, a GitHub ticket, or the discussion group.</p>
|
||||
<hr />
|
||||
<h1 id="rest-framework-30">REST framework 3.0</h1>
|
||||
<p>The 3.0 release of Django REST framework is the result of almost four years of iteration and refinement. It comprehensively addresses some of the previous remaining design issues in serializers, fields and the generic views.</p>
|
||||
<p>This release is incremental in nature. There <em>are</em> some breaking API changes, and upgrading <em>will</em> require you to read the release notes carefully, but the migration path should otherwise be relatively straightforward.</p>
|
||||
<p>The difference in quality of the REST framework API and implementation should make writing, maintaining and debugging your application far easier.</p>
|
||||
<h2 id="new-features">New features</h2>
|
||||
<p>Notable features of this new release include:</p>
|
||||
<ul>
|
||||
<li>Printable representations on serializers that allow you to inspect exactly what fields are present on the instance.</li>
|
||||
<li>Simple model serializers that are vastly easier to understand and debug, and that make it easy to switch between the implicit <code>ModelSerializer</code> class and the explicit <code>Serializer</code> class.</li>
|
||||
<li>A new <code>BaseSerializer</code> class, making it easier to write serializers for alternative storage backends, or to completely customize your serialization and validation logic.</li>
|
||||
<li>A cleaner fields API plus new <code>ListField</code> and <code>MultipleChoiceField</code> classes.</li>
|
||||
<li>Super simple default implementations for the generic views.</li>
|
||||
<li>Support for overriding how validation errors are handled by your API.</li>
|
||||
<li>A metadata API that allows you to customize how <code>OPTIONS</code> requests are handled by your API.</li>
|
||||
<li>A more compact JSON output with unicode style encoding turned on by default.</li>
|
||||
</ul>
|
||||
<p>Below is an in-depth guide to the API changes and migration notes for 3.0.</p>
|
||||
<hr />
|
||||
<h2 id="request-objects">Request objects</h2>
|
||||
<h4 id="the-data-and-query_params-properties">The <code>.data</code> and <code>.query_params</code> properties.</h4>
|
||||
<p>The usage of <code>request.DATA</code> and <code>request.FILES</code> is now pending deprecation in favor of a single <code>request.data</code> attribute that contains <em>all</em> the parsed data.</p>
|
||||
<p>Having separate attributes is reasonable for web applications that only ever parse url-encoded or multipart requests, but makes less sense for the general-purpose request parsing that REST framework supports.</p>
|
||||
<p>You may now pass all the request data to a serializer class in a single argument:</p>
|
||||
<pre class="prettyprint lang-py"><code># Do this...
|
||||
ExampleSerializer(data=request.data)
|
||||
</code></pre>
|
||||
<p>Instead of passing the files argument separately:</p>
|
||||
<pre class="prettyprint lang-py"><code># Don't do this...
|
||||
ExampleSerializer(data=request.DATA, files=request.FILES)
|
||||
</code></pre>
|
||||
<p>The usage of <code>request.QUERY_PARAMS</code> is now pending deprecation in favor of the lowercased <code>request.query_params</code>.</p>
|
||||
<h2 id="serializers">Serializers</h2>
|
||||
<h4 id="single-step-object-creation">Single-step object creation.</h4>
|
||||
<p>Previously the serializers used a two-step object creation, as follows:</p>
|
||||
<ol>
|
||||
<li>Validating the data would create an object instance. This instance would be available as <code>serializer.object</code>.</li>
|
||||
<li>Calling <code>serializer.save()</code> would then save the object instance to the database.</li>
|
||||
</ol>
|
||||
<p>This style is in-line with how the <code>ModelForm</code> class works in Django, but is problematic for a number of reasons:</p>
|
||||
<ul>
|
||||
<li>Some data, such as many-to-many relationships, cannot be added to the object instance until after it has been saved. This type of data needed to be hidden in some undocumented state on the object instance, or kept as state on the serializer instance so that it could be used when <code>.save()</code> is called.</li>
|
||||
<li>Instantiating model instances directly means that you cannot use model manager classes for instance creation, eg <code>ExampleModel.objects.create(...)</code>. Manager classes are an excellent layer at which to enforce business logic and application-level data constraints.</li>
|
||||
<li>The two step process makes it unclear where to put deserialization logic. For example, should extra attributes such as the current user get added to the instance during object creation or during object save?</li>
|
||||
</ul>
|
||||
<p>We now use single-step object creation, like so:</p>
|
||||
<ol>
|
||||
<li>Validating the data makes the cleaned data available as <code>serializer.validated_data</code>.</li>
|
||||
<li>Calling <code>serializer.save()</code> then saves and returns the new object instance.</li>
|
||||
</ol>
|
||||
<p>The resulting API changes are further detailed below.</p>
|
||||
<h4 id="the-create-and-update-methods">The <code>.create()</code> and <code>.update()</code> methods.</h4>
|
||||
<p>The <code>.restore_object()</code> method is now replaced with two separate methods, <code>.create()</code> and <code>.update()</code>.</p>
|
||||
<p>When using the <code>.create()</code> and <code>.update()</code> methods you should both create <em>and save</em> the object instance. This is in contrast to the previous <code>.restore_object()</code> behavior that would instantiate the object but not save it.</p>
|
||||
<p>The following example from the tutorial previously used <code>restore_object()</code> to handle both creating and updating object instances.</p>
|
||||
<pre class="prettyprint lang-py"><code>def restore_object(self, attrs, instance=None):
|
||||
if instance:
|
||||
# Update existing instance
|
||||
instance.title = attrs.get('title', instance.title)
|
||||
instance.code = attrs.get('code', instance.code)
|
||||
instance.linenos = attrs.get('linenos', instance.linenos)
|
||||
instance.language = attrs.get('language', instance.language)
|
||||
instance.style = attrs.get('style', instance.style)
|
||||
return instance
|
||||
|
||||
# Create new instance
|
||||
return Snippet(**attrs)
|
||||
</code></pre>
|
||||
<p>This would now be split out into two separate methods.</p>
|
||||
<pre class="prettyprint lang-py"><code>def update(self, instance, validated_data):
|
||||
instance.title = validated_data.get('title', instance.title)
|
||||
instance.code = validated_data.get('code', instance.code)
|
||||
instance.linenos = validated_data.get('linenos', instance.linenos)
|
||||
instance.language = validated_data.get('language', instance.language)
|
||||
instance.style = validated_data.get('style', instance.style)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
def create(self, validated_data):
|
||||
return Snippet.objects.create(**validated_data)
|
||||
</code></pre>
|
||||
<p>Note that these methods should return the newly created object instance.</p>
|
||||
<h4 id="use-validated_data-instead-of-object">Use <code>.validated_data</code> instead of <code>.object</code>.</h4>
|
||||
<p>You must now use the <code>.validated_data</code> attribute if you need to inspect the data before saving, rather than using the <code>.object</code> attribute, which no longer exists.</p>
|
||||
<p>For example the following code <em>is no longer valid</em>:</p>
|
||||
<pre class="prettyprint lang-py"><code>if serializer.is_valid():
|
||||
name = serializer.object.name # Inspect validated field data.
|
||||
logging.info('Creating ticket "%s"' % name)
|
||||
serializer.object.user = request.user # Include the user when saving.
|
||||
serializer.save()
|
||||
</code></pre>
|
||||
<p>Instead of using <code>.object</code> to inspect a partially constructed instance, you would now use <code>.validated_data</code> to inspect the cleaned incoming values. Also you can't set extra attributes on the instance directly, but instead pass them to the <code>.save()</code> method as keyword arguments.</p>
|
||||
<p>The corresponding code would now look like this:</p>
|
||||
<pre class="prettyprint lang-py"><code>if serializer.is_valid():
|
||||
name = serializer.validated_data['name'] # Inspect validated field data.
|
||||
logging.info('Creating ticket "%s"' % name)
|
||||
serializer.save(user=request.user) # Include the user when saving.
|
||||
</code></pre>
|
||||
<h4 id="using-serializersvalidationerror">Using <code>serializers.ValidationError</code>.</h4>
|
||||
<p>Previously <code>serializers.ValidationError</code> error was simply a synonym for <code>django.core.exceptions.ValidationError</code>. This has now been altered so that it inherits from the standard <code>APIException</code> base class.</p>
|
||||
<p>The reason behind this is that Django's <code>ValidationError</code> class is intended for use with HTML forms and its API makes using it slightly awkward with nested validation errors that can occur in serializers.</p>
|
||||
<p>For most users this change shouldn't require any updates to your codebase, but it is worth ensuring that whenever raising validation errors you are always using the <code>serializers.ValidationError</code> exception class, and not Django's built-in exception.</p>
|
||||
<p>We strongly recommend that you use the namespaced import style of <code>import serializers</code> and not <code>from serializers import ValidationError</code> in order to avoid any potential confusion.</p>
|
||||
<h4 id="change-to-validate_field_name">Change to <code>validate_<field_name></code>.</h4>
|
||||
<p>The <code>validate_<field_name></code> method hooks that can be attached to serializer classes change their signature slightly and return type. Previously these would take a dictionary of all incoming data, and a key representing the field name, and would return a dictionary including the validated data for that field:</p>
|
||||
<pre class="prettyprint lang-py"><code>def validate_score(self, attrs, source):
|
||||
if attrs[score] % 10 != 0:
|
||||
raise serializers.ValidationError('This field should be a multiple of ten.')
|
||||
return attrs
|
||||
</code></pre>
|
||||
<p>This is now simplified slightly, and the method hooks simply take the value to be validated, and return the validated value.</p>
|
||||
<pre class="prettyprint lang-py"><code>def validate_score(self, value):
|
||||
if value % 10 != 0:
|
||||
raise serializers.ValidationError('This field should be a multiple of ten.')
|
||||
return value
|
||||
</code></pre>
|
||||
<p>Any ad-hoc validation that applies to more than one field should go in the <code>.validate(self, attrs)</code> method as usual.</p>
|
||||
<p>Because <code>.validate_<field_name></code> would previously accept the complete dictionary of attributes, it could be used to validate a field depending on the input in another field. Now if you need to do this you should use <code>.validate()</code> instead.</p>
|
||||
<p>You can either return <code>non_field_errors</code> from the validate method by raising a simple <code>ValidationError</code></p>
|
||||
<pre class="prettyprint lang-py"><code>def validate(self, attrs):
|
||||
# serializer.errors == {'non_field_errors': ['A non field error']}
|
||||
raise serailizers.ValidationError('A non field error')
|
||||
</code></pre>
|
||||
<p>Alternatively if you want the errors to be against a specific field, use a dictionary of when instantiating the <code>ValidationError</code>, like so:</p>
|
||||
<pre class="prettyprint lang-py"><code>def validate(self, attrs):
|
||||
# serializer.errors == {'my_field': ['A field error']}
|
||||
raise serailizers.ValidationError({'my_field': 'A field error'})
|
||||
</code></pre>
|
||||
<p>This ensures you can still write validation that compares all the input fields, but that marks the error against a particular field.</p>
|
||||
<h4 id="limitations-of-modelserializer-validation">Limitations of ModelSerializer validation.</h4>
|
||||
<p>This change also means that we no longer use the <code>.full_clean()</code> method on model instances, but instead perform all validation explicitly on the serializer. This gives a cleaner separation, and ensures that there's no automatic validation behavior on <code>ModelSerializer</code> classes that can't also be easily replicated on regular <code>Serializer</code> classes.</p>
|
||||
<p>This change comes with the following limitations:</p>
|
||||
<ul>
|
||||
<li>The model <code>.clean()</code> method will not be called as part of serializer validation. Use the serializer <code>.validate()</code> method to perform a final validation step on incoming data where required.</li>
|
||||
<li>The <code>.unique_for_date</code>, <code>.unique_for_month</code> and <code>.unique_for_year</code> options on model fields are not automatically validated. Again, you'll need to handle these explicitly on the serializer if required.</li>
|
||||
</ul>
|
||||
<h4 id="writable-nested-serialization">Writable nested serialization.</h4>
|
||||
<p>REST framework 2.x attempted to automatically support writable nested serialization, but the behavior was complex and non-obvious. Attempting to automatically handle these case is problematic:</p>
|
||||
<ul>
|
||||
<li>There can be complex dependencies involved in order of saving multiple related model instances.</li>
|
||||
<li>It's unclear what behavior the user should expect when related models are passed <code>None</code> data.</li>
|
||||
<li>It's unclear how the user should expect to-many relationships to handle updates, creations and deletions of multiple records.</li>
|
||||
</ul>
|
||||
<p>Using the <code>depth</code> option on <code>ModelSerializer</code> will now create <strong>read-only nested serializers</strong> by default.</p>
|
||||
<p>If you try to use a writable nested serializer without writing a custom <code>create()</code> and/or <code>update()</code> method you'll see an assertion error when you attempt to save the serializer. For example:</p>
|
||||
<pre class="prettyprint lang-py"><code>>>> class ProfileSerializer(serializers.ModelSerializer):
|
||||
>>> class Meta:
|
||||
>>> model = Profile
|
||||
>>> fields = ('address', 'phone')
|
||||
>>>
|
||||
>>> class UserSerializer(serializers.ModelSerializer):
|
||||
>>> profile = ProfileSerializer()
|
||||
>>> class Meta:
|
||||
>>> model = User
|
||||
>>> fields = ('username', 'email', 'profile')
|
||||
>>>
|
||||
>>> data = {
|
||||
>>> 'username': 'lizzy',
|
||||
>>> 'email': 'lizzy@example.com',
|
||||
>>> 'profile': {'address': '123 Acacia Avenue', 'phone': '01273 100200'}
|
||||
>>> }
|
||||
>>>
|
||||
>>> serializer = UserSerializer(data=data)
|
||||
>>> serializer.save()
|
||||
AssertionError: The `.create()` method does not suport nested writable fields by default. Write an explicit `.create()` method for serializer `UserSerializer`, or set `read_only=True` on nested serializer fields.
|
||||
</code></pre>
|
||||
<p>To use writable nested serialization you'll want to declare a nested field on the serializer class, and write the <code>create()</code> and/or <code>update()</code> methods explicitly.</p>
|
||||
<pre class="prettyprint lang-py"><code>class UserSerializer(serializers.ModelSerializer):
|
||||
profile = ProfileSerializer()
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'email', 'profile')
|
||||
|
||||
def create(self, validated_data):
|
||||
profile_data = validated_data.pop['profile']
|
||||
user = User.objects.create(**validated_data)
|
||||
Profile.objects.create(user=user, **profile_data)
|
||||
return user
|
||||
</code></pre>
|
||||
<p>The single-step object creation makes this far simpler and more obvious than the previous <code>.restore_object()</code> behavior.</p>
|
||||
<h4 id="printable-serializer-representations">Printable serializer representations.</h4>
|
||||
<p>Serializer instances now support a printable representation that allows you to inspect the fields present on the instance.</p>
|
||||
<p>For instance, given the following example model:</p>
|
||||
<pre class="prettyprint lang-py"><code>class LocationRating(models.Model):
|
||||
location = models.CharField(max_length=100)
|
||||
rating = models.IntegerField()
|
||||
created_by = models.ForeignKey(User)
|
||||
</code></pre>
|
||||
<p>Let's create a simple <code>ModelSerializer</code> class corresponding to the <code>LocationRating</code> model.</p>
|
||||
<pre class="prettyprint lang-py"><code>class LocationRatingSerializer(serializer.ModelSerializer):
|
||||
class Meta:
|
||||
model = LocationRating
|
||||
</code></pre>
|
||||
<p>We can now inspect the serializer representation in the Django shell, using <code>python manage.py shell</code>...</p>
|
||||
<pre class="prettyprint lang-py"><code>>>> serializer = LocationRatingSerializer()
|
||||
>>> print(serializer) # Or use `print serializer` in Python 2.x
|
||||
LocationRatingSerializer():
|
||||
id = IntegerField(label='ID', read_only=True)
|
||||
location = CharField(max_length=100)
|
||||
rating = IntegerField()
|
||||
created_by = PrimaryKeyRelatedField(queryset=User.objects.all())
|
||||
</code></pre>
|
||||
<h4 id="the-extra_kwargs-option">The <code>extra_kwargs</code> option.</h4>
|
||||
<p>The <code>write_only_fields</code> option on <code>ModelSerializer</code> has been moved to <code>PendingDeprecation</code> and replaced with a more generic <code>extra_kwargs</code>.</p>
|
||||
<pre class="prettyprint lang-py"><code>class MySerializer(serializer.ModelSerializer):
|
||||
class Meta:
|
||||
model = MyModel
|
||||
fields = ('id', 'email', 'notes', 'is_admin')
|
||||
extra_kwargs = {
|
||||
'is_admin': {'write_only': True}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Alternatively, specify the field explicitly on the serializer class:</p>
|
||||
<pre class="prettyprint lang-py"><code>class MySerializer(serializer.ModelSerializer):
|
||||
is_admin = serializers.BooleanField(write_only=True)
|
||||
|
||||
class Meta:
|
||||
model = MyModel
|
||||
fields = ('id', 'email', 'notes', 'is_admin')
|
||||
</code></pre>
|
||||
<p>The <code>read_only_fields</code> option remains as a convenient shortcut for the more common case. </p>
|
||||
<h4 id="changes-to-hyperlinkedmodelserializer">Changes to <code>HyperlinkedModelSerializer</code>.</h4>
|
||||
<p>The <code>view_name</code> and <code>lookup_field</code> options have been moved to <code>PendingDeprecation</code>. They are no longer required, as you can use the <code>extra_kwargs</code> argument instead:</p>
|
||||
<pre class="prettyprint lang-py"><code>class MySerializer(serializer.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = MyModel
|
||||
fields = ('url', 'email', 'notes', 'is_admin')
|
||||
extra_kwargs = {
|
||||
'url': {'lookup_field': 'uuid'}
|
||||
}
|
||||
</code></pre>
|
||||
<p>Alternatively, specify the field explicitly on the serializer class:</p>
|
||||
<pre class="prettyprint lang-py"><code>class MySerializer(serializer.HyperlinkedModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name='mymodel-detail',
|
||||
lookup_field='uuid'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = MyModel
|
||||
fields = ('url', 'email', 'notes', 'is_admin')
|
||||
</code></pre>
|
||||
<h4 id="fields-for-model-methods-and-properties">Fields for model methods and properties.</h4>
|
||||
<p>With <code>ModelSerilizer</code> you can now specify field names in the <code>fields</code> option that refer to model methods or properties. For example, suppose you have the following model:</p>
|
||||
<pre class="prettyprint lang-py"><code>class Invitation(models.Model):
|
||||
created = models.DateTimeField()
|
||||
to_email = models.EmailField()
|
||||
message = models.CharField(max_length=1000)
|
||||
|
||||
def expiry_date(self):
|
||||
return self.created + datetime.timedelta(days=30)
|
||||
</code></pre>
|
||||
<p>You can include <code>expiry_date</code> as a field option on a <code>ModelSerializer</code> class.</p>
|
||||
<pre class="prettyprint lang-py"><code>class InvitationSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Invitation
|
||||
fields = ('to_email', 'message', 'expiry_date')
|
||||
</code></pre>
|
||||
<p>These fields will be mapped to <code>serializers.ReadOnlyField()</code> instances.</p>
|
||||
<pre class="prettyprint lang-py"><code>>>> serializer = InvitationSerializer()
|
||||
>>> print repr(serializer)
|
||||
InvitationSerializer():
|
||||
to_email = EmailField(max_length=75)
|
||||
message = CharField(max_length=1000)
|
||||
expiry_date = ReadOnlyField()
|
||||
</code></pre>
|
||||
<h4 id="the-listserializer-class">The <code>ListSerializer</code> class.</h4>
|
||||
<p>The <code>ListSerializer</code> class has now been added, and allows you to create base serializer classes for only accepting multiple inputs.</p>
|
||||
<pre class="prettyprint lang-py"><code>class MultipleUserSerializer(ListSerializer):
|
||||
child = UserSerializer()
|
||||
</code></pre>
|
||||
<p>You can also still use the <code>many=True</code> argument to serializer classes. It's worth noting that <code>many=True</code> argument transparently creates a <code>ListSerializer</code> instance, allowing the validation logic for list and non-list data to be cleanly separated in the REST framework codebase. </p>
|
||||
<p>See also the new <code>ListField</code> class, which validates input in the same way, but does not include the serializer interfaces of <code>.is_valid()</code>, <code>.data</code>, <code>.save()</code> and so on.</p>
|
||||
<h4 id="the-baseserializer-class">The <code>BaseSerializer</code> class.</h4>
|
||||
<p>REST framework now includes a simple <code>BaseSerializer</code> class that can be used to easily support alternative serialization and deserialization styles.</p>
|
||||
<p>This class implements the same basic API as the <code>Serializer</code> class:</p>
|
||||
<ul>
|
||||
<li><code>.data</code> - Returns the outgoing primitive representation.</li>
|
||||
<li><code>.is_valid()</code> - Deserializes and validates incoming data.</li>
|
||||
<li><code>.validated_data</code> - Returns the validated incoming data.</li>
|
||||
<li><code>.errors</code> - Returns an errors during validation.</li>
|
||||
<li><code>.save()</code> - Persists the validated data into an object instance.</li>
|
||||
</ul>
|
||||
<p>There are four mathods that can be overriding, depending on what functionality you want the serializer class to support:</p>
|
||||
<ul>
|
||||
<li><code>.to_representation()</code> - Override this to support serialization, for read operations.</li>
|
||||
<li><code>.to_internal_value()</code> - Override this to support deserialization, for write operations.</li>
|
||||
<li><code>.create()</code> and <code>.update()</code> - Overide either or both of these to support saving instances.</li>
|
||||
</ul>
|
||||
<h5 id="read-only-baseserializer-classes">Read-only <code>BaseSerializer</code> classes.</h5>
|
||||
<p>To implement a read-only serializer using the <code>BaseSerializer</code> class, we just need to override the <code>.to_representation()</code> method. Let's take a look at an example using a simple Django model:</p>
|
||||
<pre class="prettyprint lang-py"><code>class HighScore(models.Model):
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
player_name = models.CharField(max_length=10)
|
||||
score = models.IntegerField()
|
||||
</code></pre>
|
||||
<p>It's simple to create a read-only serializer for converting <code>HighScore</code> instances into primitive data types.</p>
|
||||
<pre class="prettyprint lang-py"><code>class HighScoreSerializer(serializers.BaseSerializer):
|
||||
def to_representation(self, obj):
|
||||
return {
|
||||
'score': obj.score,
|
||||
'player_name': obj.player_name
|
||||
}
|
||||
</code></pre>
|
||||
<p>We can now use this class to serialize single <code>HighScore</code> instances:</p>
|
||||
<pre class="prettyprint lang-py"><code>@api_view(['GET'])
|
||||
def high_score(request, pk):
|
||||
instance = HighScore.objects.get(pk=pk)
|
||||
serializer = HighScoreSerializer(instance)
|
||||
return Response(serializer.data)
|
||||
</code></pre>
|
||||
<p>Or use it to serialize multiple instances:</p>
|
||||
<pre class="prettyprint lang-py"><code>@api_view(['GET'])
|
||||
def all_high_scores(request):
|
||||
queryset = HighScore.objects.order_by('-score')
|
||||
serializer = HighScoreSerializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
</code></pre>
|
||||
<h5 id="read-write-baseserializer-classes">Read-write <code>BaseSerializer</code> classes.</h5>
|
||||
<p>To create a read-write serializer we first need to implement a <code>.to_internal_value()</code> method. This method returns the validated values that will be used to construct the object instance, and may raise a <code>ValidationError</code> if the supplied data is in an incorrect format.</p>
|
||||
<p>Once you've implemented <code>.to_internal_value()</code>, the basic validation API will be available on the serializer, and you will be able to use <code>.is_valid()</code>, <code>.validated_data</code> and <code>.errors</code>.</p>
|
||||
<p>If you want to also support <code>.save()</code> you'll need to also implement either or both of the <code>.create()</code> and <code>.update()</code> methods.</p>
|
||||
<p>Here's a complete example of our previous <code>HighScoreSerializer</code>, that's been updated to support both read and write operations.</p>
|
||||
<pre class="prettyprint lang-py"><code>class HighScoreSerializer(serializers.BaseSerializer):
|
||||
def to_internal_value(self, data):
|
||||
score = data.get('score')
|
||||
player_name = data.get('player_name')
|
||||
|
||||
# Perform the data validation.
|
||||
if not score:
|
||||
raise ValidationError({
|
||||
'score': 'This field is required.'
|
||||
})
|
||||
if not player_name:
|
||||
raise ValidationError({
|
||||
'player_name': 'This field is required.'
|
||||
})
|
||||
if len(player_name) > 10:
|
||||
raise ValidationError({
|
||||
'player_name': 'May not be more than 10 characters.'
|
||||
})
|
||||
|
||||
# Return the validated values. This will be available as
|
||||
# the `.validated_data` property.
|
||||
return {
|
||||
'score': int(score),
|
||||
'player_name': player_name
|
||||
}
|
||||
|
||||
def to_representation(self, obj):
|
||||
return {
|
||||
'score': obj.score,
|
||||
'player_name': obj.player_name
|
||||
}
|
||||
|
||||
def create(self, validated_data):
|
||||
return HighScore.objects.create(**validated_data)
|
||||
</code></pre>
|
||||
<h4 id="creating-new-generic-serializers-with-baseserializer">Creating new generic serializers with <code>BaseSerializer</code>.</h4>
|
||||
<p>The <code>BaseSerializer</code> class is also useful if you want to implement new generic serializer classes for dealing with particular serialization styles, or for integrating with alternative storage backends.</p>
|
||||
<p>The following class is an example of a generic serializer that can handle coercing aribitrary objects into primitive representations. </p>
|
||||
<pre class="prettyprint lang-py"><code>class ObjectSerializer(serializers.BaseSerializer):
|
||||
"""
|
||||
A read-only serializer that coerces arbitrary complex objects
|
||||
into primitive representations.
|
||||
"""
|
||||
def to_representation(self, obj):
|
||||
for attribute_name in dir(obj):
|
||||
attribute = getattr(obj, attribute_name)
|
||||
if attribute_name('_'):
|
||||
# Ignore private attributes.
|
||||
pass
|
||||
elif hasattr(attribute, '__call__'):
|
||||
# Ignore methods and other callables.
|
||||
pass
|
||||
elif isinstance(attribute, (str, int, bool, float, type(None))):
|
||||
# Primitive types can be passed through unmodified.
|
||||
output[attribute_name] = attribute
|
||||
elif isinstance(attribute, list):
|
||||
# Recursivly deal with items in lists.
|
||||
output[attribute_name] = [
|
||||
self.to_representation(item) for item in attribute
|
||||
]
|
||||
elif isinstance(attribute, dict):
|
||||
# Recursivly deal with items in dictionarys.
|
||||
output[attribute_name] = {
|
||||
str(key): self.to_representation(value)
|
||||
for key, value in attribute.items()
|
||||
}
|
||||
else:
|
||||
# Force anything else to its string representation.
|
||||
output[attribute_name] = str(attribute)
|
||||
</code></pre>
|
||||
<h2 id="serializer-fields">Serializer fields</h2>
|
||||
<h4 id="the-field-and-readonly-field-classes">The <code>Field</code> and <code>ReadOnly</code> field classes.</h4>
|
||||
<p>There are some minor tweaks to the field base classes.</p>
|
||||
<p>Previously we had these two base classes:</p>
|
||||
<ul>
|
||||
<li><code>Field</code> as the base class for read-only fields. A default implementation was included for serializing data.</li>
|
||||
<li><code>WritableField</code> as the base class for read-write fields.</li>
|
||||
</ul>
|
||||
<p>We now use the following:</p>
|
||||
<ul>
|
||||
<li><code>Field</code> is the base class for all fields. It does not include any default implementation for either serializing or deserializing data.</li>
|
||||
<li><code>ReadOnlyField</code> is a concrete implementation for read-only fields that simply returns the attribute value without modification.</li>
|
||||
</ul>
|
||||
<h4 id="the-required-allow_none-allow_blank-and-default-arguments">The <code>required</code>, <code>allow_none</code>, <code>allow_blank</code> and <code>default</code> arguments.</h4>
|
||||
<p>REST framework now has more explicit and clear control over validating empty values for fields.</p>
|
||||
<p>Previously the meaning of the <code>required=False</code> keyword argument was underspecified. In practice its use meant that a field could either be not included in the input, or it could be included, but be <code>None</code>.</p>
|
||||
<p>We now have a better separation, with separate <code>required</code> and <code>allow_none</code> arguments.</p>
|
||||
<p>The following set of arguments are used to control validation of empty values:</p>
|
||||
<ul>
|
||||
<li><code>required=False</code>: The value does not need to be present in the input, and will not be passed to <code>.create()</code> or <code>.update()</code> if it is not seen.</li>
|
||||
<li><code>default=<value></code>: The value does not need to be present in the input, and a default value will be passed to <code>.create()</code> or <code>.update()</code> if it is not seen.</li>
|
||||
<li><code>allow_none=True</code>: <code>None</code> is a valid input.</li>
|
||||
<li><code>allow_blank=True</code>: <code>''</code> is valid input. For <code>CharField</code> and subclasses only.</li>
|
||||
</ul>
|
||||
<p>Typically you'll want to use <code>required=False</code> if the corresponding model field has a default value, and additionally set either <code>allow_none=True</code> or <code>allow_blank=True</code> if required.</p>
|
||||
<p>The <code>default</code> argument is there if you need it, but you'll more typically want defaults to be set on model fields, rather than serializer fields.</p>
|
||||
<h4 id="coercing-output-types">Coercing output types.</h4>
|
||||
<p>The previous field implementations did not forcibly coerce returned values into the correct type in many cases. For example, an <code>IntegerField</code> would return a string output if the attribute value was a string. We now more strictly coerce to the correct return type, leading to more constrained and expected behavior. </p>
|
||||
<h4 id="the-listfield-class">The <code>ListField</code> class.</h4>
|
||||
<p>The <code>ListField</code> class has now been added. This field validates list input. It takes a <code>child</code> keyword argument which is used to specify the field used to validate each item in the list. For example:</p>
|
||||
<pre class="prettyprint lang-py"><code>scores = ListField(child=IntegerField(min_value=0, max_value=100))
|
||||
</code></pre>
|
||||
<p>You can also use a declarative style to create new subclasses of <code>ListField</code>, like this:</p>
|
||||
<pre class="prettyprint lang-py"><code>class ScoresField(ListField):
|
||||
child = IntegerField(min_value=0, max_value=100)
|
||||
</code></pre>
|
||||
<p>We can now use the <code>ScoresField</code> class inside another serializer:</p>
|
||||
<pre class="prettyprint lang-py"><code>scores = ScoresField()
|
||||
</code></pre>
|
||||
<p>See also the new <code>ListSerializer</code> class, which validates input in the same way, but also includes the serializer interfaces of <code>.is_valid()</code>, <code>.data</code>, <code>.save()</code> and so on.</p>
|
||||
<h4 id="the-choicefield-class-may-now-accept-a-flat-list">The <code>ChoiceField</code> class may now accept a flat list.</h4>
|
||||
<p>The <code>ChoiceField</code> class may now accept a list of choices in addition to the existing style of using a list of pairs of <code>(name, display_value)</code>. The following is now valid:</p>
|
||||
<pre class="prettyprint lang-py"><code>color = ChoiceField(choices=['red', 'green', 'blue'])
|
||||
</code></pre>
|
||||
<h4 id="the-multiplechoicefield-class">The <code>MultipleChoiceField</code> class.</h4>
|
||||
<p>The <code>MultipleChoiceField</code> class has been added. This field acts like <code>ChoiceField</code>, but returns a set, which may include none, one or many of the valid choices.</p>
|
||||
<h4 id="changes-to-the-custom-field-api">Changes to the custom field API.</h4>
|
||||
<p>The <code>from_native(self, value)</code> and <code>to_native(self, data)</code> method names have been replaced with the more obviously named <code>to_internal_value(self, data)</code> and <code>to_representation(self, value)</code>.</p>
|
||||
<p>The <code>field_from_native()</code> and <code>field_to_native()</code> methods are removed.</p>
|
||||
<h4 id="explicit-queryset-required-on-relational-fields">Explicit <code>queryset</code> required on relational fields.</h4>
|
||||
<p>Previously relational fields that were explicitly declared on a serializer class could omit the queryset argument if (and only if) they were declared on a <code>ModelSerializer</code>.</p>
|
||||
<p>This code <em>would be valid</em> in <code>2.4.3</code>:</p>
|
||||
<pre class="prettyprint lang-py"><code>class AccountSerializer(serializers.ModelSerializer):
|
||||
organisations = serializers.SlugRelatedField(slug_field='name')
|
||||
|
||||
class Meta:
|
||||
model = Account
|
||||
</code></pre>
|
||||
<p>However this code <em>would not be valid</em> in <code>2.4.3</code>:</p>
|
||||
<pre class="prettyprint lang-py"><code># Missing `queryset`
|
||||
class AccountSerializer(serializers.Serializer):
|
||||
organisations = serializers.SlugRelatedField(slug_field='name')
|
||||
|
||||
def restore_object(self, attrs, instance=None):
|
||||
# ...
|
||||
</code></pre>
|
||||
<p>The queryset argument is now always required for writable relational fields.
|
||||
This removes some magic and makes it easier and more obvious to move between implicit <code>ModelSerializer</code> classes and explicit <code>Serializer</code> classes.</p>
|
||||
<pre class="prettyprint lang-py"><code>class AccountSerializer(serializers.ModelSerializer):
|
||||
organisations = serializers.SlugRelatedField(
|
||||
slug_field='name',
|
||||
queryset=Organisation.objects.all()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Account
|
||||
</code></pre>
|
||||
<p>The <code>queryset</code> argument is only ever required for writable fields, and is not required or valid for fields with <code>read_only=True</code>.</p>
|
||||
<h4 id="optional-argument-to-serializermethodfield">Optional argument to <code>SerializerMethodField</code>.</h4>
|
||||
<p>The argument to <code>SerializerMethodField</code> is now optional, and defaults to <code>get_<field_name></code>. For example the following is valid:</p>
|
||||
<pre class="prettyprint lang-py"><code>class AccountSerializer(serializers.Serializer):
|
||||
# `method_name='get_billing_details'` by default.
|
||||
billing_details = serializers.SerializerMethodField()
|
||||
|
||||
def get_billing_details(self, account):
|
||||
return calculate_billing(account)
|
||||
</code></pre>
|
||||
<p>In order to ensure a consistent code style an assertion error will be raised if you include a redundant method name argument that matches the default method name. For example, the following code <em>will raise an error</em>:</p>
|
||||
<pre class="prettyprint lang-py"><code>billing_details = serializers.SerializerMethodField('get_billing_details')
|
||||
</code></pre>
|
||||
<h4 id="enforcing-consistent-source-usage">Enforcing consistent <code>source</code> usage.</h4>
|
||||
<p>I've see several codebases that unnecessarily include the <code>source</code> argument, setting it to the same value as the field name. This usage is redundant and confusing, making it less obvious that <code>source</code> is usually not required.</p>
|
||||
<p>The following usage will <em>now raise an error</em>:</p>
|
||||
<pre class="prettyprint lang-py"><code>email = serializers.EmailField(source='email')
|
||||
</code></pre>
|
||||
<h4 id="the-uniquevalidator-and-uniquetogethervalidator-classes">The <code>UniqueValidator</code> and <code>UniqueTogetherValidator</code> classes.</h4>
|
||||
<p>REST framework now provides two new validators that allow you to ensure field uniqueness, while still using a completely explicit <code>Serializer</code> class instead of using <code>ModelSerializer</code>.</p>
|
||||
<p>The <code>UniqueValidator</code> should be applied to a serializer field, and takes a single <code>queryset</code> argument.</p>
|
||||
<pre class="prettyprint lang-py"><code>from rest_framework import serializers
|
||||
from rest_framework.validators import UniqueValidator
|
||||
|
||||
class OrganizationSerializer(serializers.Serializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='organisation_detail')
|
||||
created = serializers.DateTimeField(read_only=True)
|
||||
name = serializers.CharField(
|
||||
max_length=100,
|
||||
validators=UniqueValidator(queryset=Organisation.objects.all())
|
||||
)
|
||||
</code></pre>
|
||||
<p>The <code>UniqueTogetherValidator</code> should be applied to a serializer, and takes a <code>queryset</code> argument and a <code>fields</code> argument which should be a list or tuple of field names.</p>
|
||||
<pre class="prettyprint lang-py"><code>class RaceResultSerializer(serializers.Serializer):
|
||||
category = serializers.ChoiceField(['5k', '10k'])
|
||||
position = serializers.IntegerField()
|
||||
name = serializers.CharField(max_length=100)
|
||||
|
||||
default_validators = [UniqueTogetherValidator(
|
||||
queryset=RaceResult.objects.all(),
|
||||
fields=('category', 'position')
|
||||
)]
|
||||
</code></pre>
|
||||
<h2 id="generic-views">Generic views</h2>
|
||||
<h4 id="simplification-of-view-logic">Simplification of view logic.</h4>
|
||||
<p>The view logic for the default method handlers has been significantly simplified, due to the new serializers API.</p>
|
||||
<h4 id="changes-to-prepost-save-hooks">Changes to pre/post save hooks.</h4>
|
||||
<p>The <code>pre_save</code> and <code>post_save</code> hooks no longer exist, but are replaced with <code>perform_create(self, serializer)</code> and <code>perform_update(self, serializer)</code>.</p>
|
||||
<p>These methods should save the object instance by calling <code>serializer.save()</code>, adding in any additional arguments as required. They may also perform any custom pre-save or post-save behavior.</p>
|
||||
<p>For example:</p>
|
||||
<pre class="prettyprint lang-py"><code>def perform_create(self, serializer):
|
||||
# Include the owner attribute directly, rather than from request data.
|
||||
instance = serializer.save(owner=self.request.user)
|
||||
# Perform a custom post-save action.
|
||||
send_email(instance.to_email, instance.message)
|
||||
</code></pre>
|
||||
<p>The <code>pre_delete</code> and <code>post_delete</code> hooks no longer exist, and are replaced with <code>.perform_destroy(self, instance)</code>, which should delete the instance and perform any custom actions.</p>
|
||||
<pre class="prettyprint lang-py"><code>def perform_destroy(self, instance):
|
||||
# Perform a custom pre-delete action.
|
||||
send_deletion_alert(user=instance.created_by, deleted=instance)
|
||||
# Delete the object instance.
|
||||
instance.delete()
|
||||
</code></pre>
|
||||
<h4 id="removal-of-view-attributes">Removal of view attributes.</h4>
|
||||
<p>The <code>.object</code> and <code>.object_list</code> attributes are no longer set on the view instance. Treating views as mutable object instances that store state during the processing of the view tends to be poor design, and can lead to obscure flow logic.</p>
|
||||
<p>I would personally recommend that developers treat view instances as immutable objects in their application code.</p>
|
||||
<h4 id="put-as-create">PUT as create.</h4>
|
||||
<p>Allowing <code>PUT</code> as create operations is problematic, as it necessarily exposes information about the existence or non-existance of objects. It's also not obvious that transparently allowing re-creating of previously deleted instances is necessarily a better default behavior than simply returning <code>404</code> responses.</p>
|
||||
<p>Both styles "<code>PUT</code> as 404" and "<code>PUT</code> as create" can be valid in different circumstances, but we've now opted for the 404 behavior as the default, due to it being simpler and more obvious.</p>
|
||||
<p>If you need to restore the previous behavior you can include the <code>AllowPUTAsCreateMixin</code> class in your view. This class can be imported from <code>rest_framework.mixins</code>.</p>
|
||||
<h4 id="customizing-error-responses">Customizing error responses.</h4>
|
||||
<p>The generic views now raise <code>ValidationFailed</code> exception for invalid data. This exception is then dealt with by the exception handler, rather than the view returning a <code>400 Bad Request</code> response directly.</p>
|
||||
<p>This change means that you can now easily customize the style of error responses across your entire API, without having to modify any of the generic views.</p>
|
||||
<h2 id="the-metadata-api">The metadata API</h2>
|
||||
<p>Behavior for dealing with <code>OPTIONS</code> requests was previously built directly into the class based views. This has now been properly separated out into a Metadata API that allows the same pluggable style as other API policies in REST framework.</p>
|
||||
<p>This makes it far easier to use a different style for <code>OPTIONS</code> responses throughout your API, and makes it possible to create third-party metadata policies.</p>
|
||||
<h2 id="api-style">API style</h2>
|
||||
<p>There are some improvements in the default style we use in our API responses.</p>
|
||||
<h4 id="unicode-json-by-default">Unicode JSON by default.</h4>
|
||||
<p>Unicode JSON is now the default. The <code>UnicodeJSONRenderer</code> class no longer exists, and the <code>UNICODE_JSON</code> setting has been added. To revert this behavior use the new setting:</p>
|
||||
<pre class="prettyprint lang-py"><code>REST_FRAMEWORK = {
|
||||
'UNICODE_JSON': False
|
||||
}
|
||||
</code></pre>
|
||||
<h4 id="compact-json-by-default">Compact JSON by default.</h4>
|
||||
<p>We now output compact JSON in responses by default. For example, we return:</p>
|
||||
<pre class="prettyprint lang-py"><code>{"email":"amy@example.com","is_admin":true}
|
||||
</code></pre>
|
||||
<p>Instead of the following:</p>
|
||||
<pre class="prettyprint lang-py"><code>{"email": "amy@example.com", "is_admin": true}
|
||||
</code></pre>
|
||||
<p>The <code>COMPACT_JSON</code> setting has been added, and can be used to revert this behavior if needed:</p>
|
||||
<pre class="prettyprint lang-py"><code>REST_FRAMEWORK = {
|
||||
'COMPACT_JSON': False
|
||||
}
|
||||
</code></pre>
|
||||
<h4 id="file-fields-as-urls">File fields as URLs</h4>
|
||||
<p>The <code>FileField</code> and <code>ImageField</code> classes are now represented as URLs by default. You should ensure you set Django's <a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-MEDIA_URL">standard <code>MEDIA_URL</code> setting</a> appropriately, and ensure your application <a href="https://docs.djangoproject.com/en/dev/howto/static-files/#serving-uploaded-files-in-development">serves the uploaded files</a>.</p>
|
||||
<p>You can revert this behavior, and display filenames in the representation by using the <code>UPLOADED_FILES_USE_URL</code> settings key:</p>
|
||||
<pre class="prettyprint lang-py"><code>REST_FRAMEWORK = {
|
||||
'UPLOADED_FILES_USE_URL': False
|
||||
}
|
||||
</code></pre>
|
||||
<p>You can also modify serializer fields individually, using the <code>use_url</code> argument:</p>
|
||||
<pre class="prettyprint lang-py"><code>uploaded_file = serializers.FileField(user_url=False)
|
||||
</code></pre>
|
||||
<p>Also note that you should pass the <code>request</code> object to the serializer as context when instantiating it, so that a fully qualified URL can be returned. Returned URLs will then be of the form <code>https://example.com/url_path/filename.txt</code>. For example:</p>
|
||||
<pre class="prettyprint lang-py"><code>context = {'request': request}
|
||||
serializer = ExampleSerializer(instance, context=context)
|
||||
return Response(serializer.data)
|
||||
</code></pre>
|
||||
<p>If the request is omitted from the context, the returned URLs will be of the form <code>/url_path/filename.txt</code>.</p>
|
||||
<h4 id="throttle-headers-using-retry-after">Throttle headers using <code>Retry-After</code>.</h4>
|
||||
<p>The custom <code>X-Throttle-Wait-Second</code> header has now been dropped in favor of the standard <code>Retry-After</code> header. You can revert this behavior if needed by writing a custom exception handler for your application.</p>
|
||||
<h4 id="date-and-time-objects-as-iso-8859-1-strings-in-serializer-data">Date and time objects as ISO-8859-1 strings in serializer data.</h4>
|
||||
<p>Date and Time objects are now coerced to strings by default in the serializer output. Previously they were returned as <code>Date</code>, <code>Time</code> and <code>DateTime</code> objects, and later coerced to strings by the renderer.</p>
|
||||
<p>You can modify this behavior globally by settings the existing <code>DATE_FORMAT</code>, <code>DATETIME_FORMAT</code> and <code>TIME_FORMAT</code> settings keys. Setting these values to <code>None</code> instead of their default value of <code>'iso-8859-1'</code> will result in native objects being returned in serializer data.</p>
|
||||
<pre class="prettyprint lang-py"><code>REST_FRAMEWORK = {
|
||||
# Return native `Date` and `Time` objects in `serializer.data`
|
||||
'DATETIME_FORMAT': None
|
||||
'DATE_FORMAT': None
|
||||
'TIME_FORMAT': None
|
||||
}
|
||||
</code></pre>
|
||||
<p>You can also modify serializer fields individually, using the <code>date_format</code>, <code>time_format</code> and <code>datetime_format</code> arguments:</p>
|
||||
<pre class="prettyprint lang-py"><code># Return `DateTime` instances in `serializer.data`, not strings.
|
||||
created = serializers.DateTimeField(format=None)
|
||||
</code></pre>
|
||||
<h4 id="decimals-as-strings-in-serializer-data">Decimals as strings in serializer data.</h4>
|
||||
<p>Decimals are now coerced to strings by default in the serializer output. Previously they were returned as <code>Decimal</code> objects, and later coerced to strings by the renderer.</p>
|
||||
<p>You can modify this behavior globally by using the <code>COERCE_DECIMAL_TO_STRING</code> settings key.</p>
|
||||
<pre class="prettyprint lang-py"><code>REST_FRAMEWORK = {
|
||||
'COERCE_DECIMAL_TO_STRING': False
|
||||
}
|
||||
</code></pre>
|
||||
<p>Or modify it on an individual serializer field, using the <code>corece_to_string</code> keyword argument.</p>
|
||||
<pre class="prettyprint lang-py"><code># Return `Decimal` instances in `serializer.data`, not strings.
|
||||
amount = serializers.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
coerce_to_string=False
|
||||
)
|
||||
</code></pre>
|
||||
<p>The default JSON renderer will return float objects for uncoerced <code>Decimal</code> instances. This allows you to easily switch between string or float representations for decimals depending on your API design needs.</p>
|
||||
<h2 id="whats-coming-next">What's coming next.</h2>
|
||||
<p>3.0 is an incremental release, and there are several upcoming features that will build on the baseline improvements that it makes.</p>
|
||||
<p>The 3.1 release is planned to address improvements in the following components:</p>
|
||||
<ul>
|
||||
<li>Request parsing, mediatypes & the implementation of the browsable API.</li>
|
||||
<li>Introduction of a new pagination API.</li>
|
||||
<li>Better support for API versioning.</li>
|
||||
</ul>
|
||||
<p>The 3.2 release is planned to introduce an alternative admin-style interface to the browsable API.</p>
|
||||
<p>You can follow development on the GitHub site, where we use <a href="https://github.com/tomchristie/django-rest-framework/milestones">milestones to indicate planning timescales</a>.</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="file:///Users/tomchristie/GitHub/django-rest-framework/html//js/jquery-1.8.1-min.js"></script>
|
||||
<script src="file:///Users/tomchristie/GitHub/django-rest-framework/html//js/prettify-1.0.js"></script>
|
||||
<script src="file:///Users/tomchristie/GitHub/django-rest-framework/html//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>
|
|
@ -356,7 +356,8 @@ More text...
|
|||
<p>If you have some functionality that you would like to implement as a third party package it's worth contacting the <a href="https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework">discussion group</a> as others may be willing to get involved. We strongly encourage third party package development and will always try to prioritize time spent helping their development, documentation and packaging.</p>
|
||||
<p>We recommend the <a href="https://github.com/dabapps/django-reusable-app"><code>django-reusable-app</code></a> template as a good resource for getting up and running with implementing a third party Django package.</p>
|
||||
<h2 id="linking-to-your-package">Linking to your package</h2>
|
||||
<p>Once your package is decently documented and available on PyPI open a pull request or issue, and we'll add a link to it from the main REST framework documentation.</p>
|
||||
<p>Once your package is decently documented and available on PyPI open a pull request or issue, and we'll add a link to it from the main REST framework documentation. You can add your package under <strong>Third party packages</strong> of the API Guide section that best applies, like <a href="../api-guide/authentication">Authentication</a> or <a href="../api-guide/permissions">Permissions</a>. You can also link your package under the <a href="third-party-resources">Third Party Resources</a> section.</p>
|
||||
<p>We also suggest adding it to the <a href="https://www.djangopackages.com/grids/g/django-rest-framework/">REST Framework</a> grid on Django Packages.</p>
|
||||
</div><!--/span-->
|
||||
</div><!--/row-->
|
||||
</div><!--/.fluid-container-->
|
||||
|
|
|
@ -242,6 +242,15 @@ a.fusion-poweredby {
|
|||
</code></pre>
|
||||
<hr />
|
||||
<h2 id="24x-series">2.4.x series</h2>
|
||||
<h3 id="244">2.4.4</h3>
|
||||
<p><strong>Date</strong>: <a href="https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.4+Release%22+">3rd November 2014</a>.</p>
|
||||
<ul>
|
||||
<li><strong>Security fix</strong>: Escape URLs when replacing <code>format=</code> query parameter, as used in dropdown on <code>GET</code> button in browsable API to allow explicit selection of JSON vs HTML output.</li>
|
||||
<li>Maintain ordering of URLs in API root view for <code>DefaultRouter</code>.</li>
|
||||
<li>Fix <code>follow=True</code> in <code>APIRequestFactory</code></li>
|
||||
<li>Resolve issue with invalid <code>read_only=True</code>, <code>required=True</code> fields being automatically generated by <code>ModelSerializer</code> in some cases.</li>
|
||||
<li>Resolve issue with <code>OPTIONS</code> requests returning incorrect information for views using <code>get_serializer_class</code> to dynamically determine serializer based on request method. </li>
|
||||
</ul>
|
||||
<h3 id="243">2.4.3</h3>
|
||||
<p><strong>Date</strong>: <a href="https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.3+Release%22+">19th September 2014</a>.</p>
|
||||
<ul>
|
||||
|
|
|
@ -214,6 +214,7 @@ a.fusion-poweredby {
|
|||
<li><a href="https://github.com/GetBlimp/django-rest-framework-jwt">djangorestframework-jwt</a> - Provides JSON Web Token Authentication support.</li>
|
||||
<li><a href="https://github.com/kumar303/hawkrest">hawkrest</a> - Provides Hawk HTTP Authorization.</li>
|
||||
<li><a href="https://github.com/etoccalino/django-rest-framework-httpsignature">djangorestframework-httpsignature</a> - Provides an easy to use HTTP Signature Authentication mechanism.</li>
|
||||
<li><a href="https://github.com/sunscrapers/djoser">djoser</a> - Provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation.</li>
|
||||
</ul>
|
||||
<h3 id="permissions">Permissions</h3>
|
||||
<ul>
|
||||
|
|
|
@ -256,9 +256,9 @@ cd tutorial
|
|||
)
|
||||
</code></pre>
|
||||
<p>We also need to wire up the root urlconf, in the <code>tutorial/urls.py</code> file, to include our snippet app's URLs.</p>
|
||||
<pre class="prettyprint lang-py"><code>urlpatterns = patterns('',
|
||||
<pre class="prettyprint lang-py"><code>urlpatterns = [
|
||||
url(r'^', include('snippets.urls')),
|
||||
)
|
||||
]
|
||||
</code></pre>
|
||||
<p>Okay, we're ready to roll.</p>
|
||||
<h2 id="creating-a-model-to-work-with">Creating a model to work with</h2>
|
||||
|
@ -459,11 +459,12 @@ def snippet_detail(request, pk):
|
|||
</code></pre>
|
||||
<p>Finally we need to wire these views up. Create the <code>snippets/urls.py</code> file:</p>
|
||||
<pre class="prettyprint lang-py"><code>from django.conf.urls import patterns, url
|
||||
from snippets import views
|
||||
|
||||
urlpatterns = patterns('snippets.views',
|
||||
url(r'^snippets/$', 'snippet_list'),
|
||||
url(r'^snippets/(?P<pk>[0-9]+)/$', 'snippet_detail'),
|
||||
)
|
||||
urlpatterns = [
|
||||
url(r'^snippets/$', views.snippet_list),
|
||||
url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
|
||||
]
|
||||
</code></pre>
|
||||
<p>It's worth noting that there are a couple of edge cases we're not dealing with properly at the moment. If we send malformed <code>json</code>, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response. Still, this'll do for now.</p>
|
||||
<h2 id="testing-our-first-attempt-at-a-web-api">Testing our first attempt at a Web API</h2>
|
||||
|
|
|
@ -294,11 +294,12 @@ def snippet_detail(request, pk):
|
|||
<p>Now update the <code>urls.py</code> file slightly, to append a set of <code>format_suffix_patterns</code> in addition to the existing URLs.</p>
|
||||
<pre class="prettyprint lang-py"><code>from django.conf.urls import patterns, url
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
from snippets import views
|
||||
|
||||
urlpatterns = patterns('snippets.views',
|
||||
url(r'^snippets/$', 'snippet_list'),
|
||||
url(r'^snippets/(?P<pk>[0-9]+)$', 'snippet_detail'),
|
||||
)
|
||||
urlpatterns = [
|
||||
url(r'^snippets/$', views.snippet_list),
|
||||
url(r'^snippets/(?P<pk>[0-9]+)$', views.snippet_detail),
|
||||
]
|
||||
|
||||
urlpatterns = format_suffix_patterns(urlpatterns)
|
||||
</code></pre>
|
||||
|
|
|
@ -263,10 +263,10 @@ class SnippetList(APIView):
|
|||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
from snippets import views
|
||||
|
||||
urlpatterns = patterns('',
|
||||
urlpatterns = [
|
||||
url(r'^snippets/$', views.SnippetList.as_view()),
|
||||
url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
|
||||
)
|
||||
]
|
||||
|
||||
urlpatterns = format_suffix_patterns(urlpatterns)
|
||||
</code></pre>
|
||||
|
|
|
@ -311,10 +311,10 @@ url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
|
|||
<pre class="prettyprint lang-py"><code>from django.conf.urls import include
|
||||
</code></pre>
|
||||
<p>And, at the end of the file, add a pattern to include the login and logout views for the browsable API.</p>
|
||||
<pre class="prettyprint lang-py"><code>urlpatterns += patterns('',
|
||||
<pre class="prettyprint lang-py"><code>urlpatterns += [
|
||||
url(r'^api-auth/', include('rest_framework.urls',
|
||||
namespace='rest_framework')),
|
||||
)
|
||||
]
|
||||
</code></pre>
|
||||
<p>The <code>r'^api-auth/'</code> part of pattern can actually be whatever URL you want to use. The only restriction is that the included urls must use the <code>'rest_framework'</code> namespace.</p>
|
||||
<p>Now if you open up the browser again and refresh the page you'll see a 'Login' link in the top right of the page. If you log in as one of the users you created earlier, you'll be able to create code snippets again.</p>
|
||||
|
|
|
@ -293,8 +293,8 @@ class UserSerializer(serializers.HyperlinkedModelSerializer):
|
|||
</ul>
|
||||
<p>After adding all those names into our URLconf, our final <code>snippets/urls.py</code> file should look something like this:</p>
|
||||
<pre class="prettyprint lang-py"><code># API endpoints
|
||||
urlpatterns = format_suffix_patterns(patterns('snippets.views',
|
||||
url(r'^$', 'api_root'),
|
||||
urlpatterns = format_suffix_patterns([
|
||||
url(r'^$', views.api_root),
|
||||
url(r'^snippets/$',
|
||||
views.SnippetList.as_view(),
|
||||
name='snippet-list'),
|
||||
|
@ -310,13 +310,13 @@ urlpatterns = format_suffix_patterns(patterns('snippets.views',
|
|||
url(r'^users/(?P<pk>[0-9]+)/$',
|
||||
views.UserDetail.as_view(),
|
||||
name='user-detail')
|
||||
))
|
||||
])
|
||||
|
||||
# Login and logout views for the browsable API
|
||||
urlpatterns += patterns('',
|
||||
urlpatterns += [
|
||||
url(r'^api-auth/', include('rest_framework.urls',
|
||||
namespace='rest_framework')),
|
||||
)
|
||||
]
|
||||
</code></pre>
|
||||
<h2 id="adding-pagination">Adding pagination</h2>
|
||||
<p>The list views for users and code snippets could end up returning quite a lot of instances, so really we'd like to make sure we paginate the results, and allow the API client to step through each of the individual pages.</p>
|
||||
|
|
|
@ -275,19 +275,19 @@ user_detail = UserViewSet.as_view({
|
|||
</code></pre>
|
||||
<p>Notice how we're creating multiple views from each <code>ViewSet</code> class, by binding the http methods to the required action for each view.</p>
|
||||
<p>Now that we've bound our resources into concrete views, we can register the views with the URL conf as usual.</p>
|
||||
<pre class="prettyprint lang-py"><code>urlpatterns = format_suffix_patterns(patterns('snippets.views',
|
||||
url(r'^$', 'api_root'),
|
||||
<pre class="prettyprint lang-py"><code>urlpatterns = format_suffix_patterns([
|
||||
url(r'^$', api_root),
|
||||
url(r'^snippets/$', snippet_list, name='snippet-list'),
|
||||
url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
|
||||
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
|
||||
url(r'^users/$', user_list, name='user-list'),
|
||||
url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
|
||||
))
|
||||
])
|
||||
</code></pre>
|
||||
<h2 id="using-routers">Using Routers</h2>
|
||||
<p>Because we're using <code>ViewSet</code> classes rather than <code>View</code> classes, we actually don't need to design the URL conf ourselves. The conventions for wiring up resources into views and urls can be handled automatically, using a <code>Router</code> class. All we need to do is register the appropriate view sets with a router, and let it do the rest.</p>
|
||||
<p>Here's our re-wired <code>urls.py</code> file.</p>
|
||||
<pre class="prettyprint lang-py"><code>from django.conf.urls import patterns, url, include
|
||||
<pre class="prettyprint lang-py"><code>from django.conf.urls import url, include
|
||||
from snippets import views
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
|
@ -298,10 +298,10 @@ router.register(r'users', views.UserViewSet)
|
|||
|
||||
# The API URLs are now determined automatically by the router.
|
||||
# Additionally, we include the login URLs for the browseable API.
|
||||
urlpatterns = patterns('',
|
||||
urlpatterns = [
|
||||
url(r'^', include(router.urls)),
|
||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
|
||||
)
|
||||
]
|
||||
</code></pre>
|
||||
<p>Registering the viewsets with the router is similar to providing a urlpattern. We include two arguments - the URL prefix for the views, and the viewset itself.</p>
|
||||
<p>The <code>DefaultRouter</code> class we're using also automatically creates the API root view for us, so we can now delete the <code>api_root</code> method from our <code>views</code> module.</p>
|
||||
|
|
Loading…
Reference in New Issue
Block a user