Merge branch 'master' of github.com:tomchristie/django-rest-framework

This commit is contained in:
Carlton Gibson 2014-12-01 11:22:39 +01:00
commit ef26f43de4
97 changed files with 2410 additions and 5678 deletions

2
.gitignore vendored
View File

@ -3,7 +3,7 @@
*~ *~
.* .*
html/ site/
htmlcov/ htmlcov/
coverage/ coverage/
build/ build/

View File

@ -1,38 +1,41 @@
language: python language: python
python: 2.7 sudo: false
env: env:
- TOX_ENV=flake8 - TOX_ENV=py27-flake8
- TOX_ENV=py3.4-django1.7 - TOX_ENV=py27-docs
- TOX_ENV=py3.3-django1.7 - TOX_ENV=py34-django17
- TOX_ENV=py3.2-django1.7 - TOX_ENV=py33-django17
- TOX_ENV=py2.7-django1.7 - TOX_ENV=py32-django17
- TOX_ENV=py3.4-django1.6 - TOX_ENV=py27-django17
- TOX_ENV=py3.3-django1.6 - TOX_ENV=py34-django16
- TOX_ENV=py3.2-django1.6 - TOX_ENV=py33-django16
- TOX_ENV=py2.7-django1.6 - TOX_ENV=py32-django16
- TOX_ENV=py2.6-django1.6 - TOX_ENV=py27-django16
- TOX_ENV=py3.4-django1.5 - TOX_ENV=py26-django16
- TOX_ENV=py3.3-django1.5 - TOX_ENV=py34-django15
- TOX_ENV=py3.2-django1.5 - TOX_ENV=py33-django15
- TOX_ENV=py2.7-django1.5 - TOX_ENV=py32-django15
- TOX_ENV=py2.6-django1.5 - TOX_ENV=py27-django15
- TOX_ENV=py2.7-django1.4 - TOX_ENV=py26-django15
- TOX_ENV=py2.6-django1.4 - TOX_ENV=py27-django14
- TOX_ENV=py3.4-djangomaster - TOX_ENV=py26-django14
- TOX_ENV=py3.3-djangomaster - TOX_ENV=py34-djangomaster
- TOX_ENV=py2.7-djangomaster - TOX_ENV=py33-djangomaster
- TOX_ENV=py32-djangomaster
- TOX_ENV=py27-djangomaster
matrix: matrix:
fast_finish: true fast_finish: true
allow_failures: allow_failures:
- env: TOX_ENV=py3.4-djangomaster - env: TOX_ENV=py34-djangomaster
- env: TOX_ENV=py3.3-djangomaster - env: TOX_ENV=py33-djangomaster
- env: TOX_ENV=py2.7-djangomaster - env: TOX_ENV=py32-djangomaster
- env: TOX_ENV=py27-djangomaster
install: install:
- "pip install tox --download-cache $HOME/.pip-cache" - pip install tox
script: script:
- tox -e $TOX_ENV - tox -e $TOX_ENV

View File

@ -101,15 +101,15 @@ There are many great markdown editors that make working with the documentation r
## Building the documentation ## 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 html output into the `html` 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 ## Language style

1
docs/CNAME Normal file
View File

@ -0,0 +1 @@
www.django-rest-framework.org

View File

@ -1,4 +1,4 @@
<a class="github" href="authentication.py"></a> source: authentication.py
# Authentication # Authentication

View File

@ -1,4 +1,4 @@
<a class="github" href="negotiation.py"></a> source: negotiation.py
# Content negotiation # Content negotiation

View File

@ -1,4 +1,4 @@
<a class="github" href="exceptions.py"></a> source: exceptions.py
# Exceptions # 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)` **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". 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)` **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". 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". 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 [cite]: http://www.doughellmann.com/articles/how-tos/python-exception-handling/index.html
[authentication]: authentication.md [authentication]: authentication.md

View File

@ -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 # 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: 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` ### `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. 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`. 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` ### `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. 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. 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` ### `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` ### `error_messages`
A dictionary of error codes to 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` ### `label`
A short text string that may be used as the name of the field in HTML form fields or other descriptive elements. A short text string that may be used as the name of the field in HTML form fields or other descriptive elements.
@ -72,122 +81,66 @@ 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. 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 # Use a radio input instead of a select input.
from django.utils.timezone import now color_channel = serializers.ChoiceField(
choices=['red', 'green', 'blue']
class Account(models.Model): style = {'base_template': 'radio.html'}
owner = models.ForeignKey('auth.user')
name = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
payment_expiry = models.DateTimeField()
def has_expired(self):
return now() > self.payment_expiry
A serializer definition that looked like this:
from rest_framework import serializers
class AccountSerializer(serializers.HyperlinkedModelSerializer):
expired = serializers.Field(source='has_expired')
class Meta:
model = Account
fields = ('url', 'owner', 'name', 'expired')
Would produce output similar to:
{
'url': 'http://example.com/api/accounts/3/',
'owner': 'http://example.com/api/users/12/',
'name': 'FooCorp business account',
'expired': True
} }
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. **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.
You can customize this behavior by overriding the `.to_native(self, value)` method.
## WritableField
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
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.
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'))`
**Signature:** `ModelField(model_field=<Django ModelField instance>)`
## 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. 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:
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('get_days_since_joined')
class Meta:
model = User
def get_days_since_joined(self, obj):
return (now() - obj.date_joined).days
--- ---
# Typed Fields # Boolean fields
These fields represent basic datatypes, and support both reading and writing values.
## BooleanField ## BooleanField
A Boolean representation. A boolean representation.
Corresponds to `django.db.models.fields.BooleanField`. Corresponds to `django.db.models.fields.BooleanField`.
**Signature:** `BooleanField()`
## NullBooleanField
A boolean representation that also accepts `None` as a valid value.
Corresponds to `django.db.models.fields.NullBooleanField`.
**Signature:** `NullBooleanField()`
---
# String fields
## CharField ## CharField
A text representation, optionally validates the text to be shorter than `max_length` and longer than `min_length`. 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.
Corresponds to `django.db.models.fields.CharField` Corresponds to `django.db.models.fields.CharField` or `django.db.models.fields.TextField`.
or `django.db.models.fields.TextField`.
**Signature:** `CharField(max_length=None, min_length=None, allow_none=False)` **Signature:** `CharField(max_length=None, min_length=None, allow_blank=False)`
## URLField - `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`.
Corresponds to `django.db.models.fields.URLField`. Uses Django's `django.core.validators.URLValidator` for validation. 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.
**Signature:** `URLField(max_length=200, min_length=None)`
## SlugField
Corresponds to `django.db.models.fields.SlugField`.
**Signature:** `SlugField(max_length=50, min_length=None)`
## ChoiceField
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.
**Signature:** `ChoiceField(choices=(), blank_display_value=None)`
## EmailField ## EmailField
@ -195,76 +148,50 @@ A text representation, validates the text to be a valid e-mail address.
Corresponds to `django.db.models.fields.EmailField` Corresponds to `django.db.models.fields.EmailField`
**Signature:** `EmailField(max_length=None, min_length=None, allow_blank=False)`
## RegexField ## RegexField
A text representation, that validates the given value matches against a certain regular expression. A text representation, that validates the given value matches against a certain regular expression.
Corresponds to `django.forms.fields.RegexField`.
**Signature:** `RegexField(regex, max_length=None, min_length=None, allow_blank=False)`
The mandatory `regex` argument may either be a string, or a compiled python regular expression object.
Uses Django's `django.core.validators.RegexValidator` for validation. Uses Django's `django.core.validators.RegexValidator` for validation.
Corresponds to `django.forms.fields.RegexField` ## SlugField
**Signature:** `RegexField(regex, max_length=None, min_length=None)` A `RegexField` that validates the input against the pattern `[a-zA-Z0-9_-]+`.
## DateTimeField Corresponds to `django.db.models.fields.SlugField`.
A date and time representation. **Signature:** `SlugField(max_length=50, min_length=None, allow_blank=False)`
Corresponds to `django.db.models.fields.DateTimeField` ## URLField
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. A `RegexField` that validates the input against a URL matching pattern. Expects fully qualified URLs of the form `http://<host>/<path>`.
If you want to override this behavior, you'll need to declare the `DateTimeField` explicitly on the serializer. For example: Corresponds to `django.db.models.fields.URLField`. Uses Django's `django.core.validators.URLValidator` for validation.
class CommentSerializer(serializers.ModelSerializer): **Signature:** `URLField(max_length=200, min_length=None, allow_blank=False)`
created = serializers.DateTimeField()
class Meta: ---
model = Comment
Note that by default, datetime representations are determined by the renderer in use, although this can be explicitly overridden as detailed below. # Numeric fields
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)`
* `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']`.
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'`)
## DateField
A date representation.
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.
* `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'`)
## 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.
* `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'`)
## IntegerField ## IntegerField
An integer representation. 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` Corresponds to `django.db.models.fields.IntegerField`, `django.db.models.fields.SmallIntegerField`, `django.db.models.fields.PositiveIntegerField` and `django.db.models.fields.PositiveSmallIntegerField`.
**Signature**: `IntegerField(max_value=None, min_value=None)`
- `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.
## FloatField ## FloatField
@ -272,17 +199,28 @@ A floating point representation.
Corresponds to `django.db.models.fields.FloatField`. Corresponds to `django.db.models.fields.FloatField`.
**Signature**: `FloatField(max_value=None, min_value=None)`
- `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 ## DecimalField
A decimal representation, represented in Python by a Decimal instance. A decimal representation, represented in Python by a `Decimal` instance.
Has two required arguments: 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. - `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. - `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.
For example, to validate numbers up to 999 with a resolution of 2 decimal places, you would use: #### Example usage
To validate numbers up to 999 with a resolution of 2 decimal places, you would use:
serializers.DecimalField(max_digits=5, decimal_places=2) serializers.DecimalField(max_digits=5, decimal_places=2)
@ -294,9 +232,101 @@ This field also takes an optional argument, `coerce_to_string`. If set to `True`
If unset, this will default to the same value as the `COERCE_DECIMAL_TO_STRING` setting, which is `True` unless set otherwise. If unset, this will default to the same value as the `COERCE_DECIMAL_TO_STRING` setting, which is `True` unless set otherwise.
**Signature:** `DecimalField(max_digits, decimal_places, coerce_to_string=None)` ---
Corresponds to `django.db.models.fields.DecimalField`. # Date and time fields
## DateTimeField
A date and time representation.
Corresponds to `django.db.models.fields.DateTimeField`.
**Signature:** `DateTimeField(format=None, input_formats=None)`
* `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']`.
#### `DateTimeField` 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 datetimes should be used. (eg `'2013-01-29T12:34:56.000000Z'`)
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`.
#### `auto_now` and `auto_now_add` model fields.
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.
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
A date representation.
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 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']`.
#### `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.
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 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']`.
#### `TimeField` 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 times should be used. (eg `'12:34:56.000000'`)
---
# Choice selection fields
## ChoiceField
A field that can accept a value out of a limited set of choices.
Used by `ModelSerializer` to automatically generate fields if the corresponding model field includes a `choices=…` argument.
**Signature:** `ChoiceField(choices)`
- `choices` - A list of valid values, or a list of `(key, display_name)` tuples.
## MultipleChoiceField
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.
**Signature:** `MultipleChoiceField(choices)`
- `choices` - A list of valid values, or a list of `(key, display_name)` tuples.
---
# File upload fields
#### Parsers and file uploads.
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 ## FileField
@ -304,34 +334,128 @@ A file representation. Performs Django's standard FileField validation.
Corresponds to `django.forms.fields.FileField`. Corresponds to `django.forms.fields.FileField`.
**Signature:** `FileField(max_length=None, allow_empty_file=False)` **Signature:** `FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)`
- `max_length` designates the maximum length for the file name. - `max_length` - Designates the maximum length for the file name.
- `allow_empty_file` - Designates if empty files are allowed.
- `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 ## ImageField
An image representation. An image representation. Validates the uploaded file content as matching a known image format.
Corresponds to `django.forms.fields.ImageField`. Corresponds to `django.forms.fields.ImageField`.
Requires either the `Pillow` package or `PIL` package. The `Pillow` package is recommended, as `PIL` is no longer actively maintained. **Signature:** `ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)`
Signature and validation is the same as with `FileField`. - `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.
--- ---
**Note:** `FileFields` and `ImageFields` are only suitable for use with MultiPartParser, since e.g. json doesn't support file uploads. # Composite fields
Django's regular [FILE_UPLOAD_HANDLERS] are used for handling uploaded files.
## 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.
---
# 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 # 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 ## 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) assert(red < 256 and green < 256 and blue < 256)
self.red, self.green, self.blue = red, green, blue self.red, self.green, self.blue = red, green, blue
class ColourField(serializers.WritableField): class ColorField(serializers.Field):
""" """
Color objects are serialized into "rgb(#, #, #)" notation. 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) 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(')') data = data.strip('rgb(').rstrip(')')
red, green, blue = [int(col) for col in data.split(',')] red, green, blue = [int(col) for col in data.split(',')]
return Color(red, green, blue) 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 `.get_attribute()` and/or `.get_value()`.
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()`.
As an example, let's create a field that can be used represent the class name of the object being serialized: 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): 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. Serialize the object's class name.
""" """

View File

@ -1,4 +1,4 @@
<a class="github" href="filters.py"></a> source: filters.py
# Filtering # Filtering

View File

@ -1,4 +1,4 @@
<a class="github" href="urlpatterns.py"></a> source: urlpatterns.py
# Format suffixes # Format suffixes

View File

@ -1,5 +1,11 @@
<a class="github" href="mixins.py"></a> source: mixins.py
<a class="github" href="generics.py"></a> 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 # Generic views
@ -7,7 +13,7 @@
> >
> &mdash; [Django Documentation][cite] > &mdash; [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. 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 20
return 100 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. * `perform_create(self, serializer)` - Called by `CreateModelMixin` when saving a new object instance.
* `post_save(self, obj, created=False)` - A hook that is called after saving an object. * `perform_update(self, serializer)` - Called by `UpdateModelMixin` when saving an existing object instance.
* `pre_delete(self, obj)` - A hook that is called before deleting an object. * `perform_destroy(self, instance)` - Called by `DestroyModelMixin` when deleting an object instance.
* `post_delete(self, obj)` - A hook that is called after deleting an object.
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): def perform_create(self, serializer):
""" serializer.save(user=self.request.user)
Set the object's owner, based on the incoming request.
"""
obj.owner = self.request.user
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. 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)
**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**: **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 serializer_class = UserSerializer
lookup_fields = ('account', 'username') 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 ## Creating custom base classes

View File

@ -1,5 +1,11 @@
<a class="github" href="metadata.py"></a> <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 # 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. > [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.

View File

@ -1,4 +1,4 @@
<a class="github" href="pagination.py"></a> source: pagination.py
# Pagination # Pagination

View File

@ -1,4 +1,4 @@
<a class="github" href="parsers.py"></a> source: parsers.py
# Parsers # 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 ## 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,) parser_classes = (YAMLParser,)
def post(self, request, format=None): 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. 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. 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 ## 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. 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 ## 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. 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 ## 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`. 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,) parser_classes = (FileUploadParser,)
def put(self, request, filename, format=None): def put(self, request, filename, format=None):
file_obj = request.FILES['file'] file_obj = request.data['file']
# ... # ...
# do some staff with uploaded 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. 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: The arguments passed to `.parse()` are:
@ -161,7 +161,7 @@ By default this will include the following keys: `view`, `request`, `args`, `kwa
## Example ## 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): class PlainTextParser(BaseParser):
""" """

View File

@ -1,4 +1,4 @@
<a class="github" href="permissions.py"></a> source: permissions.py
# Permissions # Permissions

View File

@ -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 # 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 # 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. 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.
@ -37,14 +57,14 @@ In order to explain the various types of relational fields, we'll use a couple o
def __unicode__(self): def __unicode__(self):
return '%d: %s' % (self.order, self.title) 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. For example, the following serializer.
class AlbumSerializer(serializers.ModelSerializer): class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.RelatedField(many=True) tracks = serializers.StringRelatedField(many=True)
class Meta: class Meta:
model = Album model = Album
@ -99,9 +119,9 @@ By default this field is read-write, although you can change this behavior using
**Arguments**: **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`. * `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. * `allow_null` - If set to `True`, the field will accept values of `None` or the empty string for nullable relationships. Defaults to `False`.
* `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`.
## HyperlinkedRelatedField ## HyperlinkedRelatedField
@ -110,8 +130,11 @@ By default this field is read-write, although you can change this behavior using
For example, the following serializer: For example, the following serializer:
class AlbumSerializer(serializers.ModelSerializer): class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.HyperlinkedRelatedField(many=True, read_only=True, tracks = serializers.HyperlinkedRelatedField(
view_name='track-detail') many=True,
read_only=True,
view_name='track-detail'
)
class Meta: class Meta:
model = Album model = Album
@ -134,11 +157,12 @@ By default this field is read-write, although you can change this behavior using
**Arguments**: **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`. * `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. * `allow_null` - If set to `True`, the field will accept values of `None` or the empty string for nullable relationships. Defaults to `False`.
* `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`.
* `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_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. * `format` - If using format suffixes, hyperlinked fields will use the same format suffix for the target unless overridden by using the `format` argument.
## SlugRelatedField ## SlugRelatedField
@ -148,8 +172,11 @@ By default this field is read-write, although you can change this behavior using
For example, the following serializer: For example, the following serializer:
class AlbumSerializer(serializers.ModelSerializer): class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.SlugRelatedField(many=True, read_only=True, tracks = serializers.SlugRelatedField(
slug_field='title') many=True,
read_only=True,
slug_field='title'
)
class Meta: class Meta:
model = Album model = Album
@ -175,9 +202,9 @@ When using `SlugRelatedField` as a read-write field, you will normally want to e
**Arguments**: **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** * `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`. * `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. * `allow_null` - If set to `True`, the field will accept values of `None` or the empty string for nullable relationships. Defaults to `False`.
* `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`.
## HyperlinkedIdentityField ## HyperlinkedIdentityField
@ -224,7 +251,7 @@ For example, the following serializer:
fields = ('order', 'title') fields = ('order', 'title')
class AlbumSerializer(serializers.ModelSerializer): class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True) tracks = TrackSerializer(many=True, read_only=True)
class Meta: class Meta:
model = Album model = Album
@ -245,9 +272,9 @@ Would serialize to a nested representation like this:
# Custom relational fields # 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 ## Example
@ -256,7 +283,7 @@ For, example, we could define a relational field, to serialize a track to a cust
import time import time
class TrackListingField(serializers.RelatedField): class TrackListingField(serializers.RelatedField):
def to_native(self, value): def to_representation(self, value):
duration = time.strftime('%M:%S', time.gmtime(value.duration)) duration = time.strftime('%M:%S', time.gmtime(value.duration))
return 'Track %d: %s (%s)' % (value.order, value.name, duration) return 'Track %d: %s (%s)' % (value.order, value.name, duration)
@ -284,6 +311,16 @@ This custom field would then serialize to the following representation.
# Further notes # 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 ## 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: 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:
@ -350,7 +387,7 @@ 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. 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. Serialize tagged objects to a simple textual representation.
""" """
@ -362,7 +399,7 @@ We could define a custom field that could be used to serialize tagged instances,
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, Serialize bookmark instances using a bookmark serializer,
and note instances using a note serializer. and note instances using a note serializer.
@ -404,7 +441,6 @@ attributes are not configured to correctly match the URL conf.
#### get_object(self, queryset, view_name, view_args, view_kwargs) #### get_object(self, queryset, view_name, view_args, view_kwargs)
This method should the object that corresponds to the matched URL conf arguments. This method should the object that corresponds to the matched URL conf arguments.
May raise an `ObjectDoesNotExist` exception. May raise an `ObjectDoesNotExist` exception.
@ -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 # Third Party Packages
The following third party packages are also available. The following third party packages are also available.

View File

@ -1,4 +1,4 @@
<a class="github" href="renderers.py"></a> source: renderers.py
# Renderers # Renderers

View File

@ -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 # 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. 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 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. * 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]. 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.
## .DATA and .FILES
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 ## .QUERY_PARAMS
`request.QUERY_PARAMS` is a more correctly named synonym for `request.GET`. 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`.
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 ## .parsers
@ -43,7 +52,7 @@ 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. 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.

View File

@ -1,4 +1,4 @@
<a class="github" href="response.py"></a> source: response.py
# Responses # Responses

View File

@ -1,4 +1,4 @@
<a class="github" href="reverse.py"></a> source: reverse.py
# Returning URLs # Returning URLs

View File

@ -1,4 +1,4 @@
<a class="github" href="routers.py"></a> source: routers.py
# Routers # Routers

View File

