diff options
Diffstat (limited to 'docs')
52 files changed, 2772 insertions, 1486 deletions
| diff --git a/docs/404.html b/docs/404.html deleted file mode 100644 index 864247e7..00000000 --- a/docs/404.html +++ /dev/null @@ -1,201 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> -    <meta charset="utf-8"> -    <title>Django REST framework - 404 - Page not found</title> -    <link href="http://www.django-rest-framework.org/img/favicon.ico" rel="icon" type="image/x-icon"> -    <link rel="canonical" href="http://www.django-rest-framework.org/404"/> -    <meta name="viewport" content="width=device-width, initial-scale=1.0"> -    <meta name="description" content="Django, API, REST, 404 - Page not found"> -    <meta name="author" content="Tom Christie"> - -    <!-- Le styles --> -    <link href="http://www.django-rest-framework.org/css/prettify.css" rel="stylesheet"> -    <link href="http://www.django-rest-framework.org/css/bootstrap.css" rel="stylesheet"> -    <link href="http://www.django-rest-framework.org/css/bootstrap-responsive.css" rel="stylesheet"> -    <link href="http://www.django-rest-framework.org/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> -  </head> -  <body onload="prettyPrint()" class="404-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="http://www.django-rest-framework.org">Django REST framework</a> -          <div class="nav-collapse collapse"> -            <ul class="nav"> -              <li><a href="http://www.django-rest-framework.org">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="http://www.django-rest-framework.org/tutorial/quickstart">Quickstart</a></li> -                  <li><a href="http://www.django-rest-framework.org/tutorial/1-serialization">1 - Serialization</a></li> -                  <li><a href="http://www.django-rest-framework.org/tutorial/2-requests-and-responses">2 - Requests and responses</a></li> -                  <li><a href="http://www.django-rest-framework.org/tutorial/3-class-based-views">3 - Class based views</a></li> -                  <li><a href="http://www.django-rest-framework.org/tutorial/4-authentication-and-permissions">4 - Authentication and permissions</a></li> -                  <li><a href="http://www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis">5 - Relationships and hyperlinked APIs</a></li> -                  <li><a href="http://www.django-rest-framework.org/tutorial/6-viewsets-and-routers">6 - Viewsets and routers</a></li> -                </ul> -              </li> -              <li class="dropdown"> -                <a href="#" class="dropdown-toggle" data-toggle="dropdown">API Guide <b class="caret"></b></a> -                <ul class="dropdown-menu"> -                  <li><a href="http://www.django-rest-framework.org/api-guide/requests">Requests</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/responses">Responses</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/views">Views</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/generic-views">Generic views</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/viewsets">Viewsets</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/routers">Routers</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/parsers">Parsers</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/renderers">Renderers</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/serializers">Serializers</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/fields">Serializer fields</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/relations">Serializer relations</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/authentication">Authentication</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/permissions">Permissions</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/throttling">Throttling</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/filtering">Filtering</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/pagination">Pagination</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/content-negotiation">Content negotiation</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/format-suffixes">Format suffixes</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/reverse">Returning URLs</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/exceptions">Exceptions</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/status-codes">Status codes</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/testing">Testing</a></li> -                  <li><a href="http://www.django-rest-framework.org/api-guide/settings">Settings</a></li> -                </ul> -              </li> -              <li class="dropdown"> -                <a href="#" class="dropdown-toggle" data-toggle="dropdown">Topics <b class="caret"></b></a> -                <ul class="dropdown-menu"> -                  <li><a href="http://www.django-rest-framework.org/topics/documenting-your-api">Documenting your API</a></li> -                  <li><a href="http://www.django-rest-framework.org/topics/ajax-csrf-cors">AJAX, CSRF & CORS</a></li> -                  <li><a href="http://www.django-rest-framework.org/topics/browser-enhancements">Browser enhancements</a></li> -                  <li><a href="http://www.django-rest-framework.org/topics/browsable-api">The Browsable API</a></li> -                  <li><a href="http://www.django-rest-framework.org/topics/rest-hypermedia-hateoas">REST, Hypermedia & HATEOAS</a></li> -                  <li><a href="http://www.django-rest-framework.org/topics/rest-framework-2-announcement">2.0 Announcement</a></li> -                  <li><a href="http://www.django-rest-framework.org/topics/2.2-announcement">2.2 Announcement</a></li> -                  <li><a href="http://www.django-rest-framework.org/topics/2.3-announcement">2.3 Announcement</a></li> -                  <li><a href="http://www.django-rest-framework.org/topics/release-notes">Release Notes</a></li> -                  <li><a href="http://www.django-rest-framework.org/topics/credits">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 id="main-content" class="span12"> -            <h1 id="404-page-not-found" style="text-align: center">404</h1> -            <p style="text-align: center"><strong>Page not found</strong></p> -            <p style="text-align: center">Try the <a href="http://www.django-rest-framework.org/">homepage</a>, or <a href="#searchModal" data-toggle="modal">search the documentation</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="http://www.django-rest-framework.org/js/jquery-1.8.1-min.js"></script> -    <script src="http://www.django-rest-framework.org/js/prettify-1.0.js"></script> -    <script src="http://www.django-rest-framework.org/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> diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 00000000..4e332899 --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +www.django-rest-framework.org diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 3d4e0f72..2074f1bf 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -1,4 +1,4 @@ -<a class="github" href="authentication.py"></a> +source: authentication.py  # Authentication @@ -168,12 +168,13 @@ The `curl` command line tool may be useful for testing token authenticated APIs.  If you want every user to have an automatically generated Token, you can simply catch the User's `post_save` signal. +    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) @@ -190,9 +191,10 @@ If you've already created some users, you can generate tokens for all existing u  When using `TokenAuthentication`, 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 `obtain_auth_token` view to your URLconf: -    urlpatterns += patterns('', -        url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token') -    ) +    from rest_framework.authtoken import views +    urlpatterns += [ +        url(r'^api-token-auth/', views.obtain_auth_token) +    ]  Note that the URL part of the pattern can be whatever you want to use. @@ -297,7 +299,7 @@ HTTP digest authentication is a widely implemented scheme that was intended to r  ## Django OAuth Toolkit -The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+.  The package is maintained by [Evonove][evonove] and uses the excelllent [OAuthLib][oauthlib].  The package is well documented, and comes as a recommended alternative for OAuth 2.0 support. +The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+.  The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib].  The package is well documented, and comes as a recommended alternative for OAuth 2.0 support.  ## Django OAuth2 Consumer @@ -315,6 +317,10 @@ The [HawkREST][hawkrest] library builds on the [Mohawk][mohawk] library to let y  HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a way to achieve origin authentication and message integrity for HTTP messages. Similar to [Amazon's HTTP Signature scheme][amazon-http-signature], used by many of its services, it permits stateless, per-request authentication. [Elvio Toccalino][etoccalino] maintains the [djangorestframework-httpsignature][djangorestframework-httpsignature] package which provides an easy to use HTTP Signature Authentication mechanism. +## Djoser + +[Djoser][djoser] 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. +  [cite]: http://jacobian.org/writing/rest-worst-practices/  [http401]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2  [http403]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4 @@ -349,3 +355,4 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a  [hawk]: https://github.com/hueniverse/hawk  [mohawk]: http://mohawk.readthedocs.org/en/latest/  [mac]: http://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05 +[djoser]: https://github.com/sunscrapers/djoser diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md index 94dd59ca..bc3b09fb 100644 --- a/docs/api-guide/content-negotiation.md +++ b/docs/api-guide/content-negotiation.md @@ -1,4 +1,4 @@ -<a class="github" href="negotiation.py"></a> +source: negotiation.py  # Content negotiation @@ -29,7 +29,7 @@ The priorities for each of the given media types would be:  If the requested view was only configured with renderers for `YAML` and `HTML`, then REST framework would select whichever renderer was listed first in the `renderer_classes` list or `DEFAULT_RENDERER_CLASSES` setting. -For more information on the `HTTP Accept` header, see [RFC 2616][accept-header]  +For more information on the `HTTP Accept` header, see [RFC 2616][accept-header]  --- @@ -62,7 +62,7 @@ request when selecting the appropriate parser or renderer.              Select the first parser in the `.parser_classes` list.              """              return parsers[0] -         +          def select_renderer(self, request, renderers, format_suffix):              """              Select the first renderer in the `.renderer_classes` list. diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index e61dcfa9..467ad970 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -1,4 +1,4 @@ -<a class="github" href="exceptions.py"></a> +source: exceptions.py  # Exceptions @@ -100,7 +100,7 @@ For example, if your API relies on a third party service that may sometimes be u  **Signature:** `ParseError(detail=None)` -Raised if the request contains malformed data when accessing `request.DATA` or `request.FILES`. +Raised if the request contains malformed data when accessing `request.data`.  By default this exception results in a response with the HTTP status code "400 Bad Request". @@ -140,7 +140,7 @@ By default this exception results in a response with the HTTP status code "405 M  **Signature:** `UnsupportedMediaType(media_type, detail=None)` -Raised if there are no parsers that can handle the content type of the request data when accessing `request.DATA` or `request.FILES`. +Raised if there are no parsers that can handle the content type of the request data when accessing `request.data`.  By default this exception results in a response with the HTTP status code "415 Unsupported Media Type". @@ -152,5 +152,23 @@ Raised when an incoming request fails the throttling checks.  By default this exception results in a response with the HTTP status code "429 Too Many Requests". +## ValidationError + +**Signature:** `ValidationError(detail)` + +The `ValidationError` exception is slightly different from the other `APIException` classes: + +* The `detail` argument is mandatory, not optional. +* The `detail` argument may be a list or dictionary of error details, and may also be a nested data structure. +* By convention you should import the serializers module and use a fully qualified `ValidationError` style, in order to differentiate it from Django's built-in validation error. For example. `raise serializers.ValidationError('This field must be an integer value.')` + +The `ValidationError` class should be used for serializer and field validation, and by validator classes. It is also raised when calling `serializer.is_valid` with the `raise_exception` keyword argument: + +    serializer.is_valid(raise_exception=True) + +The generic views use the `raise_exception=True` flag, which means that you can override the style of validation error responses globally in your API. To do so, use a custom exception handler, as described above. + +By default this exception results in a response with the HTTP status code "400 Bad Request". +  [cite]: http://www.doughellmann.com/articles/how-tos/python-exception-handling/index.html  [authentication]: authentication.md diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index f0778318..391a52e5 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -1,4 +1,10 @@ -<a class="github" href="fields.py"></a> +source: fields.py + +--- + +**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available. + +---  # Serializer fields @@ -10,7 +16,7 @@ Serializer fields handle converting between primitive values and internal dataty  --- -**Note:** The serializer fields are declared in fields.py, but by convention you should import them using `from rest_framework import serializers` and refer to fields as `serializers.<FieldName>`. +**Note:** The serializer fields are declared in `fields.py`, but by convention you should import them using `from rest_framework import serializers` and refer to fields as `serializers.<FieldName>`.  --- @@ -18,14 +24,6 @@ Serializer fields handle converting between primitive values and internal dataty  Each serializer field class constructor takes at least these arguments.  Some Field classes take additional, field-specific arguments, but the following should always be accepted: -### `source` - -The name of the attribute that will be used to populate the field.  May be a method that only takes a `self` argument, such as `Field(source='get_absolute_url')`, or may use dotted notation to traverse attributes, such as `Field(source='user.email')`. - -The value `source='*'` has a special meaning, and is used to indicate that the entire object should be passed through to the field.  This can be useful for creating nested representations.  (See the implementation of the `PaginationSerializer` class for an example.) - -Defaults to the name of the field. -  ### `read_only`  Set this to `True` to ensure that the field is used when serializing a representation, but is not used when creating or updating an instance during deserialization. @@ -45,25 +43,36 @@ Set to false if this field is not required to be present during deserialization.  Defaults to `True`. +### `allow_null` + +Normally an error will be raised if `None` is passed to a serializer field. Set this keyword argument to `True` if `None` should be considered a valid value. + +Defaults to `False` +  ### `default`  If set, this gives the default value that will be used for the field if no input value is supplied.  If not set the default behavior is to not populate the attribute at all.  May be set to a function or other callable, in which case the value will be evaluated each time it is used. +Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error. + +### `source` + +The name of the attribute that will be used to populate the field.  May be a method that only takes a `self` argument, such as `URLField('get_absolute_url')`, or may use dotted notation to traverse attributes, such as `EmailField(source='user.email')`. + +The value `source='*'` has a special meaning, and is used to indicate that the entire object should be passed through to the field.  This can be useful for creating nested representations, or for fields which require access to the complete object in order to determine the output representation. + +Defaults to the name of the field. +  ### `validators` -A list of Django validators that should be used to validate deserialized values. +A list of validator functions which should be applied to the incoming field input, and which either raise a validation error or simply return. Validator functions should typically raise `serializers.ValidationError`, but Django's built-in `ValidationError` is also supported for compatibility with validators defined in the Django codebase or third party Django packages.  ### `error_messages`  A dictionary of error codes to error messages. -### `widget` - -Used only if rendering the field to HTML. -This argument sets the widget that should be used to render the field. For more details, and a list of available widgets, see [the Django documentation on form widgets][django-widgets].  -  ### `label`  A short text string that may be used as the name of the field in HTML form fields or other descriptive elements. @@ -72,165 +81,191 @@ A short text string that may be used as the name of the field in HTML form field  A text string that may be used as a description of the field in HTML form fields or other descriptive elements. ---- +### `initial` -# Generic Fields +A value that should be used for pre-populating the value of HTML form fields. -These generic fields are used for representing arbitrary model fields or the output of model methods. +### `style` -## Field +A dictionary of key-value pairs that can be used to control how renderers should render the field. The API for this should still be considered experimental, and will be formalized with the 3.1 release. -A generic, **read-only** field.  You can use this field for any attribute that does not need to support write operations. +Two options are currently used in HTML form generation, `'input_type'` and `'base_template'`. -For example, using the following model. +    # Use <input type="password"> for the input. +    password = serializers.CharField( +        style={'input_type': 'password'} +    ) -    from django.db import models -    from django.utils.timezone import now +    # Use a radio input instead of a select input. +    color_channel = serializers.ChoiceField( +        choices=['red', 'green', 'blue'] +        style = {'base_template': 'radio.html'} +    } -    class Account(models.Model): -        owner = models.ForeignKey('auth.user') -        name = models.CharField(max_length=100) -        created = models.DateTimeField(auto_now_add=True) -        payment_expiry = models.DateTimeField() +**Note**: The `style` argument replaces the old-style version 2.x `widget` keyword argument. Because REST framework 3 now uses templated HTML form generation, the `widget` option that was used to support Django built-in widgets can no longer be supported. Version 3.1 is planned to include public API support for customizing HTML form generation. -        def has_expired(self): -            return now() > self.payment_expiry +--- -A serializer definition that looked like this: +# Boolean fields -    from rest_framework import serializers +## BooleanField -    class AccountSerializer(serializers.HyperlinkedModelSerializer): -        expired = serializers.Field(source='has_expired') +A boolean representation. -        class Meta: -            model = Account -            fields = ('url', 'owner', 'name', 'expired') +Corresponds to `django.db.models.fields.BooleanField`. -Would produce output similar to: +**Signature:** `BooleanField()` -    { -        'url': 'http://example.com/api/accounts/3/', -        'owner': 'http://example.com/api/users/12/', -        'name': 'FooCorp business account', -        'expired': True -    } +## NullBooleanField -By default, the `Field` class will perform a basic translation of the source value into primitive datatypes, falling back to unicode representations of complex datatypes when necessary. +A boolean representation that also accepts `None` as a valid value. -You can customize this  behavior by overriding the `.to_native(self, value)` method. +Corresponds to `django.db.models.fields.NullBooleanField`. -## WritableField +**Signature:** `NullBooleanField()` -A field that supports both read and write operations.  By itself `WritableField` does not perform any translation of input values into a given type.  You won't typically use this field directly, but you may want to override it and implement the `.to_native(self, value)` and `.from_native(self, value)` methods. +--- -## ModelField +# String fields -A generic field that can be tied to any arbitrary model field.  The `ModelField` class delegates the task of serialization/deserialization to its associated model field.  This field can be used to create serializer fields for custom model fields, without having to create a new custom serializer field. +## CharField -The `ModelField` class is generally intended for internal use, but can be used by your API if needed.  In order to properly instantiate a `ModelField`, it must be passed a field that is attached to an instantiated model.  For example: `ModelField(model_field=MyModel()._meta.get_field('custom_field'))` +A text representation. Optionally validates the text to be shorter than `max_length` and longer than `min_length`. -**Signature:** `ModelField(model_field=<Django ModelField instance>)` +Corresponds to `django.db.models.fields.CharField` or `django.db.models.fields.TextField`. -## SerializerMethodField +**Signature:** `CharField(max_length=None, min_length=None, allow_blank=False)` -This is a read-only field.  It gets its value by calling a method on the serializer class it is attached to.  It can be used to add any sort of data to the serialized representation of your object.  The field's constructor accepts a single argument, which is the name of the method on the serializer to be called.  The method should accept a single argument (in addition to `self`), which is the object being serialized.  It should return whatever you want to be included in the serialized representation of the object.  For example: +- `max_length` - Validates that the input contains no more than this number of characters. +- `min_length` - Validates that the input contains no fewer than this number of characters. +- `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`. -    from django.contrib.auth.models import User -    from django.utils.timezone import now -    from rest_framework import serializers +The `allow_null` option is also available for string fields, although its usage is discouraged in favor of `allow_blank`. It is valid to set both `allow_blank=True` and `allow_null=True`, but doing so means that there will be two differing types of empty value permissible for string representations, which can lead to data inconsistencies and subtle application bugs. -    class UserSerializer(serializers.ModelSerializer): -        days_since_joined = serializers.SerializerMethodField('get_days_since_joined') +## EmailField -        class Meta: -            model = User +A text representation, validates the text to be a valid e-mail address. -        def get_days_since_joined(self, obj): -            return (now() - obj.date_joined).days +Corresponds to `django.db.models.fields.EmailField` ---- +**Signature:** `EmailField(max_length=None, min_length=None, allow_blank=False)` -# Typed Fields +## RegexField -These fields represent basic datatypes, and support both reading and writing values. +A text representation, that validates the given value matches against a certain regular expression. -## BooleanField +Corresponds to `django.forms.fields.RegexField`. -A Boolean representation. +**Signature:** `RegexField(regex, max_length=None, min_length=None, allow_blank=False)` -Corresponds to `django.db.models.fields.BooleanField`. +The mandatory `regex` argument may either be a string, or a compiled python regular expression object. -## CharField +Uses Django's `django.core.validators.RegexValidator` for validation. + +## SlugField -A text representation, optionally validates the text to be shorter than `max_length` and longer than `min_length`. -If `allow_none` is `False` (default), `None` values will be converted to an empty string. +A `RegexField` that validates the input against the pattern `[a-zA-Z0-9_-]+`. -Corresponds to `django.db.models.fields.CharField` -or `django.db.models.fields.TextField`. +Corresponds to `django.db.models.fields.SlugField`. -**Signature:** `CharField(max_length=None, min_length=None, allow_none=False)` +**Signature:** `SlugField(max_length=50, min_length=None, allow_blank=False)`  ## URLField +A `RegexField` that validates the input against a URL matching pattern. Expects fully qualified URLs of the form `http://<host>/<path>`. +  Corresponds to `django.db.models.fields.URLField`.  Uses Django's `django.core.validators.URLValidator` for validation. -**Signature:** `URLField(max_length=200, min_length=None)` +**Signature:** `URLField(max_length=200, min_length=None, allow_blank=False)` -## SlugField +--- -Corresponds to `django.db.models.fields.SlugField`. +# Numeric fields -**Signature:** `SlugField(max_length=50, min_length=None)` +## IntegerField -## ChoiceField +An integer representation. -A field that can accept a value out of a limited set of choices. Optionally takes a `blank_display_value` parameter that customizes the display value of an empty choice. +Corresponds to `django.db.models.fields.IntegerField`, `django.db.models.fields.SmallIntegerField`, `django.db.models.fields.PositiveIntegerField` and `django.db.models.fields.PositiveSmallIntegerField`. -**Signature:** `ChoiceField(choices=(), blank_display_value=None)` +**Signature**: `IntegerField(max_value=None, min_value=None)` -## EmailField +- `max_value` Validate that the number provided is no greater than this value. +- `min_value` Validate that the number provided is no less than this value. -A text representation, validates the text to be a valid e-mail address. +## FloatField -Corresponds to `django.db.models.fields.EmailField` +A floating point representation. -## RegexField +Corresponds to `django.db.models.fields.FloatField`. -A text representation, that validates the given value matches against a certain regular expression. +**Signature**: `FloatField(max_value=None, min_value=None)` -Uses Django's `django.core.validators.RegexValidator` for validation. +- `max_value` Validate that the number provided is no greater than this value. +- `min_value` Validate that the number provided is no less than this value. + +## DecimalField + +A decimal representation, represented in Python by a `Decimal` instance. + +Corresponds to `django.db.models.fields.DecimalField`. + +**Signature**: `DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)` + +- `max_digits` The maximum number of digits allowed in the number. Note that this number must be greater than or equal to decimal_places. +- `decimal_places` The number of decimal places to store with the number. +- `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. +- `max_value` Validate that the number provided is no greater than this value. +- `min_value` Validate that the number provided is no less than this value. + +#### Example usage -Corresponds to `django.forms.fields.RegexField` +To validate numbers up to 999 with a resolution of 2 decimal places, you would use: -**Signature:** `RegexField(regex, max_length=None, min_length=None)` +    serializers.DecimalField(max_digits=5, decimal_places=2) + +And to validate numbers up to anything less than one billion with a resolution of 10 decimal places: + +    serializers.DecimalField(max_digits=19, decimal_places=10) + +This field also takes an optional argument, `coerce_to_string`. If set to `True` the representation will be output as a string. If set to `False` the representation will be left as a `Decimal` instance and the final representation will be determined by the renderer. + +If unset, this will default to the same value as the `COERCE_DECIMAL_TO_STRING` setting, which is `True` unless set otherwise. + +--- + +# Date and time fields  ## DateTimeField  A date and time representation. -Corresponds to `django.db.models.fields.DateTimeField` +Corresponds to `django.db.models.fields.DateTimeField`. -When using `ModelSerializer` or `HyperlinkedModelSerializer`, note that any model fields with `auto_now=True` or `auto_now_add=True` will use serializer fields that are `read_only=True` by default. +**Signature:** `DateTimeField(format=None, input_formats=None)` -If you want to override this behavior, you'll need to declare the `DateTimeField` explicitly on the serializer.  For example: +* `format` - A string representing the output format.  If not specified, this defaults to the same value as the `DATETIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `datetime` objects should be returned by `to_representation`. In this case the datetime encoding will be determined by the renderer. +* `input_formats` - A list of strings representing the input formats which may be used to parse the date.  If not specified, the `DATETIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. -    class CommentSerializer(serializers.ModelSerializer): -        created = serializers.DateTimeField() +#### `DateTimeField` format strings. -        class Meta: -            model = Comment +Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style datetimes should be used. (eg `'2013-01-29T12:34:56.000000Z'`) -Note that by default, datetime representations are determined by the renderer in use, although this can be explicitly overridden as detailed below. +When a value of `None` is used for the format `datetime` objects will be returned by `to_representation` and the final output representation will determined by the renderer class.  In the case of JSON this means the default datetime representation uses the [ECMA 262 date time string specification][ecma262].  This is a subset of ISO 8601 which uses millisecond precision, and includes the 'Z' suffix for the UTC timezone, for example: `2013-01-29T12:34:56.123Z`. -**Signature:** `DateTimeField(format=None, input_formats=None)` +#### `auto_now` and `auto_now_add` model fields. -* `format` - A string representing the output format.  If not specified, this defaults to `None`, which indicates that Python `datetime` objects should be returned by `to_native`.  In this case the datetime encoding will be determined by the renderer. -* `input_formats` - A list of strings representing the input formats which may be used to parse the date.  If not specified, the `DATETIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. +When using `ModelSerializer` or `HyperlinkedModelSerializer`, note that any model fields with `auto_now=True` or `auto_now_add=True` will use serializer fields that are `read_only=True` by default. -DateTime format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style datetimes should be used. (eg `'2013-01-29T12:34:56.000000Z'`) +If you want to override this behavior, you'll need to declare the `DateTimeField` explicitly on the serializer.  For example: + +    class CommentSerializer(serializers.ModelSerializer): +        created = serializers.DateTimeField() + +        class Meta: +            model = Comment  ## DateField @@ -240,63 +275,58 @@ Corresponds to `django.db.models.fields.DateField`  **Signature:** `DateField(format=None, input_formats=None)` -* `format` - A string representing the output format.  If not specified, this defaults to `None`, which indicates that Python `date` objects should be returned by `to_native`.  In this case the date encoding will be determined by the renderer. +* `format` - A string representing the output format.  If not specified, this defaults to the same value as the `DATE_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `date` objects should be returned by `to_representation`. In this case the date encoding will be determined by the renderer.  * `input_formats` - A list of strings representing the input formats which may be used to parse the date.  If not specified, the `DATE_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. -Date format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style dates should be used. (eg `'2013-01-29'`) +#### `DateField` format strings + +Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style dates should be used. (eg `'2013-01-29'`)  ## TimeField  A time representation. -Optionally takes `format` as parameter to replace the matching pattern. -  Corresponds to `django.db.models.fields.TimeField`  **Signature:** `TimeField(format=None, input_formats=None)` -* `format` - A string representing the output format.  If not specified, this defaults to `None`, which indicates that Python `time` objects should be returned by `to_native`.  In this case the time encoding will be determined by the renderer. +* `format` - A string representing the output format.  If not specified, this defaults to the same value as the `TIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `time` objects should be returned by `to_representation`. In this case the time encoding will be determined by the renderer.  * `input_formats` - A list of strings representing the input formats which may be used to parse the date.  If not specified, the `TIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. -Time format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`) +#### `TimeField` format strings -## IntegerField - -An integer representation. - -Corresponds to `django.db.models.fields.IntegerField`, `django.db.models.fields.SmallIntegerField`, `django.db.models.fields.PositiveIntegerField` and `django.db.models.fields.PositiveSmallIntegerField` - -## FloatField +Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`) -A floating point representation. +--- -Corresponds to `django.db.models.fields.FloatField`. +# Choice selection fields -## DecimalField +## ChoiceField -A decimal representation, represented in Python by a Decimal instance. +A field that can accept a value out of a limited set of choices. -Has two required arguments: +Used by `ModelSerializer` to automatically generate fields if the corresponding model field includes a `choices=…` argument. -- `max_digits` The maximum number of digits allowed in the number. Note that this number must be greater than or equal to decimal_places. +**Signature:** `ChoiceField(choices)` -- `decimal_places` The number of decimal places to store with the number. +- `choices` - A list of valid values, or a list of `(key, display_name)` tuples. -For example, to validate numbers up to 999 with a resolution of 2 decimal places, you would use: +## MultipleChoiceField -    serializers.DecimalField(max_digits=5, decimal_places=2) +A field that can accept a set of zero, one or many values, chosen from a limited set of choices. Takes a single mandatory argument. `to_internal_representation` returns a `set` containing the selected values. -And to validate numbers up to anything lesss than one billion with a resolution of 10 decimal places: +**Signature:** `MultipleChoiceField(choices)` -    serializers.DecimalField(max_digits=19, decimal_places=10) +- `choices` - A list of valid values, or a list of `(key, display_name)` tuples. -This field also takes an optional argument, `coerce_to_string`. If set to `True` the representation will be output as a string. If set to `False` the representation will be left as a `Decimal` instance and the final representation will be determined by the renderer. +--- -If unset, this will default to the same value as the `COERCE_DECIMAL_TO_STRING` setting, which is `True` unless set otherwise. +# File upload fields -**Signature:** `DecimalField(max_digits, decimal_places, coerce_to_string=None)` +#### Parsers and file uploads. -Corresponds to `django.db.models.fields.DecimalField`. +The `FileField` and `ImageField` classes are only suitable for use with `MultiPartParser` or `FileUploadParser`. Most parsers, such as e.g. JSON don't support file uploads. +Django's regular [FILE_UPLOAD_HANDLERS] are used for handling uploaded files.  ## FileField @@ -304,34 +334,128 @@ A file representation.  Performs Django's standard FileField validation.  Corresponds to `django.forms.fields.FileField`. -**Signature:** `FileField(max_length=None, allow_empty_file=False)` - - - `max_length` designates the maximum length for the file name. +**Signature:** `FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)` - - `allow_empty_file` designates if empty files are allowed. + - `max_length` - Designates the maximum length for the file name. + - `allow_empty_file` - Designates if empty files are allowed. +- `use_url` - If set to `True` then URL string values will be used for the output representation. If set to `False` then filename string values will be used for the output representation. Defaults to the value of the `UPLOADED_FILES_USE_URL` settings key, which is `True` unless set otherwise.  ## ImageField -An image representation. +An image representation. Validates the uploaded file content as matching a known image format.  Corresponds to `django.forms.fields.ImageField`. +**Signature:** `ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)` + + - `max_length` - Designates the maximum length for the file name. + - `allow_empty_file` - Designates if empty files are allowed. +- `use_url` - If set to `True` then URL string values will be used for the output representation. If set to `False` then filename string values will be used for the output representation. Defaults to the value of the `UPLOADED_FILES_USE_URL` settings key, which is `True` unless set otherwise. +  Requires either the `Pillow` package or `PIL` package.  The `Pillow` package is recommended, as `PIL` is no longer actively maintained. -Signature and validation is the same as with `FileField`. +--- + +# Composite fields + +## ListField + +A field class that validates a list of objects. + +**Signature**: `ListField(child)` + +- `child` - A field instance that should be used for validating the objects in the list. + +For example, to validate a list of integers you might use something like the following: + +    scores = serializers.ListField( +       child=serializers.IntegerField(min_value=0, max_value=100) +    ) + +The `ListField` class also supports a declarative style that allows you to write reusable list field classes. + +    class StringListField(serializers.ListField): +        child = serializers.CharField() + +We can now reuse our custom `StringListField` class throughout our application, without having to provide a `child` argument to it.  --- -**Note:** `FileFields` and `ImageFields` are only suitable for use with MultiPartParser, since e.g. json doesn't support file uploads. -Django's regular [FILE_UPLOAD_HANDLERS] are used for handling uploaded files. +# Miscellaneous fields + +## ReadOnlyField + +A field class that simply returns the value of the field without modification. + +This field is used by default with `ModelSerializer` when including field names that relate to an attribute rather than a model field. + +**Signature**: `ReadOnlyField()` + +For example, is `has_expired` was a property on the `Account` model, then the following serializer would automatically generate it as a `ReadOnlyField`: + +    class AccountSerializer(serializers.ModelSerializer): +        class Meta: +            model = Account +            fields = ('id', 'account_name', 'has_expired') + +## HiddenField + +A field class that does not take a value based on user input, but instead takes its value from a default value or callable. + +**Signature**: `HiddenField()` + +For example, to include a field that always provides the current time as part of the serializer validated data, you would use the following: + +    modified = serializer.HiddenField(default=timezone.now) + +The `HiddenField` class is usually only needed if you have some validation that needs to run based on some pre-provided field values, but you do not want to expose all of those fields to the end user. + +For further examples on `HiddenField` see the [validators](validators.md) documentation. + +## ModelField + +A generic field that can be tied to any arbitrary model field. The `ModelField` class delegates the task of serialization/deserialization to its associated model field.  This field can be used to create serializer fields for custom model fields, without having to create a new custom serializer field. + +This field is used by `ModelSerializer` to correspond to custom model field classes. + +**Signature:** `ModelField(model_field=<Django ModelField instance>)` + +The `ModelField` class is generally intended for internal use, but can be used by your API if needed.  In order to properly instantiate a `ModelField`, it must be passed a field that is attached to an instantiated model.  For example: `ModelField(model_field=MyModel()._meta.get_field('custom_field'))` + +## SerializerMethodField + +This is a read-only field. It gets its value by calling a method on the serializer class it is attached to. It can be used to add any sort of data to the serialized representation of your object. + +**Signature**: `SerializerMethodField(method_name=None)` + +- `method-name` - The name of the method on the serializer to be called. If not included this defaults to `get_<field_name>`. + +The serializer method referred to by the `method_name` argument should accept a single argument (in addition to `self`), which is the object being serialized. It should return whatever you want to be included in the serialized representation of the object. For example: + +    from django.contrib.auth.models import User +    from django.utils.timezone import now +    from rest_framework import serializers + +    class UserSerializer(serializers.ModelSerializer): +        days_since_joined = serializers.SerializerMethodField() + +        class Meta: +            model = User + +        def get_days_since_joined(self, obj): +            return (now() - obj.date_joined).days  ---  # Custom fields -If you want to create a custom field, you'll probably want to override either one or both of the `.to_native()` and `.from_native()` methods.  These two methods are used to convert between the initial datatype, and a primitive, serializable datatype.  Primitive datatypes may be any of a number, string, date/time/datetime or None.  They may also be any list or dictionary like object that only contains other primitive objects. +If you want to create a custom field, you'll need to subclass `Field` and then override either one or both of the `.to_representation()` and `.to_internal_value()` methods.  These two methods are used to convert between the initial datatype, and a primitive, serializable datatype. Primitive datatypes will typically be any of a number, string, boolean, `date`/`time`/`datetime` or `None`. They may also be any list or dictionary like object that only contains other primitive objects. Other types might be supported, depending on the renderer that you are using. -The `.to_native()` method is called to convert the initial datatype into a primitive, serializable datatype.  The `from_native()` method is called to restore a primitive datatype into its initial representation. +The `.to_representation()` method is called to convert the initial datatype into a primitive, serializable datatype. + +The `to_internal_value()` method is called to restore a primitive datatype into its internal python representation. + +Note that the `WritableField` class that was present in version 2.x no longer exists. You should subclass `Field` and override `to_internal_value()` if the field supports data input.  ## Examples @@ -346,25 +470,29 @@ Let's look at an example of serializing a class that represents an RGB color val              assert(red < 256 and green < 256 and blue < 256)              self.red, self.green, self.blue = red, green, blue -    class ColourField(serializers.WritableField): +    class ColorField(serializers.Field):          """          Color objects are serialized into "rgb(#, #, #)" notation.          """ -        def to_native(self, obj): +        def to_representation(self, obj):              return "rgb(%d, %d, %d)" % (obj.red, obj.green, obj.blue) -        def from_native(self, data): +        def to_internal_value(self, data):              data = data.strip('rgb(').rstrip(')')              red, green, blue = [int(col) for col in data.split(',')]              return Color(red, green, blue) - -By default field values are treated as mapping to an attribute on the object.  If you need to customize how the field value is accessed and set you need to override `.field_to_native()` and/or `.field_from_native()`. +By default field values are treated as mapping to an attribute on the object.  If you need to customize how the field value is accessed and set you need to override `.get_attribute()` and/or `.get_value()`.  As an example, let's create a field that can be used represent the class name of the object being serialized:      class ClassNameField(serializers.Field): -        def field_to_native(self, obj, field_name): +        def get_attribute(self, obj): +            # We pass the object instance onto `to_representation`, +            # not just the field attribute. +            return obj +  +        def to_representation(self, obj):              """              Serialize the object's class name.              """ diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index cfeb4334..83977048 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -1,4 +1,4 @@ -<a class="github" href="filters.py"></a> +source: filters.py  # Filtering @@ -26,7 +26,7 @@ For example:      class PurchaseList(generics.ListAPIView):          serializer_class = PurchaseSerializer -  +          def get_queryset(self):              """              This view should return a list of all the purchases @@ -38,7 +38,7 @@ For example:  ## Filtering against the URL -Another style of filtering might involve restricting the queryset based on some part of the URL.   +Another style of filtering might involve restricting the queryset based on some part of the URL.  For example if your URL config contained an entry like this: @@ -48,7 +48,7 @@ You could then write a view that returned a purchase queryset filtered by the us      class PurchaseList(generics.ListAPIView):          serializer_class = PurchaseSerializer -  +          def get_queryset(self):              """              This view should return a list of all the purchases for @@ -57,7 +57,7 @@ You could then write a view that returned a purchase queryset filtered by the us              username = self.kwargs['username']              return Purchase.objects.filter(purchaser__username=username) -## Filtering against query parameters  +## Filtering against query parameters  A final example of filtering the initial queryset would be to determine the initial queryset based on query parameters in the url. @@ -65,7 +65,7 @@ We can override `.get_queryset()` to deal with URLs such as `http://example.com/      class PurchaseList(generics.ListAPIView):          serializer_class = PurchaseSerializer -  +          def get_queryset(self):              """              Optionally restricts the returned purchases to a given user, @@ -113,7 +113,7 @@ For instance, given the previous example, and a product with an id of `4675`, th      http://example.com/api/products/4675/?category=clothing&max_price=10.00  ## Overriding the initial queryset -  +  Note that you can use both an overridden `.get_queryset()` and generic filtering together, and everything will work as expected.  For example, if `Product` had a many-to-many relationship with `User`, named `purchase`, you might want to write a view like this:      class PurchasedProductsList(generics.ListAPIView): @@ -124,7 +124,7 @@ Note that you can use both an overridden `.get_queryset()` and generic filtering          model = Product          serializer_class = ProductSerializer          filter_class = ProductFilter -         +          def get_queryset(self):              user = self.request.user              return user.purchase_set.all() @@ -135,7 +135,7 @@ Note that you can use both an overridden `.get_queryset()` and generic filtering  ## DjangoFilterBackend -The `DjangoFilterBackend` class supports highly customizable field filtering, using the [django-filter package][django-filter].   +The `DjangoFilterBackend` class supports highly customizable field filtering, using the [django-filter package][django-filter].  To use REST framework's `DjangoFilterBackend`, first install `django-filter`. @@ -216,7 +216,7 @@ This is nice, but it exposes the Django's double underscore convention as part o  And now you can execute:      http://example.com/api/products?manufacturer=foo -     +  For more details on using filter sets see the [django-filter documentation][django-filter-docs].  --- @@ -224,7 +224,7 @@ For more details on using filter sets see the [django-filter documentation][djan  **Hints & Tips**  * By default filtering is not enabled.  If you want to use `DjangoFilterBackend` remember to make sure it is installed by using the `'DEFAULT_FILTER_BACKENDS'` setting. -* When using boolean fields, you should use the values `True` and `False` in the URL query parameters, rather than `0`, `1`, `true` or `false`.  (The allowed boolean values are currently hardwired in Django's [NullBooleanSelect implementation][nullbooleanselect].)  +* When using boolean fields, you should use the values `True` and `False` in the URL query parameters, rather than `0`, `1`, `true` or `false`.  (The allowed boolean values are currently hardwired in Django's [NullBooleanSelect implementation][nullbooleanselect].)  * `django-filter` supports filtering across relationships, using Django's double-underscore syntax.  * For Django 1.3 support, make sure to install `django-filter` version 0.5.4, as later versions drop support for 1.3. @@ -316,7 +316,7 @@ Typically you'd instead control this by setting `order_by` on the initial querys          queryset = User.objects.all()          serializer_class = UserSerializer          filter_backends = (filters.OrderingFilter,) -        ordering = ('username',)  +        ordering = ('username',)  The `ordering` attribute may be either a string or a list/tuple of strings. diff --git a/docs/api-guide/format-suffixes.md b/docs/api-guide/format-suffixes.md index 529738e3..20c1e995 100644 --- a/docs/api-guide/format-suffixes.md +++ b/docs/api-guide/format-suffixes.md @@ -1,4 +1,4 @@ -<a class="github" href="urlpatterns.py"></a> +source: urlpatterns.py  # Format suffixes @@ -7,7 +7,7 @@ used all the time.  >  > — Roy Fielding, [REST discuss mailing list][cite] -A common pattern for Web APIs is to use filename extensions on URLs to provide an endpoint for a given media type.  For example, 'http://example.com/api/users.json' to serve a JSON representation.  +A common pattern for Web APIs is to use filename extensions on URLs to provide an endpoint for a given media type.  For example, 'http://example.com/api/users.json' to serve a JSON representation.  Adding format-suffix patterns to each individual entry in the URLconf for your API is error-prone and non-DRY, so REST framework provides a shortcut to adding these patterns to your URLConf. @@ -21,18 +21,19 @@ Arguments:  * **urlpatterns**: Required.  A URL pattern list.  * **suffix_required**:  Optional.  A boolean indicating if suffixes in the URLs should be optional or mandatory.  Defaults to `False`, meaning that suffixes are optional by default. -* **allowed**:  Optional.  A list or tuple of valid format suffixes.  If not provided, a wildcard format suffix pattern will be used.  +* **allowed**:  Optional.  A list or tuple of valid format suffixes.  If not provided, a wildcard format suffix pattern will be used.  Example:      from rest_framework.urlpatterns import format_suffix_patterns -     -    urlpatterns = patterns('blog.views', -        url(r'^/$', 'api_root'), -        url(r'^comments/$', 'comment_list'), -        url(r'^comments/(?P<pk>[0-9]+)/$', 'comment_detail') -    ) -     +    from blog import views + +    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'])  When using `format_suffix_patterns`, you must make sure to add the `'format'` keyword argument to the corresponding views.  For example: @@ -55,12 +56,12 @@ The name of the kwarg used may be modified by using the `FORMAT_SUFFIX_KWARG` se  Also note that `format_suffix_patterns` does not support descending into `include` URL patterns.  --- -         +  ## Accept headers vs. format suffixes  There seems to be a view among some of the Web community that filename extensions are not a RESTful pattern, and that `HTTP Accept` headers should always be used instead. -It is actually a misconception.  For example, take the following quote from Roy Fielding discussing the relative merits of query parameter media-type indicators vs. file extension media-type indicators:  +It is actually a misconception.  For example, take the following quote from Roy Fielding discussing the relative merits of query parameter media-type indicators vs. file extension media-type indicators:  “That's why I always prefer extensions.  Neither choice has anything to do with REST.” — Roy Fielding, [REST discuss mailing list][cite2] diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 49be0cae..f5bbdfdd 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -1,5 +1,11 @@ -<a class="github" href="mixins.py"></a> -<a class="github" href="generics.py"></a> +source: mixins.py +        generics.py + +--- + +**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available. + +---  # Generic views @@ -7,7 +13,7 @@  >  > — [Django Documentation][cite] -One of the key benefits of class based views is the way they allow you to compose bits of reusable behaviour.  REST framework takes advantage of this by providing a number of pre-built views that provide for commonly used patterns. +One of the key benefits of class based views is the way they allow you to compose bits of reusable behavior.  REST framework takes advantage of this by providing a number of pre-built views that provide for commonly used patterns.  The generic views provided by REST framework allow you to quickly build API views that map closely to your database models. @@ -171,24 +177,26 @@ For example:              return 20          return 100 -**Save / deletion hooks**: +**Save and deletion hooks**: -The following methods are provided as placeholder interfaces.  They contain empty implementations and are not called directly by `GenericAPIView`, but they are overridden and used by some of the mixin classes. +The following methods are provided by the mixin classes, and provide easy overriding of the object save or deletion behavior. -* `pre_save(self, obj)` - A hook that is called before saving an object. -* `post_save(self, obj, created=False)` - A hook that is called after saving an object. -* `pre_delete(self, obj)` - A hook that is called before deleting an object. -* `post_delete(self, obj)` - A hook that is called after deleting an object. +* `perform_create(self, serializer)` - Called by `CreateModelMixin` when saving a new object instance. +* `perform_update(self, serializer)` - Called by `UpdateModelMixin` when saving an existing object instance. +* `perform_destroy(self, instance)` - Called by `DestroyModelMixin` when deleting an object instance. -The `pre_save` method in particular is a useful hook for setting attributes that are implicit in the request, but are not part of the request data.  For instance, you might set an attribute on the object based on the request user, or based on a URL keyword argument. +These hooks are particularly useful for setting attributes that are implicit in the request, but are not part of the request data.  For instance, you might set an attribute on the object based on the request user, or based on a URL keyword argument. -    def pre_save(self, obj): -        """ -        Set the object's owner, based on the incoming request. -        """ -        obj.owner = self.request.user +    def perform_create(self, serializer): +        serializer.save(user=self.request.user) + +These override points are also particularly useful for adding behavior that occurs before or after saving an object, such as emailing a confirmation, or logging the update. + +    def perform_update(self, serializer): +        instance = serializer.save() +        send_email_confirmation(user=self.request.user, modified=instance) -Remember that the `pre_save()` method is not called by `GenericAPIView` itself, but it is called by `create()` and `update()` methods on the `CreateModelMixin` and `UpdateModelMixin` classes. +**Note**: These methods replace the old-style version 2.x `pre_save`, `post_save`, `pre_delete` and `post_delete` methods, which are no longer available.  **Other methods**: @@ -352,7 +360,7 @@ You can then simply apply this mixin to a view or viewset anytime you need to ap          serializer_class = UserSerializer          lookup_fields = ('account', 'username') -Using custom mixins is a good option if you have custom behavior that needs to be used +Using custom mixins is a good option if you have custom behavior that needs to be used.  ## Creating custom base classes @@ -368,6 +376,20 @@ If you are using a mixin across multiple views, you can take this a step further  Using custom base classes is a good option if you have custom behavior that consistently needs to be repeated across a large number of views throughout your project. +--- + +# PUT as create + +Prior to version 3.0 the REST framework mixins treated `PUT` as either an update or a create operation, depending on if the object already existed or not. + +Allowing `PUT` as create operations is problematic, as it necessarily exposes information about the existence or non-existence 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 `404` responses. + +Both styles "`PUT` as 404" and "`PUT` as create" can be valid in different circumstances, but from version 3.0 onwards we now use 404 behavior as the default, due to it being simpler and more obvious. + +If you need to generic PUT-as-create behavior you may want to include something like [this `AllowPUTAsCreateMixin` class](https://gist.github.com/tomchristie/a2ace4577eff2c603b1b) as a mixin to your views. + +--- +  # Third party packages  The following third party packages provide additional generic view implementations. diff --git a/docs/api-guide/metadata.md b/docs/api-guide/metadata.md new file mode 100644 index 00000000..c3e51148 --- /dev/null +++ b/docs/api-guide/metadata.md @@ -0,0 +1,109 @@ +<a class="github" href="metadata.py"></a> + +--- + +**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available. + +--- + +# Metadata + +> [The `OPTIONS`] 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. +> +> — [RFC7231, Section 4.3.7.][cite] + +REST framework includes a configurable mechanism for determining how your API should respond to `OPTIONS` requests. This allows you to return API schema or other resource information. + +There are not currently any widely adopted conventions for exactly what style of response should be returned for HTTP `OPTIONS` requests, so we provide an ad-hoc style that returns some useful information. + +Here's an example response that demonstrates the information that is returned by default. + +    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 +                } +            } +        } +    } + +## Setting the metadata scheme + +You can set the metadata class globally using the `'DEFAULT_METADATA_CLASS'` settings key: + +    REST_FRAMEWORK = { +        'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata' +    } + +Or you can set the metadata class individually for a view: + +    class APIRoot(APIView): +        metadata_class = APIRootMetadata +         +        def get(self, request, format=None): +            return Response({ +                ... +            }) + +The REST framework package only includes a single metadata class implementation, named `SimpleMetadata`. If you want to use an alternative style you'll need to implement a custom metadata class. + +## Creating schema endpoints + +If you have specific requirements for creating schema endpoints that are accessed with regular `GET` requests, you might consider re-using the metadata API for doing so. + +For example, the following additional route could be used on a viewset to provide a linkable schema endpoint. + +    @list_route(methods=['GET']) +    def schema(self, request): +        meta = self.metadata_class() +        data = meta.determine_metadata(request, self) +        return Response(data) + +There are a couple of reasons that you might choose to take this approach, including that `OPTIONS` responses [are not cacheable][no-options]. + +--- + +# Custom metadata classes + +If you want to provide a custom metadata class you should override `BaseMetadata` and implement the `determine_metadata(self, request, view)` method. + +Useful things that you might want to do could include returning schema information, using a format such as [JSON schema][json-schema], or returning debug information to admin users. + +## Example + +The following class could be used to limit the information that is returned to `OPTIONS` requests. + +    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() +            } + +[cite]: http://tools.ietf.org/html/rfc7231#section-4.3.7 +[no-options]: https://www.mnot.net/blog/2012/10/29/NO_OPTIONS +[json-schema]: http://json-schema.org/ diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index e57aed1a..9b7086c5 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -1,4 +1,4 @@ -<a class="github" href="pagination.py"></a> +source: pagination.py  # Pagination @@ -6,7 +6,7 @@  >  > — [Django documentation][cite] -REST framework includes a `PaginationSerializer` class that makes it easy to return paginated data in a way that can then be rendered to arbitrary media types.  +REST framework includes a `PaginationSerializer` class that makes it easy to return paginated data in a way that can then be rendered to arbitrary media types.  ## Paginating basic data @@ -33,7 +33,7 @@ The `context` argument of the `PaginationSerializer` class may optionally includ      request = RequestFactory().get('/foobar')      serializer = PaginationSerializer(instance=page, context={'request': request})      serializer.data -    # {'count': 4, 'next': 'http://testserver/foobar?page=2', 'previous': None, 'results': [u'john', u'paul']}     +    # {'count': 4, 'next': 'http://testserver/foobar?page=2', 'previous': None, 'results': [u'john', u'paul']}  We could now return that data in a `Response` object, and it would be rendered into the correct media type. diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index 72a4af64..73e3a705 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -1,4 +1,4 @@ -<a class="github" href="parsers.py"></a> +source: parsers.py  # Parsers @@ -12,7 +12,7 @@ REST framework includes a number of built in Parser classes, that allow you to a  ## How the parser is determined -The set of valid parsers for a view is always defined as a list of classes.  When either `request.DATA` or `request.FILES` is accessed, REST framework will examine the `Content-Type` header on the incoming request, and determine which parser to use to parse the request content. +The set of valid parsers for a view is always defined as a list of classes.  When  `request.data` is accessed, REST framework will examine the `Content-Type` header on the incoming request, and determine which parser to use to parse the request content.  --- @@ -48,7 +48,7 @@ using the `APIView` class based views.          parser_classes = (YAMLParser,)          def post(self, request, format=None): -            return Response({'received data': request.DATA}) +            return Response({'received data': request.data})  Or, if you're using the `@api_view` decorator with function based views. @@ -58,7 +58,7 @@ Or, if you're using the `@api_view` decorator with function based views.          """          A view that can accept POST requests with YAML content.          """ -        return Response({'received data': request.DATA}) +        return Response({'received data': request.data})  --- @@ -92,7 +92,7 @@ Requires the `defusedxml` package to be installed.  ## FormParser -Parses HTML form content.  `request.DATA` will be populated with a `QueryDict` of data, `request.FILES` will be populated with an empty `QueryDict` of data. +Parses HTML form content.  `request.data` will be populated with a `QueryDict` of data.  You will typically want to use both `FormParser` and `MultiPartParser` together in order to fully support HTML form data. @@ -100,7 +100,7 @@ You will typically want to use both `FormParser` and `MultiPartParser` together  ## MultiPartParser -Parses multipart HTML form content, which supports file uploads.  Both `request.DATA` and `request.FILES` will be populated with a `QueryDict`. +Parses multipart HTML form content, which supports file uploads.  Both `request.data` will be populated with a `QueryDict`.  You will typically want to use both `FormParser` and `MultiPartParser` together in order to fully support HTML form data. @@ -108,7 +108,7 @@ You will typically want to use both `FormParser` and `MultiPartParser` together  ## FileUploadParser -Parses raw file upload content.  The `request.DATA` property will be an empty `QueryDict`, and `request.FILES` will be a dictionary with a single key `'file'` containing the uploaded file. +Parses raw file upload content.  The `request.data` property will be a dictionary with a single key `'file'` containing the uploaded file.  If the view used with `FileUploadParser` is called with a `filename` URL keyword argument, then that argument will be used as the filename.  If it is called without a `filename` URL keyword argument, then the client must set the filename in the `Content-Disposition` HTTP header.  For example `Content-Disposition: attachment; filename=upload.jpg`. @@ -126,7 +126,7 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword          parser_classes = (FileUploadParser,)          def put(self, request, filename, format=None): -            file_obj = request.FILES['file'] +            file_obj = request.data['file']              # ...              # do some staff with uploaded file              # ... @@ -139,7 +139,7 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword  To implement a custom parser, you should override `BaseParser`, set the `.media_type` property, and implement the `.parse(self, stream, media_type, parser_context)` method. -The method should return the data that will be used to populate the `request.DATA` property. +The method should return the data that will be used to populate the `request.data` property.  The arguments passed to `.parse()` are: @@ -161,7 +161,7 @@ By default this will include the following keys: `view`, `request`, `args`, `kwa  ## Example -The following is an example plaintext parser that will populate the `request.DATA` property with a string representing the body of the request.  +The following is an example plaintext parser that will populate the `request.data` property with a string representing the body of the request.       class PlainTextParser(BaseParser):      """ @@ -197,4 +197,4 @@ The following third party packages are also available.  [juanriaza]: https://github.com/juanriaza  [vbabiy]: https://github.com/vbabiy  [djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack -[djangorestframework-camel-case]: https://github.com/vbabiy/djangorestframework-camel-case
\ No newline at end of file +[djangorestframework-camel-case]: https://github.com/vbabiy/djangorestframework-camel-case diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index a32db4a2..7558475f 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -1,4 +1,4 @@ -<a class="github" href="permissions.py"></a> +source: permissions.py  # Permissions @@ -12,7 +12,7 @@ Permission checks are always run at the very start of the view, before any other  ## How permissions are determined -Permissions in REST framework are always defined as a list of permission classes.   +Permissions in REST framework are always defined as a list of permission classes.  Before running the main body of the view each permission in the list is checked.  If any permission check fails an `exceptions.PermissionDenied` exception will be raised, and the main body of the view will not run. @@ -146,7 +146,13 @@ As with `DjangoModelPermissions`, this permission must only be applied to views  Note that `DjangoObjectPermissions` **does not** require the `django-guardian` package, and should support other object-level backends equally well. -As with `DjangoModelPermissions` you can use custom model permissions by overriding `DjangoModelPermissions` and setting the `.perms_map` property.  Refer to the source code for details.  Note that if you add a custom `view` permission for `GET`, `HEAD` and `OPTIONS` requests, you'll probably also want to consider adding the `DjangoObjectPermissionsFilter` class to ensure that list endpoints only return results including objects for which the user has appropriate view permissions. +As with `DjangoModelPermissions` you can use custom model permissions by overriding `DjangoModelPermissions` and setting the `.perms_map` property.  Refer to the source code for details. + +--- + +**Note**: If you need object level `view` permissions for `GET`, `HEAD` and `OPTIONS` requests, you'll want to consider also adding the `DjangoObjectPermissionsFilter` class to ensure that list endpoints only return results including objects for which the user has appropriate view permissions. + +---  --- @@ -168,11 +174,7 @@ If you need to test if a request is a read operation or a write operation, you s  --- -**Note**: In versions 2.0 and 2.1, the signature for the permission checks always included an optional `obj` parameter, like so: `.has_permission(self, request, view, obj=None)`.  The method would be called twice, first for the global permission checks, with no object supplied, and second for the object-level check when required. - -As of version 2.2 this signature has now been replaced with two separate method calls, which is more explicit and obvious.  The old style signature continues to work, but its use will result in a `PendingDeprecationWarning`, which is silent by default.  In 2.3 this will be escalated to a `DeprecationWarning`, and in 2.4 the old-style signature will be removed. - -For more details see the [2.2 release announcement][2.2-announcement]. +**Note**: The instance-level `has_object_permission` method will only be called if the view-level `has_permission` checks have already passed. Also note that in order for the instance-level checks to run, the view code should explicitly call `.check_object_permissions(request, obj)`. If you are using the generic views then this will be handled for you by default.  --- @@ -203,9 +205,9 @@ As well as global permissions, that are run against all incoming requests, you c          def has_object_permission(self, request, view, obj):              # Read permissions are allowed to any request,              # so we'll always allow GET, HEAD or OPTIONS requests. -            if request.method in permissions.SAFE_METHODS:             +            if request.method in permissions.SAFE_METHODS:                  return True -     +              # Instance must have an attribute named `owner`.              return obj.owner == request.user diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index cc4f5585..a79b6ea5 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -1,4 +1,10 @@ -<a class="github" href="relations.py"></a> +source: relations.py + +--- + +**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available. + +---  # Serializer relations @@ -16,6 +22,20 @@ Relational fields are used to represent model relationships.  They can be applie  --- +#### Inspecting automatically generated relationships. + +When using the `ModelSerializer` class, serializer fields and relationships will be automatically generated for you. Inspecting these automatically generated fields can be a useful tool for determining how to customize the relationship style. + +To do so, open the Django shell, using `python manage.py shell`, then import the serializer class, instantiate it, and print the object representation… + +    >>> from myapp.serializers import AccountSerializer +    >>> serializer = AccountSerializer() +    >>> print repr(serializer)  # Or `print(repr(serializer))` in Python 3.x. +    AccountSerializer(): +        id = IntegerField(label='ID', read_only=True) +        name = CharField(allow_blank=True, max_length=100, required=False) +        owner = PrimaryKeyRelatedField(queryset=User.objects.all()) +  # API Reference  In order to explain the various types of relational fields, we'll use a couple of simple models for our examples.  Our models will be for music albums, and the tracks listed on each album. @@ -33,19 +53,19 @@ In order to explain the various types of relational fields, we'll use a couple o          class Meta:              unique_together = ('album', 'order')              order_by = 'order' -         +          def __unicode__(self):              return '%d: %s' % (self.order, self.title) -## RelatedField +## StringRelatedField -`RelatedField` may be used to represent the target of the relationship using its `__unicode__` method. +`StringRelatedField` may be used to represent the target of the relationship using its `__unicode__` method.  For example, the following serializer. -  +      class AlbumSerializer(serializers.ModelSerializer): -        tracks = serializers.RelatedField(many=True) -         +        tracks = serializers.StringRelatedField(many=True) +          class Meta:              model = Album              fields = ('album_name', 'artist', 'tracks') @@ -74,10 +94,10 @@ This field is read only.  `PrimaryKeyRelatedField` may be used to represent the target of the relationship using its primary key.  For example, the following serializer: -  +      class AlbumSerializer(serializers.ModelSerializer):          tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True) -         +          class Meta:              model = Album              fields = ('album_name', 'artist', 'tracks') @@ -99,20 +119,23 @@ By default this field is read-write, although you can change this behavior using  **Arguments**: +* `queryset` - The queryset used for model instance lookups when validating the field input. Relationships must either set a queryset explicitly, or set `read_only=True`.  * `many` - If applied to a to-many relationship, you should set this argument to `True`. -* `required` - If set to `False`, the field will accept values of `None` or the empty-string for nullable relationships. -* `queryset` - By default `ModelSerializer` classes will use the default queryset for the relationship.  `Serializer` classes must either set a queryset explicitly, or set `read_only=True`. +* `allow_null` - If set to `True`, the field will accept values of `None` or the empty string for nullable relationships. Defaults to `False`.  ## HyperlinkedRelatedField  `HyperlinkedRelatedField` may be used to represent the target of the relationship using a hyperlink.  For example, the following serializer: -  +      class AlbumSerializer(serializers.ModelSerializer): -        tracks = serializers.HyperlinkedRelatedField(many=True, read_only=True, -                                                     view_name='track-detail') -         +        tracks = serializers.HyperlinkedRelatedField( +            many=True, +            read_only=True, +            view_name='track-detail' +        ) +          class Meta:              model = Album              fields = ('album_name', 'artist', 'tracks') @@ -134,11 +157,12 @@ By default this field is read-write, although you can change this behavior using  **Arguments**: -* `view_name` - The view name that should be used as the target of the relationship.  If you're using [the standard router classes][routers] this wil be a string with the format `<modelname>-detail`. **required**. +* `view_name` - The view name that should be used as the target of the relationship.  If you're using [the standard router classes][routers] this will be a string with the format `<modelname>-detail`. **required**. +* `queryset` - The queryset used for model instance lookups when validating the field input. Relationships must either set a queryset explicitly, or set `read_only=True`.  * `many` - If applied to a to-many relationship, you should set this argument to `True`. -* `required` - If set to `False`, the field will accept values of `None` or the empty-string for nullable relationships. -* `queryset` - By default `ModelSerializer` classes will use the default queryset for the relationship.  `Serializer` classes must either set a queryset explicitly, or set `read_only=True`. +* `allow_null` - If set to `True`, the field will accept values of `None` or the empty string for nullable relationships. Defaults to `False`.  * `lookup_field` - The field on the target that should be used for the lookup.  Should correspond to a URL keyword argument on the referenced view.  Default is `'pk'`. +* `lookup_url_kwarg` - The name of the keyword argument defined in the URL conf that corresponds to the lookup field. Defaults to using the same value as `lookup_field`.  * `format` - If using format suffixes, hyperlinked fields will use the same format suffix for the target unless overridden by using the `format` argument.  ## SlugRelatedField @@ -146,11 +170,14 @@ By default this field is read-write, although you can change this behavior using  `SlugRelatedField` may be used to represent the target of the relationship using a field on the target.  For example, the following serializer: -  +      class AlbumSerializer(serializers.ModelSerializer): -        tracks = serializers.SlugRelatedField(many=True, read_only=True, -                                              slug_field='title') -         +        tracks = serializers.SlugRelatedField( +            many=True, +            read_only=True, +            slug_field='title' +         ) +          class Meta:              model = Album              fields = ('album_name', 'artist', 'tracks') @@ -175,9 +202,9 @@ When using `SlugRelatedField` as a read-write field, you will normally want to e  **Arguments**:  * `slug_field` - The field on the target that should be used to represent it.  This should be a field that uniquely identifies any given instance.  For example, `username`.  **required** +* `queryset` - The queryset used for model instance lookups when validating the field input. Relationships must either set a queryset explicitly, or set `read_only=True`.  * `many` - If applied to a to-many relationship, you should set this argument to `True`. -* `required` - If set to `False`, the field will accept values of `None` or the empty-string for nullable relationships. -* `queryset` - By default `ModelSerializer` classes will use the default queryset for the relationship.  `Serializer` classes must either set a queryset explicitly, or set `read_only=True`. +* `allow_null` - If set to `True`, the field will accept values of `None` or the empty string for nullable relationships. Defaults to `False`.  ## HyperlinkedIdentityField @@ -202,7 +229,7 @@ This field is always read-only.  **Arguments**: -* `view_name` - The view name that should be used as the target of the relationship.  If you're using [the standard router classes][routers] this wil be a string with the format `<model_name>-detail`.  **required**. +* `view_name` - The view name that should be used as the target of the relationship.  If you're using [the standard router classes][routers] this will be a string with the format `<model_name>-detail`.  **required**.  * `lookup_field` - The field on the target that should be used for the lookup.  Should correspond to a URL keyword argument on the referenced view.  Default is `'pk'`.  * `format` - If using format suffixes, hyperlinked fields will use the same format suffix for the target unless overridden by using the `format` argument. @@ -222,10 +249,10 @@ For example, the following serializer:          class Meta:              model = Track              fields = ('order', 'title') -     +      class AlbumSerializer(serializers.ModelSerializer): -        tracks = TrackSerializer(many=True) -         +        tracks = TrackSerializer(many=True, read_only=True) +          class Meta:              model = Album              fields = ('album_name', 'artist', 'tracks') @@ -245,9 +272,9 @@ Would serialize to a nested representation like this:  # Custom relational fields -To implement a custom relational field, you should override `RelatedField`, and implement the `.to_native(self, value)` method.  This method takes the target of the field as the `value` argument, and should return the representation that should be used to serialize the target. +To implement a custom relational field, you should override `RelatedField`, and implement the `.to_representation(self, value)` method. This method takes the target of the field as the `value` argument, and should return the representation that should be used to serialize the target. The `value` argument will typically be a model instance. -If you want to implement a read-write relational field, you must also implement the `.from_native(self, data)` method, and add `read_only = False` to the class definition. +If you want to implement a read-write relational field, you must also implement the `.to_internal_value(self, data)` method.  ## Example @@ -256,13 +283,13 @@ For, example, we could define a relational field, to serialize a track to a cust      import time      class TrackListingField(serializers.RelatedField): -        def to_native(self, value): +        def to_representation(self, value):              duration = time.strftime('%M:%S', time.gmtime(value.duration))              return 'Track %d: %s (%s)' % (value.order, value.name, duration)      class AlbumSerializer(serializers.ModelSerializer):          tracks = TrackListingField(many=True) -         +          class Meta:              model = Album              fields = ('album_name', 'artist', 'tracks') @@ -284,6 +311,16 @@ This custom field would then serialize to the following representation.  # Further notes +## The `queryset` argument + +The `queryset` argument is only ever required for *writable* relationship field, in which case it is used for performing the model instance lookup, that maps from the primitive user input, into a model instance. + +In version 2.x a serializer class could *sometimes* automatically determine the `queryset` argument *if* a `ModelSerializer` class was being used. + +This behavior is now replaced with *always* using an explicit `queryset` argument for writable relational fields. + +Doing so reduces the amount of hidden 'magic' that `ModelSerializer` provides, makes the behavior of the field more clear, and ensures that it is trivial to move between using the `ModelSerializer` shortcut, or using fully explicit `Serializer` classes. +  ## Reverse relations  Note that reverse relationships are not automatically included by the `ModelSerializer` and `HyperlinkedModelSerializer` classes.  To include a reverse relationship, you must explicitly add it to the fields list.  For example: @@ -302,7 +339,7 @@ If you have not set a related name for the reverse relationship, you'll need to      class AlbumSerializer(serializers.ModelSerializer):          class Meta: -            fields = ('track_set', ...)  +            fields = ('track_set', ...)  See the Django documentation on [reverse relationships][reverse-relationships] for more details. @@ -315,14 +352,14 @@ For example, given the following model for a tag, which has a generic relationsh      class TaggedItem(models.Model):          """          Tags arbitrary model instances using a generic relation. -         +          See: https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/          """          tag_name = models.SlugField()          content_type = models.ForeignKey(ContentType)          object_id = models.PositiveIntegerField()          tagged_object = GenericForeignKey('content_type', 'object_id') -     +          def __unicode__(self):              return self.tag @@ -350,23 +387,23 @@ We could define a custom field that could be used to serialize tagged instances,          A custom field to use for the `tagged_object` generic relationship.          """ -        def to_native(self, value): +        def to_representation(self, value):              """              Serialize tagged objects to a simple textual representation. -            """                             +            """              if isinstance(value, Bookmark):                  return 'Bookmark: ' + value.url              elif isinstance(value, Note):                  return 'Note: ' + value.text              raise Exception('Unexpected type of tagged object') -If you need the target of the relationship to have a nested representation, you can  use the required serializers inside the `.to_native()` method: +If you need the target of the relationship to have a nested representation, you can use the required serializers inside the `.to_native()` method: -        def to_native(self, value): +        def to_representation(self, value):              """              Serialize bookmark instances using a bookmark serializer,              and note instances using a note serializer. -            """                             +            """              if isinstance(value, Bookmark):                  serializer = BookmarkSerializer(value)              elif isinstance(value, Note): @@ -391,7 +428,7 @@ to ``True``.  ## Advanced Hyperlinked fields -If you have very specific requirements for the style of your hyperlinked relationships you can override `HyperlinkedRelatedField`.  +If you have very specific requirements for the style of your hyperlinked relationships you can override `HyperlinkedRelatedField`.  There are two methods you'll need to override. @@ -404,14 +441,13 @@ attributes are not configured to correctly match the URL conf.  #### get_object(self, queryset, view_name, view_args, view_kwargs) -  This method should the object that corresponds to the matched URL conf arguments.  May raise an `ObjectDoesNotExist` exception.  ### Example -For example, if all your object URLs used both a account and a slug in the the URL to reference the object, you might create a custom field like this:  +For example, if all your object URLs used both a account and a slug in the the URL to reference the object, you might create a custom field like this:      class CustomHyperlinkedField(serializers.HyperlinkedRelatedField):          def get_url(self, obj, view_name, request, format): @@ -425,25 +461,6 @@ For example, if all your object URLs used both a account and a slug in the the U  --- -## Deprecated APIs - -The following classes have been deprecated, in favor of the `many=<bool>` syntax. -They continue to function, but their usage will raise a `PendingDeprecationWarning`, which is silent by default. - -* `ManyRelatedField` -* `ManyPrimaryKeyRelatedField` -* `ManyHyperlinkedRelatedField` -* `ManySlugRelatedField` - -The `null=<bool>` flag has been deprecated in favor of the `required=<bool>` flag.  It will continue to function, but will raise a `PendingDeprecationWarning`. - -In the 2.3 release, these warnings will be escalated to a `DeprecationWarning`, which is loud by default. -In the 2.4 release, these parts of the API will be removed entirely. - -For more details see the [2.2 release announcement][2.2-announcement]. - ---- -  # Third Party Packages  The following third party packages are also available. diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index 2e1c892f..035ec1d2 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -1,4 +1,4 @@ -<a class="github" href="renderers.py"></a> +source: renderers.py  # Renderers @@ -74,7 +74,7 @@ If your API includes views that can serve both regular webpages and API response  Renders the request data into `JSON`, using utf-8 encoding. -Note that the default style is to include unicode characters, and render the response using a compact style with no uneccessary whitespace: +Note that the default style is to include unicode characters, and render the response using a compact style with no unnecessary whitespace:      {"unicode black star":"★","value":999} @@ -115,7 +115,7 @@ The `jsonp` approach is essentially a browser hack, and is [only appropriate for  ## YAMLRenderer -Renders the request data into `YAML`.  +Renders the request data into `YAML`.  Requires the `pyyaml` package to be installed. @@ -131,7 +131,7 @@ Note that non-ascii characters will be rendered using `\uXXXX` character escape.  ## UnicodeYAMLRenderer -Renders the request data into `YAML`.  +Renders the request data into `YAML`.  Requires the `pyyaml` package to be installed. @@ -184,7 +184,7 @@ An example of a view that uses `TemplateHTMLRenderer`:          def get(self, request, *args, **kwargs):              self.object = self.get_object()              return Response({'user': self.object}, template_name='user_detail.html') -  +  You can use `TemplateHTMLRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint.  If you're building websites that use `TemplateHTMLRenderer` along with other renderer classes, you should consider listing `TemplateHTMLRenderer` as the first class in the `renderer_classes` list, so that it will be prioritised first even for browsers that send poorly formed `ACCEPT:` headers. @@ -205,7 +205,7 @@ An example of a view that uses `TemplateHTMLRenderer`:      @api_view(('GET',))      @renderer_classes((StaticHTMLRenderer,)) -    def simple_html_view(request):  +    def simple_html_view(request):          data = '<html><body><h1>Hello, world</h1></body></html>'          return Response(data) @@ -300,7 +300,7 @@ The following is an example plaintext renderer that will return a response with      class PlainTextRenderer(renderers.BaseRenderer):          media_type = 'text/plain'          format = 'txt' -         +          def render(self, data, media_type=None, renderer_context=None):              return data.encode(self.charset) @@ -340,7 +340,7 @@ You can do some pretty flexible things using REST framework's renderers.  Some e  * Provide either flat or nested representations from the same endpoint, depending on the requested media type.  * Serve both regular HTML webpages, and JSON based API responses from the same endpoints.  * Specify multiple types of HTML representation for API clients to use. -* Underspecify a renderer's media type, such as using `media_type = 'image/*'`, and use the `Accept` header to vary the encoding of the response.  +* Underspecify a renderer's media type, such as using `media_type = 'image/*'`, and use the `Accept` header to vary the encoding of the response.  ## Varying behaviour by media type diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md index 0696fedf..77000ffa 100644 --- a/docs/api-guide/requests.md +++ b/docs/api-guide/requests.md @@ -1,4 +1,10 @@ -<a class="github" href="request.py"></a> +source: request.py + +--- + +**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available. + +---  # Requests @@ -14,26 +20,29 @@ REST framework's `Request` class extends the standard `HttpRequest`, adding supp  REST framework's Request objects provide flexible request parsing that allows you to treat requests with JSON data or other media types in the same way that you would normally deal with form data. -## .DATA +## .data -`request.DATA` returns the parsed content of the request body.  This is similar to the standard `request.POST` attribute except that: +`request.data` returns the parsed content of the request body.  This is similar to the standard `request.POST` and `request.FILES` attributes except that: +* It includes all parsed content, including *file and non-file* inputs.  * It supports parsing the content of HTTP methods other than `POST`, meaning that you can access the content of `PUT` and `PATCH` requests.  * It supports REST framework's flexible request parsing, rather than just supporting form data.  For example you can handle incoming JSON data in the same way that you handle incoming form data.  For more details see the [parsers documentation]. -## .FILES +## .query_params -`request.FILES` returns any uploaded files that may be present in the content of the request body.  This is the same as the standard `HttpRequest` behavior, except that the same flexible request parsing is used for `request.DATA`. +`request.query_params` is a more correctly named synonym for `request.GET`. -For more details see the [parsers documentation]. +For clarity inside your code, we recommend using `request.query_params` instead of the Django's standard `request.GET`. Doing so will help keep your codebase more correct and obvious - any HTTP method type may include query parameters, not just `GET` requests. -## .QUERY_PARAMS +## .DATA and .FILES -`request.QUERY_PARAMS` is a more correctly named synonym for `request.GET`. +The old-style version 2.x `request.data` and `request.FILES` attributes are still available, but are now pending deprecation in favor of the unified `request.data` attribute. + +## .QUERY_PARAMS -For clarity inside your code, we recommend using `request.QUERY_PARAMS` instead of the usual `request.GET`, as *any* HTTP method type may include query parameters. +The old-style version 2.x `request.QUERY_PARAMS` attribute is still available, but is now pending deprecation in favor of the more pythonic `request.query_params`.  ## .parsers @@ -43,12 +52,26 @@ You won't typically need to access this property.  --- -**Note:** If a client sends malformed content, then accessing `request.DATA` or `request.FILES` may raise a `ParseError`.  By default REST framework's `APIView` class or `@api_view` decorator will catch the error and return a `400 Bad Request` response. +**Note:** If a client sends malformed content, then accessing `request.data` may raise a `ParseError`.  By default REST framework's `APIView` class or `@api_view` decorator will catch the error and return a `400 Bad Request` response.  If a client sends a request with a content-type that cannot be parsed then a `UnsupportedMediaType` exception will be raised, which by default will be caught and return a `415 Unsupported Media Type` response.  --- +# Content negotiation + +The request exposes some properties that allow you to determine the result of the content negotiation stage. This allows you to implement behaviour such as selecting a different serialisation schemes for different media types. + +## .accepted_renderer + +The renderer instance what was selected by the content negotiation stage. + +## .accepted_media_type + +A string representing the media type that was accepted by the content negotiation stage. + +--- +  # Authentication  REST framework provides flexible, per-request authentication, that gives you the ability to: @@ -91,7 +114,7 @@ REST framework supports a few browser enhancements such as browser-based `PUT`,  Browser-based `PUT`, `PATCH` and `DELETE` forms are transparently supported. -For more information see the [browser enhancements documentation].     +For more information see the [browser enhancements documentation].  ## .content_type @@ -101,7 +124,7 @@ You won't typically need to directly access the request's content type, as you'l  If you do need to access the content type of the request you should use the `.content_type` property in preference to using `request.META.get('HTTP_CONTENT_TYPE')`, as it provides transparent support for browser-based non-form content. -For more information see the [browser enhancements documentation].     +For more information see the [browser enhancements documentation].  ## .stream @@ -111,7 +134,7 @@ You won't typically need to directly access the request's content, as you'll nor  If you do need to access the raw content directly, you should use the `.stream` property in preference to using `request.content`, as it provides transparent support for browser-based non-form content. -For more information see the [browser enhancements documentation].     +For more information see the [browser enhancements documentation].  --- diff --git a/docs/api-guide/responses.md b/docs/api-guide/responses.md index 5a42aa92..97f31271 100644 --- a/docs/api-guide/responses.md +++ b/docs/api-guide/responses.md @@ -1,4 +1,4 @@ -<a class="github" href="response.py"></a> +source: response.py  # Responses @@ -90,6 +90,6 @@ The `Response` class extends `SimpleTemplateResponse`, and all the usual attribu  As with any other `TemplateResponse`, this method is called to render the serialized data of the response into the final response content.  When `.render()` is called, the response content will be set to the result of calling the `.render(data, accepted_media_type, renderer_context)` method on the `accepted_renderer` instance.  You won't typically need to call `.render()` yourself, as it's handled by Django's standard response cycle. -     +  [cite]: https://docs.djangoproject.com/en/dev/ref/template-response/  [statuscodes]: status-codes.md diff --git a/docs/api-guide/reverse.md b/docs/api-guide/reverse.md index 383eca4c..71fb83f9 100644 --- a/docs/api-guide/reverse.md +++ b/docs/api-guide/reverse.md @@ -1,4 +1,4 @@ -<a class="github" href="reverse.py"></a> +source: reverse.py  # Returning URLs @@ -30,7 +30,7 @@ You should **include the request as a keyword argument** to the function, for ex      from rest_framework.reverse import reverse      from rest_framework.views import APIView  	from django.utils.timezone import now -    +  	class APIRootView(APIView):  	    def get(self, request):  	        year = now().year diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 61a476b8..080230fa 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -1,4 +1,4 @@ -<a class="github" href="routers.py"></a> +source: routers.py  # Routers @@ -56,10 +56,10 @@ For example, given a method like this on the `UserViewSet` class:      from myapp.permissions import IsAdminOrIsSelf      from rest_framework.decorators import detail_route -     +      class UserViewSet(ModelViewSet):          ... -         +          @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf])          def set_password(self, request, pk=None):              ... @@ -228,7 +228,7 @@ For another example of setting the `.routes` attribute, see the source code for  ## Advanced custom routers -If you want to provide totally custom behavior, you can override `BaseRouter` and override the `get_urls(self)` method.  The method should inspect the registered viewsets and return a list of URL patterns.  The registered prefix, viewset and basename tuples may be inspected by accessing the `self.registry` attribute.   +If you want to provide totally custom behavior, you can override `BaseRouter` and override the `get_urls(self)` method.  The method should inspect the registered viewsets and return a list of URL patterns.  The registered prefix, viewset and basename tuples may be inspected by accessing the `self.registry` attribute.  You may also want to override the `get_default_base_name(self, viewset)` method, or else always explicitly set the `base_name` argument when registering your viewsets with the router. diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index a3694510..0ee80d53 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -1,4 +1,10 @@ -<a class="github" href="serializers.py"></a> +source: serializers.py + +--- + +**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available. + +---  # Serializers @@ -10,7 +16,7 @@ will take some serious design work.  Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into `JSON`, `XML` or other content types.  Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data. -REST framework's serializers work very similarly to Django's `Form` and `ModelForm` classes.  It provides a `Serializer` class which gives you a powerful, generic way to control the output of your responses, as well as a `ModelSerializer` class which provides a useful shortcut for creating serializers that deal with model instances and querysets. +The serializers in REST framework work very similarly to Django's `Form` and `ModelForm` classes. We provide a `Serializer` class which gives you a powerful, generic way to control the output of your responses, as well as a `ModelSerializer` class which provides a useful shortcut for creating serializers that deal with model instances and querysets.  ## Declaring Serializers @@ -21,10 +27,10 @@ Let's start by creating a simple object we can use for example purposes:              self.email = email              self.content = content              self.created = created or datetime.datetime.now() -     +      comment = Comment(email='leila@example.com', content='foo bar') -We'll declare a serializer that we can use to serialize and deserialize `Comment` objects. +We'll declare a serializer that we can use to serialize and deserialize data that corresponds to `Comment` objects.  Declaring a serializer looks very similar to declaring a form: @@ -35,25 +41,9 @@ Declaring a serializer looks very similar to declaring a form:          content = serializers.CharField(max_length=200)          created = serializers.DateTimeField() -        def restore_object(self, attrs, instance=None): -            """ -            Given a dictionary of deserialized field values, either update -            an existing model instance, or create a new model instance. -            """ -            if instance is not None: -                instance.email = attrs.get('email', instance.email) -                instance.content = attrs.get('content', instance.content) -                instance.created = attrs.get('created', instance.created) -                return instance -            return Comment(**attrs)  - -The first part of serializer class defines the fields that get serialized/deserialized.  The `restore_object` method defines how fully fledged instances get created when deserializing data. - -The `restore_object` method is optional, and is only required if we want our serializer to support deserialization into fully fledged object instances.  If we don't define this method, then deserializing data will simply return a dictionary of items. -  ## Serializing objects -We can now use `CommentSerializer` to serialize a comment, or list of comments.  Again, using the `Serializer` class looks a lot like using a `Form` class. +We can now use `CommentSerializer` to serialize a comment, or list of comments. Again, using the `Serializer` class looks a lot like using a `Form` class.      serializer = CommentSerializer(comment)      serializer.data @@ -67,24 +57,9 @@ At this point we've translated the model instance into Python native datatypes.      json      # '{"email": "leila@example.com", "content": "foo bar", "created": "2012-08-22T16:20:09.822"}' -### Customizing field representation - -Sometimes when serializing objects, you may not want to represent everything exactly the way it is in your model. - -If you need to customize the serialized value of a particular field, you can do this by creating a `transform_<fieldname>` method. For example if you needed to render some markdown from a text field: - -    description = serializers.CharField() -    description_html = serializers.CharField(source='description', read_only=True) - -    def transform_description_html(self, obj, value): -        from django.contrib.markup.templatetags.markup import markdown -        return markdown(value) - -These methods are essentially the reverse of `validate_<fieldname>` (see *Validation* below.) -  ## Deserializing objects -         -Deserialization is similar.  First we parse a stream into Python native datatypes...  + +Deserialization is similar. First we parse a stream into Python native datatypes...      from StringIO import StringIO      from rest_framework.parsers import JSONParser @@ -92,26 +67,90 @@ Deserialization is similar.  First we parse a stream into Python native datatype      stream = StringIO(json)      data = JSONParser().parse(stream) -...then we restore those native datatypes into a fully populated object instance. +...then we restore those native datatypes into a dictionary of validated data.      serializer = CommentSerializer(data=data)      serializer.is_valid()      # True -    serializer.object -    # <Comment object at 0x10633b2d0> +    serializer.validated_data +    # {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)} + +## Saving instances + +If we want to be able to return complete object instances based on the validated data we need to implement one or both of the `.create()` and `update()` methods. For example: + +    class CommentSerializer(serializers.Serializer): +        email = serializers.EmailField() +        content = serializers.CharField(max_length=200) +        created = serializers.DateTimeField() + +        def create(self, validated_data): +            return Comment(**validated_data) + +        def update(self, instance, validated_data): +            instance.email = validated_data.get('email', instance.email) +            instance.content = validated_data.get('content', instance.content) +            instance.created = validated_data.get('created', instance.created) +            return instance + +If your object instances correspond to Django models you'll also want to ensure that these methods save the object to the database. For example, if `Comment` was a Django model, the methods might look like this: + +        def create(self, validated_data): +            return Comment.objcts.create(**validated_data) + +        def update(self, instance, validated_data): +            instance.email = validated_data.get('email', instance.email) +            instance.content = validated_data.get('content', instance.content) +            instance.created = validated_data.get('created', instance.created) +            instance.save() +            return instance +  +Now when deserializing data, we can call `.save()` to return an object instance, based on the validated data. -When deserializing data, we can either create a new instance, or update an existing instance. +    comment = serializer.save() -    serializer = CommentSerializer(data=data)           # Create new instance -    serializer = CommentSerializer(comment, data=data)  # Update `comment` +Calling `.save()` will either create a new instance, or update an existing instance, depending on if an existing instance was passed when instantiating the serializer class: -By default, serializers must be passed values for all required fields or they will throw validation errors.  You can use the `partial` argument in order to allow partial updates. +    # .save() will create a new instance. +    serializer = CommentSerializer(data=data) +     +    # .save() will update the existing `comment` instance. +    serializer = CommentSerializer(comment, data=data) + +Both the `.create()` and `.update()` methods are optional. You can implement either neither, one, or both of them, depending on the use-case for your serializer class. + +#### Passing additional attributes to `.save()` + +Sometimes you'll want your view code to be able to inject additional data at the point of saving the instance. This additional data might include information like the current user, the current time, or anything else that is not part of the request data. + +You can do so by including additional keyword arguments when calling `.save()`. For example: + +    serializer.save(owner=request.user) + +Any additional keyword arguments will be included in the `validated_data` argument when `.create()` or `.update()` are called. + +#### Overriding `.save()` directly. -    serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)  # Update `comment` with partial data +In some cases the `.create()` and `.update()` method names may not be meaningful. For example, in a contact form we may not be creating new instances, but instead sending an email or other message. + +In these cases you might instead choose to override `.save()` directly, as being more readable and meaningful. + +For example: + +    class ContactForm(serializers.Serializer): +        email = serializers.EmailField() +        message = serializers.CharField() +         +        def save(self): +            email = self.validated_data['email'] +            message = self.validated_data['message'] +            send_email(from=email, message=message) + +Note that in the case above we're now having to access the serializer `.validated_data` property directly.  ## Validation -When deserializing data, you always need to call `is_valid()` before attempting to access the deserialized object.  If any validation errors occur, the `.errors` property will contain a dictionary representing the resulting error messages.  For example: +When deserializing data, you always need to call `is_valid()` before attempting to access the validated data, or save an object instance. If any validation errors occur, the `.errors` property will contain a dictionary representing the resulting error messages.  For example:      serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})      serializer.is_valid() @@ -119,17 +158,26 @@ When deserializing data, you always need to call `is_valid()` before attempting      serializer.errors      # {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']} -Each key in the dictionary will be the field name, and the values will be lists of strings of any error messages corresponding to that field.  The `non_field_errors` key may also be present, and will list any general validation errors. +Each key in the dictionary will be the field name, and the values will be lists of strings of any error messages corresponding to that field.  The `non_field_errors` key may also be present, and will list any general validation errors. The name of the `non_field_errors` key may be customized using the `NON_FIELD_ERRORS_KEY` REST framework setting.  When deserializing a list of items, errors will be returned as a list of dictionaries representing each of the deserialized items. +#### Raising an exception on invalid data + +The `.is_valid()` method takes an optional `raise_exception` flag that will cause it to raise a `serializers.ValidationError` exception if there are validation errors. + +These exceptions are automatically dealt with by the default exception handler that REST framework provides, and will return `HTTP 400 Bad Request` responses by default. + +    # Return a 400 response if the data was invalid. +    serializer.is_valid(raise_exception=True) +  #### Field-level validation -You can specify custom field-level validation by adding `.validate_<fieldname>` methods to your `Serializer` subclass.  These are analogous to `.clean_<fieldname>` methods on Django forms, but accept slightly different arguments. +You can specify custom field-level validation by adding `.validate_<field_name>` methods to your `Serializer` subclass.  These are similar to the `.clean_<field_name>` methods on Django forms. -They take a dictionary of deserialized attributes as a first argument, and the field name in that dictionary as a second argument (which will be either the name of the field or the value of the `source` argument to the field, if one was provided). +These methods take a single argument, which is the field value that requires validation. -Your `validate_<fieldname>` methods should either just return the `attrs` dictionary or raise a `ValidationError`.  For example: +Your `validate_<field_name>` methods should return the validated value or raise a `serializers.ValidationError`.  For example:      from rest_framework import serializers @@ -137,18 +185,17 @@ Your `validate_<fieldname>` methods should either just return the `attrs` dictio          title = serializers.CharField(max_length=100)          content = serializers.CharField() -        def validate_title(self, attrs, source): +        def validate_title(self, value):              """              Check that the blog post is about Django.              """ -            value = attrs[source] -            if "django" not in value.lower(): +            if 'django' not in value.lower():                  raise serializers.ValidationError("Blog post is not about Django") -            return attrs +            return value  #### Object-level validation -To do any other validation that requires access to multiple fields, add a method called `.validate()` to your `Serializer` subclass.  This method takes a single argument, which is the `attrs` dictionary.  It should raise a `ValidationError` if necessary, or just return `attrs`.  For example: +To do any other validation that requires access to multiple fields, add a method called `.validate()` to your `Serializer` subclass.  This method takes a single argument, which is a dictionary of field values.  It should raise a `ValidationError` if necessary, or just return the validated values.  For example:      from rest_framework import serializers @@ -157,24 +204,48 @@ To do any other validation that requires access to multiple fields, add a method          start = serializers.DateTimeField()          finish = serializers.DateTimeField() -        def validate(self, attrs): +        def validate(self, data):              """              Check that the start is before the stop.              """ -            if attrs['start'] > attrs['finish']: +            if data['start'] > data['finish']:                  raise serializers.ValidationError("finish must occur after start") -            return attrs +            return data + +#### Validators + +Individual fields on a serializer can include validators, by declaring them on the field instance, for example: + +    def multiple_of_ten(value): +        if value % 10 != 0: +            raise serializers.ValidationError('Not a multiple of ten') + +    class GameRecord(serializers.Serializer): +        score = IntegerField(validators=[multiple_of_ten]) +        ... + +Serializer classes can also include reusable validators that are applied to the complete set of field data. These validators are included by declaring them on an inner `Meta` class, like so: -## Saving object state +    class EventSerializer(serializers.Serializer): +        name = serializers.CharField() +        room_number = serializers.IntegerField(choices=[101, 102, 103, 201]) +        date = serializers.DateField() +         +        class Meta: +            # Each room only has one event per day. +            validators = UniqueTogetherValidator( +                queryset=Event.objects.all(), +                fields=['room_number', 'date'] +            ) -To save the deserialized objects created by a serializer, call the `.save()` method: +For more information see the [validators documentation](validators.md). -    if serializer.is_valid(): -        serializer.save() +## Partial updates -The default behavior of the method is to simply call `.save()` on the deserialized object instance.  You can override the default save behaviour by overriding the `.save_object(obj)` method on the serializer class. +By default, serializers must be passed values for all required fields or they will raise validation errors. You can use the `partial` argument in order to allow partial updates. -The generic views provided by REST framework call the `.save()` method when updating or creating entities.   +    # Update `comment` with partial data +    serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)  ## Dealing with nested objects @@ -206,7 +277,9 @@ Similarly if a nested representation should be a list of items, you should pass          content = serializers.CharField(max_length=200)          created = serializers.DateTimeField() -Validation of nested objects will work the same as before.  Errors with nested objects will be nested under the field name of the nested object. +## Writable nested representations + +When dealing with nested representations that support deserializing the data, an errors with nested objects will be nested under the field name of the nested object.      serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})      serializer.is_valid() @@ -214,95 +287,117 @@ Validation of nested objects will work the same as before.  Errors with nested o      serializer.errors      # {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']} -## Dealing with multiple objects +Similarly, the `.validated_data` property will include nested data structures. -The `Serializer` class can also handle serializing or deserializing lists of objects. +#### Writing `.create()` methods for nested representations -#### Serializing multiple objects +If you're supporting writable nested representations you'll need to write `.create()` or `.update()` methods that handle saving multiple objects. -To serialize a queryset or list of objects instead of a single object instance, you should pass the `many=True` flag when instantiating the serializer.  You can then pass a queryset or list of objects to be serialized. +The following example demonstrates how you might handle creating a user with a nested profile object. -    queryset = Book.objects.all() -    serializer = BookSerializer(queryset, many=True) -    serializer.data -    # [ -    #     {'id': 0, 'title': 'The electric kool-aid acid test', 'author': 'Tom Wolfe'}, -    #     {'id': 1, 'title': 'If this is a man', 'author': 'Primo Levi'}, -    #     {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'} -    # ] +    class UserSerializer(serializers.ModelSerializer): +        profile = ProfileSerializer() -#### Deserializing multiple objects for creation +        class Meta: +            model = User +            fields = ('username', 'email', 'profile') -To deserialize a list of object data, and create multiple object instances in a single pass, you should also set the `many=True` flag, and pass a list of data to be deserialized. +        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 -This allows you to write views that create multiple items when a `POST` request is made. +#### Writing `.update()` methods for nested representations -For example: +For updates you'll want to think carefully about how to handle updates to relationships. For example if the data for the relationship is `None`, or not provided, which of the following should occur? -    data = [ -        {'title': 'The bell jar', 'author': 'Sylvia Plath'}, -        {'title': 'For whom the bell tolls', 'author': 'Ernest Hemingway'} -    ] -    serializer = BookSerializer(data=data, many=True) -    serializer.is_valid() -    # True -    serializer.save()  # `.save()` will be called on each deserialized instance +* Set the relationship to `NULL` in the database. +* Delete the associated instance. +* Ignore the data and leave the instance as it is. +* Raise a validation error. -#### Deserializing multiple objects for update +Here's an example for an `update()` method on our previous `UserSerializer` class. -You can also deserialize a list of objects as part of a bulk update of multiple existing items. -In this case you need to supply both an existing list or queryset of items, as well as a list of data to update those items with. +        def update(self, instance, validated_data): +            profile_data = validated_data.pop('profile') +            # Unless the application properly enforces that this field is +            # always set, the follow could raise a `DoesNotExist`, which +            # would need to be handled. +            profile = instance.profile -This allows you to write views that update or create multiple items when a `PUT` request is made. +            user.username = validated_data.get('username', instance.username) +            user.email = validated_data.get('email', instance.email) +            user.save() -    # Capitalizing the titles of the books -    queryset = Book.objects.all() -    data = [ -        {'id': 3, 'title': 'The Bell Jar', 'author': 'Sylvia Plath'}, -        {'id': 4, 'title': 'For Whom the Bell Tolls', 'author': 'Ernest Hemingway'} -    ] -    serializer = BookSerializer(queryset, data=data, many=True) -    serializer.is_valid() -    # True -    serializer.save()  # `.save()` will be called on each updated or newly created instance. +            profile.is_premium_member = profile_data.get( +                'is_premium_member', +                profile.is_premium_member +            ) +            profile.has_support_contract = profile_data.get( +                'has_support_contract', +                profile.has_support_contract +             ) +            profile.save() -By default bulk updates will be limited to updating instances that already exist in the provided queryset. +            return user -When performing a bulk update you may want to allow new items to be created, and missing items to be deleted.  To do so, pass `allow_add_remove=True` to the serializer. +Because the behavior of nested creates and updates can be ambiguous, and may require complex dependancies between related models, REST framework 3 requires you to always write these methods explicitly. The default `ModelSerializer` `.create()` and `.update()` methods do not include support for writable nested representations. -    serializer = BookSerializer(queryset, data=data, many=True, allow_add_remove=True) -    serializer.is_valid() -    # True -    serializer.save()  # `.save()` will be called on updated or newly created instances. -                       # `.delete()` will be called on any other items in the `queryset`. +It is possible that a third party package, providing automatic support some kinds of automatic writable nested representations may be released alongside the 3.1 release. -Passing `allow_add_remove=True` ensures that any update operations will completely overwrite the existing queryset, rather than simply updating existing objects. +#### Handling saving related instances in model manager classes -#### How identity is determined when performing bulk updates +An alternative to saving multiple related instances in the serializer is to write custom model manager classes handle creating the correct instances. -Performing a bulk update is slightly more complicated than performing a bulk creation, because the serializer needs a way to determine how the items in the incoming data should be matched against the existing object instances. +For example, suppose we wanted to ensure that `User` instances and `Profile` instances are always created together as a pair. We might write a custom manager class that looks something like this: -By default the serializer class will use the `id` key on the incoming data to determine the canonical identity of an object.  If you need to change this behavior you should override the `get_identity` method on the `Serializer` class.  For example: +    class UserManager(models.Manager): +        ... -    class AccountSerializer(serializers.Serializer): -        slug = serializers.CharField(max_length=100) -        created = serializers.DateTimeField() -        ...  # Various other fields -         -        def get_identity(self, data): -            """ -            This hook is required for bulk update. -            We need to override the default, to use the slug as the identity. -             -            Note that the data has not yet been validated at this point, -            so we need to deal gracefully with incorrect datatypes. -            """ -            try: -                return data.get('slug', None) -            except AttributeError: -                return None +        def create(self, username, email, is_premium_member=False, has_support_contract=False): +            user = User(username=username, email=email) +            user.save() +            profile = Profile( +                user=user, +                is_premium_member=is_premium_member, +                has_support_contract=has_support_contract +            ) +            profile.save() +            return user + +This manager class now more nicely encapsulates that user instances and profile instances are always created at the same time. Our `.create()` method on the serializer class can now be re-written to use the new manager method. + +    def create(self, validated_data): +        return User.objects.create( +            username=validated_data['username'], +            email=validated_data['email'] +            is_premium_member=validated_data['profile']['is_premium_member'] +            has_support_contract=validated_data['profile']['has_support_contract'] +        ) + +For more details on this approach see the Django documentation on [model managers](model-managers), and [this blogpost on using model and manger classes](encapsulation-blogpost). + +## Dealing with multiple objects + +The `Serializer` class can also handle serializing or deserializing lists of objects. -To map the incoming data items to their corresponding object instances, the `.get_identity()` method will be called both against the incoming data, and against the serialized representation of the existing objects. +#### Serializing multiple objects + +To serialize a queryset or list of objects instead of a single object instance, you should pass the `many=True` flag when instantiating the serializer.  You can then pass a queryset or list of objects to be serialized. + +    queryset = Book.objects.all() +    serializer = BookSerializer(queryset, many=True) +    serializer.data +    # [ +    #     {'id': 0, 'title': 'The electric kool-aid acid test', 'author': 'Tom Wolfe'}, +    #     {'id': 1, 'title': 'If this is a man', 'author': 'Primo Levi'}, +    #     {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'} +    # ] + +#### Deserializing multiple objects + +The default behavior for deserializing multiple objects is to support multiple object creation, but not support multiple object updates. For more information on how to support or customize either of these cases, see the [ListSerializer](#ListSerializer) documentation below.  ## Including extra context @@ -314,28 +409,46 @@ You can provide arbitrary additional context by passing a `context` argument whe      serializer.data      # {'id': 6, 'owner': u'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'} -The context dictionary can be used within any serializer field logic, such as a custom `.to_native()` method, by accessing the `self.context` attribute. +The context dictionary can be used within any serializer field logic, such as a custom `.to_representation()` method, by accessing the `self.context` attribute. + +--- --  # ModelSerializer -Often you'll want serializer classes that map closely to model definitions. -The `ModelSerializer` class lets you automatically create a Serializer class with fields that correspond to the Model fields. +Often you'll want serializer classes that map closely to Django model definitions. + +The `ModelSerializer` class provides a shortcut that lets you automatically create a `Serializer` class with fields that correspond to the Model fields. + +**The `ModelSerializer` class is the same as a regular `Serializer` class, except that**: + +* It will automatically generate a set of fields for you, based on the model. +* It will automatically generate validators for the serializer, such as unique_together validators. +* It includes simple default implementations of `.create()` and `.update()`. + +Declaring a `ModelSerializer` looks like this:      class AccountSerializer(serializers.ModelSerializer):          class Meta:              model = Account -By default, all the model fields on the class will be mapped to corresponding serializer fields. +By default, all the model fields on the class will be mapped to a corresponding serializer fields. -Any relationships such as foreign keys on the model will be mapped to `PrimaryKeyRelatedField`.  Other models fields will be mapped to a corresponding serializer field. +Any relationships such as foreign keys on the model will be mapped to `PrimaryKeyRelatedField`. Reverse relationships are not included by default unless explicitly included as described below. ---- +#### Inspecting a `ModelSerializer` -**Note**: When validation is applied to a `ModelSerializer`, both the serializer fields, and their corresponding model fields must correctly validate.  If you have optional fields on your model, make sure to correctly set `blank=True` on the model field, as well as setting `required=False` on the serializer field. +Serializer classes generate helpful verbose representation strings, that allow you to fully inspect the state of their fields. This is particularly useful when working with `ModelSerializers` where you want to determine what set of fields and validators are being automatically created for you. ---- +To do so, open the Django shell, using `python manage.py shell`, then import the serializer class, instantiate it, and print the object representation… +    >>> from myapp.serializers import AccountSerializer +    >>> serializer = AccountSerializer() +    >>> print repr(serializer)  # Or `print(repr(serializer))` in Python 3.x. +    AccountSerializer(): +        id = IntegerField(label='ID', read_only=True) +        name = CharField(allow_blank=True, max_length=100, required=False) +        owner = PrimaryKeyRelatedField(queryset=User.objects.all()) +   ## Specifying which fields should be included  If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`. @@ -347,6 +460,10 @@ For example:              model = Account              fields = ('id', 'account_name', 'users', 'created') +The names in the `fields` option will normally map to model fields on the model class. + +Alternatively names in the `fields` options can map to properties or methods which take no arguments that exist on the model class. +  ## Specifying nested serialization  The default `ModelSerializer` uses primary keys for relationships, but you can also easily generate nested representations using the `depth` option: @@ -361,9 +478,24 @@ The `depth` option should be set to an integer value that indicates the depth of  If you want to customize the way the serialization is done (e.g. using `allow_add_remove`) you'll need to define the field yourself. -## Specifying which fields should be read-only  +## Specifying fields explicitly -You may wish to specify multiple fields as read-only.  Instead of adding each field explicitly with the `read_only=True` attribute, you may use the `read_only_fields` Meta option, like so: +You can add extra fields to a `ModelSerializer` or override the default fields by declaring fields on the class, just as you would for a `Serializer` class. + +    class AccountSerializer(serializers.ModelSerializer): +        url = serializers.CharField(source='get_absolute_url', read_only=True) +        groups = serializers.PrimaryKeyRelatedField(many=True) + +        class Meta: +            model = Account + +Extra fields can correspond to any property or callable on the model. + +## Specifying which fields should be read-only + +You may wish to specify multiple fields as read-only. Instead of adding each field explicitly with the `read_only=True` attribute, you may use the shortcut Meta option, `read_only_fields`. + +This option should be a list or tuple of field names, and is declared as follows:      class AccountSerializer(serializers.ModelSerializer):          class Meta: @@ -371,39 +503,28 @@ You may wish to specify multiple fields as read-only.  Instead of adding each fi              fields = ('id', 'account_name', 'users', 'created')              read_only_fields = ('account_name',) -Model fields which have `editable=False` set, and `AutoField` fields will be set to read-only by default, and do not need to be added to the `read_only_fields` option.  +Model fields which have `editable=False` set, and `AutoField` fields will be set to read-only by default, and do not need to be added to the `read_only_fields` option. + +## Specifying additional keyword arguments for fields. -## Specifying which fields should be write-only  +There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. Similarly to `read_only_fields` this means you do not need to explicitly declare the field on the serializer. -You may wish to specify multiple fields as write-only.  Instead of adding each field explicitly with the `write_only=True` attribute, you may use the `write_only_fields` Meta option, like so: +This option is a dictionary, mapping field names to a dictionary of keyword arguments. For example:      class CreateUserSerializer(serializers.ModelSerializer):          class Meta:              model = User              fields = ('email', 'username', 'password') -            write_only_fields = ('password',)  # Note: Password field is write-only - -        def restore_object(self, attrs, instance=None): -            """ -            Instantiate a new User instance. -            """ -            assert instance is None, 'Cannot update users with CreateUserSerializer'                                 -            user = User(email=attrs['email'], username=attrs['username']) -            user.set_password(attrs['password']) +            extra_kwargs = {'password': {'write_only': True}} +             +        def create(self, validated_data): +            user = User( +                email=validated_data['email'], +                username=validated_data['username'] +            ) +            user.set_password(validated_data['password']) +            user.save()              return user -  -## Specifying fields explicitly  - -You can add extra fields to a `ModelSerializer` or override the default fields by declaring fields on the class, just as you would for a `Serializer` class. - -    class AccountSerializer(serializers.ModelSerializer): -        url = serializers.CharField(source='get_absolute_url', read_only=True) -        groups = serializers.PrimaryKeyRelatedField(many=True) - -        class Meta: -            model = Account - -Extra fields can correspond to any property or callable on the model.  ## Relational fields @@ -413,6 +534,16 @@ Alternative representations include serializing using hyperlinks, serializing co  For full details see the [serializer relations][relations] documentation. +## Inheritance of the 'Meta' class + +The inner `Meta` class on serializers is not inherited from parent classes by default. This is the same behavior as with Django's `Model` and `ModelForm` classes. If you want the `Meta` class to inherit from a parent class you must do so explicitly. For example: + +    class AccountSerializer(MyBaseSerializer): +        class Meta(MyBaseSerializer.Meta): +            model = Account + +Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly. +  ---  # HyperlinkedModelSerializer @@ -436,22 +567,23 @@ There needs to be a way of determining which views should be used for hyperlinki  By default hyperlinks are expected to correspond to a view name that matches the style `'{model_name}-detail'`, and looks up the instance by a `pk` keyword argument. -You can change the field that is used for object lookups by setting the `lookup_field` option.  The value of this option should correspond both with a kwarg in the URL conf, and with a field on the model.  For example: +You can override a URL field view name and lookup field by using either, or both of, the `view_name` and `lookup_field` options in the `extra_field_kwargs` setting, like so:      class AccountSerializer(serializers.HyperlinkedModelSerializer):          class Meta:              model = Account -            fields = ('url', 'account_name', 'users', 'created') -            lookup_field = 'slug' - -Note that the `lookup_field` will be used as the default on *all* hyperlinked fields, including both the URL identity, and any hyperlinked relationships. +            fields = ('account_url', 'account_name', 'users', 'created') +            extra_field_kwargs = { +                'url': {'view_name': 'accounts', 'lookup_field': 'account_name'} +                'users': {'lookup_field': 'username'} +            } -For more specific requirements such as specifying a different lookup for each field, you'll want to set the fields on the serializer explicitly.  For example: +Alternatively you can set the fields on the serializer explicitly. For example:      class AccountSerializer(serializers.HyperlinkedModelSerializer):          url = serializers.HyperlinkedIdentityField( -            view_name='account_detail', -            lookup_field='account_name' +            view_name='accounts', +            lookup_field='slug'          )          users = serializers.HyperlinkedRelatedField(              view_name='user-detail', @@ -464,36 +596,267 @@ For more specific requirements such as specifying a different lookup for each fi              model = Account              fields = ('url', 'account_name', 'users', 'created') -## Overriding the URL field behavior +--- + +**Tip**: Properly matching together hyperlinked representations and your URL conf can sometimes be a bit fiddly. Printing the `repr` of a `HyperlinkedModelSerializer` instance is a particularly useful way to inspect exactly which view names and lookup fields the relationships are expected to map too. + +--- + +## Changing the URL field name  The name of the URL field defaults to 'url'.  You can override this globally, by using the `URL_FIELD_NAME` setting. -You can also override this on a per-serializer basis by using the `url_field_name` option on the serializer, like so: +--- -    class AccountSerializer(serializers.HyperlinkedModelSerializer): +# ListSerializer + +The `ListSerializer` class provides the behavior for serializing and validating multiple objects at once. You won't *typically* need to use `ListSerializer` directly, but should instead simply pass `many=True` when instantiating a serializer. + +When a serializer is instantiated and `many=True` is passed, a `ListSerializer` instance will be created. The serializer class then becomes a child of the parent `ListSerializer` + +There *are* a few use cases when you might want to customize the `ListSerializer` behavior. For example: + +* You want to provide particular validation of the lists, such as always ensuring that there is at least one element in a list. +* You want to customize the create or update behavior of multiple objects. + +For these cases you can modify the class that is used when `many=True` is passed, by using the `list_serializer_class` option on the serializer `Meta` class. + +For example: + +    class CustomListSerializer(serializers.ListSerializer): +        ... + +    class CustomSerializer(serializers.Serializer): +        ...          class Meta: -            model = Account -            fields = ('account_url', 'account_name', 'users', 'created') -            url_field_name = 'account_url' +            list_serializer_class = CustomListSerializer -**Note**: The generic view implementations normally generate a `Location` header in response to successful `POST` requests.  Serializers using `url_field_name` option will not have this header automatically included by the view.  If you need to do so you will ned to also override the view's `get_success_headers()` method. +#### Customizing multiple create -You can also override the URL field's view name and lookup field without overriding the field explicitly, by using the `view_name` and `lookup_field` options, like so: +The default implementation for multiple object creation is to simply call `.create()` for each item in the list. If you want to customize this behavior, you'll need to customize the `.create()` method on `ListSerializer` class that is used when `many=True` is passed. -    class AccountSerializer(serializers.HyperlinkedModelSerializer): +For example: + +    class BookListSerializer(serializers.ListSerializer): +        def create(self, validated_data): +            books = [Book(**item) for item in validated_data] +            return Book.objects.bulk_create(books) + +    class BookSerializer(serializers.Serializer): +        ...          class Meta: -            model = Account -            fields = ('account_url', 'account_name', 'users', 'created') -            view_name = 'account_detail' -            lookup_field='account_name' +            list_serializer_class = BookListSerializer + +#### Customizing multiple update + +By default the `ListSerializer` class does not support multiple updates. This is because the behavior that should be expected for insertions and deletions is ambiguous. + +To support multiple updates you'll need to do so explicitly. When writing your multiple update code make sure to keep the following in mind: + +* How do you determine which instance should be updated for each item in the list of data? +* How should insertions be handled? Are they invalid, or do they create new objects? +* How should removals be handled? Do they imply object deletion, or removing a relationship? Should they be silently ignored, or are they invalid? +* How should ordering be handled? Does changing the position of two items imply any state change or is it ignored?  + +Here's an example of how you might choose to implement multiple updates: + +    class BookListSerializer(serializers.ListSerializer): +        def update(self, instance, validated_data): +            # Maps for id->instance and id->data item. +            book_mapping = {book.id: book for book in instance} +            data_mapping = {item['id']: item for item in validated_data} + +            # Perform creations and updates. +            ret = [] +            for book_id, data in data_mapping.items(): +                book = book_mapping.get(book_id, None): +                if book is None: +                    ret.append(self.child.create(data)) +                else: +                    ret.append(self.child.update(book, data)) + +            # Perform deletions. +            for book_id, book in book_mapping.items(): +                if book_id not in data_mapping: +                    book.delete() + +            return ret + +    class BookSerializer(serializers.Serializer): +        ... +        class Meta: +            list_serializer_class = BookListSerializer + +It is possible that a third party package may be included alongside the 3.1 release that provides some automatic support for multiple update operations, similar to the `allow_add_remove` behavior that was present in REST framework 2. + +--- + +# BaseSerializer + +`BaseSerializer` class that can be used to easily support alternative serialization and deserialization styles. + +This class implements the same basic API as the `Serializer` class: + +* `.data` - Returns the outgoing primitive representation. +* `.is_valid()` - Deserializes and validates incoming data. +* `.validated_data` - Returns the validated incoming data. +* `.errors` - Returns an errors during validation. +* `.save()` - Persists the validated data into an object instance. + +There are four methods that can be overridden, depending on what functionality you want the serializer class to support: + +* `.to_representation()` - Override this to support serialization, for read operations. +* `.to_internal_value()` - Override this to support deserialization, for write operations. +* `.create()` and `.update()` - Overide either or both of these to support saving instances. + +Because this class provides the same interface as the `Serializer` class, you can use it with the existing generic class based views exactly as you would for a regular `Serializer` or `ModelSerializer`. + +The only difference you'll notice when doing so is the `BaseSerializer` classes will not generate HTML forms in the browsable API. This is because the data they return does not include all the field information that would allow each field to be rendered into a suitable HTML input. + +##### Read-only `BaseSerializer` classes + +To implement a read-only serializer using the `BaseSerializer` class, we just need to override the `.to_representation()` method. Let's take a look at an example using a simple Django model: + +    class HighScore(models.Model): +        created = models.DateTimeField(auto_now_add=True) +        player_name = models.CharField(max_length=10) +        score = models.IntegerField() + +It's simple to create a read-only serializer for converting `HighScore` instances into primitive data types. + +    class HighScoreSerializer(serializers.BaseSerializer): +        def to_representation(self, obj): +            return { +                'score': obj.score, +                'player_name': obj.player_name +            } + +We can now use this class to serialize single `HighScore` instances: + +    @api_view(['GET']) +    def high_score(request, pk): +        instance = HighScore.objects.get(pk=pk) +        serializer = HighScoreSerializer(instance) +	    return Response(serializer.data) + +Or use it to serialize multiple instances: + +    @api_view(['GET']) +    def all_high_scores(request): +        queryset = HighScore.objects.order_by('-score') +        serializer = HighScoreSerializer(queryset, many=True) +	    return Response(serializer.data) + +##### Read-write `BaseSerializer` classes + +To create a read-write serializer we first need to implement a `.to_internal_value()` method. This method returns the validated values that will be used to construct the object instance, and may raise a `ValidationError` if the supplied data is in an incorrect format. + +Once you've implemented `.to_internal_value()`, the basic validation API will be available on the serializer, and you will be able to use `.is_valid()`, `.validated_data` and `.errors`. + +If you want to also support `.save()` you'll need to also implement either or both of the `.create()` and `.update()` methods. + +Here's a complete example of our previous `HighScoreSerializer`, that's been updated to support both read and write operations. + +    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) + +#### Creating new base classes + +The `BaseSerializer` 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. + +The following class is an example of a generic serializer that can handle coercing arbitrary objects into primitive representations. + +    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): +                    # Recursively deal with items in lists. +                    output[attribute_name] = [ +                        self.to_representation(item) for item in attribute +                    ] +                elif isinstance(attribute, dict): +                    # Recursively deal with items in dictionaries. +                    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)  ---  # Advanced serializer usage -You can create customized subclasses of `ModelSerializer` or `HyperlinkedModelSerializer` that use a different set of default fields. +## Overriding serialization and deserialization behavior + +If you need to alter the serialization, deserialization or validation of a serializer class you can do so by overriding the `.to_representation()` or `.to_internal_value()` methods. + +Some reasons this might be useful include... + +* Adding new behavior for new serializer base classes. +* Modifying the behavior slightly for an existing class. +* Improving serialization performance for a frequently accessed API endpoint that returns lots of data. + +The signatures for these methods are as follows: + +#### `.to_representation(self, obj)` -Doing so should be considered advanced usage, and will only be needed if you have some particular serializer requirements that you often need to repeat. +Takes the object instance that requires serialization, and should return a primitive representation. Typically this means returning a structure of built-in Python datatypes. The exact types that can be handled will depend on the render classes you have configured for your API. + +#### ``.to_internal_value(self, data)`` + +Takes the unvalidated incoming data as input and should return the validated data that will be made available as `serializer.validated_data`. The return value will also be passed to the `.create()` or `.update()` methods if `.save()` is called on the serializer class. + +If any of the validation fails, then the method should raise a `serializers.ValidationError(errors)`. Typically the `errors` argument here will be a dictionary mapping field names to error messages. + +The `data` argument passed to this method will normally be the value of `request.data`, so the datatype it provides will depend on the parser classes you have configured for your API.  ## Dynamically modifying fields @@ -514,11 +877,11 @@ For example, if you wanted to be able to set which fields should be used by a se          def __init__(self, *args, **kwargs):              # Don't pass the 'fields' arg up to the superclass              fields = kwargs.pop('fields', None) -             +              # Instantiate the superclass normally              super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs) -     -            if fields: + +            if fields is not None:                  # Drop any fields that are not specified in the `fields` argument.                  allowed = set(fields)                  existing = set(self.fields.keys()) @@ -538,47 +901,15 @@ This would then allow you to do the following:      >>> print UserSerializer(user, fields=('id', 'email'))      {'id': 2, 'email': 'jon@example.com'} -## Customising the default fields +## Customizing the default fields -The `field_mapping` attribute is a dictionary that maps model classes to serializer classes.  Overriding the attribute will let you set a different set of default serializer classes.  +REST framework 2 provided an API to allow developers to override how a `ModelSerializer` class would automatically generate the default set of fields. -For more advanced customization than simply changing the default serializer class you can override various `get_<field_type>_field` methods.  Doing so will allow you to customize the arguments that each serializer field is initialized with.  Each of these methods may either return a field or serializer instance, or `None`. - -### get_pk_field - -**Signature**: `.get_pk_field(self, model_field)` - -Returns the field instance that should be used to represent the pk field. - -### get_nested_field - -**Signature**: `.get_nested_field(self, model_field, related_model, to_many)` - -Returns the field instance that should be used to represent a related field when `depth` is specified as being non-zero. - -Note that the `model_field` argument will be `None` for reverse relationships.  The `related_model` argument will be the model class for the target of the field.  The `to_many` argument will be a boolean indicating if this is a to-one or to-many relationship. - -### get_related_field - -**Signature**: `.get_related_field(self, model_field, related_model, to_many)` - -Returns the field instance that should be used to represent a related field when `depth` is not specified, or when nested representations are being used and the depth reaches zero. - -Note that the `model_field` argument will be `None` for reverse relationships.  The `related_model` argument will be the model class for the target of the field.  The `to_many` argument will be a boolean indicating if this is a to-one or to-many relationship. - -### get_field - -**Signature**: `.get_field(self, model_field)` - -Returns the field instance that should be used for non-relational, non-pk fields. - -### Example +This API included the `.get_field()`, `.get_pk_field()` and other methods. -The following custom model serializer could be used as a base class for model serializers that should always exclude the pk by default. +Because the serializers have been fundamentally redesigned with 3.0 this API no longer exists. You can still modify the fields that get created but you'll need to refer to the source code, and be aware that if the changes you make are against private bits of API then they may be subject to change. -    class NoPKModelSerializer(serializers.ModelSerializer): -        def get_pk_field(self, model_field): -            return None +A new interface for controlling this behavior is currently planned for REST framework 3.1.  --- @@ -600,6 +931,8 @@ The [django-rest-framework-hstore][django-rest-framework-hstore] package provide  [cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion  [relations]: relations.md +[model-managers]: https://docs.djangoproject.com/en/dev/topics/db/managers/ +[encapsulation-blogpost]: http://www.dabapps.com/blog/django-models-and-encapsulation/  [mongoengine]: https://github.com/umutbozkurt/django-rest-framework-mongoengine  [django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis  [django-rest-framework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 6a855c92..9005511b 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -1,4 +1,4 @@ -<a class="github" href="settings.py"></a> +source: settings.py  # Settings @@ -51,7 +51,7 @@ Default:  #### DEFAULT_PARSER_CLASSES -A list or tuple of parser classes, that determines the default set of parsers used when accessing the `request.DATA` property. +A list or tuple of parser classes, that determines the default set of parsers used when accessing the `request.data` property.  Default: @@ -74,7 +74,7 @@ Default:  #### DEFAULT_PERMISSION_CLASSES -A list or tuple of permission classes, that determines the default set of permissions checked at the start of a view. +A list or tuple of permission classes, that determines the default set of permissions checked at the start of a view. Permission must be granted by every class in the list.  Default: @@ -154,13 +154,13 @@ Default: `None`  ### SEARCH_PARAM -The name of a query paramater, which can be used to specify the search term used by `SearchFilter`. +The name of a query parameter, which can be used to specify the search term used by `SearchFilter`.  Default: `search`  #### ORDERING_PARAM -The name of a query paramater, which can be used to specify the ordering of results returned by `OrderingFilter`. +The name of a query parameter, which can be used to specify the ordering of results returned by `OrderingFilter`.  Default: `ordering` @@ -321,7 +321,7 @@ When set to `False`, JSON responses will escape non-ascii characters, like so:      {"unicode black star":"\u2605"} -Both styles conform to [RFC 4627][rfc4627], and are syntactically valid JSON. The unicode style is prefered as being more user-friendly when inspecting API responses. +Both styles conform to [RFC 4627][rfc4627], and are syntactically valid JSON. The unicode style is preferred as being more user-friendly when inspecting API responses.  Default: `True` @@ -399,6 +399,12 @@ This should be a function with the following signature:  Default: `'rest_framework.views.exception_handler'` +#### NON_FIELD_ERRORS_KEY + +A string representing the key that should be used for serializer errors that do not refer to a specific field, but are instead general errors. + +Default: `'non_field_errors'` +  #### URL_FIELD_NAME  A string representing the key that should be used for the URL fields generated by `HyperlinkedModelSerializer`. diff --git a/docs/api-guide/status-codes.md b/docs/api-guide/status-codes.md index 64c46434..d81e092c 100644 --- a/docs/api-guide/status-codes.md +++ b/docs/api-guide/status-codes.md @@ -1,4 +1,4 @@ -<a class="github" href="status.py"></a> +source: status.py  # Status Codes @@ -27,7 +27,7 @@ The module also includes a set of helper functions for testing if a status code  	        url = reverse('index')  	        response = self.client.get(url)  	        self.assertTrue(status.is_success(response.status_code)) -         +  For more information on proper usage of HTTP status codes see [RFC 2616][rfc2616]  and [RFC 6585][rfc6585]. @@ -51,7 +51,7 @@ This class of status code indicates that the client's request was successfully r      HTTP_205_RESET_CONTENT      HTTP_206_PARTIAL_CONTENT -## Redirection - 3xx  +## Redirection - 3xx  This class of status code indicates that further action needs to be taken by the user agent in order to fulfill the request. diff --git a/docs/api-guide/testing.md b/docs/api-guide/testing.md index 72c33961..d059fdab 100644 --- a/docs/api-guide/testing.md +++ b/docs/api-guide/testing.md @@ -1,4 +1,4 @@ -<a class="github" href="test.py"></a> +source: test.py  # Testing @@ -170,7 +170,7 @@ This can be a useful shortcut if you're testing the API but don't want to have t  To unauthenticate subsequent requests, call `force_authenticate` setting the user and/or token to `None`. -    client.force_authenticate(user=None)  +    client.force_authenticate(user=None)  ## CSRF validation @@ -197,7 +197,7 @@ You can use any of REST framework's test case classes as you would for the regul      from django.core.urlresolvers import reverse      from rest_framework import status -    from rest_framework.test import APITestCase  +    from rest_framework.test import APITestCase      class AccountTests(APITestCase):          def test_create_account(self): diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 16a7457b..3f668867 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -1,4 +1,4 @@ -<a class="github" href="throttling.py"></a> +source: throttling.py  # Throttling @@ -74,7 +74,7 @@ If you need to strictly identify unique client IP addresses, you'll need to firs  It is important to understand that if you configure the `NUM_PROXIES` setting, then all clients behind a unique [NAT'd](http://en.wikipedia.org/wiki/Network_address_translation) gateway will be treated as a single client. -Further context on how the `X-Forwarded-For` header works, and identifing a remote client IP can be [found here][identifing-clients]. +Further context on how the `X-Forwarded-For` header works, and identifying a remote client IP can be [found here][identifing-clients].  ## Setting up the cache @@ -83,9 +83,9 @@ The throttle classes provided by REST framework use Django's cache backend.  You  If you need to use a cache other than `'default'`, you can do so by creating a custom throttle class and setting the `cache` attribute.  For example:      class CustomAnonRateThrottle(AnonRateThrottle): -        cache = get_cache('alternate')  +        cache = get_cache('alternate') -You'll need to rememeber to also set your custom throttle class in the `'DEFAULT_THROTTLE_CLASSES'` settings key, or using the `throttle_classes` view attribute. +You'll need to remember to also set your custom throttle class in the `'DEFAULT_THROTTLE_CLASSES'` settings key, or using the `throttle_classes` view attribute.  --- @@ -147,15 +147,15 @@ For example, given the following views...      class ContactListView(APIView):          throttle_scope = 'contacts'          ... -     +      class ContactDetailView(ApiView):          throttle_scope = 'contacts'          ... -    class UploadView(APIView):         +    class UploadView(APIView):          throttle_scope = 'uploads'          ... -     +  ...and the following settings.      REST_FRAMEWORK = { diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md new file mode 100644 index 00000000..f087e191 --- /dev/null +++ b/docs/api-guide/validators.md @@ -0,0 +1,231 @@ +<a class="github" href="validators.py"></a> + +--- + +**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available. + +--- + +# Validators + +> Validators can be useful for re-using validation logic between different types of fields. +> +> — [Django documentation][cite] + +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. + +However, 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. + +## Validation in REST framework + +Validation in Django REST framework serializers is handled a little differently to how validation works in Django's `ModelForm` class. + +With `ModelForm` 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: + +* It introduces a proper separation of concerns, making your code behavior more obvious. +* It is easy to switch between using shortcut `ModelSerializer` classes and using  explicit `Serializer` classes. Any validation behavior being used for `ModelSerializer` is simple to replicate. +* Printing the `repr` of a serializer instance will show you exactly what validation rules it applies. There's no extra hidden validation behavior being called on the model instance. + +When you're using `ModelSerializer` all of this is handled automatically for you. If you want to drop down to using a `Serializer` classes instead, then you need to define the validation rules explicitly. + +#### Example + +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. + +    class CustomerReportRecord(models.Model): +        time_raised = models.DateTimeField(default=timezone.now, editable=False)  +        reference = models.CharField(unique=True, max_length=20) +        description = models.TextField() + +Here's a basic `ModelSerializer` that we can use for creating or updating instances of `CustomerReportRecord`: + +    class CustomerReportSerializer(serializers.ModelSerializer): +        class Meta: +            model = CustomerReportRecord + +If we open up the Django shell using `manage.py shell` we can now  + +    >>> 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'}) + +The interesting bit here is the `reference` field. We can see that the uniqueness constraint is being explicitly enforced by a validator on the serializer field. + +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. + +--- + +## UniqueValidator + +This validator can be used to enforce the `unique=True` constraint on model fields. +It takes a single required argument, and an optional `messages` argument: + +* `queryset` *required* - This is the queryset against which uniqueness should be enforced. +* `message` - The error message that should be used when validation fails. + +This validator should be applied to *serializer fields*, like so: + +    slug = SlugField( +        max_length=100, +        validators=[UniqueValidator(queryset=BlogPost.objects.all())] +    ) + +## UniqueTogetherValidator + +This validator can be used to enforce `unique_together` constraints on model instances. +It has two required arguments, and a single optional `messages` argument: + +* `queryset` *required* - This is the queryset against which uniqueness should be enforced. +* `fields` *required* - A list or tuple of field names which should make a unique set. These must exist as fields on the serializer class. +* `message` - The error message that should be used when validation fails. + +The validator should be applied to *serializer classes*, like so: + +    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') +                ) +            ] + +--- + +**Note**: The `UniqueTogetherValidation` class always imposes an implicit constraint that all the fields it applies to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input. + +--- + +## UniqueForDateValidator + +## UniqueForMonthValidator + +## UniqueForYearValidator + +These validators can be used to enforce the `unique_for_date`, `unique_for_month` and `unique_for_year` constraints on model instances. They take the following arguments: + +* `queryset` *required* - This is the queryset against which uniqueness should be enforced. +* `field` *required* - A field name against which uniqueness in the given date range will be validated. This must exist as a field on the serializer class. +* `date_field` *required* - 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. +* `message` - The error message that should be used when validation fails. + +The validator should be applied to *serializer classes*, like so: + +    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' +                ) +            ] + +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 `default=...`, because the value being used for the default wouldn't be generated until after the validation has run. + +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 `ModelSerializer` you'll probably simply rely on the defaults that REST framework generates for you, but if you are using `Serializer` or simply want more explicit control, use on of the styles demonstrated below. + +#### Using with a writable date field. + +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 `default` argument, or by setting `required=True`. + +    published = serializers.DateTimeField(required=True) + +#### Using with a read-only date field. + +If you want the date field to be visible, but not editable by the user, then set `read_only=True` and additionally set a `default=...` argument. + +    published = serializers.DateTimeField(read_only=True, default=timezone.now) + +The field will not be writable to the user, but the default value will still be passed through to the `validated_data`. + +#### Using with a hidden date field. + +If you want the date field to be entirely hidden from the user, then use `HiddenField`. This field type does not accept user input, but instead always returns it's default value to the `validated_data` in the serializer. + +    published = serializers.HiddenField(default=timezone.now) + +--- + +**Note**: The `UniqueFor<Range>Validation` classes always imposes an implicit constraint that the fields they are applied to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input. + +--- + +# Advanced 'default' argument usage + +Validators that are applied across multiple fields in the serializer can sometimes require a field input that should not be provided by the API client, but that *is* available as input to the validator. + +Two patterns that you may want to use for this sort of validation include: + +* Using `HiddenField`. This field will be present in `validated_data` but *will not* be used in the serializer output representation. +* Using a standard field with `read_only=True`, but that also includes a `default=…` argument. This field *will* be used in the serializer output representation, but cannot be set directly by the user. + +REST framework includes a couple of defaults that may be useful in this context. + +#### CurrentUserDefault + +A default class that can be used to represent the current user. In order to use this, the 'request' must have been provided as part of the context dictionary when instantiating the serializer. + +    owner = serializers.HiddenField( +        default=CurrentUserDefault() +    ) + +#### CreateOnlyDefault + +A default class that can be used to *only set a default argument during create operations*. During updates the field is omitted. + +It takes a single argument, which is the default value or callable that should be used during create operations. + +    created_at = serializers.DateTimeField( +        read_only=True, +        default=CreateOnlyDefault(timezone.now) +    ) + +--- + +# Writing custom validators + +You can use any of Django's existing validators, or write your own custom validators. + +## Function based + +A validator may be any callable that raises a `serializers.ValidationError` on failure. + +    def even_number(value): +        if value % 2 != 0: +            raise serializers.ValidationError('This field must be an even number.')  + +## Class based + +To write a class based validator, use the `__call__` method. Class based validators are useful as they allow you to parameterize and reuse behavior. + +    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) + +#### Using `set_context()` + +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 `set_context` method on a class based validator. + +    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 + +[cite]: https://docs.djangoproject.com/en/dev/ref/validators/ diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index 194a7a6b..31c62682 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -1,4 +1,5 @@ -<a class="github" href="decorators.py"></a> <a class="github" href="views.py"></a> +source: decorators.py +        views.py  # Class Based Views @@ -26,7 +27,7 @@ For example:      class ListUsers(APIView):          """          View to list all users in the system. -         +          * Requires token authentication.          * Only admin users are able to access this view.          """ @@ -54,7 +55,7 @@ The following attributes control the pluggable aspects of API views.  ### .permission_classes -### .content_negotiation_class  +### .content_negotiation_class  ## API policy instantiation methods diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 9030e3ee..28186c64 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -1,4 +1,4 @@ -<a class="github" href="viewsets.py"></a> +source: viewsets.py  # ViewSets @@ -124,7 +124,7 @@ For example:          @detail_route(methods=['post'])          def set_password(self, request, pk=None):              user = self.get_object() -            serializer = PasswordSerializer(data=request.DATA) +            serializer = PasswordSerializer(data=request.data)              if serializer.is_valid():                  user.set_password(serializer.data['password'])                  user.save() diff --git a/docs/css/default.css b/docs/css/default.css index 7f3acfed..8c9cd536 100644 --- a/docs/css/default.css +++ b/docs/css/default.css @@ -181,7 +181,7 @@ body{  }  .navbar .navbar-inner .nav li, .navbar .navbar-inner .nav li a, .navbar .navbar-inner .brand{ -  color: white;  +  color: white;  }  .nav-list > .active > a, .navbar .navbar-inner .nav li a:hover { @@ -190,8 +190,20 @@ body{  }  .navbar .navbar-inner .dropdown-menu li a, .navbar .navbar-inner .dropdown-menu li{ - color: #A30000;  + color: #A30000; +} + +.dropdown-menu .active > a, +.dropdown-menu .active > a:hover { +  background-image: none;  } + +.navbar-inverse .nav .dropdown .active > a, +.navbar-inverse .nav .dropdown .active > a:hover, +.navbar-inverse .nav .dropdown .active > a:focus { +  background-color: #eeeeee; +} +  .navbar .navbar-inner .dropdown-menu li a:hover{    background: #eeeeee;    color: #c20000; diff --git a/docs/index.md b/docs/index.md index 7e6f520c..dc7bb6d0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,6 +9,10 @@  --- +**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available. + +--- +  <p>  <h1 style="position: absolute;      width: 1px; @@ -22,9 +26,6 @@  <img alt="Django REST Framework" title="Logo by Jake 'Sid' Smith" src="img/logo.png" width="600px" style="display: block; margin: 0 auto 0 auto">  </p> -<!-- -# Django REST framework --->  Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs. @@ -43,14 +44,12 @@ Some reasons you might want to use REST framework:  **Above**: *Screenshot from the browsable API* ----- -  ## Requirements  REST framework requires the following: -* Python (2.6.5+, 2.7, 3.2, 3.3) -* Django (1.4.2+, 1.5, 1.6, 1.7) +* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4) +* Django (1.4.11+, 1.5.5+, 1.6, 1.7)  The following packages are optional: @@ -82,10 +81,10 @@ Add `'rest_framework'` to your `INSTALLED_APPS` setting.  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 `urls.py` file. -    urlpatterns = patterns('', +    urlpatterns = [          ...          url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) -    ) +    ]  Note that the URL path can be whatever you want, but you must include `'rest_framework.urls'` with the `'rest_framework'` namespace. @@ -170,6 +169,7 @@ The API guide is your complete reference manual to all the functionality provide  * [Serializers][serializers]  * [Serializer fields][fields]  * [Serializer relations][relations] +* [Validators][validators]  * [Authentication][authentication]  * [Permissions][permissions]  * [Throttling][throttling] @@ -289,6 +289,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  [serializers]: api-guide/serializers.md  [fields]: api-guide/fields.md  [relations]: api-guide/relations.md +[validators]: api-guide/validators.md  [authentication]: api-guide/authentication.md  [permissions]: api-guide/permissions.md  [throttling]: api-guide/throttling.md diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index a91fb978..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -markdown>=2.1.0 diff --git a/docs/template.html b/docs/template.html deleted file mode 100644 index bb3ae221..00000000 --- a/docs/template.html +++ /dev/null @@ -1,238 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> -    <meta charset="utf-8"> -    <title>{{ title }}</title> -    <link href="{{ base_url }}/img/favicon.ico" rel="icon" type="image/x-icon"> -    <link rel="canonical" href="{{ canonical_url }}"/> -    <meta name="viewport" content="width=device-width, initial-scale=1.0"> -    <meta name="description" content="{{ description }}"> -    <meta name="author" content="Tom Christie"> - -    <!-- Le styles --> -    <link href="{{ base_url }}/css/prettify.css" rel="stylesheet"> -    <link href="{{ base_url }}/css/bootstrap.css" rel="stylesheet"> -    <link href="{{ base_url }}/css/bootstrap-responsive.css" rel="stylesheet"> -    <link href="{{ base_url }}/css/default.css" rel="stylesheet"> - -    <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements --> -    <!--[if lt IE 9]> -      <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> -    <![endif]--> - -    <script type="text/javascript"> - -  var _gaq = _gaq || []; -  _gaq.push(['_setAccount', 'UA-18852272-2']); -  _gaq.push(['_trackPageview']); - -  (function() { -    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; -    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; -    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); -  })(); - -    </script> -    <style> -span.fusion-wrap a { -  display: block; -  margin-top: 10px; -  color: black; -} - -a.fusion-poweredby { -  display: block; -  margin-top: 10px; -} -@media (max-width: 767px) { -  div.promo {display: none;} -} -</style> -  </head> -  <body onload="prettyPrint()" class="{{ page_id }}-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 {{ next_url_disabled }}" href="{{ next_url }}">Next <i class="icon-arrow-right icon-white"></i></a> -            <a class="repo-link btn btn-inverse btn-small {{ prev_url_disabled }}" href="{{ prev_url }}"><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="{{ base_url }}{{ index }}">Django REST framework</a> -          <div class="nav-collapse collapse"> -            <ul class="nav"> -              <li><a href="{{ base_url }}{{ index }}">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="{{ base_url }}/tutorial/quickstart{{ suffix }}">Quickstart</a></li> -                  <li><a href="{{ base_url }}/tutorial/1-serialization{{ suffix }}">1 - Serialization</a></li> -                  <li><a href="{{ base_url }}/tutorial/2-requests-and-responses{{ suffix }}">2 - Requests and responses</a></li> -                  <li><a href="{{ base_url }}/tutorial/3-class-based-views{{ suffix }}">3 - Class based views</a></li> -                  <li><a href="{{ base_url }}/tutorial/4-authentication-and-permissions{{ suffix }}">4 - Authentication and permissions</a></li> -                  <li><a href="{{ base_url }}/tutorial/5-relationships-and-hyperlinked-apis{{ suffix }}">5 - Relationships and hyperlinked APIs</a></li> -                  <li><a href="{{ base_url }}/tutorial/6-viewsets-and-routers{{ suffix }}">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="{{ base_url }}/api-guide/requests{{ suffix }}">Requests</a></li> -                  <li><a href="{{ base_url }}/api-guide/responses{{ suffix }}">Responses</a></li> -                  <li><a href="{{ base_url }}/api-guide/views{{ suffix }}">Views</a></li> -                  <li><a href="{{ base_url }}/api-guide/generic-views{{ suffix }}">Generic views</a></li> -                  <li><a href="{{ base_url }}/api-guide/viewsets{{ suffix }}">Viewsets</a></li> -                  <li><a href="{{ base_url }}/api-guide/routers{{ suffix }}">Routers</a></li> -                  <li><a href="{{ base_url }}/api-guide/parsers{{ suffix }}">Parsers</a></li> -                  <li><a href="{{ base_url }}/api-guide/renderers{{ suffix }}">Renderers</a></li> -                  <li><a href="{{ base_url }}/api-guide/serializers{{ suffix }}">Serializers</a></li> -                  <li><a href="{{ base_url }}/api-guide/fields{{ suffix }}">Serializer fields</a></li> -                  <li><a href="{{ base_url }}/api-guide/relations{{ suffix }}">Serializer relations</a></li> -                  <li><a href="{{ base_url }}/api-guide/authentication{{ suffix }}">Authentication</a></li> -                  <li><a href="{{ base_url }}/api-guide/permissions{{ suffix }}">Permissions</a></li> -                  <li><a href="{{ base_url }}/api-guide/throttling{{ suffix }}">Throttling</a></li> -                  <li><a href="{{ base_url }}/api-guide/filtering{{ suffix }}">Filtering</a></li> -                  <li><a href="{{ base_url }}/api-guide/pagination{{ suffix }}">Pagination</a></li> -                  <li><a href="{{ base_url }}/api-guide/content-negotiation{{ suffix }}">Content negotiation</a></li> -                  <li><a href="{{ base_url }}/api-guide/format-suffixes{{ suffix }}">Format suffixes</a></li> -                  <li><a href="{{ base_url }}/api-guide/reverse{{ suffix }}">Returning URLs</a></li> -                  <li><a href="{{ base_url }}/api-guide/exceptions{{ suffix }}">Exceptions</a></li> -                  <li><a href="{{ base_url }}/api-guide/status-codes{{ suffix }}">Status codes</a></li> -                  <li><a href="{{ base_url }}/api-guide/testing{{ suffix }}">Testing</a></li> -                  <li><a href="{{ base_url }}/api-guide/settings{{ suffix }}">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="{{ base_url }}/topics/documenting-your-api{{ suffix }}">Documenting your API</a></li> -                  <li><a href="{{ base_url }}/topics/ajax-csrf-cors{{ suffix }}">AJAX, CSRF & CORS</a></li> -                  <li><a href="{{ base_url }}/topics/browser-enhancements{{ suffix }}">Browser enhancements</a></li> -                  <li><a href="{{ base_url }}/topics/browsable-api{{ suffix }}">The Browsable API</a></li> -                  <li><a href="{{ base_url }}/topics/rest-hypermedia-hateoas{{ suffix }}">REST, Hypermedia & HATEOAS</a></li> -                  <li><a href="{{ base_url }}/topics/third-party-resources{{ suffix }}">Third Party Resources</a></li> -                  <li><a href="{{ base_url }}/topics/contributing{{ suffix }}">Contributing to REST framework</a></li> -                  <li><a href="{{ base_url }}/topics/rest-framework-2-announcement{{ suffix }}">2.0 Announcement</a></li> -                  <li><a href="{{ base_url }}/topics/2.2-announcement{{ suffix }}">2.2 Announcement</a></li> -                  <li><a href="{{ base_url }}/topics/2.3-announcement{{ suffix }}">2.3 Announcement</a></li> -                  <li><a href="{{ base_url }}/topics/2.4-announcement{{ suffix }}">2.4 Announcement</a></li> -                  <li><a href="{{ base_url }}/topics/kickstarter-announcement{{ suffix }}">Kickstarter Announcement</a></li> -                  <li><a href="{{ base_url }}/topics/release-notes{{ suffix }}">Release Notes</a></li> -                  <li><a href="{{ base_url }}/topics/credits{{ suffix }}">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"> -                {{ toc }} -              <div class="promo"> -              {{ ad_block }} -              </div> -</ul> - -            </div> -          </div> - -          <div id="main-content" class="span9"> -            {{ content }} -          </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="{{ base_url }}/js/jquery-1.8.1-min.js"></script> -    <script src="{{ base_url }}/js/prettify-1.0.js"></script> -    <script src="{{ base_url }}/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> diff --git a/docs/topics/2.2-announcement.md b/docs/topics/2.2-announcement.md index a997c782..1df52cff 100644 --- a/docs/topics/2.2-announcement.md +++ b/docs/topics/2.2-announcement.md @@ -42,7 +42,7 @@ The 2.2 release makes a few changes to the API, in order to make it more consist  The `ManyRelatedField()` style is being deprecated in favor of a new `RelatedField(many=True)` syntax. -For example, if a user is associated with multiple questions, which we want to represent using a primary key relationship, we might use something like the following:  +For example, if a user is associated with multiple questions, which we want to represent using a primary key relationship, we might use something like the following:      class UserSerializer(serializers.HyperlinkedModelSerializer):          questions = serializers.PrimaryKeyRelatedField(many=True) @@ -58,10 +58,10 @@ The change also applies to serializers.  If you have a nested serializer, you sh          class Meta:              model = Track              fields = ('name', 'duration') -     +      class AlbumSerializer(serializer.ModelSerializer):          tracks = TrackSerializer(many=True) -         +          class Meta:              model = Album              fields = ('album_name', 'artist', 'tracks') @@ -87,7 +87,7 @@ For example, is a user account has an optional foreign key to a company, that yo  This is in line both with the rest of the serializer fields API, and with Django's `Form` and `ModelForm` API. -Using `required` throughout the serializers API means you won't need to consider if a particular field should take `blank` or `null` arguments instead of `required`, and also means there will be more consistent behavior for how fields are treated when they are not present in the incoming data.  +Using `required` throughout the serializers API means you won't need to consider if a particular field should take `blank` or `null` arguments instead of `required`, and also means there will be more consistent behavior for how fields are treated when they are not present in the incoming data.  The `null=True` argument will continue to function, and will imply `required=False`, but will raise a `PendingDeprecationWarning`. diff --git a/docs/topics/2.3-announcement.md b/docs/topics/2.3-announcement.md index ba435145..9c9f3e9f 100644 --- a/docs/topics/2.3-announcement.md +++ b/docs/topics/2.3-announcement.md @@ -15,7 +15,7 @@ As an example of just how simple REST framework APIs can now be, here's an API w      """      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 @@ -27,7 +27,7 @@ As an example of just how simple REST framework APIs can now be, here's an API w      class GroupViewSet(viewsets.ModelViewSet):          model = Group -     +      # Routers provide an easy way of automatically determining the URL conf      router = routers.DefaultRouter()      router.register(r'users', UserViewSet) @@ -36,10 +36,10 @@ As an example of just how simple REST framework APIs can now be, here's an API w      # 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')) -    ) +    ]  The best place to get started with ViewSets and Routers is to take a look at the [newest section in the tutorial][part-6], which demonstrates their usage. @@ -197,13 +197,13 @@ Usage of the old-style attributes continues to be supported, but will raise a `P  For most cases APIs using model fields will behave as previously, however if you are using a custom renderer, not provided by REST framework, then you may now need to add support for rendering `Decimal` instances to your renderer implementation. -## ModelSerializers and reverse relationships  +## ModelSerializers and reverse relationships  The support for adding reverse relationships to the `fields` option on a `ModelSerializer` class means that the `get_related_field` and `get_nested_field` method signatures have now changed.  In the unlikely event that you're providing a custom serializer class, and implementing these methods you should note the new call signature for both methods is now `(self, model_field, related_model, to_many)`.  For reverse relationships `model_field` will be `None`. -The old-style signature will continue to function but will raise a `PendingDeprecationWarning`.  +The old-style signature will continue to function but will raise a `PendingDeprecationWarning`.  ## View names and descriptions @@ -211,7 +211,7 @@ The mechanics of how the names and descriptions used in the browseable API are g  If you've been customizing this behavior, for example perhaps to use `rst` markup for the browseable API, then you'll need to take a look at the implementation to see what updates you need to make. -Note that the relevant methods have always been private APIs, and the docstrings called them out as intended to be deprecated.  +Note that the relevant methods have always been private APIs, and the docstrings called them out as intended to be deprecated.  --- diff --git a/docs/topics/2.4-announcement.md b/docs/topics/2.4-announcement.md index 09294b91..f38c743c 100644 --- a/docs/topics/2.4-announcement.md +++ b/docs/topics/2.4-announcement.md @@ -23,7 +23,7 @@ The documentation has previously stated that usage of the more explicit style is  Doing so will mean that there are cases of API code where you'll now need to include a serializer class where you previously were just using the `.model` shortcut. However we firmly believe that it is the right trade-off to make. -Removing the shortcut takes away an unneccessary layer of abstraction, and makes your codebase more explicit without any significant extra complexity. It also results in better consistency, as there's now only one way to set the serializer class and queryset attributes for the view, instead of two. +Removing the shortcut takes away an unnecessary layer of abstraction, and makes your codebase more explicit without any significant extra complexity. It also results in better consistency, as there's now only one way to set the serializer class and queryset attributes for the view, instead of two.  The `DEFAULT_MODEL_SERIALIZER_CLASS` API setting is now also deprecated. @@ -164,8 +164,8 @@ Once again, many thanks to all the generous [backers and sponsors][kickstarter-s  [lts-releases]: https://docs.djangoproject.com/en/dev/internals/release-process/#long-term-support-lts-releases  [2-4-release-notes]: release-notes#240 -[view-name-and-description-settings]: ../api-guide/settings/#view-names-and-descriptions -[client-ip-identification]: ../api-guide/throttling/#how-clients-are-identified +[view-name-and-description-settings]: ../api-guide/settings#view-names-and-descriptions +[client-ip-identification]: ../api-guide/throttling#how-clients-are-identified  [2-3-announcement]: 2.3-announcement  [github-labels]: https://github.com/tomchristie/django-rest-framework/issues  [github-milestones]: https://github.com/tomchristie/django-rest-framework/milestones diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 029d9896..b32fe510 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -1,70 +1,335 @@ -**THIS DOCUMENT IS CURRENTLY A WORK IN PROGRESS** +# REST framework 3.0 -See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-framework/pull/1800) for more details. +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. -# REST framework 3.0 +This release is incremental in nature. There *are* some breaking API changes, and upgrading *will* require you to read the release notes carefully, but the migration path should otherwise be relatively straightforward. + +The difference in quality of the REST framework API and implementation should make writing, maintaining and debugging your application far easier. + +3.0 is the first of three releases that have been funded by our recent [Kickstarter campaign](kickstarter.com/projects/tomchristie/django-rest-framework-3). -**Note incremental nature, discuss upgrading.** +As ever, a huge thank you to our many [wonderful sponsors](sponsors). If you're looking for a Django gig, and want to work with smart community-minded folks, you should probably check out that list and see who's hiring. -## Motivation +## New features -**TODO** +Notable features of this new release include: + +* Printable representations on serializers that allow you to inspect exactly what fields are present on the instance. +* Simple model serializers that are vastly easier to understand and debug, and that make it easy to switch between the implicit `ModelSerializer` class and the explicit `Serializer` class. +* A new `BaseSerializer` class, making it easier to write serializers for alternative storage backends, or to completely customize your serialization and validation logic. +* A cleaner fields API including new classes such as `ListField` and `MultipleChoiceField`. +* [Super simple default implementations][mixins.py] for the generic views. +* Support for overriding how validation errors are handled by your API. +* A metadata API that allows you to customize how `OPTIONS` requests are handled by your API. +* A more compact JSON output with unicode style encoding turned on by default. +* Templated based HTML form rendering for serializers. This will be finalized as  public API in the upcoming 3.1 release. + +Significant new functionality continues to be planned for the 3.1 and 3.2 releases. These releases will correspond to the two [Kickstarter stretch goals](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) - "Feature improvements" and "Admin interface". Further 3.x releases will present simple upgrades, without the same level of fundamental API changes necessary for the 3.0 release. + +Below is an in-depth guide to the API changes and migration notes for 3.0.  ---  ## Request objects -#### The `request.data` property. +#### The `.data` and `.query_params` properties. + +The usage of `request.DATA` and `request.FILES` is now pending deprecation in favor of a single `request.data` attribute that contains *all* the parsed data. -**TODO** +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. -#### The parser API. +You may now pass all the request data to a serializer class in a single argument: -**TODO** +    # Do this... +    ExampleSerializer(data=request.data) + +Instead of passing the files argument separately: + +    # Don't do this... +    ExampleSerializer(data=request.DATA, files=request.FILES) + + +The usage of `request.QUERY_PARAMS` is now pending deprecation in favor of the lowercased `request.query_params`.  ## Serializers  #### Single-step object creation. -**TODO**: Drop `.restore_object()`, use `.create()` and `.update()` which should save the instance. +Previously the serializers used a two-step object creation, as follows: + +1. Validating the data would create an object instance. This instance would be available as `serializer.object`. +2. Calling `serializer.save()` would then save the object instance to the database. + +This style is in-line with how the `ModelForm` class works in Django, but is problematic for a number of reasons: + +* 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 `.save()` is called. +* Instantiating model instances directly means that you cannot use model manager classes for instance creation, e.g. `ExampleModel.objects.create(...)`. Manager classes are an excellent layer at which to enforce business logic and application-level data constraints. +* 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? + +We now use single-step object creation, like so: + +1. Validating the data makes the cleaned data available as `serializer.validated_data`. +2. Calling `serializer.save()` then saves and returns the new object instance. + +The resulting API changes are further detailed below. + +#### The `.create()` and `.update()` methods. + +The `.restore_object()` method is now replaced with two separate methods, `.create()` and `.update()`. + +These methods also replace the optional `.save_object()` method, which no longer exists. + +When using the `.create()` and `.update()` methods you should both create *and save* the object instance. This is in contrast to the previous `.restore_object()` behavior that would instantiate the object but not save it. + +The following example from the tutorial previously used `restore_object()` to handle both creating and updating object instances. + +    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) + +This would now be split out into two separate methods. + +    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) + +Note that these methods should return the newly created object instance. + +#### Use `.validated_data` instead of `.object`. + +You must now use the `.validated_data` attribute if you need to inspect the data before saving, rather than using the `.object` attribute, which no longer exists. + +For example the following code *is no longer valid*: + +    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() + +Instead of using `.object` to inspect a partially constructed instance, you would now use `.validated_data` to inspect the cleaned incoming values. Also you can't set extra attributes on the instance directly, but instead pass them to the `.save()` method as keyword arguments. + +The corresponding code would now look like this: + +    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. + +#### Using `.is_valid(raise_exception=True)` + +The `.is_valid()` method now takes an optional boolean flag, `raise_exception`. + +Calling `.is_valid(raise_exception=True)` will cause a `ValidationError` to be raised if the serializer data contains validation errors. This error will be handled by REST framework's default exception handler, allowing you to remove error response handling from your view code. + +The handling and formatting of error responses may be altered globally by using the `EXCEPTION_HANDLER` settings key. + +This change also means it's now possible to alter the style of error responses used by the built-in generic views, without having to include mixin classes or other overrides. + +#### Using `serializers.ValidationError`. + +Previously `serializers.ValidationError` error was simply a synonym for `django.core.exceptions.ValidationError`. This has now been altered so that it inherits from the standard `APIException` base class. + +The reason behind this is that Django's `ValidationError` 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. + +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 `serializers.ValidationError` exception class, and not Django's built-in exception. + +We strongly recommend that you use the namespaced import style of `import serializers` and not `from serializers import ValidationError` in order to avoid any potential confusion. + +#### Change to `validate_<field_name>`. + +The `validate_<field_name>` 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: + +    def validate_score(self, attrs, source): +        if attrs['score'] % 10 != 0: +            raise serializers.ValidationError('This field should be a multiple of ten.') +        return attrs + +This is now simplified slightly, and the method hooks simply take the value to be validated, and return the validated value. + +    def validate_score(self, value): +        if value % 10 != 0: +            raise serializers.ValidationError('This field should be a multiple of ten.') +        return value + +Any ad-hoc validation that applies to more than one field should go in the `.validate(self, attrs)` method as usual. + +Because `.validate_<field_name>` 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 `.validate()` instead. + +You can either return `non_field_errors` from the validate method by raising a simple `ValidationError` + +    def validate(self, attrs): +        # serializer.errors == {'non_field_errors': ['A non field error']} +        raise serializers.ValidationError('A non field error') + +Alternatively if you want the errors to be against a specific field, use a dictionary of when instantiating the `ValidationError`, like so: + +    def validate(self, attrs): +        # serializer.errors == {'my_field': ['A field error']} +        raise serializers.ValidationError({'my_field': 'A field error'}) + +This ensures you can still write validation that compares all the input fields, but that marks the error against a particular field. + +#### Removal of `transform_<field_name>`. -**TODO**: Drop`.object`, use `.validated_data` or get the instance with `.save()`. +The under-used `transform_<field_name>` on serializer classes is no longer provided. Instead you should just override `to_representation()` if you need to apply any modifications to the representation style. -#### Always use `fields`, not `exclude`. +For example: -The `exclude` option is no longer available. You should use the more explicit `fields` option instead. +    def to_representation(self, instance): +        ret = super(UserSerializer, self).to_representation(instance) +        ret['username'] = ret['username'].lower() +        return ret + +Dropping the extra point of API means there's now only one right way to do things. This helps with repetition and reinforcement of the core API, rather than having multiple differing approaches. + +If you absolutely need to preserve `transform_<field_name>` behavior, for example, in order to provide a simpler 2.x to 3.0 upgrade, you can use a mixin, or serializer base class that add the behavior back in. For example: + +    class BaseModelSerializer(ModelSerializer): +        """ +        A custom ModelSerializer class that preserves 2.x style `transform_<field_name>` behavior. +        """ +        def to_representation(self, instance): +            ret = super(BaseModelSerializer, self).to_representation(instance) +            for key, value in ret.items(): +                method = getattr(self, 'transform_' + key, None) +                if method is not None: +                    ret[key] = method(value) +            return ret + +#### Differences between ModelSerializer validation and ModelForm. + +This change also means that we no longer use the `.full_clean()` 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 `ModelSerializer` classes that can't also be easily replicated on regular `Serializer` classes. + +It's important to note that this change also means that the model `.clean()` method will not be called as part of serializer validation, as it would be if using a `ModelForm`. Use the serializer `.validate()` method to perform a final validation step on incoming data where required. + +#### Writable nested serialization. + +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: + +* There can be complex dependencies involved in order of saving multiple related model instances. +* It's unclear what behavior the user should expect when related models are passed `None` data. +* It's unclear how the user should expect to-many relationships to handle updates, creations and deletions of multiple records. + +Using the `depth` option on `ModelSerializer` will now create **read-only nested serializers** by default. + +If you try to use a writable nested serializer without writing a custom `create()` and/or `update()` method you'll see an assertion error when you attempt to save the serializer. For example: + +    >>> 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 support nested writable fields by default. Write an explicit `.create()` method for serializer `UserSerializer`, or set `read_only=True` on nested serializer fields. + +To use writable nested serialization you'll want to declare a nested field on the serializer class, and write the `create()` and/or `update()` methods explicitly. + +    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 + +The single-step object creation makes this far simpler and more obvious than the previous `.restore_object()` behavior. + +#### Printable serializer representations. + +Serializer instances now support a printable representation that allows you to inspect the fields present on the instance. + +For instance, given the following example model: + +    class LocationRating(models.Model): +        location = models.CharField(max_length=100) +        rating = models.IntegerField() +        created_by = models.ForeignKey(User) + +Let's create a simple `ModelSerializer` class corresponding to the `LocationRating` model. + +    class LocationRatingSerializer(serializer.ModelSerializer): +        class Meta: +            model = LocationRating + +We can now inspect the serializer representation in the Django shell, using `python manage.py shell`... + +    >>> 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())  #### The `extra_kwargs` option. -The `read_only_fields` and `write_only_fields` options have been removed and replaced with a more generic `extra_kwargs`. +The `write_only_fields` option on `ModelSerializer` has been moved to `PendingDeprecation` and replaced with a more generic `extra_kwargs`.      class MySerializer(serializer.ModelSerializer):          class Meta:              model = MyModel              fields = ('id', 'email', 'notes', 'is_admin')              extra_kwargs = { -            	'is_admin': {'read_only': True} +            	    'is_admin': {'write_only': True}              }  Alternatively, specify the field explicitly on the serializer class:      class MySerializer(serializer.ModelSerializer): -        is_admin = serializers.BooleanField(read_only=True) +        is_admin = serializers.BooleanField(write_only=True)          class Meta:              model = MyModel              fields = ('id', 'email', 'notes', 'is_admin') +The `read_only_fields` option remains as a convenient shortcut for the more common case. +  #### Changes to `HyperlinkedModelSerializer`. -The `view_name` and `lookup_field` options have been removed. They are no longer required, as you can use the `extra_kwargs` argument instead: +The `view_name` and `lookup_field` options have been moved to `PendingDeprecation`. They are no longer required, as you can use the `extra_kwargs` argument instead:      class MySerializer(serializer.HyperlinkedModelSerializer):          class Meta:              model = MyModel              fields = ('url', 'email', 'notes', 'is_admin')              extra_kwargs = { -            	'url': {'lookup_field': 'uuid'} +                'url': {'lookup_field': 'uuid'}              }  Alternatively, specify the field explicitly on the serializer class: @@ -81,15 +346,15 @@ Alternatively, specify the field explicitly on the serializer class:  #### Fields for model methods and properties. -You can now specify field names in the `fields` option that refer to model methods or properties. For example, suppose you have the following model: +With `ModelSerializer` you can now specify field names in the `fields` option that refer to model methods or properties. For example, suppose you have the following model:      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) +        def expiry_date(self): +            return self.created + datetime.timedelta(days=30)  You can include `expiry_date` as a field option on a `ModelSerializer` class. @@ -107,55 +372,382 @@ These fields will be mapped to `serializers.ReadOnlyField()` instances.          message = CharField(max_length=1000)          expiry_date = ReadOnlyField() +#### The `ListSerializer` class. + +The `ListSerializer` class has now been added, and allows you to create base serializer classes for only accepting multiple inputs. + +    class MultipleUserSerializer(ListSerializer): +        child = UserSerializer() + +You can also still use the `many=True` argument to serializer classes. It's worth noting that `many=True` argument transparently creates a `ListSerializer` instance, allowing the validation logic for list and non-list data to be cleanly separated in the REST framework codebase. + +You will typically want to *continue to use the existing `many=True` flag* rather than declaring `ListSerializer` classes explicitly, but declaring the classes explicitly can be useful if you need to write custom `create` or `update` methods for bulk updates, or provide for other custom behavior. + +See also the new `ListField` class, which validates input in the same way, but does not include the serializer interfaces of `.is_valid()`, `.data`, `.save()` and so on. + +#### The `BaseSerializer` class. + +REST framework now includes a simple `BaseSerializer` class that can be used to easily support alternative serialization and deserialization styles. + +This class implements the same basic API as the `Serializer` class: + +* `.data` - Returns the outgoing primitive representation. +* `.is_valid()` - Deserializes and validates incoming data. +* `.validated_data` - Returns the validated incoming data. +* `.errors` - Returns an errors during validation. +* `.save()` - Persists the validated data into an object instance. + +There are four methods that can be overridden, depending on what functionality you want the serializer class to support: + +* `.to_representation()` - Override this to support serialization, for read operations. +* `.to_internal_value()` - Override this to support deserialization, for write operations. +* `.create()` and `.update()` - Override either or both of these to support saving instances. + +Because this class provides the same interface as the `Serializer` class, you can use it with the existing generic class based views exactly as you would for a regular `Serializer` or `ModelSerializer`. + +The only difference you'll notice when doing so is the `BaseSerializer` classes will not generate HTML forms in the browsable API. This is because the data they return does not include all the field information that would allow each field to be rendered into a suitable HTML input. + +##### Read-only `BaseSerializer` classes. + +To implement a read-only serializer using the `BaseSerializer` class, we just need to override the `.to_representation()` method. Let's take a look at an example using a simple Django model: + +    class HighScore(models.Model): +        created = models.DateTimeField(auto_now_add=True) +        player_name = models.CharField(max_length=10) +        score = models.IntegerField() + +It's simple to create a read-only serializer for converting `HighScore` instances into primitive data types. + +    class HighScoreSerializer(serializers.BaseSerializer): +        def to_representation(self, obj): +            return { +                'score': obj.score, +                'player_name': obj.player_name +            } + +We can now use this class to serialize single `HighScore` instances: + +    @api_view(['GET']) +    def high_score(request, pk): +        instance = HighScore.objects.get(pk=pk) +        serializer = HighScoreSerializer(instance) +	    return Response(serializer.data) + +Or use it to serialize multiple instances: + +    @api_view(['GET']) +    def all_high_scores(request): +        queryset = HighScore.objects.order_by('-score') +        serializer = HighScoreSerializer(queryset, many=True) +	    return Response(serializer.data) + +##### Read-write `BaseSerializer` classes. + +To create a read-write serializer we first need to implement a `.to_internal_value()` method. This method returns the validated values that will be used to construct the object instance, and may raise a `ValidationError` if the supplied data is in an incorrect format. + +Once you've implemented `.to_internal_value()`, the basic validation API will be available on the serializer, and you will be able to use `.is_valid()`, `.validated_data` and `.errors`. + +If you want to also support `.save()` you'll need to also implement either or both of the `.create()` and `.update()` methods. + +Here's a complete example of our previous `HighScoreSerializer`, that's been updated to support both read and write operations. + +    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) + +#### Creating new generic serializers with `BaseSerializer`. + +The `BaseSerializer` 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. + +The following class is an example of a generic serializer that can handle coercing arbitrary objects into primitive representations. + +    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): +                    # Recursively deal with items in lists. +                    output[attribute_name] = [ +                        self.to_representation(item) for item in attribute +                    ] +                elif isinstance(attribute, dict): +                    # Recursively deal with items in dictionaries. +                    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)  ## Serializer fields  #### The `Field` and `ReadOnly` field classes. -**TODO** +There are some minor tweaks to the field base classes. + +Previously we had these two base classes: + +* `Field` as the base class for read-only fields. A default implementation was included for serializing data. +* `WritableField` as the base class for read-write fields. + +We now use the following: + +* `Field` is the base class for all fields. It does not include any default implementation for either serializing or deserializing data. +* `ReadOnlyField` is a concrete implementation for read-only fields that simply returns the attribute value without modification. + +#### The `required`, `allow_none`, `allow_blank` and `default` arguments. + +REST framework now has more explicit and clear control over validating empty values for fields. + +Previously the meaning of the `required=False` 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 `None` or the empty string. + +We now have a better separation, with separate `required`, `allow_none` and `allow_blank` arguments. + +The following set of arguments are used to control validation of empty values: + +* `required=False`: The value does not need to be present in the input, and will not be passed to `.create()` or `.update()` if it is not seen. +* `default=<value>`: The value does not need to be present in the input, and a default value will be passed to `.create()` or `.update()` if it is not seen. +* `allow_none=True`: `None` is a valid input. +* `allow_blank=True`: `''` is valid input. For `CharField` and subclasses only. + +Typically you'll want to use `required=False` if the corresponding model field has a default value, and additionally set either `allow_none=True` or `allow_blank=True` if required. + +The `default` argument is also available and always implies that the field is not required to be in the input. It is unnecessary to use the `required` argument when a default is specified, and doing so will result in an error.  #### Coercing output types. -**TODO** +The previous field implementations did not forcibly coerce returned values into the correct type in many cases. For example, an `IntegerField` 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. -#### The `ListSerializer` class. +#### Removal of `.validate()`. + +The `.validate()` method is now removed from field classes. This method was in any case undocumented and not public API. You should instead simply override `to_internal_value()`. + +    class UppercaseCharField(serializers.CharField): +        def to_internal_value(self, data): +            value = super(UppercaseCharField, self).to_internal_value(data) +            if value != value.upper(): +                raise serializers.ValidationError('The input should be uppercase only.') +            return value + +Previously validation errors could be raised in either `.to_native()` or `.validate()`, making it non-obvious which should be used. Providing only a single point of API ensures more repetition and reinforcement of the core API. + +#### The `ListField` class. + +The `ListField` class has now been added. This field validates list input. It takes a `child` keyword argument which is used to specify the field used to validate each item in the list. For example: + +    scores = ListField(child=IntegerField(min_value=0, max_value=100)) + +You can also use a declarative style to create new subclasses of `ListField`, like this: + +    class ScoresField(ListField): +        child = IntegerField(min_value=0, max_value=100) + +We can now use the `ScoresField` class inside another serializer: + +    scores = ScoresField() -**TODO** +See also the new `ListSerializer` class, which validates input in the same way, but also includes the serializer interfaces of `.is_valid()`, `.data`, `.save()` and so on. + +#### The `ChoiceField` class may now accept a flat list. + +The `ChoiceField` class may now accept a list of choices in addition to the existing style of using a list of pairs of `(name, display_value)`. The following is now valid: + +    color = ChoiceField(choices=['red', 'green', 'blue'])  #### The `MultipleChoiceField` class. -**TODO** +The `MultipleChoiceField` class has been added. This field acts like `ChoiceField`, but returns a set, which may include none, one or many of the valid choices.  #### Changes to the custom field API. -**TODO** `to_representation`, `to_internal_value`. +The `from_native(self, value)` and `to_native(self, data)` method names have been replaced with the more obviously named `to_internal_value(self, data)` and `to_representation(self, value)`. + +The `field_from_native()` and `field_to_native()` methods are removed. Previously you could use these methods if you wanted to customise the behaviour in a way that did not simply lookup the field value from the object. For example... + +    def field_to_native(self, obj, field_name): +        """A custom read-only field that returns the class name.""" +        return obj.__class__.__name__ + +Now if you need to access the entire object you'll instead need to override one or both of the following: + +* Use `get_attribute` to modify the attribute value passed to `to_representation()`. +* Use `get_value` to modify the data value passed `to_internal_value()`. + +For example: -#### Explicit `querysets` required on relational fields. +    def get_attribute(self, obj): +        # Pass the entire object through to `to_representation()`, +        # instead of the standard attribute lookup. +        return obj + +    def to_representation(self, value): +        return value.__class__.__name__ + +#### Explicit `queryset` required on relational fields. + +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 `ModelSerializer`. + +This code *would be valid* in `2.4.3`: + +    class AccountSerializer(serializers.ModelSerializer): +        organizations = serializers.SlugRelatedField(slug_field='name') + +        class Meta: +            model = Account -**TODO** +However this code *would not be valid* in `2.4.3`: + +    # Missing `queryset` +    class AccountSerializer(serializers.Serializer): +        organizations = serializers.SlugRelatedField(slug_field='name') + +        def restore_object(self, attrs, instance=None): +            # ... + +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 `ModelSerializer` classes and explicit `Serializer` classes. + +    class AccountSerializer(serializers.ModelSerializer): +        organizations = serializers.SlugRelatedField( +            slug_field='name', +            queryset=Organization.objects.all() +        ) + +        class Meta: +            model = Account + +The `queryset` argument is only ever required for writable fields, and is not required or valid for fields with `read_only=True`.  #### Optional argument to `SerializerMethodField`. -**TODO** +The argument to `SerializerMethodField` is now optional, and defaults to `get_<field_name>`. For example the following is valid: + +    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) + +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 *will raise an error*: + +    billing_details = serializers.SerializerMethodField('get_billing_details') + +#### Enforcing consistent `source` usage. + +I've see several codebases that unnecessarily include the `source` argument, setting it to the same value as the field name. This usage is redundant and confusing, making it less obvious that `source` is usually not required. + +The following usage will *now raise an error*: + +    email = serializers.EmailField(source='email') + +#### The `UniqueValidator` and `UniqueTogetherValidator` classes. + +REST framework now provides new validators that allow you to ensure field uniqueness, while still using a completely explicit `Serializer` class instead of using `ModelSerializer`. + +The `UniqueValidator` should be applied to a serializer field, and takes a single `queryset` argument. + +    from rest_framework import serializers +    from rest_framework.validators import UniqueValidator + +    class OrganizationSerializer(serializers.Serializer): +        url = serializers.HyperlinkedIdentityField(view_name='organization_detail') +        created = serializers.DateTimeField(read_only=True) +        name = serializers.CharField( +            max_length=100, +            validators=UniqueValidator(queryset=Organization.objects.all()) +        ) + +The `UniqueTogetherValidator` should be applied to a serializer, and takes a `queryset` argument and a `fields` argument which should be a list or tuple of field names. + +    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') +        )] + +#### The `UniqueForDateValidator` classes. + +REST framework also now includes explicit validator classes for validating the `unique_for_date`, `unique_for_month`, and `unique_for_year` model field constraints. These are used internally instead of calling into `Model.full_clean()`. + +These classes are documented in the [Validators](../api-guide/validators.md) section of the documentation.  ## Generic views  #### Simplification of view logic. -**TODO** +The view logic for the default method handlers has been significantly simplified, due to the new serializers API. + +#### Changes to pre/post save hooks. -#### Removal of pre/post save hooks.  +The `pre_save` and `post_save` hooks no longer exist, but are replaced with `perform_create(self, serializer)` and `perform_update(self, serializer)`. -The following method hooks no longer exist on the new, simplified, generic views: `pre_save`, `post_save`, `pre_delete`, `post_delete`. +These methods should save the object instance by calling `serializer.save()`, adding in any additional arguments as required. They may also perform any custom pre-save or post-save behavior. -If you do need custom behavior, you might choose to instead override the `.save()` method on your serializer class. For example: +For example: -    def save(self, *args, **kwargs): -        instance = super(MySerializer).save(*args, **kwarg) +    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) -		return instance -Alternatively write your view logic exlpicitly, or tie your pre/post save behavior into the model class or model manager. +The `pre_delete` and `post_delete` hooks no longer exist, and are replaced with `.perform_destroy(self, instance)`, which should delete the instance and perform any custom actions. + +    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()  #### Removal of view attributes. @@ -165,7 +757,54 @@ I would personally recommend that developers treat view instances as immutable o  #### PUT as create. -**TODO** +Allowing `PUT` as create operations is problematic, as it necessarily exposes information about the existence or non-existence 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 `404` responses. + +Both styles "`PUT` as 404" and "`PUT` 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. + +If you need to restore the previous behavior you may want to include [this `AllowPUTAsCreateMixin` class](https://gist.github.com/tomchristie/a2ace4577eff2c603b1b) as a mixin to your views. + +#### Customizing error responses. + +The generic views now raise `ValidationFailed` exception for invalid data. This exception is then dealt with by the exception handler, rather than the view returning a `400 Bad Request` response directly. + +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. + +## The metadata API + +Behavior for dealing with `OPTIONS` 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. + +This makes it far easier to use a different style for `OPTIONS` responses throughout your API, and makes it possible to create third-party metadata policies. + +## Serializers as HTML forms + +REST framework 3.0 includes templated HTML form rendering for serializers. + +This API should not yet be considered finalized, and will only be promoted to public API for the 3.1 release. + +Significant changes that you do need to be aware of include: + +* Nested HTML forms are now supported, for example, a `UserSerializer` with a nested `ProfileSerializer` will now render a nested `fieldset` when used in the browsable API. +* Nested lists of HTML forms are not yet supported, but are planned for 3.1. +* Because we now use templated HTML form generation, **the `widget` option is no longer available for serializer fields**. You can instead control the template that is used for a given field, by using the `style` dictionary. + +#### The `style` keyword argument for serializer fields. + +The `style` keyword argument can be used to pass through additional information from a serializer field, to the renderer class. In particular, the `HTMLFormRenderer` uses the `base_template` key to determine which template to render the field with. + +For example, to use a `textarea` control instead of the default `input` control, you would use the following… + +    additional_notes = serializers.CharField( +        style={'base_template': 'text_area.html'} +    ) + +Similarly, to use a radio button control instead of the default `select` control, you would use the following… + +    color_channel = serializers.ChoiceField( +        choices=['red', 'blue', 'green'], +        style={'base_template': 'radio.html'} +    ) + +This API should be considered provisional, and there may be minor alterations with the incoming 3.1 release.  ## API style @@ -195,6 +834,28 @@ The `COMPACT_JSON` setting has been added, and can be used to revert this behavi          'COMPACT_JSON': False      } +#### File fields as URLs + +The `FileField` and `ImageField` classes are now represented as URLs by default. You should ensure you set Django's [standard `MEDIA_URL` setting](https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-MEDIA_URL) appropriately, and ensure your application [serves the uploaded files](https://docs.djangoproject.com/en/dev/howto/static-files/#serving-uploaded-files-in-development). + +You can revert this behavior, and display filenames in the representation by using the `UPLOADED_FILES_USE_URL` settings key: + +    REST_FRAMEWORK = { +        'UPLOADED_FILES_USE_URL': False +    } + +You can also modify serializer fields individually, using the `use_url` argument: + +    uploaded_file = serializers.FileField(use_url=False) + +Also note that you should pass the `request` 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 `https://example.com/url_path/filename.txt`. For example: + +    context = {'request': request} +    serializer = ExampleSerializer(instance, context=context) +    return Response(serializer.data) + +If the request is omitted from the context, the returned URLs will be of the form `/url_path/filename.txt`. +  #### Throttle headers using `Retry-After`.  The custom `X-Throttle-Wait-Second` header has now been dropped in favor of the standard `Retry-After` header. You can revert this behavior if needed by writing a custom exception handler for your application. @@ -227,7 +888,7 @@ You can modify this behavior globally by using the `COERCE_DECIMAL_TO_STRING` se          'COERCE_DECIMAL_TO_STRING': False      } -Or modify it on an individual serializer field, using the `corece_to_string` keyword argument. +Or modify it on an individual serializer field, using the `coerce_to_string` keyword argument.      # Return `Decimal` instances in `serializer.data`, not strings.      amount = serializers.DecimalField( @@ -236,4 +897,27 @@ Or modify it on an individual serializer field, using the `corece_to_string` key          coerce_to_string=False      ) -The default JSON renderer will return float objects for uncoerced `Decimal` instances. This allows you to easily switch between string or float representations for decimals depending on your API design needs. +The default JSON renderer will return float objects for un-coerced `Decimal` instances. This allows you to easily switch between string or float representations for decimals depending on your API design needs. + +## Miscellaneous notes. + +* The serializer `ChoiceField` does not currently display nested choices, as was the case in 2.4. This will be address as part of 3.1. +* Due to the new templated form rendering, the 'widget' option is no longer valid. This means there's no easy way of using third party "autocomplete" widgets for rendering select inputs that contain a large number of choices. You'll either need to use a regular select or a plain text input. We may consider addressing this in 3.1 or 3.2 if there's sufficient demand. + +## What's coming next. + +3.0 is an incremental release, and there are several upcoming features that will build on the baseline improvements that it makes. + +The 3.1 release is planned to address improvements in the following components: + +* Public API for using serializers as HTML forms. +* Request parsing, mediatypes & the implementation of the browsable API. +* Introduction of a new pagination API. +* Better support for API versioning. + +The 3.2 release is planned to introduce an alternative admin-style interface to the browsable API. + +You can follow development on the GitHub site, where we use [milestones to indicate planning timescales](https://github.com/tomchristie/django-rest-framework/milestones). + +[sponsors]: http://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors +[mixins.py]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py diff --git a/docs/topics/ajax-csrf-cors.md b/docs/topics/ajax-csrf-cors.md index 97dd4710..ad88810d 100644 --- a/docs/topics/ajax-csrf-cors.md +++ b/docs/topics/ajax-csrf-cors.md @@ -10,7 +10,7 @@ If you’re building a JavaScript client to interface with your Web API, you'll  AJAX requests that are made within the same context as the API they are interacting with will typically use `SessionAuthentication`.  This ensures that once a user has logged in, any AJAX requests made can be authenticated using the same session-based authentication that is used for the rest of the website. -AJAX requests that are made on a different site from the API they are communicating with will typically need to use a non-session-based authentication scheme, such as `TokenAuthentication`.  +AJAX requests that are made on a different site from the API they are communicating with will typically need to use a non-session-based authentication scheme, such as `TokenAuthentication`.  ## CSRF protection @@ -19,7 +19,7 @@ AJAX requests that are made on a different site from the API they are communicat  To guard against these type of attacks, you need to do two things:  1. Ensure that the 'safe' HTTP operations, such as `GET`, `HEAD` and `OPTIONS` cannot be used to alter any server-side state. -2. Ensure that any 'unsafe' HTTP operations, such as `POST`, `PUT`, `PATCH` and `DELETE`, always require a valid CSRF token.  +2. Ensure that any 'unsafe' HTTP operations, such as `POST`, `PUT`, `PATCH` and `DELETE`, always require a valid CSRF token.  If you're using `SessionAuthentication` you'll need to include valid CSRF tokens for any `POST`, `PUT`, `PATCH` or `DELETE` operations. @@ -35,7 +35,7 @@ The best way to deal with CORS in REST framework is to add the required response  [cite]: http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html  [csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) -[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax +[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax  [cors]: http://www.w3.org/TR/cors/  [ottoyiu]: https://github.com/ottoyiu/  [django-cors-headers]: https://github.com/ottoyiu/django-cors-headers/ diff --git a/docs/topics/browsable-api.md b/docs/topics/browsable-api.md index ad812f4b..2879db74 100644 --- a/docs/topics/browsable-api.md +++ b/docs/topics/browsable-api.md @@ -130,34 +130,24 @@ You can override the `BrowsableAPIRenderer.get_context()` method to customise th  For more advanced customization, such as not having a Bootstrap basis or tighter integration with the rest of your site, you can simply choose not to have `api.html` extend `base.html`.  Then the page content and capabilities are entirely up to you. -#### Autocompletion +#### Handling `ChoiceField` with large numbers of items. -When a `ChoiceField` has too many items, rendering the widget containing all the options can become very slow, and cause the browsable API rendering to perform poorly.  One solution is to replace the selector by an autocomplete widget, that only loads and renders a subset of the available options as needed. +When a relationship or `ChoiceField` has too many items, rendering the widget containing all the options can become very slow, and cause the browsable API rendering to perform poorly. -There are [a variety of packages for autocomplete widgets][autocomplete-packages], such as [django-autocomplete-light][django-autocomplete-light].  To setup `django-autocomplete-light`, follow the [installation documentation][django-autocomplete-light-install], add the the following to the `api.html` template: +The simplest option in this case is to replace the select input with a standard text input. For example: -    {% block script %} -    {{ block.super }} -    {% include 'autocomplete_light/static.html' %} -    {% endblock %} - -You can now add the `autocomplete_light.ChoiceWidget` widget to the serializer field. - -    import autocomplete_light +     author = serializers.HyperlinkedRelatedField( +        queryset=User.objects.all(), +        style={'base_template': 'input.html'} +    ) -    class BookSerializer(serializers.ModelSerializer): -        author = serializers.ChoiceField( -            widget=autocomplete_light.ChoiceWidget('AuthorAutocomplete') -        ) +#### Autocomplete -        class Meta: -            model = Book - ---- +An alternative, but more complex option would be to replace the input with an autocomplete widget, that only loads and renders a subset of the available options as needed. If you need to do this you'll need to do some work to build a custom autocomplete HTML template yourself. -![Autocomplete][autocomplete-image] +There are [a variety of packages for autocomplete widgets][autocomplete-packages], such as [django-autocomplete-light][django-autocomplete-light], that you may want to refer to. Note that you will not be able to simply include these components as standard widgets, but will need to write the HTML template explicitly. This is because REST framework 3.0 no longer supports the `widget` keyword argument since it now uses templated HTML generation. -*Screenshot of the autocomplete-light widget* +Better support for autocomplete inputs is planned in future versions.  --- @@ -175,4 +165,3 @@ You can now add the `autocomplete_light.ChoiceWidget` widget to the serializer f  [autocomplete-packages]: https://www.djangopackages.com/grids/g/auto-complete/  [django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light  [django-autocomplete-light-install]: http://django-autocomplete-light.readthedocs.org/en/latest/#install -[autocomplete-image]: ../img/autocomplete.png diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md index 3400bc8f..99f4fc3c 100644 --- a/docs/topics/contributing.md +++ b/docs/topics/contributing.md @@ -10,9 +10,9 @@ There are many ways you can contribute to Django REST framework.  We'd like it t  The most important thing you can do to help push the REST framework project forward is to be actively involved wherever possible.  Code contributions are often overvalued as being the primary way to get involved in a project, we don't believe that needs to be the case. -If you use REST framework, we'd love you to be vocal about your experiences with it - you might consider writing a blog post about using REST framework, or publishing a tutorial about building a project with a particular Javascript framework.  Experiences from beginners can be particularly helpful because you'll be in the best position to assess which bits of REST framework are more difficult to understand and work with. +If you use REST framework, we'd love you to be vocal about your experiences with it - you might consider writing a blog post about using REST framework, or publishing a tutorial about building a project with a particular JavaScript framework.  Experiences from beginners can be particularly helpful because you'll be in the best position to assess which bits of REST framework are more difficult to understand and work with. -Other really great ways you can help move the community forward include helping answer questions on the [discussion group][google-group], or setting up an [email alert on StackOverflow][so-filter] so that you get notified of any new questions with the `django-rest-framework` tag. +Other really great ways you can help move the community forward include helping to answer questions on the [discussion group][google-group], or setting up an [email alert on StackOverflow][so-filter] so that you get notified of any new questions with the `django-rest-framework` tag.  When answering questions make sure to help future contributors find their way around by hyperlinking wherever possible to related threads and tickets, and include backlinks from those items if relevant. @@ -33,7 +33,7 @@ Some tips on good issue reporting:  * When describing issues try to phrase your ticket in terms of the *behavior* you think needs changing rather than the *code* you think need changing.  * Search the issue list first for related items, and make sure you're running the latest version of REST framework before reporting an issue.  * If reporting a bug, then try to include a pull request with a failing test case.  This will help us quickly identify if there is a valid issue, and make sure that it gets fixed more quickly if there is one. -* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library.  Keeping new feature requests implemented as third party libraries allows us to keep down the maintainence overhead of REST framework, so that the focus can be on continued stability, bugfixes, and great documentation. +* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library.  Keeping new feature requests implemented as third party libraries allows us to keep down the maintenance overhead of REST framework, so that the focus can be on continued stability, bugfixes, and great documentation.  * Closing an issue doesn't necessarily mean the end of a discussion.  If you believe your issue has been closed incorrectly, explain why and we'll consider if it needs to be reopened.  ## Triaging issues @@ -52,7 +52,7 @@ To start developing on Django REST framework, clone the repo:      git clone git@github.com:tomchristie/django-rest-framework.git -Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you setup your editor to automatically indicated non-conforming styles. +Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you set up your editor to automatically indicate non-conforming styles.  ## Testing @@ -71,31 +71,31 @@ To run the tests, clone the repository, and then:  Run using a more concise output style. -    ./runtests -q +    ./runtests.py -q  Run the tests using a more concise output style, no coverage, no flake8. -    ./runtests --fast +    ./runtests.py --fast  Don't run the flake8 code linting. -    ./runtests --nolint +    ./runtests.py --nolint  Only run the flake8 code linting, don't run the tests. -    ./runtests --lintonly +    ./runtests.py --lintonly  Run the tests for a given test case. -    ./runtests MyTestCase +    ./runtests.py MyTestCase  Run the tests for a given test method. -    ./runtests MyTestCase.test_this_method +    ./runtests.py MyTestCase.test_this_method  Shorter form to run the tests for a given test method. -    ./runtests test_this_method +    ./runtests.py test_this_method  Note: The test case and test method matching is fuzzy and will sometimes run other tests that contain a partial string match to the given  command line input. @@ -109,7 +109,7 @@ You can also use the excellent [tox][tox] testing tool to run the tests against  It's a good idea to make pull requests early on.  A pull request represents the start of a discussion, and doesn't necessarily need to be the final, finished submission. -It's also always best to make a new branch before starting work on a pull request.  This means that you'll be able to later switch back to working on another seperate issue without interfering with an ongoing pull requests. +It's also always best to make a new branch before starting work on a pull request.  This means that you'll be able to later switch back to working on another separate issue without interfering with an ongoing pull requests.  It's also useful to remember that if you have an outstanding pull request then pushing new commits to your GitHub repo will also automatically update the pull requests. @@ -117,7 +117,7 @@ GitHub's documentation for working on pull requests is [available here][pull-req  Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible with both Python 2 and Python 3, and that they run properly on all supported versions of Django. -Once you've made a pull request take a look at the travis build status in the GitHub interface and make sure the tests are runnning as you'd expect. +Once you've made a pull request take a look at the Travis build status in the GitHub interface and make sure the tests are running as you'd expect.  ![Travis status][travis-status] @@ -131,19 +131,19 @@ Sometimes, in order to ensure your code works on various different versions of D  The documentation for REST framework is built from the [Markdown][markdown] source files in [the docs directory][docs]. -There are many great markdown editors that make working with the documentation really easy.  The [Mou editor for Mac][mou] is one such editor that comes highly recommended. +There are many great Markdown editors that make working with the documentation really easy.  The [Mou editor for Mac][mou] is one such editor that comes highly recommended.  ## Building the documentation -To build the documentation, simply run the `mkdocs.py` script. +To build the documentation, install MkDocs with `pip install mkdocs` and then run the following command. -    ./mkdocs.py +    mkdocs build -This will build the html output into the `html` directory. +This will build the documentation into the `site` directory. -You can build the documentation and open a preview in a browser window by using the `-p` flag. +You can build the documentation and open a preview in a browser window by using the `serve` command. -    ./mkdocs.py -p +    mkdocs serve  ## Language style @@ -152,8 +152,7 @@ Documentation should be in American English.  The tone of the documentation is v  Some other tips:  * Keep paragraphs reasonably short. -* Use double spacing after the end of sentences. -* Don't use the abbreviations such as 'e.g.' but instead use long form, such as 'For example'. +* Don't use abbreviations such as 'e.g.' but instead use the long form, such as 'For example'.  ## Markdown style @@ -186,7 +185,7 @@ If you are hyperlinking to another REST framework document, you should use a rel      [authentication]: ../api-guide/authentication.md -Linking in this style means you'll be able to click the hyperlink in your markdown editor to open the referenced document.  When the documentation is built, these links will be converted into regular links to HTML pages. +Linking in this style means you'll be able to click the hyperlink in your Markdown editor to open the referenced document.  When the documentation is built, these links will be converted into regular links to HTML pages.  ##### 3. Notes @@ -198,19 +197,6 @@ If you want to draw attention to a note or warning, use a pair of enclosing line      --- -# Third party packages - -New features to REST framework are generally recommended to be implemented as third party libraries that are developed outside of the core framework.  Ideally third party libraries should be properly documented and packaged, and made available on PyPI. - -## Getting started - -If you have some functionality that you would like to implement as a third party package it's worth contacting the [discussion group][google-group] 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. - -We recommend the [`django-reusable-app`][django-reusable-app] template as a good resource for getting up and running with implementing a third party Django package. - -## Linking to your package - -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.  [cite]: http://www.w3.org/People/Berners-Lee/FAQ.html  [code-of-conduct]: https://www.djangoproject.com/conduct/ @@ -224,4 +210,3 @@ Once your package is decently documented and available on PyPI open a pull reque  [markdown]: http://daringfireball.net/projects/markdown/basics  [docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs  [mou]: http://mouapp.com/ -[django-reusable-app]: https://github.com/dabapps/django-reusable-app diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md index e20f9712..d65e251f 100644 --- a/docs/topics/documenting-your-api.md +++ b/docs/topics/documenting-your-api.md @@ -54,7 +54,7 @@ The title that is used in the browsable API is generated from the view class nam  For example, the view `UserListView`, will be named `User List` when presented in the browsable API. -When working with viewsets, an appropriate suffix is appended to each generated view.  For example, the view set `UserViewSet` will generate views named `User List` and `User Instance`.  +When working with viewsets, an appropriate suffix is appended to each generated view.  For example, the view set `UserViewSet` will generate views named `User List` and `User Instance`.  #### Setting the description @@ -65,9 +65,9 @@ If the python `markdown` library is installed, then [markdown syntax][markdown]      class AccountListView(views.APIView):          """          Returns a list of all **active** accounts in the system. -         +          For more details on how accounts are activated please [see here][ref]. -         +          [ref]: http://example.com/activating-accounts          """ @@ -84,7 +84,7 @@ You can modify the response behavior to `OPTIONS` requests by overriding the `me      def metadata(self, request):          """          Don't include the view description in OPTIONS responses. -        """  +        """          data = super(ExampleView, self).metadata(request)          data.pop('description')          return data diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index 7d1f6d0e..91ead751 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -43,20 +43,20 @@ We've now blazed way past all our goals, with a staggering £30,000 (~$50,000),  Our platinum sponsors have each made a hugely substantial contribution to the future development of Django REST framework, and I simply can't thank them enough.  <ul class="sponsor diamond"> -<li><a href="https://www.eventbrite.com/" rel="nofollow" style="background-image:url(../img/sponsors/0-eventbrite.png);">Eventbrite</a></li> +<li><a href="https://www.eventbrite.com/" rel="nofollow" style="background-image:url(../../img/sponsors//0-eventbrite.png);">Eventbrite</a></li>  </ul>  <ul class="sponsor platinum"> -<li><a href="https://www.divio.ch/" rel="nofollow" style="background-image:url(../img/sponsors/1-divio.png);">Divio</a></li> -<li><a href="http://company.onlulu.com/en/" rel="nofollow" style="background-image:url(../img/sponsors/1-lulu.png);">Lulu</a></li> -<li><a href="https://p.ota.to/" rel="nofollow" style="background-image:url(../img/sponsors/1-potato.png);">Potato</a></li> -<li><a href="http://www.wiredrive.com/" rel="nofollow" style="background-image:url(../img/sponsors/1-wiredrive.png);">Wiredrive</a></li> -<li><a href="http://www.cyaninc.com/" rel="nofollow" style="background-image:url(../img/sponsors/1-cyan.png);">Cyan</a></li> -<li><a href="https://www.runscope.com/" rel="nofollow" style="background-image:url(../img/sponsors/1-runscope.png);">Runscope</a></li> -<li><a href="http://simpleenergy.com/" rel="nofollow" style="background-image:url(../img/sponsors/1-simple-energy.png);">Simple Energy</a></li> -<li><a href="http://vokalinteractive.com/" rel="nofollow" style="background-image:url(../img/sponsors/1-vokal_interactive.png);">VOKAL Interactive</a></li> -<li><a href="http://www.purplebit.com/" rel="nofollow" style="background-image:url(../img/sponsors/1-purplebit.png);">Purple Bit</a></li> -<li><a href="http://www.kuwaitnet.net/" rel="nofollow" style="background-image:url(../img/sponsors/1-kuwaitnet.png);">KuwaitNET</a></li> +<li><a href="https://www.divio.ch/" rel="nofollow" style="background-image:url(../../img/sponsors//1-divio.png);">Divio</a></li> +<li><a href="http://company.onlulu.com/en/" rel="nofollow" style="background-image:url(../../img/sponsors//1-lulu.png);">Lulu</a></li> +<li><a href="https://p.ota.to/" rel="nofollow" style="background-image:url(../../img/sponsors//1-potato.png);">Potato</a></li> +<li><a href="http://www.wiredrive.com/" rel="nofollow" style="background-image:url(../../img/sponsors//1-wiredrive.png);">Wiredrive</a></li> +<li><a href="http://www.cyaninc.com/" rel="nofollow" style="background-image:url(../../img/sponsors//1-cyan.png);">Cyan</a></li> +<li><a href="https://www.runscope.com/" rel="nofollow" style="background-image:url(../../img/sponsors//1-runscope.png);">Runscope</a></li> +<li><a href="http://simpleenergy.com/" rel="nofollow" style="background-image:url(../../img/sponsors//1-simple-energy.png);">Simple Energy</a></li> +<li><a href="http://vokalinteractive.com/" rel="nofollow" style="background-image:url(../../img/sponsors//1-vokal_interactive.png);">VOKAL Interactive</a></li> +<li><a href="http://www.purplebit.com/" rel="nofollow" style="background-image:url(../../img/sponsors//1-purplebit.png);">Purple Bit</a></li> +<li><a href="http://www.kuwaitnet.net/" rel="nofollow" style="background-image:url(../../img/sponsors//1-kuwaitnet.png);">KuwaitNET</a></li>  </ul>  <div style="clear: both"></div> @@ -68,31 +68,31 @@ Our platinum sponsors have each made a hugely substantial contribution to the fu  Our gold sponsors include companies large and small. Many thanks for their significant funding of the project and their commitment to sustainable open-source development.  <ul class="sponsor gold"> -<li><a href="https://laterpay.net/" rel="nofollow" style="background-image:url(../img/sponsors/2-laterpay.png);">LaterPay</a></li> -<li><a href="https://www.schubergphilis.com/" rel="nofollow" style="background-image:url(../img/sponsors/2-schuberg_philis.png);">Schuberg Philis</a></li> -<li><a href="http://prorenata.se/" rel="nofollow" style="background-image:url(../img/sponsors/2-prorenata.png);">ProReNata AB</a></li> -<li><a href="https://www.sgawebsites.com/" rel="nofollow" style="background-image:url(../img/sponsors/2-sga.png);">SGA Websites</a></li> -<li><a href="http://www.sirono.com/" rel="nofollow" style="background-image:url(../img/sponsors/2-sirono.png);">Sirono</a></li> -<li><a href="http://www.vinta.com.br/" rel="nofollow" style="background-image:url(../img/sponsors/2-vinta.png);">Vinta Software Studio</a></li> -<li><a href="http://www.rapasso.nl/index.php/en" rel="nofollow" style="background-image:url(../img/sponsors/2-rapasso.png);">Rapasso</a></li> -<li><a href="https://mirusresearch.com/" rel="nofollow" style="background-image:url(../img/sponsors/2-mirus_research.png);">Mirus Research</a></li> -<li><a href="http://hipolabs.com" rel="nofollow" style="background-image:url(../img/sponsors/2-hipo.png);">Hipo</a></li> -<li><a href="http://www.byte.nl" rel="nofollow" style="background-image:url(../img/sponsors/2-byte.png);">Byte</a></li> -<li><a href="http://lightningkite.com/" rel="nofollow" style="background-image:url(../img/sponsors/2-lightning_kite.png);">Lightning Kite</a></li> -<li><a href="https://opbeat.com/" rel="nofollow" style="background-image:url(../img/sponsors/2-opbeat.png);">Opbeat</a></li> -<li><a href="https://koordinates.com" rel="nofollow" style="background-image:url(../img/sponsors/2-koordinates.png);">Koordinates</a></li> -<li><a href="http://pulsecode.ca" rel="nofollow" style="background-image:url(../img/sponsors/2-pulsecode.png);">Pulsecode Inc.</a></li> -<li><a href="http://singinghorsestudio.com" rel="nofollow" style="background-image:url(../img/sponsors/2-singing-horse.png);">Singing Horse Studio Ltd.</a></li> -<li><a href="https://www.heroku.com/" rel="nofollow" style="background-image:url(../img/sponsors/2-heroku.png);">Heroku</a></li> -<li><a href="https://www.galileo-press.de/" rel="nofollow" style="background-image:url(../img/sponsors/2-galileo_press.png);">Galileo Press</a></li> -<li><a href="http://www.securitycompass.com/" rel="nofollow" style="background-image:url(../img/sponsors/2-security_compass.png);">Security Compass</a></li> -<li><a href="https://www.djangoproject.com/foundation/" rel="nofollow" style="background-image:url(../img/sponsors/2-django.png);">Django Software Foundation</a></li> -<li><a href="http://www.hipflaskapp.com" rel="nofollow" style="background-image:url(../img/sponsors/2-hipflask.png);">Hipflask</a></li> -<li><a href="http://www.crate.io/" rel="nofollow" style="background-image:url(../img/sponsors/2-crate.png);">Crate</a></li> -<li><a href="http://crypticocorp.com/" rel="nofollow" style="background-image:url(../img/sponsors/2-cryptico.png);">Cryptico Corp</a></li> -<li><a href="http://www.nexthub.com/" rel="nofollow" style="background-image:url(../img/sponsors/2-nexthub.png);">NextHub</a></li> -<li><a href="https://www.compile.com/" rel="nofollow" style="background-image:url(../img/sponsors/2-compile.png);">Compile</a></li> -<li><a href="http://wusawork.org" rel="nofollow" style="background-image:url(../img/sponsors/2-wusawork.png);">WusaWork</a></li> +<li><a href="https://laterpay.net/" rel="nofollow" style="background-image:url(../../img/sponsors//2-laterpay.png);">LaterPay</a></li> +<li><a href="https://www.schubergphilis.com/" rel="nofollow" style="background-image:url(../../img/sponsors//2-schuberg_philis.png);">Schuberg Philis</a></li> +<li><a href="http://prorenata.se/" rel="nofollow" style="background-image:url(../../img/sponsors//2-prorenata.png);">ProReNata AB</a></li> +<li><a href="https://www.sgawebsites.com/" rel="nofollow" style="background-image:url(../../img/sponsors//2-sga.png);">SGA Websites</a></li> +<li><a href="http://www.sirono.com/" rel="nofollow" style="background-image:url(../../img/sponsors//2-sirono.png);">Sirono</a></li> +<li><a href="http://www.vinta.com.br/" rel="nofollow" style="background-image:url(../../img/sponsors//2-vinta.png);">Vinta Software Studio</a></li> +<li><a href="http://www.rapasso.nl/index.php/en" rel="nofollow" style="background-image:url(../../img/sponsors//2-rapasso.png);">Rapasso</a></li> +<li><a href="https://mirusresearch.com/" rel="nofollow" style="background-image:url(../../img/sponsors//2-mirus_research.png);">Mirus Research</a></li> +<li><a href="http://hipolabs.com" rel="nofollow" style="background-image:url(../../img/sponsors//2-hipo.png);">Hipo</a></li> +<li><a href="http://www.byte.nl" rel="nofollow" style="background-image:url(../../img/sponsors//2-byte.png);">Byte</a></li> +<li><a href="http://lightningkite.com/" rel="nofollow" style="background-image:url(../../img/sponsors//2-lightning_kite.png);">Lightning Kite</a></li> +<li><a href="https://opbeat.com/" rel="nofollow" style="background-image:url(../../img/sponsors//2-opbeat.png);">Opbeat</a></li> +<li><a href="https://koordinates.com" rel="nofollow" style="background-image:url(../../img/sponsors//2-koordinates.png);">Koordinates</a></li> +<li><a href="http://pulsecode.ca" rel="nofollow" style="background-image:url(../../img/sponsors//2-pulsecode.png);">Pulsecode Inc.</a></li> +<li><a href="http://singinghorsestudio.com" rel="nofollow" style="background-image:url(../../img/sponsors//2-singing-horse.png);">Singing Horse Studio Ltd.</a></li> +<li><a href="https://www.heroku.com/" rel="nofollow" style="background-image:url(../../img/sponsors//2-heroku.png);">Heroku</a></li> +<li><a href="https://www.galileo-press.de/" rel="nofollow" style="background-image:url(../../img/sponsors//2-galileo_press.png);">Galileo Press</a></li> +<li><a href="http://www.securitycompass.com/" rel="nofollow" style="background-image:url(../../img/sponsors//2-security_compass.png);">Security Compass</a></li> +<li><a href="https://www.djangoproject.com/foundation/" rel="nofollow" style="background-image:url(../../img/sponsors//2-django.png);">Django Software Foundation</a></li> +<li><a href="http://www.hipflaskapp.com" rel="nofollow" style="background-image:url(../../img/sponsors//2-hipflask.png);">Hipflask</a></li> +<li><a href="http://www.crate.io/" rel="nofollow" style="background-image:url(../../img/sponsors//2-crate.png);">Crate</a></li> +<li><a href="http://crypticocorp.com/" rel="nofollow" style="background-image:url(../../img/sponsors//2-cryptico.png);">Cryptico Corp</a></li> +<li><a href="http://www.nexthub.com/" rel="nofollow" style="background-image:url(../../img/sponsors//2-nexthub.png);">NextHub</a></li> +<li><a href="https://www.compile.com/" rel="nofollow" style="background-image:url(../../img/sponsors//2-compile.png);">Compile</a></li> +<li><a href="http://wusawork.org" rel="nofollow" style="background-image:url(../../img/sponsors//2-wusawork.png);">WusaWork</a></li>  <li><a href="http://envisionlinux.org/blog" rel="nofollow">Envision Linux</a></li>  </ul> @@ -105,41 +105,41 @@ Our gold sponsors include companies large and small. Many thanks for their signi  The serious financial contribution that our silver sponsors have made is very much appreciated. I'd like to say a particular thank you to individuals who have choosen to privately support the project at this level.  <ul class="sponsor silver"> -<li><a href="http://www.imtapps.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-imt_computer_services.png);">IMT Computer Services</a></li> -<li><a href="http://wildfish.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-wildfish.png);">Wildfish</a></li> -<li><a href="http://www.thermondo.de/" rel="nofollow" style="background-image:url(../img/sponsors/3-thermondo-gmbh.png);">Thermondo GmbH</a></li> -<li><a href="http://providenz.fr/" rel="nofollow" style="background-image:url(../img/sponsors/3-providenz.png);">Providenz</a></li> -<li><a href="https://www.alwaysdata.com" rel="nofollow" style="background-image:url(../img/sponsors/3-alwaysdata.png);">alwaysdata.com</a></li> -<li><a href="http://www.triggeredmessaging.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-triggered_messaging.png);">Triggered Messaging</a></li> -<li><a href="https://www.ipushpull.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-ipushpull.png);">PushPull Technology Ltd</a></li> -<li><a href="http://www.transcode.de/" rel="nofollow" style="background-image:url(../img/sponsors/3-transcode.png);">Transcode</a></li> -<li><a href="https://garfo.io/" rel="nofollow" style="background-image:url(../img/sponsors/3-garfo.png);">Garfo</a></li> -<li><a href="https://goshippo.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-shippo.png);">Shippo</a></li> -<li><a href="http://www.gizmag.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-gizmag.png);">Gizmag</a></li> -<li><a href="http://www.tivix.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-tivix.png);">Tivix</a></li> -<li><a href="http://www.safaribooksonline.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-safari.png);">Safari</a></li> -<li><a href="http://brightloop.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-brightloop.png);">Bright Loop</a></li> -<li><a href="http://www.aba-systems.com.au/" rel="nofollow" style="background-image:url(../img/sponsors/3-aba.png);">ABA Systems</a></li> -<li><a href="http://beefarm.ru/" rel="nofollow" style="background-image:url(../img/sponsors/3-beefarm.png);">beefarm.ru</a></li> -<li><a href="http://www.vzzual.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-vzzual.png);">Vzzual.com</a></li> -<li><a href="http://infinite-code.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-infinite_code.png);">Infinite Code</a></li> -<li><a href="http://crosswordtracker.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-crosswordtracker.png);">Crossword Tracker</a></li> -<li><a href="https://www.pkgfarm.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-pkgfarm.png);">PkgFarm</a></li> -<li><a href="http://life.tl/" rel="nofollow" style="background-image:url(../img/sponsors/3-life_the_game.png);">Life. The Game.</a></li> -<li><a href="http://blimp.io/" rel="nofollow" style="background-image:url(../img/sponsors/3-blimp.png);">Blimp</a></li> -<li><a href="http://pathwright.com" rel="nofollow" style="background-image:url(../img/sponsors/3-pathwright.png);">Pathwright</a></li> -<li><a href="http://fluxility.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-fluxility.png);">Fluxility</a></li> -<li><a href="http://teonite.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-teonite.png);">Teonite</a></li> -<li><a href="http://trackmaven.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-trackmaven.png);">TrackMaven</a></li> -<li><a href="http://www.phurba.net/" rel="nofollow" style="background-image:url(../img/sponsors/3-phurba.png);">Phurba</a></li> -<li><a href="http://www.nephila.co.uk/" rel="nofollow" style="background-image:url(../img/sponsors/3-nephila.png);">Nephila</a></li> -<li><a href="http://www.aditium.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-aditium.png);">Aditium</a></li> -<li><a href="http://www.eyesopen.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-openeye.png);">OpenEye Scientific Software</a></li> -<li><a href="https://holvi.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-holvi.png);">Holvi</a></li> -<li><a href="http://cantemo.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-cantemo.gif);">Cantemo</a></li> -<li><a href="https://www.makespace.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-makespace.png);">MakeSpace</a></li> -<li><a href="https://www.ax-semantics.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-ax_semantics.png);">AX Semantics</a></li> -<li><a href="http://istrategylabs.com/" rel="nofollow" style="background-image:url(../img/sponsors/3-isl.png);">ISL</a></li> +<li><a href="http://www.imtapps.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-imt_computer_services.png);">IMT Computer Services</a></li> +<li><a href="http://wildfish.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-wildfish.png);">Wildfish</a></li> +<li><a href="http://www.thermondo.de/" rel="nofollow" style="background-image:url(../../img/sponsors//3-thermondo-gmbh.png);">Thermondo GmbH</a></li> +<li><a href="http://providenz.fr/" rel="nofollow" style="background-image:url(../../img/sponsors//3-providenz.png);">Providenz</a></li> +<li><a href="https://www.alwaysdata.com" rel="nofollow" style="background-image:url(../../img/sponsors//3-alwaysdata.png);">alwaysdata.com</a></li> +<li><a href="http://www.triggeredmessaging.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-triggered_messaging.png);">Triggered Messaging</a></li> +<li><a href="https://www.ipushpull.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-ipushpull.png);">PushPull Technology Ltd</a></li> +<li><a href="http://www.transcode.de/" rel="nofollow" style="background-image:url(../../img/sponsors//3-transcode.png);">Transcode</a></li> +<li><a href="https://garfo.io/" rel="nofollow" style="background-image:url(../../img/sponsors//3-garfo.png);">Garfo</a></li> +<li><a href="https://goshippo.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-shippo.png);">Shippo</a></li> +<li><a href="http://www.gizmag.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-gizmag.png);">Gizmag</a></li> +<li><a href="http://www.tivix.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-tivix.png);">Tivix</a></li> +<li><a href="http://www.safaribooksonline.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-safari.png);">Safari</a></li> +<li><a href="http://brightloop.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-brightloop.png);">Bright Loop</a></li> +<li><a href="http://www.aba-systems.com.au/" rel="nofollow" style="background-image:url(../../img/sponsors//3-aba.png);">ABA Systems</a></li> +<li><a href="http://beefarm.ru/" rel="nofollow" style="background-image:url(../../img/sponsors//3-beefarm.png);">beefarm.ru</a></li> +<li><a href="http://www.vzzual.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-vzzual.png);">Vzzual.com</a></li> +<li><a href="http://infinite-code.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-infinite_code.png);">Infinite Code</a></li> +<li><a href="http://crosswordtracker.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-crosswordtracker.png);">Crossword Tracker</a></li> +<li><a href="https://www.pkgfarm.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-pkgfarm.png);">PkgFarm</a></li> +<li><a href="http://life.tl/" rel="nofollow" style="background-image:url(../../img/sponsors//3-life_the_game.png);">Life. The Game.</a></li> +<li><a href="http://blimp.io/" rel="nofollow" style="background-image:url(../../img/sponsors//3-blimp.png);">Blimp</a></li> +<li><a href="http://pathwright.com" rel="nofollow" style="background-image:url(../../img/sponsors//3-pathwright.png);">Pathwright</a></li> +<li><a href="http://fluxility.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-fluxility.png);">Fluxility</a></li> +<li><a href="http://teonite.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-teonite.png);">Teonite</a></li> +<li><a href="http://trackmaven.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-trackmaven.png);">TrackMaven</a></li> +<li><a href="http://www.phurba.net/" rel="nofollow" style="background-image:url(../../img/sponsors//3-phurba.png);">Phurba</a></li> +<li><a href="http://www.nephila.co.uk/" rel="nofollow" style="background-image:url(../../img/sponsors//3-nephila.png);">Nephila</a></li> +<li><a href="http://www.aditium.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-aditium.png);">Aditium</a></li> +<li><a href="http://www.eyesopen.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-openeye.png);">OpenEye Scientific Software</a></li> +<li><a href="https://holvi.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-holvi.png);">Holvi</a></li> +<li><a href="http://cantemo.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-cantemo.gif);">Cantemo</a></li> +<li><a href="https://www.makespace.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-makespace.png);">MakeSpace</a></li> +<li><a href="https://www.ax-semantics.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-ax_semantics.png);">AX Semantics</a></li> +<li><a href="http://istrategylabs.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-isl.png);">ISL</a></li>  </ul>  <div style="clear: both; padding-bottom: 40px;"></div> @@ -160,4 +160,4 @@ The following individuals made a significant financial contribution to the devel  ### Supporters -There were also almost 300 further individuals choosing to help fund the project at other levels or choosing to give anonymously. Again, thank you, thank you, thank you!
\ No newline at end of file +There were also almost 300 further individuals choosing to help fund the project at other levels or choosing to give anonymously. Again, thank you, thank you, thank you! diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index d758ae6a..53187589 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -10,7 +10,7 @@ Minor version numbers (0.0.x) are used for changes that are API compatible.  You  Medium version numbers (0.x.0) may include API changes, in line with the [deprecation policy][deprecation-policy].  You should read the release notes carefully before upgrading between medium point releases. -Major version numbers (x.0.0) are reserved for substantial project milestones.  No major point releases are currently planned. +Major version numbers (x.0.0) are reserved for substantial project milestones.  ## Deprecation policy @@ -40,21 +40,47 @@ You can determine your currently installed version using `pip freeze`:  ## 2.4.x series +### 2.4.4 + +**Date**: [3rd November 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.4+Release%22+). + +* **Security fix**: Escape URLs when replacing `format=` query parameter, as used in dropdown on `GET` button in browsable API to allow explicit selection of JSON vs HTML output. +* Maintain ordering of URLs in API root view for `DefaultRouter`. +* Fix `follow=True` in `APIRequestFactory` +* Resolve issue with invalid `read_only=True`, `required=True` fields being automatically generated by `ModelSerializer` in some cases. +* Resolve issue with `OPTIONS` requests returning incorrect information for views using `get_serializer_class` to dynamically determine serializer based on request method.  + +### 2.4.3 + +**Date**: [19th September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.3+Release%22+). + +* Support translatable view docstrings being displayed in the browsable API. +* Support [encoded `filename*`][rfc-6266] in raw file uploads with `FileUploadParser`. +* Allow routers to support viewsets that don't include any list routes or that don't include any detail routes. +* Don't render an empty login control in browsable API if `login` view is not included. +* CSRF exemption performed in `.as_view()` to prevent accidental omission if overriding `.dispatch()`. +* Login on browsable API now displays validation errors. +* Bugfix: Fix migration in `authtoken` application. +* Bugfix: Allow selection of integer keys in nested choices. +* Bugfix: Return `None` instead of `'None'` in `CharField` with `allow_none=True`. +* Bugfix: Ensure custom model fields map to equivelent serializer fields more reliably. +* Bugfix: `DjangoFilterBackend` no longer quietly changes queryset ordering. +  ### 2.4.2 -**Date**: 3rd September 2014 +**Date**: [3rd September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.2+Release%22+).  * Bugfix: Fix broken pagination for 2.4.x series.  ### 2.4.1 -**Date**: 1st September 2014 +**Date**: [1st September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.1+Release%22+).  * Bugfix: Fix broken login template for browsable API.  ### 2.4.0 -**Date**: 29th August 2014 +**Date**: [29th August 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.0+Release%22+).  **Django version requirements**: The lowest supported version of Django is now 1.4.2. @@ -133,7 +159,7 @@ You can determine your currently installed version using `pip freeze`:  * Added `write_only_fields` option to `ModelSerializer` classes.  * JSON renderer now deals with objects that implement a dict-like interface.  * Fix compatiblity with newer versions of `django-oauth-plus`. -* Bugfix: Refine behavior that calls model manager `all()` across nested serializer relationships, preventing erronous behavior with some non-ORM objects, and preventing unneccessary queryset re-evaluations. +* Bugfix: Refine behavior that calls model manager `all()` across nested serializer relationships, preventing erronous behavior with some non-ORM objects, and preventing unnecessary queryset re-evaluations.  * Bugfix: Allow defaults on BooleanFields to be properly honored when values are not supplied.  * Bugfix: Prevent double-escaping of non-latin1 URL query params when appending `format=json` params. @@ -416,7 +442,7 @@ The security vulnerabilities only affect APIs which use the `XMLParser` class, b  * Bugfix: Validation errors instead of exceptions when related fields receive incorrect types.  * Bugfix: Handle ObjectDoesNotExist exception when serializing null reverse one-to-one -**Note**: Prior to 2.1.16, The Decimals would render in JSON using floating point if `simplejson` was installed, but otherwise render using string notation.  Now that use of `simplejson` has been deprecated, Decimals will consistently render using string notation.  See [#582] for more details. +**Note**: Prior to 2.1.16, The Decimals would render in JSON using floating point if `simplejson` was installed, but otherwise render using string notation.  Now that use of `simplejson` has been deprecated, Decimals will consistently render using string notation.  See [ticket 582](ticket-582) for more details.  ### 2.1.15 @@ -588,122 +614,7 @@ This change will not affect user code, so long as it's following the recommended  * **Fix all of the things.**  (Well, almost.)  * For more information please see the [2.0 announcement][announcement]. ---- - -## 0.4.x series - -### 0.4.0 - -* Supports Django 1.5. -* Fixes issues with 'HEAD' method. -* Allow views to specify template used by TemplateRenderer -* More consistent error responses -* Some serializer fixes -* Fix internet explorer ajax behavior -* Minor xml and yaml fixes -* Improve setup (e.g. use staticfiles, not the defunct ADMIN_MEDIA_PREFIX) -* Sensible absolute URL generation, not using hacky set_script_prefix - ---- - -## 0.3.x series - -### 0.3.3 - -* Added DjangoModelPermissions class to support `django.contrib.auth` style permissions. -* Use `staticfiles` for css files. -  - Easier to override.  Won't conflict with customized admin styles (e.g. grappelli) -* Templates are now nicely namespaced. -  - Allows easier overriding. -* Drop implied 'pk' filter if last arg in urlconf is unnamed. -  - Too magical.  Explicit is better than implicit. -* Saner template variable auto-escaping. -* Tidier setup.py -* Updated for URLObject 2.0 -* Bugfixes: -  - Bug with PerUserThrottling when user contains unicode chars. - -### 0.3.2 - -* Bugfixes: -  * Fix 403 for POST and PUT from the UI with UserLoggedInAuthentication (#115) -  * serialize_model method in serializer.py may cause wrong value (#73) -  * Fix Error when clicking OPTIONS button (#146) -  * And many other fixes -* Remove short status codes -  - Zen of Python: "There should be one-- and preferably only one --obvious way to do it." -* get_name, get_description become methods on the view - makes them overridable. -* Improved model mixin API - Hooks for build_query, get_instance_data, get_model, get_queryset, get_ordering - -### 0.3.1 - -* [not documented] - -### 0.3.0 - -* JSONP Support -* Bugfixes, including support for latest markdown release - ---- - -## 0.2.x series - -### 0.2.4 - -* Fix broken IsAdminUser permission. -* OPTIONS support. -* XMLParser. -* Drop mentions of Blog, BitBucket. - -### 0.2.3 - -* Fix some throttling bugs. -* ``X-Throttle`` header on throttling. -* Support for nesting resources on related models. - -### 0.2.2 - -* Throttling support complete. - -### 0.2.1 - -* Couple of simple bugfixes over 0.2.0 - -### 0.2.0 - -* Big refactoring changes since 0.1.0, ask on the discussion group if anything isn't clear. -  The public API has been massively cleaned up.  Expect it to be fairly stable from here on in. - -* ``Resource`` becomes decoupled into ``View`` and ``Resource``, your views should now inherit from ``View``, not ``Resource``. - -* The handler functions on views ``.get() .put() .post()`` etc, no longer have the ``content`` and ``auth`` args. -  Use ``self.CONTENT`` inside a view to access the deserialized, validated content. -  Use ``self.user`` inside a view to access the authenticated user. - -* ``allowed_methods`` and ``anon_allowed_methods`` are now defunct.  if a method is defined, it's available. -  The ``permissions`` attribute on a ``View`` is now used to provide generic permissions checking. -  Use permission classes such as ``FullAnonAccess``, ``IsAuthenticated`` or ``IsUserOrIsAnonReadOnly`` to set the permissions. - -* The ``authenticators`` class becomes ``authentication``.  Class names change to ``Authentication``. - -* The ``emitters`` class becomes ``renderers``.  Class names change to ``Renderers``. - -* ``ResponseException`` becomes ``ErrorResponse``. - -* The mixin classes have been nicely refactored, the basic mixins are now ``RequestMixin``, ``ResponseMixin``, ``AuthMixin``, and ``ResourceMixin`` -  You can reuse these mixin classes individually without using the ``View`` class. - ---- - -## 0.1.x series - -### 0.1.1 - -* Final build before pulling in all the refactoring changes for 0.2, in case anyone needs to hang on to 0.1. - -### 0.1.0 - -* Initial release. +For older release notes, [please see the GitHub repo](old-release-notes).  [cite]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html  [deprecation-policy]: #deprecation-policy @@ -716,4 +627,6 @@ This change will not affect user code, so long as it's following the recommended  [staticfiles13]: https://docs.djangoproject.com/en/1.3/howto/static-files/#with-a-template-tag  [2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion  [announcement]: rest-framework-2-announcement.md -[#582]: https://github.com/tomchristie/django-rest-framework/issues/582 +[ticket-582]: https://github.com/tomchristie/django-rest-framework/issues/582 +[rfc-6266]: http://tools.ietf.org/html/rfc6266#section-4.3 +[old-release-notes]: https://github.com/tomchristie/django-rest-framework/blob/2.4.4/docs/topics/release-notes.md#04x-series diff --git a/docs/topics/rest-framework-2-announcement.md b/docs/topics/rest-framework-2-announcement.md index f1060d90..a7746932 100644 --- a/docs/topics/rest-framework-2-announcement.md +++ b/docs/topics/rest-framework-2-announcement.md @@ -8,7 +8,7 @@ What it is, and why you should care.  --- -**Announcement:** REST framework 2 released - Tue 30th Oct 2012  +**Announcement:** REST framework 2 released - Tue 30th Oct 2012  --- @@ -37,7 +37,7 @@ REST framework 2 includes a totally re-worked serialization engine, that was ini  * A declarative serialization API, that mirrors Django's `Forms`/`ModelForms` API.  * Structural concerns are decoupled from encoding concerns.  * Able to support rendering and parsing to many formats, including both machine-readable representations and HTML forms. -* Validation that can be mapped to obvious and comprehensive error responses.  +* Validation that can be mapped to obvious and comprehensive error responses.  * Serializers that support both nested, flat, and partially-nested representations.  * Relationships that can be expressed as primary keys, hyperlinks, slug fields, and other custom representations. diff --git a/docs/topics/rest-hypermedia-hateoas.md b/docs/topics/rest-hypermedia-hateoas.md index 3d700488..7e6d2408 100644 --- a/docs/topics/rest-hypermedia-hateoas.md +++ b/docs/topics/rest-hypermedia-hateoas.md @@ -1,19 +1,19 @@  # REST, Hypermedia & HATEOAS -> You keep using that word "REST".  I do not think it means what you think it means. +> You keep using that word "REST". I do not think it means what you think it means.  >  > — Mike Amundsen, [REST fest 2012 keynote][cite]. -First off, the disclaimer.  The name "Django REST framework" was chosen simply to sure the project would be easily found by developers.  Throughout the documentation we try to use the more simple and technically correct terminology of "Web APIs". +First off, the disclaimer.  The name "Django REST framework" was decided back in early 2011 and was chosen simply to sure the project would be easily found by developers. Throughout the documentation we try to use the more simple and technically correct terminology of "Web APIs". -If you are serious about designing a Hypermedia APIs, you should look to resources outside of this documentation to help inform your design choices. +If you are serious about designing a Hypermedia API, you should look to resources outside of this documentation to help inform your design choices.  The following fall into the "required reading" category.  * Roy Fielding's dissertation - [Architectural Styles and  the Design of Network-based Software Architectures][dissertation].  * Roy Fielding's "[REST APIs must be hypertext-driven][hypertext-driven]" blog post. -* Leonard Richardson & Sam Ruby's [RESTful Web Services][restful-web-services]. +* Leonard Richardson & Mike Amundsen's [RESTful Web APIs][restful-web-apis].  * Mike Amundsen's [Building Hypermedia APIs with HTML5 and Node][building-hypermedia-apis].  * Steve Klabnik's [Designing Hypermedia APIs][designing-hypermedia-apis].  * The [Richardson Maturity Model][maturitymodel]. @@ -32,12 +32,12 @@ REST framework also includes [serialization] and [parser]/[renderer] components  ## What REST framework doesn't provide. -What REST framework doesn't do is give you is machine readable hypermedia formats such as [HAL][hal], [Collection+JSON][collection], [JSON API][json-api] or HTML [microformats] by default, or the ability to auto-magically create fully HATEOAS style APIs that include hypermedia-based form descriptions and semantically labelled hyperlinks.  Doing so would involve making opinionated choices about API design that should really remain outside of the framework's scope. +What REST framework doesn't do is give you is machine readable hypermedia formats such as [HAL][hal], [Collection+JSON][collection], [JSON API][json-api] or HTML [microformats] by default, or the ability to auto-magically create fully HATEOAS style APIs that include hypermedia-based form descriptions and semantically labelled hyperlinks. Doing so would involve making opinionated choices about API design that should really remain outside of the framework's scope.  [cite]: http://vimeo.com/channels/restfest/page:2  [dissertation]: http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm  [hypertext-driven]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven -[restful-web-services]: http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260 +[restful-web-apis]: http://restfulwebapis.org/  [building-hypermedia-apis]: http://www.amazon.com/Building-Hypermedia-APIs-HTML5-Node/dp/1449306578  [designing-hypermedia-apis]: http://designinghypermediaapis.com/  [restisover]: http://blog.steveklabnik.com/posts/2012-02-23-rest-is-over diff --git a/docs/topics/third-party-resources.md b/docs/topics/third-party-resources.md index 1ca91742..ffb490af 100644 --- a/docs/topics/third-party-resources.md +++ b/docs/topics/third-party-resources.md @@ -1,91 +1,322 @@  # Third Party Resources -Django REST Framework has a growing community of developers, packages, and resources. +## About Third Party Packages + +Third Party Packages allow developers to share code that extends the functionality of Django REST framework, in order to support additional use-cases. + +We **support**, **encourage** and **strongly favour** the creation of Third Party Packages to encapsulate new behaviour rather than adding additional functionality directly to Django REST Framework. + +We aim to make creating Third Party Packages as easy as possible, whilst keeping the **simplicity** of the core API and ensuring that **maintenance** of the main project remains under control. If a Third Party Package proves popular it is relatively easy to move it into the main project; removing features is much more problematic. + +If you have an idea for a new feature please consider how it may be packaged as a Third Party Package. We're always happy to dicuss ideas on the [Mailing List][discussion-group]. + +## How to create a Third Party Package + +### Creating your package + +You can use [this cookiecutter template][cookiecutter] for creating reusable Django REST Framework packages quickly. Cookiecutter creates projects from project templates. While optional, this cookiecutter template includes best practices from Django REST framework and other packages, as well as a Travis CI configuration, Tox configuration, and a sane setup.py for easy PyPI registration/distribution. + +Note: Let us know if you have an alternate cookiecuter package so we can also link to it. + +#### Running the initial cookiecutter command + +To run the initial cookiecutter command, you'll first need to install the Python `cookiecutter` package. + +    $ pip install cookiecutter + +Once `cookiecutter` is installed just run the following to create a new project. + +    $ cookiecutter gh:jpadilla/cookiecutter-django-rest-framework + +You'll be prompted for some questions, answer them, then it'll create your Python package in the current working directory based on those values. + +    full_name (default is "Your full name here")? Johnny Appleseed +    email (default is "you@example.com")? jappleseed@example.com +    github_username (default is "yourname")? jappleseed +    pypi_project_name (default is "dj-package")? djangorestframework-custom-auth +    repo_name (default is "dj-package")? django-rest-framework-custom-auth +    app_name (default is "djpackage")? custom_auth +    project_short_description (default is "Your project description goes here")? +    year (default is "2014")? +    version (default is "0.1.0")? + +#### Getting it onto GitHub + +To put your project up on GitHub, you'll need a repository for it to live in. You can create a new repository [here][new-repo]. If you need help, check out the [Create A Repo][create-a-repo] article on GitHub. + + +#### Adding to Travis CI + +We recommend using [Travis CI][travis-ci], a hosted continuous integration service which integrates well with GitHub and is free for public repositories. + +To get started with Travis CI, [sign in][travis-ci] with your GitHub account. Once you're signed in, go to your [profile page][travis-profile] and enable the service hook for the repository you want. + +If you use the cookiecutter template, your project will already contain a `.travis.yml` file which Travis CI will use to build your project and run tests.  By default, builds are triggered everytime you push to your repository or create Pull Request. + +#### Uploading to PyPI + +Once you've got at least a prototype working and tests running, you should publish it on PyPI to allow others to install it via `pip`. + +You must [register][pypi-register] an account before publishing to PyPI. + +To register your package on PyPI run the following command. + +    $ python setup.py register + +If this is the first time publishing to PyPI, you'll be prompted to login. + +Note: Before publishing you'll need to make sure you have the latest pip that supports `wheel` as well as install the `wheel` package. + +    $ pip install --upgrade pip +    $ pip install wheel + +After this, every time you want to release a new version on PyPI just run the following command. + +    $ python setup.py publish +    You probably want to also tag the version now: +        git tag -a {0} -m 'version 0.1.0' +        git push --tags + +After releasing a new version to PyPI, it's always a good idea to tag the version and make available as a GitHub Release. + +We recommend to follow [Semantic Versioning][semver] for your package's versions. + +### Development + +#### Version requirements + +The cookiecutter template assumes a set of supported versions will be provided for Python and Django. Make sure you correctly update your requirements, docs, `tox.ini`, `.travis.yml`, and `setup.py` to match the set of versions you wish to support. + +#### Tests + +The cookiecutter template includes a `runtests.py` which uses the `pytest` package as a test runner. + +Before running, you'll need to install a couple test requirements. + +    $ pip install -r requirements-test.txt -Check out a grid detailing all the packages and ecosystem around Django REST Framework at [Django Packages](https://www.djangopackages.com/grids/g/django-rest-framework/). +Once requirements installed, you can run `runtests.py`. -To submit new content, [open an issue](https://github.com/tomchristie/django-rest-framework/issues/new) or [create a pull request](https://github.com/tomchristie/django-rest-framework/). +    $ ./runtests.py -## Libraries and Extensions +Run using a more concise output style. + +    $ ./runtests.py -q + +Run the tests using a more concise output style, no coverage, no flake8. + +    $ ./runtests.py --fast + +Don't run the flake8 code linting. + +    $ ./runtests.py --nolint + +Only run the flake8 code linting, don't run the tests. + +    $ ./runtests.py --lintonly + +Run the tests for a given test case. + +    $ ./runtests.py MyTestCase + +Run the tests for a given test method. + +    $ ./runtests.py MyTestCase.test_this_method + +Shorter form to run the tests for a given test method. + +    $ ./runtests.py test_this_method + +To run your tests against multiple versions of Python as different versions of requirements such as Django we recommend using `tox`. [Tox][tox-docs] is a generic virtualenv management and test command line tool. + +First, install `tox` globally. + +    $ pip install tox + +To run `tox`, just simply run: + +    $ tox + +To run a particular `tox` environment: + +    $ tox -e envlist + +`envlist` is a comma-separated value to that specifies the environments to run tests against. To view a list of all possible test environments, run: + +    $ tox -l + +#### Version compatibility + +Sometimes, in order to ensure your code works on various different versions of Django, Python or third party libraries, you'll need to run slightly different code depending on the environment. Any code that branches in this way should be isolated into a `compat.py` module, and should provide a single common interface that the rest of the codebase can use. + +Check out Django REST framework's [compat.py][drf-compat] for an example. + +### Once your package is available + +Once your package is decently documented and available on PyPI, you might want share it with others that might find it useful. + +#### Adding to the Django REST framework grid + +We suggest adding your package to the [REST Framework][rest-framework-grid] grid on Django Packages. + +#### Adding to the Django REST framework docs + +Create a [Pull Request][drf-create-pr] or [Issue][drf-create-issue] on GitHub, and we'll add a link to it from the main REST framework documentation. You can add your package under **Third party packages** of the API Guide section that best applies, like [Authentication][authentication] or [Permissions][permissions]. You can also link your package under the [Third Party Resources][third-party-resources] section. + +#### Announce on the discussion group. + +You can also let others know about your package through the [discussion group][discussion-group]. + +## Existing Third Party Packages + +Django REST Framework has a growing community of developers, packages, and resources. + +Check out a grid detailing all the packages and ecosystem around Django REST Framework at [Django Packages][rest-framework-grid]. + +To submit new content, [open an issue][drf-create-issue] or [create a pull request][drf-create-pr].  ### Authentication -* [djangorestframework-digestauth](https://github.com/juanriaza/django-rest-framework-digestauth) - Provides Digest Access Authentication support. -* [django-oauth-toolkit](https://github.com/evonove/django-oauth-toolkit) - Provides OAuth 2.0 support. -* [doac](https://github.com/Rediker-Software/doac) - Provides OAuth 2.0 support. -* [djangorestframework-jwt](https://github.com/GetBlimp/django-rest-framework-jwt) - Provides JSON Web Token Authentication support. -* [hawkrest](https://github.com/kumar303/hawkrest) - Provides Hawk HTTP Authorization. -* [djangorestframework-httpsignature](https://github.com/etoccalino/django-rest-framework-httpsignature) - Provides an easy to use HTTP Signature Authentication mechanism. +* [djangorestframework-digestauth][djangorestframework-digestauth] - Provides Digest Access Authentication support. +* [django-oauth-toolkit][django-oauth-toolkit] - Provides OAuth 2.0 support. +* [doac][doac] - Provides OAuth 2.0 support. +* [djangorestframework-jwt][djangorestframework-jwt] - Provides JSON Web Token Authentication support. +* [hawkrest][hawkrest] - Provides Hawk HTTP Authorization. +* [djangorestframework-httpsignature][djangorestframework-httpsignature] - Provides an easy to use HTTP Signature Authentication mechanism. +* [djoser][djoser] - Provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation.  ### Permissions -* [drf-any-permissions](https://github.com/kevin-brown/drf-any-permissions) - Provides alternative permission handling. -* [djangorestframework-composed-permissions](https://github.com/niwibe/djangorestframework-composed-permissions) - Provides a simple way to define complex permissions. -* [rest_condition](https://github.com/caxap/rest_condition) - Another extension for building complex permissions in a simple and convenient way. +* [drf-any-permissions][drf-any-permissions] - Provides alternative permission handling. +* [djangorestframework-composed-permissions][djangorestframework-composed-permissions] - Provides a simple way to define complex permissions. +* [rest_condition][rest-condition] - Another extension for building complex permissions in a simple and convenient way.  ### Serializers -* [django-rest-framework-mongoengine](https://github.com/umutbozkurt/django-rest-framework-mongoengine) - Serializer class that supports using MongoDB as the storage layer for Django REST framework. -* [djangorestframework-gis](https://github.com/djangonauts/django-rest-framework-gis) - Geographic add-ons -* [djangorestframework-hstore](https://github.com/djangonauts/django-rest-framework-hstore) - Serializer class to support django-hstore DictionaryField model field and its schema-mode feature. +* [django-rest-framework-mongoengine][django-rest-framework-mongoengine] - Serializer class that supports using MongoDB as the storage layer for Django REST framework. +* [djangorestframework-gis][djangorestframework-gis] - Geographic add-ons +* [djangorestframework-hstore][djangorestframework-hstore] - Serializer class to support django-hstore DictionaryField model field and its schema-mode feature.  ### Serializer fields -* [drf-compound-fields](https://github.com/estebistec/drf-compound-fields) - Provides "compound" serializer fields, such as lists of simple values. -* [django-extra-fields](https://github.com/Hipo/drf-extra-fields) - Provides extra serializer fields. +* [drf-compound-fields][drf-compound-fields] - Provides "compound" serializer fields, such as lists of simple values. +* [django-extra-fields][django-extra-fields] - Provides extra serializer fields.  ### Views -* [djangorestframework-bulk](https://github.com/miki725/django-rest-framework-bulk) - Implements generic view mixins as well as some common concrete generic views to allow to apply bulk operations via API requests. +* [djangorestframework-bulk][djangorestframework-bulk] - Implements generic view mixins as well as some common concrete generic views to allow to apply bulk operations via API requests.  ### Routers -* [drf-nested-routers](https://github.com/alanjds/drf-nested-routers) - Provides routers and relationship fields for working with nested resources. -* [wq.db.rest](http://wq.io/docs/about-rest) - Provides an admin-style model registration API with reasonable default URLs and viewsets. +* [drf-nested-routers][drf-nested-routers] - Provides routers and relationship fields for working with nested resources. +* [wq.db.rest][wq.db.rest] - Provides an admin-style model registration API with reasonable default URLs and viewsets.  ### Parsers -* [djangorestframework-msgpack](https://github.com/juanriaza/django-rest-framework-msgpack) - Provides MessagePack renderer and parser support. -* [djangorestframework-camel-case](https://github.com/vbabiy/djangorestframework-camel-case) - Provides camel case JSON renderers and parsers. +* [djangorestframework-msgpack][djangorestframework-msgpack] - Provides MessagePack renderer and parser support. +* [djangorestframework-camel-case][djangorestframework-camel-case] - Provides camel case JSON renderers and parsers.  ### Renderers -* [djangorestframework-csv](https://github.com/mjumbewu/django-rest-framework-csv) - Provides CSV renderer support. -* [drf_ujson](https://github.com/gizmag/drf-ujson-renderer) - Implements JSON rendering using the UJSON package. -* [Django REST Pandas](https://github.com/wq/django-rest-pandas) - Pandas DataFrame-powered renderers including Excel, CSV, and SVG formats. +* [djangorestframework-csv][djangorestframework-csv] - Provides CSV renderer support. +* [drf_ujson][drf_ujson] - Implements JSON rendering using the UJSON package. +* [rest-pandas][rest-pandas] - Pandas DataFrame-powered renderers including Excel, CSV, and SVG formats.  ### Filtering -* [djangorestframework-chain](https://github.com/philipn/django-rest-framework-chain) - Allows arbitrary chaining of both relations and lookup filters. +* [djangorestframework-chain][djangorestframework-chain] - Allows arbitrary chaining of both relations and lookup filters.  ### Misc -* [djangorestrelationalhyperlink](https://github.com/fredkingham/django_rest_model_hyperlink_serializers_project) - A hyperlinked serialiser that can can be used to alter relationships via hyperlinks, but otherwise like a hyperlink model serializer. -* [django-rest-swagger](https://github.com/marcgibbons/django-rest-swagger) - An API documentation generator for Swagger UI. -* [django-rest-framework-proxy ](https://github.com/eofs/django-rest-framework-proxy) - Proxy to redirect incoming request to another API server. -* [gaiarestframework](https://github.com/AppsFuel/gaiarestframework) - Utils for django-rest-framewok -* [drf-extensions](https://github.com/chibisov/drf-extensions) - A collection of custom extensions -* [ember-data-django-rest-adapter](https://github.com/toranb/ember-data-django-rest-adapter) - An ember-data adapter +* [djangorestrelationalhyperlink][djangorestrelationalhyperlink] - A hyperlinked serialiser that can can be used to alter relationships via hyperlinks, but otherwise like a hyperlink model serializer. +* [django-rest-swagger][django-rest-swagger] - An API documentation generator for Swagger UI. +* [django-rest-framework-proxy][django-rest-framework-proxy] - Proxy to redirect incoming request to another API server. +* [gaiarestframework][gaiarestframework] - Utils for django-rest-framewok +* [drf-extensions][drf-extensions] - A collection of custom extensions +* [ember-data-django-rest-adapter][ember-data-django-rest-adapter] - An ember-data adapter + +## Other Resources + +### Tutorials + +* [Beginner's Guide to the Django Rest Framework][beginners-guide-to-the-django-rest-framework] +* [Getting Started with Django Rest Framework and AngularJS][getting-started-with-django-rest-framework-and-angularjs] +* [End to end web app with Django-Rest-Framework & AngularJS][end-to-end-web-app-with-django-rest-framework-angularjs] +* [Start Your API - django-rest-framework part 1][start-your-api-django-rest-framework-part-1] +* [Permissions & Authentication - django-rest-framework part 2][permissions-authentication-django-rest-framework-part-2] +* [ViewSets and Routers - django-rest-framework part 3][viewsets-and-routers-django-rest-framework-part-3] +* [Django Rest Framework User Endpoint][django-rest-framework-user-endpoint] +* [Check credentials using Django Rest Framework][check-credentials-using-django-rest-framework] -## Tutorials +### Videos -* [Beginner's Guide to the Django Rest Framework](http://code.tutsplus.com/tutorials/beginners-guide-to-the-django-rest-framework--cms-19786) -* [Getting Started with Django Rest Framework and AngularJS](http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html) -* [End to end web app with Django-Rest-Framework & AngularJS](http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework) -* [Start Your API - django-rest-framework part 1](https://godjango.com/41-start-your-api-django-rest-framework-part-1/) -* [Permissions & Authentication - django-rest-framework part 2](https://godjango.com/43-permissions-authentication-django-rest-framework-part-2/) -* [ViewSets and Routers - django-rest-framework part 3](https://godjango.com/45-viewsets-and-routers-django-rest-framework-part-3/) -* [Django Rest Framework User Endpoint](http://richardtier.com/2014/02/25/django-rest-framework-user-endpoint/) -* [Check credentials using Django Rest Framework](http://richardtier.com/2014/03/06/110/) +* [Ember and Django Part 1 (Video)][ember-and-django-part 1-video] +* [Django Rest Framework Part 1 (Video)][django-rest-framework-part-1-video] +* [Pyowa July 2013 - Django Rest Framework (Video)][pyowa-july-2013-django-rest-framework-video] +* [django-rest-framework and angularjs (Video)][django-rest-framework-and-angularjs-video] -## Videos +### Articles -* [Ember and Django Part 1 (Video)](http://www.neckbeardrepublic.com/screencasts/ember-and-django-part-1) -* [Django Rest Framework Part 1 (Video)](http://www.neckbeardrepublic.com/screencasts/django-rest-framework-part-1) -* [Pyowa July 2013 - Django Rest Framework (Video)](http://www.youtube.com/watch?v=E1ZrehVxpBo) -* [django-rest-framework and angularjs (Video)](http://www.youtube.com/watch?v=Q8FRBGTJ020) +* [Web API performance: profiling Django REST framework][web-api-performance-profiling-django-rest-framework] +* [API Development with Django and Django REST Framework][api-development-with-django-and-django-rest-framework] -## Articles -* [Web API performance: profiling Django REST framework](http://dabapps.com/blog/api-performance-profiling-django-rest-framework/) -* [API Development with Django and Django REST Framework](https://bnotions.com/api-development-with-django-and-django-rest-framework/) +[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework +[new-repo]: https://github.com/new +[create-a-repo]: https://help.github.com/articles/create-a-repo/ +[travis-ci]: https://travis-ci.org +[travis-profile]: https://travis-ci.org/profile +[pypi-register]: https://pypi.python.org/pypi?%3Aaction=register_form +[semver]: http://semver.org/ +[tox-docs]: https://tox.readthedocs.org/en/latest/ +[drf-compat]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/compat.py +[rest-framework-grid]: https://www.djangopackages.com/grids/g/django-rest-framework/ +[drf-create-pr]: https://github.com/tomchristie/django-rest-framework/compare +[drf-create-issue]: https://github.com/tomchristie/django-rest-framework/issues/new +[authentication]: ../api-guide/authentication.md +[permissions]: ../api-guide/permissions.md +[discussion-group]: https://groups.google.com/forum/#!forum/django-rest-framework +[djangorestframework-digestauth]: https://github.com/juanriaza/django-rest-framework-digestauth +[django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit +[doac]: https://github.com/Rediker-Software/doac +[djangorestframework-jwt]: https://github.com/GetBlimp/django-rest-framework-jwt +[hawkrest]: https://github.com/kumar303/hawkrest +[djangorestframework-httpsignature]: https://github.com/etoccalino/django-rest-framework-httpsignature +[djoser]: https://github.com/sunscrapers/djoser +[drf-any-permissions]: https://github.com/kevin-brown/drf-any-permissions +[djangorestframework-composed-permissions]: https://github.com/niwibe/djangorestframework-composed-permissions +[rest-condition]: https://github.com/caxap/rest_condition +[django-rest-framework-mongoengine]: https://github.com/umutbozkurt/django-rest-framework-mongoengine +[djangorestframework-gis]: https://github.com/djangonauts/django-rest-framework-gis +[djangorestframework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore +[drf-compound-fields]: https://github.com/estebistec/drf-compound-fields +[django-extra-fields]: https://github.com/Hipo/drf-extra-fields +[djangorestframework-bulk]: https://github.com/miki725/django-rest-framework-bulk +[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers +[wq.db.rest]: http://wq.io/docs/about-rest +[djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack +[djangorestframework-camel-case]: https://github.com/vbabiy/djangorestframework-camel-case +[djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv +[drf_ujson]: https://github.com/gizmag/drf-ujson-renderer +[rest-pandas]: https://github.com/wq/django-rest-pandas +[djangorestframework-chain]: https://github.com/philipn/django-rest-framework-chain +[djangorestrelationalhyperlink]: https://github.com/fredkingham/django_rest_model_hyperlink_serializers_project +[django-rest-swagger]: https://github.com/marcgibbons/django-rest-swagger +[django-rest-framework-proxy]: https://github.com/eofs/django-rest-framework-proxy +[gaiarestframework]: https://github.com/AppsFuel/gaiarestframework +[drf-extensions]: https://github.com/chibisov/drf-extensions +[ember-data-django-rest-adapter]: https://github.com/toranb/ember-data-django-rest-adapter +[beginners-guide-to-the-django-rest-framework]: http://code.tutsplus.com/tutorials/beginners-guide-to-the-django-rest-framework--cms-19786 +[getting-started-with-django-rest-framework-and-angularjs]: http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html +[end-to-end-web-app-with-django-rest-framework-angularjs]: http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework +[start-your-api-django-rest-framework-part-1]: https://godjango.com/41-start-your-api-django-rest-framework-part-1/ +[permissions-authentication-django-rest-framework-part-2]: https://godjango.com/43-permissions-authentication-django-rest-framework-part-2/ +[viewsets-and-routers-django-rest-framework-part-3]: https://godjango.com/45-viewsets-and-routers-django-rest-framework-part-3/ +[django-rest-framework-user-endpoint]: http://richardtier.com/2014/02/25/django-rest-framework-user-endpoint/ +[check-credentials-using-django-rest-framework]: http://richardtier.com/2014/03/06/110/ +[ember-and-django-part 1-video]: http://www.neckbeardrepublic.com/screencasts/ember-and-django-part-1 +[django-rest-framework-part-1-video]: http://www.neckbeardrepublic.com/screencasts/django-rest-framework-part-1 +[pyowa-july-2013-django-rest-framework-video]: http://www.youtube.com/watch?v=e1zrehvxpbo +[django-rest-framework-and-angularjs-video]: http://www.youtube.com/watch?v=q8frbgtj020 +[web-api-performance-profiling-django-rest-framework]: http://dabapps.com/blog/api-performance-profiling-django-rest-framework/ +[api-development-with-django-and-django-rest-framework]: https://bnotions.com/api-development-with-django-and-django-rest-framework/ diff --git a/docs/topics/writable-nested-serializers.md b/docs/topics/writable-nested-serializers.md index 66ea7815..ed614bd2 100644 --- a/docs/topics/writable-nested-serializers.md +++ b/docs/topics/writable-nested-serializers.md @@ -6,9 +6,9 @@  Although flat data structures serve to properly delineate between the individual entities in your service, there are cases where it may be more appropriate or convenient to use nested data structures. -Nested data structures are easy enough to work with if they're read-only - simply nest your serializer classes and you're good to go.  However, there are a few more subtleties to using writable nested serializers, due to the dependancies between the various model instances, and the need to save or delete multiple instances in a single action. +Nested data structures are easy enough to work with if they're read-only - simply nest your serializer classes and you're good to go.  However, there are a few more subtleties to using writable nested serializers, due to the dependencies between the various model instances, and the need to save or delete multiple instances in a single action. -## One-to-many data structures  +## One-to-many data structures  *Example of a **read-only** nested serializer.  Nothing complex to worry about here.* @@ -16,10 +16,10 @@ Nested data structures are easy enough to work with if they're read-only - simpl  	    class Meta:  	        model = ToDoItem  	        fields = ('text', 'is_completed') -	 +  	class ToDoListSerializer(serializers.ModelSerializer):  	    items = ToDoItemSerializer(many=True, read_only=True) -	 +  	    class Meta:  	        model = ToDoList  	        fields = ('title', 'items') @@ -31,7 +31,7 @@ Some example output from our serializer.          'items': {              {'text': 'Compile playlist', 'is_completed': True},              {'text': 'Send invites', 'is_completed': False}, -            {'text': 'Clean house', 'is_completed': False}             +            {'text': 'Clean house', 'is_completed': False}          }      } @@ -44,4 +44,4 @@ Let's take a look at updating our nested one-to-many data structure.  ### Making PATCH requests -[cite]: http://jsonapi.org/format/#url-based-json-api
\ No newline at end of file +[cite]: http://jsonapi.org/format/#url-based-json-api diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 96214f5b..a3c19858 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -41,20 +41,7 @@ Once that's done we can create an app that we'll use to create a simple Web API.      python manage.py startapp snippets -The simplest way to get up and running will probably be to use an `sqlite3` database for the tutorial.  Edit the `tutorial/settings.py` file, and set the default database `"ENGINE"` to `"sqlite3"`, and `"NAME"` to `"tmp.db"`. - -    DATABASES = { -        'default': { -            'ENGINE': 'django.db.backends.sqlite3', -            'NAME': 'tmp.db', -            'USER': '', -            'PASSWORD': '', -            'HOST': '', -            'PORT': '', -        } -    } - -We'll also need to add our new `snippets` app and the `rest_framework` app to `INSTALLED_APPS`. +We'll need to add our new `snippets` app and the `rest_framework` app to `INSTALLED_APPS`. Let's edit the `tutorial/settings.py` file:      INSTALLED_APPS = (          ... @@ -64,15 +51,15 @@ We'll also need to add our new `snippets` app and the `rest_framework` app to `I  We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet app's URLs. -    urlpatterns = patterns('', +    urlpatterns = [          url(r'^', include('snippets.urls')), -    ) +    ]  Okay, we're ready to roll.  ## Creating a model to work with -For the purposes of this tutorial we're going to start by creating a simple `Snippet` model that is used to store code snippets.  Go ahead and edit the `snippets` app's `models.py` file.  Note: Good programming practices include comments.  Although you will find them in our repository version of this tutorial code, we have omitted them here to focus on the code itself. +For the purposes of this tutorial we're going to start by creating a simple `Snippet` model that is used to store code snippets.  Go ahead and edit the `snippets/models.py` file.  Note: Good programming practices include comments.  Although you will find them in our repository version of this tutorial code, we have omitted them here to focus on the code itself.      from django.db import models      from pygments.lexers import get_all_lexers @@ -98,9 +85,10 @@ For the purposes of this tutorial we're going to start by creating a simple `Sni          class Meta:              ordering = ('created',) -Don't forget to sync the database for the first time. +We'll also need to create an initial migration for our snippet model, and sync the database for the first time. -    python manage.py syncdb +    python manage.py makemigrations snippets +    python manage.py migrate  ## Creating a Serializer class @@ -112,42 +100,41 @@ The first thing we need to get started on our Web API is to provide a way of ser      class SnippetSerializer(serializers.Serializer): -        pk = serializers.Field()  # Note: `Field` is an untyped read-only field. +        pk = serializers.IntegerField(read_only=True)          title = serializers.CharField(required=False,                                        max_length=100) -        code = serializers.CharField(widget=widgets.Textarea, -                                     max_length=100000) +        code = serializers.CharField(style={'type': 'textarea'})          linenos = serializers.BooleanField(required=False)          language = serializers.ChoiceField(choices=LANGUAGE_CHOICES,                                             default='python')          style = serializers.ChoiceField(choices=STYLE_CHOICES,                                          default='friendly') -        def restore_object(self, attrs, instance=None): +        def create(self, validated_attrs):              """ -            Create or update a new snippet instance, given a dictionary -            of deserialized field values. +            Create and return a new `Snippet` instance, given the validated data. +            """ +            return Snippet.objects.create(**validated_attrs) -            Note that if we don't define this method, then deserializing -            data will simply return a dictionary of items. +        def update(self, instance, validated_attrs): +            """ +            Update and return an existing `Snippet` instance, given the validated data.              """ -            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 +            instance.title = validated_attrs.get('title', instance.title) +            instance.code = validated_attrs.get('code', instance.code) +            instance.linenos = validated_attrs.get('linenos', instance.linenos) +            instance.language = validated_attrs.get('language', instance.language) +            instance.style = validated_attrs.get('style', instance.style) +            instance.save() +            return instance -            # Create new instance -            return Snippet(**attrs) +The first part of the serializer class defines the fields that get serialized/deserialized.  The `create()` and `update()` methods define how fully fledged instances are created or modified when calling `serializer.save()` -The first part of the serializer class defines the fields that get serialized/deserialized.  The `restore_object` method defines how fully fledged instances get created when deserializing data. +A serializer class is very similar to a Django `Form` class, and includes similar validation flags on the various fields, such as `required`, `max_length` and `default`. -Notice that we can also use various attributes that would typically be used on form fields, such as `widget=widgets.Textarea`.  These can be used to control how the serializer should render when displayed as an HTML form.  This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial. +The field flags can also control how the serializer should be displayed in certain circumstances, such as when rendering to HTML. The `style={'type': 'textarea'}` flag above is equivelent to using `widget=widgets.Textarea` on a Django `Form` class. This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial. -We can actually also save ourselves some time by using the `ModelSerializer` class, as we'll see later, but for now we'll keep our serializer definition explicit.   +We can actually also save ourselves some time by using the `ModelSerializer` class, as we'll see later, but for now we'll keep our serializer definition explicit.  ## Working with Serializers @@ -219,6 +206,24 @@ Open the file `snippets/serializers.py` again, and edit the `SnippetSerializer`              model = Snippet              fields = ('id', 'title', 'code', 'linenos', 'language', 'style') +One nice property that serializers have is that you can inspect all the fields in a serializer instance, by printing it's representation. Open the Django shell with `python manange.py shell`, then try the following: + +    >>> from snippets.serializers import SnippetSerializer +    >>> serializer = SnippetSerializer() +    >>> print repr(serializer)  # In python 3 use `print(repr(serializer))` +    SnippetSerializer(): +        id = IntegerField(label='ID', read_only=True) +        title = CharField(allow_blank=True, max_length=100, required=False) +        code = CharField(style={'type': 'textarea'}) +        linenos = BooleanField(required=False) +        language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')... +        style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')... + +It's important to remember that `ModelSerializer` classes don't do anything particularly magical, they are simply a shortcut for creating serializer classes: + +* An automatically determined set of fields. +* Simple default implementations for the `create()` and `update()` methods. +  ## Writing regular Django views using our Serializer  Let's see how we can write some API views using our new Serializer class. @@ -297,11 +302,12 @@ We'll also need a view which corresponds to an individual snippet, and can be us  Finally we need to wire these views up.  Create the `snippets/urls.py` file:      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), +    ]  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 `json`, 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. diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index e70bbbfc..f377c712 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -5,10 +5,10 @@ Let's introduce a couple of essential building blocks.  ## Request objects -REST framework introduces a `Request` object that extends the regular `HttpRequest`, and provides more flexible request parsing.  The core functionality of the `Request` object is the `request.DATA` attribute, which is similar to `request.POST`, but more useful for working with Web APIs. +REST framework introduces a `Request` object that extends the regular `HttpRequest`, and provides more flexible request parsing.  The core functionality of the `Request` object is the `request.data` attribute, which is similar to `request.POST`, but more useful for working with Web APIs.      request.POST  # Only handles form data.  Only works for 'POST' method. -    request.DATA  # Handles arbitrary data.  Works for 'POST', 'PUT' and 'PATCH' methods. +    request.data  # Handles arbitrary data.  Works for 'POST', 'PUT' and 'PATCH' methods.  ## Response objects @@ -29,7 +29,7 @@ REST framework provides two wrappers you can use to write API views.  These wrappers provide a few bits of functionality such as making sure you receive `Request` instances in your view, and adding context to `Response` objects so that content negotiation can be performed. -The wrappers also provide behaviour such as returning `405 Method Not Allowed` responses when appropriate, and handling any `ParseError` exception that occurs when accessing `request.DATA` with malformed input. +The wrappers also provide behaviour such as returning `405 Method Not Allowed` responses when appropriate, and handling any `ParseError` exception that occurs when accessing `request.data` with malformed input.  ## Pulling it all together @@ -55,7 +55,7 @@ We don't need our `JSONResponse` class in `views.py` anymore, so go ahead and de              return Response(serializer.data)          elif request.method == 'POST': -            serializer = SnippetSerializer(data=request.DATA) +            serializer = SnippetSerializer(data=request.data)              if serializer.is_valid():                  serializer.save()                  return Response(serializer.data, status=status.HTTP_201_CREATED) @@ -80,7 +80,7 @@ Here is the view for an individual snippet, in the `views.py` module.              return Response(serializer.data)          elif request.method == 'PUT': -            serializer = SnippetSerializer(snippet, data=request.DATA) +            serializer = SnippetSerializer(snippet, data=request.data)              if serializer.is_valid():                  serializer.save()                  return Response(serializer.data) @@ -92,7 +92,7 @@ Here is the view for an individual snippet, in the `views.py` module.  This should all feel very familiar - it is not a lot different from working with regular Django views. -Notice that we're no longer explicitly tying our requests or responses to a given content type.  `request.DATA` can handle incoming `json` requests, but it can also handle `yaml` and other formats.  Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us. +Notice that we're no longer explicitly tying our requests or responses to a given content type.  `request.data` can handle incoming `json` requests, but it can also handle `yaml` and other formats.  Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us.  ## Adding optional format suffixes to our URLs @@ -110,11 +110,12 @@ Now update the `urls.py` file slightly, to append a set of `format_suffix_patter      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) diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index e04072ca..0a9ea3f1 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -24,7 +24,7 @@ We'll start by rewriting the root view as a class based view.  All this involves              return Response(serializer.data)          def post(self, request, format=None): -            serializer = SnippetSerializer(data=request.DATA) +            serializer = SnippetSerializer(data=request.data)              if serializer.is_valid():                  serializer.save()                  return Response(serializer.data, status=status.HTTP_201_CREATED) @@ -49,7 +49,7 @@ So far, so good.  It looks pretty similar to the previous case, but we've got be          def put(self, request, pk, format=None):              snippet = self.get_object(pk) -            serializer = SnippetSerializer(snippet, data=request.DATA) +            serializer = SnippetSerializer(snippet, data=request.data)              if serializer.is_valid():                  serializer.save()                  return Response(serializer.data) @@ -68,10 +68,10 @@ We'll also need to refactor our `urls.py` slightly now we're using class based v      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) diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 74ad9a55..4e4edeea 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -44,7 +44,9 @@ When that's all done we'll need to update our database tables.  Normally we'd create a database migration in order to do that, but for the purposes of this tutorial, let's just delete the database and start again.      rm tmp.db -    python manage.py syncdb +    rm -r snippets/migrations +    python manage.py makemigrations snippets +    python manage.py migrate  You might also want to create a few different users, to use for testing the API.  The quickest way to do this will be with the `createsuperuser` command. @@ -92,24 +94,26 @@ Finally we need to add those views into the API, by referencing them from the UR  Right now, if we created a code snippet, there'd be no way of associating the user that created the snippet, with the snippet instance.  The user isn't sent as part of the serialized representation, but is instead a property of the incoming request. -The way we deal with that is by overriding a `.pre_save()` method on our snippet views, that allows us to handle any information that is implicit in the incoming request or requested URL. +The way we deal with that is by overriding a `.perform_create()` method on our snippet views, that allows us to modify how the instance save is managed, and handle any information that is implicit in the incoming request or requested URL. -On **both** the `SnippetList` and `SnippetDetail` view classes, add the following method: +On the `SnippetList` view class, add the following method: -    def pre_save(self, obj): -        obj.owner = self.request.user +    def perform_create(self, serializer): +        serializer.save(owner=self.request.user) + +The `create()` method of our serializer will now be passed an additional `'owner'` field, along with the validated data from the request.  ## Updating our serializer  Now that snippets are associated with the user that created them, let's update our `SnippetSerializer` to reflect that.  Add the following field to the serializer definition in `serializers.py`: -    owner = serializers.Field(source='owner.username') +    owner = serializers.ReadOnlyField(source='owner.username')  **Note**: Make sure you also add `'owner',` to the list of fields in the inner `Meta` class.  This field is doing something quite interesting.  The `source` argument controls which attribute is used to populate a field, and can point at any attribute on the serialized instance.  It can also take the dotted notation shown above, in which case it will traverse the given attributes, in a similar way as it is used with Django's template language. -The field we've added is the untyped `Field` class, in contrast to the other typed fields, such as `CharField`, `BooleanField` etc...  The untyped `Field` is always read-only, and will be used for serialized representations, but will not be used for updating model instances when they are deserialized. +The field we've added is the untyped `ReadOnlyField` class, in contrast to the other typed fields, such as `CharField`, `BooleanField` etc...  The untyped `ReadOnlyField` is always read-only, and will be used for serialized representations, but will not be used for updating model instances when they are deserialized. We could have also used `CharField(read_only=True)` here.  ## Adding required permissions to views @@ -137,10 +141,10 @@ Add the following import at the top of the file:  And, at the end of the file, add a pattern to include the login and logout views for the browsable API. -    urlpatterns += patterns('', +    urlpatterns += [          url(r'^api-auth/', include('rest_framework.urls',                                     namespace='rest_framework')), -    ) +    ]  The `r'^api-auth/'` part of pattern can actually be whatever URL you want to use.  The only restriction is that the included urls must use the `'rest_framework'` namespace. diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index 9c61fe3d..50552616 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -6,7 +6,6 @@ At the moment relationships within our API are represented by using primary keys  Right now we have endpoints for 'snippets' and 'users', but we don't have a single entry point to our API.  To create one, we'll use a regular function-based view and the `@api_view` decorator we introduced earlier. In your `snippets/views.py` add: -    from rest_framework import renderers      from rest_framework.decorators import api_view      from rest_framework.response import Response      from rest_framework.reverse import reverse @@ -108,8 +107,8 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p  After adding all those names into our URLconf, our final `snippets/urls.py` file should look something like this:      # 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'), @@ -125,13 +124,13 @@ After adding all those names into our URLconf, our final `snippets/urls.py` file          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')), -    ) +    ]  ## Adding pagination diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index b2019520..3fad509a 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -60,7 +60,7 @@ To see what's going on under the hood let's first explicitly create a set of vie  In the `urls.py` file we bind our `ViewSet` classes into a set of concrete views. -    from snippets.views import SnippetViewSet, UserViewSet +    from snippets.views import SnippetViewSet, UserViewSet, api_root      from rest_framework import renderers      snippet_list = SnippetViewSet.as_view({ @@ -87,14 +87,14 @@ Notice how we're creating multiple views from each `ViewSet` class, by binding t  Now that we've bound our resources into concrete views, we can register the views with the URL conf as usual. -    urlpatterns = format_suffix_patterns(patterns('snippets.views', -        url(r'^$', 'api_root'), +    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') -    )) +    ])  ## Using Routers @@ -102,7 +102,7 @@ Because we're using `ViewSet` classes rather than `View` classes, we actually do  Here's our re-wired `urls.py` file. -    from django.conf.urls import patterns, url, include +    from django.conf.urls import url, include      from snippets import views      from rest_framework.routers import DefaultRouter @@ -113,10 +113,10 @@ Here's our re-wired `urls.py` file.      # 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')) -    ) +    ]  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. diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 813e9872..1c398c1f 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -19,18 +19,20 @@ Create a new Django project named `tutorial`, then start a new app called `quick      pip install djangorestframework      # Set up a new project with a single application -    django-admin.py startproject tutorial . +    django-admin.py startproject tutorial      cd tutorial      django-admin.py startapp quickstart  	cd ..  Now sync your database for the first time: -    python manage.py syncdb +    python manage.py migrate -Make sure to create an initial user named `admin` with a password of `password`. We'll authenticate as that user later in our example. +We'll also create an initial user named `admin` with a password of `password`. We'll authenticate as that user later in our example. -Once you've set up a database and got everything synced and ready to go, open up the app's directory and we'll get coding... +    python manage.py createsuperuser + +Once you've set up a database and initial user created and ready to go, open up the app's directory and we'll get coding...  ## Serializers | 
