diff options
| author | Tom Christie | 2012-11-09 13:49:52 +0000 | 
|---|---|---|
| committer | Tom Christie | 2012-11-09 13:49:52 +0000 | 
| commit | 8953a60196cb55ec75902882314da5a42636349c (patch) | |
| tree | 43bf6ea1f69955aeecd83fb9f866d92ea9a5f3df /docs/api-guide | |
| parent | b78872b7dbb55f1aa2d21f15fbb952f0c7156326 (diff) | |
| parent | 9aaeeacdfebc244850e82469e4af45af252cca4d (diff) | |
| download | django-rest-framework-8953a60196cb55ec75902882314da5a42636349c.tar.bz2 | |
Merge with master
Diffstat (limited to 'docs/api-guide')
| -rw-r--r-- | docs/api-guide/authentication.md | 20 | ||||
| -rw-r--r-- | docs/api-guide/content-negotiation.md | 57 | ||||
| -rw-r--r-- | docs/api-guide/exceptions.md | 8 | ||||
| -rw-r--r-- | docs/api-guide/fields.md | 133 | ||||
| -rw-r--r-- | docs/api-guide/filtering.md | 179 | ||||
| -rw-r--r-- | docs/api-guide/format-suffixes.md | 52 | ||||
| -rw-r--r-- | docs/api-guide/generic-views.md | 72 | ||||
| -rw-r--r-- | docs/api-guide/pagination.md | 6 | ||||
| -rw-r--r-- | docs/api-guide/parsers.md | 31 | ||||
| -rw-r--r-- | docs/api-guide/permissions.md | 25 | ||||
| -rw-r--r-- | docs/api-guide/renderers.md | 73 | ||||
| -rw-r--r-- | docs/api-guide/requests.md | 14 | ||||
| -rw-r--r-- | docs/api-guide/responses.md | 2 | ||||
| -rw-r--r-- | docs/api-guide/reverse.md | 2 | ||||
| -rw-r--r-- | docs/api-guide/serializers.md | 128 | ||||
| -rw-r--r-- | docs/api-guide/settings.md | 34 | ||||
| -rw-r--r-- | docs/api-guide/status-codes.md | 2 | ||||
| -rw-r--r-- | docs/api-guide/throttling.md | 32 | ||||
| -rw-r--r-- | docs/api-guide/views.md | 54 | 
19 files changed, 751 insertions, 173 deletions
| diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 3ace6519..c87ba83e 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -35,8 +35,8 @@ The value of `request.user` and `request.auth` for unauthenticated requests can  The default authentication schemes may be set globally, using the `DEFAULT_AUTHENTICATION` setting.  For example.      REST_FRAMEWORK = { -        'DEFAULT_AUTHENTICATION': ( -            'rest_framework.authentication.UserBasicAuthentication', +        'DEFAULT_AUTHENTICATION_CLASSES': ( +            'rest_framework.authentication.BasicAuthentication',              'rest_framework.authentication.SessionAuthentication',          )      } @@ -44,7 +44,7 @@ The default authentication schemes may be set globally, using the `DEFAULT_AUTHE  You can also set the authentication scheme on a per-view basis, using the `APIView` class based views.      class ExampleView(APIView): -        authentication_classes = (SessionAuthentication, UserBasicAuthentication) +        authentication_classes = (SessionAuthentication, BasicAuthentication)          permission_classes = (IsAuthenticated,)          def get(self, request, format=None): @@ -56,8 +56,8 @@ You can also set the authentication scheme on a per-view basis, using the `APIVi  Or, if you're using the `@api_view` decorator with function based views. -    @api_view(('GET',)), -    @authentication_classes((SessionAuthentication, UserBasicAuthentication)) +    @api_view(['GET']) +    @authentication_classes((SessionAuthentication, BasicAuthentication))      @permissions_classes((IsAuthenticated,))      def example_view(request, format=None):          content = { @@ -89,7 +89,7 @@ This authentication scheme uses [HTTP Basic Authentication][basicauth], signed a  If successfully authenticated, `BasicAuthentication` provides the following credentials. -* `request.user` will be a `django.contrib.auth.models.User` instance. +* `request.user` will be a Django `User` instance.  * `request.auth` will be `None`.  Unauthenticated responses that are denied permission will result in an `HTTP 401 Unauthorized` response with an appropriate WWW-Authenticate header.  For example: @@ -111,13 +111,13 @@ You'll also need to create tokens for your users.      token = Token.objects.create(user=...)      print token.key -For clients to authenticate, the token key should be included in the `Authorization` HTTP header.  The key should be prefixed by the string literal "Token", with whitespace seperating the two strings.  For example: +For clients to authenticate, the token key should be included in the `Authorization` HTTP header.  The key should be prefixed by the string literal "Token", with whitespace separating the two strings.  For example:      Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b  If successfully authenticated, `TokenAuthentication` provides the following credentials. -* `request.user` will be a `django.contrib.auth.models.User` instance. +* `request.user` will be a Django `User` instance.  * `request.auth` will be a `rest_framework.tokenauth.models.BasicToken` instance.  Unauthenticated responses that are denied permission will result in an `HTTP 401 Unauthorized` response with an appropriate WWW-Authenticate header.  For example: @@ -132,7 +132,7 @@ This authentication scheme uses the [OAuth 2.0][oauth] protocol to authenticate  If successfully authenticated, `OAuth2Authentication` provides the following credentials. -* `request.user` will be a `django.contrib.auth.models.User` instance. +* `request.user` will be a Django `User` instance.  * `request.auth` will be a `rest_framework.models.OAuthToken` instance.  **TODO**: Note type of response (401 vs 403) @@ -145,7 +145,7 @@ This authentication scheme uses Django's default session backend for authenticat  If successfully authenticated, `SessionAuthentication` provides the following credentials. -* `request.user` will be a `django.contrib.auth.models.User` instance. +* `request.user` will be a Django `User` instance.  * `request.auth` will be `None`.  Unauthenticated responses that are denied permission will result in an `HTTP 403 Forbidden` response. diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md index ad98de3b..10288c94 100644 --- a/docs/api-guide/content-negotiation.md +++ b/docs/api-guide/content-negotiation.md @@ -7,3 +7,60 @@  > — [RFC 2616][cite], Fielding et al.  [cite]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html + +Content negotiation is the process of selecting one of multiple possible representations to return to a client, based on client or server preferences. + +## Determining the accepted renderer + +REST framework uses a simple style of content negotiation to determine which media type should be returned to a client, based on the available renderers, the priorities of each of those renderers, and the client's `Accept:` header.  The style used is partly client-driven, and partly server-driven. + +1. More specific media types are given preference to less specific media types. +2. If multiple media types have the same specificity, then preference is given to based on the ordering of the renderers configured for the given view. + +For example, given the following `Accept` header: + +    application/json; indent=4, application/json, application/yaml, text/html, */* + +The priorities for each of the given media types would be: + +* `application/json; indent=4` +* `application/json`, `application/yaml` and `text/html` +* `*/*` + +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]  + +--- + +**Note**: "q" values are not taken into account by REST framework when determining preference.  The use of "q" values negatively impacts caching, and in the author's opinion they are an unnecessary and overcomplicated approach to content negotiation. + +This is a valid approach as the HTTP spec deliberately underspecifies how a server should weight server-based preferences against client-based preferences. + +--- + +# Custom content negotiation + +It's unlikely that you'll want to provide a custom content negotiation scheme for REST framework, but you can do so if needed.  To implement a custom content negotiation scheme override `BaseContentNegotiation`. + +REST framework's content negotiation classes handle selection of both the appropriate parser for the request, and the appropriate renderer for the response,  so you should implement both the `.select_parser(request, parsers)` and `.select_renderer(request, renderers, format_suffix)` methods. + +## Example + +The following is a custom content negotiation class which ignores the client +request when selecting the appropriate parser or renderer. + +    class IgnoreClientContentNegotiation(BaseContentNegotiation): +        def select_parser(self, request, parsers): +            """ +            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. +            """ +            return renderers[0] + +[accept-header]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index c30f586a..8b3e50f1 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -25,7 +25,7 @@ For example, the following request:      DELETE http://api.example.com/foo/bar HTTP/1.1      Accept: application/json -Might recieve an error response indicating that the `DELETE` method is not allowed on that resource: +Might receive an error response indicating that the `DELETE` method is not allowed on that resource:      HTTP/1.1 405 Method Not Allowed      Content-Type: application/json; charset=utf-8 @@ -33,6 +33,10 @@ Might recieve an error response indicating that the `DELETE` method is not allow      {"detail": "Method 'DELETE' not allowed."} +--- + +# API Reference +  ## APIException  **Signature:** `APIException(detail=None)` @@ -98,4 +102,4 @@ 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".  [cite]: http://www.doughellmann.com/articles/how-tos/python-exception-handling/index.html -[authentication]: authentication.md
\ No newline at end of file +[authentication]: authentication.md diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index dc9ab045..0485b158 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -14,6 +14,51 @@ Serializer fields handle converting between primative values and internal dataty  --- +## Core arguments + +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 updating an instance dureing deserialization. + +Defaults to `False` + +### `required` + +Normally an error will be raised if a field is not supplied during deserialization. +Set to false if this field is not required to be present during deserialization. + +Defaults to `True`. + +### `default` + +If set, this gives the default value that will be used for the field if none is supplied.  If not set the default behaviour is to not populate the attribute at all. + +### `validators` + +A list of Django validators that should be used to validate deserialized values. + +### `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. + + +--- +  # Generic Fields  These generic fields are used for representing arbitrary model fields or the output of model methods. @@ -42,7 +87,7 @@ A serializer definition that looked like this:          class Meta:              fields = ('url', 'owner', 'name', 'expired') -Would produced output similar to: +Would produce output similar to:      {          'url': 'http://example.com/api/accounts/3/', @@ -51,7 +96,7 @@ Would produced output similar to:          'expired': True      } -Be default, the `Field` class will perform a basic translation of the source value into primative datatypes, falling back to unicode representations of complex datatypes when neccesary. +By default, the `Field` class will perform a basic translation of the source value into primative datatypes, falling back to unicode representations of complex datatypes when necessary.  You can customize this  behaviour by overriding the `.to_native(self, value)` method. @@ -73,34 +118,52 @@ These fields represent basic datatypes, and support both reading and writing val  ## BooleanField -A Boolean representation, corresponds to `django.db.models.fields.BooleanField`. +A Boolean representation. + +Corresponds to `django.db.models.fields.BooleanField`.  ## CharField -A text representation, optionally validates the text to be shorter than `max_length` and longer than `min_length`, corresponds to `django.db.models.fields.CharField` +A text representation, optionally validates the text to be shorter than `max_length` and longer than `min_length`. + +Corresponds to `django.db.models.fields.CharField`  or `django.db.models.fields.TextField`. -**Signature:** `CharField([max_length=<Integer>[, min_length=<Integer>]])` +**Signature:** `CharField(max_length=None, min_length=None)` + +## ChoiceField + +A field that can accept a value out of a limited set of choices.  ## EmailField -A text representation, validates the text to be a valid e-mail adress. Corresponds to `django.db.models.fields.EmailField` +A text representation, validates the text to be a valid e-mail address. + +Corresponds to `django.db.models.fields.EmailField`  ## DateField -A date representation. Corresponds to `django.db.models.fields.DateField` +A date representation. + +Corresponds to `django.db.models.fields.DateField`  ## DateTimeField -A date and time representation. Corresponds to `django.db.models.fields.DateTimeField` +A date and time representation. + +Corresponds to `django.db.models.fields.DateTimeField`  ## 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` +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 -A floating point representation. Corresponds to `django.db.models.fields.FloatField`. +A floating point representation. + +Corresponds to `django.db.models.fields.FloatField`.  --- @@ -165,33 +228,61 @@ And a model serializer defined like this:              model = Bookmark              exclude = ('id',) -The an example output format for a Bookmark instance would be: +Then an example output format for a Bookmark instance would be:      {          'tags': [u'django', u'python'],          'url': u'https://www.djangoproject.com/'      } -## PrimaryKeyRelatedField +## PrimaryKeyRelatedField / ManyPrimaryKeyRelatedField + +`PrimaryKeyRelatedField` and `ManyPrimaryKeyRelatedField` will represent the target of the relationship using it's primary key. + +By default these fields are read-write, although you can change this behaviour using the `read_only` flag. + +**Arguments**: + +* `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`. -As with `RelatedField` field can be applied to any "to-one" relationship, such as a `ForeignKey` field. +## SlugRelatedField / ManySlugRelatedField -`PrimaryKeyRelatedField` will represent the target of the field using it's primary key. +`SlugRelatedField` and `ManySlugRelatedField` will represent the target of the relationship using a unique slug. -Be default, `PrimaryKeyRelatedField` is read-write, although you can change this behaviour using the `readonly` flag. +By default these fields read-write, although you can change this behaviour using the `read_only` flag. -## ManyPrimaryKeyRelatedField +**Arguments**: -As with `RelatedField` field can be applied to any "to-many" relationship, such as a `ManyToManyField` field, or a reverse `ForeignKey` relationship. +* `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`. +* `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`. -`PrimaryKeyRelatedField` will represent the target of the field using their primary key. +## HyperlinkedRelatedField / ManyHyperlinkedRelatedField -Be default, `ManyPrimaryKeyRelatedField` is read-write, although you can change this behaviour using the `readonly` flag. +`HyperlinkedRelatedField` and `ManyHyperlinkedRelatedField` will represent the target of the relationship using a hyperlink. -## HyperlinkedRelatedField +By default, `HyperlinkedRelatedField` is read-write, although you can change this behaviour using the `read_only` flag. -## ManyHyperlinkedRelatedField +**Arguments**: + +* `view_name` - The view name that should be used as the target of the relationship.  **required**. +* `format` - If using format suffixes, hyperlinked fields will use the same format suffix for the target unless overridden by using the `format` argument. +* `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`. +* `slug_field` - The field on the target that should be used for the lookup. Default is `'slug'`. +* `pk_url_kwarg` - The named url parameter for the pk field lookup. Default is `pk`. +* `slug_url_kwarg` - The named url parameter for the slug field lookup. Default is to use the same value as given for `slug_field`.  ## HyperLinkedIdentityField +This field can be applied as an identity relationship, such as the `'url'` field on  a HyperlinkedModelSerializer. + +This field is always read-only. + +**Arguments**: + +* `view_name` - The view name that should be used as the target of the relationship.  **required**. +* `format` - If using format suffixes, hyperlinked fields will use the same format suffix for the target unless overridden by using the `format` argument. +* `slug_field` - The field on the target that should be used for the lookup. Default is `'slug'`. +* `pk_url_kwarg` - The named url parameter for the pk field lookup. Default is `pk`. +* `slug_url_kwarg` - The named url parameter for the slug field lookup. Default is to use the same value as given for `slug_field`. +  [cite]: http://www.python.org/dev/peps/pep-0020/ diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md new file mode 100644 index 00000000..14ab9a26 --- /dev/null +++ b/docs/api-guide/filtering.md @@ -0,0 +1,179 @@ +<a class="github" href="filters.py"></a> + +# Filtering + +> The root QuerySet provided by the Manager describes all objects in the database table. Usually, though, you'll need to select only a subset of the complete set of objects. +> +> — [Django documentation][cite] + +The default behavior of REST framework's generic list views is to return the entire queryset for a model manager.  Often you will want your API to restrict the items that are returned by the queryset. + +The simplest way to filter the queryset of any view that subclasses `MultipleObjectAPIView` is to override the `.get_queryset()` method. + +Overriding this method allows you to customize the queryset returned by the view in a number of different ways. + +## Filtering against the current user + +You might want to filter the queryset to ensure that only results relevant to the currently authenticated user making the request are returned. + +You can do so by filtering based on the value of `request.user`. + +For example: + +    class PurchaseList(generics.ListAPIView) +        model = Purchase +        serializer_class = PurchaseSerializer +  +        def get_queryset(self): +            """ +            This view should return a list of all the purchases +            for the currently authenticated user. +            """ +            user = self.request.user +            return Purchase.objects.filter(purchaser=user) + + +## Filtering against 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: + +    url('^purchases/(?P<username>.+)/$', PurchaseList.as_view()), + +You could then write a view that returned a purchase queryset filtered by the username portion of the URL: + +    class PurchaseList(generics.ListAPIView) +        model = Purchase +        serializer_class = PurchaseSerializer +  +        def get_queryset(self): +            """ +            This view should return a list of all the purchases for +            the user as determined by the username portion of the URL. +            """ +            username = self.kwargs['username'] +            return Purchase.objects.filter(purchaser__username=username) + +## 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. + +We can override `.get_queryset()` to deal with URLs such as `http://example.com/api/purchases?username=denvercoder9`, and filter the queryset only if the `username` parameter is included in the URL: + +    class PurchaseList(generics.ListAPIView) +        model = Purchase +        serializer_class = PurchaseSerializer +  +        def get_queryset(self): +            """ +            Optionally restricts the returned purchases to a given user, +            by filtering against a `username` query parameter in the URL. +            """ +            queryset = Purchase.objects.all() +            username = self.request.QUERY_PARAMS.get('username', None): +            if username is not None: +                queryset = queryset.filter(purchaser__username=username) +            return queryset + +--- + +# Generic Filtering + +As well as being able to override the default queryset, REST framework also includes support for generic filtering backends that allow you to easily construct complex filters that can be specified by the client using query parameters. + +REST framework supports pluggable backends to implement filtering, and provides an implementation which uses the [django-filter] package. + +To use REST framework's default filtering backend, first install `django-filter`. + +    pip install -e git+https://github.com/alex/django-filter.git#egg=django-filter + +You must also set the filter backend to `DjangoFilterBackend` in your settings: + +    REST_FRAMEWORK = { +        'FILTER_BACKEND': 'rest_framework.filters.DjangoFilterBackend' +    } + +**Note**: The currently supported version of `django-filter` is the `master` branch.  A PyPI release is expected to be coming soon. + +## Specifying filter fields + +If all you need is simple equality-based filtering, you can set a `filter_fields` attribute on the view, listing the set of fields you wish to filter against. + +    class ProductList(generics.ListAPIView): +        model = Product +        serializer_class = ProductSerializer +        filter_fields = ('category', 'in_stock') + +This will automatically create a `FilterSet` class for the given fields, and will allow you to make requests such as: + +    http://example.com/api/products?category=clothing&in_stock=True + +## Specifying a FilterSet + +For more advanced filtering requirements you can specify a `FilterSet` class that should be used by the view.  For example: + +    class ProductFilter(django_filters.FilterSet): +        min_price = django_filters.NumberFilter(lookup_type='gte') +        max_price = django_filters.NumberFilter(lookup_type='lte') +        class Meta: +            model = Product +            fields = ['category', 'in_stock', 'min_price', 'max_price'] + +    class ProductList(generics.ListAPIView): +        model = Product +        serializer_class = ProductSerializer +        filter_class = ProductFilter + +Which will allow you to make requests such as: + +    http://example.com/api/products?category=clothing&max_price=10.00 + +For more details on using filter sets see the [django-filter documentation][django-filter-docs]. + +--- + +**Hints & Tips** + +* By default filtering is not enabled.  If you want to use `DjangoFilterBackend` remember to make sure it is installed by using the `'FILTER_BACKEND'` 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].)  +* `django-filter` supports filtering across relationships, using Django's double-underscore syntax. + +--- + +## 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): +        """ +        Return a list of all the products that the authenticated +        user has ever purchased, with optional filtering. +        """ +        model = Product +        serializer_class = ProductSerializer +        filter_class = ProductFilter +         +        def get_queryset(self): +            user = self.request.user +            return user.purchase_set.all() +--- + +# Custom generic filtering + +You can also provide your own generic filtering backend, or write an installable app for other developers to use. + +To do so override `BaseFilterBackend`, and override the `.filter_queryset(self, request, queryset, view)` method.  The method should return a new, filtered queryset. + +To install the filter backend, set the `'FILTER_BACKEND'` key in your `'REST_FRAMEWORK'` setting, using the dotted import path of the filter backend class. + +For example: + +    REST_FRAMEWORK = { +        'FILTER_BACKEND': 'custom_filters.CustomFilterBackend' +    } + +[cite]: https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-specific-objects-with-filters +[django-filter]: https://github.com/alex/django-filter +[django-filter-docs]: https://django-filter.readthedocs.org/en/latest/index.html +[nullbooleanselect]: https://github.com/django/django/blob/master/django/forms/widgets.py
\ No newline at end of file diff --git a/docs/api-guide/format-suffixes.md b/docs/api-guide/format-suffixes.md index 7d72d9f8..6d5feba4 100644 --- a/docs/api-guide/format-suffixes.md +++ b/docs/api-guide/format-suffixes.md @@ -7,5 +7,55 @@ used all the time.  >  > — Roy Fielding, [REST discuss mailing list][cite] -[cite]: http://tech.groups.yahoo.com/group/rest-discuss/message/5857 +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. + +## format_suffix_patterns + +**Signature**: format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None) + +Returns a URL pattern list which includes format suffix patterns appended to each of the URL patterns provided. + +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.  + +Example: + +    from rest_framework.urlpatterns import format_suffix_patterns +     +    urlpatterns = patterns('blog.views', +        url(r'^/$', 'api_root'), +        url(r'^comment/$', 'comment_root'), +        url(r'^comment/(?P<pk>[0-9]+)/$', 'comment_instance') +    ) +     +    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. +    @api_view(('GET',)) +    def api_root(request, format=None): +        # do stuff... + +The name of the kwarg used may be modified by using the `FORMAT_SUFFIX_KWARG` setting. + +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:  + +“That's why I always prefer extensions. Neither choice has anything to do with REST.” — Roy Fielding, [REST discuss mailing list][cite2] + +The quote does not mention Accept headers, but it does make it clear that format suffixes should be considered an acceptable pattern. + +[cite]: http://tech.groups.yahoo.com/group/rest-discuss/message/5857 +[cite2]: http://tech.groups.yahoo.com/group/rest-discuss/message/14844
\ No newline at end of file diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 8bf7a7e2..360ef1a2 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -30,7 +30,7 @@ For more complex cases you might also want to override various methods on the vi          serializer_class = UserSerializer          permission_classes = (IsAdminUser,) -        def get_paginate_by(self): +        def get_paginate_by(self, queryset):              """              Use smaller pagination for HTML representations.              """ @@ -49,21 +49,21 @@ For very simple cases you might want to pass through any class attributes using  The following classes are the concrete generic views.  If you're using generic views this is normally the level you'll be working at unless you need heavily customized behavior. -## ListAPIView +## CreateAPIView -Used for **read-only** endpoints to represent a **collection of model instances**. +Used for **create-only** endpoints. -Provides a `get` method handler. +Provides `post` method handlers. -Extends: [MultipleObjectBaseAPIView], [ListModelMixin] +Extends: [GenericAPIView], [CreateModelMixin] -## ListCreateAPIView +## ListAPIView -Used for **read-write** endpoints to represent a **collection of model instances**. +Used for **read-only** endpoints to represent a **collection of model instances**. -Provides `get` and `post` method handlers. +Provides a `get` method handler. -Extends: [MultipleObjectBaseAPIView], [ListModelMixin], [CreateModelMixin] +Extends: [MultipleObjectAPIView], [ListModelMixin]  ## RetrieveAPIView @@ -71,7 +71,31 @@ Used for **read-only** endpoints to represent a **single model instance**.  Provides a `get` method handler. -Extends: [SingleObjectBaseAPIView], [RetrieveModelMixin] +Extends: [SingleObjectAPIView], [RetrieveModelMixin] + +## DestroyAPIView + +Used for **delete-only** endpoints for a **single model instance**. + +Provides a `delete` method handler. + +Extends: [SingleObjectAPIView], [DestroyModelMixin] + +## UpdateAPIView + +Used for **update-only** endpoints for a **single model instance**. + +Provides a `put` method handler. + +Extends: [SingleObjectAPIView], [UpdateModelMixin] + +## ListCreateAPIView + +Used for **read-write** endpoints to represent a **collection of model instances**. + +Provides `get` and `post` method handlers. + +Extends: [MultipleObjectAPIView], [ListModelMixin], [CreateModelMixin]  ## RetrieveDestroyAPIView @@ -79,15 +103,15 @@ Used for **read or delete** endpoints to represent a **single model instance**.  Provides `get` and `delete` method handlers. -Extends: [SingleObjectBaseAPIView], [RetrieveModelMixin], [DestroyModelMixin] +Extends: [SingleObjectAPIView], [RetrieveModelMixin], [DestroyModelMixin]  ## RetrieveUpdateDestroyAPIView -Used for **read-write** endpoints to represent a **single model instance**. +Used for **read-write-delete** endpoints to represent a **single model instance**.  Provides `get`, `put` and `delete` method handlers. -Extends: [SingleObjectBaseAPIView], [RetrieveModelMixin], [UpdateModelMixin], [DestroyModelMixin] +Extends: [SingleObjectAPIView], [RetrieveModelMixin], [UpdateModelMixin], [DestroyModelMixin]  --- @@ -95,17 +119,17 @@ Extends: [SingleObjectBaseAPIView], [RetrieveModelMixin], [UpdateModelMixin], [D  Each of the generic views provided is built by combining one of the base views below, with one or more mixin classes. -## BaseAPIView +## GenericAPIView  Extends REST framework's `APIView` class, adding support for serialization of model instances and model querysets. -## MultipleObjectBaseAPIView +## MultipleObjectAPIView  Provides a base view for acting on a single object, by combining REST framework's `APIView`, and Django's [MultipleObjectMixin].  **See also:** ccbv.co.uk documentation for [MultipleObjectMixin][multiple-object-mixin-classy]. -## SingleObjectBaseAPIView +## SingleObjectAPIView  Provides a base view for acting on a single object, by combining REST framework's `APIView`, and Django's [SingleObjectMixin]. @@ -121,31 +145,31 @@ The mixin classes provide the actions that are used to provide the basic view be  Provides a `.list(request, *args, **kwargs)` method, that implements listing a queryset. -Should be mixed in with [MultipleObjectBaseAPIView]. +Should be mixed in with [MultipleObjectAPIView].  ## CreateModelMixin  Provides a `.create(request, *args, **kwargs)` method, that implements creating and saving a new model instance. -Should be mixed in with any [BaseAPIView]. +Should be mixed in with any [GenericAPIView].  ## RetrieveModelMixin  Provides a `.retrieve(request, *args, **kwargs)` method, that implements returning an existing model instance in a response. -Should be mixed in with [SingleObjectBaseAPIView]. +Should be mixed in with [SingleObjectAPIView].  ## UpdateModelMixin  Provides a `.update(request, *args, **kwargs)` method, that implements updating and saving an existing model instance. -Should be mixed in with [SingleObjectBaseAPIView]. +Should be mixed in with [SingleObjectAPIView].  ## DestroyModelMixin  Provides a `.destroy(request, *args, **kwargs)` method, that implements deletion of an existing model instance. -Should be mixed in with [SingleObjectBaseAPIView]. +Should be mixed in with [SingleObjectAPIView].  [cite]: https://docs.djangoproject.com/en/dev/ref/class-based-views/#base-vs-generic-views  [MultipleObjectMixin]: https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/ @@ -153,9 +177,9 @@ Should be mixed in with [SingleObjectBaseAPIView].  [multiple-object-mixin-classy]: http://ccbv.co.uk/projects/Django/1.4/django.views.generic.list/MultipleObjectMixin/  [single-object-mixin-classy]: http://ccbv.co.uk/projects/Django/1.4/django.views.generic.detail/SingleObjectMixin/ -[BaseAPIView]: #baseapiview -[SingleObjectBaseAPIView]: #singleobjectbaseapiview -[MultipleObjectBaseAPIView]: #multipleobjectbaseapiview +[GenericAPIView]: #genericapiview +[SingleObjectAPIView]: #singleobjectapiview +[MultipleObjectAPIView]: #multipleobjectapiview  [ListModelMixin]: #listmodelmixin  [CreateModelMixin]: #createmodelmixin  [RetrieveModelMixin]: #retrievemodelmixin diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index e416de02..597baba4 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -100,12 +100,16 @@ You can also set the pagination style on a per-view basis, using the `ListAPIVie  For more complex requirements such as serialization that differs depending on the requested media type you can override the `.get_paginate_by()` and `.get_pagination_serializer_class()` methods. -## Custom pagination serializers +--- + +# Custom pagination serializers  To create a custom pagination serializer class you should override `pagination.BasePaginationSerializer` and set the fields that you want the serializer to return.  You can also override the name used for the object list field, by setting the `results_field` attribute, which defaults to `'results'`. +## Example +  For example, to nest a pair of links labelled 'prev' and 'next', and set the name for the results field to 'objects', you might use something like this.      class LinksSerializer(serializers.Serializer): diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index 4f145ba3..185b616c 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -8,7 +8,7 @@ sending more complex data than simple forms  >  > — Malcom Tredinnick, [Django developers group][cite] -REST framework includes a number of built in Parser classes, that allow you to accept requests with various media types.  There is also support for defining your own custom parsers, which gives you the flexiblity to design the media types that your API accepts. +REST framework includes a number of built in Parser classes, that allow you to accept requests with various media types.  There is also support for defining your own custom parsers, which gives you the flexibility to design the media types that your API accepts.  ## How the parser is determined @@ -16,10 +16,10 @@ The set of valid parsers for a view is always defined as a list of classes.  Whe  ## Setting the parsers -The default set of parsers may be set globally, using the `DEFAULT_PARSERS` setting.  For example, the following settings would allow requests with `YAML` content. +The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting.  For example, the following settings would allow requests with `YAML` content.      REST_FRAMEWORK = { -        'DEFAULT_PARSERS': ( +        'DEFAULT_PARSER_CLASSES': (              'rest_framework.parsers.YAMLParser',          )      } @@ -37,7 +37,7 @@ You can also set the renderers used for an individual view, using the `APIView`  Or, if you're using the `@api_view` decorator with function based views. -    @api_view(('POST',)), +    @api_view(['POST'])      @parser_classes((YAMLParser,))      def example_view(request, format=None):          """ @@ -65,7 +65,7 @@ Parses `YAML` request content.  Parses REST framework's default style of `XML` request content. -Note that the `XML` markup language is used typically used as the base language for more strictly defined domain-specific languages, such as `RSS`, `Atom`, and `XHTML`. +Note that the `XML` markup language is typically used as the base language for more strictly defined domain-specific languages, such as `RSS`, `Atom`, and `XHTML`.  If you are considering using `XML` for your API, you may want to consider implementing a custom renderer and parser for your specific requirements, and using an existing domain-specific media-type, or creating your own custom XML-based media-type. @@ -91,19 +91,27 @@ You will typically want to use both `FormParser` and `MultiPartParser` together  # Custom parsers -To implement a custom parser, you should override `BaseParser`, set the `.media_type` property, and implement the `.parse_stream(self, stream, parser_context)` method. +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 arguments passed to `.parse_stream()` are: +The arguments passed to `.parse()` are:  ### stream  A stream-like object representing the body of the request. +### media_type + +Optional.  If provided, this is the media type of the incoming request content. + +Depending on the request's `Content-Type:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters.  For example `"text/plain; charset=utf-8"`. +  ### parser_context -If supplied, this argument will be a dictionary containing any additional context that may be required to parse the request content.  By default it includes the keys `'upload_handlers'` and `'meta'`, which contain the values of the `request.upload_handlers` and `request.meta` properties. +Optional.  If supplied, this argument will be a dictionary containing any additional context that may be required to parse the request content. + +By default this will include the following keys: `view`, `request`, `args`, `kwargs`.  ## Example @@ -116,7 +124,7 @@ The following is an example plaintext parser that will populate the `request.DAT      media_type = 'text/plain' -    def parse_stream(self, stream, parser_context=None): +    def parse(self, stream, media_type=None, parser_context=None):          """          Simply return a string representing the body of the request.          """ @@ -124,7 +132,7 @@ The following is an example plaintext parser that will populate the `request.DAT  ## Uploading file content -If your custom parser needs to support file uploads, you may return a `DataAndFiles` object from the `.parse_stream()` method.  `DataAndFiles` should be instantiated with two arguments.  The first argument will be used to populate the `request.DATA` property, and the second argument will be used to populate the `request.FILES` property. +If your custom parser needs to support file uploads, you may return a `DataAndFiles` object from the `.parse()` method.  `DataAndFiles` should be instantiated with two arguments.  The first argument will be used to populate the `request.DATA` property, and the second argument will be used to populate the `request.FILES` property.  For example: @@ -132,8 +140,9 @@ For example:          """          A naive raw file upload parser.          """ +        media_type = '*/*'  # Accept anything -        def parse_stream(self, stream, parser_context): +        def parse(self, stream, media_type=None, parser_context=None):              content = stream.read()              name = 'example.dat'              content_type = 'application/octet-stream' diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index eb290849..1a746fb6 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -6,7 +6,7 @@  >  > — [Apple Developer Documentation][cite] -Together with [authentication] and [throttling], permissions determine wheter a request should be granted or denied access. +Together with [authentication] and [throttling], permissions determine whether a request should be granted or denied access.  Permission checks are always run at the very start of the view, before any other code is allowed to proceed.  Permission checks will typically use the authentication information in the `request.user` and `request.auth` properties to determine if the incoming request should be permitted. @@ -25,14 +25,20 @@ Object level permissions are run by REST framework's generic views when `.get_ob  ## Setting the permission policy -The default permission policy may be set globally, using the `DEFAULT_PERMISSIONS` setting.  For example. +The default permission policy may be set globally, using the `DEFAULT_PERMISSION_CLASSES` setting.  For example.      REST_FRAMEWORK = { -        'DEFAULT_PERMISSIONS': ( +        'DEFAULT_PERMISSION_CLASSES': (              'rest_framework.permissions.IsAuthenticated',          )      } +If not specified, this setting defaults to allowing unrestricted access: + +    'DEFAULT_PERMISSION_CLASSES': ( +       'rest_framework.permissions.AllowAny', +    ) +  You can also set the authentication policy on a per-view basis, using the `APIView` class based views.      class ExampleView(APIView): @@ -54,8 +60,16 @@ Or, if you're using the `@api_view` decorator with function based views.          }          return Response(content) +--- +  # API Reference +## AllowAny + +The `AllowAny` permission class will allow unrestricted access, **regardless of if the request was authenticated or unauthenticated**. + +This permission is not strictly required, since you can achieve the same result by using an empty list or tuple for the permissions setting, but you may find it useful to specify this class because it makes the intention explicit. +  ## IsAuthenticated  The `IsAuthenticated` permission class will deny permission to any unauthenticated user, and allow permission otherwise. @@ -64,7 +78,7 @@ This permission is suitable if you want your API to only be accessible to regist  ## IsAdminUser -The `IsAdminUser` permission class will deny permission to any user, unless `user.is_staff`is `True` in which case permission will be allowed. +The `IsAdminUser` permission class will deny permission to any user, unless `user.is_staff` is `True` in which case permission will be allowed.  This permission is suitable is you want your API to only be accessible to a subset of trusted administrators. @@ -88,12 +102,15 @@ To use custom model permissions, override `DjangoModelPermissions` and set the `  The `DjangoModelPermissions` class also supports object-level permissions.  Third-party authorization backends such as [django-guardian][guardian] that provide object-level permissions should work just fine with `DjangoModelPermissions` without any custom configuration required. +--- +  # Custom permissions  To implement a custom permission, override `BasePermission` and implement the `.has_permission(self, request, view, obj=None)` method.  The method should return `True` if the request should be granted access, and `False` otherwise. +  [cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html  [authentication]: authentication.md  [throttling]: throttling.md diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index c8addb32..374ff0ab 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -6,7 +6,7 @@  >  > — [Django documentation][cite] -REST framework includes a number of built in Renderer classes, that allow you to return responses with various media types.  There is also support for defining your own custom renderers, which gives you the flexiblity to design your own media types. +REST framework includes a number of built in Renderer classes, that allow you to return responses with various media types.  There is also support for defining your own custom renderers, which gives you the flexibility to design your own media types.  ## How the renderer is determined @@ -18,10 +18,10 @@ For more information see the documentation on [content negotation][conneg].  ## Setting the renderers -The default set of renderers may be set globally, using the `DEFAULT_RENDERERS` setting.  For example, the following settings would use `YAML` as the main media type and also include the self describing API. +The default set of renderers may be set globally, using the `DEFAULT_RENDERER_CLASSES` setting.  For example, the following settings would use `YAML` as the main media type and also include the self describing API.      REST_FRAMEWORK = { -        'DEFAULT_RENDERERS': ( +        'DEFAULT_RENDERER_CLASSES': (              'rest_framework.renderers.YAMLRenderer',              'rest_framework.renderers.BrowsableAPIRenderer',          ) @@ -42,7 +42,7 @@ You can also set the renderers used for an individual view, using the `APIView`  Or, if you're using the `@api_view` decorator with function based views. -    @api_view(('GET',)), +    @api_view(['GET'])      @renderer_classes((JSONRenderer, JSONPRenderer))      def user_count_view(request, format=None):          """ @@ -106,12 +106,12 @@ If you are considering using `XML` for your API, you may want to consider implem  **.format**: `'.xml'` -## HTMLRenderer +## TemplateHTMLRenderer  Renders data to HTML, using Django's standard template rendering.  Unlike other renderers, the data passed to the `Response` does not need to be serialized.  Also, unlike other renderers, you may want to include a `template_name` argument when creating the `Response`. -The HTMLRenderer will create a `RequestContext`, using the `response.data` as the context dict, and determine a template name to use to render the context. +The TemplateHTMLRenderer will create a `RequestContext`, using the `response.data` as the context dict, and determine a template name to use to render the context.  The template name is determined by (in order of preference): @@ -119,27 +119,49 @@ The template name is determined by (in order of preference):  2. An explicit `.template_name` attribute set on this class.  3. The return result of calling `view.get_template_names()`. -An example of a view that uses `HTMLRenderer`: +An example of a view that uses `TemplateHTMLRenderer`:      class UserInstance(generics.RetrieveUserAPIView):          """          A view that returns a templated HTML representations of a given user.          """          model = Users -        renderer_classes = (HTMLRenderer,) +        renderer_classes = (TemplateHTMLRenderer,)          def get(self, request, *args, **kwargs)              self.object = self.get_object() -            return Response(self.object, template_name='user_detail.html') +            return Response({'user': self.object}, template_name='user_detail.html') -You can use `HTMLRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint. +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 `HTMLRenderer` along with other renderer classes, you should consider listing `HTMLRenderer` 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. +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.  **.media_type**: `text/html`  **.format**: `'.html'` +See also: `StaticHTMLRenderer` + +## StaticHTMLRenderer + +A simple renderer that simply returns pre-rendered HTML.  Unlike other renderers, the data passed to the response object should be a string representing the content to be returned. + +An example of a view that uses `TemplateHTMLRenderer`: + +    @api_view(('GET',)) +    @renderer_classes((StaticHTMLRenderer,)) +    def simple_html_view(request):  +        data = '<html><body><h1>Hello, world</h1></body></html>' +        return Response(data) + +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. + +**.media_type**: `text/html` + +**.format**: `'.html'` + +See also: `TemplateHTMLRenderer` +  ## BrowsableAPIRenderer  Renders data into HTML for the Browseable API.  This renderer will determine which other renderer would have been given highest priority, and use that to display an API style response within the HTML page. @@ -162,11 +184,14 @@ The request data, as set by the `Response()` instantiation.  ### `media_type=None` -Optional. If provided, this is the accepted media type, as determined by the content negotiation stage.  Depending on the client's `Accept:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters.  For example `"application/json; nested=true"`. +Optional. If provided, this is the accepted media type, as determined by the content negotiation stage. + +Depending on the client's `Accept:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters.  For example `"application/json; nested=true"`.  ### `renderer_context=None`  Optional. If provided, this is a dictionary of contextual information provided by the view. +  By default this will include the following keys: `view`, `request`, `response`, `args`, `kwargs`.  ## Example @@ -204,7 +229,7 @@ In some cases you might want your view to use different serialization styles dep  For example:      @api_view(('GET',)) -    @renderer_classes((HTMLRenderer, JSONRenderer)) +    @renderer_classes((TemplateHTMLRenderer, JSONRenderer))      def list_users(request):          """          A view that can return JSON or HTML representations @@ -212,9 +237,9 @@ For example:          """          queryset = Users.objects.filter(active=True) -        if request.accepted_media_type == 'text/html': +        if request.accepted_renderer.format == 'html':              # TemplateHTMLRenderer takes a context dict, -            # and additionally requiresa 'template_name'. +            # and additionally requires a 'template_name'.              # It does not require serialization.              data = {'users': queryset}              return Response(data, template_name='list_users.html') @@ -226,12 +251,27 @@ For example:  ## Designing your media types -For the purposes of many Web APIs, simple `JSON` responses with hyperlinked relations may be sufficient.  If you want to fully embrace RESTful design and [HATEOAS] you'll neeed to consider the design and usage of your media types in more detail. +For the purposes of many Web APIs, simple `JSON` responses with hyperlinked relations may be sufficient.  If you want to fully embrace RESTful design and [HATEOAS] you'll need to consider the design and usage of your media types in more detail.  In [the words of Roy Fielding][quote], "A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state, or in defining extended relation names and/or hypertext-enabled mark-up for existing standard media types.".  For good examples of custom media types, see GitHub's use of a custom [application/vnd.github+json] media type, and Mike Amundsen's IANA approved [application/vnd.collection+json] JSON-based hypermedia. +## HTML error views + +Typically a renderer will behave the same regardless of if it's dealing with a regular response, or with a response caused by an exception being raised, such as an `Http404` or `PermissionDenied` exception, or a subclass of `APIException`. + +If you're using either the `TemplateHTMLRenderer` or the `StaticHTMLRenderer` and an exception is raised, the behavior is slightly different, and mirrors [Django's default handling of error views][django-error-views]. + +Exceptions raised and handled by an HTML renderer will attempt to render using one of the following methods, by order of precedence. + +* Load and render a template named `{status_code}.html`. +* Load and render a template named `api_exception.html`. +* Render the HTTP status code and text, for example "404 Not Found". + +Templates will render with a `RequestContext` which includes the `status_code` and `details` keys. + +  [cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process  [conneg]: content-negotiation.md  [browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers @@ -240,3 +280,4 @@ For good examples of custom media types, see GitHub's use of a custom [applicati  [quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven  [application/vnd.github+json]: http://developer.github.com/v3/media/  [application/vnd.collection+json]: http://www.amundsen.com/media-types/collection/ +[django-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views
\ No newline at end of file diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md index 439c97bc..72932f5d 100644 --- a/docs/api-guide/requests.md +++ b/docs/api-guide/requests.md @@ -25,19 +25,19 @@ For more details see the [parsers documentation].  ## .FILES -`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 that is used for `request.DATA`. +`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`.  For more details see the [parsers documentation].  ## .QUERY_PARAMS -`request.QUERY_PARAMS` is a more correcly named synonym for `request.GET`. +`request.QUERY_PARAMS` is a more correctly named synonym for `request.GET`.  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.  ## .parsers -The `APIView` class or `@api_view` decorator will ensure that this property is automatically to a list of `Parser` instances, based on the `parser_classes` set on the view or based on the `DEFAULT_PARSERS` setting. +The `APIView` class or `@api_view` decorator will ensure that this property is automatically set to a list of `Parser` instances, based on the `parser_classes` set on the view or based on the `DEFAULT_PARSER_CLASSES` setting.  You won't typically need to access this property. @@ -51,7 +51,7 @@ If a client sends a request with a content-type that cannot be parsed then a `Un  # Authentication -REST framework provides flexbile, per-request authentication, that gives you the abilty to: +REST framework provides flexible, per-request authentication, that gives you the ability to:  * Use different authentication policies for different parts of your API.  * Support the use of multiple authentication policies. @@ -75,7 +75,7 @@ For more details see the [authentication documentation].  ## .authenticators -The `APIView` class or `@api_view` decorator will ensure that this property is automatically to a list of `Authentication` instances, based on the `authentication_classes` set on the view or based on the `DEFAULT_AUTHENTICATORS` setting. +The `APIView` class or `@api_view` decorator will ensure that this property is automatically set to a list of `Authentication` instances, based on the `authentication_classes` set on the view or based on the `DEFAULT_AUTHENTICATORS` setting.  You won't typically need to access this property. @@ -83,7 +83,7 @@ You won't typically need to access this property.  # Browser enhancements -REST framework supports a few browser enhancments such as browser-based `PUT` and `DELETE` forms. +REST framework supports a few browser enhancements such as browser-based `PUT` and `DELETE` forms.  ## .method @@ -125,4 +125,4 @@ Note that due to implementation reasons the `Request` class does not inherit fro  [cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion  [parsers documentation]: parsers.md  [authentication documentation]: authentication.md -[browser enhancements documentation]: ../topics/browser-enhancements.md
\ No newline at end of file +[browser enhancements documentation]: ../topics/browser-enhancements.md diff --git a/docs/api-guide/responses.md b/docs/api-guide/responses.md index 395decda..794f9377 100644 --- a/docs/api-guide/responses.md +++ b/docs/api-guide/responses.md @@ -86,7 +86,7 @@ The `Response` class extends `SimpleTemplateResponse`, and all the usual attribu  **Signature:** `.render()` -As with any other `TemplateResponse`, this methd 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. +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. diff --git a/docs/api-guide/reverse.md b/docs/api-guide/reverse.md index 12346eb4..19930dc3 100644 --- a/docs/api-guide/reverse.md +++ b/docs/api-guide/reverse.md @@ -6,7 +6,7 @@  >  > — Roy Fielding, [Architectural Styles and the Design of Network-based Software Architectures][cite] -As a rule, it's probably better practice to return absolute URIs from you Web APIs, such as `http://example.com/foobar`, rather than returning relative URIs, such as `/foobar`. +As a rule, it's probably better practice to return absolute URIs from your Web APIs, such as `http://example.com/foobar`, rather than returning relative URIs, such as `/foobar`.  The advantages of doing so are: diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 47958fe3..0cdae1ce 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -47,7 +47,7 @@ The first part of serializer class defines the fields that get serialized/deseri  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(instance=comment) +    serializer = CommentSerializer(comment)      serializer.data      # {'email': u'leila@example.com', 'content': u'foo bar', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)} @@ -65,18 +65,54 @@ Deserialization is similar.  First we parse a stream into python native datatype  ...then we restore those native datatypes into a fully populated object instance. -    serializer = CommentSerializer(data) +    serializer = CommentSerializer(data=data)      serializer.is_valid()      # True      serializer.object      # <Comment object at 0x10633b2d0>      >>> serializer.deserialize('json', stream) +When deserializing data, we can either create a new instance, or update an existing instance. + +    serializer = CommentSerializer(data=data)           # Create new instance +    serializer = CommentSerializer(comment, data=data)  # Update `instance` +  ## 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` and `.non_field_errors` properties will contain the resulting error messages. -**TODO: Describe validation in more depth** +### Field-level validation + +You can specify custom field-level validation by adding `.validate_<fieldname>` methods to your `Serializer` subclass. These are analagous to `.clean_<fieldname>` methods on Django forms, but accept slightly different arguments. + +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). + +Your `validate_<fieldname>` methods should either just return the `attrs` dictionary or raise a `ValidationError`. For example: + +    from rest_framework import serializers + +    class BlogPostSerializer(serializers.Serializer): +        title = serializers.CharField(max_length=100) +        content = serializers.CharField() + +        def validate_title(self, attrs, source): +            """ +            Check that the blog post is about Django. +            """ +            value = attrs[source] +            if "django" not in value.lower(): +                raise serializers.ValidationError("Blog post is not about Django") +            return attrs + +### 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`. + +## Saving object state + +Serializers also include a `.save()` method that you can override if you want to provide a method of persisting the state of a deserialized object.  The default behavior of the method is to simply call `.save()` on the deserialized object instance. + +The generic views provided by REST framework call the `.save()` method when updating or creating entities.    ## Dealing with nested objects @@ -86,21 +122,21 @@ where some of the attributes of an object might not be simple datatypes such as  The `Serializer` class is itself a type of `Field`, and can be used to represent relationships where one object type is nested inside another.      class UserSerializer(serializers.Serializer): -        email = serializers.EmailField() -        username = serializers.CharField() -         -        def restore_object(self, attrs, instance=None): -            return User(**attrs) - +        email = serializers.Field() +        username = serializers.Field()      class CommentSerializer(serializers.Serializer):          user = UserSerializer() -        title = serializers.CharField() -        content = serializers.CharField(max_length=200) -        created = serializers.DateTimeField() -         -        def restore_object(self, attrs, instance=None): -            return Comment(**attrs) +        title = serializers.Field() +        content = serializers.Field() +        created = serializers.Field() + +--- + +**Note**: Nested serializers are only suitable for read-only representations, as there are cases where they would have ambiguous or non-obvious behavior if used when updating instances.  For read-write representations you should always use a flat representation, by using one of the `RelatedField` subclasses. + +--- +  ## Creating custom fields @@ -114,7 +150,6 @@ Let's look at an example of serializing a class that represents an RGB color val          """          A color represented in the RGB colorspace.          """ -          def __init__(self, red, green, blue):              assert(red >= 0 and green >= 0 and blue >= 0)              assert(red < 256 and green < 256 and blue < 256) @@ -124,7 +159,6 @@ Let's look at an example of serializing a class that represents an RGB color val          """          Color objects are serialized into "rgb(#, #, #)" notation.          """ -          def to_native(self, obj):              return "rgb(%d, %d, %d)" % (obj.red, obj.green, obj.blue) @@ -156,7 +190,7 @@ As an example, let's create a field that can be used represent the class name of  # ModelSerializers  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 corrospond to the Model fields. +The `ModelSerializer` class lets you automatically create a Serializer class with fields that correspond to the Model fields.      class AccountSerializer(serializers.ModelSerializer):          class Meta: @@ -169,13 +203,13 @@ The `ModelSerializer` class lets you automatically create a Serializer class wit  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 = CharField(source='get_absolute_url', readonly=True) +        url = CharField(source='get_absolute_url', read_only=True)          group = NaturalKeyField()          class Meta:              model = Account -Extra fields can corrospond to any property or callable on the model. +Extra fields can correspond to any property or callable on the model.  ## Relational fields @@ -187,7 +221,7 @@ The `PrimaryKeyRelatedField` and `HyperlinkedRelatedField` fields provide altern  The `ModelSerializer` class can itself be used as a field, in order to serialize relationships using nested representations. -The `RelatedField` class may be subclassed to create a custom represenation of a relationship.  The subclass should override `.to_native()`, and optionally `.from_native()` if deserialization is supported. +The `RelatedField` class may be subclassed to create a custom representation of a relationship.  The subclass should override `.to_native()`, and optionally `.from_native()` if deserialization is supported.  All the relational fields may be used for any relationship or reverse relationship on a model. @@ -204,40 +238,54 @@ For example:  ## Specifiying nested serialization -The default `ModelSerializer` uses primary keys for relationships, but you can also easily generate nested representations using the `nested` option: +The default `ModelSerializer` uses primary keys for relationships, but you can also easily generate nested representations using the `depth` option:      class AccountSerializer(serializers.ModelSerializer):          class Meta:              model = Account              exclude = ('id',) -            nested = True +            depth = 1 -The `nested` option may be set to either `True`, `False`, or an integer value.  If given an integer value it indicates the depth of relationships that should be traversed before reverting to a flat representation. +The `depth` option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation. -When serializing objects using a nested representation any occurances of recursion will be recognised, and will fall back to using a flat representation. +## Customising the default fields -## Customising the default fields used by a ModelSerializer +You can create customized subclasses of `ModelSerializer` that use a different set of default fields for the representation, by overriding various `get_<field_type>_field` methods. +Each of these methods may either return a field or serializer instance, or `None`. +### get_pk_field -    class AccountSerializer(serializers.ModelSerializer): -        class Meta: -            model = Account +**Signature**: `.get_pk_field(self, model_field)` -        def get_pk_field(self, model_field): -            return serializers.Field(readonly=True) +Returns the field instance that should be used to represent the pk field. -        def get_nested_field(self, model_field): -            return serializers.ModelSerializer() +### get_nested_field -        def get_related_field(self, model_field, to_many=False): -            queryset = model_field.rel.to._default_manager -            if to_many: -                return return serializers.ManyRelatedField(queryset=queryset) -            return serializers.RelatedField(queryset=queryset) +**Signature**: `.get_nested_field(self, model_field)` + +Returns the field instance that should be used to represent a related field when `depth` is specified as being non-zero. + +### get_related_field + +**Signature**: `.get_related_field(self, model_field, to_many=False)` + +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. + +### get_field + +**Signature**: `.get_field(self, model_field)` + +Returns the field instance that should be used for non-relational, non-pk fields. + +### Example: + +The following custom model serializer could be used as a base class for model serializers that should always exclude the pk by default. + +    class NoPKModelSerializer(serializers.ModelSerializer): +        def get_pk_field(self, model_field): +            return None -        def get_field(self, model_field): -            return serializers.ModelField(model_field=model_field)  [cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index f473128e..4f87b30d 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -11,10 +11,10 @@ Configuration for REST framework is all namespaced inside a single Django settin  For example your project's `settings.py` file might include something like this:      REST_FRAMEWORK = { -        'DEFAULT_RENDERERS': ( +        'DEFAULT_RENDERER_CLASSES': (              'rest_framework.renderers.YAMLRenderer', -        ) -        'DEFAULT_PARSERS': ( +        ), +        'DEFAULT_PARSER_CLASSES': (              'rest_framework.parsers.YAMLParser',          )      } @@ -26,11 +26,15 @@ you should use the `api_settings` object.  For example.      from rest_framework.settings import api_settings -    print api_settings.DEFAULT_AUTHENTICATION +    print api_settings.DEFAULT_AUTHENTICATION_CLASSES  The `api_settings` object will check for any user-defined settings, and otherwise fallback to the default values.  Any setting that uses string import paths to refer to a class will automatically import and return the referenced class, instead of the string literal. -## DEFAULT_RENDERERS +--- + +# API Reference + +## DEFAULT_RENDERER_CLASSES  A list or tuple of renderer classes, that determines the default set of renderers that may be used when returning a `Response` object. @@ -38,11 +42,11 @@ Default:      (          'rest_framework.renderers.JSONRenderer', -        'rest_framework.renderers.BrowsableAPIRenderer' +        'rest_framework.renderers.BrowsableAPIRenderer',          'rest_framework.renderers.TemplateHTMLRenderer'      ) -## DEFAULT_PARSERS +## DEFAULT_PARSER_CLASSES  A list or tuple of parser classes, that determines the default set of parsers used when accessing the `request.DATA` property. @@ -53,7 +57,7 @@ Default:          'rest_framework.parsers.FormParser'      ) -## DEFAULT_AUTHENTICATION +## DEFAULT_AUTHENTICATION_CLASSES  A list or tuple of authentication classes, that determines the default set of authenticators used when accessing the `request.user` or `request.auth` properties. @@ -64,25 +68,29 @@ Default:          'rest_framework.authentication.UserBasicAuthentication'      ) -## DEFAULT_PERMISSIONS +## DEFAULT_PERMISSION_CLASSES  A list or tuple of permission classes, that determines the default set of permissions checked at the start of a view. -Default: `()` +Default: + +    ( +        'rest_framework.permissions.AllowAny', +    ) -## DEFAULT_THROTTLES +## DEFAULT_THROTTLE_CLASSES  A list or tuple of throttle classes, that determines the default set of throttles checked at the start of a view.  Default: `()` -## DEFAULT_MODEL_SERIALIZER +## DEFAULT_MODEL_SERIALIZER_CLASS  **TODO**  Default: `rest_framework.serializers.ModelSerializer` -## DEFAULT_PAGINATION_SERIALIZER +## DEFAULT_PAGINATION_SERIALIZER_CLASS  **TODO** diff --git a/docs/api-guide/status-codes.md b/docs/api-guide/status-codes.md index 401f45ce..b50c96ae 100644 --- a/docs/api-guide/status-codes.md +++ b/docs/api-guide/status-codes.md @@ -87,7 +87,7 @@ Response status codes beginning with the digit "5" indicate cases in which the s      HTTP_503_SERVICE_UNAVAILABLE      HTTP_504_GATEWAY_TIMEOUT      HTTP_505_HTTP_VERSION_NOT_SUPPORTED -    HTTP_511_NETWORD_AUTHENTICATION_REQUIRED +    HTTP_511_NETWORK_AUTHENTICATION_REQUIRED  [rfc2324]: http://www.ietf.org/rfc/rfc2324.txt diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 3fb95ae3..b03bc9e0 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -27,13 +27,13 @@ If any throttle check fails an `exceptions.Throttled` exception will be raised,  ## Setting the throttling policy -The default throttling policy may be set globally, using the `DEFAULT_THROTTLES` and `DEFAULT_THROTTLE_RATES` settings.  For example. +The default throttling policy may be set globally, using the `DEFAULT_THROTTLE_CLASSES` and `DEFAULT_THROTTLE_RATES` settings.  For example.      REST_FRAMEWORK = { -        'DEFAULT_THROTTLES': ( -            'rest_framework.throttles.AnonThrottle', -            'rest_framework.throttles.UserThrottle', -        ) +        'DEFAULT_THROTTLE_CLASSES': ( +            'rest_framework.throttling.AnonRateThrottle', +            'rest_framework.throttling.UserRateThrottle' +        ),          'DEFAULT_THROTTLE_RATES': {              'anon': '100/day',              'user': '1000/day' @@ -63,6 +63,8 @@ Or, if you're using the `@api_view` decorator with function based views.          }          return Response(content) +--- +  # API Reference  ## AnonRateThrottle @@ -78,7 +80,7 @@ The allowed request rate is determined from one of the following (in order of pr  ## UserRateThrottle -The `UserThrottle` will throttle users to a given rate of requests across the API.  The user id is used to generate a unique key to throttle against.  Unauthenticted requests will fall back to using the IP address of the incoming request to generate a unique key to throttle against. +The `UserThrottle` will throttle users to a given rate of requests across the API.  The user id is used to generate a unique key to throttle against.  Unauthenticated requests will fall back to using the IP address of the incoming request to generate a unique key to throttle against.  The allowed request rate is determined from one of the following (in order of preference). @@ -98,10 +100,10 @@ For example, multiple user throttle rates could be implemented by using the foll  ...and the following settings.      REST_FRAMEWORK = { -        'DEFAULT_THROTTLES': ( +        'DEFAULT_THROTTLE_CLASSES': (              'example.throttles.BurstRateThrottle', -            'example.throttles.SustainedRateThrottle', -        ) +            'example.throttles.SustainedRateThrottle' +        ),          'DEFAULT_THROTTLE_RATES': {              'burst': '60/min',              'sustained': '1000/day' @@ -112,7 +114,7 @@ For example, multiple user throttle rates could be implemented by using the foll  ## ScopedRateThrottle -The `ScopedThrottle` class can be used to restrict access to specific parts of the API.  This throttle will only be applied if the view that is being accessed includes a `.throttle_scope` property.  The unique throttle key will then be formed by concatenating the "scope" of the request with the unqiue user id or IP address. +The `ScopedThrottle` class can be used to restrict access to specific parts of the API.  This throttle will only be applied if the view that is being accessed includes a `.throttle_scope` property.  The unique throttle key will then be formed by concatenating the "scope" of the request with the unique user id or IP address.  The allowed request rate is determined by the `DEFAULT_THROTTLE_RATES` setting using a key from the request "scope". @@ -133,9 +135,9 @@ For example, given the following views...  ...and the following settings.      REST_FRAMEWORK = { -        'DEFAULT_THROTTLES': ( -            'rest_framework.throttles.ScopedRateThrottle', -        ) +        'DEFAULT_THROTTLE_CLASSES': ( +            'rest_framework.throttling.ScopedRateThrottle' +        ),          'DEFAULT_THROTTLE_RATES': {              'contacts': '1000/day',              'uploads': '20/day' @@ -144,10 +146,12 @@ For example, given the following views...  User requests to either `ContactListView` or `ContactDetailView` would be restricted to a total of 1000 requests per-day.  User requests to `UploadView` would be restricted to 20 requests per day. +--- +  # Custom throttles  To create a custom throttle, override `BaseThrottle` and implement `.allow_request(request, view)`.  The method should return `True` if the request should be allowed, and `False` otherwise. -Optionally you may also override the `.wait()` method.  If implemented, `.wait()` should return a recomended number of seconds to wait before attempting the next request, or `None`.  The `.wait()` method will only be called if `.allow_request()` has previously returned `False`. +Optionally you may also override the `.wait()` method.  If implemented, `.wait()` should return a recommended number of seconds to wait before attempting the next request, or `None`.  The `.wait()` method will only be called if `.allow_request()` has previously returned `False`.  [permissions]: permissions.md diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index cbfa2e28..5b072827 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -27,14 +27,14 @@ For example:          * Only admin users are able to access this view.          """          authentication_classes = (authentication.TokenAuthentication,) -        permission_classes = (permissions.IsAdmin,) +        permission_classes = (permissions.IsAdminUser,)          def get(self, request, format=None):              """              Return a list of all users.              """ -            users = [user.username for user in User.objects.all()] -            return Response(users) +            usernames = [user.username for user in User.objects.all()] +            return Response(usernames)  ## API policy attributes @@ -118,9 +118,51 @@ You won't typically need to override this method.  >  > — [Nick Coghlan][cite2] -REST framework also gives you to work with regular function based views... +REST framework also allows you to work with regular function based views. It provides a set of simple decorators that wrap your function based views to ensure they receive an instance of `Request` (rather than the usual Django `HttpRequest`) and allows them to return a `Response` (instead of a Django `HttpResponse`), and allow you to configure how the request is processed. -**[TODO]** +## @api_view() + +**Signature:** `@api_view(http_method_names)` + +The core of this functionality is the `api_view` decorator, which takes a list of HTTP methods that your view should respond to. For example, this is how you would write a very simple view that just manually returns some data: + +    from rest_framework.decorators import api_view + +    @api_view(['GET']) +    def hello_world(request): +        return Response({"message": "Hello, world!"}) + + +This view will use the default renderers, parsers, authentication classes etc specified in the [settings](settings). + +## API policy decorators + +To override the default settings, REST framework provides a set of additional decorators which can be added to your views. These must come *after* (below) the `@api_view` decorator. For example, to create a view that uses a [throttle](throttling) to ensure it can only be called once per day by a particular user, use the `@throttle_classes` decorator, passing a list of throttle classes: + +    from rest_framework.decorators import api_view, throttle_classes +    from rest_framework.throttling import UserRateThrottle + +    class OncePerDayUserThrottle(UserRateThrottle): +            rate = '1/day' + +    @api_view(['GET']) +    @throttle_classes([OncePerDayUserThrottle]) +    def view(request): +        return Response({"message": "Hello for today! See you tomorrow!"}) + +These decorators correspond to the attributes set on `APIView` subclasses, described above. + +The available decorators are: + +* `@renderer_classes(...)` +* `@parser_classes(...)` +* `@authentication_classes(...)` +* `@throttle_classes(...)` +* `@permission_classes(...)` + +Each of these decorators takes a single argument which must be a list or tuple of classes.  [cite]: http://reinout.vanrees.org/weblog/2011/08/24/class-based-views-usage.html -[cite2]: http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html
\ No newline at end of file +[cite2]: http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html +[settings]: api-guide/settings.md +[throttling]: api-guide/throttling.md | 
