aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rwxr-xr-xdocs/api-guide/authentication.md16
-rw-r--r--docs/api-guide/fields.md35
-rw-r--r--docs/api-guide/filtering.md4
-rw-r--r--docs/api-guide/format-suffixes.md4
-rwxr-xr-xdocs/api-guide/generic-views.md2
-rw-r--r--docs/api-guide/permissions.md4
-rw-r--r--docs/api-guide/relations.md6
-rw-r--r--docs/api-guide/renderers.md37
-rw-r--r--docs/api-guide/responses.md2
-rw-r--r--docs/api-guide/routers.md4
-rw-r--r--docs/api-guide/serializers.md14
-rw-r--r--docs/api-guide/settings.md2
-rw-r--r--docs/api-guide/status-codes.md6
-rw-r--r--docs/api-guide/views.md6
-rw-r--r--docs/api-guide/viewsets.md11
-rw-r--r--docs/index.md11
-rw-r--r--docs/topics/2.2-announcement.md6
-rw-r--r--docs/topics/2.3-announcement.md8
-rw-r--r--docs/topics/ajax-csrf-cors.md2
-rw-r--r--docs/topics/browsable-api.md22
-rw-r--r--docs/topics/browser-enhancements.md4
-rw-r--r--docs/topics/contributing.md7
-rw-r--r--docs/topics/credits.md4
-rw-r--r--docs/topics/release-notes.md31
-rw-r--r--docs/topics/rest-framework-2-announcement.md10
-rw-r--r--docs/topics/rest-hypermedia-hateoas.md2
-rw-r--r--docs/tutorial/1-serialization.md10
-rw-r--r--docs/tutorial/2-requests-and-responses.md4
-rw-r--r--docs/tutorial/3-class-based-views.md2
-rw-r--r--docs/tutorial/4-authentication-and-permissions.md8
-rw-r--r--docs/tutorial/5-relationships-and-hyperlinked-apis.md2
-rw-r--r--docs/tutorial/quickstart.md6
-rw-r--r--rest_framework/__init__.py2
-rw-r--r--rest_framework/decorators.py10
-rw-r--r--rest_framework/fields.py51
-rw-r--r--rest_framework/generics.py56
-rw-r--r--rest_framework/relations.py67
-rw-r--r--rest_framework/renderers.py30
-rw-r--r--rest_framework/response.py2
-rw-r--r--rest_framework/routers.py10
-rw-r--r--rest_framework/serializers.py28
-rw-r--r--rest_framework/templatetags/rest_framework.py2
-rw-r--r--rest_framework/tests/models.py2
-rw-r--r--rest_framework/tests/routers.py55
-rw-r--r--rest_framework/tests/test_authentication.py (renamed from rest_framework/tests/authentication.py)14
-rw-r--r--rest_framework/tests/test_breadcrumbs.py (renamed from rest_framework/tests/breadcrumbs.py)2
-rw-r--r--rest_framework/tests/test_decorators.py (renamed from rest_framework/tests/decorators.py)0
-rw-r--r--rest_framework/tests/test_description.py (renamed from rest_framework/tests/description.py)0
-rw-r--r--rest_framework/tests/test_fields.py (renamed from rest_framework/tests/fields.py)78
-rw-r--r--rest_framework/tests/test_files.py (renamed from rest_framework/tests/files.py)0
-rw-r--r--rest_framework/tests/test_filters.py (renamed from rest_framework/tests/filters.py)2
-rw-r--r--rest_framework/tests/test_genericrelations.py (renamed from rest_framework/tests/genericrelations.py)0
-rw-r--r--rest_framework/tests/test_generics.py (renamed from rest_framework/tests/generics.py)56
-rw-r--r--rest_framework/tests/test_htmlrenderer.py (renamed from rest_framework/tests/htmlrenderer.py)4
-rw-r--r--rest_framework/tests/test_hyperlinkedserializers.py (renamed from rest_framework/tests/hyperlinkedserializers.py)12
-rw-r--r--rest_framework/tests/test_multitable_inheritance.py (renamed from rest_framework/tests/multitable_inheritance.py)0
-rw-r--r--rest_framework/tests/test_negotiation.py (renamed from rest_framework/tests/negotiation.py)0
-rw-r--r--rest_framework/tests/test_pagination.py (renamed from rest_framework/tests/pagination.py)0
-rw-r--r--rest_framework/tests/test_parsers.py (renamed from rest_framework/tests/parsers.py)0
-rw-r--r--rest_framework/tests/test_permissions.py (renamed from rest_framework/tests/permissions.py)42
-rw-r--r--rest_framework/tests/test_relations.py (renamed from rest_framework/tests/relations.py)0
-rw-r--r--rest_framework/tests/test_relations_hyperlink.py (renamed from rest_framework/tests/relations_hyperlink.py)10
-rw-r--r--rest_framework/tests/test_relations_nested.py (renamed from rest_framework/tests/relations_nested.py)0
-rw-r--r--rest_framework/tests/test_relations_pk.py (renamed from rest_framework/tests/relations_pk.py)0
-rw-r--r--rest_framework/tests/test_relations_slug.py (renamed from rest_framework/tests/relations_slug.py)0
-rw-r--r--rest_framework/tests/test_renderers.py (renamed from rest_framework/tests/renderers.py)28
-rw-r--r--rest_framework/tests/test_request.py (renamed from rest_framework/tests/request.py)2
-rw-r--r--rest_framework/tests/test_response.py (renamed from rest_framework/tests/response.py)8
-rw-r--r--rest_framework/tests/test_reverse.py (renamed from rest_framework/tests/reverse.py)2
-rw-r--r--rest_framework/tests/test_routers.py121
-rw-r--r--rest_framework/tests/test_serializer.py (renamed from rest_framework/tests/serializer.py)51
-rw-r--r--rest_framework/tests/test_serializer_bulk_update.py (renamed from rest_framework/tests/serializer_bulk_update.py)0
-rw-r--r--rest_framework/tests/test_serializer_nested.py (renamed from rest_framework/tests/serializer_nested.py)0
-rw-r--r--rest_framework/tests/test_settings.py (renamed from rest_framework/tests/settings.py)0
-rw-r--r--rest_framework/tests/test_throttling.py (renamed from rest_framework/tests/throttling.py)2
-rw-r--r--rest_framework/tests/test_urlpatterns.py (renamed from rest_framework/tests/urlpatterns.py)0
-rw-r--r--rest_framework/tests/test_validation.py (renamed from rest_framework/tests/validation.py)0
-rw-r--r--rest_framework/tests/test_views.py (renamed from rest_framework/tests/views.py)5
-rw-r--r--rest_framework/tests/testcases.py66
-rw-r--r--rest_framework/tests/tests.py6
-rw-r--r--rest_framework/utils/encoders.py7
-rw-r--r--rest_framework/views.py31
83 files changed, 772 insertions, 391 deletions
diff --git a/README.md b/README.md
index 5d1631d4..94996c39 100644
--- a/README.md
+++ b/README.md
@@ -102,6 +102,12 @@ For questions and support, use the [REST framework discussion group][group], or
You may also want to [follow the author on Twitter][twitter].
+# Security
+
+If you believe you’ve found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**.
+
+Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
+
# License
Copyright (c) 2011-2013, Tom Christie
@@ -149,3 +155,4 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[pyyaml]: http://pypi.python.org/pypi/PyYAML
[defusedxml]: https://pypi.python.org/pypi/defusedxml
[django-filter]: http://pypi.python.org/pypi/django-filter
+[security-mail]: mailto:rest-framework-security@googlegroups.com
diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md
index c2f73901..6888ac4e 100755
--- a/docs/api-guide/authentication.md
+++ b/docs/api-guide/authentication.md
@@ -86,7 +86,7 @@ Note that when a request may successfully authenticate, but still be denied perm
Note that if deploying to [Apache using mod_wsgi][mod_wsgi_official], the authorization header is not passed through to a WSGI application by default, as it is assumed that authentication will be handled by Apache, rather than at an application level.
-If you are deploying to Apache, and using any non-session based authentication, you will need to explicitly configure mod_wsgi to pass the required headers through to the application. This can be done by specifying the `WSGIPassAuthorization` directive in the appropriate context and setting it to `'On'`.
+If you are deploying to Apache, and using any non-session based authentication, you will need to explicitly configure mod_wsgi to pass the required headers through to the application. This can be done by specifying the `WSGIPassAuthorization` directive in the appropriate context and setting it to `'On'`.
# this can go in either server config, virtual host, directory or .htaccess
WSGIPassAuthorization On
@@ -216,9 +216,9 @@ If you're using an AJAX style API with SessionAuthentication, you'll need to mak
## OAuthAuthentication
-This authentication uses [OAuth 1.0a][oauth-1.0a] authentication scheme. OAuth 1.0a provides signature validation which provides a reasonable level of security over plain non-HTTPS connections. However, it may also be considered more complicated than OAuth2, as it requires clients to sign their requests.
+This authentication uses [OAuth 1.0a][oauth-1.0a] authentication scheme. OAuth 1.0a provides signature validation which provides a reasonable level of security over plain non-HTTPS connections. However, it may also be considered more complicated than OAuth2, as it requires clients to sign their requests.
-This authentication class depends on the optional `django-oauth-plus` and `oauth2` packages. In order to make it work you must install these packages and add `oauth_provider` to your `INSTALLED_APPS`:
+This authentication class depends on the optional `django-oauth-plus` and `oauth2` packages. In order to make it work you must install these packages and add `oauth_provider` to your `INSTALLED_APPS`:
INSTALLED_APPS = (
...
@@ -231,7 +231,7 @@ Don't forget to run `syncdb` once you've added the package.
#### Getting started with django-oauth-plus
-The OAuthAuthentication class only provides token verification and signature validation for requests. It doesn't provide authorization flow for your clients. You still need to implement your own views for accessing and authorizing tokens.
+The OAuthAuthentication class only provides token verification and signature validation for requests. It doesn't provide authorization flow for your clients. You still need to implement your own views for accessing and authorizing tokens.
The `django-oauth-plus` package provides simple foundation for classic 'three-legged' oauth flow. Please refer to [the documentation][django-oauth-plus] for more details.
@@ -239,7 +239,7 @@ The `django-oauth-plus` package provides simple foundation for classic 'three-le
This authentication uses [OAuth 2.0][rfc6749] authentication scheme. OAuth2 is more simple to work with than OAuth1, and provides much better security than simple token authentication. It is an unauthenticated scheme, and requires you to use an HTTPS connection.
-This authentication class depends on the optional [django-oauth2-provider][django-oauth2-provider] project. In order to make it work you must install this package and add `provider` and `provider.oauth2` to your `INSTALLED_APPS`:
+This authentication class depends on the optional [django-oauth2-provider][django-oauth2-provider] project. In order to make it work you must install this package and add `provider` and `provider.oauth2` to your `INSTALLED_APPS`:
INSTALLED_APPS = (
...
@@ -266,9 +266,9 @@ Finally, sync your database.
#### Getting started with django-oauth2-provider
-The `OAuth2Authentication` class only provides token verification for requests. It doesn't provide authorization flow for your clients.
+The `OAuth2Authentication` class only provides token verification for requests. It doesn't provide authorization flow for your clients.
-The OAuth 2 authorization flow is taken care by the [django-oauth2-provider][django-oauth2-provider] dependency. A walkthrough is given here, but for more details you should refer to [the documentation][django-oauth2-provider-docs].
+The OAuth 2 authorization flow is taken care by the [django-oauth2-provider][django-oauth2-provider] dependency. A walkthrough is given here, but for more details you should refer to [the documentation][django-oauth2-provider-docs].
To get started:
@@ -276,7 +276,7 @@ To get started:
You can create a client, either through the shell, or by using the Django admin.
-Go to the admin panel and create a new `Provider.Client` entry. It will create the `client_id` and `client_secret` properties for you.
+Go to the admin panel and create a new `Provider.Client` entry. It will create the `client_id` and `client_secret` properties for you.
##### 2. Request an access token
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index e117c370..c7db32ed 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -16,7 +16,7 @@ Serializer fields handle converting between primitive values and internal dataty
## Core arguments
-Each serializer field class constructor takes at least these arguments. Some Field classes take additional, field-specific arguments, but the following should always be accepted:
+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`
@@ -41,7 +41,9 @@ Defaults to `True`.
### `default`
-If set, this gives the default value that will be used for the field if none is supplied. If not set the default behavior is to not populate the attribute at all.
+If set, this gives the default value that will be used for the field if none 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.
### `validators`
@@ -56,6 +58,13 @@ A dictionary of error codes to error messages.
Used only if rendering the field to HTML.
This argument sets the widget that should be used to render the field.
+### `label`
+
+A short text string that may be used as the name of the field in HTML form fields or other descriptive elements.
+
+### `help_text`
+
+A text string that may be used as a description of the field in HTML form fields or other descriptive elements.
---
@@ -112,7 +121,7 @@ A generic field that can be tied to any arbitrary model field. The `ModelField`
## 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:
+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 rest_framework import serializers
from django.contrib.auth.models import User
@@ -151,7 +160,7 @@ or `django.db.models.fields.TextField`.
## URLField
-Corresponds to `django.db.models.fields.URLField`. Uses Django's `django.core.validators.URLValidator` for validation.
+Corresponds to `django.db.models.fields.URLField`. Uses Django's `django.core.validators.URLValidator` for validation.
**Signature:** `CharField(max_length=200, min_length=None)`
@@ -197,16 +206,16 @@ If you want to override this behavior, you'll need to declare the `DateTimeField
class Meta:
model = Comment
-Note that by default, datetime representations are deteremined by the renderer in use, although this can be explicitly overridden as detailed below.
+Note that by default, datetime representations are determined by the renderer in use, although this can be explicitly overridden as detailed below.
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']`.
+* `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 specifiy 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'`)
+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
@@ -217,9 +226,9 @@ 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']`.
+* `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 specifiy the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style dates should be used. (eg `'2013-01-29'`)
+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
@@ -232,9 +241,9 @@ 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']`.
+* `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 specifiy the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`)
+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
@@ -256,7 +265,7 @@ Corresponds to `django.db.models.fields.DecimalField`.
## FileField
-A file representation. Performs Django's standard FileField validation.
+A file representation. Performs Django's standard FileField validation.
Corresponds to `django.forms.fields.FileField`.
@@ -285,7 +294,7 @@ Django's regular [FILE_UPLOAD_HANDLERS] are used for handling uploaded files.
# 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 intial datatype, and a primative, serializable datatype. Primative 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 primative objects.
+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 primative, serializable datatype. Primative 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 primative objects.
The `.to_native()` method is called to convert the initial datatype into a primative, serializable datatype. The `from_native()` method is called to restore a primative datatype into it's initial representation.
diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md
index dd4cf8d7..05c997a3 100644
--- a/docs/api-guide/filtering.md
+++ b/docs/api-guide/filtering.md
@@ -2,7 +2,7 @@
# Filtering
-> The root QuerySet provided by the Manager describes all objects in the database table. Usually, though, you'll need to select only a subset of the complete set of objects.
+> The root QuerySet provided by the Manager describes all objects in the database table. Usually, though, you'll need to select only a subset of the complete set of objects.
>
> — [Django documentation][cite]
@@ -231,7 +231,7 @@ Multiple orderings may also be specified:
If an `ordering` attribute is set on the view, this will be used as the default ordering.
-Typicaly you'd instead control this by setting `order_by` on the initial queryset, but using the `ordering` parameter on the view allows you to specify the ordering in a way that it can then be passed automatically as context to a rendered template. This makes it possible to automatically render column headers differently if they are being used to order the results.
+Typically you'd instead control this by setting `order_by` on the initial queryset, but using the `ordering` parameter on the view allows you to specify the ordering in a way that it can then be passed automatically as context to a rendered template. This makes it possible to automatically render column headers differently if they are being used to order the results.
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
diff --git a/docs/api-guide/format-suffixes.md b/docs/api-guide/format-suffixes.md
index dae3dea3..529738e3 100644
--- a/docs/api-guide/format-suffixes.md
+++ b/docs/api-guide/format-suffixes.md
@@ -58,11 +58,11 @@ Also note that `format_suffix_patterns` does not support descending into `includ
## Accept headers vs. format suffixes
-There seems to be a view among some of the Web community that filename extensions are not a RESTful pattern, and that `HTTP Accept` headers should always be used instead.
+There seems to be a view among some of the Web community that filename extensions are not a RESTful pattern, and that `HTTP Accept` headers should always be used instead.
It is actually a misconception. For example, take the following quote from Roy Fielding discussing the relative merits of query parameter media-type indicators vs. file extension media-type indicators:
-“That's why I always prefer extensions. Neither choice has anything to do with REST.” — Roy Fielding, [REST discuss mailing list][cite2]
+“That's why I always prefer extensions. Neither choice has anything to do with REST.” — Roy Fielding, [REST discuss mailing list][cite2]
The quote does not mention Accept headers, but it does make it clear that format suffixes should be considered an acceptable pattern.
diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md
index 1a060a32..20b9440b 100755
--- a/docs/api-guide/generic-views.md
+++ b/docs/api-guide/generic-views.md
@@ -71,7 +71,7 @@ The following attributes control the basic view behavior.
The following attibutes are used to control pagination when used with list views.
* `paginate_by` - The size of pages to use with paginated data. If set to `None` then pagination is turned off. If unset this uses the same value as the `PAGINATE_BY` setting, which defaults to `None`.
-* `paginate_by_param` - The name of a query parameter, which can be used by the client to overide the default page size to use for pagination. If unset this uses the same value as the `PAGINATE_BY_PARAM` setting, which defaults to `None`.
+* `paginate_by_param` - The name of a query parameter, which can be used by the client to override the default page size to use for pagination. If unset this uses the same value as the `PAGINATE_BY_PARAM` setting, which defaults to `None`.
* `pagination_serializer_class` - The pagination serializer class to use when determining the style of paginated responses. Defaults to the same value as the `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting.
* `page_kwarg` - The name of a URL kwarg or URL query parameter which can be used by the client to control which page is requested. Defaults to `'page'`.
diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md
index db0d4b26..2c0a055c 100644
--- a/docs/api-guide/permissions.md
+++ b/docs/api-guide/permissions.md
@@ -2,7 +2,7 @@
# Permissions
-> Authentication or identification by itself is not usually sufficient to gain access to information or code. For that, the entity requesting access must have authorization.
+> Authentication or identification by itself is not usually sufficient to gain access to information or code. For that, the entity requesting access must have authorization.
>
> — [Apple Developer Documentation][cite]
@@ -147,7 +147,7 @@ If you need to test if a request is a read operation or a write operation, you s
**Note**: In versions 2.0 and 2.1, the signature for the permission checks always included an optional `obj` parameter, like so: `.has_permission(self, request, view, obj=None)`. The method would be called twice, first for the global permission checks, with no object supplied, and second for the object-level check when required.
-As of version 2.2 this signature has now been replaced with two seperate method calls, which is more explict and obvious. The old style signature continues to work, but it's use will result in a `PendingDeprecationWarning`, which is silent by default. In 2.3 this will be escalated to a `DeprecationWarning`, and in 2.4 the old-style signature will be removed.
+As of version 2.2 this signature has now been replaced with two separate method calls, which is more explict and obvious. The old style signature continues to work, but it's use will result in a `PendingDeprecationWarning`, which is silent by default. In 2.3 this will be escalated to a `DeprecationWarning`, and in 2.4 the old-style signature will be removed.
For more details see the [2.2 release announcement][2.2-announcement].
diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md
index 99fe1083..50c9bc54 100644
--- a/docs/api-guide/relations.md
+++ b/docs/api-guide/relations.md
@@ -138,7 +138,7 @@ By default this field is read-write, although you can change this behavior using
* `many` - If applied to a to-many relationship, you should set this argument to `True`.
* `required` - If set to `False`, the field will accept values of `None` or the empty-string for nullable relationships.
* `queryset` - By default `ModelSerializer` classes will use the default queryset for the relationship. `Serializer` classes must either set a queryset explicitly, or set `read_only=True`.
-* `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'`.
* `format` - If using format suffixes, hyperlinked fields will use the same format suffix for the target unless overridden by using the `format` argument.
## SlugRelatedField
@@ -202,7 +202,7 @@ This field is always read-only.
**Arguments**:
* `view_name` - The view name that should be used as the target of the relationship. **required**.
-* `lookup_field` - The field on the target that should be used for the lookup. Should correspond to a URL keyword argument on the referenced view. Default is `'pk'`.
+* `lookup_field` - The field on the target that should be used for the lookup. Should correspond to a URL keyword argument on the referenced view. Default is `'pk'`.
* `format` - If using format suffixes, hyperlinked fields will use the same format suffix for the target unless overridden by using the `format` argument.
---
@@ -237,7 +237,7 @@ Would serialize to a nested representation like this:
'album_name': 'The Grey Album',
'artist': 'Danger Mouse'
'tracks': [
- {'order': 1, 'title': 'Public Service Annoucement'},
+ {'order': 1, 'title': 'Public Service Announcement'},
{'order': 2, 'title': 'What More Can I Say'},
{'order': 3, 'title': 'Encore'},
...
diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md
index a5eba9fc..b627c930 100644
--- a/docs/api-guide/renderers.md
+++ b/docs/api-guide/renderers.md
@@ -14,7 +14,7 @@ The set of valid renderers for a view is always defined as a list of classes. W
The basic process of content negotiation involves examining the request's `Accept` header, to determine which media types it expects in the response. Optionally, format suffixes on the URL may be used to explicitly request a particular representation. For example the URL `http://example.com/api/users_count.json` might be an endpoint that always returns JSON data.
-For more information see the documentation on [content negotation][conneg].
+For more information see the documentation on [content negotiation][conneg].
## Setting the renderers
@@ -67,22 +67,40 @@ If your API includes views that can serve both regular webpages and API response
## JSONRenderer
-Renders the request data into `JSON`, using ASCII encoding.
+Renders the request data into `JSON`, using utf-8 encoding.
+
+Note that non-ascii characters will be rendered using JSON's `\uXXXX` character escape. For example:
+
+ {"unicode black star": "\u2605"}
The client may additionally include an `'indent'` media type parameter, in which case the returned `JSON` will be indented. For example `Accept: application/json; indent=4`.
+ {
+ "unicode black star": "\u2605"
+ }
+
**.media_type**: `application/json`
**.format**: `'.json'`
-**.charset**: `iso-8859-1`
+**.charset**: `utf-8`
## UnicodeJSONRenderer
Renders the request data into `JSON`, using utf-8 encoding.
+Note that non-ascii characters will not be character escaped. For example:
+
+ {"unicode black star": "★"}
+
The client may additionally include an `'indent'` media type parameter, in which case the returned `JSON` will be indented. For example `Accept: application/json; indent=4`.
+ {
+ "unicode black star": "★"
+ }
+
+Both the `JSONRenderer` and `UnicodeJSONRenderer` styles conform to [RFC 4627][rfc4627], and are syntactically valid JSON.
+
**.media_type**: `application/json`
**.format**: `'.json'`
@@ -101,7 +119,7 @@ The javascript callback function must be set by the client including a `callback
**.format**: `'.jsonp'`
-**.charset**: `iso-8859-1`
+**.charset**: `utf-8`
## YAMLRenderer
@@ -215,13 +233,13 @@ The request data, as set by the `Response()` instantiation.
### `media_type=None`
-Optional. If provided, this is the accepted media type, as determined by the content negotiation stage.
+Optional. If provided, this is the accepted media type, as determined by the content negotiation stage.
Depending on the client's `Accept:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters. For example `"application/json; nested=true"`.
### `renderer_context=None`
-Optional. If provided, this is a dictionary of contextual information provided by the view.
+Optional. If provided, this is a dictionary of contextual information provided by the view.
By default this will include the following keys: `view`, `request`, `response`, `args`, `kwargs`.
@@ -252,7 +270,9 @@ By default renderer classes are assumed to be using the `UTF-8` encoding. To us
def render(self, data, media_type=None, renderer_context=None):
return data.encode(self.charset)
-If the renderer returns a raw bytestring, you should set a charset value of `None`, which will ensure the `Content-Type` header of the response will not have a `charset` value set. Doing so will also ensure that the browsable API will not attempt to display the binary content as a string.
+Note that if a renderer class returns a unicode string, then the response content will be coerced into a bytestring by the `Response` class, with the `charset` attribute set on the renderer used to determine the encoding.
+
+If the renderer returns a bytestring representing raw binary content, you should set a charset value of `None`, which will ensure the `Content-Type` header of the response will not have a `charset` value set. Doing so will also ensure that the browsable API will not attempt to display the binary content as a string.
class JPEGRenderer(renderers.BaseRenderer):
media_type = 'image/jpeg'
@@ -305,7 +325,7 @@ For example:
In some cases you might want a renderer to serve a range of media types.
In this case you can underspecify the media types it should respond to, by using a `media_type` value such as `image/*`, or `*/*`.
-If you underspecify the renderer's media type, you should make sure to specify the media type explictly when you return the response, using the `content_type` attribute. For example:
+If you underspecify the renderer's media type, you should make sure to specify the media type explicitly when you return the response, using the `content_type` attribute. For example:
return Response(data, content_type='image/png')
@@ -350,6 +370,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process
[conneg]: content-negotiation.md
[browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers
+[rfc4627]: http://www.ietf.org/rfc/rfc4627.txt
[cors]: http://www.w3.org/TR/cors/
[cors-docs]: ../topics/ajax-csrf-cors.md
[HATEOAS]: http://timelessrepo.com/haters-gonna-hateoas
diff --git a/docs/api-guide/responses.md b/docs/api-guide/responses.md
index 59632ad5..374276dc 100644
--- a/docs/api-guide/responses.md
+++ b/docs/api-guide/responses.md
@@ -2,7 +2,7 @@
# Responses
-> Unlike basic HttpResponse objects, TemplateResponse objects retain the details of the context that was provided by the view to compute the response. The final output of the response is not computed until it is needed, later in the response process.
+> Unlike basic HttpResponse objects, TemplateResponse objects retain the details of the context that was provided by the view to compute the response. The final output of the response is not computed until it is needed, later in the response process.
>
> — [Django documentation][cite]
diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md
index 6588d7e5..3c3a6ae3 100644
--- a/docs/api-guide/routers.md
+++ b/docs/api-guide/routers.md
@@ -2,7 +2,7 @@
# Routers
-> Resource routing allows you to quickly declare all of the common routes for a given resourceful controller. Instead of declaring separate routes for your index... a resourceful route declares them in a single line of code.
+> Resource routing allows you to quickly declare all of the common routes for a given resourceful controller. Instead of declaring separate routes for your index... a resourceful route declares them in a single line of code.
>
> — [Ruby on Rails Documentation][cite]
@@ -85,7 +85,7 @@ This router is similar to `SimpleRouter` as above, but additionally includes a d
# Custom Routers
-Implementing a custom router isn't something you'd need to do very often, but it can be useful if you have specfic requirements about how the your URLs for your API are strutured. Doing so allows you to encapsulate the URL structure in a reusable way that ensures you don't have to write your URL patterns explicitly for each new view.
+Implementing a custom router isn't something you'd need to do very often, but it can be useful if you have specific requirements about how the your URLs for your API are strutured. Doing so allows you to encapsulate the URL structure in a reusable way that ensures you don't have to write your URL patterns explicitly for each new view.
The simplest way to implement a custom router is to subclass one of the existing router classes. The `.routes` attribute is used to template the URL patterns that will be mapped to each viewset.
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index c83a0967..71f0abb7 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -3,7 +3,7 @@
# Serializers
> Expanding the usefulness of the serializers is something that we would
-like to address. However, it's not a trivial problem, and it
+like to address. However, it's not a trivial problem, and it
will take some serious design work.
>
> — Russell Keith-Magee, [Django users group][cite]
@@ -104,11 +104,11 @@ When deserializing a list of items, errors will be returned as a list of diction
#### Field-level validation
-You can specify custom field-level validation by adding `.validate_<fieldname>` methods to your `Serializer` subclass. These are analagous to `.clean_<fieldname>` methods on Django forms, but accept slightly different arguments.
+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.
They take a dictionary of deserialized attributes as a first argument, and the field name in that dictionary as a second argument (which will be either the name of the field or the value of the `source` argument to the field, if one was provided).
-Your `validate_<fieldname>` methods should either just return the `attrs` dictionary or raise a `ValidationError`. For example:
+Your `validate_<fieldname>` methods should either just return the `attrs` dictionary or raise a `ValidationError`. For example:
from rest_framework import serializers
@@ -127,7 +127,7 @@ Your `validate_<fieldname>` methods should either just return the `attrs` dictio
#### 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 the `attrs` dictionary. It should raise a `ValidationError` if necessary, or just return `attrs`. For example:
from rest_framework import serializers
@@ -285,7 +285,7 @@ To map the incoming data items to their corresponding object instances, the `.ge
There are some cases where you need to provide extra context to the serializer in addition to the object being serialized. One common case is if you're using a serializer that includes hyperlinked relations, which requires the serializer to have access to the current request so that it can properly generate fully qualified URLs.
-You can provide arbitrary additional context by passing a `context` argument when instantiating the serializer. For example:
+You can provide arbitrary additional context by passing a `context` argument when instantiating the serializer. For example:
serializer = AccountSerializer(account, context={'request': request})
serializer.data
@@ -333,7 +333,7 @@ The `depth` option should be set to an integer value that indicates the depth of
## 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:
+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:
@@ -425,7 +425,7 @@ Doing so should be considered advanced usage, and will only be needed if you hav
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.
-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`.
+For more advanced customization than simply changing the default serializer class you can override various `get_<field_type>_field` methods. Doing so will allow you to customize the arguments that each serializer field is initialized with. Each of these methods may either return a field or serializer instance, or `None`.
### get_pk_field
diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md
index b00ab4c1..8d8c00cf 100644
--- a/docs/api-guide/settings.md
+++ b/docs/api-guide/settings.md
@@ -125,7 +125,7 @@ Default: `None`
#### PAGINATE_BY_PARAM
-The name of a query parameter, which can be used by the client to overide the default page size to use for pagination. If set to `None`, clients may not override the default page size.
+The name of a query parameter, which can be used by the client to override the default page size to use for pagination. If set to `None`, clients may not override the default page size.
Default: `None`
diff --git a/docs/api-guide/status-codes.md b/docs/api-guide/status-codes.md
index b50c96ae..db2e059c 100644
--- a/docs/api-guide/status-codes.md
+++ b/docs/api-guide/status-codes.md
@@ -2,7 +2,7 @@
# Status Codes
-> 418 I'm a teapot - Any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot". The resulting entity body MAY be short and stout.
+> 418 I'm a teapot - Any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot". The resulting entity body MAY be short and stout.
>
> &mdash; [RFC 2324][rfc2324], Hyper Text Coffee Pot Control Protocol
@@ -53,7 +53,7 @@ This class of status code indicates that further action needs to be taken by the
## Client Error - 4xx
-The 4xx class of status code is intended for cases in which the client seems to have erred. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition.
+The 4xx class of status code is intended for cases in which the client seems to have erred. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition.
HTTP_400_BAD_REQUEST
HTTP_401_UNAUTHORIZED
@@ -79,7 +79,7 @@ The 4xx class of status code is intended for cases in which the client seems to
## Server Error - 5xx
-Response status codes beginning with the digit "5" indicate cases in which the server is aware that it has erred or is incapable of performing the request. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition.
+Response status codes beginning with the digit "5" indicate cases in which the server is aware that it has erred or is incapable of performing the request. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition.
HTTP_500_INTERNAL_SERVER_ERROR
HTTP_501_NOT_IMPLEMENTED
diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md
index 8b26b3e3..37ebd55f 100644
--- a/docs/api-guide/views.md
+++ b/docs/api-guide/views.md
@@ -122,13 +122,13 @@ You won't typically need to override this method.
>
> &mdash; [Nick Coghlan][cite2]
-REST framework also allows you to work with regular function based views. It provides a set of simple decorators that wrap your function based views to ensure they receive an instance of `Request` (rather than the usual Django `HttpRequest`) and allows them to return a `Response` (instead of a Django `HttpResponse`), and allow you to configure how the request is processed.
+REST framework also allows you to work with regular function based views. It provides a set of simple decorators that wrap your function based views to ensure they receive an instance of `Request` (rather than the usual Django `HttpRequest`) and allows them to return a `Response` (instead of a Django `HttpResponse`), and allow you to configure how the request is processed.
## @api_view()
**Signature:** `@api_view(http_method_names)`
-The core of this functionality is the `api_view` decorator, which takes a list of HTTP methods that your view should respond to. For example, this is how you would write a very simple view that just manually returns some data:
+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
@@ -141,7 +141,7 @@ This view will use the default renderers, parsers, authentication classes etc sp
## 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:
from rest_framework.decorators import api_view, throttle_classes
from rest_framework.throttling import UserRateThrottle
diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md
index cd92dc58..2783da98 100644
--- a/docs/api-guide/viewsets.md
+++ b/docs/api-guide/viewsets.md
@@ -34,7 +34,7 @@ Let's define a simple viewset that can be used to list or retrieve all the users
serializer = UserSerializer(user)
return Response(serializer.data)
-If we need to, we can bind this viewset into two seperate views, like so:
+If we need to, we can bind this viewset into two separate views, like so:
user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})
@@ -65,7 +65,7 @@ Both of these come with a trade-off. Using regular views and URL confs is more
The default routers included with REST framework will provide routes for a standard set of create/retrieve/update/destroy style operations, as shown below:
- class UserViewSet(viewsets.VietSet):
+ class UserViewSet(viewsets.ViewSet):
"""
Example empty viewset demonstrating the standard
actions that will be handled by a router class.
@@ -92,7 +92,7 @@ The default routers included with REST framework will provide routes for a stand
def destroy(self, request, pk=None):
pass
-If you have ad-hoc methods that you need to be routed to, you can mark them as requiring routing using the `@link` or `@action` decorators. The `@link` decorator will route `GET` requests, and the `@action` decroator will route `POST` requests.
+If you have ad-hoc methods that you need to be routed to, you can mark them as requiring routing using the `@link` or `@action` decorators. The `@link` decorator will route `GET` requests, and the `@action` decorator will route `POST` requests.
For example:
@@ -126,6 +126,11 @@ The `@action` and `@link` decorators can additionally take extra arguments that
def set_password(self, request, pk=None):
...
+The `@action` decorator will route `POST` requests by default, but may also accept other HTTP methods, by using the `method` argument. For example:
+
+ @action(methods=['POST', 'DELETE'])
+ def unset_password(self, request, pk=None):
+ ...
---
# API Reference
diff --git a/docs/index.md b/docs/index.md
index 7c38efd3..78259a00 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -15,7 +15,7 @@ Django REST framework is a powerful and flexible toolkit that makes it easy to b
Some reasons you might want to use REST framework:
-* The Web browseable API is a huge useability win for your developers.
+* The Web browseable API is a huge usability win for your developers.
* Authentication policies including OAuth1a and OAuth2 out of the box.
* Serialization that supports both ORM and non-ORM data sources.
* Customizable all the way down - just use regular function-based views if you don't need the more powerful features.
@@ -206,7 +206,13 @@ For updates on REST framework development, you may also want to follow [the auth
<a style="padding-top: 10px" href="https://twitter.com/_tomchristie" class="twitter-follow-button" data-show-count="false">Follow @_tomchristie</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
-
+
+## Security
+
+If you believe you’ve found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**.
+
+Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
+
## License
Copyright (c) 2011-2013, Tom Christie
@@ -294,6 +300,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[stack-overflow]: http://stackoverflow.com/
[django-rest-framework-tag]: http://stackoverflow.com/questions/tagged/django-rest-framework
[django-tag]: http://stackoverflow.com/questions/tagged/django
+[security-mail]: mailto:rest-framework-security@googlegroups.com
[paid-support]: http://dabapps.com/services/build/api-development/
[dabapps]: http://dabapps.com
[contact-dabapps]: http://dabapps.com/contact/
diff --git a/docs/topics/2.2-announcement.md b/docs/topics/2.2-announcement.md
index d7164ce4..02cac129 100644
--- a/docs/topics/2.2-announcement.md
+++ b/docs/topics/2.2-announcement.md
@@ -75,7 +75,7 @@ This more explicit behavior on serializing and deserializing data [makes integra
The implicit to-many behavior on serializers, and the `ManyRelatedField` style classes will continue to function, but will raise a `PendingDeprecationWarning`, which can be made visible using the `-Wd` flag.
-**Note**: If you need to forcibly turn off the implict "`many=True` for `__iter__` objects" behavior, you can now do so by specifying `many=False`. This will become the default (instead of the current default of `None`) once the deprecation of the implicit behavior is finalised in version 2.4.
+**Note**: If you need to forcibly turn off the implicit "`many=True` for `__iter__` objects" behavior, you can now do so by specifying `many=False`. This will become the default (instead of the current default of `None`) once the deprecation of the implicit behavior is finalised in version 2.4.
### Cleaner optional relationships
@@ -103,9 +103,9 @@ The `blank` keyword argument will continue to function, but will raise a `Pendin
### Simpler object-level permissions
-Custom permissions classes previously used the signatute `.has_permission(self, request, view, obj=None)`. This method would be called twice, firstly for the global permissions check, with the `obj` parameter set to `None`, and again for the object-level permissions check when appropriate, with the `obj` parameter set to the relevant model instance.
+Custom permissions classes previously used the signature `.has_permission(self, request, view, obj=None)`. This method would be called twice, firstly for the global permissions check, with the `obj` parameter set to `None`, and again for the object-level permissions check when appropriate, with the `obj` parameter set to the relevant model instance.
-The global permissions check and object-level permissions check are now seperated into two seperate methods, which gives a cleaner, more obvious API.
+The global permissions check and object-level permissions check are now separated into two separate methods, which gives a cleaner, more obvious API.
* Global permission checks now use the `.has_permission(self, request, view)` signature.
* Object-level permission checks use a new method `.has_object_permission(self, request, view, obj)`.
diff --git a/docs/topics/2.3-announcement.md b/docs/topics/2.3-announcement.md
index 4df9c819..9fdebcd9 100644
--- a/docs/topics/2.3-announcement.md
+++ b/docs/topics/2.3-announcement.md
@@ -45,13 +45,13 @@ The best place to get started with ViewSets and Routers is to take a look at the
## Simpler views
-This release rationalises the API and implementation of the generic views, dropping the dependancy on Django's `SingleObjectMixin` and `MultipleObjectMixin` classes, removing a number of unneeded attributes, and generally making the implementation more obvious and easy to work with.
+This release rationalises the API and implementation of the generic views, dropping the dependency on Django's `SingleObjectMixin` and `MultipleObjectMixin` classes, removing a number of unneeded attributes, and generally making the implementation more obvious and easy to work with.
This improvement is reflected in improved documentation for the `GenericAPIView` base class, and should make it easier to determine how to override methods on the base class if you need to write customized subclasses.
## Easier Serializers
-REST framework lets you be totally explict regarding how you want to represent relationships, allowing you to choose between styles such as hyperlinking or primary key relationships.
+REST framework lets you be totally explicit regarding how you want to represent relationships, allowing you to choose between styles such as hyperlinking or primary key relationships.
The ability to specify exactly how you want to represent relationships is powerful, but it also introduces complexity. In order to keep things more simple, REST framework now allows you to include reverse relationships simply by including the field name in the `fields` metadata of the serializer class.
@@ -108,7 +108,7 @@ Using the `SingleObjectAPIView` and `MultipleObjectAPIView` base classes continu
### Removed attributes
-The following attributes and methods, were previously present as part of Django's generic view implementations, but were unneeded and unusedand have now been entirely removed.
+The following attributes and methods, were previously present as part of Django's generic view implementations, but were unneeded and unused and have now been entirely removed.
* context_object_name
* get_context_data()
@@ -173,7 +173,7 @@ For example:
raise Http404
return queryset
-In our opinion removing lesser-used attributes like `allow_empty` helps us move towards simpler generic view implementations, making them more obvious to use and override, and re-inforcing the preferred style of developers writing their own base classes and mixins for custom behavior rather than relying on the configurability of the generic views.
+In our opinion removing lesser-used attributes like `allow_empty` helps us move towards simpler generic view implementations, making them more obvious to use and override, and re-enforcing the preferred style of developers writing their own base classes and mixins for custom behavior rather than relying on the configurability of the generic views.
## Simpler URL lookups
diff --git a/docs/topics/ajax-csrf-cors.md b/docs/topics/ajax-csrf-cors.md
index f7d12940..4566f38b 100644
--- a/docs/topics/ajax-csrf-cors.md
+++ b/docs/topics/ajax-csrf-cors.md
@@ -1,6 +1,6 @@
# Working with AJAX, CSRF & CORS
-> "Take a close look at possible CSRF / XSRF vulnerabilities on your own websites. They're the worst kind of vulnerability &mdash; very easy to exploit by attackers, yet not so intuitively easy to understand for software developers, at least until you've been bitten by one."
+> "Take a close look at possible CSRF / XSRF vulnerabilities on your own websites. They're the worst kind of vulnerability &mdash; very easy to exploit by attackers, yet not so intuitively easy to understand for software developers, at least until you've been bitten by one."
>
> &mdash; [Jeff Atwood][cite]
diff --git a/docs/topics/browsable-api.md b/docs/topics/browsable-api.md
index 65f76abc..dcba9b0a 100644
--- a/docs/topics/browsable-api.md
+++ b/docs/topics/browsable-api.md
@@ -1,20 +1,20 @@
# The Browsable API
-> It is a profoundly erroneous truism... that we should cultivate the habit of thinking of what we are doing. The precise opposite is the case. Civilization advances by extending the number of important operations which we can perform without thinking about them.
+> It is a profoundly erroneous truism... that we should cultivate the habit of thinking of what we are doing. The precise opposite is the case. Civilization advances by extending the number of important operations which we can perform without thinking about them.
>
> &mdash; [Alfred North Whitehead][cite], An Introduction to Mathematics (1911)
-API may stand for Application *Programming* Interface, but humans have to be able to read the APIs, too; someone has to do the programming. Django REST Framework supports generating human-friendly HTML output for each resource when the `HTML` format is requested. These pages allow for easy browsing of resources, as well as forms for submitting data to the resources using `POST`, `PUT`, and `DELETE`.
+API may stand for Application *Programming* Interface, but humans have to be able to read the APIs, too; someone has to do the programming. Django REST Framework supports generating human-friendly HTML output for each resource when the `HTML` format is requested. These pages allow for easy browsing of resources, as well as forms for submitting data to the resources using `POST`, `PUT`, and `DELETE`.
## URLs
-If you include fully-qualified URLs in your resource output, they will be 'urlized' and made clickable for easy browsing by humans. The `rest_framework` package includes a [`reverse`][drfreverse] helper for this purpose.
+If you include fully-qualified URLs in your resource output, they will be 'urlized' and made clickable for easy browsing by humans. The `rest_framework` package includes a [`reverse`][drfreverse] helper for this purpose.
## Formats
-By default, the API will return the format specified by the headers, which in the case of the browser is HTML. The format can be specified using `?format=` in the request, so you can look at the raw JSON response in a browser by adding `?format=json` to the URL. There are helpful extensions for viewing JSON in [Firefox][ffjsonview] and [Chrome][chromejsonview].
+By default, the API will return the format specified by the headers, which in the case of the browser is HTML. The format can be specified using `?format=` in the request, so you can look at the raw JSON response in a browser by adding `?format=json` to the URL. There are helpful extensions for viewing JSON in [Firefox][ffjsonview] and [Chrome][chromejsonview].
## Customizing
@@ -25,15 +25,15 @@ The included browsable API template is built with [Bootstrap (2.1.1)][bootstrap]
### Theme
-To replace the theme wholesale, add a `bootstrap_theme` block to your `api.html` and insert a `link` to the desired Bootstrap theme css file. This will completely replace the included theme.
+To replace the theme wholesale, add a `bootstrap_theme` block to your `api.html` and insert a `link` to the desired Bootstrap theme css file. This will completely replace the included theme.
{% block bootstrap_theme %}
<link rel="stylesheet" href="/path/to/my/bootstrap.css" type="text/css">
{% endblock %}
-A suitable replacement theme can be generated using Bootstrap's [Customize Tool][bcustomize]. Also, there are pre-made themes available at [Bootswatch][bswatch]. To use any of the Bootswatch themes, simply download the theme's `bootstrap.min.css` file, add it to your project, and replace the default one as described above.
+A suitable replacement theme can be generated using Bootstrap's [Customize Tool][bcustomize]. Also, there are pre-made themes available at [Bootswatch][bswatch]. To use any of the Bootswatch themes, simply download the theme's `bootstrap.min.css` file, add it to your project, and replace the default one as described above.
-You can also change the navbar variant, which by default is `navbar-inverse`, using the `bootstrap_navbar_variant` block. The empty `{% block bootstrap_navbar_variant %}{% endblock %}` will use the original Bootstrap navbar style.
+You can also change the navbar variant, which by default is `navbar-inverse`, using the `bootstrap_navbar_variant` block. The empty `{% block bootstrap_navbar_variant %}{% endblock %}` will use the original Bootstrap navbar style.
Full Example
@@ -57,11 +57,11 @@ All of the blocks available in the browsable API base template that can be used
* `bootstrap_theme` - CSS for the Bootstrap theme
* `bootstrap_navbar_variant` - CSS class for the navbar
* `branding` - section of the navbar, see [Bootstrap components][bcomponentsnav]
-* `breadcrumbs` - Links showing resource nesting, allowing the user to go back up the resources. It's recommended to preserve these, but they can be overridden using the breadcrumbs block.
+* `breadcrumbs` - Links showing resource nesting, allowing the user to go back up the resources. It's recommended to preserve these, but they can be overridden using the breadcrumbs block.
* `footer` - Any copyright notices or similar footer materials can go here (by default right-aligned)
* `style` - CSS stylesheets for the page
* `title` - title of the page
-* `userlinks` - This is a list of links on the right of the header, by default containing login/logout links. To add links instead of replace, use {{ block.super }} to preserve the authentication links.
+* `userlinks` - This is a list of links on the right of the header, by default containing login/logout links. To add links instead of replace, use {{ block.super }} to preserve the authentication links.
#### Components
@@ -69,7 +69,7 @@ All of the [Bootstrap components][bcomponents] are available.
##### Tooltips
-The browsable API makes use of the Bootstrap tooltips component. Any element with the `js-tooltip` class and a `title` attribute has that title content displayed in a tooltip on hover after a 1000ms delay.
+The browsable API makes use of the Bootstrap tooltips component. Any element with the `js-tooltip` class and a `title` attribute has that title content displayed in a tooltip on hover after a 1000ms delay.
### Login Template
@@ -107,7 +107,7 @@ The context that's available to the template:
#### Not using base.html
-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.
[cite]: http://en.wikiquote.org/wiki/Alfred_North_Whitehead
[drfreverse]: ../api-guide/reverse.md
diff --git a/docs/topics/browser-enhancements.md b/docs/topics/browser-enhancements.md
index ce07fe95..5a172620 100644
--- a/docs/topics/browser-enhancements.md
+++ b/docs/topics/browser-enhancements.md
@@ -21,7 +21,7 @@ For example, given the following form:
## HTTP header based method overriding
-REST framework also supports method overriding via the semi-standard `X-HTTP-Method-Override` header. This can be useful if you are working with non-form content such as JSON and are working with an older web server and/or hosting provider that doesn't recognise particular HTTP methods such as `PATCH`. For example [Amazon Web Services ELB][aws_elb].
+REST framework also supports method overriding via the semi-standard `X-HTTP-Method-Override` header. This can be useful if you are working with non-form content such as JSON and are working with an older web server and/or hosting provider that doesn't recognise particular HTTP methods such as `PATCH`. For example [Amazon Web Services ELB][aws_elb].
To use it, make a `POST` request, setting the `X-HTTP-Method-Override` header.
@@ -60,7 +60,7 @@ have any control over what is sent in the `Accept` header.
## URL based format suffixes
REST framework can take `?format=json` style URL parameters, which can be a
-useful shortcut for determing which content type should be returned from
+useful shortcut for determining which content type should be returned from
the view.
This is a more concise than using the `accept` override, but it also gives
diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md
index 1d1fe892..123e4a8a 100644
--- a/docs/topics/contributing.md
+++ b/docs/topics/contributing.md
@@ -1,6 +1,6 @@
# Contributing to REST framework
-> The world can only really be changed one piece at a time. The art is picking that piece.
+> The world can only really be changed one piece at a time. The art is picking that piece.
>
> &mdash; [Tim Berners-Lee][cite]
@@ -22,9 +22,9 @@ It's really helpful if you make sure you address issues to the correct channel.
Some tips on good issue reporting:
-* When decribing issues try to phrase your ticket in terms of the *behavior* you think needs changing rather than the *code* you think need changing.
+* When describing issues try to phrase your ticket in terms of the *behavior* you think needs changing rather than the *code* you think need changing.
* Search the issue list first for related items, and make sure you're running the latest version of REST framework before reporting an issue.
-* If reporting a bug, then try to include a pull request with a failing test case. This'll help us quickly identify if there is a valid issue, and make sure that it gets fixed more quickly if there is one.
+* If reporting a bug, then try to include a pull request with a failing test case. This will help us quickly identify if there is a valid issue, and make sure that it gets fixed more quickly if there is one.
@@ -32,6 +32,7 @@ Some tips on good issue reporting:
# Development
+
* git clone & PYTHONPATH
* Pep8
* Recommend editor that runs pep8
diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index 4b39d452..2552af19 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -136,6 +136,8 @@ The following people have helped make REST framework great.
* Andrew Tarzwell - [atarzwell]
* Michal Dvořák - [mikee2185]
* Markus Törnqvist - [mjtorn]
+* Pascal Borreli - [pborreli]
+* Alex Burgel - [aburgel]
Many thanks to everyone who's contributed to the project.
@@ -308,3 +310,5 @@ You can also contact [@_tomchristie][twitter] directly on twitter.
[atarzwell]: https://github.com/atarzwell
[mikee2185]: https://github.com/mikee2185
[mjtorn]: https://github.com/mjtorn
+[pborreli]: https://github.com/pborreli
+[aburgel]: https://github.com/aburgel
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 560dd305..005538ae 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -42,9 +42,32 @@ You can determine your currently installed version using `pip freeze`:
### Master
+* Added `get_url` hook to `HyperlinkedIdentityField`.
+* Serializer field `default` argument may be a callable.
+* `@action` decorator now accepts a `methods` argument.
+* Bugfix: The `lookup_field` option on `HyperlinkedIdentityField` should apply by default to the url field on the serializer.
+* Bugfix: `HyperlinkedIdentityField` should continue to support `pk_url_kwarg`, `slug_url_kwarg`, `slug_field`, in a pending deprecation state.
+* Bugfix: Ensure we always return 404 instead of 500 if a lookup field cannot be converted to the correct lookup type. (Eg non-numeric `AutoInteger` pk lookup)
+
+### 2.3.4
+
+**Date**: 24th May 2013
+
+* Serializer fields now support `label` and `help_text`.
+* Added `UnicodeJSONRenderer`.
+* `OPTIONS` requests now return metadata about fields for `POST` and `PUT` requests.
+* Bugfix: `charset` now properly included in `Content-Type` of responses.
+* Bugfix: Blank choice now added in browsable API on nullable relationships.
+* Bugfix: Many to many relationships with `through` tables are now read-only.
+* Bugfix: Serializer fields now respect model field args such as `max_length`.
+* Bugfix: SlugField now performs slug validation.
+* Bugfix: Lazy-translatable strings now properly serialized.
+* Bugfix: Browsable API now supports bootswatch styles properly.
* Bugfix: HyperlinkedIdentityField now uses `lookup_field` kwarg.
-### 2.3.2
+**Note**: Responses now correctly include an appropriate charset on the `Content-Type` header. For example: `application/json; charset=utf-8`. If you have tests that check the content type of responses, you may need to update these accordingly.
+
+### 2.3.3
**Date**: 16th May 2013
@@ -92,14 +115,14 @@ You can determine your currently installed version using `pip freeze`:
**Date**: 17th April 2013
* Loud failure when view does not return a `Response` or `HttpResponse`.
-* Bugfix: Fix for Django 1.3 compatiblity.
+* Bugfix: Fix for Django 1.3 compatibility.
* Bugfix: Allow overridden `get_object()` to work correctly.
### 2.2.6
**Date**: 4th April 2013
-* OAuth2 authentication no longer requires unneccessary URL parameters in addition to the token.
+* OAuth2 authentication no longer requires unnecessary URL parameters in addition to the token.
* URL hyperlinking in browsable API now handles more cases correctly.
* Long HTTP headers in browsable API are broken in multiple lines when possible.
* Bugfix: Fix regression with DjangoFilterBackend not worthing correctly with single object views.
@@ -211,7 +234,7 @@ The security vulnerabilities only affect APIs which use the `XMLParser` class, b
* Bugfix: Validation errors instead of exceptions when related fields receive incorrect types.
* Bugfix: Handle ObjectDoesNotExist exception when serializing null reverse one-to-one
-**Note**: Prior to 2.1.16, The Decimals would render in JSON using floating point if `simplejson` was installed, but otherwise render using string notation. Now that use of `simplejson` has been deprecated, Decimals will consistently render using string notation. See [#582] for more details.
+**Note**: Prior to 2.1.16, The Decimals would render in JSON using floating point if `simplejson` was installed, but otherwise render using string notation. Now that use of `simplejson` has been deprecated, Decimals will consistently render using string notation. See [#582] for more details.
### 2.1.15
diff --git a/docs/topics/rest-framework-2-announcement.md b/docs/topics/rest-framework-2-announcement.md
index 309548d0..f1060d90 100644
--- a/docs/topics/rest-framework-2-announcement.md
+++ b/docs/topics/rest-framework-2-announcement.md
@@ -2,7 +2,7 @@
What it is, and why you should care.
-> Most people just make the mistake that it should be simple to design simple things. In reality, the effort required to design something is inversely proportional to the simplicity of the result.
+> Most people just make the mistake that it should be simple to design simple things. In reality, the effort required to design something is inversely proportional to the simplicity of the result.
>
> &mdash; [Roy Fielding][cite]
@@ -22,9 +22,9 @@ This article is intended to give you a flavor of what REST framework 2 is, and w
Before we get cracking, let's start with the hard sell, with a few bits of feedback from some early adopters…
-"Django REST framework 2 is beautiful. Some of the API design is worthy of @kennethreitz." - [Kit La Touche][quote1]
+"Django REST framework 2 is beautiful. Some of the API design is worthy of @kennethreitz." - [Kit La Touche][quote1]
-"Since it's pretty much just Django, controlling things like URLs has been a breeze... I think [REST framework 2] has definitely got the right approach here; even simple things like being able to override a function called post to do custom work during rather than having to intimately know what happens during a post make a huge difference to your productivity." - [Ian Strachan][quote2]
+"Since it's pretty much just Django, controlling things like URLs has been a breeze... I think [REST framework 2] has definitely got the right approach here; even simple things like being able to override a function called post to do custom work during rather than having to intimately know what happens during a post make a huge difference to your productivity." - [Ian Strachan][quote2]
"I switched to the 2.0 branch and I don't regret it - fully refactored my code in another &half; day and it's *much* more to my tastes" - [Bruno Desthuilliers][quote3]
@@ -60,7 +60,7 @@ REST framework 2 also allows you to work with both function-based and class-base
## API Design
-Pretty much every aspect of REST framework has been reworked, with the aim of ironing out some of the design flaws of the previous versions. Each of the components of REST framework are cleanly decoupled, and can be used independantly of each-other, and there are no monolithic resource classes, overcomplicated mixin combinations, or opinionated serialization or URL routing decisions.
+Pretty much every aspect of REST framework has been reworked, with the aim of ironing out some of the design flaws of the previous versions. Each of the components of REST framework are cleanly decoupled, and can be used independently of each-other, and there are no monolithic resource classes, overcomplicated mixin combinations, or opinionated serialization or URL routing decisions.
## The Browsable API
@@ -70,7 +70,7 @@ Browsable Web APIs are easier to work with, visualize and debug, and generally m
With REST framework 2, the browsable API gets a snazzy new bootstrap-based theme that looks great and is even nicer to work with.
-There are also some functionality improvments - actions such as as `POST` and `DELETE` will only display if the user has the appropriate permissions.
+There are also some functionality improvements - actions such as as `POST` and `DELETE` will only display if the user has the appropriate permissions.
![Browsable API][image]
diff --git a/docs/topics/rest-hypermedia-hateoas.md b/docs/topics/rest-hypermedia-hateoas.md
index aeba579f..b4899a36 100644
--- a/docs/topics/rest-hypermedia-hateoas.md
+++ b/docs/topics/rest-hypermedia-hateoas.md
@@ -1,6 +1,6 @@
# REST, Hypermedia & HATEOAS
-> You keep using that word "REST". I do not think it means what you think it means.
+> You keep using that word "REST". I do not think it means what you think it means.
>
> &mdash; Mike Amundsen, [REST fest 2012 keynote][cite].
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index ed54a876..bbb9b73c 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -2,13 +2,13 @@
## Introduction
-This tutorial will cover creating a simple pastebin code highlighting Web API. Along the way it will introduce the various components that make up REST framework, and give you a comprehensive understanding of how everything fits together.
+This tutorial will cover creating a simple pastebin code highlighting Web API. Along the way it will introduce the various components that make up REST framework, and give you a comprehensive understanding of how everything fits together.
The tutorial is fairly in-depth, so you should probably get a cookie and a cup of your favorite brew before getting started. If you just want a quick overview, you should head over to the [quickstart] documentation instead.
---
-**Note**: The code for this tutorial is available in the [tomchristie/rest-framework-tutorial][repo] repository on GitHub. The completed implementation is also online as a sandbox version for testing, [available here][sandbox].
+**Note**: The code for this tutorial is available in the [tomchristie/rest-framework-tutorial][repo] repository on GitHub. The completed implementation is also online as a sandbox version for testing, [available here][sandbox].
---
@@ -73,7 +73,7 @@ Okay, we're ready to roll.
## Creating a model to work with
-For the purposes of this tutorial we're going to start by creating a simple `Snippet` model that is used to store code snippets. Go ahead and edit the `snippets` app's `models.py` file. Note: Good programming practices include comments. Although you will find them in our repository version of this tutorial code, we have omitted them here to focus on the code itself.
+For the purposes of this tutorial we're going to start by creating a simple `Snippet` model that is used to store code snippets. Go ahead and edit the `snippets` app's `models.py` file. Note: Good programming practices include comments. Although you will find them in our repository version of this tutorial code, we have omitted them here to focus on the code itself.
from django.db import models
from pygments.lexers import get_all_lexers
@@ -146,6 +146,8 @@ The first thing we need to get started on our Web API is provide a way of serial
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.
+Notice that we can also use various attributes that would typically be used on form fields, such as `widget=widgets.Testarea`. These can be used to control how the serializer should render when displayed as an HTML form. This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial.
+
We can actually also save ourselves some time by using the `ModelSerializer` class, as we'll see later, but for now we'll keep our serializer definition explicit.
## Working with Serializers
@@ -293,7 +295,7 @@ We'll also need a view which corresponds to an individual snippet, and can be us
snippet.delete()
return HttpResponse(status=204)
-Finally we need to wire these views up. Create the `snippets/urls.py` file:
+Finally we need to wire these views up. Create the `snippets/urls.py` file:
from django.conf.urls import patterns, url
diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md
index 260c4d83..30966a10 100644
--- a/docs/tutorial/2-requests-and-responses.md
+++ b/docs/tutorial/2-requests-and-responses.md
@@ -98,7 +98,7 @@ Notice that we're no longer explicitly tying our requests or responses to a give
## Adding optional format suffixes to our URLs
-To take advantage of the fact that our responses are no longer hardwired to a single content type let's add support for format suffixes to our API endpoints. Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as [http://example.com/api/items/4.json][json-url].
+To take advantage of the fact that our responses are no longer hardwired to a single content type let's add support for format suffixes to our API endpoints. Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as [http://example.com/api/items/4.json][json-url].
Start by adding a `format` keyword argument to both of the views, like so.
@@ -158,7 +158,7 @@ Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/sni
### Browsability
-Because the API chooses the content type of the response based on the client request, it will, by default, return an HTML-formatted representation of the resource when that resource is requested by a web browser. This allows for the API to return a fully web-browsable HTML representation.
+Because the API chooses the content type of the response based on the client request, it will, by default, return an HTML-formatted representation of the resource when that resource is requested by a web browser. This allows for the API to return a fully web-browsable HTML representation.
Having a web-browsable API is a huge usability win, and makes developing and using your API much easier. It also dramatically lowers the barrier-to-entry for other developers wanting to inspect and work with your API.
diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md
index 70cf2c54..c1b3d8f2 100644
--- a/docs/tutorial/3-class-based-views.md
+++ b/docs/tutorial/3-class-based-views.md
@@ -102,7 +102,7 @@ Let's take a look at how we can compose our views by using the mixin classes.
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
-We'll take a moment to examine exactly what's happening here. We're building our view using `GenericAPIView`, and adding in `ListModelMixin` and `CreateModelMixin`.
+We'll take a moment to examine exactly what's happening here. We're building our view using `GenericAPIView`, and adding in `ListModelMixin` and `CreateModelMixin`.
The base class provides the core functionality, and the mixin classes provide the `.list()` and `.create()` actions. We're then explicitly binding the `get` and `post` methods to the appropriate actions. Simple enough stuff so far.
diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md
index f6c3efb0..393d879a 100644
--- a/docs/tutorial/4-authentication-and-permissions.md
+++ b/docs/tutorial/4-authentication-and-permissions.md
@@ -17,7 +17,7 @@ Add the following two fields to the model.
owner = models.ForeignKey('auth.User', related_name='snippets')
highlighted = models.TextField()
-We'd also need to make sure that when the model is saved, that we populate the highlighted field, using the `pygments` code higlighting library.
+We'd also need to make sure that when the model is saved, that we populate the highlighted field, using the `pygments` code highlighting library.
We'll need some extra imports:
@@ -94,7 +94,7 @@ On **both** the `SnippetList` and `SnippetDetail` view classes, add the followin
## Updating our serializer
-Now that snippets are associated with the user that created them, let's update our `SnippetSerializer` to reflect that. Add the following field to the serializer definition:
+Now that snippets are associated with the user that created them, let's update our `SnippetSerializer` to reflect that. Add the following field to the serializer definition:
owner = serializers.Field(source='owner.username')
@@ -120,7 +120,7 @@ Then, add the following property to **both** the `SnippetList` and `SnippetDetai
## Adding login to the Browsable API
-If you open a browser and navigate to the browsable API at the moment, you'll find that you're no longer able to create new code snippets. In order to do so we'd need to be able to login as a user.
+If you open a browser and navigate to the browsable API at the moment, you'll find that you're no longer able to create new code snippets. In order to do so we'd need to be able to login as a user.
We can add a login view for use with the browsable API, by editing our URLconf once more.
@@ -137,7 +137,7 @@ And, at the end of the file, add a pattern to include the login and logout views
The `r'^api-auth/'` part of pattern can actually be whatever URL you want to use. The only restriction is that the included urls must use the `'rest_framework'` namespace.
-Now if you open up the browser again and refresh the page you'll see a 'Login' link in the top right of the page. If you log in as one of the users you created earier, you'll be able to create code snippets again.
+Now if you open up the browser again and refresh the page you'll see a 'Login' link in the top right of the page. If you log in as one of the users you created earlier, you'll be able to create code snippets again.
Once you've created a few code snippets, navigate to the '/users/' endpoint, and notice that the representation includes a list of the snippet pks that are associated with each user, in each user's 'snippets' field.
diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
index cb2e092c..2e013a94 100644
--- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md
+++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
@@ -29,7 +29,7 @@ Unlike all our other API endpoints, we don't want to use JSON, but instead just
The other thing we need to consider when creating the code highlight view is that there's no existing concrete generic view that we can use. We're not returning an object instance, but instead a property of an object instance.
-Instead of using a concrete generic view, we'll use the base class for representing instances, and create our own `.get()` method. In your snippets.views add:
+Instead of using a concrete generic view, we'll use the base class for representing instances, and create our own `.get()` method. In your snippets.views add:
from rest_framework import renderers
from rest_framework.response import Response
diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
index c41cb63f..f15e75c0 100644
--- a/docs/tutorial/quickstart.md
+++ b/docs/tutorial/quickstart.md
@@ -10,7 +10,7 @@ Create a new Django project named `tutorial`, then start a new app called `quick
django-admin.py startproject tutorial
cd tutorial
- # Create a virtualenv to isolate our package dependancies locally
+ # Create a virtualenv to isolate our package dependencies locally
virtualenv env
source env/bin/activate
@@ -36,7 +36,7 @@ Next you'll need to get a database set up and synced. If you just want to use S
The run `syncdb` like so:
- python manange.py syncdb
+ python manage.py syncdb
Once you've set up a database and got everything synced and ready to go, open up the app's directory and we'll get coding...
@@ -91,7 +91,7 @@ We can easily break these down into individual views if we need to, but using vi
## URLs
-Okay, now let's wire up the API URLs. On to `quickstart/urls.py`...
+Okay, now let's wire up the API URLs. On to `tutorial/urls.py`...
from django.conf.urls import patterns, url, include
from rest_framework import routers
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index 0b1e67fb..59046733 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '2.3.3'
+__version__ = '2.3.4'
VERSION = __version__ # synonym
diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py
index 81e585e1..c69756a4 100644
--- a/rest_framework/decorators.py
+++ b/rest_framework/decorators.py
@@ -1,5 +1,5 @@
"""
-The most imporant decorator in this module is `@api_view`, which is used
+The most important decorator in this module is `@api_view`, which is used
for writing function-based views with REST framework.
There are also various decorators for setting the API policies on function
@@ -40,7 +40,7 @@ def api_view(http_method_names):
# api_view applied with eg. string instead of list of strings
assert isinstance(http_method_names, (list, tuple)), \
- '@api_view expected a list of strings, recieved %s' % type(http_method_names).__name__
+ '@api_view expected a list of strings, received %s' % type(http_method_names).__name__
allowed_methods = set(http_method_names) | set(('options',))
WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods]
@@ -112,18 +112,18 @@ def link(**kwargs):
Used to mark a method on a ViewSet that should be routed for GET requests.
"""
def decorator(func):
- func.bind_to_method = 'get'
+ func.bind_to_methods = ['get']
func.kwargs = kwargs
return func
return decorator
-def action(**kwargs):
+def action(methods=['post'], **kwargs):
"""
Used to mark a method on a ViewSet that should be routed for POST requests.
"""
def decorator(func):
- func.bind_to_method = 'post'
+ func.bind_to_methods = methods
func.kwargs = kwargs
return func
return decorator
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index d772c400..535aa2ac 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -11,7 +11,6 @@ from decimal import Decimal, DecimalException
import inspect
import re
import warnings
-
from django.core import validators
from django.core.exceptions import ValidationError
from django.conf import settings
@@ -21,9 +20,9 @@ from django.forms import widgets
from django.utils.encoding import is_protected_type
from django.utils.translation import ugettext_lazy as _
from django.utils.datastructures import SortedDict
-
from rest_framework import ISO_8601
-from rest_framework.compat import timezone, parse_date, parse_datetime, parse_time
+from rest_framework.compat import (timezone, parse_date, parse_datetime,
+ parse_time)
from rest_framework.compat import BytesIO
from rest_framework.compat import six
from rest_framework.compat import smart_text, force_text, is_non_str_iterable
@@ -45,6 +44,7 @@ def is_simple_callable(obj):
len_defaults = len(defaults) if defaults else 0
return len_args <= len_defaults
+
def get_component(obj, attr_name):
"""
Given an object, and an attribute name,
@@ -61,7 +61,8 @@ def get_component(obj, attr_name):
def readable_datetime_formats(formats):
- format = ', '.join(formats).replace(ISO_8601, 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]')
+ format = ', '.join(formats).replace(ISO_8601,
+ 'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]')
return humanize_strptime(format)
@@ -108,6 +109,7 @@ class Field(object):
partial = False
use_files = False
form_field_class = forms.CharField
+ type_label = 'field'
def __init__(self, source=None, label=None, help_text=None):
self.parent = None
@@ -193,6 +195,18 @@ class Field(object):
return {'type': self.type_name}
return {}
+ def metadata(self):
+ metadata = SortedDict()
+ metadata['type'] = self.type_label
+ metadata['required'] = getattr(self, 'required', False)
+ optional_attrs = ['read_only', 'label', 'help_text',
+ 'min_length', 'max_length']
+ for attr in optional_attrs:
+ value = getattr(self, attr, None)
+ if value is not None and value != '':
+ metadata[attr] = force_text(value, strings_only=True)
+ return metadata
+
class WritableField(Field):
"""
@@ -281,7 +295,10 @@ class WritableField(Field):
except KeyError:
if self.default is not None and not self.partial:
# Note: partial updates shouldn't set defaults
- native = self.default
+ if is_simple_callable(self.default):
+ native = self.default()
+ else:
+ native = self.default
else:
if self.required:
raise ValidationError(self.error_messages['required'])
@@ -348,6 +365,7 @@ class ModelField(WritableField):
class BooleanField(WritableField):
type_name = 'BooleanField'
+ type_label = 'boolean'
form_field_class = forms.BooleanField
widget = widgets.CheckboxInput
default_error_messages = {
@@ -370,6 +388,7 @@ class BooleanField(WritableField):
class CharField(WritableField):
type_name = 'CharField'
+ type_label = 'string'
form_field_class = forms.CharField
def __init__(self, max_length=None, min_length=None, *args, **kwargs):
@@ -388,6 +407,7 @@ class CharField(WritableField):
class URLField(CharField):
type_name = 'URLField'
+ type_label = 'url'
def __init__(self, **kwargs):
kwargs['validators'] = [validators.URLValidator()]
@@ -396,14 +416,15 @@ class URLField(CharField):
class SlugField(CharField):
type_name = 'SlugField'
+ type_label = 'slug'
form_field_class = forms.SlugField
-
+
default_error_messages = {
'invalid': _("Enter a valid 'slug' consisting of letters, numbers,"
" underscores or hyphens."),
}
default_validators = [validators.validate_slug]
-
+
def __init__(self, *args, **kwargs):
super(SlugField, self).__init__(*args, **kwargs)
@@ -413,10 +434,11 @@ class SlugField(CharField):
#result.widget = copy.deepcopy(self.widget, memo)
result.validators = self.validators[:]
return result
-
-
+
+
class ChoiceField(WritableField):
type_name = 'ChoiceField'
+ type_label = 'multiple choice'
form_field_class = forms.ChoiceField
widget = widgets.Select
default_error_messages = {
@@ -467,6 +489,7 @@ class ChoiceField(WritableField):
class EmailField(CharField):
type_name = 'EmailField'
+ type_label = 'email'
form_field_class = forms.EmailField
default_error_messages = {
@@ -490,6 +513,7 @@ class EmailField(CharField):
class RegexField(CharField):
type_name = 'RegexField'
+ type_label = 'regex'
form_field_class = forms.RegexField
def __init__(self, regex, max_length=None, min_length=None, *args, **kwargs):
@@ -519,6 +543,7 @@ class RegexField(CharField):
class DateField(WritableField):
type_name = 'DateField'
+ type_label = 'date'
widget = widgets.DateInput
form_field_class = forms.DateField
@@ -582,6 +607,7 @@ class DateField(WritableField):
class DateTimeField(WritableField):
type_name = 'DateTimeField'
+ type_label = 'datetime'
widget = widgets.DateTimeInput
form_field_class = forms.DateTimeField
@@ -651,6 +677,7 @@ class DateTimeField(WritableField):
class TimeField(WritableField):
type_name = 'TimeField'
+ type_label = 'time'
widget = widgets.TimeInput
form_field_class = forms.TimeField
@@ -707,6 +734,7 @@ class TimeField(WritableField):
class IntegerField(WritableField):
type_name = 'IntegerField'
+ type_label = 'integer'
form_field_class = forms.IntegerField
default_error_messages = {
@@ -737,6 +765,7 @@ class IntegerField(WritableField):
class FloatField(WritableField):
type_name = 'FloatField'
+ type_label = 'float'
form_field_class = forms.FloatField
default_error_messages = {
@@ -756,6 +785,7 @@ class FloatField(WritableField):
class DecimalField(WritableField):
type_name = 'DecimalField'
+ type_label = 'decimal'
form_field_class = forms.DecimalField
default_error_messages = {
@@ -826,6 +856,7 @@ class DecimalField(WritableField):
class FileField(WritableField):
use_files = True
type_name = 'FileField'
+ type_label = 'file upload'
form_field_class = forms.FileField
widget = widgets.FileInput
@@ -869,6 +900,8 @@ class FileField(WritableField):
class ImageField(FileField):
use_files = True
+ type_name = 'ImageField'
+ type_label = 'image upload'
form_field_class = forms.ImageField
default_error_messages = {
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 05ec93d3..9ccc7898 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -3,17 +3,28 @@ Generic views that provide commonly needed behaviour.
"""
from __future__ import unicode_literals
-from django.core.exceptions import ImproperlyConfigured
+from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.core.paginator import Paginator, InvalidPage
from django.http import Http404
-from django.shortcuts import get_object_or_404
+from django.shortcuts import get_object_or_404 as _get_object_or_404
from django.utils.translation import ugettext as _
-from rest_framework import views, mixins
-from rest_framework.exceptions import ConfigurationError
+from rest_framework import views, mixins, exceptions
+from rest_framework.request import clone_request
from rest_framework.settings import api_settings
import warnings
+def get_object_or_404(queryset, **filter_kwargs):
+ """
+ Same as Django's standard shortcut, but make sure to raise 404
+ if the filter_kwargs don't match the required types.
+ """
+ try:
+ return _get_object_or_404(queryset, **filter_kwargs)
+ except (TypeError, ValueError):
+ raise Http404
+
+
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
@@ -274,7 +285,7 @@ class GenericAPIView(views.APIView):
)
filter_kwargs = {self.slug_field: slug}
else:
- raise ConfigurationError(
+ raise exceptions.ConfigurationError(
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
@@ -310,6 +321,41 @@ class GenericAPIView(views.APIView):
"""
pass
+ def metadata(self, request):
+ """
+ Return a dictionary of metadata about the view.
+ Used to return responses for OPTIONS requests.
+
+ We override the default behavior, and add some extra information
+ about the required request body for POST and PUT operations.
+ """
+ ret = super(GenericAPIView, self).metadata(request)
+
+ actions = {}
+ for method in ('PUT', 'POST'):
+ if method not in self.allowed_methods:
+ continue
+
+ cloned_request = clone_request(request, method)
+ try:
+ # Test global permissions
+ self.check_permissions(cloned_request)
+ # Test object permissions
+ if method == 'PUT':
+ self.get_object()
+ except (exceptions.APIException, PermissionDenied, Http404):
+ pass
+ else:
+ # If user has appropriate permissions for the view, include
+ # appropriate metadata about the fields that should be supplied.
+ serializer = self.get_serializer()
+ actions[method] = serializer.metadata()
+
+ if actions:
+ ret['actions'] = actions
+
+ return ret
+
##########################################################
### Concrete view classes that provide method handlers ###
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index c4271e33..e3675b51 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -72,7 +72,6 @@ class RelatedField(WritableField):
else: # Reverse
self.queryset = manager.field.rel.to._default_manager.all()
except Exception:
- raise
msg = ('Serializer related fields must include a `queryset`' +
' argument or set `read_only=True')
raise Exception(msg)
@@ -488,13 +487,15 @@ class HyperlinkedIdentityField(Field):
slug_url_kwarg = None # Defaults to same as `slug_field` unless overridden
def __init__(self, *args, **kwargs):
- # TODO: Make view_name mandatory, and have the
- # HyperlinkedModelSerializer set it on-the-fly
- self.view_name = kwargs.pop('view_name', None)
- # Optionally the format of the target hyperlink may be specified
- self.format = kwargs.pop('format', None)
+ try:
+ self.view_name = kwargs.pop('view_name')
+ except KeyError:
+ msg = "HyperlinkedIdentityField requires 'view_name' argument"
+ raise ValueError(msg)
- self.lookup_field = kwargs.pop('lookup_field', self.lookup_field)
+ self.format = kwargs.pop('format', None)
+ lookup_field = kwargs.pop('lookup_field', None)
+ self.lookup_field = lookup_field or self.lookup_field
# These are pending deprecation
if 'pk_url_kwarg' in kwargs:
@@ -517,9 +518,7 @@ class HyperlinkedIdentityField(Field):
def field_to_native(self, obj, field_name):
request = self.context.get('request', None)
format = self.context.get('format', None)
- view_name = self.view_name or self.parent.opts.view_name
- lookup_field = getattr(obj, self.lookup_field)
- kwargs = {self.lookup_field: lookup_field}
+ view_name = self.view_name
if request is None:
warnings.warn("Using `HyperlinkedIdentityField` without including the "
@@ -539,29 +538,51 @@ class HyperlinkedIdentityField(Field):
if format and self.format and self.format != format:
format = self.format
+ # Return the hyperlink, or error if incorrectly configured.
try:
- return reverse(view_name, kwargs=kwargs, request=request, format=format)
+ return self.get_url(obj, view_name, request, format)
except NoReverseMatch:
- pass
-
- slug = getattr(obj, self.slug_field, None)
+ msg = (
+ 'Could not resolve URL for hyperlinked relationship using '
+ 'view name "%s". You may have failed to include the related '
+ 'model in your API, or incorrectly configured the '
+ '`lookup_field` attribute on this field.'
+ )
+ raise Exception(msg % view_name)
- if not slug:
- raise Exception('Could not resolve URL for field using view name "%s"' % view_name)
+ def get_url(self, obj, view_name, request, format):
+ """
+ Given an object, return the URL that hyperlinks to the object.
- kwargs = {self.slug_url_kwarg: slug}
+ May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
+ attributes are not configured to correctly match the URL conf.
+ """
+ lookup_field = getattr(obj, self.lookup_field)
+ kwargs = {self.lookup_field: lookup_field}
try:
return reverse(view_name, kwargs=kwargs, request=request, format=format)
except NoReverseMatch:
pass
- kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug}
- try:
- return reverse(view_name, kwargs=kwargs, request=request, format=format)
- except NoReverseMatch:
- pass
+ if self.pk_url_kwarg != 'pk':
+ # Only try pk lookup if it has been explicitly set.
+ # Otherwise, the default `lookup_field = 'pk'` has us covered.
+ kwargs = {self.pk_url_kwarg: obj.pk}
+ try:
+ return reverse(view_name, kwargs=kwargs, request=request, format=format)
+ except NoReverseMatch:
+ pass
+
+ slug = getattr(obj, self.slug_field, None)
+ if slug:
+ # Only use slug lookup if a slug field exists on the model
+ kwargs = {self.slug_url_kwarg: slug}
+ try:
+ return reverse(view_name, kwargs=kwargs, request=request, format=format)
+ except NoReverseMatch:
+ pass
- raise Exception('Could not resolve URL for field using view name "%s"' % view_name)
+ raise NoReverseMatch()
### Old-style many classes for backwards compat
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index b4fa55bd..b2fe43ea 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -43,18 +43,21 @@ class BaseRenderer(object):
class JSONRenderer(BaseRenderer):
"""
- Renderer which serializes to json.
+ Renderer which serializes to JSON.
+ Applies JSON's backslash-u character escaping for non-ascii characters.
"""
media_type = 'application/json'
format = 'json'
encoder_class = encoders.JSONEncoder
ensure_ascii = True
- charset = 'iso-8859-1'
+ charset = 'utf-8'
+ # Note that JSON encodings must be utf-8, utf-16 or utf-32.
+ # See: http://www.ietf.org/rfc/rfc4627.txt
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
- Render `obj` into json.
+ Render `data` into JSON.
"""
if data is None:
return ''
@@ -77,7 +80,11 @@ class JSONRenderer(BaseRenderer):
ret = json.dumps(data, cls=self.encoder_class,
indent=indent, ensure_ascii=self.ensure_ascii)
- if not self.ensure_ascii:
+ # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True,
+ # but if ensure_ascii=False, the return type is underspecified,
+ # and may (or may not) be unicode.
+ # On python 3.x json.dumps() returns unicode strings.
+ if isinstance(ret, six.text_type):
return bytes(ret.encode(self.charset))
return ret
@@ -85,6 +92,10 @@ class JSONRenderer(BaseRenderer):
class UnicodeJSONRenderer(JSONRenderer):
ensure_ascii = False
charset = 'utf-8'
+ """
+ Renderer which serializes to JSON.
+ Does *not* apply JSON's character escaping for non-ascii characters.
+ """
class JSONPRenderer(JSONRenderer):
@@ -117,7 +128,7 @@ class JSONPRenderer(JSONRenderer):
callback = self.get_callback(renderer_context)
json = super(JSONPRenderer, self).render(data, accepted_media_type,
renderer_context)
- return "%s(%s);" % (callback, json)
+ return callback.encode(self.charset) + b'(' + json + b');'
class XMLRenderer(BaseRenderer):
@@ -138,7 +149,7 @@ class XMLRenderer(BaseRenderer):
stream = StringIO()
- xml = SimplerXMLGenerator(stream, "utf-8")
+ xml = SimplerXMLGenerator(stream, self.charset)
xml.startDocument()
xml.startElement("root", {})
@@ -188,7 +199,7 @@ class YAMLRenderer(BaseRenderer):
if data is None:
return ''
- return yaml.dump(data, stream=None, Dumper=self.encoder)
+ return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder)
class TemplateHTMLRenderer(BaseRenderer):
@@ -496,10 +507,7 @@ class BrowsableAPIRenderer(BaseRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
- Renders *obj* using the :attr:`template` set on the class.
-
- The context used in the template contains all the information
- needed to self-document the response to this request.
+ Render the HTML for the browsable API representation.
"""
accepted_media_type = accepted_media_type or ''
renderer_context = renderer_context or {}
diff --git a/rest_framework/response.py b/rest_framework/response.py
index 110ccb13..3ee52ae0 100644
--- a/rest_framework/response.py
+++ b/rest_framework/response.py
@@ -1,5 +1,5 @@
"""
-The Response class in REST framework is similiar to HTTPResponse, except that
+The Response class in REST framework is similar to HTTPResponse, except that
it is initialized with unrendered data, instead of a pre-rendered string.
The appropriate renderer is called during Django's template response rendering.
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index dba104c3..6c5fd004 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -131,20 +131,20 @@ class SimpleRouter(BaseRouter):
dynamic_routes = []
for methodname in dir(viewset):
attr = getattr(viewset, methodname)
- httpmethod = getattr(attr, 'bind_to_method', None)
- if httpmethod:
- dynamic_routes.append((httpmethod, methodname))
+ httpmethods = getattr(attr, 'bind_to_methods', None)
+ if httpmethods:
+ dynamic_routes.append((httpmethods, methodname))
ret = []
for route in self.routes:
if route.mapping == {'{httpmethod}': '{methodname}'}:
# Dynamic routes (@link or @action decorator)
- for httpmethod, methodname in dynamic_routes:
+ for httpmethods, methodname in dynamic_routes:
initkwargs = route.initkwargs.copy()
initkwargs.update(getattr(viewset, methodname).kwargs)
ret.append(Route(
url=replace_methodname(route.url, methodname),
- mapping={httpmethod: methodname},
+ mapping=dict((httpmethod, methodname) for httpmethod in httpmethods),
name=replace_methodname(route.name, methodname),
initkwargs=initkwargs,
))
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 31f261e1..a4969f60 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -25,7 +25,7 @@ from rest_framework.compat import get_concrete_model, six
#
# example_field = serializers.CharField(...)
#
-# This helps keep the seperation between model fields, form fields, and
+# This helps keep the separation between model fields, form fields, and
# serializer fields more explicit.
from rest_framework.relations import *
@@ -58,7 +58,7 @@ class DictWithMetadata(dict):
def __getstate__(self):
"""
Used by pickle (e.g., caching).
- Overriden to remove the metadata from the dict, since it shouldn't be
+ Overridden to remove the metadata from the dict, since it shouldn't be
pickled and may in some instances be unpickleable.
"""
return dict(self)
@@ -521,6 +521,17 @@ class BaseSerializer(WritableField):
return self.object
+ def metadata(self):
+ """
+ Return a dictionary of metadata about the fields on the serializer.
+ Useful for things like responding to OPTIONS requests, or generating
+ API schemas for auto-documentation.
+ """
+ return SortedDict(
+ [(field_name, field.metadata())
+ for field_name, field in six.iteritems(self.fields)]
+ )
+
class Serializer(six.with_metaclass(SerializerMetaclass, BaseSerializer)):
pass
@@ -892,13 +903,24 @@ class HyperlinkedModelSerializer(ModelSerializer):
_default_view_name = '%(model_name)s-detail'
_hyperlink_field_class = HyperlinkedRelatedField
- url = HyperlinkedIdentityField()
+ # Just a placeholder to ensure 'url' is the first field
+ # The field itself is actually created on initialization,
+ # when the view_name and lookup_field arguments are available.
+ url = Field()
def __init__(self, *args, **kwargs):
super(HyperlinkedModelSerializer, self).__init__(*args, **kwargs)
+
if self.opts.view_name is None:
self.opts.view_name = self._get_default_view_name(self.opts.model)
+ url_field = HyperlinkedIdentityField(
+ view_name=self.opts.view_name,
+ lookup_field=self.opts.lookup_field
+ )
+ url_field.initialize(self, 'url')
+ self.fields['url'] = url_field
+
def _get_default_view_name(self, model):
"""
Return the view name to use if 'view_name' is not specified in 'Meta'
diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py
index c86b6456..e9c1cdd5 100644
--- a/rest_framework/templatetags/rest_framework.py
+++ b/rest_framework/templatetags/rest_framework.py
@@ -15,7 +15,7 @@ register = template.Library()
# When 1.3 becomes unsupported by REST framework, we can instead start to
# use the {% load staticfiles %} tag, remove the following code,
-# and add a dependancy that `django.contrib.staticfiles` must be installed.
+# and add a dependency that `django.contrib.staticfiles` must be installed.
# Note: We can't put this into the `compat` module because the compat import
# from rest_framework.compat import ...
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
index abf50a2d..e2d4eacd 100644
--- a/rest_framework/tests/models.py
+++ b/rest_framework/tests/models.py
@@ -162,8 +162,8 @@ class NullableOneToOneSource(RESTFrameworkModel):
target = models.OneToOneField(OneToOneTarget, null=True, blank=True,
related_name='nullable_source')
+
# Serializer used to test BasicModel
class BasicModelSerializer(serializers.ModelSerializer):
class Meta:
model = BasicModel
-
diff --git a/rest_framework/tests/routers.py b/rest_framework/tests/routers.py
deleted file mode 100644
index 4e4765cb..00000000
--- a/rest_framework/tests/routers.py
+++ /dev/null
@@ -1,55 +0,0 @@
-from __future__ import unicode_literals
-from django.test import TestCase
-from django.test.client import RequestFactory
-from rest_framework import status
-from rest_framework.response import Response
-from rest_framework import viewsets
-from rest_framework.decorators import link, action
-from rest_framework.routers import SimpleRouter
-import copy
-
-factory = RequestFactory()
-
-
-class BasicViewSet(viewsets.ViewSet):
- def list(self, request, *args, **kwargs):
- return Response({'method': 'list'})
-
- @action()
- def action1(self, request, *args, **kwargs):
- return Response({'method': 'action1'})
-
- @action()
- def action2(self, request, *args, **kwargs):
- return Response({'method': 'action2'})
-
- @link()
- def link1(self, request, *args, **kwargs):
- return Response({'method': 'link1'})
-
- @link()
- def link2(self, request, *args, **kwargs):
- return Response({'method': 'link2'})
-
-
-class TestSimpleRouter(TestCase):
- def setUp(self):
- self.router = SimpleRouter()
-
- def test_link_and_action_decorator(self):
- routes = self.router.get_routes(BasicViewSet)
- decorator_routes = routes[2:]
- # Make sure all these endpoints exist and none have been clobbered
- for i, endpoint in enumerate(['action1', 'action2', 'link1', 'link2']):
- route = decorator_routes[i]
- # check url listing
- self.assertEqual(route.url,
- '^{{prefix}}/{{lookup}}/{0}/$'.format(endpoint))
- # check method to function mapping
- if endpoint.startswith('action'):
- method_map = 'post'
- else:
- method_map = 'get'
- self.assertEqual(route.mapping[method_map], endpoint)
-
-
diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/test_authentication.py
index 8e6d3e51..05e9fbc3 100644
--- a/rest_framework/tests/authentication.py
+++ b/rest_framework/tests/test_authentication.py
@@ -48,7 +48,7 @@ urlpatterns = patterns('',
(r'^token/$', MockView.as_view(authentication_classes=[TokenAuthentication])),
(r'^auth-token/$', 'rest_framework.authtoken.views.obtain_auth_token'),
(r'^oauth/$', MockView.as_view(authentication_classes=[OAuthAuthentication])),
- (r'^oauth-with-scope/$', MockView.as_view(authentication_classes=[OAuthAuthentication],
+ (r'^oauth-with-scope/$', MockView.as_view(authentication_classes=[OAuthAuthentication],
permission_classes=[permissions.TokenHasReadWriteScope]))
)
@@ -56,14 +56,14 @@ if oauth2_provider is not None:
urlpatterns += patterns('',
url(r'^oauth2/', include('provider.oauth2.urls', namespace='oauth2')),
url(r'^oauth2-test/$', MockView.as_view(authentication_classes=[OAuth2Authentication])),
- url(r'^oauth2-with-scope-test/$', MockView.as_view(authentication_classes=[OAuth2Authentication],
+ url(r'^oauth2-with-scope-test/$', MockView.as_view(authentication_classes=[OAuth2Authentication],
permission_classes=[permissions.TokenHasReadWriteScope])),
)
class BasicAuthTests(TestCase):
"""Basic authentication"""
- urls = 'rest_framework.tests.authentication'
+ urls = 'rest_framework.tests.test_authentication'
def setUp(self):
self.csrf_client = Client(enforce_csrf_checks=True)
@@ -102,7 +102,7 @@ class BasicAuthTests(TestCase):
class SessionAuthTests(TestCase):
"""User session authentication"""
- urls = 'rest_framework.tests.authentication'
+ urls = 'rest_framework.tests.test_authentication'
def setUp(self):
self.csrf_client = Client(enforce_csrf_checks=True)
@@ -149,7 +149,7 @@ class SessionAuthTests(TestCase):
class TokenAuthTests(TestCase):
"""Token authentication"""
- urls = 'rest_framework.tests.authentication'
+ urls = 'rest_framework.tests.test_authentication'
def setUp(self):
self.csrf_client = Client(enforce_csrf_checks=True)
@@ -243,7 +243,7 @@ class IncorrectCredentialsTests(TestCase):
class OAuthTests(TestCase):
"""OAuth 1.0a authentication"""
- urls = 'rest_framework.tests.authentication'
+ urls = 'rest_framework.tests.test_authentication'
def setUp(self):
# these imports are here because oauth is optional and hiding them in try..except block or compat
@@ -429,7 +429,7 @@ class OAuthTests(TestCase):
class OAuth2Tests(TestCase):
"""OAuth 2.0 authentication"""
- urls = 'rest_framework.tests.authentication'
+ urls = 'rest_framework.tests.test_authentication'
def setUp(self):
self.csrf_client = Client(enforce_csrf_checks=True)
diff --git a/rest_framework/tests/breadcrumbs.py b/rest_framework/tests/test_breadcrumbs.py
index d9ed647e..41ddf2ce 100644
--- a/rest_framework/tests/breadcrumbs.py
+++ b/rest_framework/tests/test_breadcrumbs.py
@@ -36,7 +36,7 @@ urlpatterns = patterns('',
class BreadcrumbTests(TestCase):
"""Tests the breadcrumb functionality used by the HTML renderer."""
- urls = 'rest_framework.tests.breadcrumbs'
+ urls = 'rest_framework.tests.test_breadcrumbs'
def test_root_breadcrumbs(self):
url = '/'
diff --git a/rest_framework/tests/decorators.py b/rest_framework/tests/test_decorators.py
index 1016fed3..1016fed3 100644
--- a/rest_framework/tests/decorators.py
+++ b/rest_framework/tests/test_decorators.py
diff --git a/rest_framework/tests/description.py b/rest_framework/tests/test_description.py
index 52c1a34c..52c1a34c 100644
--- a/rest_framework/tests/description.py
+++ b/rest_framework/tests/test_description.py
diff --git a/rest_framework/tests/fields.py b/rest_framework/tests/test_fields.py
index a3104206..de371001 100644
--- a/rest_framework/tests/fields.py
+++ b/rest_framework/tests/test_fields.py
@@ -2,14 +2,15 @@
General serializer field tests.
"""
from __future__ import unicode_literals
-from django.utils.datastructures import SortedDict
+
import datetime
from decimal import Decimal
+from uuid import uuid4
+from django.core import validators
from django.db import models
from django.test import TestCase
-from django.core import validators
+from django.utils.datastructures import SortedDict
from rest_framework import serializers
-from rest_framework.serializers import Serializer
from rest_framework.tests.models import RESTFrameworkModel
@@ -587,7 +588,7 @@ class DecimalFieldTest(TestCase):
"""
Make sure the serializer works correctly
"""
- class DecimalSerializer(Serializer):
+ class DecimalSerializer(serializers.Serializer):
decimal_field = serializers.DecimalField(max_value=9010,
min_value=9000,
max_digits=6,
@@ -605,7 +606,7 @@ class DecimalFieldTest(TestCase):
"""
Make sure max_value violations raises ValidationError
"""
- class DecimalSerializer(Serializer):
+ class DecimalSerializer(serializers.Serializer):
decimal_field = serializers.DecimalField(max_value=100)
s = DecimalSerializer(data={'decimal_field': '123'})
@@ -617,7 +618,7 @@ class DecimalFieldTest(TestCase):
"""
Make sure min_value violations raises ValidationError
"""
- class DecimalSerializer(Serializer):
+ class DecimalSerializer(serializers.Serializer):
decimal_field = serializers.DecimalField(min_value=100)
s = DecimalSerializer(data={'decimal_field': '99'})
@@ -629,7 +630,7 @@ class DecimalFieldTest(TestCase):
"""
Make sure max_digits violations raises ValidationError
"""
- class DecimalSerializer(Serializer):
+ class DecimalSerializer(serializers.Serializer):
decimal_field = serializers.DecimalField(max_digits=5)
s = DecimalSerializer(data={'decimal_field': '123.456'})
@@ -641,7 +642,7 @@ class DecimalFieldTest(TestCase):
"""
Make sure max_decimal_places violations raises ValidationError
"""
- class DecimalSerializer(Serializer):
+ class DecimalSerializer(serializers.Serializer):
decimal_field = serializers.DecimalField(decimal_places=3)
s = DecimalSerializer(data={'decimal_field': '123.4567'})
@@ -653,7 +654,7 @@ class DecimalFieldTest(TestCase):
"""
Make sure max_whole_digits violations raises ValidationError
"""
- class DecimalSerializer(Serializer):
+ class DecimalSerializer(serializers.Serializer):
decimal_field = serializers.DecimalField(max_digits=4, decimal_places=3)
s = DecimalSerializer(data={'decimal_field': '12345.6'})
@@ -760,14 +761,16 @@ class SlugFieldTests(TestCase):
def test_given_serializer_value(self):
class SlugFieldSerializer(serializers.ModelSerializer):
- slug_field = serializers.SlugField(source='slug_field', max_length=20, required=False)
+ slug_field = serializers.SlugField(source='slug_field',
+ max_length=20, required=False)
class Meta:
model = self.SlugFieldModel
serializer = SlugFieldSerializer(data={})
self.assertEqual(serializer.is_valid(), True)
- self.assertEqual(getattr(serializer.fields['slug_field'], 'max_length'), 20)
+ self.assertEqual(getattr(serializer.fields['slug_field'],
+ 'max_length'), 20)
def test_invalid_slug(self):
"""
@@ -775,12 +778,12 @@ class SlugFieldTests(TestCase):
"""
class SlugFieldSerializer(serializers.ModelSerializer):
slug_field = serializers.SlugField(source='slug_field', max_length=20, required=True)
-
+
class Meta:
model = self.SlugFieldModel
-
+
s = SlugFieldSerializer(data={'slug_field': 'a b'})
-
+
self.assertEqual(s.is_valid(), False)
self.assertEqual(s.errors, {'slug_field': ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]})
@@ -803,7 +806,8 @@ class URLFieldTests(TestCase):
serializer = URLFieldSerializer(data={})
self.assertEqual(serializer.is_valid(), True)
- self.assertEqual(getattr(serializer.fields['url_field'], 'max_length'), 200)
+ self.assertEqual(getattr(serializer.fields['url_field'],
+ 'max_length'), 200)
def test_given_model_value(self):
class URLFieldSerializer(serializers.ModelSerializer):
@@ -812,15 +816,53 @@ class URLFieldTests(TestCase):
serializer = URLFieldSerializer(data={})
self.assertEqual(serializer.is_valid(), True)
- self.assertEqual(getattr(serializer.fields['url_field'], 'max_length'), 128)
+ self.assertEqual(getattr(serializer.fields['url_field'],
+ 'max_length'), 128)
def test_given_serializer_value(self):
class URLFieldSerializer(serializers.ModelSerializer):
- url_field = serializers.URLField(source='url_field', max_length=20, required=False)
+ url_field = serializers.URLField(source='url_field',
+ max_length=20, required=False)
class Meta:
model = self.URLFieldWithGivenMaxLengthModel
serializer = URLFieldSerializer(data={})
self.assertEqual(serializer.is_valid(), True)
- self.assertEqual(getattr(serializer.fields['url_field'], 'max_length'), 20)
+ self.assertEqual(getattr(serializer.fields['url_field'],
+ 'max_length'), 20)
+
+
+class FieldMetadata(TestCase):
+ def setUp(self):
+ self.required_field = serializers.Field()
+ self.required_field.label = uuid4().hex
+ self.required_field.required = True
+
+ self.optional_field = serializers.Field()
+ self.optional_field.label = uuid4().hex
+ self.optional_field.required = False
+
+ def test_required(self):
+ self.assertEqual(self.required_field.metadata()['required'], True)
+
+ def test_optional(self):
+ self.assertEqual(self.optional_field.metadata()['required'], False)
+
+ def test_label(self):
+ for field in (self.required_field, self.optional_field):
+ self.assertEqual(field.metadata()['label'], field.label)
+
+
+class FieldCallableDefault(TestCase):
+ def setUp(self):
+ self.simple_callable = lambda: 'foo bar'
+
+ def test_default_can_be_simple_callable(self):
+ """
+ Ensure that the 'default' argument can also be a simple callable.
+ """
+ field = serializers.WritableField(default=self.simple_callable)
+ into = {}
+ field.field_from_native({}, {}, 'field', into)
+ self.assertEquals(into, {'field': 'foo bar'})
diff --git a/rest_framework/tests/files.py b/rest_framework/tests/test_files.py
index 487046ac..487046ac 100644
--- a/rest_framework/tests/files.py
+++ b/rest_framework/tests/test_files.py
diff --git a/rest_framework/tests/filters.py b/rest_framework/tests/test_filters.py
index 8ae6d530..aaed6247 100644
--- a/rest_framework/tests/filters.py
+++ b/rest_framework/tests/test_filters.py
@@ -243,7 +243,7 @@ class IntegrationTestDetailFiltering(CommonFilteringTestCase):
"""
Integration tests for filtered detail views.
"""
- urls = 'rest_framework.tests.filters'
+ urls = 'rest_framework.tests.test_filters'
def _get_url(self, item):
return reverse('detail-view', kwargs=dict(pk=item.pk))
diff --git a/rest_framework/tests/genericrelations.py b/rest_framework/tests/test_genericrelations.py
index c38bfb9f..c38bfb9f 100644
--- a/rest_framework/tests/genericrelations.py
+++ b/rest_framework/tests/test_genericrelations.py
diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/test_generics.py
index 15d87e86..37734195 100644
--- a/rest_framework/tests/generics.py
+++ b/rest_framework/tests/test_generics.py
@@ -121,7 +121,25 @@ class TestRootView(TestCase):
'text/html'
],
'name': 'Root',
- 'description': 'Example description for OPTIONS.'
+ 'description': 'Example description for OPTIONS.',
+ 'actions': {
+ 'POST': {
+ 'text': {
+ 'max_length': 100,
+ 'read_only': False,
+ 'required': True,
+ 'type': 'string',
+ "label": "Text comes here",
+ "help_text": "Text description."
+ },
+ 'id': {
+ 'read_only': True,
+ 'required': False,
+ 'type': 'integer',
+ 'label': 'ID',
+ },
+ }
+ }
}
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, expected)
@@ -224,9 +242,9 @@ class TestInstanceView(TestCase):
"""
OPTIONS requests to RetrieveUpdateDestroyAPIView should return metadata
"""
- request = factory.options('/')
- with self.assertNumQueries(0):
- response = self.view(request).render()
+ request = factory.options('/1')
+ with self.assertNumQueries(1):
+ response = self.view(request, pk=1).render()
expected = {
'parses': [
'application/json',
@@ -238,11 +256,39 @@ class TestInstanceView(TestCase):
'text/html'
],
'name': 'Instance',
- 'description': 'Example description for OPTIONS.'
+ 'description': 'Example description for OPTIONS.',
+ 'actions': {
+ 'PUT': {
+ 'text': {
+ 'max_length': 100,
+ 'read_only': False,
+ 'required': True,
+ 'type': 'string',
+ 'label': 'Text comes here',
+ 'help_text': 'Text description.'
+ },
+ 'id': {
+ 'read_only': True,
+ 'required': False,
+ 'type': 'integer',
+ 'label': 'ID',
+ },
+ }
+ }
}
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, expected)
+ def test_get_instance_view_incorrect_arg(self):
+ """
+ GET requests with an incorrect pk type, should raise 404, not 500.
+ Regression test for #890.
+ """
+ request = factory.get('/a')
+ with self.assertNumQueries(0):
+ response = self.view(request, pk='a').render()
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
def test_put_cannot_set_id(self):
"""
PUT requests to create a new object should not be able to set the id.
diff --git a/rest_framework/tests/htmlrenderer.py b/rest_framework/tests/test_htmlrenderer.py
index 5d18a6e8..8957a43c 100644
--- a/rest_framework/tests/htmlrenderer.py
+++ b/rest_framework/tests/test_htmlrenderer.py
@@ -42,7 +42,7 @@ urlpatterns = patterns('',
class TemplateHTMLRendererTests(TestCase):
- urls = 'rest_framework.tests.htmlrenderer'
+ urls = 'rest_framework.tests.test_htmlrenderer'
def setUp(self):
"""
@@ -82,7 +82,7 @@ class TemplateHTMLRendererTests(TestCase):
class TemplateHTMLRendererExceptionTests(TestCase):
- urls = 'rest_framework.tests.htmlrenderer'
+ urls = 'rest_framework.tests.test_htmlrenderer'
def setUp(self):
"""
diff --git a/rest_framework/tests/hyperlinkedserializers.py b/rest_framework/tests/test_hyperlinkedserializers.py
index 8fc6ba77..1894ddb2 100644
--- a/rest_framework/tests/hyperlinkedserializers.py
+++ b/rest_framework/tests/test_hyperlinkedserializers.py
@@ -106,7 +106,7 @@ urlpatterns = patterns('',
class TestBasicHyperlinkedView(TestCase):
- urls = 'rest_framework.tests.hyperlinkedserializers'
+ urls = 'rest_framework.tests.test_hyperlinkedserializers'
def setUp(self):
"""
@@ -143,7 +143,7 @@ class TestBasicHyperlinkedView(TestCase):
class TestManyToManyHyperlinkedView(TestCase):
- urls = 'rest_framework.tests.hyperlinkedserializers'
+ urls = 'rest_framework.tests.test_hyperlinkedserializers'
def setUp(self):
"""
@@ -191,7 +191,7 @@ class TestManyToManyHyperlinkedView(TestCase):
class TestHyperlinkedIdentityFieldLookup(TestCase):
- urls = 'rest_framework.tests.hyperlinkedserializers'
+ urls = 'rest_framework.tests.test_hyperlinkedserializers'
def setUp(self):
"""
@@ -221,7 +221,7 @@ class TestHyperlinkedIdentityFieldLookup(TestCase):
class TestCreateWithForeignKeys(TestCase):
- urls = 'rest_framework.tests.hyperlinkedserializers'
+ urls = 'rest_framework.tests.test_hyperlinkedserializers'
def setUp(self):
"""
@@ -246,7 +246,7 @@ class TestCreateWithForeignKeys(TestCase):
class TestCreateWithForeignKeysAndCustomSlug(TestCase):
- urls = 'rest_framework.tests.hyperlinkedserializers'
+ urls = 'rest_framework.tests.test_hyperlinkedserializers'
def setUp(self):
"""
@@ -271,7 +271,7 @@ class TestCreateWithForeignKeysAndCustomSlug(TestCase):
class TestOptionalRelationHyperlinkedView(TestCase):
- urls = 'rest_framework.tests.hyperlinkedserializers'
+ urls = 'rest_framework.tests.test_hyperlinkedserializers'
def setUp(self):
"""
diff --git a/rest_framework/tests/multitable_inheritance.py b/rest_framework/tests/test_multitable_inheritance.py
index 00c15327..00c15327 100644
--- a/rest_framework/tests/multitable_inheritance.py
+++ b/rest_framework/tests/test_multitable_inheritance.py
diff --git a/rest_framework/tests/negotiation.py b/rest_framework/tests/test_negotiation.py
index 7f84827f..7f84827f 100644
--- a/rest_framework/tests/negotiation.py
+++ b/rest_framework/tests/test_negotiation.py
diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/test_pagination.py
index e538a78e..e538a78e 100644
--- a/rest_framework/tests/pagination.py
+++ b/rest_framework/tests/test_pagination.py
diff --git a/rest_framework/tests/parsers.py b/rest_framework/tests/test_parsers.py
index 7699e10c..7699e10c 100644
--- a/rest_framework/tests/parsers.py
+++ b/rest_framework/tests/test_parsers.py
diff --git a/rest_framework/tests/permissions.py b/rest_framework/tests/test_permissions.py
index b3993be5..6caaf65b 100644
--- a/rest_framework/tests/permissions.py
+++ b/rest_framework/tests/test_permissions.py
@@ -108,6 +108,48 @@ class ModelPermissionsIntegrationTests(TestCase):
response = instance_view(request, pk='2')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+ def test_options_permitted(self):
+ request = factory.options('/', content_type='application/json',
+ HTTP_AUTHORIZATION=self.permitted_credentials)
+ response = root_view(request, pk='1')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertIn('actions', response.data)
+ self.assertEqual(list(response.data['actions'].keys()), ['POST'])
+
+ request = factory.options('/1', content_type='application/json',
+ HTTP_AUTHORIZATION=self.permitted_credentials)
+ response = instance_view(request, pk='1')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertIn('actions', response.data)
+ self.assertEqual(list(response.data['actions'].keys()), ['PUT'])
+
+ def test_options_disallowed(self):
+ request = factory.options('/', content_type='application/json',
+ HTTP_AUTHORIZATION=self.disallowed_credentials)
+ response = root_view(request, pk='1')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertNotIn('actions', response.data)
+
+ request = factory.options('/1', content_type='application/json',
+ HTTP_AUTHORIZATION=self.disallowed_credentials)
+ response = instance_view(request, pk='1')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertNotIn('actions', response.data)
+
+ def test_options_updateonly(self):
+ request = factory.options('/', content_type='application/json',
+ HTTP_AUTHORIZATION=self.updateonly_credentials)
+ response = root_view(request, pk='1')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertNotIn('actions', response.data)
+
+ request = factory.options('/1', content_type='application/json',
+ HTTP_AUTHORIZATION=self.updateonly_credentials)
+ response = instance_view(request, pk='1')
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertIn('actions', response.data)
+ self.assertEqual(list(response.data['actions'].keys()), ['PUT'])
+
class OwnerModel(models.Model):
text = models.CharField(max_length=100)
diff --git a/rest_framework/tests/relations.py b/rest_framework/tests/test_relations.py
index d19219c9..d19219c9 100644
--- a/rest_framework/tests/relations.py
+++ b/rest_framework/tests/test_relations.py
diff --git a/rest_framework/tests/relations_hyperlink.py b/rest_framework/tests/test_relations_hyperlink.py
index b3efbf52..2ca7f4f2 100644
--- a/rest_framework/tests/relations_hyperlink.py
+++ b/rest_framework/tests/test_relations_hyperlink.py
@@ -71,7 +71,7 @@ class NullableOneToOneTargetSerializer(serializers.HyperlinkedModelSerializer):
# TODO: Add test that .data cannot be accessed prior to .is_valid
class HyperlinkedManyToManyTests(TestCase):
- urls = 'rest_framework.tests.relations_hyperlink'
+ urls = 'rest_framework.tests.test_relations_hyperlink'
def setUp(self):
for idx in range(1, 4):
@@ -179,7 +179,7 @@ class HyperlinkedManyToManyTests(TestCase):
class HyperlinkedForeignKeyTests(TestCase):
- urls = 'rest_framework.tests.relations_hyperlink'
+ urls = 'rest_framework.tests.test_relations_hyperlink'
def setUp(self):
target = ForeignKeyTarget(name='target-1')
@@ -307,7 +307,7 @@ class HyperlinkedForeignKeyTests(TestCase):
class HyperlinkedNullableForeignKeyTests(TestCase):
- urls = 'rest_framework.tests.relations_hyperlink'
+ urls = 'rest_framework.tests.test_relations_hyperlink'
def setUp(self):
target = ForeignKeyTarget(name='target-1')
@@ -435,7 +435,7 @@ class HyperlinkedNullableForeignKeyTests(TestCase):
class HyperlinkedNullableOneToOneTests(TestCase):
- urls = 'rest_framework.tests.relations_hyperlink'
+ urls = 'rest_framework.tests.test_relations_hyperlink'
def setUp(self):
target = OneToOneTarget(name='target-1')
@@ -458,7 +458,7 @@ class HyperlinkedNullableOneToOneTests(TestCase):
# Regression tests for #694 (`source` attribute on related fields)
class HyperlinkedRelatedFieldSourceTests(TestCase):
- urls = 'rest_framework.tests.relations_hyperlink'
+ urls = 'rest_framework.tests.test_relations_hyperlink'
def test_related_manager_source(self):
"""
diff --git a/rest_framework/tests/relations_nested.py b/rest_framework/tests/test_relations_nested.py
index f6d006b3..f6d006b3 100644
--- a/rest_framework/tests/relations_nested.py
+++ b/rest_framework/tests/test_relations_nested.py
diff --git a/rest_framework/tests/relations_pk.py b/rest_framework/tests/test_relations_pk.py
index e2a1b815..e2a1b815 100644
--- a/rest_framework/tests/relations_pk.py
+++ b/rest_framework/tests/test_relations_pk.py
diff --git a/rest_framework/tests/relations_slug.py b/rest_framework/tests/test_relations_slug.py
index 435c821c..435c821c 100644
--- a/rest_framework/tests/relations_slug.py
+++ b/rest_framework/tests/test_relations_slug.py
diff --git a/rest_framework/tests/renderers.py b/rest_framework/tests/test_renderers.py
index 1b2b9279..95b59741 100644
--- a/rest_framework/tests/renderers.py
+++ b/rest_framework/tests/test_renderers.py
@@ -6,6 +6,7 @@ from django.core.cache import cache
from django.test import TestCase
from django.test.client import RequestFactory
from django.utils import unittest
+from django.utils.translation import ugettext_lazy as _
from rest_framework import status, permissions
from rest_framework.compat import yaml, etree, patterns, url, include
from rest_framework.response import Response
@@ -29,7 +30,7 @@ RENDERER_B_SERIALIZER = lambda x: ('Renderer B: %s' % x).encode('ascii')
expected_results = [
- ((elem for elem in [1, 2, 3]), JSONRenderer, '[1, 2, 3]') # Generator
+ ((elem for elem in [1, 2, 3]), JSONRenderer, b'[1, 2, 3]') # Generator
]
@@ -132,7 +133,7 @@ class RendererEndToEndTests(TestCase):
End-to-end testing of renderers using an RendererMixin on a generic view.
"""
- urls = 'rest_framework.tests.renderers'
+ urls = 'rest_framework.tests.test_renderers'
def test_default_renderer_serializes_content(self):
"""If the Accept header is not set the default renderer should serialize the response."""
@@ -238,6 +239,13 @@ class JSONRendererTests(TestCase):
Tests specific to the JSON Renderer
"""
+ def test_render_lazy_strings(self):
+ """
+ JSONRenderer should deal with lazy translated strings.
+ """
+ ret = JSONRenderer().render(_('test'))
+ self.assertEqual(ret, b'"test"')
+
def test_without_content_type_args(self):
"""
Test basic JSON rendering.
@@ -246,7 +254,7 @@ class JSONRendererTests(TestCase):
renderer = JSONRenderer()
content = renderer.render(obj, 'application/json')
# Fix failing test case which depends on version of JSON library.
- self.assertEqual(content, _flat_repr)
+ self.assertEqual(content.decode('utf-8'), _flat_repr)
def test_with_content_type_args(self):
"""
@@ -255,13 +263,13 @@ class JSONRendererTests(TestCase):
obj = {'foo': ['bar', 'baz']}
renderer = JSONRenderer()
content = renderer.render(obj, 'application/json; indent=2')
- self.assertEqual(strip_trailing_whitespace(content), _indented_repr)
+ self.assertEqual(strip_trailing_whitespace(content.decode('utf-8')), _indented_repr)
def test_check_ascii(self):
obj = {'countries': ['United Kingdom', 'France', 'España']}
renderer = JSONRenderer()
content = renderer.render(obj, 'application/json')
- self.assertEqual(content, '{"countries": ["United Kingdom", "France", "Espa\\u00f1a"]}')
+ self.assertEqual(content, '{"countries": ["United Kingdom", "France", "Espa\\u00f1a"]}'.encode('utf-8'))
class UnicodeJSONRendererTests(TestCase):
@@ -280,7 +288,7 @@ class JSONPRendererTests(TestCase):
Tests specific to the JSONP Renderer
"""
- urls = 'rest_framework.tests.renderers'
+ urls = 'rest_framework.tests.test_renderers'
def test_without_callback_with_json_renderer(self):
"""
@@ -289,7 +297,7 @@ class JSONPRendererTests(TestCase):
resp = self.client.get('/jsonp/jsonrenderer',
HTTP_ACCEPT='application/javascript')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
- self.assertEqual(resp['Content-Type'], 'application/javascript; charset=iso-8859-1')
+ self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8')
self.assertEqual(resp.content,
('callback(%s);' % _flat_repr).encode('ascii'))
@@ -300,7 +308,7 @@ class JSONPRendererTests(TestCase):
resp = self.client.get('/jsonp/nojsonrenderer',
HTTP_ACCEPT='application/javascript')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
- self.assertEqual(resp['Content-Type'], 'application/javascript; charset=iso-8859-1')
+ self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8')
self.assertEqual(resp.content,
('callback(%s);' % _flat_repr).encode('ascii'))
@@ -312,7 +320,7 @@ class JSONPRendererTests(TestCase):
resp = self.client.get('/jsonp/nojsonrenderer?callback=' + callback_func,
HTTP_ACCEPT='application/javascript')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
- self.assertEqual(resp['Content-Type'], 'application/javascript; charset=iso-8859-1')
+ self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8')
self.assertEqual(resp.content,
('%s(%s);' % (callback_func, _flat_repr)).encode('ascii'))
@@ -453,7 +461,7 @@ class CacheRenderTest(TestCase):
Tests specific to caching responses
"""
- urls = 'rest_framework.tests.renderers'
+ urls = 'rest_framework.tests.test_renderers'
cache_key = 'just_a_cache_key'
diff --git a/rest_framework/tests/request.py b/rest_framework/tests/test_request.py
index 97e5af20..a5c5e84c 100644
--- a/rest_framework/tests/request.py
+++ b/rest_framework/tests/test_request.py
@@ -254,7 +254,7 @@ urlpatterns = patterns('',
class TestContentParsingWithAuthentication(TestCase):
- urls = 'rest_framework.tests.request'
+ urls = 'rest_framework.tests.test_request'
def setUp(self):
self.csrf_client = Client(enforce_csrf_checks=True)
diff --git a/rest_framework/tests/response.py b/rest_framework/tests/test_response.py
index 4e04ac5c..eea3c641 100644
--- a/rest_framework/tests/response.py
+++ b/rest_framework/tests/test_response.py
@@ -118,7 +118,7 @@ class RendererIntegrationTests(TestCase):
End-to-end testing of renderers using an ResponseMixin on a generic view.
"""
- urls = 'rest_framework.tests.response'
+ urls = 'rest_framework.tests.test_response'
def test_default_renderer_serializes_content(self):
"""If the Accept header is not set the default renderer should serialize the response."""
@@ -198,7 +198,7 @@ class Issue122Tests(TestCase):
"""
Tests that covers #122.
"""
- urls = 'rest_framework.tests.response'
+ urls = 'rest_framework.tests.test_response'
def test_only_html_renderer(self):
"""
@@ -218,7 +218,7 @@ class Issue467Tests(TestCase):
Tests for #467
"""
- urls = 'rest_framework.tests.response'
+ urls = 'rest_framework.tests.test_response'
def test_form_has_label_and_help_text(self):
resp = self.client.get('/html_new_model')
@@ -232,7 +232,7 @@ class Issue807Tests(TestCase):
Covers #807
"""
- urls = 'rest_framework.tests.response'
+ urls = 'rest_framework.tests.test_response'
def test_does_not_append_charset_by_default(self):
"""
diff --git a/rest_framework/tests/reverse.py b/rest_framework/tests/test_reverse.py
index cb8d8132..93ef5637 100644
--- a/rest_framework/tests/reverse.py
+++ b/rest_framework/tests/test_reverse.py
@@ -19,7 +19,7 @@ class ReverseTests(TestCase):
"""
Tests for fully qualified URLs when using `reverse`.
"""
- urls = 'rest_framework.tests.reverse'
+ urls = 'rest_framework.tests.test_reverse'
def test_reversed_urls_are_fully_qualified(self):
request = factory.get('/view')
diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py
new file mode 100644
index 00000000..10d3cc25
--- /dev/null
+++ b/rest_framework/tests/test_routers.py
@@ -0,0 +1,121 @@
+from __future__ import unicode_literals
+from django.db import models
+from django.test import TestCase
+from django.test.client import RequestFactory
+from rest_framework import serializers, viewsets
+from rest_framework.compat import include, patterns, url
+from rest_framework.decorators import link, action
+from rest_framework.response import Response
+from rest_framework.routers import SimpleRouter
+
+factory = RequestFactory()
+
+urlpatterns = patterns('',)
+
+
+class BasicViewSet(viewsets.ViewSet):
+ def list(self, request, *args, **kwargs):
+ return Response({'method': 'list'})
+
+ @action()
+ def action1(self, request, *args, **kwargs):
+ return Response({'method': 'action1'})
+
+ @action()
+ def action2(self, request, *args, **kwargs):
+ return Response({'method': 'action2'})
+
+ @action(methods=['post', 'delete'])
+ def action3(self, request, *args, **kwargs):
+ return Response({'method': 'action2'})
+
+ @link()
+ def link1(self, request, *args, **kwargs):
+ return Response({'method': 'link1'})
+
+ @link()
+ def link2(self, request, *args, **kwargs):
+ return Response({'method': 'link2'})
+
+
+class TestSimpleRouter(TestCase):
+ def setUp(self):
+ self.router = SimpleRouter()
+
+ def test_link_and_action_decorator(self):
+ routes = self.router.get_routes(BasicViewSet)
+ decorator_routes = routes[2:]
+ # Make sure all these endpoints exist and none have been clobbered
+ for i, endpoint in enumerate(['action1', 'action2', 'action3', 'link1', 'link2']):
+ route = decorator_routes[i]
+ # check url listing
+ self.assertEqual(route.url,
+ '^{{prefix}}/{{lookup}}/{0}/$'.format(endpoint))
+ # check method to function mapping
+ if endpoint == 'action3':
+ methods_map = ['post', 'delete']
+ elif endpoint.startswith('action'):
+ methods_map = ['post']
+ else:
+ methods_map = ['get']
+ for method in methods_map:
+ self.assertEqual(route.mapping[method], endpoint)
+
+
+class RouterTestModel(models.Model):
+ uuid = models.CharField(max_length=20)
+ text = models.CharField(max_length=200)
+
+
+class TestCustomLookupFields(TestCase):
+ """
+ Ensure that custom lookup fields are correctly routed.
+ """
+ urls = 'rest_framework.tests.test_routers'
+
+ def setUp(self):
+ class NoteSerializer(serializers.HyperlinkedModelSerializer):
+ class Meta:
+ model = RouterTestModel
+ lookup_field = 'uuid'
+ fields = ('url', 'uuid', 'text')
+
+ class NoteViewSet(viewsets.ModelViewSet):
+ queryset = RouterTestModel.objects.all()
+ serializer_class = NoteSerializer
+ lookup_field = 'uuid'
+
+ RouterTestModel.objects.create(uuid='123', text='foo bar')
+
+ self.router = SimpleRouter()
+ self.router.register(r'notes', NoteViewSet)
+
+ from rest_framework.tests import test_routers
+ urls = getattr(test_routers, 'urlpatterns')
+ urls += patterns('',
+ url(r'^', include(self.router.urls)),
+ )
+
+ def test_custom_lookup_field_route(self):
+ detail_route = self.router.urls[-1]
+ detail_url_pattern = detail_route.regex.pattern
+ self.assertIn('<uuid>', detail_url_pattern)
+
+ def test_retrieve_lookup_field_list_view(self):
+ response = self.client.get('/notes/')
+ self.assertEquals(response.data,
+ [{
+ "url": "http://testserver/notes/123/",
+ "uuid": "123", "text": "foo bar"
+ }]
+ )
+
+ def test_retrieve_lookup_field_detail_view(self):
+ response = self.client.get('/notes/123/')
+ self.assertEquals(response.data,
+ {
+ "url": "http://testserver/notes/123/",
+ "uuid": "123", "text": "foo bar"
+ }
+ )
+
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/test_serializer.py
index 1772ee37..6cc913c5 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/test_serializer.py
@@ -129,11 +129,6 @@ class PositiveIntegerAsChoiceSerializer(serializers.ModelSerializer):
fields = ['some_integer']
-class BrokenModelSerializer(serializers.ModelSerializer):
- class Meta:
- fields = ['some_field']
-
-
class BasicTests(TestCase):
def setUp(self):
self.comment = Comment(
@@ -424,8 +419,12 @@ class ValidationTests(TestCase):
Assert that a meaningful exception message is outputted when the model
field is missing (e.g. when mistyping ``model``).
"""
+ class BrokenModelSerializer(serializers.ModelSerializer):
+ class Meta:
+ fields = ['some_field']
+
try:
- serializer = BrokenModelSerializer()
+ BrokenModelSerializer()
except AssertionError as e:
self.assertEqual(e.args[0], "Serializer class 'BrokenModelSerializer' is missing 'model' Meta option")
except:
@@ -447,7 +446,7 @@ class CustomValidationTests(TestCase):
class CommentSerializerWithFieldValidator(CommentSerializer):
def validate_email(self, attrs, source):
- value = attrs[source]
+ attrs[source]
return attrs
def validate_content(self, attrs, source):
@@ -1365,16 +1364,16 @@ class FieldLabelTest(TestCase):
serializer = self.serializer_class()
text_field = serializer.fields['text']
- self.assertEquals('Text comes here', text_field.label)
- self.assertEquals('Text description.', text_field.help_text)
+ self.assertEqual('Text comes here', text_field.label)
+ self.assertEqual('Text description.', text_field.help_text)
def test_field_ctor(self):
"""
This is check that ctor supports both label and help_text.
"""
- self.assertEquals('Label', fields.Field(label='Label', help_text='Help').label)
- self.assertEquals('Help', fields.CharField(label='Label', help_text='Help').help_text)
- self.assertEquals('Label', relations.HyperlinkedRelatedField(view_name='fake', label='Label', help_text='Help', many=True).label)
+ self.assertEqual('Label', fields.Field(label='Label', help_text='Help').label)
+ self.assertEqual('Help', fields.CharField(label='Label', help_text='Help').help_text)
+ self.assertEqual('Label', relations.HyperlinkedRelatedField(view_name='fake', label='Label', help_text='Help', many=True).label)
class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
@@ -1529,3 +1528,31 @@ class DefaultValuesOnAutogeneratedFieldsTests(TestCase):
def test_url_field(self):
self.field_test('url_field')
+
+
+class MetadataSerializer(serializers.Serializer):
+ field1 = serializers.CharField(3, required=True)
+ field2 = serializers.CharField(10, required=False)
+
+
+class MetadataSerializerTestCase(TestCase):
+ def setUp(self):
+ self.serializer = MetadataSerializer()
+
+ def test_serializer_metadata(self):
+ metadata = self.serializer.metadata()
+ expected = {
+ 'field1': {
+ 'required': True,
+ 'max_length': 3,
+ 'type': 'string',
+ 'read_only': False
+ },
+ 'field2': {
+ 'required': False,
+ 'max_length': 10,
+ 'type': 'string',
+ 'read_only': False
+ }
+ }
+ self.assertEqual(expected, metadata)
diff --git a/rest_framework/tests/serializer_bulk_update.py b/rest_framework/tests/test_serializer_bulk_update.py
index 8b0ded1a..8b0ded1a 100644
--- a/rest_framework/tests/serializer_bulk_update.py
+++ b/rest_framework/tests/test_serializer_bulk_update.py
diff --git a/rest_framework/tests/serializer_nested.py b/rest_framework/tests/test_serializer_nested.py
index 71d0e24b..71d0e24b 100644
--- a/rest_framework/tests/serializer_nested.py
+++ b/rest_framework/tests/test_serializer_nested.py
diff --git a/rest_framework/tests/settings.py b/rest_framework/tests/test_settings.py
index 857375c2..857375c2 100644
--- a/rest_framework/tests/settings.py
+++ b/rest_framework/tests/test_settings.py
diff --git a/rest_framework/tests/throttling.py b/rest_framework/tests/test_throttling.py
index 11cbd8eb..da400b2f 100644
--- a/rest_framework/tests/throttling.py
+++ b/rest_framework/tests/test_throttling.py
@@ -36,7 +36,7 @@ class MockView_MinuteThrottling(APIView):
class ThrottlingTests(TestCase):
- urls = 'rest_framework.tests.throttling'
+ urls = 'rest_framework.tests.test_throttling'
def setUp(self):
"""
diff --git a/rest_framework/tests/urlpatterns.py b/rest_framework/tests/test_urlpatterns.py
index 29ed4a96..29ed4a96 100644
--- a/rest_framework/tests/urlpatterns.py
+++ b/rest_framework/tests/test_urlpatterns.py
diff --git a/rest_framework/tests/validation.py b/rest_framework/tests/test_validation.py
index cbdd6515..cbdd6515 100644
--- a/rest_framework/tests/validation.py
+++ b/rest_framework/tests/test_validation.py
diff --git a/rest_framework/tests/views.py b/rest_framework/tests/test_views.py
index 994cf6dc..2767d24c 100644
--- a/rest_framework/tests/views.py
+++ b/rest_framework/tests/test_views.py
@@ -1,12 +1,15 @@
from __future__ import unicode_literals
+
+import copy
+
from django.test import TestCase
from django.test.client import RequestFactory
+
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
-import copy
factory = RequestFactory()
diff --git a/rest_framework/tests/testcases.py b/rest_framework/tests/testcases.py
deleted file mode 100644
index f8c2579e..00000000
--- a/rest_framework/tests/testcases.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# http://djangosnippets.org/snippets/1011/
-from __future__ import unicode_literals
-from django.conf import settings
-from django.core.management import call_command
-from django.db.models import loading
-from django.test import TestCase
-
-NO_SETTING = ('!', None)
-
-
-class TestSettingsManager(object):
- """
- A class which can modify some Django settings temporarily for a
- test and then revert them to their original values later.
-
- Automatically handles resyncing the DB if INSTALLED_APPS is
- modified.
-
- """
- def __init__(self):
- self._original_settings = {}
-
- def set(self, **kwargs):
- for k, v in kwargs.iteritems():
- self._original_settings.setdefault(k, getattr(settings, k,
- NO_SETTING))
- setattr(settings, k, v)
- if 'INSTALLED_APPS' in kwargs:
- self.syncdb()
-
- def syncdb(self):
- loading.cache.loaded = False
- call_command('syncdb', verbosity=0)
-
- def revert(self):
- for k, v in self._original_settings.iteritems():
- if v == NO_SETTING:
- delattr(settings, k)
- else:
- setattr(settings, k, v)
- if 'INSTALLED_APPS' in self._original_settings:
- self.syncdb()
- self._original_settings = {}
-
-
-class SettingsTestCase(TestCase):
- """
- A subclass of the Django TestCase with a settings_manager
- attribute which is an instance of TestSettingsManager.
-
- Comes with a tearDown() method that calls
- self.settings_manager.revert().
-
- """
- def __init__(self, *args, **kwargs):
- super(SettingsTestCase, self).__init__(*args, **kwargs)
- self.settings_manager = TestSettingsManager()
-
- def tearDown(self):
- self.settings_manager.revert()
-
-
-class TestModelsTestCase(SettingsTestCase):
- def setUp(self, *args, **kwargs):
- installed_apps = tuple(settings.INSTALLED_APPS) + ('rest_framework.tests',)
- self.settings_manager.set(INSTALLED_APPS=installed_apps)
diff --git a/rest_framework/tests/tests.py b/rest_framework/tests/tests.py
index 08f88e11..554ebd1a 100644
--- a/rest_framework/tests/tests.py
+++ b/rest_framework/tests/tests.py
@@ -4,11 +4,13 @@ runner to pick up the tests. Yowzers.
"""
from __future__ import unicode_literals
import os
+import django
modules = [filename.rsplit('.', 1)[0]
for filename in os.listdir(os.path.dirname(__file__))
if filename.endswith('.py') and not filename.startswith('_')]
__test__ = dict()
-for module in modules:
- exec("from rest_framework.tests.%s import *" % module)
+if django.VERSION < (1, 6):
+ for module in modules:
+ exec("from rest_framework.tests.%s import *" % module)
diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py
index b6de18a8..b26a2085 100644
--- a/rest_framework/utils/encoders.py
+++ b/rest_framework/utils/encoders.py
@@ -3,7 +3,8 @@ Helper classes for parsers.
"""
from __future__ import unicode_literals
from django.utils.datastructures import SortedDict
-from rest_framework.compat import timezone
+from django.utils.functional import Promise
+from rest_framework.compat import timezone, force_text
from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata
import datetime
import decimal
@@ -19,7 +20,9 @@ class JSONEncoder(json.JSONEncoder):
def default(self, o):
# For Date Time string spec, see ECMA 262
# http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
- if isinstance(o, datetime.datetime):
+ if isinstance(o, Promise):
+ return force_text(o)
+ elif isinstance(o, datetime.datetime):
r = o.isoformat()
if o.microsecond:
r = r[:23] + r[26:]
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 555fa2f4..e1b6705b 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -2,13 +2,15 @@
Provides an APIView class that is the base of all views in REST framework.
"""
from __future__ import unicode_literals
+
from django.core.exceptions import PermissionDenied
from django.http import Http404, HttpResponse
+from django.utils.datastructures import SortedDict
from django.views.decorators.csrf import csrf_exempt
from rest_framework import status, exceptions
from rest_framework.compat import View
-from rest_framework.response import Response
from rest_framework.request import Request
+from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.utils.formatting import get_view_name, get_view_description
@@ -51,21 +53,6 @@ class APIView(View):
'Vary': 'Accept'
}
- def metadata(self, request):
- return {
- 'name': get_view_name(self.__class__),
- 'description': get_view_description(self.__class__),
- 'renders': [renderer.media_type for renderer in self.renderer_classes],
- 'parses': [parser.media_type for parser in self.parser_classes],
- }
- # TODO: Add 'fields', from serializer info, if it exists.
- # serializer = self.get_serializer()
- # if serializer is not None:
- # field_name_types = {}
- # for name, field in form.fields.iteritems():
- # field_name_types[name] = field.__class__.__name__
- # content['fields'] = field_name_types
-
def http_method_not_allowed(self, request, *args, **kwargs):
"""
If `request.method` does not correspond to a handler method,
@@ -348,3 +335,15 @@ class APIView(View):
a less useful default implementation.
"""
return Response(self.metadata(request), status=status.HTTP_200_OK)
+
+ def metadata(self, request):
+ """
+ Return a dictionary of metadata about the view.
+ Used to return responses for OPTIONS requests.
+ """
+ ret = SortedDict()
+ ret['name'] = get_view_name(self.__class__)
+ ret['description'] = get_view_description(self.__class__)
+ ret['renders'] = [renderer.media_type for renderer in self.renderer_classes]
+ ret['parses'] = [parser.media_type for parser in self.parser_classes]
+ return ret