@ -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 # 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. 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 ## Declaring Serializers
@ -24,7 +30,7 @@ Let's start by creating a simple object we can use for example purposes:
comment = Comment(email='leila@example.com', content='foo bar') 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: Declaring a serializer looks very similar to declaring a form:
@ -35,22 +41,6 @@ Declaring a serializer looks very similar to declaring a form:
content = serializers.CharField(max_length=200) content = serializers.CharField(max_length=200)
created = serializers.DateTimeField() 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 ## 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.
@ -67,21 +57,6 @@ At this point we've translated the model instance into Python native datatypes.
json json
# '{"email": "leila@example.com", "content": "foo bar", "created": "2012-08-22T16:20:09.822"}' # '{"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 ## 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...
@ -92,26 +67,90 @@ Deserialization is similar. First we parse a stream into Python native datatype
stream = StringIO(json) stream = StringIO(json)
data = JSONParser().parse(stream) 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 = CommentSerializer(data=data)
serializer.is_valid() serializer.is_valid()
# True # True
serializer.object serializer.validated_data
# <Comment object at 0x10633b2d0> # {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}
When deserializing data, we can either create a new instance, or update an existing instance. ## Saving instances
serializer = CommentSerializer(data=data) # Create new instance 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:
serializer = CommentSerializer(comment, data=data) # Update `comment`
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. class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True) # Update `comment` with partial data 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.
comment = serializer.save()
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:
# .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.
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 ## 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 = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid() serializer.is_valid()
@ -119,17 +158,26 @@ When deserializing data, you always need to call `is_valid()` before attempting
serializer.errors serializer.errors
# {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']} # {'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. 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 #### 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 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) title = serializers.CharField(max_length=100)
content = serializers.CharField() content = serializers.CharField()
def validate_title(self, attrs, source): def validate_title(self, value):
""" """
Check that the blog post is about Django. 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") raise serializers.ValidationError("Blog post is not about Django")
return attrs return value
#### Object-level validation #### 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 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() start = serializers.DateTimeField()
finish = serializers.DateTimeField() finish = serializers.DateTimeField()
def validate(self, attrs): def validate(self, data):
""" """
Check that the start is before the stop. 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") raise serializers.ValidationError("finish must occur after start")
return attrs return data
## Saving object state #### Validators
To save the deserialized objects created by a serializer, call the `.save()` method: Individual fields on a serializer can include validators, by declaring them on the field instance, for example:
if serializer.is_valid(): def multiple_of_ten(value):
serializer.save() if value % 10 != 0:
raise serializers.ValidationError('Not a multiple of ten')
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. class GameRecord(serializers.Serializer):
score = IntegerField(validators=[multiple_of_ten])
...
The generic views provided by REST framework call the `.save()` method when updating or creating entities. 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:
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']
)
For more information see the [validators documentation](validators.md).
## Partial updates
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.
# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
## Dealing with nested objects ## 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) content = serializers.CharField(max_length=200)
created = serializers.DateTimeField() 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 = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid() serializer.is_valid()
@ -214,6 +287,97 @@ Validation of nested objects will work the same as before. Errors with nested o
serializer.errors serializer.errors
# {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']} # {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']}
Similarly, the `.validated_data` property will include nested data structures.
#### Writing `.create()` methods for nested representations
If you're supporting writable nested representations you'll need to write `.create()` or `.update()` methods that handle saving multiple objects.
The following example demonstrates how you might handle creating a user with a nested profile object.
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
#### Writing `.update()` methods for nested representations
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?
* 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.
Here's an example for an `update()` method on our previous `UserSerializer` class.
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
user.username = validated_data.get('username', instance.username)
user.email = validated_data.get('email', instance.email)
user.save()
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()
return user
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.
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.
#### Handling saving related instances in model manager classes
An alternative to saving multiple related instances in the serializer is to write custom model manager classes handle creating the correct 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:
class UserManager(models.Manager):
...
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 ## Dealing with multiple objects
The `Serializer` class can also handle serializing or deserializing lists of objects. The `Serializer` class can also handle serializing or deserializing lists of objects.
@ -231,78 +395,9 @@ To serialize a queryset or list of objects instead of a single object instance,
# {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'} # {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'}
# ] # ]
#### Deserializing multiple objects for creation #### Deserializing multiple objects
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. 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.
This allows you to write views that create multiple items when a `POST` request is made.
For example:
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
#### Deserializing multiple objects for update
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.
This allows you to write views that update or create multiple items when a `PUT` request is made.
# 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.
By default bulk updates will be limited to updating instances that already exist in the provided queryset.
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.
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`.
Passing `allow_add_remove=True` ensures that any update operations will completely overwrite the existing queryset, rather than simply updating existing objects.
#### How identity is determined when performing bulk updates
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.
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 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
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.
## Including extra context ## Including extra context
@ -314,27 +409,45 @@ You can provide arbitrary additional context by passing a `context` argument whe
serializer.data serializer.data
# {'id': 6, 'owner': u'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'} # {'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 # ModelSerializer
Often you'll want serializer classes that map closely to model definitions. Often you'll want serializer classes that map closely to Django model definitions.
The `ModelSerializer` class lets you automatically create a Serializer class with fields that correspond to the Model fields.
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 AccountSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Account 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 ## Specifying which fields should be included
@ -347,6 +460,10 @@ For example:
model = Account model = Account
fields = ('id', 'account_name', 'users', 'created') 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 ## Specifying nested serialization
The default `ModelSerializer` uses primary keys for relationships, but you can also easily generate nested representations using the `depth` option: The default `ModelSerializer` uses primary keys for relationships, but you can also easily generate nested representations using the `depth` option:
@ -361,37 +478,6 @@ 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. 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
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:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
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.
## Specifying which fields should be write-only
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:
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'])
return user
## Specifying fields explicitly ## 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. 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.
@ -405,6 +491,41 @@ You can add extra fields to a `ModelSerializer` or override the default fields b
Extra fields can correspond to any property or callable on the model. 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:
model = Account
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.
## Specifying additional keyword arguments for fields.
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.
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')
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
## Relational fields ## Relational fields
When serializing model instances, there are a number of different ways you might choose to represent relationships. The default representation for `ModelSerializer` is to use the primary keys of the related instances. When serializing model instances, there are a number of different ways you might choose to represent relationships. The default representation for `ModelSerializer` is to use the primary keys of the related instances.
@ -415,7 +536,7 @@ For full details see the [serializer relations][relations] documentation.
## Inheritance of the 'Meta' class ## Inheritance of the 'Meta' class
The inner `Meta` class on serializers is not inherited from parent classes by default. This is the same behaviour 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: 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 AccountSerializer(MyBaseSerializer):
class Meta(MyBaseSerializer.Meta): class Meta(MyBaseSerializer.Meta):
@ -446,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. 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 AccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = Account model = Account
fields = ('url', 'account_name', 'users', 'created') fields = ('account_url', 'account_name', 'users', 'created')
lookup_field = 'slug' extra_field_kwargs = {
'url': {'view_name': 'accounts', 'lookup_field': 'account_name'}
'users': {'lookup_field': 'username'}
}
Note that the `lookup_field` will be used as the default on *all* hyperlinked fields, including both the URL identity, and any hyperlinked relationships. Alternatively you can set the fields on the serializer explicitly. For example:
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:
class AccountSerializer(serializers.HyperlinkedModelSerializer): class AccountSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField( url = serializers.HyperlinkedIdentityField(
view_name='account_detail', view_name='accounts',
lookup_field='account_name' lookup_field='slug'
) )
users = serializers.HyperlinkedRelatedField( users = serializers.HyperlinkedRelatedField(
view_name='user-detail', view_name='user-detail',
@ -474,36 +596,267 @@ For more specific requirements such as specifying a different lookup for each fi
model = Account model = Account
fields = ('url', 'account_name', 'users', 'created') 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. 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: class Meta:
model = Account list_serializer_class = CustomListSerializer
fields = ('account_url', 'account_name', 'users', 'created')
url_field_name = 'account_url'
**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: class Meta:
model = Account list_serializer_class = BookListSerializer
fields = ('account_url', 'account_name', 'users', 'created')
view_name = 'account_detail' #### Customizing multiple update
lookup_field='account_name'
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 # 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
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. 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)`
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 ## Dynamically modifying fields
@ -528,7 +881,7 @@ For example, if you wanted to be able to set which fields should be used by a se
# Instantiate the superclass normally # Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs) super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields: if fields is not None:
# Drop any fields that are not specified in the `fields` argument. # Drop any fields that are not specified in the `fields` argument.
allowed = set(fields) allowed = set(fields)
existing = set(self.fields.keys()) existing = set(self.fields.keys())
@ -548,47 +901,15 @@ This would then allow you to do the following:
>>> print UserSerializer(user, fields=('id', 'email')) >>> print UserSerializer(user, fields=('id', 'email'))
{'id': 2, 'email': 'jon@example.com'} {'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`. This API included the `.get_field()`, `.get_pk_field()` and other methods.
### get_pk_field 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.
**Signature**: `.get_pk_field(self, model_field)` A new interface for controlling this behavior is currently planned for REST framework 3.1.
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
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
--- ---
@ -610,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 [cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion
[relations]: relations.md [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 [mongoengine]: https://github.com/umutbozkurt/django-rest-framework-mongoengine
[django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis [django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis
[django-rest-framework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore [django-rest-framework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore

View File

@ -1,4 +1,4 @@
<a class="github" href="settings.py"></a> source: settings.py
# Settings # Settings
@ -51,7 +51,7 @@ Default:
#### DEFAULT_PARSER_CLASSES #### 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: Default:
@ -74,7 +74,7 @@ Default:
#### DEFAULT_PERMISSION_CLASSES #### 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: Default:

View File

@ -1,4 +1,4 @@
<a class="github" href="status.py"></a> source: status.py
# Status Codes # Status Codes

View File

@ -1,4 +1,4 @@
<a class="github" href="test.py"></a> source: test.py
# Testing # Testing

View File

@ -1,4 +1,4 @@
<a class="github" href="throttling.py"></a> source: throttling.py
# Throttling # Throttling

View File

@ -1,5 +1,11 @@
<a class="github" href="validators.py"></a> <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
> Validators can be useful for re-using validation logic between different types of fields. > Validators can be useful for re-using validation logic between different types of fields.

View File

@ -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 # Class Based Views
@ -126,19 +127,26 @@ REST framework also allows you to work with regular function based views. It pr
## @api_view() ## @api_view()
**Signature:** `@api_view(http_method_names)` **Signature:** `@api_view(http_method_names=['GET'])`
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: 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 from rest_framework.decorators import api_view
@api_view(['GET']) @api_view()
def hello_world(request): def hello_world(request):
return Response({"message": "Hello, world!"}) return Response({"message": "Hello, world!"})
This view will use the default renderers, parsers, authentication classes etc specified in the [settings]. This view will use the default renderers, parsers, authentication classes etc specified in the [settings].
By default only `GET` methods will be accepted. Other methods will respond with "405 Method Not Allowed". To alter this behavior, specify which methods the view allows, like so:
@api_view(['GET', 'POST'])
def hello_world(request):
if request.method == 'POST':
return Response({"message": "Got some data!", "data": request.data})
return Response({"message": "Hello, world!"})
## API policy decorators ## 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: 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:

View File

@ -1,4 +1,4 @@
<a class="github" href="viewsets.py"></a> source: viewsets.py
# ViewSets # ViewSets
@ -124,7 +124,7 @@ For example:
@detail_route(methods=['post']) @detail_route(methods=['post'])
def set_password(self, request, pk=None): def set_password(self, request, pk=None):
user = self.get_object() user = self.get_object()
serializer = PasswordSerializer(data=request.DATA) serializer = PasswordSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
user.set_password(serializer.data['password']) user.set_password(serializer.data['password'])
user.save() user.save()

View File

@ -192,6 +192,18 @@ body{
.navbar .navbar-inner .dropdown-menu li a, .navbar .navbar-inner .dropdown-menu li{ .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{ .navbar .navbar-inner .dropdown-menu li a:hover{
background: #eeeeee; background: #eeeeee;
color: #c20000; color: #c20000;

View File

@ -9,7 +9,9 @@
--- ---
**Note**: The incoming 3.0 version has now been merged to the `master` branch on GitHub. For the source of the currently available PyPI version, please see the `2.4.4` tag. **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.
For more details see the [3.0 release notes](3.0-announcement).
--- ---
@ -26,9 +28,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"> <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> </p>
<!--
# Django REST framework
-->
Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs. Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs.
@ -204,6 +203,7 @@ General guides to using REST framework.
* [2.2 Announcement][2.2-announcement] * [2.2 Announcement][2.2-announcement]
* [2.3 Announcement][2.3-announcement] * [2.3 Announcement][2.3-announcement]
* [2.4 Announcement][2.4-announcement] * [2.4 Announcement][2.4-announcement]
* [3.0 Announcement][3.0-announcement]
* [Kickstarter Announcement][kickstarter-announcement] * [Kickstarter Announcement][kickstarter-announcement]
* [Release Notes][release-notes] * [Release Notes][release-notes]
* [Credits][credits] * [Credits][credits]
@ -297,7 +297,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[serializers]: api-guide/serializers.md [serializers]: api-guide/serializers.md
[fields]: api-guide/fields.md [fields]: api-guide/fields.md
[relations]: api-guide/relations.md [relations]: api-guide/relations.md
[validation]: api-guide/validation.md [validators]: api-guide/validators.md
[authentication]: api-guide/authentication.md [authentication]: api-guide/authentication.md
[permissions]: api-guide/permissions.md [permissions]: api-guide/permissions.md
[throttling]: api-guide/throttling.md [throttling]: api-guide/throttling.md
@ -322,6 +322,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[2.2-announcement]: topics/2.2-announcement.md [2.2-announcement]: topics/2.2-announcement.md
[2.3-announcement]: topics/2.3-announcement.md [2.3-announcement]: topics/2.3-announcement.md
[2.4-announcement]: topics/2.4-announcement.md [2.4-announcement]: topics/2.4-announcement.md
[3.0-announcement]: topics/3.0-announcement.md
[kickstarter-announcement]: topics/kickstarter-announcement.md [kickstarter-announcement]: topics/kickstarter-announcement.md
[release-notes]: topics/release-notes.md [release-notes]: topics/release-notes.md
[credits]: topics/credits.md [credits]: topics/credits.md

View File

@ -1 +0,0 @@
markdown>=2.1.0

View File

@ -1,239 +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/validators{{ suffix }}">Validators</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">&times;</button>
<h3 id="myModalLabel">Documentation search</h3>
</div>
<div class="modal-body">
<!-- Custom google search -->
<script>
(function() {
var cx = '015016005043623903336:rxraeohqk6w';
var gcse = document.createElement('script');
gcse.type = 'text/javascript';
gcse.async = true;
gcse.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') +
'//www.google.com/cse/cse.js?cx=' + cx;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
})();
</script>
<gcse:search></gcse:search>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
</div>
</div>
<div class="row-fluid">
<div class="span3">
<!-- TODO
<p style="margin-top: -12px">
<a class="btn btn-mini btn-primary" style="width: 60px">&laquo; previous</a>
<a class="btn btn-mini btn-primary" style="float: right; margin-right: 8px; width: 60px;">next &raquo;</a>
</p>
-->
<div id="table-of-contents">
<ul class="nav nav-list side-nav well sidebar-nav-fixed">
{{ 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>

View File

@ -1,17 +1,3 @@
## Pre-release notes:
The 3.0 release is now ready for some tentative testing and upgrades for early adopters. You can install the development version directly from GitHub like so:
pip install https://github.com/tomchristie/django-rest-framework/archive/master.zip
See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-framework/pull/1800) for more details on remaining work.
**Your feedback on the upgrade process and 3.0 changes is hugely important!**
Please do get in touch via twitter, IRC, a GitHub ticket, or the discussion group.
---
# REST framework 3.0 # REST framework 3.0
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. 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.
@ -20,6 +6,10 @@ This release is incremental in nature. There *are* some breaking API changes, an
The difference in quality of the REST framework API and implementation should make writing, maintaining and debugging your application far easier. 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).
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.
## New features ## New features
Notable features of this new release include: Notable features of this new release include:
@ -32,6 +22,9 @@ Notable features of this new release include:
* Support for overriding how validation errors are handled by your API. * 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 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. * 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. Below is an in-depth guide to the API changes and migration notes for 3.0.
@ -140,6 +133,16 @@ The corresponding code would now look like this:
logging.info('Creating ticket "%s"' % name) logging.info('Creating ticket "%s"' % name)
serializer.save(user=request.user) # Include the user when saving. 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`. #### 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. 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.
@ -155,7 +158,7 @@ We strongly recommend that you use the namespaced import style of `import serial
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: 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): def validate_score(self, attrs, source):
if attrs[score] % 10 != 0: if attrs['score'] % 10 != 0:
raise serializers.ValidationError('This field should be a multiple of ten.') raise serializers.ValidationError('This field should be a multiple of ten.')
return attrs return attrs
@ -174,16 +177,43 @@ You can either return `non_field_errors` from the validate method by raising a s
def validate(self, attrs): def validate(self, attrs):
# serializer.errors == {'non_field_errors': ['A non field error']} # serializer.errors == {'non_field_errors': ['A non field error']}
raise serailizers.ValidationError('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: 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): def validate(self, attrs):
# serializer.errors == {'my_field': ['A field error']} # serializer.errors == {'my_field': ['A field error']}
raise serailizers.ValidationError({'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. 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>`.
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.
For example:
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. #### 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. 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.
@ -351,6 +381,8 @@ The `ListSerializer` class has now been added, and allows you to create base ser
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 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. 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. #### The `BaseSerializer` class.
@ -369,7 +401,11 @@ There are four methods that can be overridden, depending on what functionality y
* `.to_representation()` - Override this to support serialization, for read operations. * `.to_representation()` - Override this to support serialization, for read operations.
* `.to_internal_value()` - Override this to support deserialization, for write 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. * `.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. ##### Read-only `BaseSerializer` classes.
@ -454,7 +490,7 @@ Here's a complete example of our previous `HighScoreSerializer`, that's been upd
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 `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 aribitrary objects into primitive representations. The following class is an example of a generic serializer that can handle coercing arbitrary objects into primitive representations.
class ObjectSerializer(serializers.BaseSerializer): class ObjectSerializer(serializers.BaseSerializer):
""" """
@ -474,12 +510,12 @@ The following class is an example of a generic serializer that can handle coerci
# Primitive types can be passed through unmodified. # Primitive types can be passed through unmodified.
output[attribute_name] = attribute output[attribute_name] = attribute
elif isinstance(attribute, list): elif isinstance(attribute, list):
# Recursivly deal with items in lists. # Recursively deal with items in lists.
output[attribute_name] = [ output[attribute_name] = [
self.to_representation(item) for item in attribute self.to_representation(item) for item in attribute
] ]
elif isinstance(attribute, dict): elif isinstance(attribute, dict):
# Recursivly deal with items in dictionarys. # Recursively deal with items in dictionaries.
output[attribute_name] = { output[attribute_name] = {
str(key): self.to_representation(value) str(key): self.to_representation(value)
for key, value in attribute.items() for key, value in attribute.items()
@ -527,6 +563,19 @@ The `default` argument is also available and always implies that the field is no
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 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.
#### 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.
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: 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:
@ -668,7 +717,9 @@ The `UniqueTogetherValidator` should be applied to a serializer, and takes a `qu
#### The `UniqueForDateValidator` classes. #### The `UniqueForDateValidator` classes.
**TODO: Needs documenting.** 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 ## Generic views
@ -726,7 +777,34 @@ This makes it far easier to use a different style for `OPTIONS` responses throug
## Serializers as HTML forms ## Serializers as HTML forms
**TODO: Document this.** 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 ## API style
@ -819,7 +897,12 @@ Or modify it on an individual serializer field, using the `coerce_to_string` key
coerce_to_string=False 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. ## What's coming next.
@ -836,4 +919,5 @@ The 3.2 release is planned to introduce an alternative admin-style interface to
You can follow development on the GitHub site, where we use [milestones to indicate planning timescales](https://github.com/tomchristie/django-rest-framework/milestones). 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 [mixins.py]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py

View File

@ -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. 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 %} author = serializers.HyperlinkedRelatedField(
{{ block.super }} queryset=User.objects.all(),
{% include 'autocomplete_light/static.html' %} style={'base_template': 'input.html'}
{% endblock %}
You can now add the `autocomplete_light.ChoiceWidget` widget to the serializer field.
import autocomplete_light
class BookSerializer(serializers.ModelSerializer):
author = serializers.ChoiceField(
widget=autocomplete_light.ChoiceWidget('AuthorAutocomplete')
) )
class Meta: #### Autocomplete
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/ [autocomplete-packages]: https://www.djangopackages.com/grids/g/auto-complete/
[django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light [django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light
[django-autocomplete-light-install]: http://django-autocomplete-light.readthedocs.org/en/latest/#install [django-autocomplete-light-install]: http://django-autocomplete-light.readthedocs.org/en/latest/#install
[autocomplete-image]: ../img/autocomplete.png

View File

@ -135,15 +135,15 @@ There are many great Markdown editors that make working with the documentation r
## Building the documentation ## 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 ## Language style
@ -152,7 +152,6 @@ Documentation should be in American English. The tone of the documentation is v
Some other tips: Some other tips:
* Keep paragraphs reasonably short. * Keep paragraphs reasonably short.
* Use double spacing after the end of sentences.
* Don't use abbreviations such as 'e.g.' but instead use the 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 ## Markdown style
@ -198,21 +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. 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.
We also suggest adding it to the [REST Framework][rest-framework-grid] grid on Django Packages.
[cite]: http://www.w3.org/People/Berners-Lee/FAQ.html [cite]: http://www.w3.org/People/Berners-Lee/FAQ.html
[code-of-conduct]: https://www.djangoproject.com/conduct/ [code-of-conduct]: https://www.djangoproject.com/conduct/
@ -226,8 +210,3 @@ We also suggest adding it to the [REST Framework][rest-framework-grid] grid on D
[markdown]: http://daringfireball.net/projects/markdown/basics [markdown]: http://daringfireball.net/projects/markdown/basics
[docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs [docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs
[mou]: http://mouapp.com/ [mou]: http://mouapp.com/
[django-reusable-app]: https://github.com/dabapps/django-reusable-app
[authentication]: ../api-guide/authentication.md
[permissions]: ../api-guide/permissions.md
[third-party-resources]: third-party-resources.md
[rest-framework-grid]: https://www.djangopackages.com/grids/g/django-rest-framework/

View File

@ -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. 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"> <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>
<ul class="sponsor platinum"> <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="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="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="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.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="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="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://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://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.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="http://www.kuwaitnet.net/" rel="nofollow" style="background-image:url(../../img/sponsors//1-kuwaitnet.png);">KuwaitNET</a></li>
</ul> </ul>
<div style="clear: both"></div> <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. 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"> <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://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="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="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="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.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.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="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="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://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://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="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://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="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://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="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.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="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="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="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.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://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://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="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="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://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> <li><a href="http://envisionlinux.org/blog" rel="nofollow">Envision Linux</a></li>
</ul> </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&nbsp;you to individuals who have choosen to privately support the project at this level. The serious financial contribution that our silver sponsors have made is very much appreciated. I'd like to say a particular thank&nbsp;you to individuals who have choosen to privately support the project at this level.
<ul class="sponsor silver"> <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://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://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://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="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="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="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="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="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://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="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.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.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://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://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://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://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://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://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="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="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://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://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://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://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://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://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.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.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.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="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="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="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.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="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://istrategylabs.com/" rel="nofollow" style="background-image:url(../../img/sponsors//3-isl.png);">ISL</a></li>
</ul> </ul>
<div style="clear: both; padding-bottom: 40px;"></div> <div style="clear: both; padding-bottom: 40px;"></div>

View File

@ -442,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: Validation errors instead of exceptions when related fields receive incorrect types.
* Bugfix: Handle ObjectDoesNotExist exception when serializing null reverse one-to-one * 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 ### 2.1.15
@ -614,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.) * **Fix all of the things.** (Well, almost.)
* For more information please see the [2.0 announcement][announcement]. * For more information please see the [2.0 announcement][announcement].
--- For older release notes, [please see the GitHub repo](old-release-notes).
## 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.
[cite]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html [cite]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html
[deprecation-policy]: #deprecation-policy [deprecation-policy]: #deprecation-policy
@ -742,5 +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 [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 [2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion
[announcement]: rest-framework-2-announcement.md [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 [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

View File

@ -4,9 +4,9 @@
> >
> &mdash; Mike Amundsen, [REST fest 2012 keynote][cite]. > &mdash; 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. The following fall into the "required reading" category.

View File

@ -1,92 +1,322 @@
# Third Party Resources # Third Party 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 favor** the creation of Third Party Packages to encapsulate new behavior rather than adding additional functionality directly to Django REST Framework.
We aim to make creating third party packages as easy as possible, whilst keeping a **simple** and **well maintained** core API. By promoting third party packages we ensure that the responsibility for a package remains with its author. If a package proves suitably popular it can always be considered for inclusion into the core REST framework.
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 discuss 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
Once requirements installed, you can run `runtests.py`.
$ ./runtests.py
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. 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](https://www.djangopackages.com/grids/g/django-rest-framework/). 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](https://github.com/tomchristie/django-rest-framework/issues/new) or [create a pull request](https://github.com/tomchristie/django-rest-framework/). To submit new content, [open an issue][drf-create-issue] or [create a pull request][drf-create-pr].
## Libraries and Extensions
### Authentication ### Authentication
* [djangorestframework-digestauth](https://github.com/juanriaza/django-rest-framework-digestauth) - Provides Digest Access Authentication support. * [djangorestframework-digestauth][djangorestframework-digestauth] - Provides Digest Access Authentication support.
* [django-oauth-toolkit](https://github.com/evonove/django-oauth-toolkit) - Provides OAuth 2.0 support. * [django-oauth-toolkit][django-oauth-toolkit] - Provides OAuth 2.0 support.
* [doac](https://github.com/Rediker-Software/doac) - Provides OAuth 2.0 support. * [doac][doac] - Provides OAuth 2.0 support.
* [djangorestframework-jwt](https://github.com/GetBlimp/django-rest-framework-jwt) - Provides JSON Web Token Authentication support. * [djangorestframework-jwt][djangorestframework-jwt] - Provides JSON Web Token Authentication support.
* [hawkrest](https://github.com/kumar303/hawkrest) - Provides Hawk HTTP Authorization. * [hawkrest][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-httpsignature][djangorestframework-httpsignature] - Provides an easy to use HTTP Signature Authentication mechanism.
* [djoser](https://github.com/sunscrapers/djoser) - Provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation. * [djoser][djoser] - Provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation.
### Permissions ### Permissions
* [drf-any-permissions](https://github.com/kevin-brown/drf-any-permissions) - Provides alternative permission handling. * [drf-any-permissions][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. * [djangorestframework-composed-permissions][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. * [rest_condition][rest-condition] - Another extension for building complex permissions in a simple and convenient way.
### Serializers ### 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. * [django-rest-framework-mongoengine][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-gis][djangorestframework-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. * [djangorestframework-hstore][djangorestframework-hstore] - Serializer class to support django-hstore DictionaryField model field and its schema-mode feature.
### Serializer fields ### Serializer fields
* [drf-compound-fields](https://github.com/estebistec/drf-compound-fields) - Provides "compound" serializer fields, such as lists of simple values. * [drf-compound-fields][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. * [django-extra-fields][django-extra-fields] - Provides extra serializer fields.
### Views ### 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 ### Routers
* [drf-nested-routers](https://github.com/alanjds/drf-nested-routers) - Provides routers and relationship fields for working with nested resources. * [drf-nested-routers][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. * [wq.db.rest][wq.db.rest] - Provides an admin-style model registration API with reasonable default URLs and viewsets.
### Parsers ### Parsers
* [djangorestframework-msgpack](https://github.com/juanriaza/django-rest-framework-msgpack) - Provides MessagePack renderer and parser support. * [djangorestframework-msgpack][djangorestframework-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-camel-case][djangorestframework-camel-case] - Provides camel case JSON renderers and parsers.
### Renderers ### Renderers
* [djangorestframework-csv](https://github.com/mjumbewu/django-rest-framework-csv) - Provides CSV renderer support. * [djangorestframework-csv][djangorestframework-csv] - Provides CSV renderer support.
* [drf_ujson](https://github.com/gizmag/drf-ujson-renderer) - Implements JSON rendering using the UJSON package. * [drf_ujson][drf_ujson] - 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. * [rest-pandas][rest-pandas] - Pandas DataFrame-powered renderers including Excel, CSV, and SVG formats.
### Filtering ### 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 ### 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. * [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](https://github.com/marcgibbons/django-rest-swagger) - An API documentation generator for Swagger UI. * [django-rest-swagger][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. * [django-rest-framework-proxy][django-rest-framework-proxy] - Proxy to redirect incoming request to another API server.
* [gaiarestframework](https://github.com/AppsFuel/gaiarestframework) - Utils for django-rest-framewok * [gaiarestframework][gaiarestframework] - Utils for django-rest-framewok
* [drf-extensions](https://github.com/chibisov/drf-extensions) - A collection of custom extensions * [drf-extensions][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 * [ember-data-django-rest-adapter][ember-data-django-rest-adapter] - An ember-data adapter
## Tutorials ## Other Resources
* [Beginner's Guide to the Django Rest Framework](http://code.tutsplus.com/tutorials/beginners-guide-to-the-django-rest-framework--cms-19786) ### Tutorials
* [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/)
## Videos * [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]
* [Ember and Django Part 1 (Video)](http://www.neckbeardrepublic.com/screencasts/ember-and-django-part-1) ### Videos
* [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)
## Articles * [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]
* [Web API performance: profiling Django REST framework](http://dabapps.com/blog/api-performance-profiling-django-rest-framework/) ### Articles
* [API Development with Django and Django REST Framework](https://bnotions.com/api-development-with-django-and-django-rest-framework/)
* [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]
[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/

View File

@ -206,7 +206,7 @@ Open the file `snippets/serializers.py` again, and edit the `SnippetSerializer`
model = Snippet model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style') fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
Once nice property that serializers have is that you can inspect all the fields an serializer instance, by printing it's representation. Open the Django shell with `python manange.py shell`, then try the following: 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 >>> from snippets.serializers import SnippetSerializer
>>> serializer = SnippetSerializer() >>> serializer = SnippetSerializer()
@ -219,7 +219,7 @@ Once nice property that serializers have is that you can inspect all the fields
language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')... language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')... style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
It's important to remember that `ModelSerializer` classes don't do anything particularly magically, they are simply a shortcut to creating a serializer class with: 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. * An automatically determined set of fields.
* Simple default implementations for the `create()` and `update()` methods. * Simple default implementations for the `create()` and `update()` methods.

View File

@ -5,10 +5,10 @@ Let's introduce a couple of essential building blocks.
## Request objects ## 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.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 ## 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. 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 ## 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) return Response(serializer.data)
elif request.method == 'POST': elif request.method == 'POST':
serializer = SnippetSerializer(data=request.DATA) serializer = SnippetSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED) 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) return Response(serializer.data)
elif request.method == 'PUT': elif request.method == 'PUT':
serializer = SnippetSerializer(snippet, data=request.DATA) serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
return Response(serializer.data) 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. 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 ## Adding optional format suffixes to our URLs

View File

@ -24,7 +24,7 @@ We'll start by rewriting the root view as a class based view. All this involves
return Response(serializer.data) return Response(serializer.data)
def post(self, request, format=None): def post(self, request, format=None):
serializer = SnippetSerializer(data=request.DATA) serializer = SnippetSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED) 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): def put(self, request, pk, format=None):
snippet = self.get_object(pk) snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.DATA) serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
return Response(serializer.data) return Response(serializer.data)

View File

@ -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. 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 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. 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.

View File

@ -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: 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.decorators import api_view
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.reverse import reverse from rest_framework.reverse import reverse

View File

@ -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. 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 from rest_framework import renderers
snippet_list = SnippetViewSet.as_view({ snippet_list = SnippetViewSet.as_view({

View File

@ -19,7 +19,7 @@ Create a new Django project named `tutorial`, then start a new app called `quick
pip install djangorestframework pip install djangorestframework
# Set up a new project with a single application # Set up a new project with a single application
django-admin.py startproject tutorial . django-admin.py startproject tutorial
cd tutorial cd tutorial
django-admin.py startapp quickstart django-admin.py startapp quickstart
cd .. cd ..

View File

@ -1,6 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8"> <meta charset="utf-8">
<title>Django REST framework - 404 - Page not found</title> <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 href="http://www.django-rest-framework.org/img/favicon.ico" rel="icon" type="image/x-icon">
@ -21,19 +23,21 @@
<![endif]--> <![endif]-->
<script type="text/javascript"> <script type="text/javascript">
var _gaq = _gaq || []; var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-18852272-2']); _gaq.push(['_setAccount', 'UA-18852272-2']);
_gaq.push(['_trackPageview']); _gaq.push(['_trackPageview']);
(function() { (function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; 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'; 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); var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})(); })();
</script> </script>
</head> </head>
<body onload="prettyPrint()" class="404-page"> <body onload="prettyPrint()" class="404-page">
<div class="wrapper"> <div class="wrapper">
@ -121,14 +125,14 @@
</li> </li>
--> -->
</ul> </ul>
</div><!--/.nav-collapse --> </div>
<!--/.nav-collapse -->
</div> </div>
</div> </div>
</div> </div>
<div class="body-content"> <div class="body-content">
<div class="container-fluid"> <div class="container-fluid">
<!-- Search Modal --> <!-- Search Modal -->
<div id="searchModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div id="searchModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header"> <div class="modal-header">
@ -159,18 +163,25 @@
<div class="row-fluid"> <div class="row-fluid">
<div id="main-content" class="span12"> <div id="main-content" class="span12">
<h1 id="404-page-not-found" style="text-align: center">404</h1> <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"><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> <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>
</div><!--/row--> <!--/span-->
</div><!--/.fluid-container--> </div>
</div><!--/.body content--> <!--/row-->
</div>
<!--/.fluid-container-->
</div>
<!--/.body content-->
<div id="push"></div> <div id="push"></div>
</div><!--/.wrapper --> </div>
<!--/.wrapper -->
<footer class="span12"> <footer class="span12">
<p>Sponsored by <a href="http://dabapps.com/">DabApps</a>.</a></p> <p>Sponsored by <a href="http://dabapps.com/">DabApps</a>.</a>
</p>
</footer> </footer>
<!-- Le javascript <!-- Le javascript
@ -181,7 +192,9 @@
<script src="http://www.django-rest-framework.org/js/bootstrap-2.1.1-min.js"></script> <script src="http://www.django-rest-framework.org/js/bootstrap-2.1.1-min.js"></script>
<script> <script>
//$('.side-nav').scrollspy() //$('.side-nav').scrollspy()
var shiftWindow = function() { scrollBy(0, -50) }; var shiftWindow = function() {
scrollBy(0, -50)
};
if (location.hash) shiftWindow(); if (location.hash) shiftWindow();
window.addEventListener("hashchange", shiftWindow); window.addEventListener("hashchange", shiftWindow);
@ -198,4 +211,6 @@
}); });
}); });
</script> </script>
</body></html> </body>
</html>

196
docs_theme/base.html Normal file
View File

@ -0,0 +1,196 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>{{ page_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="Django, API, REST, {{ current_page.title }}">
<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="{% if current_page.is_homepage %}index{% endif %}-page">
<div class="wrapper">
{% include "nav.html" %}
<div class="body-content">
<div class="container-fluid">
<!-- Search Modal -->
<div id="searchModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 id="myModalLabel">Documentation search</h3>
</div>
<div class="modal-body">
<!-- Custom google search -->
<script>
(function() {
var cx = '015016005043623903336:rxraeohqk6w';
var gcse = document.createElement('script');
gcse.type = 'text/javascript';
gcse.async = true;
gcse.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') +
'//www.google.com/cse/cse.js?cx=' + cx;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
})();
</script>
<gcse:search></gcse:search>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
</div>
</div>
<div class="row-fluid">
<div class="span3">
<!-- TODO
<p style="margin-top: -12px">
<a class="btn btn-mini btn-primary" style="width: 60px">&laquo; previous</a>
<a class="btn btn-mini btn-primary" style="float: right; margin-right: 8px; width: 60px;">next &raquo;</a>
</p>
-->
<div id="table-of-contents">
<ul class="nav nav-list side-nav well sidebar-nav-fixed">
{% if current_page.is_homepage %}
<li class="main">
<a href="#">Django REST framework</a>
</li>
{% endif %}
{% for toc_item in toc %}
<li class="{% if not current_page.is_homepage %}main{% endif %}">
<a href="{{ toc_item.url }}">{{ toc_item.title }}</a>
</li>
{% for toc_item in toc_item.children %}
<li>
<a href="{{ toc_item.url }}">{{ toc_item.title }}</a>
</li>
{% endfor %}
{% endfor %}
{% if current_page.is_homepage %}
<div class="promo">
<hr/>
<script type="text/javascript" src="//cdn.fusionads.net/fusion.js?zoneid=1332&serve=C6SDP2Y&placement=djangorestframework" id="_fusionads_js"></script>
</div>
{% endif %}
</ul>
</div>
</div>
<div id="main-content" class="span9">
{% if meta.source %}
{% for filename in meta.source %}
<a class="github" href="https://github.com/tomchristie/django-rest-framework/tree/master/rest_framework/{{ filename }}">
<span class="label label-info">{{ filename }}</span>
</a>
{% endfor %}
{% endif %}
{{ content }}
</div>
<!--/span-->
</div>
<!--/row-->
</div>
<!--/.fluid-container-->
</div>
<!--/.body content-->
<div id="push"></div>
</div>
<!--/.wrapper -->
<footer class="span12">
<p>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</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>

47
docs_theme/nav.html Normal file
View File

@ -0,0 +1,47 @@
<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 {% if not next_page %}disabled{% endif %}" rel="prev" {% if next_page %}href="{{ next_page.url }}"{% endif %}>
Next <i class="icon-arrow-right icon-white"></i>
</a>
<a class="repo-link btn btn-inverse btn-small {% if not previous_page %}disabled{% endif %}" rel="next" {% if previous_page %}href="{{ previous_page.url }}"{% endif %}>
<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">
{% if include_nav %}
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li {% if current_page.is_homepage %}class="active"{% endif %}><a href="/">Home</a></li>
{% for nav_item in nav %} {% if nav_item.children %}
<li class="dropdown{% if nav_item.active %} active{% endif %}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ nav_item.title }} <b class="caret"></b></a>
<ul class="dropdown-menu">
{% for nav_item in nav_item.children %}
<li {% if nav_item.active %}class="active" {% endif %}>
<a href="{{ nav_item.url }}">{{ nav_item.title }}</a>
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li {% if nav_item.active %}class="active" {% endif %}>
<a href="{{ nav_item.url }}">{{ nav_item.title }}</a>
</li>
{% endif %} {% endfor %}
</ul>
{% endif %}
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>

203
mkdocs.py
View File

@ -1,203 +0,0 @@
#!/usr/bin/env python
import markdown
import os
import re
import shutil
import sys
root_dir = os.path.abspath(os.path.dirname(__file__))
docs_dir = os.path.join(root_dir, 'docs')
html_dir = os.path.join(root_dir, 'html')
local = not '--deploy' in sys.argv
preview = '-p' in sys.argv
if local:
base_url = 'file://%s/' % os.path.normpath(os.path.join(os.getcwd(), html_dir))
suffix = '.html'
index = 'index.html'
else:
base_url = 'http://www.django-rest-framework.org'
suffix = ''
index = ''
main_header = '<li class="main"><a href="#{{ anchor }}">{{ title }}</a></li>'
sub_header = '<li><a href="#{{ anchor }}">{{ title }}</a></li>'
code_label = r'<a class="github" href="https://github.com/tomchristie/django-rest-framework/tree/master/rest_framework/\1"><span class="label label-info">\1</span></a>'
page = open(os.path.join(docs_dir, 'template.html'), 'r').read()
# Copy static files
# for static in ['css', 'js', 'img']:
# source = os.path.join(docs_dir, 'static', static)
# target = os.path.join(html_dir, static)
# if os.path.exists(target):
# shutil.rmtree(target)
# shutil.copytree(source, target)
# Hacky, but what the hell, it'll do the job
path_list = [
'index.md',
'tutorial/quickstart.md',
'tutorial/1-serialization.md',
'tutorial/2-requests-and-responses.md',
'tutorial/3-class-based-views.md',
'tutorial/4-authentication-and-permissions.md',
'tutorial/5-relationships-and-hyperlinked-apis.md',
'tutorial/6-viewsets-and-routers.md',
'api-guide/requests.md',
'api-guide/responses.md',
'api-guide/views.md',
'api-guide/generic-views.md',
'api-guide/viewsets.md',
'api-guide/routers.md',
'api-guide/parsers.md',
'api-guide/renderers.md',
'api-guide/serializers.md',
'api-guide/fields.md',
'api-guide/relations.md',
'api-guide/validators.md',
'api-guide/authentication.md',
'api-guide/permissions.md',
'api-guide/throttling.md',
'api-guide/filtering.md',
'api-guide/pagination.md',
'api-guide/content-negotiation.md',
'api-guide/format-suffixes.md',
'api-guide/reverse.md',
'api-guide/exceptions.md',
'api-guide/status-codes.md',
'api-guide/testing.md',
'api-guide/settings.md',
'topics/documenting-your-api.md',
'topics/ajax-csrf-cors.md',
'topics/browser-enhancements.md',
'topics/browsable-api.md',
'topics/rest-hypermedia-hateoas.md',
'topics/third-party-resources.md',
'topics/contributing.md',
'topics/rest-framework-2-announcement.md',
'topics/2.2-announcement.md',
'topics/2.3-announcement.md',
'topics/2.4-announcement.md',
'topics/release-notes.md',
'topics/credits.md',
]
prev_url_map = {}
next_url_map = {}
for idx in range(len(path_list)):
path = path_list[idx]
rel = '../' * path.count('/')
if idx == 1 and not local:
# Link back to '/', not '/index'
prev_url_map[path] = '/'
elif idx > 0:
prev_url_map[path] = rel + path_list[idx - 1][:-3] + suffix
if idx < len(path_list) - 1:
next_url_map[path] = rel + path_list[idx + 1][:-3] + suffix
for (dirpath, dirnames, filenames) in os.walk(docs_dir):
relative_dir = dirpath.replace(docs_dir, '').lstrip(os.path.sep)
build_dir = os.path.join(html_dir, relative_dir)
if not os.path.exists(build_dir):
os.makedirs(build_dir)
for filename in filenames:
path = os.path.join(dirpath, filename)
relative_path = os.path.join(relative_dir, filename)
if not filename.endswith('.md'):
if relative_dir:
output_path = os.path.join(build_dir, filename)
shutil.copy(path, output_path)
continue
output_path = os.path.join(build_dir, filename[:-3] + '.html')
toc = ''
text = open(path, 'r').read().decode('utf-8')
main_title = None
description = 'Django, API, REST'
for line in text.splitlines():
if line.startswith('# '):
title = line[2:].strip()
template = main_header
description = description + ', ' + title
elif line.startswith('## '):
title = line[3:].strip()
template = sub_header
else:
continue
if not main_title:
main_title = title
anchor = title.lower().replace(' ', '-').replace(':-', '-').replace("'", '').replace('?', '').replace('.', '')
template = template.replace('{{ title }}', title)
template = template.replace('{{ anchor }}', anchor)
toc += template + '\n'
if filename == 'index.md':
main_title = 'Django REST framework - Web APIs for Django'
else:
main_title = main_title + ' - Django REST framework'
if relative_path == 'index.md':
canonical_url = base_url
else:
canonical_url = base_url + '/' + relative_path[:-3] + suffix
prev_url = prev_url_map.get(relative_path)
next_url = next_url_map.get(relative_path)
content = markdown.markdown(text, ['headerid'])
output = page.replace('{{ content }}', content).replace('{{ toc }}', toc).replace('{{ base_url }}', base_url).replace('{{ suffix }}', suffix).replace('{{ index }}', index)
output = output.replace('{{ title }}', main_title)
output = output.replace('{{ description }}', description)
output = output.replace('{{ page_id }}', filename[:-3])
output = output.replace('{{ canonical_url }}', canonical_url)
if filename =='index.md':
output = output.replace('{{ ad_block }}', """<hr/>
<script type="text/javascript" src="//cdn.fusionads.net/fusion.js?zoneid=1332&serve=C6SDP2Y&placement=djangorestframework" id="_fusionads_js"></script>""")
else:
output = output.replace('{{ ad_block }}', '')
if prev_url:
output = output.replace('{{ prev_url }}', prev_url)
output = output.replace('{{ prev_url_disabled }}', '')
else:
output = output.replace('{{ prev_url }}', '#')
output = output.replace('{{ prev_url_disabled }}', 'disabled')
if next_url:
output = output.replace('{{ next_url }}', next_url)
output = output.replace('{{ next_url_disabled }}', '')
else:
output = output.replace('{{ next_url }}', '#')
output = output.replace('{{ next_url_disabled }}', 'disabled')
output = re.sub(r'a href="([^"]*)\.md"', r'a href="\1%s"' % suffix, output)
output = re.sub(r'<pre><code>:::bash', r'<pre class="prettyprint lang-bsh">', output)
output = re.sub(r'<pre>', r'<pre class="prettyprint lang-py">', output)
output = re.sub(r'<a class="github" href="([^"]*)"></a>', code_label, output)
open(output_path, 'w').write(output.encode('utf-8'))
if preview:
import subprocess
url = 'html/index.html'
try:
subprocess.Popen(["open", url]) # Mac
except OSError:
subprocess.Popen(["xdg-open", url]) # Linux
except:
os.startfile(url) # Windows

56
mkdocs.yml Normal file
View File

@ -0,0 +1,56 @@
site_name: Django REST framework
site_url: http://www.django-rest-framework.org/
site_description: Django REST framework - Web APIs for Django
repo_url: https://github.com/tomchristie/django-rest-framework
theme_dir: docs_theme
pages:
- ['index.md', 'Home']
- ['tutorial/quickstart.md', 'Tutorial', 'Quickstart']
- ['tutorial/1-serialization.md', 'Tutorial', '1 - Serialization']
- ['tutorial/2-requests-and-responses.md', 'Tutorial', '2 - Requests and responses']
- ['tutorial/3-class-based-views.md', 'Tutorial', '3 - Class based views']
- ['tutorial/4-authentication-and-permissions.md', 'Tutorial', '4 - Authentication and permissions']
- ['tutorial/5-relationships-and-hyperlinked-apis.md', 'Tutorial', '5 - Relationships and hyperlinked APIs']
- ['tutorial/6-viewsets-and-routers.md', 'Tutorial', '6 - Viewsets and routers']
- ['api-guide/requests.md', 'API Guide', 'Requests']
- ['api-guide/responses.md', 'API Guide', 'Responses']
- ['api-guide/views.md', 'API Guide', 'Views']
- ['api-guide/generic-views.md', 'API Guide', 'Generic views']
- ['api-guide/viewsets.md', 'API Guide', 'Viewsets']
- ['api-guide/routers.md', 'API Guide', 'Routers']
- ['api-guide/parsers.md', 'API Guide', 'Parsers']
- ['api-guide/renderers.md', 'API Guide', 'Renderers']
- ['api-guide/serializers.md', 'API Guide', 'Serializers']
- ['api-guide/fields.md', 'API Guide', 'Serializer fields']
- ['api-guide/relations.md', 'API Guide', 'Serializer relations']
- ['api-guide/validators.md', 'API Guide', 'Validators']
- ['api-guide/authentication.md', 'API Guide', 'Authentication']
- ['api-guide/permissions.md', 'API Guide', 'Permissions']
- ['api-guide/throttling.md', 'API Guide', 'Throttling']
- ['api-guide/filtering.md', 'API Guide', 'Filtering']
- ['api-guide/pagination.md', 'API Guide', 'Pagination']
- ['api-guide/content-negotiation.md', 'API Guide', 'Content negotiation']
- ['api-guide/format-suffixes.md', 'API Guide', 'Format suffixes']
- ['api-guide/reverse.md', 'API Guide', 'Returning URLs']
- ['api-guide/exceptions.md', 'API Guide', 'Exceptions']
- ['api-guide/status-codes.md', 'API Guide', 'Status codes']
- ['api-guide/testing.md', 'API Guide', 'Testing']
- ['api-guide/settings.md', 'API Guide', 'Settings']
- ['topics/documenting-your-api.md', 'Topics', 'Documenting your API']
- ['topics/ajax-csrf-cors.md', 'Topics', 'AJAX, CSRF & CORS']
- ['topics/browser-enhancements.md', 'Topics',]
- ['topics/browsable-api.md', 'Topics', 'The Browsable API']
- ['topics/rest-hypermedia-hateoas.md', 'Topics', 'REST, Hypermedia & HATEOAS']
- ['topics/third-party-resources.md', 'Topics', 'Third Party Resources']
- ['topics/contributing.md', 'Topics', 'Contributing to REST framework']
- ['topics/rest-framework-2-announcement.md', 'Topics', '2.0 Announcement']
- ['topics/2.2-announcement.md', 'Topics', '2.2 Announcement']
- ['topics/2.3-announcement.md', 'Topics', '2.3 Announcement']
- ['topics/2.4-announcement.md', 'Topics', '2.4 Announcement']
- ['topics/3.0-announcement.md', 'Topics', '3.0 Announcement']
- ['topics/kickstarter-announcement.md', 'Topics', 'Kickstarter Announcement']
- ['topics/release-notes.md', 'Topics', 'Release Notes']
- ['topics/credits.md', 'Topics', 'Credits']

View File

@ -1,5 +1,4 @@
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework import status
from rest_framework import parsers from rest_framework import parsers
from rest_framework import renderers from rest_framework import renderers
from rest_framework.response import Response from rest_framework.response import Response
@ -12,16 +11,13 @@ class ObtainAuthToken(APIView):
permission_classes = () permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,) parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,) renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
model = Token
def post(self, request): def post(self, request):
serializer = self.serializer_class(data=request.data) serializer = AuthTokenSerializer(data=request.data)
if serializer.is_valid(): serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user'] user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user) token, created = Token.objects.get_or_create(user=user)
return Response({'token': token.key}) return Response({'token': token.key})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
obtain_auth_token = ObtainAuthToken.as_view() obtain_auth_token = ObtainAuthToken.as_view()

View File

@ -12,12 +12,14 @@ from rest_framework.views import APIView
import types import types
def api_view(http_method_names): def api_view(http_method_names=None):
""" """
Decorator that converts a function-based view into an APIView subclass. Decorator that converts a function-based view into an APIView subclass.
Takes a list of allowed methods for the view as an argument. Takes a list of allowed methods for the view as an argument.
""" """
if http_method_names is None:
http_method_names = ['GET']
def decorator(func): def decorator(func):

View File

@ -5,20 +5,44 @@ In addition Django's built in 403 and 404 exceptions are handled.
(`django.http.Http404` and `django.core.exceptions.PermissionDenied`) (`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from rest_framework import status from rest_framework import status
from rest_framework.compat import force_text
import math import math
def _force_text_recursive(data):
"""
Descend into a nested data structure, forcing any
lazy translation strings into plain text.
"""
if isinstance(data, list):
return [
_force_text_recursive(item) for item in data
]
elif isinstance(data, dict):
return dict([
(key, _force_text_recursive(value))
for key, value in data.items()
])
return force_text(data)
class APIException(Exception): class APIException(Exception):
""" """
Base class for REST framework exceptions. Base class for REST framework exceptions.
Subclasses should provide `.status_code` and `.default_detail` properties. Subclasses should provide `.status_code` and `.default_detail` properties.
""" """
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
default_detail = 'A server error occured' default_detail = _('A server error occured')
def __init__(self, detail=None): def __init__(self, detail=None):
self.detail = detail or self.default_detail if detail is not None:
self.detail = force_text(detail)
else:
self.detail = force_text(self.default_detail)
def __str__(self): def __str__(self):
return self.detail return self.detail
@ -39,7 +63,7 @@ class ValidationError(APIException):
# The details should always be coerced to a list if not already. # The details should always be coerced to a list if not already.
if not isinstance(detail, dict) and not isinstance(detail, list): if not isinstance(detail, dict) and not isinstance(detail, list):
detail = [detail] detail = [detail]
self.detail = detail self.detail = _force_text_recursive(detail)
def __str__(self): def __str__(self):
return str(self.detail) return str(self.detail)
@ -47,59 +71,77 @@ class ValidationError(APIException):
class ParseError(APIException): class ParseError(APIException):
status_code = status.HTTP_400_BAD_REQUEST status_code = status.HTTP_400_BAD_REQUEST
default_detail = 'Malformed request.' default_detail = _('Malformed request.')
class AuthenticationFailed(APIException): class AuthenticationFailed(APIException):
status_code = status.HTTP_401_UNAUTHORIZED status_code = status.HTTP_401_UNAUTHORIZED
default_detail = 'Incorrect authentication credentials.' default_detail = _('Incorrect authentication credentials.')
class NotAuthenticated(APIException): class NotAuthenticated(APIException):
status_code = status.HTTP_401_UNAUTHORIZED status_code = status.HTTP_401_UNAUTHORIZED
default_detail = 'Authentication credentials were not provided.' default_detail = _('Authentication credentials were not provided.')
class PermissionDenied(APIException): class PermissionDenied(APIException):
status_code = status.HTTP_403_FORBIDDEN status_code = status.HTTP_403_FORBIDDEN
default_detail = 'You do not have permission to perform this action.' default_detail = _('You do not have permission to perform this action.')
class MethodNotAllowed(APIException): class MethodNotAllowed(APIException):
status_code = status.HTTP_405_METHOD_NOT_ALLOWED status_code = status.HTTP_405_METHOD_NOT_ALLOWED
default_detail = "Method '%s' not allowed." default_detail = _("Method '%s' not allowed.")
def __init__(self, method, detail=None): def __init__(self, method, detail=None):
self.detail = detail or (self.default_detail % method) if detail is not None:
self.detail = force_text(detail)
else:
self.detail = force_text(self.default_detail) % method
class NotAcceptable(APIException): class NotAcceptable(APIException):
status_code = status.HTTP_406_NOT_ACCEPTABLE status_code = status.HTTP_406_NOT_ACCEPTABLE
default_detail = "Could not satisfy the request Accept header" default_detail = _('Could not satisfy the request Accept header')
def __init__(self, detail=None, available_renderers=None): def __init__(self, detail=None, available_renderers=None):
self.detail = detail or self.default_detail if detail is not None:
self.detail = force_text(detail)
else:
self.detail = force_text(self.default_detail)
self.available_renderers = available_renderers self.available_renderers = available_renderers
class UnsupportedMediaType(APIException): class UnsupportedMediaType(APIException):
status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
default_detail = "Unsupported media type '%s' in request." default_detail = _("Unsupported media type '%s' in request.")
def __init__(self, media_type, detail=None): def __init__(self, media_type, detail=None):
self.detail = detail or (self.default_detail % media_type) if detail is not None:
self.detail = force_text(detail)
else:
self.detail = force_text(self.default_detail) % media_type
class Throttled(APIException): class Throttled(APIException):
status_code = status.HTTP_429_TOO_MANY_REQUESTS status_code = status.HTTP_429_TOO_MANY_REQUESTS
default_detail = 'Request was throttled.' default_detail = _('Request was throttled.')
extra_detail = " Expected available in %d second%s." extra_detail = ungettext_lazy(
'Expected available in %(wait)d second.',
'Expected available in %(wait)d seconds.',
'wait'
)
def __init__(self, wait=None, detail=None): def __init__(self, wait=None, detail=None):
if detail is not None:
self.detail = force_text(detail)
else:
self.detail = force_text(self.default_detail)
if wait is None: if wait is None:
self.detail = detail or self.default_detail
self.wait = None self.wait = None
else: else:
format = (detail or self.default_detail) + self.extra_detail
self.detail = format % (wait, wait != 1 and 's' or '')
self.wait = math.ceil(wait) self.wait = math.ceil(wait)
self.detail += ' ' + force_text(
self.extra_detail % {'wait': self.wait}
)

View File

@ -69,7 +69,7 @@ def get_attribute(instance, attrs):
except (KeyError, TypeError, AttributeError): except (KeyError, TypeError, AttributeError):
raise exc raise exc
if is_simple_callable(instance): if is_simple_callable(instance):
return instance() instance = instance()
return instance return instance
@ -181,6 +181,9 @@ class Field(object):
self.style = {} if style is None else style self.style = {} if style is None else style
self.allow_null = allow_null self.allow_null = allow_null
if allow_null and self.default_empty_html is empty:
self.default_empty_html = None
if validators is not None: if validators is not None:
self.validators = validators[:] self.validators = validators[:]
@ -259,7 +262,11 @@ class Field(object):
if html.is_html_input(dictionary): if html.is_html_input(dictionary):
# HTML forms will represent empty fields as '', and cannot # HTML forms will represent empty fields as '', and cannot
# represent None or False values directly. # represent None or False values directly.
ret = dictionary.get(self.field_name, '') if self.field_name not in dictionary:
if getattr(self.root, 'partial', False):
return empty
return self.default_empty_html
ret = dictionary[self.field_name]
return self.default_empty_html if (ret == '') else ret return self.default_empty_html if (ret == '') else ret
return dictionary.get(self.field_name, empty) return dictionary.get(self.field_name, empty)
@ -314,7 +321,6 @@ class Field(object):
value = self.to_internal_value(data) value = self.to_internal_value(data)
self.run_validators(value) self.run_validators(value)
self.validate(value)
return value return value
def run_validators(self, value): def run_validators(self, value):
@ -341,9 +347,6 @@ class Field(object):
if errors: if errors:
raise ValidationError(errors) raise ValidationError(errors)
def validate(self, value):
pass
def to_internal_value(self, data): def to_internal_value(self, data):
""" """
Transform the *incoming* primitive data into a native value. Transform the *incoming* primitive data into a native value.
@ -495,6 +498,7 @@ class CharField(Field):
} }
initial = '' initial = ''
coerce_blank_to_null = False coerce_blank_to_null = False
default_empty_html = ''
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.allow_blank = kwargs.pop('allow_blank', False) self.allow_blank = kwargs.pop('allow_blank', False)
@ -947,6 +951,8 @@ class ChoiceField(Field):
self.fail('invalid_choice', input=data) self.fail('invalid_choice', input=data)
def to_representation(self, value): def to_representation(self, value):
if value in ('', None):
return value
return self.choice_strings_to_values[six.text_type(value)] return self.choice_strings_to_values[six.text_type(value)]

View File

@ -121,7 +121,10 @@ class SimpleMetadata(BaseMetadata):
if hasattr(field, 'choices'): if hasattr(field, 'choices'):
field_info['choices'] = [ field_info['choices'] = [
{'value': choice_value, 'display_name': choice_name} {
'value': choice_value,
'display_name': force_text(choice_name, strings_only=True)
}
for choice_value, choice_name in field.choices.items() for choice_value, choice_name in field.choices.items()
] ]

View File

@ -49,6 +49,21 @@ class RelatedField(Field):
@classmethod @classmethod
def many_init(cls, *args, **kwargs): def many_init(cls, *args, **kwargs):
"""
This method handles creating a parent `ManyRelatedField` instance
when the `many=True` keyword argument is passed.
Typically you won't need to override this method.
Note that we're over-cautious in passing most arguments to both parent
and child classes in order to try to cover the general case. If you're
overriding this method you'll probably want something much simpler, eg:
@classmethod
def many_init(cls, *args, **kwargs):
kwargs['child'] = cls()
return CustomManyRelatedField(*args, **kwargs)
"""
list_kwargs = {'child_relation': cls(*args, **kwargs)} list_kwargs = {'child_relation': cls(*args, **kwargs)}
for key in kwargs.keys(): for key in kwargs.keys():
if key in MANY_RELATION_KWARGS: if key in MANY_RELATION_KWARGS:
@ -306,7 +321,9 @@ class ManyRelatedField(Field):
The `ManyRelatedField` class is responsible for handling iterating through The `ManyRelatedField` class is responsible for handling iterating through
the values and passing each one to the child relationship. the values and passing each one to the child relationship.
You shouldn't need to be using this class directly yourself. This class is treated as private API.
You shouldn't generally need to be using this class directly yourself,
and should instead simply set 'many=True' on the relationship.
""" """
initial = [] initial = []
default_empty_html = [] default_empty_html = []

View File

@ -429,7 +429,10 @@ class HTMLFormRenderer(BaseRenderer):
style['base_template'] = self.base_template style['base_template'] = self.base_template
style['renderer'] = self style['renderer'] = self
if 'template' in style: # This API needs to be finessed and finalized for 3.1
if 'template' in renderer_context:
template_name = renderer_context['template']
elif 'template' in style:
template_name = style['template'] template_name = style['template']
else: else:
template_name = style['template_pack'].strip('/') + '/' + style['base_template'] template_name = style['template_pack'].strip('/') + '/' + style['base_template']
@ -522,7 +525,10 @@ class BrowsableAPIRenderer(BaseRenderer):
else: else:
instance = None instance = None
if request.method == method: # If this is valid serializer data, and the form is for the same
# HTTP method as was used in the request then use the existing
# serializer instance, rather than dynamically creating a new one.
if request.method == method and serializer is not None:
try: try:
data = request.data data = request.data
except ParseError: except ParseError:
@ -555,7 +561,14 @@ class BrowsableAPIRenderer(BaseRenderer):
if data is not None: if data is not None:
serializer.is_valid() serializer.is_valid()
form_renderer = self.form_renderer_class() form_renderer = self.form_renderer_class()
return form_renderer.render(serializer.data, self.accepted_media_type, self.renderer_context) return form_renderer.render(
serializer.data,
self.accepted_media_type,
dict(
list(self.renderer_context.items()) +
[('template', 'rest_framework/api_form.html')]
)
)
def get_raw_data_form(self, data, view, method, request): def get_raw_data_form(self, data, view, method, request):
""" """

View File

@ -310,7 +310,7 @@ class Request(object):
def _load_data_and_files(self): def _load_data_and_files(self):
""" """
Parses the request content into self.DATA and self.FILES. Parses the request content into `self.data`.
""" """
if not _hasattr(self, '_content_type'): if not _hasattr(self, '_content_type'):
self._load_method_and_content_type() self._load_method_and_content_type()

View File

@ -5,7 +5,6 @@ it is initialized with unrendered data, instead of a pre-rendered string.
The appropriate renderer is called during Django's template response rendering. The appropriate renderer is called during Django's template response rendering.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import django
from django.core.handlers.wsgi import STATUS_CODE_TEXT from django.core.handlers.wsgi import STATUS_CODE_TEXT
from django.template.response import SimpleTemplateResponse from django.template.response import SimpleTemplateResponse
from django.utils import six from django.utils import six
@ -16,9 +15,6 @@ class Response(SimpleTemplateResponse):
An HttpResponse that allows its data to be rendered into An HttpResponse that allows its data to be rendered into
arbitrary media types. arbitrary media types.
""" """
# TODO: remove that once Django 1.3 isn't supported
if django.VERSION >= (1, 4):
rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_closable_objects']
def __init__(self, data=None, status=None, def __init__(self, data=None, status=None,
template_name=None, headers=None, template_name=None, headers=None,

View File

@ -86,6 +86,15 @@ class BaseSerializer(Field):
class when `many=True` is used. You can customize it if you need to class when `many=True` is used. You can customize it if you need to
control which keyword arguments are passed to the parent, and control which keyword arguments are passed to the parent, and
which are passed to the child. which are passed to the child.
Note that we're over-cautious in passing most arguments to both parent
and child classes in order to try to cover the general case. If you're
overriding this method you'll probably want something much simpler, eg:
@classmethod
def many_init(cls, *args, **kwargs):
kwargs['child'] = cls()
return CustomListSerializer(*args, **kwargs)
""" """
child_serializer = cls(*args, **kwargs) child_serializer = cls(*args, **kwargs)
list_kwargs = {'child': child_serializer} list_kwargs = {'child': child_serializer}
@ -93,7 +102,9 @@ class BaseSerializer(Field):
(key, value) for key, value in kwargs.items() (key, value) for key, value in kwargs.items()
if key in LIST_SERIALIZER_KWARGS if key in LIST_SERIALIZER_KWARGS
])) ]))
return ListSerializer(*args, **list_kwargs) meta = getattr(cls, 'Meta', None)
list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer)
return list_serializer_class(*args, **list_kwargs)
def to_internal_value(self, data): def to_internal_value(self, data):
raise NotImplementedError('`to_internal_value()` must be implemented.') raise NotImplementedError('`to_internal_value()` must be implemented.')
@ -362,14 +373,9 @@ class Serializer(BaseSerializer):
for field in fields: for field in fields:
attribute = field.get_attribute(instance) attribute = field.get_attribute(instance)
if attribute is None: if attribute is None:
value = None ret[field.field_name] = None
else: else:
value = field.to_representation(attribute) ret[field.field_name] = field.to_representation(attribute)
transform_method = getattr(self, 'transform_' + field.field_name, None)
if transform_method is not None:
value = transform_method(value)
ret[field.field_name] = value
return ret return ret
@ -720,49 +726,62 @@ class ModelSerializer(Serializer):
# Determine if we need any additional `HiddenField` or extra keyword # Determine if we need any additional `HiddenField` or extra keyword
# arguments to deal with `unique_for` dates that are required to # arguments to deal with `unique_for` dates that are required to
# be in the input data in order to validate it. # be in the input data in order to validate it.
unique_fields = {} hidden_fields = {}
unique_constraint_names = set()
for model_field_name, field_name in model_field_mapping.items(): for model_field_name, field_name in model_field_mapping.items():
try: try:
model_field = model._meta.get_field(model_field_name) model_field = model._meta.get_field(model_field_name)
except FieldDoesNotExist: except FieldDoesNotExist:
continue continue
# Deal with each of the `unique_for_*` cases. # Include each of the `unique_for_*` field names.
for date_field_name in ( unique_constraint_names |= set([
model_field.unique_for_date, model_field.unique_for_date,
model_field.unique_for_month, model_field.unique_for_month,
model_field.unique_for_year model_field.unique_for_year
): ])
if date_field_name is None:
continue
unique_constraint_names -= set([None])
# Include each of the `unique_together` field names,
# so long as all the field names are included on the serializer.
for parent_class in [model] + list(model._meta.parents.keys()):
for unique_together_list in parent_class._meta.unique_together:
if set(fields).issuperset(set(unique_together_list)):
unique_constraint_names |= set(unique_together_list)
# Now we have all the field names that have uniqueness constraints
# applied, we can add the extra 'required=...' or 'default=...'
# arguments that are appropriate to these fields, or add a `HiddenField` for it.
for unique_constraint_name in unique_constraint_names:
# Get the model field that is refered too. # Get the model field that is refered too.
date_field = model._meta.get_field(date_field_name) unique_constraint_field = model._meta.get_field(unique_constraint_name)
if date_field.auto_now_add: if getattr(unique_constraint_field, 'auto_now_add', None):
default = CreateOnlyDefault(timezone.now) default = CreateOnlyDefault(timezone.now)
elif date_field.auto_now: elif getattr(unique_constraint_field, 'auto_now', None):
default = timezone.now default = timezone.now
elif date_field.has_default(): elif unique_constraint_field.has_default():
default = model_field.default default = unique_constraint_field.default
else: else:
default = empty default = empty
if date_field_name in model_field_mapping: if unique_constraint_name in model_field_mapping:
# The corresponding date field is present in the serializer # The corresponding field is present in the serializer
if date_field_name not in extra_kwargs: if unique_constraint_name not in extra_kwargs:
extra_kwargs[date_field_name] = {} extra_kwargs[unique_constraint_name] = {}
if default is empty: if default is empty:
if 'required' not in extra_kwargs[date_field_name]: if 'required' not in extra_kwargs[unique_constraint_name]:
extra_kwargs[date_field_name]['required'] = True extra_kwargs[unique_constraint_name]['required'] = True
else: else:
if 'default' not in extra_kwargs[date_field_name]: if 'default' not in extra_kwargs[unique_constraint_name]:
extra_kwargs[date_field_name]['default'] = default extra_kwargs[unique_constraint_name]['default'] = default
else: elif default is not empty:
# The corresponding date field is not present in the, # The corresponding field is not present in the,
# serializer. We have a default to use for the date, so # serializer. We have a default to use for it, so
# add in a hidden field that populates it. # add in a hidden field that populates it.
unique_fields[date_field_name] = HiddenField(default=default) hidden_fields[unique_constraint_name] = HiddenField(default=default)
# Now determine the fields that should be included on the serializer. # Now determine the fields that should be included on the serializer.
for field_name in fields: for field_name in fields:
@ -838,12 +857,16 @@ class ModelSerializer(Serializer):
'validators', 'queryset' 'validators', 'queryset'
]: ]:
kwargs.pop(attr, None) kwargs.pop(attr, None)
if extras.get('default') and kwargs.get('required') is False:
kwargs.pop('required')
kwargs.update(extras) kwargs.update(extras)
# Create the serializer field. # Create the serializer field.
ret[field_name] = field_cls(**kwargs) ret[field_name] = field_cls(**kwargs)
for field_name, field in unique_fields.items(): for field_name, field in hidden_fields.items():
ret[field_name] = field ret[field_name] = field
return ret return ret

View File

@ -0,0 +1,8 @@
{% load rest_framework %}
{% csrf_token %}
{% for field in form %}
{% if not field.read_only %}
{% render_field field style=style %}
{% endif %}
{% endfor %}
<!-- form.non_field_errors -->

View File

@ -237,13 +237,6 @@
</div> </div>
<!-- END Content --> <!-- END Content -->
</div><!-- /.container --> </div><!-- /.container -->
<footer>
{% block footer %}
<p>Sponsored by <a href="http://dabapps.com/">DabApps</a>.</p>
{% endblock %}
</footer>
</div><!-- ./wrapper --> </div><!-- ./wrapper -->
{% block script %} {% block script %}

View File

@ -5,9 +5,12 @@
<legend class="control-label col-sm-2 {% if style.hide_label %}sr-only{% endif %}" style="border-bottom: 0">{{ field.label }}</legend> <legend class="control-label col-sm-2 {% if style.hide_label %}sr-only{% endif %}" style="border-bottom: 0">{{ field.label }}</legend>
</div> </div>
{% endif %} {% endif %}
<!--
<ul> <ul>
{% for child in field.value %} {% for child in field.value %}
<li>TODO</li> <li>TODO</li>
{% endfor %} {% endfor %}
</ul> </ul>
-->
<p>Lists are not currently supported in HTML input.</p>
</fieldset> </fieldset>

View File

@ -4,6 +4,9 @@
{% endif %} {% endif %}
<div class="col-sm-10"> <div class="col-sm-10">
<select class="form-control" name="{{ field.name }}"> <select class="form-control" name="{{ field.name }}">
{% if field.allow_null %}
<option value="" {% if not field.value %}selected{% endif %}>--------</option>
{% endif %}
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<option value="{{ key }}" {% if key == field.value %}selected{% endif %}>{{ text }}</option> <option value="{{ key }}" {% if key == field.value %}selected{% endif %}>{{ text }}</option>
{% endfor %} {% endfor %}

View File

@ -0,0 +1 @@
<span>Lists are not currently supported in HTML input.</span>

View File

@ -3,6 +3,9 @@
<label class="sr-only">{{ field.label }}</label> <label class="sr-only">{{ field.label }}</label>
{% endif %} {% endif %}
<select class="form-control" name="{{ field.name }}"> <select class="form-control" name="{{ field.name }}">
{% if field.allow_null %}
<option value="" {% if not field.value %}selected{% endif %}>--------</option>
{% endif %}
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<option value="{{ key }}" {% if key == field.value %}selected{% endif %}>{{ text }}</option> <option value="{{ key }}" {% if key == field.value %}selected{% endif %}>{{ text }}</option>
{% endfor %} {% endfor %}

View File

@ -22,7 +22,7 @@
<div id="div_id_username" <div id="div_id_username"
class="clearfix control-group {% if form.username.errors %}error{% endif %}"> class="clearfix control-group {% if form.username.errors %}error{% endif %}">
<div class="controls"> <div class="controls">
<Label class="span4">Username:</label> <label class="span4">Username:</label>
<input style="height: 25px" type="text" name="username" maxlength="100" <input style="height: 25px" type="text" name="username" maxlength="100"
autocapitalize="off" autocapitalize="off"
autocorrect="off" class="span12 textinput textInput" autocorrect="off" class="span12 textinput textInput"
@ -36,9 +36,10 @@
</div> </div>
</div> </div>
<div id="div_id_password" <div id="div_id_password"
class="clearfix control-group {% if form.password.errors %}error{% endif %}"> class="clearfix control-group {% if form.password.errors %}error{% endif %}"
style="margin-top: 10px">
<div class="controls"> <div class="controls">
<Label class="span4">Password:</label> <label class="span4">Password:</label>
<input style="height: 25px" type="password" name="password" maxlength="100" <input style="height: 25px" type="password" name="password" maxlength="100"
autocapitalize="off" autocorrect="off" class="span12 textinput textInput" autocapitalize="off" autocorrect="off" class="span12 textinput textInput"
id="id_password" required> id="id_password" required>
@ -55,7 +56,7 @@
<div class="well well-small text-error" style="border: none">{{ error }}</div> <div class="well well-small text-error" style="border: none">{{ error }}</div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<div class="form-actions-no-box"> <div class="form-actions-no-box" style="margin-top: 20px">
<input type="submit" name="submit" value="Log in" class="btn btn-primary" id="submit-id-submit"> <input type="submit" name="submit" value="Log in" class="btn btn-primary" id="submit-id-submit">
</div> </div>
</form> </form>

View File

@ -4,4 +4,5 @@
{% for field_item in field.value.field_items.values() %} {% for field_item in field.value.field_items.values() %}
{{ renderer.render_field(field_item, layout=layout) }} {{ renderer.render_field(field_item, layout=layout) }}
{% endfor %} --> {% endfor %} -->
<p>Lists are not currently supported in HTML input.</p>
</fieldset> </fieldset>

View File

@ -3,6 +3,9 @@
<label {% if style.hide_label %}class="sr-only"{% endif %}>{{ field.label }}</label> <label {% if style.hide_label %}class="sr-only"{% endif %}>{{ field.label }}</label>
{% endif %} {% endif %}
<select class="form-control" name="{{ field.name }}"> <select class="form-control" name="{{ field.name }}">
{% if field.allow_null %}
<option value="" {% if not field.value %}selected{% endif %}>--------</option>
{% endif %}
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<option value="{{ key }}" {% if key == field.value %}selected{% endif %}>{{ text }}</option> <option value="{{ key }}" {% if key == field.value %}selected{% endif %}>{{ text }}</option>
{% endfor %} {% endfor %}

View File

@ -6,6 +6,7 @@ relationships and their associated metadata.
Usage: `get_field_info(model)` returns a `FieldInfo` instance. Usage: `get_field_info(model)` returns a `FieldInfo` instance.
""" """
from collections import namedtuple from collections import namedtuple
from django.core.exceptions import ImproperlyConfigured
from django.db import models from django.db import models
from django.utils import six from django.utils import six
from rest_framework.compat import OrderedDict from rest_framework.compat import OrderedDict
@ -43,7 +44,11 @@ def _resolve_model(obj):
""" """
if isinstance(obj, six.string_types) and len(obj.split('.')) == 2: if isinstance(obj, six.string_types) and len(obj.split('.')) == 2:
app_name, model_name = obj.split('.') app_name, model_name = obj.split('.')
return models.get_model(app_name, model_name) resolved_model = models.get_model(app_name, model_name)
if resolved_model is None:
msg = "Django did not return a model for {0}.{1}"
raise ImproperlyConfigured(msg.format(app_name, model_name))
return resolved_model
elif inspect.isclass(obj) and issubclass(obj, models.Model): elif inspect.isclass(obj) and issubclass(obj, models.Model):
return obj return obj
raise ValueError("{0} is not a Django model".format(obj)) raise ValueError("{0} is not a Django model".format(obj))

View File

@ -93,6 +93,9 @@ class UniqueTogetherValidator:
The `UniqueTogetherValidator` always forces an implied 'required' The `UniqueTogetherValidator` always forces an implied 'required'
state on the fields it applies to. state on the fields it applies to.
""" """
if self.instance is not None:
return
missing = dict([ missing = dict([
(field_name, self.missing_message) (field_name, self.missing_message)
for field_name in self.fields for field_name in self.fields
@ -105,8 +108,17 @@ class UniqueTogetherValidator:
""" """
Filter the queryset to all instances matching the given attributes. Filter the queryset to all instances matching the given attributes.
""" """
# If this is an update, then any unprovided field should
# have it's value set based on the existing instance attribute.
if self.instance is not None:
for field_name in self.fields:
if field_name not in attrs:
attrs[field_name] = getattr(self.instance, field_name)
# Determine the filter keyword arguments and filter the queryset.
filter_kwargs = dict([ filter_kwargs = dict([
(field_name, attrs[field_name]) for field_name in self.fields (field_name, attrs[field_name])
for field_name in self.fields
]) ])
return queryset.filter(**filter_kwargs) return queryset.filter(**filter_kwargs)

View File

@ -142,7 +142,7 @@ class SessionAuthTests(TestCase):
cf. [#1810](https://github.com/tomchristie/django-rest-framework/pull/1810) cf. [#1810](https://github.com/tomchristie/django-rest-framework/pull/1810)
""" """
response = self.csrf_client.get('/auth/login/') response = self.csrf_client.get('/auth/login/')
self.assertContains(response, '<Label class="span4">Username:</label>') self.assertContains(response, '<label class="span4">Username:</label>')
def test_post_form_session_auth_failing_csrf(self): def test_post_form_session_auth_failing_csrf(self):
""" """

View File

@ -793,7 +793,8 @@ class TestChoiceField(FieldValues):
'amazing': ['`amazing` is not a valid choice.'] 'amazing': ['`amazing` is not a valid choice.']
} }
outputs = { outputs = {
'good': 'good' 'good': 'good',
'': ''
} }
field = serializers.ChoiceField( field = serializers.ChoiceField(
choices=[ choices=[

View File

@ -1,92 +0,0 @@
# from __future__ import unicode_literals
# from django.test import TestCase
# from django.utils import six
# from rest_framework import serializers
# from rest_framework.compat import BytesIO
# import datetime
# class UploadedFile(object):
# def __init__(self, file=None, created=None):
# self.file = file
# self.created = created or datetime.datetime.now()
# class UploadedFileSerializer(serializers.Serializer):
# file = serializers.FileField(required=False)
# created = serializers.DateTimeField()
# def restore_object(self, attrs, instance=None):
# if instance:
# instance.file = attrs['file']
# instance.created = attrs['created']
# return instance
# return UploadedFile(**attrs)
# class FileSerializerTests(TestCase):
# def test_create(self):
# now = datetime.datetime.now()
# file = BytesIO(six.b('stuff'))
# file.name = 'stuff.txt'
# file.size = len(file.getvalue())
# serializer = UploadedFileSerializer(data={'created': now}, files={'file': file})
# uploaded_file = UploadedFile(file=file, created=now)
# self.assertTrue(serializer.is_valid())
# self.assertEqual(serializer.object.created, uploaded_file.created)
# self.assertEqual(serializer.object.file, uploaded_file.file)
# self.assertFalse(serializer.object is uploaded_file)
# def test_creation_failure(self):
# """
# Passing files=None should result in an ValidationError
# Regression test for:
# https://github.com/tomchristie/django-rest-framework/issues/542
# """
# now = datetime.datetime.now()
# serializer = UploadedFileSerializer(data={'created': now})
# self.assertTrue(serializer.is_valid())
# self.assertEqual(serializer.object.created, now)
# self.assertIsNone(serializer.object.file)
# def test_remove_with_empty_string(self):
# """
# Passing empty string as data should cause file to be removed
# Test for:
# https://github.com/tomchristie/django-rest-framework/issues/937
# """
# now = datetime.datetime.now()
# file = BytesIO(six.b('stuff'))
# file.name = 'stuff.txt'
# file.size = len(file.getvalue())
# uploaded_file = UploadedFile(file=file, created=now)
# serializer = UploadedFileSerializer(instance=uploaded_file, data={'created': now, 'file': ''})
# self.assertTrue(serializer.is_valid())
# self.assertEqual(serializer.object.created, uploaded_file.created)
# self.assertIsNone(serializer.object.file)
# def test_validation_error_with_non_file(self):
# """
# Passing non-files should raise a validation error.
# """
# now = datetime.datetime.now()
# errmsg = 'No file was submitted. Check the encoding type on the form.'
# serializer = UploadedFileSerializer(data={'created': now, 'file': 'abc'})
# self.assertFalse(serializer.is_valid())
# self.assertEqual(serializer.errors, {'file': [errmsg]})
# def test_validation_with_no_data(self):
# """
# Validation should still function when no data dictionary is provided.
# """
# uploaded_file = BytesIO(six.b('stuff'))
# uploaded_file.name = 'stuff.txt'
# uploaded_file.size = len(uploaded_file.getvalue())
# serializer = UploadedFileSerializer(files={'file': uploaded_file})
# self.assertFalse(serializer.is_valid())

View File

@ -1,406 +0,0 @@
# from __future__ import unicode_literals
# import json
# from django.test import TestCase
# from rest_framework import generics, status, serializers
# from django.conf.urls import patterns, url
# from rest_framework.settings import api_settings
# from rest_framework.test import APIRequestFactory
# from tests.models import (
# Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment,
# Album, Photo, OptionalRelationModel
# )
# factory = APIRequestFactory()
# class BlogPostCommentSerializer(serializers.ModelSerializer):
# url = serializers.HyperlinkedIdentityField(view_name='blogpostcomment-detail')
# text = serializers.CharField()
# blog_post_url = serializers.HyperlinkedRelatedField(source='blog_post', view_name='blogpost-detail')
# class Meta:
# model = BlogPostComment
# fields = ('text', 'blog_post_url', 'url')
# class PhotoSerializer(serializers.Serializer):
# description = serializers.CharField()
# album_url = serializers.HyperlinkedRelatedField(source='album', view_name='album-detail', queryset=Album.objects.all(), lookup_field='title')
# def restore_object(self, attrs, instance=None):
# return Photo(**attrs)
# class AlbumSerializer(serializers.ModelSerializer):
# url = serializers.HyperlinkedIdentityField(view_name='album-detail', lookup_field='title')
# class Meta:
# model = Album
# fields = ('title', 'url')
# class BasicSerializer(serializers.HyperlinkedModelSerializer):
# class Meta:
# model = BasicModel
# class AnchorSerializer(serializers.HyperlinkedModelSerializer):
# class Meta:
# model = Anchor
# class ManyToManySerializer(serializers.HyperlinkedModelSerializer):
# class Meta:
# model = ManyToManyModel
# class BlogPostSerializer(serializers.ModelSerializer):
# class Meta:
# model = BlogPost
# class OptionalRelationSerializer(serializers.HyperlinkedModelSerializer):
# class Meta:
# model = OptionalRelationModel
# class BasicList(generics.ListCreateAPIView):
# queryset = BasicModel.objects.all()
# serializer_class = BasicSerializer
# class BasicDetail(generics.RetrieveUpdateDestroyAPIView):
# queryset = BasicModel.objects.all()
# serializer_class = BasicSerializer
# class AnchorDetail(generics.RetrieveAPIView):
# queryset = Anchor.objects.all()
# serializer_class = AnchorSerializer
# class ManyToManyList(generics.ListAPIView):
# queryset = ManyToManyModel.objects.all()
# serializer_class = ManyToManySerializer
# class ManyToManyDetail(generics.RetrieveAPIView):
# queryset = ManyToManyModel.objects.all()
# serializer_class = ManyToManySerializer
# class BlogPostCommentListCreate(generics.ListCreateAPIView):
# queryset = BlogPostComment.objects.all()
# serializer_class = BlogPostCommentSerializer
# class BlogPostCommentDetail(generics.RetrieveAPIView):
# queryset = BlogPostComment.objects.all()
# serializer_class = BlogPostCommentSerializer
# class BlogPostDetail(generics.RetrieveAPIView):
# queryset = BlogPost.objects.all()
# serializer_class = BlogPostSerializer
# class PhotoListCreate(generics.ListCreateAPIView):
# queryset = Photo.objects.all()
# serializer_class = PhotoSerializer
# class AlbumDetail(generics.RetrieveAPIView):
# queryset = Album.objects.all()
# serializer_class = AlbumSerializer
# lookup_field = 'title'
# class OptionalRelationDetail(generics.RetrieveUpdateDestroyAPIView):
# queryset = OptionalRelationModel.objects.all()
# serializer_class = OptionalRelationSerializer
# urlpatterns = patterns(
# '',
# url(r'^basic/$', BasicList.as_view(), name='basicmodel-list'),
# url(r'^basic/(?P<pk>\d+)/$', BasicDetail.as_view(), name='basicmodel-detail'),
# url(r'^anchor/(?P<pk>\d+)/$', AnchorDetail.as_view(), name='anchor-detail'),
# url(r'^manytomany/$', ManyToManyList.as_view(), name='manytomanymodel-list'),
# url(r'^manytomany/(?P<pk>\d+)/$', ManyToManyDetail.as_view(), name='manytomanymodel-detail'),
# url(r'^posts/(?P<pk>\d+)/$', BlogPostDetail.as_view(), name='blogpost-detail'),
# url(r'^comments/$', BlogPostCommentListCreate.as_view(), name='blogpostcomment-list'),
# url(r'^comments/(?P<pk>\d+)/$', BlogPostCommentDetail.as_view(), name='blogpostcomment-detail'),
# url(r'^albums/(?P<title>\w[\w-]*)/$', AlbumDetail.as_view(), name='album-detail'),
# url(r'^photos/$', PhotoListCreate.as_view(), name='photo-list'),
# url(r'^optionalrelation/(?P<pk>\d+)/$', OptionalRelationDetail.as_view(), name='optionalrelationmodel-detail'),
# )
# class TestBasicHyperlinkedView(TestCase):
# urls = 'tests.test_hyperlinkedserializers'
# def setUp(self):
# """
# Create 3 BasicModel instances.
# """
# items = ['foo', 'bar', 'baz']
# for item in items:
# BasicModel(text=item).save()
# self.objects = BasicModel.objects
# self.data = [
# {'url': 'http://testserver/basic/%d/' % obj.id, 'text': obj.text}
# for obj in self.objects.all()
# ]
# self.list_view = BasicList.as_view()
# self.detail_view = BasicDetail.as_view()
# def test_get_list_view(self):
# """
# GET requests to ListCreateAPIView should return list of objects.
# """
# request = factory.get('/basic/')
# response = self.list_view(request).render()
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(response.data, self.data)
# def test_get_detail_view(self):
# """
# GET requests to ListCreateAPIView should return list of objects.
# """
# request = factory.get('/basic/1')
# response = self.detail_view(request, pk=1).render()
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(response.data, self.data[0])
# class TestManyToManyHyperlinkedView(TestCase):
# urls = 'tests.test_hyperlinkedserializers'
# def setUp(self):
# """
# Create 3 BasicModel instances.
# """
# items = ['foo', 'bar', 'baz']
# anchors = []
# for item in items:
# anchor = Anchor(text=item)
# anchor.save()
# anchors.append(anchor)
# manytomany = ManyToManyModel()
# manytomany.save()
# manytomany.rel.add(*anchors)
# self.data = [{
# 'url': 'http://testserver/manytomany/1/',
# 'rel': [
# 'http://testserver/anchor/1/',
# 'http://testserver/anchor/2/',
# 'http://testserver/anchor/3/',
# ]
# }]
# self.list_view = ManyToManyList.as_view()
# self.detail_view = ManyToManyDetail.as_view()
# def test_get_list_view(self):
# """
# GET requests to ListCreateAPIView should return list of objects.
# """
# request = factory.get('/manytomany/')
# response = self.list_view(request)
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(response.data, self.data)
# def test_get_detail_view(self):
# """
# GET requests to ListCreateAPIView should return list of objects.
# """
# request = factory.get('/manytomany/1/')
# response = self.detail_view(request, pk=1)
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(response.data, self.data[0])
# class TestHyperlinkedIdentityFieldLookup(TestCase):
# urls = 'tests.test_hyperlinkedserializers'
# def setUp(self):
# """
# Create 3 Album instances.
# """
# titles = ['foo', 'bar', 'baz']
# for title in titles:
# album = Album(title=title)
# album.save()
# self.detail_view = AlbumDetail.as_view()
# self.data = {
# 'foo': {'title': 'foo', 'url': 'http://testserver/albums/foo/'},
# 'bar': {'title': 'bar', 'url': 'http://testserver/albums/bar/'},
# 'baz': {'title': 'baz', 'url': 'http://testserver/albums/baz/'}
# }
# def test_lookup_field(self):
# """
# GET requests to AlbumDetail view should return serialized Albums
# with a url field keyed by `title`.
# """
# for album in Album.objects.all():
# request = factory.get('/albums/{0}/'.format(album.title))
# response = self.detail_view(request, title=album.title)
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(response.data, self.data[album.title])
# class TestCreateWithForeignKeys(TestCase):
# urls = 'tests.test_hyperlinkedserializers'
# def setUp(self):
# """
# Create a blog post
# """
# self.post = BlogPost.objects.create(title="Test post")
# self.create_view = BlogPostCommentListCreate.as_view()
# def test_create_comment(self):
# data = {
# 'text': 'A test comment',
# 'blog_post_url': 'http://testserver/posts/1/'
# }
# request = factory.post('/comments/', data=data)
# response = self.create_view(request)
# self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# self.assertEqual(response['Location'], 'http://testserver/comments/1/')
# self.assertEqual(self.post.blogpostcomment_set.count(), 1)
# self.assertEqual(self.post.blogpostcomment_set.all()[0].text, 'A test comment')
# class TestCreateWithForeignKeysAndCustomSlug(TestCase):
# urls = 'tests.test_hyperlinkedserializers'
# def setUp(self):
# """
# Create an Album
# """
# self.post = Album.objects.create(title='test-album')
# self.list_create_view = PhotoListCreate.as_view()
# def test_create_photo(self):
# data = {
# 'description': 'A test photo',
# 'album_url': 'http://testserver/albums/test-album/'
# }
# request = factory.post('/photos/', data=data)
# response = self.list_create_view(request)
# self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# self.assertNotIn('Location', response, msg='Location should only be included if there is a "url" field on the serializer')
# self.assertEqual(self.post.photo_set.count(), 1)
# self.assertEqual(self.post.photo_set.all()[0].description, 'A test photo')
# class TestOptionalRelationHyperlinkedView(TestCase):
# urls = 'tests.test_hyperlinkedserializers'
# def setUp(self):
# """
# Create 1 OptionalRelationModel instances.
# """
# OptionalRelationModel().save()
# self.objects = OptionalRelationModel.objects
# self.detail_view = OptionalRelationDetail.as_view()
# self.data = {"url": "http://testserver/optionalrelation/1/", "other": None}
# def test_get_detail_view(self):
# """
# GET requests to RetrieveAPIView with optional relations should return None
# for non existing relations.
# """
# request = factory.get('/optionalrelationmodel-detail/1')
# response = self.detail_view(request, pk=1)
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(response.data, self.data)
# def test_put_detail_view(self):
# """
# PUT requests to RetrieveUpdateDestroyAPIView with optional relations
# should accept None for non existing relations.
# """
# response = self.client.put('/optionalrelation/1/',
# data=json.dumps(self.data),
# content_type='application/json')
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# class TestOverriddenURLField(TestCase):
# def setUp(self):
# class OverriddenURLSerializer(serializers.HyperlinkedModelSerializer):
# url = serializers.SerializerMethodField('get_url')
# class Meta:
# model = BlogPost
# fields = ('title', 'url')
# def get_url(self, obj):
# return 'foo bar'
# self.Serializer = OverriddenURLSerializer
# self.obj = BlogPost.objects.create(title='New blog post')
# def test_overridden_url_field(self):
# """
# The 'url' field should respect overriding.
# Regression test for #936.
# """
# serializer = self.Serializer(self.obj)
# self.assertEqual(
# serializer.data,
# {'title': 'New blog post', 'url': 'foo bar'}
# )
# class TestURLFieldNameBySettings(TestCase):
# urls = 'tests.test_hyperlinkedserializers'
# def setUp(self):
# self.saved_url_field_name = api_settings.URL_FIELD_NAME
# api_settings.URL_FIELD_NAME = 'global_url_field'
# class Serializer(serializers.HyperlinkedModelSerializer):
# class Meta:
# model = BlogPost
# fields = ('title', api_settings.URL_FIELD_NAME)
# self.Serializer = Serializer
# self.obj = BlogPost.objects.create(title="New blog post")
# def tearDown(self):
# api_settings.URL_FIELD_NAME = self.saved_url_field_name
# def test_overridden_url_field_name(self):
# request = factory.get('/posts/')
# serializer = self.Serializer(self.obj, context={'request': request})
# self.assertIn(api_settings.URL_FIELD_NAME, serializer.data)
# class TestURLFieldNameByOptions(TestCase):
# urls = 'tests.test_hyperlinkedserializers'
# def setUp(self):
# class Serializer(serializers.HyperlinkedModelSerializer):
# class Meta:
# model = BlogPost
# fields = ('title', 'serializer_url_field')
# url_field_name = 'serializer_url_field'
# self.Serializer = Serializer
# self.obj = BlogPost.objects.create(title="New blog post")
# def test_overridden_url_field_name(self):
# request = factory.get('/posts/')
# serializer = self.Serializer(self.obj, context={'request': request})
# self.assertIn(self.Serializer.Meta.url_field_name, serializer.data)

View File

@ -1,39 +0,0 @@
# from django.core.urlresolvers import reverse
# from django.conf.urls import patterns, url
# from rest_framework import serializers, generics
# from rest_framework.test import APITestCase
# from tests.models import NullableForeignKeySource
# class NullableFKSourceSerializer(serializers.ModelSerializer):
# class Meta:
# model = NullableForeignKeySource
# class NullableFKSourceDetail(generics.RetrieveUpdateDestroyAPIView):
# queryset = NullableForeignKeySource.objects.all()
# serializer_class = NullableFKSourceSerializer
# urlpatterns = patterns(
# '',
# url(r'^objects/(?P<pk>\d+)/$', NullableFKSourceDetail.as_view(), name='object-detail'),
# )
# class NullableForeignKeyTests(APITestCase):
# """
# DRF should be able to handle nullable foreign keys when a test
# Client POST/PUT request is made with its own serialized object.
# """
# urls = 'tests.test_nullable_fields'
# def test_updating_object_with_null_fk(self):
# obj = NullableForeignKeySource(name='example', target=None)
# obj.save()
# serialized_data = NullableFKSourceSerializer(obj).data
# response = self.client.put(reverse('object-detail', args=[obj.pk]), serialized_data)
# self.assertEqual(response.data, serialized_data)

View File

@ -134,155 +134,3 @@ class TestSlugRelatedField(APISimpleTestCase):
def test_representation(self): def test_representation(self):
representation = self.field.to_representation(self.instance) representation = self.field.to_representation(self.instance)
assert representation == self.instance.name assert representation == self.instance.name
# Older tests, for review...
# """
# General tests for relational fields.
# """
# from __future__ import unicode_literals
# from django import get_version
# from django.db import models
# from django.test import TestCase
# from django.utils import unittest
# from rest_framework import serializers
# from tests.models import BlogPost
# class NullModel(models.Model):
# pass
# class FieldTests(TestCase):
# def test_pk_related_field_with_empty_string(self):
# """
# Regression test for #446
# https://github.com/tomchristie/django-rest-framework/issues/446
# """
# field = serializers.PrimaryKeyRelatedField(queryset=NullModel.objects.all())
# self.assertRaises(serializers.ValidationError, field.to_primitive, '')
# self.assertRaises(serializers.ValidationError, field.to_primitive, [])
# def test_hyperlinked_related_field_with_empty_string(self):
# field = serializers.HyperlinkedRelatedField(queryset=NullModel.objects.all(), view_name='')
# self.assertRaises(serializers.ValidationError, field.to_primitive, '')
# self.assertRaises(serializers.ValidationError, field.to_primitive, [])
# def test_slug_related_field_with_empty_string(self):
# field = serializers.SlugRelatedField(queryset=NullModel.objects.all(), slug_field='pk')
# self.assertRaises(serializers.ValidationError, field.to_primitive, '')
# self.assertRaises(serializers.ValidationError, field.to_primitive, [])
# class TestManyRelatedMixin(TestCase):
# def test_missing_many_to_many_related_field(self):
# '''
# Regression test for #632
# https://github.com/tomchristie/django-rest-framework/pull/632
# '''
# field = serializers.RelatedField(many=True, read_only=False)
# into = {}
# field.field_from_native({}, None, 'field_name', into)
# self.assertEqual(into['field_name'], [])
# # Regression tests for #694 (`source` attribute on related fields)
# class RelatedFieldSourceTests(TestCase):
# def test_related_manager_source(self):
# """
# Relational fields should be able to use manager-returning methods as their source.
# """
# BlogPost.objects.create(title='blah')
# field = serializers.RelatedField(many=True, source='get_blogposts_manager')
# class ClassWithManagerMethod(object):
# def get_blogposts_manager(self):
# return BlogPost.objects
# obj = ClassWithManagerMethod()
# value = field.field_to_native(obj, 'field_name')
# self.assertEqual(value, ['BlogPost object'])
# def test_related_queryset_source(self):
# """
# Relational fields should be able to use queryset-returning methods as their source.
# """
# BlogPost.objects.create(title='blah')
# field = serializers.RelatedField(many=True, source='get_blogposts_queryset')
# class ClassWithQuerysetMethod(object):
# def get_blogposts_queryset(self):
# return BlogPost.objects.all()
# obj = ClassWithQuerysetMethod()
# value = field.field_to_native(obj, 'field_name')
# self.assertEqual(value, ['BlogPost object'])
# def test_dotted_source(self):
# """
# Source argument should support dotted.source notation.
# """
# BlogPost.objects.create(title='blah')
# field = serializers.RelatedField(many=True, source='a.b.c')
# class ClassWithQuerysetMethod(object):
# a = {
# 'b': {
# 'c': BlogPost.objects.all()
# }
# }
# obj = ClassWithQuerysetMethod()
# value = field.field_to_native(obj, 'field_name')
# self.assertEqual(value, ['BlogPost object'])
# # Regression for #1129
# def test_exception_for_incorect_fk(self):
# """
# Check that the exception message are correct if the source field
# doesn't exist.
# """
# from tests.models import ManyToManySource
# class Meta:
# model = ManyToManySource
# attrs = {
# 'name': serializers.SlugRelatedField(
# slug_field='name', source='banzai'),
# 'Meta': Meta,
# }
# TestSerializer = type(
# str('TestSerializer'),
# (serializers.ModelSerializer,),
# attrs
# )
# with self.assertRaises(AttributeError):
# TestSerializer(data={'name': 'foo'})
# @unittest.skipIf(get_version() < '1.6.0', 'Upstream behaviour changed in v1.6')
# class RelatedFieldChoicesTests(TestCase):
# """
# Tests for #1408 "Web browseable API doesn't have blank option on drop down list box"
# https://github.com/tomchristie/django-rest-framework/issues/1408
# """
# def test_blank_option_is_added_to_choice_if_required_equals_false(self):
# """
# """
# post = BlogPost(title="Checking blank option is added")
# post.save()
# queryset = BlogPost.objects.all()
# field = serializers.RelatedField(required=False, queryset=queryset)
# choice_count = BlogPost.objects.count()
# widget_count = len(field.widget.choices)
# self.assertEqual(widget_count, choice_count + 1, 'BLANK_CHOICE_DASH option should have been added')

View File

@ -411,30 +411,6 @@ class HyperlinkedNullableForeignKeyTests(TestCase):
] ]
self.assertEqual(serializer.data, expected) self.assertEqual(serializer.data, expected)
# # reverse foreign keys MUST be read_only
# # In the general case they do not provide .remove() or .clear()
# # and cannot be arbitrarily set.
# def test_reverse_foreign_key_update(self):
# data = {'id': 1, 'name': 'target-1', 'sources': [1]}
# instance = ForeignKeyTarget.objects.get(pk=1)
# serializer = ForeignKeyTargetSerializer(instance, data=data)
# print serializer.is_valid()
# print serializer.errors
# print serializer
# self.assertTrue(serializer.is_valid())
# serializer.save()
# self.assertEqual(serializer.data, data)
# # Ensure target 1 is updated, and everything else is as expected
# queryset = ForeignKeyTarget.objects.all()
# serializer = ForeignKeyTargetSerializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'target-1', 'sources': [1]},
# {'id': 2, 'name': 'target-2', 'sources': []},
# ]
# self.assertEqual(serializer.data, expected)
class HyperlinkedNullableOneToOneTests(TestCase): class HyperlinkedNullableOneToOneTests(TestCase):
urls = 'tests.test_relations_hyperlink' urls = 'tests.test_relations_hyperlink'
@ -455,72 +431,3 @@ class HyperlinkedNullableOneToOneTests(TestCase):
{'url': 'http://testserver/onetoonetarget/2/', 'name': 'target-2', 'nullable_source': None}, {'url': 'http://testserver/onetoonetarget/2/', 'name': 'target-2', 'nullable_source': None},
] ]
self.assertEqual(serializer.data, expected) self.assertEqual(serializer.data, expected)
# # Regression tests for #694 (`source` attribute on related fields)
# class HyperlinkedRelatedFieldSourceTests(TestCase):
# urls = 'tests.test_relations_hyperlink'
# def test_related_manager_source(self):
# """
# Relational fields should be able to use manager-returning methods as their source.
# """
# BlogPost.objects.create(title='blah')
# field = serializers.HyperlinkedRelatedField(
# many=True,
# source='get_blogposts_manager',
# view_name='dummy-url',
# )
# field.context = {'request': request}
# class ClassWithManagerMethod(object):
# def get_blogposts_manager(self):
# return BlogPost.objects
# obj = ClassWithManagerMethod()
# value = field.field_to_native(obj, 'field_name')
# self.assertEqual(value, ['http://testserver/dummyurl/1/'])
# def test_related_queryset_source(self):
# """
# Relational fields should be able to use queryset-returning methods as their source.
# """
# BlogPost.objects.create(title='blah')
# field = serializers.HyperlinkedRelatedField(
# many=True,
# source='get_blogposts_queryset',
# view_name='dummy-url',
# )
# field.context = {'request': request}
# class ClassWithQuerysetMethod(object):
# def get_blogposts_queryset(self):
# return BlogPost.objects.all()
# obj = ClassWithQuerysetMethod()
# value = field.field_to_native(obj, 'field_name')
# self.assertEqual(value, ['http://testserver/dummyurl/1/'])
# def test_dotted_source(self):
# """
# Source argument should support dotted.source notation.
# """
# BlogPost.objects.create(title='blah')
# field = serializers.HyperlinkedRelatedField(
# many=True,
# source='a.b.c',
# view_name='dummy-url',
# )
# field.context = {'request': request}
# class ClassWithQuerysetMethod(object):
# a = {
# 'b': {
# 'c': BlogPost.objects.all()
# }
# }
# obj = ClassWithQuerysetMethod()
# value = field.field_to_native(obj, 'field_name')
# self.assertEqual(value, ['http://testserver/dummyurl/1/'])

View File

@ -1,326 +0,0 @@
# from __future__ import unicode_literals
# from django.db import models
# from django.test import TestCase
# from rest_framework import serializers
# from .models import OneToOneTarget
# class OneToOneSource(models.Model):
# name = models.CharField(max_length=100)
# target = models.OneToOneField(OneToOneTarget, related_name='source',
# null=True, blank=True)
# class OneToManyTarget(models.Model):
# name = models.CharField(max_length=100)
# class OneToManySource(models.Model):
# name = models.CharField(max_length=100)
# target = models.ForeignKey(OneToManyTarget, related_name='sources')
# class ReverseNestedOneToOneTests(TestCase):
# def setUp(self):
# class OneToOneSourceSerializer(serializers.ModelSerializer):
# class Meta:
# model = OneToOneSource
# fields = ('id', 'name')
# class OneToOneTargetSerializer(serializers.ModelSerializer):
# source = OneToOneSourceSerializer()
# class Meta:
# model = OneToOneTarget
# fields = ('id', 'name', 'source')
# self.Serializer = OneToOneTargetSerializer
# for idx in range(1, 4):
# target = OneToOneTarget(name='target-%d' % idx)
# target.save()
# source = OneToOneSource(name='source-%d' % idx, target=target)
# source.save()
# def test_one_to_one_retrieve(self):
# queryset = OneToOneTarget.objects.all()
# serializer = self.Serializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'target-1', 'source': {'id': 1, 'name': 'source-1'}},
# {'id': 2, 'name': 'target-2', 'source': {'id': 2, 'name': 'source-2'}},
# {'id': 3, 'name': 'target-3', 'source': {'id': 3, 'name': 'source-3'}}
# ]
# self.assertEqual(serializer.data, expected)
# def test_one_to_one_create(self):
# data = {'id': 4, 'name': 'target-4', 'source': {'id': 4, 'name': 'source-4'}}
# serializer = self.Serializer(data=data)
# self.assertTrue(serializer.is_valid())
# obj = serializer.save()
# self.assertEqual(serializer.data, data)
# self.assertEqual(obj.name, 'target-4')
# # Ensure (target 4, target_source 4, source 4) are added, and
# # everything else is as expected.
# queryset = OneToOneTarget.objects.all()
# serializer = self.Serializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'target-1', 'source': {'id': 1, 'name': 'source-1'}},
# {'id': 2, 'name': 'target-2', 'source': {'id': 2, 'name': 'source-2'}},
# {'id': 3, 'name': 'target-3', 'source': {'id': 3, 'name': 'source-3'}},
# {'id': 4, 'name': 'target-4', 'source': {'id': 4, 'name': 'source-4'}}
# ]
# self.assertEqual(serializer.data, expected)
# def test_one_to_one_create_with_invalid_data(self):
# data = {'id': 4, 'name': 'target-4', 'source': {'id': 4}}
# serializer = self.Serializer(data=data)
# self.assertFalse(serializer.is_valid())
# self.assertEqual(serializer.errors, {'source': [{'name': ['This field is required.']}]})
# def test_one_to_one_update(self):
# data = {'id': 3, 'name': 'target-3-updated', 'source': {'id': 3, 'name': 'source-3-updated'}}
# instance = OneToOneTarget.objects.get(pk=3)
# serializer = self.Serializer(instance, data=data)
# self.assertTrue(serializer.is_valid())
# obj = serializer.save()
# self.assertEqual(serializer.data, data)
# self.assertEqual(obj.name, 'target-3-updated')
# # Ensure (target 3, target_source 3, source 3) are updated,
# # and everything else is as expected.
# queryset = OneToOneTarget.objects.all()
# serializer = self.Serializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'target-1', 'source': {'id': 1, 'name': 'source-1'}},
# {'id': 2, 'name': 'target-2', 'source': {'id': 2, 'name': 'source-2'}},
# {'id': 3, 'name': 'target-3-updated', 'source': {'id': 3, 'name': 'source-3-updated'}}
# ]
# self.assertEqual(serializer.data, expected)
# class ForwardNestedOneToOneTests(TestCase):
# def setUp(self):
# class OneToOneTargetSerializer(serializers.ModelSerializer):
# class Meta:
# model = OneToOneTarget
# fields = ('id', 'name')
# class OneToOneSourceSerializer(serializers.ModelSerializer):
# target = OneToOneTargetSerializer()
# class Meta:
# model = OneToOneSource
# fields = ('id', 'name', 'target')
# self.Serializer = OneToOneSourceSerializer
# for idx in range(1, 4):
# target = OneToOneTarget(name='target-%d' % idx)
# target.save()
# source = OneToOneSource(name='source-%d' % idx, target=target)
# source.save()
# def test_one_to_one_retrieve(self):
# queryset = OneToOneSource.objects.all()
# serializer = self.Serializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'source-1', 'target': {'id': 1, 'name': 'target-1'}},
# {'id': 2, 'name': 'source-2', 'target': {'id': 2, 'name': 'target-2'}},
# {'id': 3, 'name': 'source-3', 'target': {'id': 3, 'name': 'target-3'}}
# ]
# self.assertEqual(serializer.data, expected)
# def test_one_to_one_create(self):
# data = {'id': 4, 'name': 'source-4', 'target': {'id': 4, 'name': 'target-4'}}
# serializer = self.Serializer(data=data)
# self.assertTrue(serializer.is_valid())
# obj = serializer.save()
# self.assertEqual(serializer.data, data)
# self.assertEqual(obj.name, 'source-4')
# # Ensure (target 4, target_source 4, source 4) are added, and
# # everything else is as expected.
# queryset = OneToOneSource.objects.all()
# serializer = self.Serializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'source-1', 'target': {'id': 1, 'name': 'target-1'}},
# {'id': 2, 'name': 'source-2', 'target': {'id': 2, 'name': 'target-2'}},
# {'id': 3, 'name': 'source-3', 'target': {'id': 3, 'name': 'target-3'}},
# {'id': 4, 'name': 'source-4', 'target': {'id': 4, 'name': 'target-4'}}
# ]
# self.assertEqual(serializer.data, expected)
# def test_one_to_one_create_with_invalid_data(self):
# data = {'id': 4, 'name': 'source-4', 'target': {'id': 4}}
# serializer = self.Serializer(data=data)
# self.assertFalse(serializer.is_valid())
# self.assertEqual(serializer.errors, {'target': [{'name': ['This field is required.']}]})
# def test_one_to_one_update(self):
# data = {'id': 3, 'name': 'source-3-updated', 'target': {'id': 3, 'name': 'target-3-updated'}}
# instance = OneToOneSource.objects.get(pk=3)
# serializer = self.Serializer(instance, data=data)
# self.assertTrue(serializer.is_valid())
# obj = serializer.save()
# self.assertEqual(serializer.data, data)
# self.assertEqual(obj.name, 'source-3-updated')
# # Ensure (target 3, target_source 3, source 3) are updated,
# # and everything else is as expected.
# queryset = OneToOneSource.objects.all()
# serializer = self.Serializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'source-1', 'target': {'id': 1, 'name': 'target-1'}},
# {'id': 2, 'name': 'source-2', 'target': {'id': 2, 'name': 'target-2'}},
# {'id': 3, 'name': 'source-3-updated', 'target': {'id': 3, 'name': 'target-3-updated'}}
# ]
# self.assertEqual(serializer.data, expected)
# def test_one_to_one_update_to_null(self):
# data = {'id': 3, 'name': 'source-3-updated', 'target': None}
# instance = OneToOneSource.objects.get(pk=3)
# serializer = self.Serializer(instance, data=data)
# self.assertTrue(serializer.is_valid())
# obj = serializer.save()
# self.assertEqual(serializer.data, data)
# self.assertEqual(obj.name, 'source-3-updated')
# self.assertEqual(obj.target, None)
# queryset = OneToOneSource.objects.all()
# serializer = self.Serializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'source-1', 'target': {'id': 1, 'name': 'target-1'}},
# {'id': 2, 'name': 'source-2', 'target': {'id': 2, 'name': 'target-2'}},
# {'id': 3, 'name': 'source-3-updated', 'target': None}
# ]
# self.assertEqual(serializer.data, expected)
# # TODO: Nullable 1-1 tests
# # def test_one_to_one_delete(self):
# # data = {'id': 3, 'name': 'target-3', 'target_source': None}
# # instance = OneToOneTarget.objects.get(pk=3)
# # serializer = self.Serializer(instance, data=data)
# # self.assertTrue(serializer.is_valid())
# # serializer.save()
# # # Ensure (target_source 3, source 3) are deleted,
# # # and everything else is as expected.
# # queryset = OneToOneTarget.objects.all()
# # serializer = self.Serializer(queryset)
# # expected = [
# # {'id': 1, 'name': 'target-1', 'source': {'id': 1, 'name': 'source-1'}},
# # {'id': 2, 'name': 'target-2', 'source': {'id': 2, 'name': 'source-2'}},
# # {'id': 3, 'name': 'target-3', 'source': None}
# # ]
# # self.assertEqual(serializer.data, expected)
# class ReverseNestedOneToManyTests(TestCase):
# def setUp(self):
# class OneToManySourceSerializer(serializers.ModelSerializer):
# class Meta:
# model = OneToManySource
# fields = ('id', 'name')
# class OneToManyTargetSerializer(serializers.ModelSerializer):
# sources = OneToManySourceSerializer(many=True, allow_add_remove=True)
# class Meta:
# model = OneToManyTarget
# fields = ('id', 'name', 'sources')
# self.Serializer = OneToManyTargetSerializer
# target = OneToManyTarget(name='target-1')
# target.save()
# for idx in range(1, 4):
# source = OneToManySource(name='source-%d' % idx, target=target)
# source.save()
# def test_one_to_many_retrieve(self):
# queryset = OneToManyTarget.objects.all()
# serializer = self.Serializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'target-1', 'sources': [{'id': 1, 'name': 'source-1'},
# {'id': 2, 'name': 'source-2'},
# {'id': 3, 'name': 'source-3'}]},
# ]
# self.assertEqual(serializer.data, expected)
# def test_one_to_many_create(self):
# data = {'id': 1, 'name': 'target-1', 'sources': [{'id': 1, 'name': 'source-1'},
# {'id': 2, 'name': 'source-2'},
# {'id': 3, 'name': 'source-3'},
# {'id': 4, 'name': 'source-4'}]}
# instance = OneToManyTarget.objects.get(pk=1)
# serializer = self.Serializer(instance, data=data)
# self.assertTrue(serializer.is_valid())
# obj = serializer.save()
# self.assertEqual(serializer.data, data)
# self.assertEqual(obj.name, 'target-1')
# # Ensure source 4 is added, and everything else is as
# # expected.
# queryset = OneToManyTarget.objects.all()
# serializer = self.Serializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'target-1', 'sources': [{'id': 1, 'name': 'source-1'},
# {'id': 2, 'name': 'source-2'},
# {'id': 3, 'name': 'source-3'},
# {'id': 4, 'name': 'source-4'}]}
# ]
# self.assertEqual(serializer.data, expected)
# def test_one_to_many_create_with_invalid_data(self):
# data = {'id': 1, 'name': 'target-1', 'sources': [{'id': 1, 'name': 'source-1'},
# {'id': 2, 'name': 'source-2'},
# {'id': 3, 'name': 'source-3'},
# {'id': 4}]}
# serializer = self.Serializer(data=data)
# self.assertFalse(serializer.is_valid())
# self.assertEqual(serializer.errors, {'sources': [{}, {}, {}, {'name': ['This field is required.']}]})
# def test_one_to_many_update(self):
# data = {'id': 1, 'name': 'target-1-updated', 'sources': [{'id': 1, 'name': 'source-1-updated'},
# {'id': 2, 'name': 'source-2'},
# {'id': 3, 'name': 'source-3'}]}
# instance = OneToManyTarget.objects.get(pk=1)
# serializer = self.Serializer(instance, data=data)
# self.assertTrue(serializer.is_valid())
# obj = serializer.save()
# self.assertEqual(serializer.data, data)
# self.assertEqual(obj.name, 'target-1-updated')
# # Ensure (target 1, source 1) are updated,
# # and everything else is as expected.
# queryset = OneToManyTarget.objects.all()
# serializer = self.Serializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'target-1-updated', 'sources': [{'id': 1, 'name': 'source-1-updated'},
# {'id': 2, 'name': 'source-2'},
# {'id': 3, 'name': 'source-3'}]}
# ]
# self.assertEqual(serializer.data, expected)
# def test_one_to_many_delete(self):
# data = {'id': 1, 'name': 'target-1', 'sources': [{'id': 1, 'name': 'source-1'},
# {'id': 3, 'name': 'source-3'}]}
# instance = OneToManyTarget.objects.get(pk=1)
# serializer = self.Serializer(instance, data=data)
# self.assertTrue(serializer.is_valid())
# serializer.save()
# # Ensure source 2 is deleted, and everything else is as
# # expected.
# queryset = OneToManyTarget.objects.all()
# serializer = self.Serializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'target-1', 'sources': [{'id': 1, 'name': 'source-1'},
# {'id': 3, 'name': 'source-3'}]}
# ]
# self.assertEqual(serializer.data, expected)

View File

@ -398,27 +398,6 @@ class PKNullableForeignKeyTests(TestCase):
] ]
self.assertEqual(serializer.data, expected) self.assertEqual(serializer.data, expected)
# reverse foreign keys MUST be read_only
# In the general case they do not provide .remove() or .clear()
# and cannot be arbitrarily set.
# def test_reverse_foreign_key_update(self):
# data = {'id': 1, 'name': 'target-1', 'sources': [1]}
# instance = ForeignKeyTarget.objects.get(pk=1)
# serializer = ForeignKeyTargetSerializer(instance, data=data)
# self.assertTrue(serializer.is_valid())
# self.assertEqual(serializer.data, data)
# serializer.save()
# # Ensure target 1 is updated, and everything else is as expected
# queryset = ForeignKeyTarget.objects.all()
# serializer = ForeignKeyTargetSerializer(queryset, many=True)
# expected = [
# {'id': 1, 'name': 'target-1', 'sources': [1]},
# {'id': 2, 'name': 'target-2', 'sources': []},
# ]
# self.assertEqual(serializer.data, expected)
class PKNullableOneToOneTests(TestCase): class PKNullableOneToOneTests(TestCase):
def setUp(self): def setUp(self):
@ -437,113 +416,3 @@ class PKNullableOneToOneTests(TestCase):
{'id': 2, 'name': 'target-2', 'nullable_source': 1}, {'id': 2, 'name': 'target-2', 'nullable_source': 1},
] ]
self.assertEqual(serializer.data, expected) self.assertEqual(serializer.data, expected)
# The below models and tests ensure that serializer fields corresponding
# to a ManyToManyField field with a user-specified ``through`` model are
# set to read only
# class ManyToManyThroughTarget(models.Model):
# name = models.CharField(max_length=100)
# class ManyToManyThrough(models.Model):
# source = models.ForeignKey('ManyToManyThroughSource')
# target = models.ForeignKey(ManyToManyThroughTarget)
# class ManyToManyThroughSource(models.Model):
# name = models.CharField(max_length=100)
# targets = models.ManyToManyField(ManyToManyThroughTarget,
# related_name='sources',
# through='ManyToManyThrough')
# class ManyToManyThroughTargetSerializer(serializers.ModelSerializer):
# class Meta:
# model = ManyToManyThroughTarget
# fields = ('id', 'name', 'sources')
# class ManyToManyThroughSourceSerializer(serializers.ModelSerializer):
# class Meta:
# model = ManyToManyThroughSource
# fields = ('id', 'name', 'targets')
# class PKManyToManyThroughTests(TestCase):
# def setUp(self):
# self.source = ManyToManyThroughSource.objects.create(
# name='through-source-1')
# self.target = ManyToManyThroughTarget.objects.create(
# name='through-target-1')
# def test_many_to_many_create(self):
# data = {'id': 2, 'name': 'source-2', 'targets': [self.target.pk]}
# serializer = ManyToManyThroughSourceSerializer(data=data)
# self.assertTrue(serializer.is_valid())
# obj = serializer.save()
# self.assertEqual(obj.name, 'source-2')
# self.assertEqual(obj.targets.count(), 0)
# def test_many_to_many_reverse_create(self):
# data = {'id': 2, 'name': 'target-2', 'sources': [self.source.pk]}
# serializer = ManyToManyThroughTargetSerializer(data=data)
# self.assertTrue(serializer.is_valid())
# obj = serializer.save()
# self.assertEqual(obj.name, 'target-2')
# self.assertEqual(obj.sources.count(), 0)
# # Regression tests for #694 (`source` attribute on related fields)
# class PrimaryKeyRelatedFieldSourceTests(TestCase):
# def test_related_manager_source(self):
# """
# Relational fields should be able to use manager-returning methods as their source.
# """
# BlogPost.objects.create(title='blah')
# field = serializers.PrimaryKeyRelatedField(many=True, source='get_blogposts_manager')
# class ClassWithManagerMethod(object):
# def get_blogposts_manager(self):
# return BlogPost.objects
# obj = ClassWithManagerMethod()
# value = field.field_to_native(obj, 'field_name')
# self.assertEqual(value, [1])
# def test_related_queryset_source(self):
# """
# Relational fields should be able to use queryset-returning methods as their source.
# """
# BlogPost.objects.create(title='blah')
# field = serializers.PrimaryKeyRelatedField(many=True, source='get_blogposts_queryset')
# class ClassWithQuerysetMethod(object):
# def get_blogposts_queryset(self):
# return BlogPost.objects.all()
# obj = ClassWithQuerysetMethod()
# value = field.field_to_native(obj, 'field_name')
# self.assertEqual(value, [1])
# def test_dotted_source(self):
# """
# Source argument should support dotted.source notation.
# """
# BlogPost.objects.create(title='blah')
# field = serializers.PrimaryKeyRelatedField(many=True, source='a.b.c')
# class ClassWithQuerysetMethod(object):
# a = {
# 'b': {
# 'c': BlogPost.objects.all()
# }
# }
# obj = ClassWithQuerysetMethod()
# value = field.field_to_native(obj, 'field_name')
# self.assertEqual(value, [1])

View File

@ -9,7 +9,7 @@ from django.test import TestCase
from django.utils import six, unittest from django.utils import six, unittest
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import status, permissions from rest_framework import status, permissions
from rest_framework.compat import yaml, etree, StringIO from rest_framework.compat import yaml, etree, StringIO, BytesIO
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
@ -467,7 +467,7 @@ if yaml:
obj = {'foo': ['bar', 'baz']} obj = {'foo': ['bar', 'baz']}
renderer = YAMLRenderer() renderer = YAMLRenderer()
content = renderer.render(obj, 'application/yaml') content = renderer.render(obj, 'application/yaml')
self.assertEqual(content, _yaml_repr) self.assertEqual(content.decode('utf-8'), _yaml_repr)
def test_render_and_parse(self): def test_render_and_parse(self):
""" """
@ -480,7 +480,7 @@ if yaml:
parser = YAMLParser() parser = YAMLParser()
content = renderer.render(obj, 'application/yaml') content = renderer.render(obj, 'application/yaml')
data = parser.parse(StringIO(content)) data = parser.parse(BytesIO(content))
self.assertEqual(obj, data) self.assertEqual(obj, data)
def test_render_decimal(self): def test_render_decimal(self):
@ -489,7 +489,7 @@ if yaml:
""" """
renderer = YAMLRenderer() renderer = YAMLRenderer()
content = renderer.render({'field': Decimal('111.2')}, 'application/yaml') content = renderer.render({'field': Decimal('111.2')}, 'application/yaml')
self.assertYAMLContains(content, "field: '111.2'") self.assertYAMLContains(content.decode('utf-8'), "field: '111.2'")
def assertYAMLContains(self, content, string): def assertYAMLContains(self, content, string):
self.assertTrue(string in content, '%r not in %r' % (string, content)) self.assertTrue(string in content, '%r not in %r' % (string, content))
@ -646,6 +646,7 @@ class CacheRenderTest(TestCase):
""" """
method = getattr(self.client, http_method) method = getattr(self.client, http_method)
resp = method(url) resp = method(url)
resp._closable_objects = []
del resp.client, resp.request del resp.client, resp.request
try: try:
del resp.wsgi_request del resp.wsgi_request

View File

@ -179,89 +179,6 @@ class TestContentParsing(TestCase):
self.assertEqual(request._data, Empty) self.assertEqual(request._data, Empty)
self.assertEqual(request._files, Empty) self.assertEqual(request._files, Empty)
# def test_accessing_post_after_data_form(self):
# """
# Ensures request.POST can be accessed after request.DATA in
# form request.
# """
# data = {'qwerty': 'uiop'}
# request = factory.post('/', data=data)
# self.assertEqual(request.DATA.items(), data.items())
# self.assertEqual(request.POST.items(), data.items())
# def test_accessing_post_after_data_for_json(self):
# """
# Ensures request.POST can be accessed after request.DATA in
# json request.
# """
# data = {'qwerty': 'uiop'}
# content = json.dumps(data)
# content_type = 'application/json'
# parsers = (JSONParser, )
# request = factory.post('/', content, content_type=content_type,
# parsers=parsers)
# self.assertEqual(request.DATA.items(), data.items())
# self.assertEqual(request.POST.items(), [])
# def test_accessing_post_after_data_for_overloaded_json(self):
# """
# Ensures request.POST can be accessed after request.DATA in overloaded
# json request.
# """
# data = {'qwerty': 'uiop'}
# content = json.dumps(data)
# content_type = 'application/json'
# parsers = (JSONParser, )
# form_data = {Request._CONTENT_PARAM: content,
# Request._CONTENTTYPE_PARAM: content_type}
# request = factory.post('/', form_data, parsers=parsers)
# self.assertEqual(request.DATA.items(), data.items())
# self.assertEqual(request.POST.items(), form_data.items())
# def test_accessing_data_after_post_form(self):
# """
# Ensures request.DATA can be accessed after request.POST in
# form request.
# """
# data = {'qwerty': 'uiop'}
# parsers = (FormParser, MultiPartParser)
# request = factory.post('/', data, parsers=parsers)
# self.assertEqual(request.POST.items(), data.items())
# self.assertEqual(request.DATA.items(), data.items())
# def test_accessing_data_after_post_for_json(self):
# """
# Ensures request.DATA can be accessed after request.POST in
# json request.
# """
# data = {'qwerty': 'uiop'}
# content = json.dumps(data)
# content_type = 'application/json'
# parsers = (JSONParser, )
# request = factory.post('/', content, content_type=content_type,
# parsers=parsers)
# self.assertEqual(request.POST.items(), [])
# self.assertEqual(request.DATA.items(), data.items())
# def test_accessing_data_after_post_for_overloaded_json(self):
# """
# Ensures request.DATA can be accessed after request.POST in overloaded
# json request
# """
# data = {'qwerty': 'uiop'}
# content = json.dumps(data)
# content_type = 'application/json'
# parsers = (JSONParser, )
# form_data = {Request._CONTENT_PARAM: content,
# Request._CONTENTTYPE_PARAM: content_type}
# request = factory.post('/', form_data, parsers=parsers)
# self.assertEqual(request.POST.items(), form_data.items())
# self.assertEqual(request.DATA.items(), data.items())
class MockView(APIView): class MockView(APIView):
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
@ -301,18 +218,6 @@ class TestContentParsingWithAuthentication(TestCase):
response = self.csrf_client.post('/', content) response = self.csrf_client.post('/', content)
self.assertEqual(status.HTTP_200_OK, response.status_code) self.assertEqual(status.HTTP_200_OK, response.status_code)
# def test_user_logged_in_authentication_has_post_when_logged_in(self):
# """Ensures request.POST exists after UserLoggedInAuthentication when user does log in"""
# self.client.login(username='john', password='password')
# self.csrf_client.login(username='john', password='password')
# content = {'example': 'example'}
# response = self.client.post('/', content)
# self.assertEqual(status.OK, response.status_code, "POST data is malformed")
# response = self.csrf_client.post('/', content)
# self.assertEqual(status.OK, response.status_code, "POST data is malformed")
class TestUserSetter(TestCase): class TestUserSetter(TestCase):

File diff suppressed because it is too large Load Diff

View File

@ -121,161 +121,3 @@ class BulkCreateSerializerTests(TestCase):
expected_errors = {'non_field_errors': ['Expected a list of items but got type `dict`.']} expected_errors = {'non_field_errors': ['Expected a list of items but got type `dict`.']}
self.assertEqual(serializer.errors, expected_errors) self.assertEqual(serializer.errors, expected_errors)
# class BulkUpdateSerializerTests(TestCase):
# """
# Updating multiple instances using serializers.
# """
# def setUp(self):
# class Book(object):
# """
# A data type that can be persisted to a mock storage backend
# with `.save()` and `.delete()`.
# """
# object_map = {}
# def __init__(self, id, title, author):
# self.id = id
# self.title = title
# self.author = author
# def save(self):
# Book.object_map[self.id] = self
# def delete(self):
# del Book.object_map[self.id]
# class BookSerializer(serializers.Serializer):
# id = serializers.IntegerField()
# title = serializers.CharField(max_length=100)
# author = serializers.CharField(max_length=100)
# def restore_object(self, attrs, instance=None):
# if instance:
# instance.id = attrs['id']
# instance.title = attrs['title']
# instance.author = attrs['author']
# return instance
# return Book(**attrs)
# self.Book = Book
# self.BookSerializer = BookSerializer
# 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'
# }
# ]
# for item in data:
# book = Book(item['id'], item['title'], item['author'])
# book.save()
# def books(self):
# """
# Return all the objects in the mock storage backend.
# """
# return self.Book.object_map.values()
# def test_bulk_update_success(self):
# """
# Correct bulk update serialization should return the input data.
# """
# data = [
# {
# 'id': 0,
# 'title': 'The electric kool-aid acid test',
# 'author': 'Tom Wolfe'
# }, {
# 'id': 2,
# 'title': 'Kafka on the shore',
# 'author': 'Haruki Murakami'
# }
# ]
# serializer = self.BookSerializer(self.books(), data=data, many=True, allow_add_remove=True)
# self.assertEqual(serializer.is_valid(), True)
# self.assertEqual(serializer.data, data)
# serializer.save()
# new_data = self.BookSerializer(self.books(), many=True).data
# self.assertEqual(data, new_data)
# def test_bulk_update_and_create(self):
# """
# Bulk update serialization may also include created items.
# """
# data = [
# {
# 'id': 0,
# 'title': 'The electric kool-aid acid test',
# 'author': 'Tom Wolfe'
# }, {
# 'id': 3,
# 'title': 'Kafka on the shore',
# 'author': 'Haruki Murakami'
# }
# ]
# serializer = self.BookSerializer(self.books(), data=data, many=True, allow_add_remove=True)
# self.assertEqual(serializer.is_valid(), True)
# self.assertEqual(serializer.data, data)
# serializer.save()
# new_data = self.BookSerializer(self.books(), many=True).data
# self.assertEqual(data, new_data)
# def test_bulk_update_invalid_create(self):
# """
# Bulk update serialization without allow_add_remove may not create items.
# """
# data = [
# {
# 'id': 0,
# 'title': 'The electric kool-aid acid test',
# 'author': 'Tom Wolfe'
# }, {
# 'id': 3,
# 'title': 'Kafka on the shore',
# 'author': 'Haruki Murakami'
# }
# ]
# expected_errors = [
# {},
# {'non_field_errors': ['Cannot create a new item, only existing items may be updated.']}
# ]
# serializer = self.BookSerializer(self.books(), data=data, many=True)
# self.assertEqual(serializer.is_valid(), False)
# self.assertEqual(serializer.errors, expected_errors)
# def test_bulk_update_error(self):
# """
# Incorrect bulk update serialization should return error data.
# """
# data = [
# {
# 'id': 0,
# 'title': 'The electric kool-aid acid test',
# 'author': 'Tom Wolfe'
# }, {
# 'id': 'foo',
# 'title': 'Kafka on the shore',
# 'author': 'Haruki Murakami'
# }
# ]
# expected_errors = [
# {},
# {'id': ['Enter a whole number.']}
# ]
# serializer = self.BookSerializer(self.books(), data=data, many=True, allow_add_remove=True)
# self.assertEqual(serializer.is_valid(), False)
# self.assertEqual(serializer.errors, expected_errors)

View File

@ -1,15 +0,0 @@
# from django.test import TestCase
# from rest_framework import serializers
# class EmptySerializerTestCase(TestCase):
# def test_empty_serializer(self):
# class FooBarSerializer(serializers.Serializer):
# foo = serializers.IntegerField()
# bar = serializers.SerializerMethodField()
# def get_bar(self, obj):
# return 'bar'
# serializer = FooBarSerializer()
# self.assertEquals(serializer.data, {'foo': 0})

View File

@ -38,353 +38,3 @@ class TestNestedSerializer:
} }
serializer = self.Serializer() serializer = self.Serializer()
assert serializer.data == expected_data assert serializer.data == expected_data
# """
# Tests to cover nested serializers.
# Doesn't cover model serializers.
# """
# from __future__ import unicode_literals
# from django.test import TestCase
# from rest_framework import serializers
# from . import models
# class WritableNestedSerializerBasicTests(TestCase):
# """
# Tests for deserializing nested entities.
# Basic tests that use serializers that simply restore to dicts.
# """
# def setUp(self):
# class TrackSerializer(serializers.Serializer):
# order = serializers.IntegerField()
# title = serializers.CharField(max_length=100)
# duration = serializers.IntegerField()
# class AlbumSerializer(serializers.Serializer):
# album_name = serializers.CharField(max_length=100)
# artist = serializers.CharField(max_length=100)
# tracks = TrackSerializer(many=True)
# self.AlbumSerializer = AlbumSerializer
# def test_nested_validation_success(self):
# """
# Correct nested serialization should return the input data.
# """
# data = {
# 'album_name': 'Discovery',
# 'artist': 'Daft Punk',
# 'tracks': [
# {'order': 1, 'title': 'One More Time', 'duration': 235},
# {'order': 2, 'title': 'Aerodynamic', 'duration': 184},
# {'order': 3, 'title': 'Digital Love', 'duration': 239}
# ]
# }
# serializer = self.AlbumSerializer(data=data)
# self.assertEqual(serializer.is_valid(), True)
# self.assertEqual(serializer.object, data)
# def test_nested_validation_error(self):
# """
# Incorrect nested serialization should return appropriate error data.
# """
# data = {
# 'album_name': 'Discovery',
# 'artist': 'Daft Punk',
# 'tracks': [
# {'order': 1, 'title': 'One More Time', 'duration': 235},
# {'order': 2, 'title': 'Aerodynamic', 'duration': 184},
# {'order': 3, 'title': 'Digital Love', 'duration': 'foobar'}
# ]
# }
# expected_errors = {
# 'tracks': [
# {},
# {},
# {'duration': ['Enter a whole number.']}
# ]
# }
# serializer = self.AlbumSerializer(data=data)
# self.assertEqual(serializer.is_valid(), False)
# self.assertEqual(serializer.errors, expected_errors)
# def test_many_nested_validation_error(self):
# """
# Incorrect nested serialization should return appropriate error data
# when multiple entities are being deserialized.
# """
# data = [
# {
# 'album_name': 'Russian Red',
# 'artist': 'I Love Your Glasses',
# 'tracks': [
# {'order': 1, 'title': 'Cigarettes', 'duration': 121},
# {'order': 2, 'title': 'No Past Land', 'duration': 198},
# {'order': 3, 'title': 'They Don\'t Believe', 'duration': 191}
# ]
# },
# {
# 'album_name': 'Discovery',
# 'artist': 'Daft Punk',
# 'tracks': [
# {'order': 1, 'title': 'One More Time', 'duration': 235},
# {'order': 2, 'title': 'Aerodynamic', 'duration': 184},
# {'order': 3, 'title': 'Digital Love', 'duration': 'foobar'}
# ]
# }
# ]
# expected_errors = [
# {},
# {
# 'tracks': [
# {},
# {},
# {'duration': ['Enter a whole number.']}
# ]
# }
# ]
# serializer = self.AlbumSerializer(data=data, many=True)
# self.assertEqual(serializer.is_valid(), False)
# self.assertEqual(serializer.errors, expected_errors)
# class WritableNestedSerializerObjectTests(TestCase):
# """
# Tests for deserializing nested entities.
# These tests use serializers that restore to concrete objects.
# """
# def setUp(self):
# # Couple of concrete objects that we're going to deserialize into
# class Track(object):
# def __init__(self, order, title, duration):
# self.order, self.title, self.duration = order, title, duration
# def __eq__(self, other):
# return (
# self.order == other.order and
# self.title == other.title and
# self.duration == other.duration
# )
# class Album(object):
# def __init__(self, album_name, artist, tracks):
# self.album_name, self.artist, self.tracks = album_name, artist, tracks
# def __eq__(self, other):
# return (
# self.album_name == other.album_name and
# self.artist == other.artist and
# self.tracks == other.tracks
# )
# # And their corresponding serializers
# class TrackSerializer(serializers.Serializer):
# order = serializers.IntegerField()
# title = serializers.CharField(max_length=100)
# duration = serializers.IntegerField()
# def restore_object(self, attrs, instance=None):
# return Track(attrs['order'], attrs['title'], attrs['duration'])
# class AlbumSerializer(serializers.Serializer):
# album_name = serializers.CharField(max_length=100)
# artist = serializers.CharField(max_length=100)
# tracks = TrackSerializer(many=True)
# def restore_object(self, attrs, instance=None):
# return Album(attrs['album_name'], attrs['artist'], attrs['tracks'])
# self.Album, self.Track = Album, Track
# self.AlbumSerializer = AlbumSerializer
# def test_nested_validation_success(self):
# """
# Correct nested serialization should return a restored object
# that corresponds to the input data.
# """
# data = {
# 'album_name': 'Discovery',
# 'artist': 'Daft Punk',
# 'tracks': [
# {'order': 1, 'title': 'One More Time', 'duration': 235},
# {'order': 2, 'title': 'Aerodynamic', 'duration': 184},
# {'order': 3, 'title': 'Digital Love', 'duration': 239}
# ]
# }
# expected_object = self.Album(
# album_name='Discovery',
# artist='Daft Punk',
# tracks=[
# self.Track(order=1, title='One More Time', duration=235),
# self.Track(order=2, title='Aerodynamic', duration=184),
# self.Track(order=3, title='Digital Love', duration=239),
# ]
# )
# serializer = self.AlbumSerializer(data=data)
# self.assertEqual(serializer.is_valid(), True)
# self.assertEqual(serializer.object, expected_object)
# def test_many_nested_validation_success(self):
# """
# Correct nested serialization should return multiple restored objects
# that corresponds to the input data when multiple objects are
# being deserialized.
# """
# data = [
# {
# 'album_name': 'Russian Red',
# 'artist': 'I Love Your Glasses',
# 'tracks': [
# {'order': 1, 'title': 'Cigarettes', 'duration': 121},
# {'order': 2, 'title': 'No Past Land', 'duration': 198},
# {'order': 3, 'title': 'They Don\'t Believe', 'duration': 191}
# ]
# },
# {
# 'album_name': 'Discovery',
# 'artist': 'Daft Punk',
# 'tracks': [
# {'order': 1, 'title': 'One More Time', 'duration': 235},
# {'order': 2, 'title': 'Aerodynamic', 'duration': 184},
# {'order': 3, 'title': 'Digital Love', 'duration': 239}
# ]
# }
# ]
# expected_object = [
# self.Album(
# album_name='Russian Red',
# artist='I Love Your Glasses',
# tracks=[
# self.Track(order=1, title='Cigarettes', duration=121),
# self.Track(order=2, title='No Past Land', duration=198),
# self.Track(order=3, title='They Don\'t Believe', duration=191),
# ]
# ),
# self.Album(
# album_name='Discovery',
# artist='Daft Punk',
# tracks=[
# self.Track(order=1, title='One More Time', duration=235),
# self.Track(order=2, title='Aerodynamic', duration=184),
# self.Track(order=3, title='Digital Love', duration=239),
# ]
# )
# ]
# serializer = self.AlbumSerializer(data=data, many=True)
# self.assertEqual(serializer.is_valid(), True)
# self.assertEqual(serializer.object, expected_object)
# class ForeignKeyNestedSerializerUpdateTests(TestCase):
# def setUp(self):
# class Artist(object):
# def __init__(self, name):
# self.name = name
# def __eq__(self, other):
# return self.name == other.name
# class Album(object):
# def __init__(self, name, artist):
# self.name, self.artist = name, artist
# def __eq__(self, other):
# return self.name == other.name and self.artist == other.artist
# class ArtistSerializer(serializers.Serializer):
# name = serializers.CharField()
# def restore_object(self, attrs, instance=None):
# if instance:
# instance.name = attrs['name']
# else:
# instance = Artist(attrs['name'])
# return instance
# class AlbumSerializer(serializers.Serializer):
# name = serializers.CharField()
# by = ArtistSerializer(source='artist')
# def restore_object(self, attrs, instance=None):
# if instance:
# instance.name = attrs['name']
# instance.artist = attrs['artist']
# else:
# instance = Album(attrs['name'], attrs['artist'])
# return instance
# self.Artist = Artist
# self.Album = Album
# self.AlbumSerializer = AlbumSerializer
# def test_create_via_foreign_key_with_source(self):
# """
# Check that we can both *create* and *update* into objects across
# ForeignKeys that have a `source` specified.
# Regression test for #1170
# """
# data = {
# 'name': 'Discovery',
# 'by': {'name': 'Daft Punk'},
# }
# expected = self.Album(artist=self.Artist('Daft Punk'), name='Discovery')
# # create
# serializer = self.AlbumSerializer(data=data)
# self.assertEqual(serializer.is_valid(), True)
# self.assertEqual(serializer.object, expected)
# # update
# original = self.Album(artist=self.Artist('The Bats'), name='Free All the Monsters')
# serializer = self.AlbumSerializer(instance=original, data=data)
# self.assertEqual(serializer.is_valid(), True)
# self.assertEqual(serializer.object, expected)
# class NestedModelSerializerUpdateTests(TestCase):
# def test_second_nested_level(self):
# john = models.Person.objects.create(name="john")
# post = john.blogpost_set.create(title="Test blog post")
# post.blogpostcomment_set.create(text="I hate this blog post")
# post.blogpostcomment_set.create(text="I love this blog post")
# class BlogPostCommentSerializer(serializers.ModelSerializer):
# class Meta:
# model = models.BlogPostComment
# class BlogPostSerializer(serializers.ModelSerializer):
# comments = BlogPostCommentSerializer(many=True, source='blogpostcomment_set')
# class Meta:
# model = models.BlogPost
# fields = ('id', 'title', 'comments')
# class PersonSerializer(serializers.ModelSerializer):
# posts = BlogPostSerializer(many=True, source='blogpost_set')
# class Meta:
# model = models.Person
# fields = ('id', 'name', 'age', 'posts')
# serialize = PersonSerializer(instance=john)
# deserialize = PersonSerializer(data=serialize.data, instance=john)
# self.assertTrue(deserialize.is_valid())
# result = deserialize.object
# result.save()
# self.assertEqual(result.id, john.id)

View File

@ -1,4 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.exceptions import ImproperlyConfigured
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from django.test import TestCase from django.test import TestCase
from django.utils import six from django.utils import six
@ -7,6 +8,8 @@ from rest_framework.utils.breadcrumbs import get_breadcrumbs
from rest_framework.views import APIView from rest_framework.views import APIView
from tests.models import BasicModel from tests.models import BasicModel
import rest_framework.utils.model_meta
class Root(APIView): class Root(APIView):
pass pass
@ -130,3 +133,34 @@ class ResolveModelTests(TestCase):
def test_resolve_improper_string_representation(self): def test_resolve_improper_string_representation(self):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
_resolve_model('BasicModel') _resolve_model('BasicModel')
class ResolveModelWithPatchedDjangoTests(TestCase):
"""
Test coverage for when Django's `get_model` returns `None`.
Under certain circumstances Django may return `None` with `get_model`:
http://git.io/get-model-source
It usually happens with circular imports so it is important that DRF
excepts early, otherwise fault happens downstream and is much more
difficult to debug.
"""
def setUp(self):
"""Monkeypatch get_model."""
self.get_model = rest_framework.utils.model_meta.models.get_model
def get_model(app_label, model_name):
return None
rest_framework.utils.model_meta.models.get_model = get_model
def tearDown(self):
"""Revert monkeypatching."""
rest_framework.utils.model_meta.models.get_model = self.get_model
def test_blows_up_if_model_does_not_resolve(self):
with self.assertRaises(ImproperlyConfigured):
_resolve_model('tests.BasicModel')

View File

@ -165,17 +165,6 @@ class TestChoiceFieldChoicesValidate(TestCase):
except serializers.ValidationError: except serializers.ValidationError:
self.fail("Value %s does not validate" % str(value)) self.fail("Value %s does not validate" % str(value))
# def test_nested_choices(self):
# """
# Make sure a nested value for choices works as expected.
# """
# f = serializers.ChoiceField(choices=self.CHOICES_NESTED)
# value = self.CHOICES_NESTED[0][1][0][0]
# try:
# f.to_native(value)
# except ValidationError:
# self.fail("Value %s does not validate" % str(value))
class RegexSerializer(serializers.Serializer): class RegexSerializer(serializers.Serializer):
pin = serializers.CharField( pin = serializers.CharField(

View File

@ -88,8 +88,8 @@ class TestUniquenessTogetherValidation(TestCase):
expected = dedent(""" expected = dedent("""
UniquenessTogetherSerializer(): UniquenessTogetherSerializer():
id = IntegerField(label='ID', read_only=True) id = IntegerField(label='ID', read_only=True)
race_name = CharField(max_length=100) race_name = CharField(max_length=100, required=True)
position = IntegerField() position = IntegerField(required=True)
class Meta: class Meta:
validators = [<UniqueTogetherValidator(queryset=UniquenessTogetherModel.objects.all(), fields=('race_name', 'position'))>] validators = [<UniqueTogetherValidator(queryset=UniquenessTogetherModel.objects.all(), fields=('race_name', 'position'))>]
""") """)

197
tox.ini
View File

@ -1,181 +1,38 @@
[tox] [tox]
downloadcache = {toxworkdir}/cache/
envlist = envlist =
flake8, py27-flake8,
py3.4-django1.7,py3.3-django1.7,py3.2-django1.7,py2.7-django1.7, {py26,py27}-django14,
py3.4-django1.6,py3.3-django1.6,py3.2-django1.6,py2.7-django1.6,py2.6-django1.6, {py26,py27,py32,py33,py34}-django{15,16},
py3.4-django1.5,py3.3-django1.5,py3.2-django1.5,py2.7-django1.5,py2.6-django1.5, {py27,py32,py33,py34}-django{17,master}
py2.7-django1.4,py2.6-django1.4,
py3.4-djangomaster,py3.3-djangomaster,py2.7-djangomaster
[testenv] [testenv]
commands = ./runtests.py --fast commands = ./runtests.py --fast
setenv = setenv =
PYTHONDONTWRITEBYTECODE=1 PYTHONDONTWRITEBYTECODE=1
deps =
django14: Django==1.4.16
django15: Django==1.5.11
django16: Django==1.6.8
django17: Django==1.7.1
djangomaster: https://github.com/django/django/zipball/master
{py26,py27}-django{14,15,16,17}: django-guardian==1.2.3
{py26,py27}-django{14,15,16}: oauth2==1.5.211
{py26,py27}-django{14,15,16}: django-oauth-plus==2.2.1
{py26,py27}-django{14,15}: django-oauth2-provider==0.2.3
{py26,py27}-django16: django-oauth2-provider==0.2.4
pytest-django==2.6.1
django-filter==0.7
defusedxml==0.3
markdown>=2.1.0
PyYAML>=3.10
[testenv:flake8] [testenv:py27-flake8]
basepython = python2.7 deps =
deps = pytest==2.5.2 pytest==2.5.2
flake8==2.2.2 flake8==2.2.2
commands = ./runtests.py --lintonly commands = ./runtests.py --lintonly
[testenv:py3.4-django1.7] [testenv:py27-docs]
basepython = python3.4 deps =
deps = Django==1.7 mkdocs>=0.11.1
django-filter==0.7 commands = mkdocs build
defusedxml==0.3
pytest-django==2.6.1
[testenv:py3.3-django1.7]
basepython = python3.3
deps = Django==1.7
django-filter==0.7
defusedxml==0.3
pytest-django==2.6.1
[testenv:py3.2-django1.7]
basepython = python3.2
deps = Django==1.7
django-filter==0.7
defusedxml==0.3
pytest-django==2.6.1
[testenv:py2.7-django1.7]
basepython = python2.7
deps = Django==1.7
django-filter==0.7
defusedxml==0.3
# django-oauth-plus==2.2.1
# oauth2==1.5.211
# django-oauth2-provider==0.2.4
django-guardian==1.2.3
pytest-django==2.6.1
[testenv:py3.4-django1.6]
basepython = python3.4
deps = Django==1.6.3
django-filter==0.7
defusedxml==0.3
pytest-django==2.6.1
[testenv:py3.3-django1.6]
basepython = python3.3
deps = Django==1.6.3
django-filter==0.7
defusedxml==0.3
pytest-django==2.6.1
[testenv:py3.2-django1.6]
basepython = python3.2
deps = Django==1.6.3
django-filter==0.7
defusedxml==0.3
pytest-django==2.6.1
[testenv:py2.7-django1.6]
basepython = python2.7
deps = Django==1.6.3
django-filter==0.7
defusedxml==0.3
django-oauth-plus==2.2.1
oauth2==1.5.211
django-oauth2-provider==0.2.4
django-guardian==1.2.3
pytest-django==2.6.1
[testenv:py2.6-django1.6]
basepython = python2.6
deps = Django==1.6.3
django-filter==0.7
defusedxml==0.3
django-oauth-plus==2.2.1
oauth2==1.5.211
django-oauth2-provider==0.2.4
django-guardian==1.2.3
pytest-django==2.6.1
[testenv:py3.4-django1.5]
basepython = python3.4
deps = django==1.5.6
django-filter==0.7
defusedxml==0.3
pytest-django==2.6.1
[testenv:py3.3-django1.5]
basepython = python3.3
deps = django==1.5.6
django-filter==0.7
defusedxml==0.3
pytest-django==2.6.1
[testenv:py3.2-django1.5]
basepython = python3.2
deps = django==1.5.6
django-filter==0.7
defusedxml==0.3
pytest-django==2.6.1
[testenv:py2.7-django1.5]
basepython = python2.7
deps = django==1.5.6
django-filter==0.7
defusedxml==0.3
django-oauth-plus==2.2.1
oauth2==1.5.211
django-oauth2-provider==0.2.3
django-guardian==1.2.3
pytest-django==2.6.1
[testenv:py2.6-django1.5]
basepython = python2.6
deps = django==1.5.6
django-filter==0.7
defusedxml==0.3
django-oauth-plus==2.2.1
oauth2==1.5.211
django-oauth2-provider==0.2.3
django-guardian==1.2.3
pytest-django==2.6.1
[testenv:py2.7-django1.4]
basepython = python2.7
deps = django==1.4.11
django-filter==0.7
defusedxml==0.3
django-oauth-plus==2.2.1
oauth2==1.5.211
django-oauth2-provider==0.2.3
django-guardian==1.2.3
pytest-django==2.6.1
[testenv:py2.6-django1.4]
basepython = python2.6
deps = django==1.4.11
django-filter==0.7
defusedxml==0.3
django-oauth-plus==2.2.1
oauth2==1.5.211
django-oauth2-provider==0.2.3
django-guardian==1.2.3
pytest-django==2.6.1
[testenv:py3.4-djangomaster]
basepython = python3.4
deps = https://github.com/django/django/zipball/master
django-filter==0.7
defusedxml==0.3
pytest-django==2.6.1
[testenv:py3.3-djangomaster]
basepython = python3.3
deps = https://github.com/django/django/zipball/master
django-filter==0.7
defusedxml==0.3
pytest-django==2.6.1
[testenv:py2.7-djangomaster]
basepython = python3.2
deps = https://github.com/django/django/zipball/master
django-filter==0.7
defusedxml==0.3
pytest-django==2.6.1