Merge branch 'master' into master

This commit is contained in:
Artem Khomenko 2017-02-06 15:24:22 +02:00 committed by GitHub
commit 94f4003ea5
321 changed files with 18179 additions and 8115 deletions

1
.gitignore vendored
View File

@ -11,6 +11,7 @@
/*.egg-info/
/env/
MANIFEST
coverage.*
!.gitignore
!.travis.yml

View File

@ -1,31 +1,51 @@
language: python
python:
- "3.5"
- "2.7"
- "3.4"
- "3.5"
sudo: false
env:
- TOX_ENV=py27-lint
- TOX_ENV=py27-docs
- TOX_ENV=py35-django19
- TOX_ENV=py34-django19
- TOX_ENV=py27-django19
- TOX_ENV=py34-django18
- TOX_ENV=py33-django18
- TOX_ENV=py32-django18
- TOX_ENV=py27-django18
- DJANGO=1.8
- DJANGO=1.9
- DJANGO=1.10
- DJANGO=1.11
- DJANGO=master
matrix:
fast_finish: true
fast_finish: true
include:
- python: "3.6"
env: DJANGO=master
- python: "3.6"
env: DJANGO=1.11
- python: "3.3"
env: DJANGO=1.8
- python: "2.7"
env: TOXENV="lint"
- python: "2.7"
env: TOXENV="docs"
exclude:
- python: "2.7"
env: DJANGO=master
- python: "3.4"
env: DJANGO=master
allow_failures:
- env: DJANGO=master
- env: DJANGO=1.11
install:
# Virtualenv < 14 is required to keep the Python 3.2 builds running.
- pip install tox "virtualenv<14"
- pip install tox tox-travis
script:
- tox -e $TOX_ENV
- tox
after_success:
- pip install codecov
- codecov -e TOX_ENV
- codecov -e TOXENV,DJANGO
notifications:
email: false

View File

@ -61,6 +61,7 @@ To run the tests, clone the repository, and then:
# Setup the virtual environment
virtualenv env
source env/bin/activate
pip install django
pip install -r requirements.txt
# Run the tests
@ -200,7 +201,7 @@ If you want to draw attention to a note or warning, use a pair of enclosing line
[issues]: https://github.com/tomchristie/django-rest-framework/issues?state=open
[pep-8]: http://www.python.org/dev/peps/pep-0008/
[pull-requests]: https://help.github.com/articles/using-pull-requests
[tox]: http://tox.readthedocs.org/en/latest/
[tox]: https://tox.readthedocs.io/en/latest/
[markdown]: http://daringfireball.net/projects/markdown/basics
[docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs
[mou]: http://mouapp.com/

View File

@ -3,6 +3,7 @@
[![build-status-image]][travis]
[![coverage-status-image]][codecov]
[![pypi-version]][pypi]
[![Gitter](https://badges.gitter.im/tomchristie/django-rest-framework.svg)](https://gitter.im/tomchristie/django-rest-framework?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
**Awesome web-browsable Web APIs.**
@ -10,9 +11,24 @@ Full documentation for the project is available at [http://www.django-rest-frame
---
**Note**: We have now released Django REST framework 3.3. For older codebases you may want to refer to the version 2.4.4 [source code][2.4-code], and [documentation][2.4-docs].
# Funding
For more details see the 3.3 [announcement][3.3-announcement] and [release notes][3.3-release-notes].
REST framework is a *collaboratively funded project*. If you use
REST framework commercially we strongly encourage you to invest in its
continued development by **[signing up for a paid plan][funding]**.
The initial aim is to provide a single full-time position on REST framework.
*Every single sign-up makes a significant impact towards making that possible.*
<p align="center">
<a href="http://jobs.rover.com/"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/rover-readme.png"/></a>
<a href="https://getsentry.com/welcome/"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/sentry-readme.png"/></a>
<a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/stream-readme.png"/></a>
<a href="https://hello.machinalis.co.uk/"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/machinalis-readme.png"/></a>
<a href="https://rollbar.com/"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/rollbar-readme.png"/></a>
</p>
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://hello.machinalis.co.uk/), and [Rollbar](https://rollbar.com).*
---
@ -37,7 +53,7 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements
* Python (2.7, 3.2, 3.3, 3.4, 3.5)
* Django (1.8, 1.9)
* Django (1.8, 1.9, 1.10)
# Installation
@ -155,7 +171,7 @@ You may also want to [follow the author on Twitter][twitter].
# Security
If you believe youve found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**.
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.
@ -169,6 +185,9 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[sandbox]: http://restframework.herokuapp.com/
[funding]: https://fund.django-rest-framework.org/topics/funding/
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
[oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
[serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers
@ -183,7 +202,3 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
[docs]: http://www.django-rest-framework.org/
[security-mail]: mailto:rest-framework-security@googlegroups.com
[2.4-code]: https://github.com/tomchristie/django-rest-framework/tree/version-2.4.x
[2.4-docs]: http://tomchristie.github.io/rest-framework-2-docs/
[3.3-announcement]: http://www.django-rest-framework.org/topics/3.3-announcement/
[3.3-release-notes]: http://www.django-rest-framework.org/topics/release-notes/#33x-series

View File

@ -44,7 +44,7 @@ The default authentication schemes may be set globally, using the `DEFAULT_AUTHE
}
You can also set the authentication scheme on a per-view or per-viewset basis,
using the `APIView` class based views.
using the `APIView` class-based views.
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
@ -128,11 +128,10 @@ To use the `TokenAuthentication` scheme you'll need to [configure the authentica
---
**Note:** Make sure to run `manage.py syncdb` after changing your settings. The `rest_framework.authtoken` app provides both Django (from v1.7) and South database migrations. See [Schema migrations](#schema-migrations) below.
**Note:** Make sure to run `manage.py migrate` after changing your settings. The `rest_framework.authtoken` app provides Django database migrations.
---
You'll also need to create tokens for your users.
from rest_framework.authtoken.models import Token
@ -144,10 +143,12 @@ For clients to authenticate, the token key should be included in the `Authorizat
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
**Note:** If you want to use a different keyword in the header, such as `Bearer`, simply subclass `TokenAuthentication` and set the `keyword` class variable.
If successfully authenticated, `TokenAuthentication` provides the following credentials.
* `request.user` will be a Django `User` instance.
* `request.auth` will be a `rest_framework.authtoken.models.BasicToken` instance.
* `request.auth` will be a `rest_framework.authtoken.models.Token` instance.
Unauthenticated responses that are denied permission will result in an `HTTP 401 Unauthorized` response with an appropriate WWW-Authenticate header. For example:
@ -206,6 +207,10 @@ The `obtain_auth_token` view will return a JSON response when valid `username` a
Note that the default `obtain_auth_token` view explicitly uses JSON requests and responses, rather than using default renderer and parser classes in your settings. If you need a customized version of the `obtain_auth_token` view, you can do so by overriding the `ObtainAuthToken` view class, and using that in your url conf instead.
By default there are no permissions or throttling applied to the `obtain_auth_token` view. If you do wish to apply throttling you'll need to override the view class,
and include them using the `throttle_classes` attribute.
##### With Django admin
It is also possible to create Tokens manually through admin interface. In case you are using a large user base, we recommend that you monkey patch the `TokenAdmin` class to customize it to your needs, more specifically by declaring the `user` field as `raw_field`.
@ -217,38 +222,6 @@ It is also possible to create Tokens manually through admin interface. In case y
TokenAdmin.raw_id_fields = ('user',)
#### Schema migrations
The `rest_framework.authtoken` app includes both Django native migrations (for Django versions >1.7) and South migrations (for Django versions <1.7) that will create the authtoken table.
----
**Note**: From REST Framework v2.4.0 using South with Django <1.7 requires upgrading South v1.0+
----
If you're using a [custom user model][custom-user-model] you'll need to make sure that any initial migration that creates the user table runs before the authtoken table is created.
You can do so by inserting a `needed_by` attribute in your user migration:
class Migration:
needed_by = (
('authtoken', '0001_initial'),
)
def forwards(self):
...
For more details, see the [south documentation on dependencies][south-dependencies].
Also note that if you're using a `post_save` signal to create tokens, then the first time you create the database tables, you'll need to ensure any migrations are run prior to creating any superusers. For example:
python manage.py syncdb --noinput # Won't create a superuser just yet, due to `--noinput`.
python manage.py migrate
python manage.py createsuperuser
## SessionAuthentication
This authentication scheme uses Django's default session backend for authentication. Session authentication is appropriate for AJAX clients that are running in the same session context as your website.
@ -390,11 +363,9 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
[oauth]: http://oauth.net/2/
[permission]: permissions.md
[throttling]: throttling.md
[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax
[mod_wsgi_official]: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPassAuthorization
[custom-user-model]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#specifying-a-custom-user-model
[south-dependencies]: http://south.readthedocs.org/en/latest/dependencies.html
[django-oauth-toolkit-getting-started]: https://django-oauth-toolkit.readthedocs.org/en/latest/rest-framework/getting_started.html
[django-oauth-toolkit-getting-started]: https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html
[django-rest-framework-oauth]: http://jpadilla.github.io/django-rest-framework-oauth/
[django-rest-framework-oauth-authentication]: http://jpadilla.github.io/django-rest-framework-oauth/authentication/
[django-rest-framework-oauth-permissions]: http://jpadilla.github.io/django-rest-framework-oauth/permissions/
@ -403,7 +374,7 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
[oauth-1.0a]: http://oauth.net/core/1.0a
[django-oauth-plus]: http://code.larlet.fr/django-oauth-plus
[django-oauth2-provider]: https://github.com/caffeinehit/django-oauth2-provider
[django-oauth2-provider-docs]: https://django-oauth2-provider.readthedocs.org/en/latest/
[django-oauth2-provider-docs]: https://django-oauth2-provider.readthedocs.io/en/latest/
[rfc6749]: http://tools.ietf.org/html/rfc6749
[django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit
[evonove]: https://github.com/evonove/
@ -417,9 +388,9 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
[djangorestframework-httpsignature]: https://github.com/etoccalino/django-rest-framework-httpsignature
[amazon-http-signature]: http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
[http-signature-ietf-draft]: https://datatracker.ietf.org/doc/draft-cavage-http-signatures/
[hawkrest]: http://hawkrest.readthedocs.org/en/latest/
[hawkrest]: https://hawkrest.readthedocs.io/en/latest/
[hawk]: https://github.com/hueniverse/hawk
[mohawk]: http://mohawk.readthedocs.org/en/latest/
[mohawk]: https://mohawk.readthedocs.io/en/latest/
[mac]: http://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05
[djoser]: https://github.com/sunscrapers/djoser
[django-rest-auth]: https://github.com/Tivix/django-rest-auth

View File

@ -77,7 +77,7 @@ The default content negotiation class may be set globally, using the `DEFAULT_CO
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'myapp.negotiation.IgnoreClientContentNegotiation',
}
You can also set the content negotiation used for an individual view, or viewset, using the `APIView` class based views.
You can also set the content negotiation used for an individual view, or viewset, using the `APIView` class-based views.
from myapp.negotiation import IgnoreClientContentNegotiation
from rest_framework.response import Response

View File

@ -47,7 +47,7 @@ Any example validation error might look like this:
You can implement custom exception handling by creating a handler function that converts exceptions raised in your API views into response objects. This allows you to control the style of error responses used by your API.
The function must take a pair of arguments, this first is the exception to be handled, and the second is a dictionary containing any extra context such as the view currently being handled. The exception handler function should either return a `Response` object, or return `None` if the exception cannot be handled. If the handler returns `None` then the exception will be re-raised and Django will return a standard HTTP 500 'server error' response.
The function must take a pair of arguments, the first is the exception to be handled, and the second is a dictionary containing any extra context such as the view currently being handled. The exception handler function should either return a `Response` object, or return `None` if the exception cannot be handled. If the handler returns `None` then the exception will be re-raised and Django will return a standard HTTP 500 'server error' response.
For example, you might want to ensure that all error responses include the HTTP status code in the body of the response, like so:
@ -98,7 +98,7 @@ Note that the exception handler will only be called for responses generated by r
The **base class** for all exceptions raised inside an `APIView` class or `@api_view`.
To provide a custom exception, subclass `APIException` and set the `.status_code` and `.default_detail` properties on the class.
To provide a custom exception, subclass `APIException` and set the `.status_code`, `.default_detail`, and `default_code` attributes on the class.
For example, if your API relies on a third party service that may sometimes be unreachable, you might want to implement an exception for the "503 Service Unavailable" HTTP response code. You could do this like so:
@ -107,10 +107,42 @@ For example, if your API relies on a third party service that may sometimes be u
class ServiceUnavailable(APIException):
status_code = 503
default_detail = 'Service temporarily unavailable, try again later.'
default_code = 'service_unavailable'
#### Inspecting API exceptions
There are a number of different properties available for inspecting the status
of an API exception. You can use these to build custom exception handling
for your project.
The available attributes and methods are:
* `.detail` - Return the textual description of the error.
* `.get_codes()` - Return the code identifier of the error.
* `.get_full_details()` - Return both the textual description and the code identifier.
In most cases the error detail will be a simple item:
>>> print(exc.detail)
You do not have permission to perform this action.
>>> print(exc.get_codes())
permission_denied
>>> print(exc.get_full_details())
{'message':'You do not have permission to perform this action.','code':'permission_denied'}
In the case of validation errors the error detail will be either a list or
dictionary of items:
>>> print(exc.detail)
{"name":"This field is required.","age":"A valid integer is required."}
>>> print(exc.get_codes())
{"name":"required","age":"invalid"}
>>> print(exc.get_full_details())
{"name":{"message":"This field is required.","code":"required"},"age":{"message":"A valid integer is required.","code":"invalid"}}
## ParseError
**Signature:** `ParseError(detail=None)`
**Signature:** `ParseError(detail=None, code=None)`
Raised if the request contains malformed data when accessing `request.data`.
@ -118,7 +150,7 @@ By default this exception results in a response with the HTTP status code "400 B
## AuthenticationFailed
**Signature:** `AuthenticationFailed(detail=None)`
**Signature:** `AuthenticationFailed(detail=None, code=None)`
Raised when an incoming request includes incorrect authentication.
@ -126,7 +158,7 @@ By default this exception results in a response with the HTTP status code "401 U
## NotAuthenticated
**Signature:** `NotAuthenticated(detail=None)`
**Signature:** `NotAuthenticated(detail=None, code=None)`
Raised when an unauthenticated request fails the permission checks.
@ -134,7 +166,7 @@ By default this exception results in a response with the HTTP status code "401 U
## PermissionDenied
**Signature:** `PermissionDenied(detail=None)`
**Signature:** `PermissionDenied(detail=None, code=None)`
Raised when an authenticated request fails the permission checks.
@ -142,7 +174,7 @@ By default this exception results in a response with the HTTP status code "403 F
## NotFound
**Signature:** `NotFound(detail=None)`
**Signature:** `NotFound(detail=None, code=None)`
Raised when a resource does not exists at the given URL. This exception is equivalent to the standard `Http404` Django exception.
@ -150,7 +182,7 @@ By default this exception results in a response with the HTTP status code "404 N
## MethodNotAllowed
**Signature:** `MethodNotAllowed(method, detail=None)`
**Signature:** `MethodNotAllowed(method, detail=None, code=None)`
Raised when an incoming request occurs that does not map to a handler method on the view.
@ -158,7 +190,7 @@ By default this exception results in a response with the HTTP status code "405 M
## NotAcceptable
**Signature:** `NotAcceptable(detail=None)`
**Signature:** `NotAcceptable(detail=None, code=None)`
Raised when an incoming request occurs with an `Accept` header that cannot be satisfied by any of the available renderers.
@ -166,7 +198,7 @@ By default this exception results in a response with the HTTP status code "406 N
## UnsupportedMediaType
**Signature:** `UnsupportedMediaType(media_type, detail=None)`
**Signature:** `UnsupportedMediaType(media_type, detail=None, code=None)`
Raised if there are no parsers that can handle the content type of the request data when accessing `request.data`.
@ -174,7 +206,7 @@ By default this exception results in a response with the HTTP status code "415 U
## Throttled
**Signature:** `Throttled(wait=None, detail=None)`
**Signature:** `Throttled(wait=None, detail=None, code=None)`
Raised when an incoming request fails the throttling checks.
@ -182,7 +214,7 @@ By default this exception results in a response with the HTTP status code "429 T
## ValidationError
**Signature:** `ValidationError(detail)`
**Signature:** `ValidationError(detail, code=None)`
The `ValidationError` exception is slightly different from the other `APIException` classes:

View File

@ -49,7 +49,9 @@ Defaults to `False`
### `default`
If set, this gives the default value that will be used for the field if no input value is supplied. If not set the default behavior is to not populate the attribute at all.
If set, this gives the default value that will be used for the field if no input value is supplied. If not set the default behaviour is to not populate the attribute at all.
The `default` is not applied during partial update operations. In the partial update case only fields that are provided in the incoming data will have a validated value returned.
May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `set_context` method, that will be called each time before getting the value with the field instance as only argument. This works the same way as for [validators](validators.md#using-set_context).
@ -259,11 +261,12 @@ Corresponds to `django.db.models.fields.DecimalField`.
**Signature**: `DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)`
- `max_digits` The maximum number of digits allowed in the number. Note that this number must be greater than or equal to decimal_places.
- `max_digits` The maximum number of digits allowed in the number. It must be either `None` or an integer greater than or equal to `decimal_places`.
- `decimal_places` The number of decimal places to store with the number.
- `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer.
- `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. Note that setting `localize` will force the value to `True`.
- `max_value` Validate that the number provided is no greater than this value.
- `min_value` Validate that the number provided is no less than this value.
- `localize` Set to `True` to enable localization of input and output based on the current locale. This will also force `coerce_to_string` to `True`. Defaults to `False`. Note that data formatting is enabled if you have set `USE_L10N=True` in your settings file.
#### Example usage
@ -289,9 +292,9 @@ A date and time representation.
Corresponds to `django.db.models.fields.DateTimeField`.
**Signature:** `DateTimeField(format=None, input_formats=None)`
**Signature:** `DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)`
* `format` - A string representing the output format. If not specified, this defaults to the same value as the `DATETIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `datetime` objects should be returned by `to_representation`. In this case the datetime encoding will be determined by the renderer.
* `format` - A string representing the output format. If not specified, this defaults to the same value as the `DATETIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `datetime` objects should be returned by `to_representation`. In this case the datetime encoding will be determined by the renderer.
* `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `DATETIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`.
#### `DateTimeField` format strings.
@ -320,7 +323,7 @@ A date representation.
Corresponds to `django.db.models.fields.DateField`
**Signature:** `DateField(format=None, input_formats=None)`
**Signature:** `DateField(format=api_settings.DATE_FORMAT, input_formats=None)`
* `format` - A string representing the output format. If not specified, this defaults to the same value as the `DATE_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `date` objects should be returned by `to_representation`. In this case the date encoding will be determined by the renderer.
* `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `DATE_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`.
@ -335,7 +338,7 @@ A time representation.
Corresponds to `django.db.models.fields.TimeField`
**Signature:** `TimeField(format=None, input_formats=None)`
**Signature:** `TimeField(format=api_settings.TIME_FORMAT, input_formats=None)`
* `format` - A string representing the output format. If not specified, this defaults to the same value as the `TIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `time` objects should be returned by `to_representation`. In this case the time encoding will be determined by the renderer.
* `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `TIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`.
@ -431,9 +434,11 @@ Requires either the `Pillow` package or `PIL` package. The `Pillow` package is
A field class that validates a list of objects.
**Signature**: `ListField(child)`
**Signature**: `ListField(child, min_length=None, max_length=None)`
- `child` - A field instance that should be used for validating the objects in the list. If this argument is not provided then objects in the list will not be validated.
- `min_length` - Validates that the list contains no fewer than this number of elements.
- `max_length` - Validates that the list contains no more than this number of elements.
For example, to validate a list of integers you might use something like the following:
@ -485,7 +490,7 @@ This field is used by default with `ModelSerializer` when including field names
**Signature**: `ReadOnlyField()`
For example, is `has_expired` was a property on the `Account` model, then the following serializer would automatically generate it as a `ReadOnlyField`:
For example, if `has_expired` was a property on the `Account` model, then the following serializer would automatically generate it as a `ReadOnlyField`:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
@ -623,7 +628,6 @@ The `.fail()` method is a shortcut for raising `ValidationError` that takes a me
def to_internal_value(self, data):
if not isinstance(data, six.text_type):
msg = 'Incorrect type. Expected a string, but got %s'
self.fail('incorrect_type', input_type=type(data).__name__)
if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data):
@ -663,14 +667,14 @@ The [django-rest-framework-gis][django-rest-framework-gis] package provides geog
The [django-rest-framework-hstore][django-rest-framework-hstore] package provides an `HStoreField` to support [django-hstore][django-hstore] `DictionaryField` model field.
[cite]: https://docs.djangoproject.com/en/dev/ref/forms/api/#django.forms.Form.cleaned_data
[cite]: https://docs.djangoproject.com/en/stable/ref/forms/api/#django.forms.Form.cleaned_data
[html-and-forms]: ../topics/html-and-forms.md
[FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS
[FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS
[ecma262]: http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
[strftime]: http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior
[django-widgets]: https://docs.djangoproject.com/en/dev/ref/forms/widgets/
[strftime]: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior
[django-widgets]: https://docs.djangoproject.com/en/stable/ref/forms/widgets/
[iso8601]: http://www.w3.org/TR/NOTE-datetime
[drf-compound-fields]: http://drf-compound-fields.readthedocs.org
[drf-compound-fields]: https://drf-compound-fields.readthedocs.io
[drf-extra-fields]: https://github.com/Hipo/drf-extra-fields
[djangorestframework-recursive]: https://github.com/heywbj/django-rest-framework-recursive
[django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis

View File

@ -89,24 +89,24 @@ Generic filters can also present themselves as HTML controls in the browsable AP
## Setting filter backends
The default filter backends may be set globally, using the `DEFAULT_FILTER_BACKENDS` setting. For example.
The default filter backends may be set globally, using the `DEFAULT_FILTER_BACKENDS` setting. For example.
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',)
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
You can also set the filter backends on a per-view, or per-viewset basis,
using the `GenericAPIView` class based views.
using the `GenericAPIView` class-based views.
import django_filters.rest_framework
from django.contrib.auth.models import User
from myapp.serializers import UserSerializer
from rest_framework import filters
from rest_framework import generics
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer = UserSerializer
filter_backends = (filters.DjangoFilterBackend,)
serializer_class = UserSerializer
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
## Filtering and object lookups
@ -139,12 +139,27 @@ Note that you can use both an overridden `.get_queryset()` and generic filtering
## DjangoFilterBackend
The `DjangoFilterBackend` class supports highly customizable field filtering, using the [django-filter package][django-filter].
The `django-filter` library includes a `DjangoFilterBackend` class which
supports highly customizable field filtering for REST framework.
To use REST framework's `DjangoFilterBackend`, first install `django-filter`.
To use `DjangoFilterBackend`, first install `django-filter`.
pip install django-filter
You should now either add the filter backend to your settings:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
Or add the filter backend to an individual View or ViewSet.
from django_filters.rest_framework import DjangoFilterBackend
class UserListView(generics.ListAPIView):
...
filter_backends = (DjangoFilterBackend,)
If you are using the browsable API or admin API you may also want to install `django-crispy-forms`, which will enhance the presentation of the filter forms in HTML views, by allowing them to render Bootstrap 3 HTML.
pip install django-crispy-forms
@ -174,12 +189,11 @@ For more advanced filtering requirements you can specify a `FilterSet` class tha
import django_filters
from myapp.models import Product
from myapp.serializers import ProductSerializer
from rest_framework import filters
from rest_framework import generics
class ProductFilter(filters.FilterSet):
min_price = django_filters.NumberFilter(name="price", lookup_type='gte')
max_price = django_filters.NumberFilter(name="price", lookup_type='lte')
class ProductFilter(django_filters.rest_framework.FilterSet):
min_price = django_filters.NumberFilter(name="price", lookup_expr='gte')
max_price = django_filters.NumberFilter(name="price", lookup_expr='lte')
class Meta:
model = Product
fields = ['category', 'in_stock', 'min_price', 'max_price']
@ -187,7 +201,7 @@ For more advanced filtering requirements you can specify a `FilterSet` class tha
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
filter_class = ProductFilter
@ -199,12 +213,12 @@ You can also span relationships using `django-filter`, let's assume that each
product has foreign key to `Manufacturer` model, so we create filter that
filters using `Manufacturer` name. For example:
import django_filters
from myapp.models import Product
from myapp.serializers import ProductSerializer
from rest_framework import filters
from rest_framework import generics
class ProductFilter(filters.FilterSet):
class ProductFilter(django_filters.rest_framework.FilterSet):
class Meta:
model = Product
fields = ['category', 'in_stock', 'manufacturer__name']
@ -218,10 +232,9 @@ This is nice, but it exposes the Django's double underscore convention as part o
import django_filters
from myapp.models import Product
from myapp.serializers import ProductSerializer
from rest_framework import filters
from rest_framework import generics
class ProductFilter(filters.FilterSet):
class ProductFilter(django_filters.rest_framework.FilterSet):
manufacturer = django_filters.CharFilter(name="manufacturer__name")
class Meta:
@ -241,7 +254,6 @@ For more details on using filter sets see the [django-filter documentation][djan
* By default filtering is not enabled. If you want to use `DjangoFilterBackend` remember to make sure it is installed by using the `'DEFAULT_FILTER_BACKENDS'` setting.
* When using boolean fields, you should use the values `True` and `False` in the URL query parameters, rather than `0`, `1`, `true` or `false`. (The allowed boolean values are currently hardwired in Django's [NullBooleanSelect implementation][nullbooleanselect].)
* `django-filter` supports filtering across relationships, using Django's double-underscore syntax.
* For Django 1.3 support, make sure to install `django-filter` version 0.5.4, as later versions drop support for 1.3.
---
@ -257,7 +269,7 @@ The `SearchFilter` class will only be applied if the view has a `search_fields`
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer = UserSerializer
serializer_class = UserSerializer
filter_backends = (filters.SearchFilter,)
search_fields = ('username', 'email')
@ -380,7 +392,7 @@ A complete example using both `DjangoObjectPermissionsFilter` and `DjangoObjectP
'change' or 'delete' permissions.
"""
queryset = Event.objects.all()
serializer = EventSerializer
serializer_class = EventSerializer
filter_backends = (filters.DjangoObjectPermissionsFilter,)
permission_classes = (myapp.permissions.CustomObjectPermissions,)
@ -417,6 +429,12 @@ Generic filters may also present an interface in the browsable API. To do so you
The method should return a rendered HTML string.
## Pagination & schemas
You can also make the filter controls available to the schema autogeneration
that REST framework provides, by implementing a `get_schema_fields()` method,
which should return a list of `coreapi.Field` instances.
# Third party packages
The following third party packages provide additional filter implementations.
@ -433,14 +451,19 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter]
[django-url-filter][django-url-filter] provides a safe way to filter data via human-friendly URLs. It works very similar to DRF serializers and fields in a sense that they can be nested except they are called filtersets and filters. That provides easy way to filter related data. Also this library is generic-purpose so it can be used to filter other sources of data and not only Django `QuerySet`s.
[cite]: https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-specific-objects-with-filters
## drf-url-filters
[drf-url-filter][drf-url-filter] is a simple Django app to apply filters on drf `ModelViewSet`'s `Queryset` in a clean, simple and configurable way. It also supports validations on incoming query params and their values. A beautiful python package `Voluptuous` is being used for validations on the incoming query parameters. The best part about voluptuous is you can define your own validations as per your query params requirements.
[cite]: https://docs.djangoproject.com/en/stable/topics/db/queries/#retrieving-specific-objects-with-filters
[django-filter]: https://github.com/alex/django-filter
[django-filter-docs]: https://django-filter.readthedocs.org/en/latest/index.html
[guardian]: https://django-guardian.readthedocs.org/
[view-permissions]: https://django-guardian.readthedocs.org/en/latest/userguide/assign.html
[django-filter-docs]: https://django-filter.readthedocs.io/en/latest/index.html
[guardian]: https://django-guardian.readthedocs.io/
[view-permissions]: https://django-guardian.readthedocs.io/en/latest/userguide/assign.html
[view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models
[nullbooleanselect]: https://github.com/django/django/blob/master/django/forms/widgets.py
[search-django-admin]: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
[search-django-admin]: https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
[django-rest-framework-filters]: https://github.com/philipn/django-rest-framework-filters
[django-rest-framework-word-search-filter]: https://github.com/trollknurr/django-rest-framework-word-search-filter
[django-url-filter]: https://github.com/miki725/django-url-filter
[drf-url-filter]: https://github.com/manjitkumar/drf-url-filters

View File

@ -42,7 +42,7 @@ When using `format_suffix_patterns`, you must make sure to add the `'format'` ke
def comment_list(request, format=None):
# do stuff...
Or with class based views:
Or with class-based views:
class CommentList(APIView):
def get(self, request, format=None):

View File

@ -7,7 +7,7 @@ source: mixins.py
>
> &mdash; [Django Documentation][cite]
One of the key benefits of class based views is the way they allow you to compose bits of reusable behavior. REST framework takes advantage of this by providing a number of pre-built views that provide for commonly used patterns.
One of the key benefits of class-based views is the way they allow you to compose bits of reusable behavior. REST framework takes advantage of this by providing a number of pre-built views that provide for commonly used patterns.
The generic views provided by REST framework allow you to quickly build API views that map closely to your database models.
@ -26,7 +26,6 @@ Typically when using the generic views, you'll override the view, and set severa
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsAdminUser,)
paginate_by = 100
For more complex cases you might also want to override various methods on the view class. For example.
@ -72,8 +71,6 @@ The following attributes are used to control pagination when used with list view
* `pagination_class` - The pagination class that should be used when paginating list results. Defaults to the same value as the `DEFAULT_PAGINATION_CLASS` setting, which is `'rest_framework.pagination.PageNumberPagination'`.
Note that usage of the `paginate_by`, `paginate_by_param` and `page_kwarg` attributes are now pending deprecation. The `pagination_serializer_class` attribute and `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting have been removed completely. Pagination settings should instead be controlled by overriding a pagination class and setting any configuration attributes there. See the pagination documentation for more details.
**Filtering**:
* `filter_backends` - A list of filter backend classes that should be used for filtering the queryset. Defaults to the same value as the `DEFAULT_FILTER_BACKENDS` setting.
@ -223,8 +220,6 @@ Also provides a `.partial_update(request, *args, **kwargs)` method, which is sim
If an object is updated this returns a `200 OK` response, with a serialized representation of the object as the body of the response.
If an object is created, for example when making a `DELETE` request followed by a `PUT` request to the same URL, this returns a `201 Created` response, with a serialized representation of the object as the body of the response.
If the request data provided for updating the object was invalid, a `400 Bad Request` response will be returned, with the error details as the body of the response.
## DestroyModelMixin
@ -333,7 +328,8 @@ For example, if you need to lookup objects based on multiple fields in the URL c
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
filter[field] = self.kwargs[field]
if self.kwargs[field]: # Ignore empty fields.
filter[field] = self.kwargs[field]
return get_object_or_404(queryset, **filter) # Lookup the object
You can then simply apply this mixin to a view or viewset anytime you need to apply the custom behavior.
@ -386,7 +382,7 @@ The [django-rest-framework-bulk package][django-rest-framework-bulk] implements
[Django Rest Multiple Models][django-rest-multiple-models] provides a generic view (and mixin) for sending multiple serialized models and/or querysets via a single API request.
[cite]: https://docs.djangoproject.com/en/dev/ref/class-based-views/#base-vs-generic-views
[cite]: https://docs.djangoproject.com/en/stable/ref/class-based-views/#base-vs-generic-views
[GenericAPIView]: #genericapiview
[ListModelMixin]: #listmodelmixin
[CreateModelMixin]: #createmodelmixin

View File

@ -104,6 +104,18 @@ Then configure your settings to use this custom class:
'DEFAULT_METADATA_CLASS': 'myproject.apps.core.MinimalMetadata'
}
# Third party packages
The following third party packages provide additional metadata implementations.
## DRF-schema-adapter
[drf-schema-adapter][drf-schema-adapter] is a set of tools that makes it easier to provide schema information to frontend frameworks and libraries. It provides a metadata mixin as well as 2 metadata classes and several adapters suitable to generate [json-schema][json-schema] as well as schema information readable by various libraries.
You can also write your own adapter to work with your specific frontend.
If you wish to do so, it also provides an exporter that can export those schema information to json files.
[cite]: http://tools.ietf.org/html/rfc7231#section-4.3.7
[no-options]: https://www.mnot.net/blog/2012/10/29/NO_OPTIONS
[json-schema]: http://json-schema.org/
[drf-schema-adapter]: https://github.com/drf-forms/drf-schema-adapter

View File

@ -21,12 +21,15 @@ Pagination can be turned off by setting the pagination class to `None`.
## Setting the pagination style
The default pagination style may be set globally, using the `DEFAULT_PAGINATION_CLASS` settings key. For example, to use the built-in limit/offset pagination, you would do:
The default pagination style may be set globally, using the `DEFAULT_PAGINATION_CLASS` and `PAGE_SIZE` setting keys. For example, to use the built-in limit/offset pagination, you would do something like this:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination'
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 100
}
Note that you need to set both the pagination class, and the page size that should be used.
You can also set the pagination class on an individual view by using the `pagination_class` attribute. Typically you'll want to use the same pagination style throughout your API, although you might want to vary individual aspects of the pagination, such as default or maximum page size, on a per-view basis.
## Modifying the pagination style
@ -47,14 +50,14 @@ You can then apply your new style to a view using the `.pagination_class` attrib
class BillingRecordsView(generics.ListAPIView):
queryset = Billing.objects.all()
serializer = BillingRecordsSerializer
serializer_class = BillingRecordsSerializer
pagination_class = LargeResultsSetPagination
Or apply the style globally, using the `DEFAULT_PAGINATION_CLASS` settings key. For example:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'apps.core.pagination.StandardResultsSetPagination'
}
}
---
@ -109,7 +112,7 @@ To set these attributes you should override the `PageNumberPagination` class, an
## LimitOffsetPagination
This pagination style mirrors the syntax used when looking up multiple database records. The client includes both a "limit" and an
This pagination style mirrors the syntax used when looking up multiple database records. The client includes both a "limit" and an
"offset" query parameter. The limit indicates the maximum number of items to return, and is equivalent to the `page_size` in other styles. The offset indicates the starting position of the query in relation to the complete set of unpaginated items.
**Request**:
@ -273,6 +276,12 @@ To have your custom pagination class be used by default, use the `DEFAULT_PAGINA
API responses for list endpoints will now include a `Link` header, instead of including the pagination links as part of the body of the response, for example:
## Pagination & schemas
You can also make the pagination controls available to the schema autogeneration
that REST framework provides, by implementing a `get_schema_fields()` method,
which should return a list of `coreapi.Field` instances.
---
![Link Header][link-header]
@ -312,9 +321,14 @@ The following third party packages are also available.
The [`DRF-extensions` package][drf-extensions] includes a [`PaginateByMaxMixin` mixin class][paginate-by-max-mixin] that allows your API clients to specify `?page_size=max` to obtain the maximum allowed page size.
[cite]: https://docs.djangoproject.com/en/dev/topics/pagination/
## drf-proxy-pagination
The [`drf-proxy-pagination` package][drf-proxy-pagination] includes a `ProxyPagination` class which allows to choose pagination class with a query parameter.
[cite]: https://docs.djangoproject.com/en/stable/topics/pagination/
[github-link-pagination]: https://developer.github.com/guides/traversing-with-pagination/
[link-header]: ../img/link-header-pagination.png
[drf-extensions]: http://chibisov.github.io/drf-extensions/docs/
[paginate-by-max-mixin]: http://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin
[drf-proxy-pagination]: https://github.com/tuffnatty/drf-proxy-pagination
[disqus-cursor-api]: http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api

View File

@ -35,7 +35,7 @@ The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSE
}
You can also set the parsers used for an individual view, or viewset,
using the `APIView` class based views.
using the `APIView` class-based views.
from rest_framework.parsers import JSONParser
from rest_framework.response import Response
@ -51,7 +51,7 @@ using the `APIView` class based views.
return Response({'received data': request.data})
Or, if you're using the `@api_view` decorator with function based views.
from rest_framework.decorators import api_view
from rest_framework.decorators import parser_classes
@ -93,7 +93,9 @@ You will typically want to use both `FormParser` and `MultiPartParser` together
Parses raw file upload content. The `request.data` property will be a dictionary with a single key `'file'` containing the uploaded file.
If the view used with `FileUploadParser` is called with a `filename` URL keyword argument, then that argument will be used as the filename. If it is called without a `filename` URL keyword argument, then the client must set the filename in the `Content-Disposition` HTTP header. For example `Content-Disposition: attachment; filename=upload.jpg`.
If the view used with `FileUploadParser` is called with a `filename` URL keyword argument, then that argument will be used as the filename.
If it is called without a `filename` URL keyword argument, then the client must set the filename in the `Content-Disposition` HTTP header. For example `Content-Disposition: attachment; filename=upload.jpg`.
**.media_type**: `*/*`
@ -105,6 +107,7 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword
##### Basic usage example:
# views.py
class FileUploadView(views.APIView):
parser_classes = (FileUploadParser,)
@ -115,6 +118,11 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword
# ...
return Response(status=204)
# urls.py
urlpatterns = [
# ...
url(r'^upload/(?P<filename>[^/]+)$', FileUploadView.as_view())
]
---
@ -216,7 +224,7 @@ Modify your REST framework settings.
[jquery-ajax]: http://api.jquery.com/jQuery.ajax/
[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion
[upload-handlers]: https://docs.djangoproject.com/en/dev/topics/http/file-uploads/#upload-handlers
[upload-handlers]: https://docs.djangoproject.com/en/stable/topics/http/file-uploads/#upload-handlers
[rest-framework-yaml]: http://jpadilla.github.io/django-rest-framework-yaml/
[rest-framework-xml]: http://jpadilla.github.io/django-rest-framework-xml/
[yaml]: http://www.yaml.org/

View File

@ -71,7 +71,7 @@ If not specified, this setting defaults to allowing unrestricted access:
)
You can also set the authentication policy on a per-view, or per-viewset basis,
using the `APIView` class based views.
using the `APIView` class-based views.
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@ -92,7 +92,7 @@ Or, if you're using the `@api_view` decorator with function based views.
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@api_view('GET')
@api_view(['GET'])
@permission_classes((IsAuthenticated, ))
def example_view(request, format=None):
content = {
@ -132,7 +132,7 @@ This permission is suitable if you want to your API to allow read permissions to
## DjangoModelPermissions
This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. This permission must only be applied to views that has a `.queryset` property set. Authorization will only be granted if the user *is authenticated* and has the *relevant model permissions* assigned.
This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. This permission must only be applied to views that have a `.queryset` property set. Authorization will only be granted if the user *is authenticated* and has the *relevant model permissions* assigned.
* `POST` requests require the user to have the `add` permission on the model.
* `PUT` and `PATCH` requests require the user to have the `change` permission on the model.
@ -144,7 +144,7 @@ To use custom model permissions, override `DjangoModelPermissions` and set the `
#### Using with views that do not include a `queryset` attribute.
If you're using this permission with a view that uses an overridden `get_queryset()` method there may not be a `queryset` attribute on the view. In this case we suggest also marking the view with a sential queryset, so that this class can determine the required permissions. For example:
If you're using this permission with a view that uses an overridden `get_queryset()` method there may not be a `queryset` attribute on the view. In this case we suggest also marking the view with a sentinel queryset, so that this class can determine the required permissions. For example:
queryset = User.objects.none() # Required for DjangoModelPermissions
@ -164,7 +164,7 @@ As with `DjangoModelPermissions`, this permission must only be applied to views
Note that `DjangoObjectPermissions` **does not** require the `django-guardian` package, and should support other object-level backends equally well.
As with `DjangoModelPermissions` you can use custom model permissions by overriding `DjangoModelPermissions` and setting the `.perms_map` property. Refer to the source code for details.
As with `DjangoModelPermissions` you can use custom model permissions by overriding `DjangoObjectPermissions` and setting the `.perms_map` property. Refer to the source code for details.
---
@ -259,14 +259,22 @@ The [REST Condition][rest-condition] package is another extension for building c
## DRY Rest Permissions
The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to define different permissions for individual default and custom actions. This package is made for apps with permissions that are derived from relationships defined in the app's data model. It also supports permission checks being returned to a client app through the API's serializer. Additionally it supports adding permissions to the default and custom list actions to restrict the data they retrive per user.
The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to define different permissions for individual default and custom actions. This package is made for apps with permissions that are derived from relationships defined in the app's data model. It also supports permission checks being returned to a client app through the API's serializer. Additionally it supports adding permissions to the default and custom list actions to restrict the data they retrieve per user.
## Django Rest Framework Roles
The [Django Rest Framework Roles][django-rest-framework-roles] package makes it easier to parameterize your API over multiple types of users.
## Django Rest Framework API Key
The [Django Rest Framework API Key][django-rest-framework-api-key] package allows you to ensure that every request made to the server requires an API key header. You can generate one from the django admin interface.
[cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html
[authentication]: authentication.md
[throttling]: throttling.md
[filtering]: filtering.md
[contribauth]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#custom-permissions
[objectpermissions]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#handling-object-permissions
[contribauth]: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#custom-permissions
[objectpermissions]: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#handling-object-permissions
[guardian]: https://github.com/lukaszb/django-guardian
[get_objects_for_user]: http://pythonhosted.org/django-guardian/api/guardian.shortcuts.html#get-objects-for-user
[2.2-announcement]: ../topics/2.2-announcement.md
@ -275,3 +283,5 @@ The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to
[composed-permissions]: https://github.com/niwibe/djangorestframework-composed-permissions
[rest-condition]: https://github.com/caxap/rest_condition
[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions
[django-rest-framework-roles]: https://github.com/computer-lab/django-rest-framework-roles
[django-rest-framework-api-key]: https://github.com/manosim/django-rest-framework-api-key

View File

@ -39,7 +39,7 @@ In order to explain the various types of relational fields, we'll use a couple o
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks')
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
@ -99,8 +99,8 @@ For example, the following serializer:
Would serialize to a representation like this:
{
'album_name': 'The Roots',
'artist': 'Undun',
'album_name': 'Undun',
'artist': 'The Roots',
'tracks': [
89,
90,
@ -286,7 +286,7 @@ Would serialize to a nested representation like this:
],
}
# Writable nested serializers
## Writable nested serializers
By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create `create()` and/or `update()` methods in order to explicitly specify how the child relationships should be saved.
@ -324,8 +324,14 @@ By default nested serializers are read-only. If you want to support write-operat
>>> serializer.save()
<Album: Album object>
---
# Custom relational fields
In rare cases where none of the existing relational styles fit the representation you need,
you can implement a completely custom relational field, that describes exactly how the
output representation should be generated from the model instance.
To implement a custom relational field, you should override `RelatedField`, and implement the `.to_representation(self, value)` method. This method takes the target of the field as the `value` argument, and should return the representation that should be used to serialize the target. The `value` argument will typically be a model instance.
If you want to implement a read-write relational field, you must also implement the `.to_internal_value(self, data)` method.
@ -457,6 +463,8 @@ There are two keyword arguments you can use to control this behavior:
- `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Set to `None` to disable any limiting. Defaults to `1000`.
- `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"`
You can also control these globally using the settings `HTML_SELECT_CUTOFF` and `HTML_SELECT_CUTOFF_TEXT`.
In cases where the cutoff is being enforced you may want to instead use a plain input field in the HTML form. You can do so using the `style` keyword argument. For example:
assigned_to = serializers.SlugRelatedField(
@ -476,7 +484,7 @@ Note that reverse relationships are not automatically included by the `ModelSeri
You'll normally want to ensure that you've set an appropriate `related_name` argument on the relationship, that you can use as the field name. For example:
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks')
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
...
If you have not set a related name for the reverse relationship, you'll need to use the automatically generated related name in the `fields` argument. For example:
@ -489,7 +497,7 @@ See the Django documentation on [reverse relationships][reverse-relationships] f
## Generic relationships
If you want to serialize a generic foreign key, you need to define a custom field, to determine explicitly how you want serialize the targets of the relationship.
If you want to serialize a generic foreign key, you need to define a custom field, to determine explicitly how you want to serialize the targets of the relationship.
For example, given the following model for a tag, which has a generic relationship with other arbitrary models:
@ -497,10 +505,10 @@ For example, given the following model for a tag, which has a generic relationsh
"""
Tags arbitrary model instances using a generic relation.
See: https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/
See: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/
"""
tag_name = models.SlugField()
content_type = models.ForeignKey(ContentType)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
tagged_object = GenericForeignKey('content_type', 'object_id')
@ -585,9 +593,9 @@ The [drf-nested-routers package][drf-nested-routers] provides routers and relati
The [rest-framework-generic-relations][drf-nested-relations] library provides read/write serialization for generic foreign keys.
[cite]: http://lwn.net/Articles/193245/
[reverse-relationships]: https://docs.djangoproject.com/en/dev/topics/db/queries/#following-relationships-backward
[reverse-relationships]: https://docs.djangoproject.com/en/stable/topics/db/queries/#following-relationships-backward
[routers]: http://www.django-rest-framework.org/api-guide/routers#defaultrouter
[generic-relations]: https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/#id1
[generic-relations]: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#id1
[2.2-announcement]: ../topics/2.2-announcement.md
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
[drf-nested-relations]: https://github.com/Ian-Foote/rest-framework-generic-relations

View File

@ -28,7 +28,7 @@ The default set of renderers may be set globally, using the `DEFAULT_RENDERER_CL
}
You can also set the renderers used for an individual view, or viewset,
using the `APIView` class based views.
using the `APIView` class-based views.
from django.contrib.auth.models import User
from rest_framework.renderers import JSONRenderer
@ -123,6 +123,8 @@ You can use `TemplateHTMLRenderer` either to return regular HTML pages using RES
If you're building websites that use `TemplateHTMLRenderer` along with other renderer classes, you should consider listing `TemplateHTMLRenderer` as the first class in the `renderer_classes` list, so that it will be prioritised first even for browsers that send poorly formed `ACCEPT:` headers.
See the [_HTML & Forms_ Topic Page][html-and-forms] for further examples of `TemplateHTMLRenderer` usage.
**.media_type**: `text/html`
**.format**: `'.html'`
@ -471,8 +473,12 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[Django REST Pandas] provides a serializer and renderers that support additional data processing and output via the [Pandas] DataFrame API. Django REST Pandas includes renderers for Pandas-style CSV files, Excel workbooks (both `.xls` and `.xlsx`), and a number of [other formats]. It is maintained by [S. Andrew Sheppard][sheppard] as part of the [wq Project][wq].
## LaTeX
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process
[Rest Framework Latex] provides a renderer that outputs PDFs using Laulatex. It is maintained by [Pebble (S/F Software)][mypebble].
[cite]: https://docs.djangoproject.com/en/stable/stable/template-response/#the-rendering-process
[conneg]: content-negotiation.md
[html-and-forms]: ../topics/html-and-forms.md
[browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers
@ -481,7 +487,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
[application/vnd.github+json]: http://developer.github.com/v3/media/
[application/vnd.collection+json]: http://www.amundsen.com/media-types/collection/
[django-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views
[django-error-views]: https://docs.djangoproject.com/en/stable/topics/http/views/#customizing-error-views
[rest-framework-jsonp]: http://jpadilla.github.io/django-rest-framework-jsonp/
[cors]: http://www.w3.org/TR/cors/
[cors-docs]: http://www.django-rest-framework.org/topics/ajax-csrf-cors/
@ -506,3 +512,5 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[other formats]: https://github.com/wq/django-rest-pandas#supported-formats
[sheppard]: https://github.com/sheppard
[wq]: https://github.com/wq
[mypebble]: https://github.com/mypebble
[Rest Framework Latex]: https://github.com/mypebble/rest-framework-latex

View File

@ -118,10 +118,6 @@ For more information see the [browser enhancements documentation].
You won't typically need to directly access the request's content, as you'll normally rely on REST framework's default request parsing behavior.
If you do need to access the raw content directly, you should use the `.stream` property in preference to using `request.content`, as it provides transparent support for browser-based non-form content.
For more information see the [browser enhancements documentation].
---
# Standard HttpRequest attributes

View File

@ -91,5 +91,5 @@ As with any other `TemplateResponse`, this method is called to render the serial
You won't typically need to call `.render()` yourself, as it's handled by Django's standard response cycle.
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/
[cite]: https://docs.djangoproject.com/en/stable/stable/template-response/
[statuscodes]: status-codes.md

View File

@ -23,7 +23,7 @@ There's no requirement for you to use them, but if you do then the self-describi
**Signature:** `reverse(viewname, *args, **kwargs)`
Has the same behavior as [`django.core.urlresolvers.reverse`][reverse], except that it returns a fully qualified URL, using the request to determine the host and port.
Has the same behavior as [`django.urls.reverse`][reverse], except that it returns a fully qualified URL, using the request to determine the host and port.
You should **include the request as a keyword argument** to the function, for example:
@ -44,12 +44,12 @@ You should **include the request as a keyword argument** to the function, for ex
**Signature:** `reverse_lazy(viewname, *args, **kwargs)`
Has the same behavior as [`django.core.urlresolvers.reverse_lazy`][reverse-lazy], except that it returns a fully qualified URL, using the request to determine the host and port.
Has the same behavior as [`django.urls.reverse_lazy`][reverse-lazy], except that it returns a fully qualified URL, using the request to determine the host and port.
As with the `reverse` function, you should **include the request as a keyword argument** to the function, for example:
api_root = reverse_lazy('api-root', request=request)
[cite]: http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5
[reverse]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse
[reverse-lazy]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy
[reverse]: https://docs.djangoproject.com/en/stable/topics/http/urls/#reverse
[reverse-lazy]: https://docs.djangoproject.com/en/stable/topics/http/urls/#reverse-lazy

View File

@ -118,6 +118,26 @@ The above example would now generate the following URL pattern:
* URL pattern: `^users/{pk}/change-password/$` Name: `'user-change-password'`
In the case you do not want to use the default name generated for your custom action, you can use the url_name parameter to customize it.
For example, if you want to change the name of our custom action to `'user-change-password'`, you could write:
from myapp.permissions import IsAdminOrIsSelf
from rest_framework.decorators import detail_route
class UserViewSet(ModelViewSet):
...
@detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf], url_name='change-password')
def set_password(self, request, pk=None):
...
The above example would now generate the following URL pattern:
* URL pattern: `^users/{pk}/set_password/$` Name: `'user-change-password'`
You can also use url_path and url_name parameters together to obtain extra control on URL generation for custom views.
For more information see the viewset documentation on [marking extra actions for routing][route-decorators].
# API Guide
@ -226,9 +246,9 @@ The following example will only route to the `list` and `retrieve` actions, and
),
Route(
url=r'^{prefix}/{lookup}$',
mapping={'get': 'retrieve'},
name='{basename}-detail',
initkwargs={'suffix': 'Detail'}
mapping={'get': 'retrieve'},
name='{basename}-detail',
initkwargs={'suffix': 'Detail'}
),
DynamicDetailRoute(
url=r'^{prefix}/{lookup}/{methodnamehyphen}$',

557
docs/api-guide/schemas.md Normal file
View File

@ -0,0 +1,557 @@
source: schemas.py
# Schemas
> A machine-readable [schema] describes what resources are available via the API, what their URLs are, how they are represented and what operations they support.
>
> &mdash; Heroku, [JSON Schema for the Heroku Platform API][cite]
API schemas are a useful tool that allow for a range of use cases, including
generating reference documentation, or driving dynamic client libraries that
can interact with your API.
## Representing schemas internally
REST framework uses [Core API][coreapi] in order to model schema information in
a format-independent representation. This information can then be rendered
into various different schema formats, or used to generate API documentation.
When using Core API, a schema is represented as a `Document` which is the
top-level container object for information about the API. Available API
interactions are represented using `Link` objects. Each link includes a URL,
HTTP method, and may include a list of `Field` instances, which describe any
parameters that may be accepted by the API endpoint. The `Link` and `Field`
instances may also include descriptions, that allow an API schema to be
rendered into user documentation.
Here's an example of an API description that includes a single `search`
endpoint:
coreapi.Document(
title='Flight Search API',
url='https://api.example.org/',
content={
'search': coreapi.Link(
url='/search/',
action='get',
fields=[
coreapi.Field(
name='from',
required=True,
location='query',
description='City name or airport code.'
),
coreapi.Field(
name='to',
required=True,
location='query',
description='City name or airport code.'
),
coreapi.Field(
name='date',
required=True,
location='query',
description='Flight date in "YYYY-MM-DD" format.'
)
],
description='Return flight availability and prices.'
)
}
)
## Schema output formats
In order to be presented in an HTTP response, the internal representation
has to be rendered into the actual bytes that are used in the response.
[Core JSON][corejson] is designed as a canonical format for use with Core API.
REST framework includes a renderer class for handling this media type, which
is available as `renderers.CoreJSONRenderer`.
Other schema formats such as [Open API][open-api] ("Swagger"),
[JSON HyperSchema][json-hyperschema], or [API Blueprint][api-blueprint] can
also be supported by implementing a custom renderer class.
## Schemas vs Hypermedia
It's worth pointing out here that Core API can also be used to model hypermedia
responses, which present an alternative interaction style to API schemas.
With an API schema, the entire available interface is presented up-front
as a single endpoint. Responses to individual API endpoints are then typically
presented as plain data, without any further interactions contained in each
response.
With Hypermedia, the client is instead presented with a document containing
both data and available interactions. Each interaction results in a new
document, detailing both the current state and the available interactions.
Further information and support on building Hypermedia APIs with REST framework
is planned for a future version.
---
# Adding a schema
You'll need to install the `coreapi` package in order to add schema support
for REST framework.
pip install coreapi
REST framework includes functionality for auto-generating a schema,
or allows you to specify one explicitly. There are a few different ways to
add a schema to your API, depending on exactly what you need.
## The get_schema_view shortcut
The simplest way to include a schema in your project is to use the
`get_schema_view()` function.
schema_view = get_schema_view(title="Server Monitoring API")
urlpatterns = [
url('^$', schema_view),
...
]
Once the view has been added, you'll be able to make API requests to retrieve
the auto-generated schema definition.
$ http http://127.0.0.1:8000/ Accept:application/vnd.coreapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/vnd.coreapi+json
{
"_meta": {
"title": "Server Monitoring API"
},
"_type": "document",
...
}
The arguments to `get_schema_view()` are:
#### `title`
May be used to provide a descriptive title for the schema definition.
#### `url`
May be used to pass a canonical URL for the schema.
schema_view = get_schema_view(
title='Server Monitoring API',
url='https://www.example.org/api/'
)
#### `urlconf`
A string representing the import path to the URL conf that you want
to generate an API schema for. This defaults to the value of Django's
ROOT_URLCONF setting.
schema_view = get_schema_view(
title='Server Monitoring API',
url='https://www.example.org/api/',
urlconf='myproject.urls'
)
#### `renderer_classes`
May be used to pass the set of renderer classes that can be used to render the API root endpoint.
from rest_framework.renderers import CoreJSONRenderer
from my_custom_package import APIBlueprintRenderer
schema_view = get_schema_view(
title='Server Monitoring API',
url='https://www.example.org/api/',
renderer_classes=[CoreJSONRenderer, APIBlueprintRenderer]
)
## Using an explicit schema view
If you need a little more control than the `get_schema_view()` shortcut gives you,
then you can use the `SchemaGenerator` class directly to auto-generate the
`Document` instance, and to return that from a view.
This option gives you the flexibility of setting up the schema endpoint
with whatever behaviour you want. For example, you can apply different
permission, throttling, or authentication policies to the schema endpoint.
Here's an example of using `SchemaGenerator` together with a view to
return the schema.
**views.py:**
from rest_framework.decorators import api_view, renderer_classes
from rest_framework import renderers, response, schemas
generator = schemas.SchemaGenerator(title='Bookings API')
@api_view()
@renderer_classes([renderers.CoreJSONRenderer])
def schema_view(request):
schema = generator.get_schema(request)
return response.Response(schema)
**urls.py:**
urlpatterns = [
url('/', schema_view),
...
]
You can also serve different schemas to different users, depending on the
permissions they have available. This approach can be used to ensure that
unauthenticated requests are presented with a different schema to
authenticated requests, or to ensure that different parts of the API are
made visible to different users depending on their role.
In order to present a schema with endpoints filtered by user permissions,
you need to pass the `request` argument to the `get_schema()` method, like so:
@api_view()
@renderer_classes([renderers.CoreJSONRenderer])
def schema_view(request):
generator = schemas.SchemaGenerator(title='Bookings API')
return response.Response(generator.get_schema(request=request))
## Explicit schema definition
An alternative to the auto-generated approach is to specify the API schema
explicitly, by declaring a `Document` object in your codebase. Doing so is a
little more work, but ensures that you have full control over the schema
representation.
import coreapi
from rest_framework.decorators import api_view, renderer_classes
from rest_framework import renderers, response
schema = coreapi.Document(
title='Bookings API',
content={
...
}
)
@api_view()
@renderer_classes([renderers.CoreJSONRenderer])
def schema_view(request):
return response.Response(schema)
## Static schema file
A final option is to write your API schema as a static file, using one
of the available formats, such as Core JSON or Open API.
You could then either:
* Write a schema definition as a static file, and [serve the static file directly][static-files].
* Write a schema definition that is loaded using `Core API`, and then
rendered to one of many available formats, depending on the client request.
---
# Schemas as documentation
One common usage of API schemas is to use them to build documentation pages.
The schema generation in REST framework uses docstrings to automatically
populate descriptions in the schema document.
These descriptions will be based on:
* The corresponding method docstring if one exists.
* A named section within the class docstring, which can be either single line or multi-line.
* The class docstring.
## Examples
An `APIView`, with an explicit method docstring.
class ListUsernames(APIView):
def get(self, request):
"""
Return a list of all user names in the system.
"""
usernames = [user.username for user in User.objects.all()]
return Response(usernames)
A `ViewSet`, with an explict action docstring.
class ListUsernames(ViewSet):
def list(self, request):
"""
Return a list of all user names in the system.
"""
usernames = [user.username for user in User.objects.all()]
return Response(usernames)
A generic view with sections in the class docstring, using single-line style.
class UserList(generics.ListCreateAPIView):
"""
get: List all the users.
post: Create a new user.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsAdminUser,)
A generic viewset with sections in the class docstring, using multi-line style.
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
retrieve:
Return a user instance.
list:
Return all users, ordered by most recently joined.
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
---
# Alternate schema formats
In order to support an alternate schema format, you need to implement a custom renderer
class that handles converting a `Document` instance into a bytestring representation.
If there is a Core API codec package that supports encoding into the format you
want to use then implementing the renderer class can be done by using the codec.
## Example
For example, the `openapi_codec` package provides support for encoding or decoding
to the Open API ("Swagger") format:
from rest_framework import renderers
from openapi_codec import OpenAPICodec
class SwaggerRenderer(renderers.BaseRenderer):
media_type = 'application/openapi+json'
format = 'swagger'
def render(self, data, media_type=None, renderer_context=None):
codec = OpenAPICodec()
return codec.dump(data)
---
# API Reference
## SchemaGenerator
A class that deals with introspecting your API views, which can be used to
generate a schema.
Typically you'll instantiate `SchemaGenerator` with a single argument, like so:
generator = SchemaGenerator(title='Stock Prices API')
Arguments:
* `title` **required** - The name of the API.
* `url` - The root URL of the API schema. This option is not required unless the schema is included under path prefix.
* `patterns` - A list of URLs to inspect when generating the schema. Defaults to the project's URL conf.
* `urlconf` - A URL conf module name to use when generating the schema. Defaults to `settings.ROOT_URLCONF`.
### get_schema(self, request)
Returns a `coreapi.Document` instance that represents the API schema.
@api_view
@renderer_classes([renderers.CoreJSONRenderer])
def schema_view(request):
generator = schemas.SchemaGenerator(title='Bookings API')
return Response(generator.get_schema())
The `request` argument is optional, and may be used if you want to apply per-user
permissions to the resulting schema generation.
### get_links(self, request)
Return a nested dictionary containing all the links that should be included in the API schema.
This is a good point to override if you want to modify the resulting structure of the generated schema,
as you can build a new dictionary with a different layout.
### get_link(self, path, method, view)
Returns a `coreapi.Link` instance corresponding to the given view.
You can override this if you need to provide custom behaviors for particular views.
### get_description(self, path, method, view)
Returns a string to use as the link description. By default this is based on the
view docstring as described in the "Schemas as Documentation" section above.
### get_encoding(self, path, method, view)
Returns a string to indicate the encoding for any request body, when interacting
with the given view. Eg. `'application/json'`. May return a blank string for views
that do not expect a request body.
### get_path_fields(self, path, method, view):
Return a list of `coreapi.Link()` instances. One for each path parameter in the URL.
### get_serializer_fields(self, path, method, view)
Return a list of `coreapi.Link()` instances. One for each field in the serializer class used by the view.
### get_pagination_fields(self, path, method, view
Return a list of `coreapi.Link()` instances, as returned by the `get_schema_fields()` method on any pagination class used by the view.
### get_filter_fields(self, path, method, view)
Return a list of `coreapi.Link()` instances, as returned by the `get_schema_fields()` method of any filter classes used by the view.
---
## Core API
This documentation gives a brief overview of the components within the `coreapi`
package that are used to represent an API schema.
Note that these classes are imported from the `coreapi` package, rather than
from the `rest_framework` package.
### Document
Represents a container for the API schema.
#### `title`
A name for the API.
#### `url`
A canonical URL for the API.
#### `content`
A dictionary, containing the `Link` objects that the schema contains.
In order to provide more structure to the schema, the `content` dictionary
may be nested, typically to a second level. For example:
content={
"bookings": {
"list": Link(...),
"create": Link(...),
...
},
"venues": {
"list": Link(...),
...
},
...
}
### Link
Represents an individual API endpoint.
#### `url`
The URL of the endpoint. May be a URI template, such as `/users/{username}/`.
#### `action`
The HTTP method associated with the endpoint. Note that URLs that support
more than one HTTP method, should correspond to a single `Link` for each.
#### `fields`
A list of `Field` instances, describing the available parameters on the input.
#### `description`
A short description of the meaning and intended usage of the endpoint.
### Field
Represents a single input parameter on a given API endpoint.
#### `name`
A descriptive name for the input.
#### `required`
A boolean, indicated if the client is required to included a value, or if
the parameter can be omitted.
#### `location`
Determines how the information is encoded into the request. Should be one of
the following strings:
**"path"**
Included in a templated URI. For example a `url` value of `/products/{product_code}/` could be used together with a `"path"` field, to handle API inputs in a URL path such as `/products/slim-fit-jeans/`.
These fields will normally correspond with [named arguments in the project URL conf][named-arguments].
**"query"**
Included as a URL query parameter. For example `?search=sale`. Typically for `GET` requests.
These fields will normally correspond with pagination and filtering controls on a view.
**"form"**
Included in the request body, as a single item of a JSON object or HTML form. For example `{"colour": "blue", ...}`. Typically for `POST`, `PUT` and `PATCH` requests. Multiple `"form"` fields may be included on a single link.
These fields will normally correspond with serializer fields on a view.
**"body"**
Included as the complete request body. Typically for `POST`, `PUT` and `PATCH` requests. No more than one `"body"` field may exist on a link. May not be used together with `"form"` fields.
These fields will normally correspond with views that use `ListSerializer` to validate the request input, or with file upload views.
#### `encoding`
**"application/json"**
JSON encoded request content. Corresponds to views using `JSONParser`.
Valid only if either one or more `location="form"` fields, or a single
`location="body"` field is included on the `Link`.
**"multipart/form-data"**
Multipart encoded request content. Corresponds to views using `MultiPartParser`.
Valid only if one or more `location="form"` fields is included on the `Link`.
**"application/x-www-form-urlencoded"**
URL encoded request content. Corresponds to views using `FormParser`. Valid
only if one or more `location="form"` fields is included on the `Link`.
**"application/octet-stream"**
Binary upload request content. Corresponds to views using `FileUploadParser`.
Valid only if a `location="body"` field is included on the `Link`.
#### `description`
A short description of the meaning and intended usage of the input field.
[cite]: https://blog.heroku.com/archives/2014/1/8/json_schema_for_heroku_platform_api
[coreapi]: http://www.coreapi.org/
[corejson]: http://www.coreapi.org/specification/encoding/#core-json-encoding
[open-api]: https://openapis.org/
[json-hyperschema]: http://json-schema.org/latest/json-schema-hypermedia.html
[api-blueprint]: https://apiblueprint.org/
[static-files]: https://docs.djangoproject.com/en/stable/howto/static-files/
[named-arguments]: https://docs.djangoproject.com/en/stable/topics/http/urls/#named-groups

View File

@ -442,7 +442,7 @@ Declaring a `ModelSerializer` looks like this:
By default, all the model fields on the class will be mapped to a corresponding serializer fields.
Any relationships such as foreign keys on the model will be mapped to `PrimaryKeyRelatedField`. Reverse relationships are not included by default unless explicitly included as described below.
Any relationships such as foreign keys on the model will be mapped to `PrimaryKeyRelatedField`. Reverse relationships are not included by default unless explicitly included as specified in the [serializer relations][relations] documentation.
#### Inspecting a `ModelSerializer`
@ -452,7 +452,7 @@ To do so, open the Django shell, using `python manage.py shell`, then import the
>>> from myapp.serializers import AccountSerializer
>>> serializer = AccountSerializer()
>>> print repr(serializer) # Or `print(repr(serializer))` in Python 3.x.
>>> print(repr(serializer))
AccountSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(allow_blank=True, max_length=100, required=False)
@ -578,16 +578,6 @@ Alternative representations include serializing using hyperlinks, serializing co
For full details see the [serializer relations][relations] documentation.
## Inheritance of the 'Meta' class
The inner `Meta` class on serializers is not inherited from parent classes by default. This is the same behavior as with Django's `Model` and `ModelForm` classes. If you want the `Meta` class to inherit from a parent class you must do so explicitly. For example:
class AccountSerializer(MyBaseSerializer):
class Meta(MyBaseSerializer.Meta):
model = Account
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
## Customizing field mappings
The ModelSerializer class also exposes an API that you can override in order to alter how serializer fields are automatically determined when instantiating the serializer.
@ -678,6 +668,25 @@ You can explicitly include the primary key by adding it to the `fields` option,
model = Account
fields = ('url', 'id', 'account_name', 'users', 'created')
## Absolute and relative URLs
When instantiating a `HyperlinkedModelSerializer` you must include the current
`request` in the serializer context, for example:
serializer = AccountSerializer(queryset, context={'request': request})
Doing so will ensure that the hyperlinks can include an appropriate hostname,
so that the resulting representation uses fully qualified URLs, such as:
http://api.example.com/accounts/1/
Rather than relative URLs, such as:
/accounts/1/
If you *do* want to use relative URLs, you should explicitly pass `{'request': None}`
in the serializer context.
## How hyperlinked views are determined
There needs to be a way of determining which views should be used for hyperlinking to model instances.
@ -731,9 +740,17 @@ The `ListSerializer` class provides the behavior for serializing and validating
When a serializer is instantiated and `many=True` is passed, a `ListSerializer` instance will be created. The serializer class then becomes a child of the parent `ListSerializer`
The following argument can also be passed to a `ListSerializer` field or a serializer that is passed `many=True`:
### `allow_empty`
This is `True` by default, but can be set to `False` if you want to disallow empty lists as valid input.
### Customizing `ListSerializer` behavior
There *are* a few use cases when you might want to customize the `ListSerializer` behavior. For example:
* You want to provide particular validation of the lists, such as always ensuring that there is at least one element in a list.
* You want to provide particular validation of the lists, such as checking that one element does not conflict with another element in a list.
* You want to customize the create or update behavior of multiple objects.
For these cases you can modify the class that is used when `many=True` is passed, by using the `list_serializer_class` option on the serializer `Meta` class.
@ -849,7 +866,7 @@ There are four methods that can be overridden, depending on what functionality y
* `.to_internal_value()` - Override this to support deserialization, for write operations.
* `.create()` and `.update()` - Override either or both of these to support saving instances.
Because this class provides the same interface as the `Serializer` class, you can use it with the existing generic class based views exactly as you would for a regular `Serializer` or `ModelSerializer`.
Because this class provides the same interface as the `Serializer` class, you can use it with the existing generic class-based views exactly as you would for a regular `Serializer` or `ModelSerializer`.
The only difference you'll notice when doing so is the `BaseSerializer` classes will not generate HTML forms in the browsable API. This is because the data they return does not include all the field information that would allow each field to be rendered into a suitable HTML input.
@ -998,6 +1015,40 @@ If any of the validation fails, then the method should raise a `serializers.Vali
The `data` argument passed to this method will normally be the value of `request.data`, so the datatype it provides will depend on the parser classes you have configured for your API.
## Serializer Inheritance
Similar to Django forms, you can extend and reuse serializers through inheritance. This allows you to declare a common set of fields or methods on a parent class that can then be used in a number of serializers. For example,
class MyBaseSerializer(Serializer):
my_field = serializers.CharField()
def validate_my_field(self):
...
class MySerializer(MyBaseSerializer):
...
Like Django's `Model` and `ModelForm` classes, the inner `Meta` class on serializers does not implicitly inherit from it's parents' inner `Meta` classes. If you want the `Meta` class to inherit from a parent class you must do so explicitly. For example:
class AccountSerializer(MyBaseSerializer):
class Meta(MyBaseSerializer.Meta):
model = Account
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
Additionally, the following caveats apply to serializer inheritance:
* Normal Python name resolution rules apply. If you have multiple base classes that declare a `Meta` inner class, only the first one will be used. This means the childs `Meta`, if it exists, otherwise the `Meta` of the first parent, etc.
* Its possible to declaratively remove a `Field` inherited from a parent class by setting the name to be `None` on the subclass.
class MyBaseSerializer(ModelSerializer):
my_field = serializers.CharField()
class MySerializer(MyBaseSerializer):
my_field = None
However, you can only use this technique to opt out from a field defined declaratively by a parent class; it wont prevent the `ModelSerializer` from generating a default field. To opt-out from default fields, see [Specifying which fields to include](#specifying-which-fields-to-include).
## Dynamically modifying fields
Once a serializer has been initialized, the dictionary of fields that are set on the serializer may be accessed using the `.fields` attribute. Accessing and modifying this attribute allows you to dynamically modify the serializer.
@ -1062,6 +1113,7 @@ The following third party packages are also available.
The [django-rest-marshmallow][django-rest-marshmallow] package provides an alternative implementation for serializers, using the python [marshmallow][marshmallow] library. It exposes the same API as the REST framework serializers, and can be used as a drop-in replacement in some use-cases.
## Serpy
The [serpy][serpy] package is an alternative implementation for serializers that is built for speed. [Serpy][serpy] serializes complex datatypes to simple native types. The native types can be easily converted to JSON or any other format needed.
## MongoengineModelSerializer
@ -1080,15 +1132,50 @@ The [django-rest-framework-hstore][django-rest-framework-hstore] package provide
The [dynamic-rest][dynamic-rest] package extends the ModelSerializer and ModelViewSet interfaces, adding API query parameters for filtering, sorting, and including / excluding all fields and relationships defined by your serializers.
## Dynamic Fields Mixin
The [drf-dynamic-fields][drf-dynamic-fields] package provides a mixin to dynamically limit the fields per serializer to a subset specified by an URL parameter.
## DRF FlexFields
The [drf-flex-fields][drf-flex-fields] package extends the ModelSerializer and ModelViewSet to provide commonly used functionality for dynamically setting fields and expanding primitive fields to nested models, both from URL parameters and your serializer class definitions.
## Serializer Extensions
The [django-rest-framework-serializer-extensions][drf-serializer-extensions]
package provides a collection of tools to DRY up your serializers, by allowing
fields to be defined on a per-view/request basis. Fields can be whitelisted,
blacklisted and child serializers can be optionally expanded.
## HTML JSON Forms
The [html-json-forms][html-json-forms] package provides an algorithm and serializer for processing `<form>` submissions per the (inactive) [HTML JSON Form specification][json-form-spec]. The serializer facilitates processing of arbitrarily nested JSON structures within HTML. For example, `<input name="items[0][id]" value="5">` will be interpreted as `{"items": [{"id": "5"}]}`.
## DRF-Base64
[DRF-Base64][drf-base64] provides a set of field and model serializers that handles the upload of base64-encoded files.
## QueryFields
[djangorestframework-queryfields][djangorestframework-queryfields] allows API clients to specify which fields will be sent in the response via inclusion/exclusion query parameters.
[cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion
[relations]: relations.md
[model-managers]: https://docs.djangoproject.com/en/dev/topics/db/managers/
[model-managers]: https://docs.djangoproject.com/en/stable/topics/db/managers/
[encapsulation-blogpost]: http://www.dabapps.com/blog/django-models-and-encapsulation/
[django-rest-marshmallow]: http://tomchristie.github.io/django-rest-marshmallow/
[marshmallow]: https://marshmallow.readthedocs.org/en/latest/
[marshmallow]: https://marshmallow.readthedocs.io/en/latest/
[serpy]: https://github.com/clarkduvall/serpy
[mongoengine]: https://github.com/umutbozkurt/django-rest-framework-mongoengine
[django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis
[django-rest-framework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore
[django-hstore]: https://github.com/djangonauts/django-hstore
[dynamic-rest]: https://github.com/AltSchool/dynamic-rest
[html-json-forms]: https://github.com/wq/html-json-forms
[drf-flex-fields]: https://github.com/rsinger86/drf-flex-fields
[json-form-spec]: https://www.w3.org/TR/html-json-forms/
[drf-dynamic-fields]: https://github.com/dbrgn/drf-dynamic-fields
[drf-base64]: https://bitbucket.org/levit_scs/drf_base64
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
[djangorestframework-queryfields]: http://djangorestframework-queryfields.readthedocs.io/

View File

@ -36,7 +36,7 @@ The `api_settings` object will check for any user-defined settings, and otherwis
## API policy settings
*The following settings control the basic API policies, and are applied to every `APIView` class based view, or `@api_view` function based view.*
*The following settings control the basic API policies, and are applied to every `APIView` class-based view, or `@api_view` function based view.*
#### DEFAULT_RENDERER_CLASSES
@ -98,13 +98,19 @@ Default: `'rest_framework.negotiation.DefaultContentNegotiation'`
## Generic view settings
*The following settings control the behavior of the generic class based views.*
*The following settings control the behavior of the generic class-based views.*
#### DEFAULT_PAGINATION_SERIALIZER_CLASS
A class the determines the default serialization style for paginated responses.
---
Default: `rest_framework.pagination.PaginationSerializer`
**This setting has been removed.**
The pagination API does not use serializers to determine the output format, and
you'll need to instead override the `get_paginated_response method on a
pagination class in order to specify how the output format is controlled.
---
#### DEFAULT_FILTER_BACKENDS
@ -113,6 +119,16 @@ If set to `None` then generic filtering is disabled.
#### PAGINATE_BY
---
**This setting has been removed.**
See the pagination documentation for further guidance on [setting the pagination style](pagination.md#modifying-the-pagination-style).
---
#### PAGE_SIZE
The default page size to use for pagination. If set to `None`, pagination is disabled by default.
Default: `None`
@ -121,27 +137,12 @@ Default: `None`
---
**This setting is pending deprecation.**
**This setting has been removed.**
See the pagination documentation for further guidance on [setting the pagination style](pagination.md#modifying-the-pagination-style).
---
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.
For example, given the following settings:
REST_FRAMEWORK = {
'PAGINATE_BY': 10,
'PAGINATE_BY_PARAM': 'page_size',
}
A client would be able to modify the pagination size by using the `page_size` query parameter. For example:
GET http://example.com/api/accounts?page_size=25
Default: `None`
#### MAX_PAGINATE_BY
---
@ -152,22 +153,6 @@ See the pagination documentation for further guidance on [setting the pagination
---
The maximum page size to allow when the page size is specified by the client. If set to `None`, then no maximum limit is applied.
For example, given the following settings:
REST_FRAMEWORK = {
'PAGINATE_BY': 10,
'PAGINATE_BY_PARAM': 'page_size',
'MAX_PAGINATE_BY': 100
}
A client request like the following would return a paginated list of up to 100 items.
GET http://example.com/api/accounts?page_size=999
Default: `None`
### SEARCH_PARAM
The name of a query parameter, which can be used to specify the search term used by `SearchFilter`.
@ -196,7 +181,7 @@ If set, this value will restrict the set of versions that may be returned by the
Default: `None`
#### VERSION_PARAMETER
#### VERSION_PARAM
The string that should used for any versioning parameters, such as in the media type or URL query parameters.
@ -249,6 +234,28 @@ Default:
---
## Schema generation controls
#### SCHEMA_COERCE_PATH_PK
If set, this maps the `'pk'` identifier in the URL conf onto the actual field
name when generating a schema path parameter. Typically this will be `'id'`.
This gives a more suitable representation as "primary key" is an implementation
detail, whereas "identifier" is a more general concept.
Default: `True`
#### SCHEMA_COERCE_METHOD_NAMES
If set, this is used to map internal viewset method names onto external action
names used in the schema generation. This allows us to generate names that
are more suitable for an external representation than those that are used
internally in the codebase.
Default: `{'retrieve': 'read', 'destroy': 'delete'}`
---
## Content type controls
#### URL_FORMAT_OVERRIDE
@ -397,6 +404,22 @@ This should be a function with the following signature:
Default: `'rest_framework.views.get_view_description'`
## HTML Select Field cutoffs
Global settings for [select field cutoffs for rendering relational fields](relations.md#select-field-cutoffs) in the browsable API.
#### HTML_SELECT_CUTOFF
Global setting for the `html_cutoff` value. Must be an integer.
Default: 1000
#### HTML_SELECT_CUTOFF_TEXT
A string representing a global setting for `html_cutoff_text`.
Default: `"More than {count} items..."`
---
## Miscellaneous settings
@ -433,7 +456,7 @@ An integer of 0 or more, that may be used to specify the number of application p
Default: `None`
[cite]: http://www.python.org/dev/peps/pep-0020/
[cite]: https://www.python.org/dev/peps/pep-0020/
[rfc4627]: http://www.ietf.org/rfc/rfc4627.txt
[heroku-minified-json]: https://github.com/interagent/http-api-design#keep-json-minified-in-all-responses
[strftime]: http://docs.python.org/2/library/time.html#time.strftime
[strftime]: https://docs.python.org/3/library/time.html#time.strftime

View File

@ -50,6 +50,7 @@ This class of status code indicates that the client's request was successfully r
HTTP_204_NO_CONTENT
HTTP_205_RESET_CONTENT
HTTP_206_PARTIAL_CONTENT
HTTP_207_MULTI_STATUS
## Redirection - 3xx
@ -86,6 +87,9 @@ The 4xx class of status code is intended for cases in which the client seems to
HTTP_415_UNSUPPORTED_MEDIA_TYPE
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE
HTTP_417_EXPECTATION_FAILED
HTTP_422_UNPROCESSABLE_ENTITY
HTTP_423_LOCKED
HTTP_424_FAILED_DEPENDENCY
HTTP_428_PRECONDITION_REQUIRED
HTTP_429_TOO_MANY_REQUESTS
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE
@ -101,6 +105,7 @@ Response status codes beginning with the digit "5" indicate cases in which the s
HTTP_503_SERVICE_UNAVAILABLE
HTTP_504_GATEWAY_TIMEOUT
HTTP_505_HTTP_VERSION_NOT_SUPPORTED
HTTP_507_INSUFFICIENT_STORAGE
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED
## Helper functions

View File

@ -184,6 +184,110 @@ As usual CSRF validation will only apply to any session authenticated views. Th
---
# RequestsClient
REST framework also includes a client for interacting with your application
using the popular Python library, `requests`. This may be useful if:
* You are expecting to interface with the API primarily from another Python service,
and want to test the service at the same level as the client will see.
* You want to write tests in such a way that they can also be run against a staging or
live environment. (See "Live tests" below.)
This exposes exactly the same interface as if you were using a requests session
directly.
client = RequestsClient()
response = client.get('http://testserver/users/')
assert response.status_code == 200
Note that the requests client requires you to pass fully qualified URLs.
## `RequestsClient` and working with the database
The `RequestsClient` class is useful if you want to write tests that solely interact with the service interface. This is a little stricter than using the standard Django test client, as it means that all interactions should be via the API.
If you're using `RequestsClient` you'll want to ensure that test setup, and results assertions are performed as regular API calls, rather than interacting with the database models directly. For example, rather than checking that `Customer.objects.count() == 3` you would list the customers endpoint, and ensure that it contains three records.
## Headers & Authentication
Custom headers and authentication credentials can be provided in the same way
as [when using a standard `requests.Session` instance](http://docs.python-requests.org/en/master/user/advanced/#session-objects).
from requests.auth import HTTPBasicAuth
client.auth = HTTPBasicAuth('user', 'pass')
client.headers.update({'x-test': 'true'})
## CSRF
If you're using `SessionAuthentication` then you'll need to include a CSRF token
for any `POST`, `PUT`, `PATCH` or `DELETE` requests.
You can do so by following the same flow that a JavaScript based client would use.
First make a `GET` request in order to obtain a CRSF token, then present that
token in the following request.
For example...
client = RequestsClient()
# Obtain a CSRF token.
response = client.get('/homepage/')
assert response.status_code == 200
csrftoken = response.cookies['csrftoken']
# Interact with the API.
response = client.post('/organisations/', json={
'name': 'MegaCorp',
'status': 'active'
}, headers={'X-CSRFToken': csrftoken})
assert response.status_code == 200
## Live tests
With careful usage both the `RequestsClient` and the `CoreAPIClient` provide
the ability to write test cases that can run either in development, or be run
directly against your staging server or production environment.
Using this style to create basic tests of a few core piece of functionality is
a powerful way to validate your live service. Doing so may require some careful
attention to setup and teardown to ensure that the tests run in a way that they
do not directly affect customer data.
---
# CoreAPIClient
The CoreAPIClient allows you to interact with your API using the Python
`coreapi` client library.
# Fetch the API schema
client = CoreAPIClient()
schema = client.get('http://testserver/schema/')
# Create a new organisation
params = {'name': 'MegaCorp', 'status': 'active'}
client.action(schema, ['organisations', 'create'], params)
# Ensure that the organisation exists in the listing
data = client.action(schema, ['organisations', 'list'])
assert(len(data) == 1)
assert(data == [{'name': 'MegaCorp', 'status': 'active'}])
## Headers & Authentication
Custom headers and authentication may be used with `CoreAPIClient` in a
similar way as with `RequestsClient`.
from requests.auth import HTTPBasicAuth
client = CoreAPIClient()
client.session.auth = HTTPBasicAuth('user', 'pass')
client.session.headers.update({'x-test': 'true'})
---
# Test cases
REST framework includes the following test case classes, that mirror the existing Django test case classes, but use `APIClient` instead of Django's default `Client`.
@ -197,7 +301,7 @@ REST framework includes the following test case classes, that mirror the existin
You can use any of REST framework's test case classes as you would for the regular Django test case classes. The `self.client` attribute will be an `APIClient` instance.
from django.core.urlresolvers import reverse
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from myproject.apps.core.models import Account
@ -271,6 +375,6 @@ For example, to add support for using `format='html'` in test requests, you migh
}
[cite]: http://jacobian.org/writing/django-apps-with-buildout/#s-create-a-test-wrapper
[client]: https://docs.djangoproject.com/en/dev/topics/testing/tools/#the-test-client
[requestfactory]: https://docs.djangoproject.com/en/dev/topics/testing/advanced/#django.test.client.RequestFactory
[client]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#the-test-client
[requestfactory]: https://docs.djangoproject.com/en/stable/topics/testing/advanced/#django.test.client.RequestFactory
[configuration]: #configuration

View File

@ -41,7 +41,7 @@ The default throttling policy may be set globally, using the `DEFAULT_THROTTLE_C
The rate descriptions used in `DEFAULT_THROTTLE_RATES` may include `second`, `minute`, `hour` or `day` as the throttle period.
You can also set the throttling policy on a per-view or per-viewset basis,
using the `APIView` class based views.
using the `APIView` class-based views.
from rest_framework.response import Response
from rest_framework.throttling import UserRateThrottle
@ -184,12 +184,14 @@ If the `.wait()` method is implemented and the request is throttled, then a `Ret
The following is an example of a rate throttle, that will randomly throttle 1 in every 10 requests.
import random
class RandomRateThrottle(throttling.BaseThrottle):
def allow_request(self, request, view):
return random.randint(1, 10) == 1
return random.randint(1, 10) != 1
[cite]: https://dev.twitter.com/docs/error-codes-responses
[permissions]: permissions.md
[identifing-clients]: http://oxpedia.org/wiki/index.php?title=AppSuite:Grizzly#Multiple_Proxies_in_front_of_the_cluster
[cache-setting]: https://docs.djangoproject.com/en/dev/ref/settings/#caches
[cache-docs]: https://docs.djangoproject.com/en/dev/topics/cache/#setting-up-the-cache
[cache-setting]: https://docs.djangoproject.com/en/stable/ref/settings/#caches
[cache-docs]: https://docs.djangoproject.com/en/stable/topics/cache/#setting-up-the-cache

View File

@ -20,7 +20,7 @@ With `ModelForm` the validation is performed partially on the form, and partiall
* It is easy to switch between using shortcut `ModelSerializer` classes and using explicit `Serializer` classes. Any validation behavior being used for `ModelSerializer` is simple to replicate.
* Printing the `repr` of a serializer instance will show you exactly what validation rules it applies. There's no extra hidden validation behavior being called on the model instance.
When you're using `ModelSerializer` all of this is handled automatically for you. If you want to drop down to using a `Serializer` classes instead, then you need to define the validation rules explicitly.
When you're using `ModelSerializer` all of this is handled automatically for you. If you want to drop down to using `Serializer` classes instead, then you need to define the validation rules explicitly.
#### Example
@ -61,9 +61,12 @@ It takes a single required argument, and an optional `messages` argument:
* `queryset` *required* - This is the queryset against which uniqueness should be enforced.
* `message` - The error message that should be used when validation fails.
* `lookup` - The lookup used to find an existing instance with the value being validated. Defaults to `'exact'`.
This validator should be applied to *serializer fields*, like so:
from rest_framework.validators import UniqueValidator
slug = SlugField(
max_length=100,
validators=[UniqueValidator(queryset=BlogPost.objects.all())]
@ -80,6 +83,8 @@ It has two required arguments, and a single optional `messages` argument:
The validator should be applied to *serializer classes*, like so:
from rest_framework.validators import UniqueTogetherValidator
class ExampleSerializer(serializers.Serializer):
# ...
class Meta:
@ -114,6 +119,8 @@ These validators can be used to enforce the `unique_for_date`, `unique_for_month
The validator should be applied to *serializer classes*, like so:
from rest_framework.validators import UniqueForYearValidator
class ExampleSerializer(serializers.Serializer):
# ...
class Meta:
@ -146,17 +153,17 @@ The field will not be writable to the user, but the default value will still be
#### Using with a hidden date field.
If you want the date field to be entirely hidden from the user, then use `HiddenField`. This field type does not accept user input, but instead always returns it's default value to the `validated_data` in the serializer.
If you want the date field to be entirely hidden from the user, then use `HiddenField`. This field type does not accept user input, but instead always returns its default value to the `validated_data` in the serializer.
published = serializers.HiddenField(default=timezone.now)
---
**Note**: The `UniqueFor<Range>Validation` classes always imposes an implicit constraint that the fields they are applied to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input.
**Note**: The `UniqueFor<Range>Validation` classes impose an implicit constraint that the fields they are applied to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input.
---
# Advanced 'default' argument usage
# Advanced field defaults
Validators that are applied across multiple fields in the serializer can sometimes require a field input that should not be provided by the API client, but that *is* available as input to the validator.
@ -183,11 +190,76 @@ It takes a single argument, which is the default value or callable that should b
created_at = serializers.DateTimeField(
read_only=True,
default=CreateOnlyDefault(timezone.now)
default=serializers.CreateOnlyDefault(timezone.now)
)
---
# Limitations of validators
There are some ambiguous cases where you'll need to instead handle validation
explicitly, rather than relying on the default serializer classes that
`ModelSerializer` generates.
In these cases you may want to disable the automatically generated validators,
by specifying an empty list for the serializer `Meta.validators` attribute.
## Optional fields
By default "unique together" validation enforces that all fields be
`required=True`. In some cases, you might want to explicit apply
`required=False` to one of the fields, in which case the desired behaviour
of the validation is ambiguous.
In this case you will typically need to exclude the validator from the
serializer class, and instead write any validation logic explicitly, either
in the `.validate()` method, or else in the view.
For example:
class BillingRecordSerializer(serializers.ModelSerializer):
def validate(self, data):
# Apply custom validation either here, or in the view.
class Meta:
fields = ('client', 'date', 'amount')
extra_kwargs = {'client': {'required': 'False'}}
validators = [] # Remove a default "unique together" constraint.
## Updating nested serializers
When applying an update to an existing instance, uniqueness validators will
exclude the current instance from the uniqueness check. The current instance
is available in the context of the uniqueness check, because it exists as
an attribute on the serializer, having initially been passed using
`instance=...` when instantiating the serializer.
In the case of update operations on *nested* serializers there's no way of
applying this exclusion, because the instance is not available.
Again, you'll probably want to explicitly remove the validator from the
serializer class, and write the code the for the validation constraint
explicitly, in a `.validate()` method, or in the view.
## Debugging complex cases
If you're not sure exactly what behavior a `ModelSerializer` class will
generate it is usually a good idea to run `manage.py shell`, and print
an instance of the serializer, so that you can inspect the fields and
validators that it automatically generates for you.
>>> serializer = MyComplexModelSerializer()
>>> print(serializer)
class MyComplexModelSerializer:
my_fields = ...
Also keep in mind that with complex cases it can often be better to explicitly
define your serializer classes, rather than relying on the default
`ModelSerializer` behavior. This involves a little more code, but ensures
that the resulting behavior is more transparent.
---
# Writing custom validators
You can use any of Django's existing validators, or write your own custom validators.
@ -200,9 +272,15 @@ A validator may be any callable that raises a `serializers.ValidationError` on f
if value % 2 != 0:
raise serializers.ValidationError('This field must be an even number.')
## Class based
#### Field-level validation
To write a class based validator, use the `__call__` method. Class based validators are useful as they allow you to parameterize and reuse behavior.
You can specify custom field-level validation by adding `.validate_<field_name>` methods
to your `Serializer` subclass. This is documented in the
[Serializer docs](http://www.django-rest-framework.org/api-guide/serializers/#field-level-validation)
## Class-based
To write a class-based validator, use the `__call__` method. Class-based validators are useful as they allow you to parameterize and reuse behavior.
class MultipleOf(object):
def __init__(self, base):
@ -215,11 +293,11 @@ To write a class based validator, use the `__call__` method. Class based validat
#### Using `set_context()`
In some advanced cases you might want a validator to be passed the serializer field it is being used with as additional context. You can do so by declaring a `set_context` method on a class based validator.
In some advanced cases you might want a validator to be passed the serializer field it is being used with as additional context. You can do so by declaring a `set_context` method on a class-based validator.
def set_context(self, serializer_field):
# Determine if this is an update or a create operation.
# In `__call__` we can then use that information to modify the validation behavior.
self.is_update = serializer_field.parent.instance is not None
[cite]: https://docs.djangoproject.com/en/dev/ref/validators/
[cite]: https://docs.djangoproject.com/en/stable/ref/validators/

View File

@ -71,8 +71,8 @@ You can also set the versioning scheme on an individual view. Typically you won'
The following settings keys are also used to control versioning:
* `DEFAULT_VERSION`. The value that should be used for `request.version` when no versioning information is present. Defaults to `None`.
* `ALLOWED_VERSIONS`. If set, this value will restrict the set of versions that may be returned by the versioning scheme, and will raise an error if the provided version if not in this set. Note that the value used for the `DEFAULT_VERSION` setting is always considered to be part of the `ALLOWED_VERSIONS` set. Defaults to `None`.
* `VERSION_PARAM`. The string that should used for any versioning parameters, such as in the media type or URL query parameters. Defaults to `'version'`.
* `ALLOWED_VERSIONS`. If set, this value will restrict the set of versions that may be returned by the versioning scheme, and will raise an error if the provided version is not in this set. Note that the value used for the `DEFAULT_VERSION` setting is always considered to be part of the `ALLOWED_VERSIONS` set (unless it is `None`). Defaults to `None`.
* `VERSION_PARAM`. The string that should be used for any versioning parameters, such as in the media type or URL query parameters. Defaults to `'version'`.
You can also set your versioning class plus those three values on a per-view or a per-viewset basis by defining your own versioning scheme and using the `default_version`, `allowed_versions` and `version_param` class variables. For example, if you want to use `URLPathVersioning`:

View File

@ -1,9 +1,9 @@
source: decorators.py
views.py
# Class Based Views
# Class-based Views
> Django's class based views are a welcome departure from the old-style views.
> Django's class-based views are a welcome departure from the old-style views.
>
> &mdash; [Reinout van Rees][cite]
@ -73,6 +73,8 @@ The following methods are used by REST framework to instantiate the various plug
### .get_content_negotiator(self)
### .get_exception_handler(self)
## API policy implementation methods
The following methods are called before dispatching to the handler method.
@ -119,7 +121,7 @@ You won't typically need to override this method.
# Function Based Views
> Saying [that Class based views] is always the superior solution is a mistake.
> Saying [that class-based views] is always the superior solution is a mistake.
>
> &mdash; [Nick Coghlan][cite2]
@ -127,7 +129,7 @@ REST framework also allows you to work with regular function based views. It pr
## @api_view()
**Signature:** `@api_view(http_method_names=['GET'])`
**Signature:** `@api_view(http_method_names=['GET'], exclude_from_schema=False)`
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:
@ -139,7 +141,7 @@ The core of this functionality is the `api_view` decorator, which takes a list o
This view will use the default renderers, parsers, authentication classes etc specified in the [settings].
By default only `GET` methods will be accepted. Other methods will respond with "405 Method Not Allowed". To alter this behavior, specify which methods the view allows, like so:
By default only `GET` methods will be accepted. Other methods will respond with "405 Method Not Allowed". To alter this behaviour, specify which methods the view allows, like so:
@api_view(['GET', 'POST'])
def hello_world(request):
@ -147,6 +149,13 @@ By default only `GET` methods will be accepted. Other methods will respond with
return Response({"message": "Got some data!", "data": request.data})
return Response({"message": "Hello, world!"})
You can also mark an API view as being omitted from any [auto-generated schema][schemas],
using the `exclude_from_schema` argument.:
@api_view(['GET'], exclude_from_schema=True)
def api_docs(request):
...
## 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:
@ -178,3 +187,4 @@ Each of these decorators takes a single argument which must be a list or tuple o
[cite2]: http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html
[settings]: settings.md
[throttling]: throttling.md
[schemas]: schemas.md

View File

@ -179,7 +179,7 @@ In order to use a `GenericViewSet` class you'll override the class and either mi
The `ModelViewSet` class inherits from `GenericAPIView` and includes implementations for various actions, by mixing in the behavior of the various mixin classes.
The actions provided by the `ModelViewSet` class are `.list()`, `.retrieve()`, `.create()`, `.update()`, and `.destroy()`.
The actions provided by the `ModelViewSet` class are `.list()`, `.retrieve()`, `.create()`, `.update()`, `.partial_update()`, and `.destroy()`.
#### Example

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/img/raml.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
docs/img/rover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -1,3 +1,22 @@
<style>
.promo li a {
float: left;
width: 130px;
height: 20px;
text-align: center;
margin: 10px 30px;
padding: 150px 0 0 0;
background-position: 0 50%;
background-size: 130px auto;
background-repeat: no-repeat;
font-size: 120%;
color: black;
}
.promo li {
list-style: none;
}
</style>
<p class="badges" height=20px>
<iframe src="http://ghbtns.com/github-btn.html?user=tomchristie&amp;repo=django-rest-framework&amp;type=watch&amp;count=true" class="github-star-button" allowtransparency="true" frameborder="0" scrolling="0" width="110px" height="20px"></iframe>
@ -38,23 +57,42 @@ Some reasons you might want to use REST framework:
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
* [Extensive documentation][index], and [great community support][group].
* Used and trusted by large companies such as [Mozilla][mozilla] and [Eventbrite][eventbrite].
* Used and trusted by internationally recognised companies including [Mozilla][mozilla], [Red Hat][redhat], [Heroku][heroku], and [Eventbrite][eventbrite].
---
![Screenshot][image]
## Funding
**Above**: *Screenshot from the browsable API*
REST framework is a *collaboratively funded project*. If you use
REST framework commercially we strongly encourage you to invest in its
continued development by **[signing up for a paid plan][funding]**.
The initial aim is to provide a single full-time position on REST framework.
*Every single sign-up makes a significant impact towards making that possible.*
<ul class="premium-promo promo">
<li><a href="http://jobs.rover.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
<li><a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
<li><a href="https://hello.machinalis.co.uk/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar.png)">Rollbar</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://hello.machinalis.co.uk/), and [Rollbar](https://rollbar.com).*
---
## Requirements
REST framework requires the following:
* Python (2.7, 3.2, 3.3, 3.4, 3.5)
* Django (1.7+, 1.8, 1.9)
* Django (1.8, 1.9, 1.10)
The following packages are optional:
* [coreapi][coreapi] (1.32.0+) - Schema generation support.
* [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API.
* [django-filter][django-filter] (0.9.2+) - Filtering support.
* [django-crispy-forms][django-crispy-forms] - Improved HTML display for filtering.
@ -147,10 +185,11 @@ The tutorial will walk you through the building blocks that make up REST framewo
* [1 - Serialization][tut-1]
* [2 - Requests & Responses][tut-2]
* [3 - Class based views][tut-3]
* [3 - Class-based views][tut-3]
* [4 - Authentication & permissions][tut-4]
* [5 - Relationships & hyperlinked APIs][tut-5]
* [6 - Viewsets & routers][tut-6]
* [7 - Schemas & client libraries][tut-7]
There is a live example API of the finished tutorial API for testing purposes, [available here][sandbox].
@ -178,6 +217,7 @@ The API guide is your complete reference manual to all the functionality provide
* [Versioning][versioning]
* [Content negotiation][contentnegotiation]
* [Metadata][metadata]
* [Schemas][schemas]
* [Format suffixes][formatsuffixes]
* [Returning URLs][reverse]
* [Exceptions][exceptions]
@ -190,6 +230,7 @@ The API guide is your complete reference manual to all the functionality provide
General guides to using REST framework.
* [Documenting your API][documenting-your-api]
* [API Clients][api-clients]
* [Internationalization][internationalization]
* [AJAX, CSRF & CORS][ajax-csrf-cors]
* [HTML & Forms][html-and-forms]
@ -203,7 +244,11 @@ General guides to using REST framework.
* [3.1 Announcement][3.1-announcement]
* [3.2 Announcement][3.2-announcement]
* [3.3 Announcement][3.3-announcement]
* [3.4 Announcement][3.4-announcement]
* [3.5 Announcement][3.5-announcement]
* [Kickstarter Announcement][kickstarter-announcement]
* [Mozilla Grant][mozilla-grant]
* [Funding][funding]
* [Release Notes][release-notes]
## Development
@ -216,7 +261,7 @@ Framework.
For support please see the [REST framework discussion group][group], try the `#restframework` channel on `irc.freenode.net`, search [the IRC archives][botbot], or raise a question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag.
[Paid support is available][paid-support] from [DabApps][dabapps], and can include work on REST framework core, or support with building your REST framework API. Please [contact DabApps][contact-dabapps] if you'd like to discuss commercial support options.
For priority support please sign up for a [professional or premium sponsorship plan](https://fund.django-rest-framework.org/topics/funding/).
For updates on REST framework development, you may also want to follow [the author][twitter] on Twitter.
@ -255,7 +300,10 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[mozilla]: http://www.mozilla.org/en-US/about/
[redhat]: https://www.redhat.com/
[heroku]: https://www.heroku.com/
[eventbrite]: https://www.eventbrite.co.uk/about/
[coreapi]: http://pypi.python.org/pypi/coreapi/
[markdown]: http://pypi.python.org/pypi/Markdown/
[django-filter]: http://pypi.python.org/pypi/django-filter
[django-crispy-forms]: https://github.com/maraujop/django-crispy-forms
@ -269,6 +317,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[modelserializer-section]: api-guide/serializers#modelserializer
[functionview-section]: api-guide/views#function-based-views
[sandbox]: http://restframework.herokuapp.com/
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
[quickstart]: tutorial/quickstart.md
[tut-1]: tutorial/1-serialization.md
@ -277,6 +326,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[tut-4]: tutorial/4-authentication-and-permissions.md
[tut-5]: tutorial/5-relationships-and-hyperlinked-apis.md
[tut-6]: tutorial/6-viewsets-and-routers.md
[tut-7]: tutorial/7-schemas-and-client-libraries.md
[request]: api-guide/requests.md
[response]: api-guide/responses.md
@ -298,6 +348,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[versioning]: api-guide/versioning.md
[contentnegotiation]: api-guide/content-negotiation.md
[metadata]: api-guide/metadata.md
[schemas]: api-guide/schemas.md
[formatsuffixes]: api-guide/format-suffixes.md
[reverse]: api-guide/reverse.md
[exceptions]: api-guide/exceptions.md
@ -306,6 +357,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[settings]: api-guide/settings.md
[documenting-your-api]: topics/documenting-your-api.md
[api-clients]: topics/api-clients.md
[internationalization]: topics/internationalization.md
[ajax-csrf-cors]: topics/ajax-csrf-cors.md
[html-and-forms]: topics/html-and-forms.md
@ -319,7 +371,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[3.1-announcement]: topics/3.1-announcement.md
[3.2-announcement]: topics/3.2-announcement.md
[3.3-announcement]: topics/3.3-announcement.md
[3.4-announcement]: topics/3.4-announcement.md
[3.5-announcement]: topics/3.5-announcement.md
[kickstarter-announcement]: topics/kickstarter-announcement.md
[mozilla-grant]: topics/mozilla-grant.md
[funding]: topics/funding.md
[release-notes]: topics/release-notes.md

View File

@ -147,10 +147,10 @@ When using a serializer with a `HyperlinkedRelatedField` or `HyperlinkedIdentity
From version 2.2 onwards, serializers with hyperlinked relationships *always* require a `'request'` key to be supplied in the context dictionary. The implicit behavior will continue to function, but its use will raise a `PendingDeprecationWarning`.
[xordoquy]: https://github.com/xordoquy
[django-python-3]: https://docs.djangoproject.com/en/dev/faq/install/#can-i-use-django-with-python-3
[porting-python-3]: https://docs.djangoproject.com/en/dev/topics/python3/
[python-compat]: https://docs.djangoproject.com/en/dev/releases/1.5/#python-compatibility
[django-deprecation-policy]: https://docs.djangoproject.com/en/dev/internals/release-process/#internal-release-deprecation-policy
[django-python-3]: https://docs.djangoproject.com/en/stable/faq/install/#can-i-use-django-with-python-3
[porting-python-3]: https://docs.djangoproject.com/en/stable/topics/python3/
[python-compat]: https://docs.djangoproject.com/en/stable/releases/1.5/#python-compatibility
[django-deprecation-policy]: https://docs.djangoproject.com/en/stable/internals/release-process/#internal-release-deprecation-policy
[credits]: http://www.django-rest-framework.org/topics/credits
[mailing-list]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[django-rest-framework-docs]: https://github.com/marcgibbons/django-rest-framework-docs

View File

@ -6,7 +6,7 @@ REST framework 2.3 makes it even quicker and easier to build your Web APIs.
The 2.3 release introduces the [ViewSet][viewset] and [Router][router] classes.
A viewset is simply a type of class based view that allows you to group multiple views into a single common class.
A viewset is simply a type of class-based view that allows you to group multiple views into a single common class.
Routers allow you to automatically determine the URLconf for your viewset classes.

View File

@ -39,7 +39,7 @@ Then run the `runtests.py` script.
./runtests.py
The new test runner also includes [flake8](https://flake8.readthedocs.org) code linting, which should help keep our coding style consistent.
The new test runner also includes [flake8](https://flake8.readthedocs.io) code linting, which should help keep our coding style consistent.
#### Test runner flags
@ -162,7 +162,7 @@ The next planned release will be 3.0, featuring an improved and simplified seria
Once again, many thanks to all the generous [backers and sponsors][kickstarter-sponsors] who've helped make this possible!
[lts-releases]: https://docs.djangoproject.com/en/dev/internals/release-process/#long-term-support-lts-releases
[lts-releases]: https://docs.djangoproject.com/en/stable/internals/release-process/#long-term-support-lts-releases
[2-4-release-notes]: release-notes#240
[view-name-and-description-settings]: ../api-guide/settings#view-names-and-descriptions
[client-ip-identification]: ../api-guide/throttling#how-clients-are-identified

View File

@ -426,7 +426,7 @@ There are four methods that can be overridden, depending on what functionality y
* `.to_internal_value()` - Override this to support deserialization, for write operations.
* `.create()` and `.update()` - Override either or both of these to support saving instances.
Because this class provides the same interface as the `Serializer` class, you can use it with the existing generic class based views exactly as you would for a regular `Serializer` or `ModelSerializer`.
Because this class provides the same interface as the `Serializer` class, you can use it with the existing generic class-based views exactly as you would for a regular `Serializer` or `ModelSerializer`.
The only difference you'll notice when doing so is the `BaseSerializer` classes will not generate HTML forms in the browsable API. This is because the data they return does not include all the field information that would allow each field to be rendered into a suitable HTML input.
@ -801,7 +801,7 @@ This change means that you can now easily customize the style of error responses
## The metadata API
Behavior for dealing with `OPTIONS` requests was previously built directly into the class based views. This has now been properly separated out into a Metadata API that allows the same pluggable style as other API policies in REST framework.
Behavior for dealing with `OPTIONS` requests was previously built directly into the class-based views. This has now been properly separated out into a Metadata API that allows the same pluggable style as other API policies in REST framework.
This makes it far easier to use a different style for `OPTIONS` responses throughout your API, and makes it possible to create third-party metadata policies.
@ -870,7 +870,7 @@ The `COMPACT_JSON` setting has been added, and can be used to revert this behavi
#### File fields as URLs
The `FileField` and `ImageField` classes are now represented as URLs by default. You should ensure you set Django's [standard `MEDIA_URL` setting](https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-MEDIA_URL) appropriately, and ensure your application [serves the uploaded files](https://docs.djangoproject.com/en/dev/howto/static-files/#serving-uploaded-files-in-development).
The `FileField` and `ImageField` classes are now represented as URLs by default. You should ensure you set Django's [standard `MEDIA_URL` setting](https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-MEDIA_URL) appropriately, and ensure your application [serves the uploaded files](https://docs.djangoproject.com/en/stable/howto/static-files/#serving-uploaded-files-in-development).
You can revert this behavior, and display filenames in the representation by using the `UPLOADED_FILES_USE_URL` settings key:
@ -894,11 +894,11 @@ If the request is omitted from the context, the returned URLs will be of the for
The custom `X-Throttle-Wait-Second` header has now been dropped in favor of the standard `Retry-After` header. You can revert this behavior if needed by writing a custom exception handler for your application.
#### Date and time objects as ISO-8859-1 strings in serializer data.
#### Date and time objects as ISO-8601 strings in serializer data.
Date and Time objects are now coerced to strings by default in the serializer output. Previously they were returned as `Date`, `Time` and `DateTime` objects, and later coerced to strings by the renderer.
You can modify this behavior globally by settings the existing `DATE_FORMAT`, `DATETIME_FORMAT` and `TIME_FORMAT` settings keys. Setting these values to `None` instead of their default value of `'iso-8859-1'` will result in native objects being returned in serializer data.
You can modify this behavior globally by settings the existing `DATE_FORMAT`, `DATETIME_FORMAT` and `TIME_FORMAT` settings keys. Setting these values to `None` instead of their default value of `'iso-8601'` will result in native objects being returned in serializer data.
REST_FRAMEWORK = {
# Return native `Date` and `Time` objects in `serializer.data`
@ -962,4 +962,4 @@ You can follow development on the GitHub site, where we use [milestones to indic
[kickstarter]: http://kickstarter.com/projects/tomchristie/django-rest-framework-3
[sponsors]: http://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors
[mixins.py]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py
[django-localization]: https://docs.djangoproject.com/en/dev/topics/i18n/translation/#localization-how-to-create-language-files
[django-localization]: https://docs.djangoproject.com/en/stable/topics/i18n/translation/#localization-how-to-create-language-files

View File

@ -30,7 +30,7 @@ Note that as a result of this work a number of settings keys and generic view at
Until now, there has only been a single built-in pagination style in REST framework. We now have page, limit/offset and cursor based schemes included by default.
The cursor based pagination scheme is particularly smart, and is a better approach for clients iterating through large or frequently changing result sets. The scheme supports paging against non-unique indexes, by using both cursor and limit/offset information. It also allows for both forward and reverse cursor pagination. Much credit goes to David Cramer for [this blog post](http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api/) on the subject.
The cursor based pagination scheme is particularly smart, and is a better approach for clients iterating through large or frequently changing result sets. The scheme supports paging against non-unique indexes, by using both cursor and limit/offset information. It also allows for both forward and reverse cursor pagination. Much credit goes to David Cramer for [this blog post](http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api) on the subject.
#### Pagination controls in the browsable API.
@ -110,7 +110,7 @@ When per-request internationalization is enabled, client requests will respect t
"detail": "No se ha podido satisfacer la solicitud de cabecera de Accept."
}
Note that the structure of the error responses is still the same. We still have a `details` key in the response. If needed you can modify this behavior too, by using a [custom exception handler][custom-exception-handler].
Note that the structure of the error responses is still the same. We still have a `detail` key in the response. If needed you can modify this behavior too, by using a [custom exception handler][custom-exception-handler].
We include built-in translations both for standard exception cases, and for serializer validation errors.
@ -153,7 +153,7 @@ For more information, see the documentation on [customizing field mappings][cust
We've now moved a number of packages out of the core of REST framework, and into separately installable packages. If you're currently using these you don't need to worry, you simply need to `pip install` the new packages, and change any import paths.
We're making this change in order to help distribute the maintainance workload, and keep better focus of the core essentials of the framework.
We're making this change in order to help distribute the maintenance workload, and keep better focus of the core essentials of the framework.
The change also means we can be more flexible with which external packages we recommend. For example, the excellently maintained [Django OAuth toolkit](https://github.com/evonove/django-oauth-toolkit) has now been promoted as our recommended option for integrating OAuth support.

View File

@ -54,7 +54,7 @@ The following pagination view attributes and settings have been moved into attri
* `view.max_paginate_by` - Use `paginator.max_page_size` instead.
* `settings.PAGINATE_BY` - Use `paginator.page_size` instead.
* `settings.PAGINATE_BY_PARAM` - Use `paginator.page_size_query_param` instead.
* `settings.MAX_PAGINATE_BY` - Use `max_page_size` instead.
* `settings.MAX_PAGINATE_BY` - Use `paginator.max_page_size` instead.
## Modifications to list behaviors

View File

@ -0,0 +1,194 @@
<style>
.promo li a {
float: left;
width: 130px;
height: 20px;
text-align: center;
margin: 10px 30px;
padding: 150px 0 0 0;
background-position: 0 50%;
background-size: 130px auto;
background-repeat: no-repeat;
font-size: 120%;
color: black;
}
.promo li {
list-style: none;
}
</style>
# Django REST framework 3.4
The 3.4 release is the first in a planned series that will be addressing schema
generation, hypermedia support, API clients, and finally realtime support.
---
## Funding
The 3.4 release has been made possible a recent [Mozilla grant][moss], and by our
[collaborative funding model][funding]. If you use REST framework commercially, and would
like to see this work continue, we strongly encourage you to invest in its
continued development by **[signing up for a paid plan][funding]**.
The initial aim is to provide a single full-time position on REST framework.
Right now we're over 60% of the way towards achieving that.
*Every single sign-up makes a significant impact.*
<ul class="premium-promo promo">
<li><a href="http://jobs.rover.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
<li><a href="https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [awesome sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), and [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf).*
---
## Schemas & client libraries
REST framework 3.4 brings built-in support for generating API schemas.
We provide this support by using [Core API][core-api], a Document Object Model
for describing APIs.
Because Core API represents the API schema in an format-independent
manner, we're able to render the Core API `Document` object into many different
schema formats, by allowing the renderer class to determine how the internal
representation maps onto the external schema format.
This approach should also open the door to a range of auto-generated API
documentation options in the future, by rendering the `Document` object into
HTML documentation pages.
Alongside the built-in schema support, we're also now providing the following:
* A [command line tool][command-line-client] for interacting with APIs.
* A [Python client library][client-library] for interacting with APIs.
These API clients are dynamically driven, and able to interact with any API
that exposes a supported schema format.
Dynamically driven clients allow you to interact with an API at an application
layer interface, rather than a network layer interface, while still providing
the benefits of RESTful Web API design.
We're expecting to expand the range of languages that we provide client libraries
for over the coming months.
Further work on maturing the API schema support is also planned, including
documentation on supporting file upload and download, and improved support for
documentation generation and parameter annotation.
---
Current support for schema formats is as follows:
Name | Support | PyPI package
---------------------------------|-------------------------------------|--------------------------------
[Core JSON][core-json] | Schema generation & client support. | Built-in support in `coreapi`.
[Swagger / OpenAPI][swagger] | Schema generation & client support. | The `openapi-codec` package.
[JSON Hyper-Schema][hyperschema] | Currently client support only. | The `hyperschema-codec` package.
[API Blueprint][api-blueprint] | Not yet available. | Not yet available.
---
You can read more about any of this new functionality in the following:
* New tutorial section on [schemas & client libraries][tut-7].
* Documentation page on [schema generation][schema-generation].
* Topic page on [API clients][api-clients].
It is also worth noting that Marc Gibbons is currently working towards a 2.0 release of
the popular Django REST Swagger package, which will tie in with our new built-in support.
---
## Supported versions
The 3.4.0 release adds support for Django 1.10.
The following versions of Python and Django are now supported:
* Django versions 1.8, 1.9, and 1.10.
* Python versions 2.7, 3.2(\*), 3.3(\*), 3.4, 3.5.
(\*) Note that Python 3.2 and 3.3 are not supported from Django 1.9 onwards.
---
## Deprecations and changes
The 3.4 release includes very limited deprecation or behavioral changes, and
should present a straightforward upgrade.
### Use fields or exclude on serializer classes.
The following change in 3.3.0 is now escalated from "pending deprecation" to
"deprecated". Its usage will continue to function but will raise warnings:
`ModelSerializer` and `HyperlinkedModelSerializer` should include either a `fields`
option, or an `exclude` option. The `fields = '__all__'` shortcut may be used
to explicitly include all fields.
### Microsecond precision when returning time or datetime.
Using the default JSON renderer and directly returning a `datetime` or `time`
instance will now render with microsecond precision (6 digits), rather than
millisecond precision (3 digits). This makes the output format consistent with the
default string output of `serializers.DateTimeField` and `serializers.TimeField`.
This change *does not affect the default behavior when using serializers*,
which is to serialize `datetime` and `time` instances into strings with
microsecond precision.
The serializer behavior can be modified if needed, using the `DATETIME_FORMAT`
and `TIME_FORMAT` settings.
The renderer behavior can be modified by setting a custom `encoder_class`
attribute on a `JSONRenderer` subclass.
### Relational choices no longer displayed in OPTIONS requests.
Making an `OPTIONS` request to views that have a serializer choice field
will result in a list of the available choices being returned in the response.
In cases where there is a relational field, the previous behavior would be
to return a list of available instances to choose from for that relational field.
In order to minimise exposed information the behavior now is to *not* return
choices information for relational fields.
If you want to override this new behavior you'll need to [implement a custom
metadata class][metadata].
See [issue #3751][gh3751] for more information on this behavioral change.
---
## Other improvements
This release includes further work from a huge number of [pull requests and issues][milestone].
Many thanks to all our contributors who've been involved in the release, either through raising issues, giving feedback, improving the documentation, or suggesting and implementing code changes.
The full set of itemized release notes [are available here][release-notes].
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
[moss]: mozilla-grant.md
[funding]: funding.md
[core-api]: http://www.coreapi.org/
[command-line-client]: api-clients#command-line-client
[client-library]: api-clients#python-client-library
[core-json]: http://www.coreapi.org/specification/encoding/#core-json-encoding
[swagger]: https://openapis.org/specification
[hyperschema]: http://json-schema.org/latest/json-schema-hypermedia.html
[api-blueprint]: https://apiblueprint.org/
[tut-7]: ../../tutorial/7-schemas-and-client-libraries/
[schema-generation]: ../../api-guide/schemas/
[api-clients]: api-clients.md
[milestone]: https://github.com/tomchristie/django-rest-framework/milestone/35
[release-notes]: release-notes#34
[metadata]: ../../api-guide/metadata/#custom-metadata-classes
[gh3751]: https://github.com/tomchristie/django-rest-framework/issues/3751

View File

@ -0,0 +1,266 @@
<style>
.promo li a {
float: left;
width: 130px;
height: 20px;
text-align: center;
margin: 10px 30px;
padding: 150px 0 0 0;
background-position: 0 50%;
background-size: 130px auto;
background-repeat: no-repeat;
font-size: 120%;
color: black;
}
.promo li {
list-style: none;
}
</style>
# Django REST framework 3.5
The 3.5 release is the second in a planned series that is addressing schema
generation, hypermedia support, API client libraries, and finally realtime support.
---
## Funding
The 3.5 release would not have been possible without our [collaborative funding model][funding].
If you use REST framework commercially and would like to see this work continue,
we strongly encourage you to invest in its continued development by
**[signing up for a paid&nbsp;plan][funding]**.
<ul class="premium-promo promo">
<li><a href="http://jobs.rover.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
<li><a href="https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
<li><a href="http://www.machinalis.com/#services" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), and [Machinalis](http://www.machinalis.com/#services).*
---
## Improved schema generation
Docstrings on views are now pulled through into schema definitions, allowing
you to [use the schema definition to document your&nbsp;API][schema-docs].
There is now also a shortcut function, `get_schema_view()`, which makes it easier to
[adding schema views][schema-view] to your API.
For example, to include a swagger schema to your API, you would do the following:
* Run `pip install django-rest-swagger`.
* Add `'rest_framework_swagger'` to your `INSTALLED_APPS` setting.
* Include the schema view in your URL conf:
```py
from rest_framework.schemas import get_schema_view
from rest_framework_swagger.renderers import OpenAPIRenderer, SwaggerUIRenderer
schema_view = get_schema_view(
title='Example API',
renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer]
)
urlpatterns = [
url(r'^swagger/$', schema_view),
...
]
```
There have been a large number of fixes to the schema generation. These should
resolve issues for anyone using the latest version of the `django-rest-swagger`
package.
Some of these changes do affect the resulting schema structure,
so if you're already using schema generation you should make sure to review
[the deprecation notes](#deprecations), particularly if you're currently using
a dynamic client library to interact with your API.
Finally, we're also now exposing the schema generation as a
[publicly documented API][schema-generation-api], allowing you to more easily
override the behaviour.
## Requests test client
You can now test your project using the `requests` library.
This exposes exactly the same interface as if you were using a standard
requests session instance.
client = RequestsClient()
response = client.get('http://testserver/users/')
assert response.status_code == 200
Rather than sending any HTTP requests to the network, this interface will
coerce all outgoing requests into WSGI, and call into your application directly.
## Core API client
You can also now test your project by interacting with it using the `coreapi`
client library.
# Fetch the API schema
client = CoreAPIClient()
schema = client.get('http://testserver/schema/')
# Create a new organisation
params = {'name': 'MegaCorp', 'status': 'active'}
client.action(schema, ['organisations', 'create'], params)
# Ensure that the organisation exists in the listing
data = client.action(schema, ['organisations', 'list'])
assert(len(data) == 1)
assert(data == [{'name': 'MegaCorp', 'status': 'active'}])
Again, this will call directly into the application using the WSGI interface,
rather than making actual network calls.
This is a good option if you are planning for clients to mainly interact with
your API using the `coreapi` client library, or some other auto-generated client.
## Live tests
One interesting aspect of both the `requests` client and the `coreapi` client
is that they allow you to write tests in such a way that they can also be made
to run against a live service.
By switching the WSGI based client instances to actual instances of `requests.Session`
or `coreapi.Client` you can have the test cases make actual network calls.
Being able to write test cases that can exercise your staging or production
environment is a powerful tool. However in order to do this, you'll need to pay
close attention to how you handle setup and teardown to ensure a strict isolation
of test data from other live or staging data.
## RAML support
We now have preliminary support for [RAML documentation generation][django-rest-raml].
![RAML Example][raml-image]
Further work on the encoding and documentation generation is planned, in order to
make features such as the 'Try it now' support available at a later date.
This work also now means that you can use the Core API client libraries to interact
with APIs that expose a RAML specification. The [RAML codec][raml-codec] gives some examples of
interacting with the Spotify API in this way.
## Validation codes
Exceptions raised by REST framework now include short code identifiers.
When used together with our customizable error handling, this now allows you to
modify the style of API error messages.
As an example, this allows for the following style of error responses:
{
"message": "You do not have permission to perform this action.",
"code": "permission_denied"
}
This is particularly useful with validation errors, which use appropriate
codes to identify differing kinds of failure...
{
"name": {"message": "This field is required.", "code": "required"},
"age": {"message": "A valid integer is required.", "code": "invalid"}
}
## Client upload & download support
The Python `coreapi` client library and the Core API command line tool both
now fully support file [uploads][uploads] and [downloads][downloads].
---
## Deprecations
### Generating schemas from Router
The router arguments for generating a schema view, such as `schema_title`,
are now pending deprecation.
Instead of using `DefaultRouter(schema_title='Example API')`, you should use
the `get_schema_view()` function, and include the view in your URL conf.
Make sure to include the view before your router urls. For example:
from rest_framework.schemas import get_schema_view
from my_project.routers import router
schema_view = get_schema_view(title='Example API')
urlpatterns = [
url('^$', schema_view),
url(r'^', include(router.urls)),
]
### Schema path representations
The `'pk'` identifier in schema paths is now mapped onto the actually model field
name by default. This will typically be `'id'`.
This gives a better external representation for schemas, with less implementation
detail being exposed. It also reflects the behaviour of using a ModelSerializer
class with `fields = '__all__'`.
You can revert to the previous behaviour by setting `'SCHEMA_COERCE_PATH_PK': False`
in the REST framework settings.
### Schema action name representations
The internal `retrieve()` and `destroy()` method names are now coerced to an
external representation of `read` and `delete`.
You can revert to the previous behaviour by setting `'SCHEMA_COERCE_METHOD_NAMES': {}`
in the REST framework settings.
### DjangoFilterBackend
The functionality of the built-in `DjangoFilterBackend` is now completely
included by the `django-filter` package.
You should change your imports and REST framework filter settings as follows:
* `rest_framework.filters.DjangoFilterBackend` becomes `django_filters.rest_framework.DjangoFilterBackend`.
* `rest_framework.filters.FilterSet` becomes `django_filters.rest_framework.FilterSet`.
The existing imports will continue to work but are now pending deprecation.
### CoreJSON media type
The media type for `CoreJSON` is now `application/json+coreapi`, rather than
the previous `application/vnd.json+coreapi`. This brings it more into line with
other custom media types, such as those used by Swagger and RAML.
The clients currently accept either media type. The old style-media type will
be deprecated at a later date.
### ModelSerializer 'fields' and 'exclude'
ModelSerializer and HyperlinkedModelSerializer must include either a fields
option, or an exclude option. The `fields = '__all__'` shortcut may be used to
explicitly include all fields.
Failing to set either `fields` or `exclude` raised a pending deprecation warning
in version 3.3 and raised a deprecation warning in 3.4. Its usage is now mandatory.
---
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
[funding]: funding.md
[uploads]: http://core-api.github.io/python-client/api-guide/utils/#file
[downloads]: http://core-api.github.io/python-client/api-guide/codecs/#downloadcodec
[schema-generation-api]: ../api-guide/schemas/#schemagenerator
[schema-docs]: ../api-guide/schemas/#schemas-as-documentation
[schema-view]: ../api-guide/schemas/#the-get_schema_view-shortcut
[django-rest-raml]: https://github.com/tomchristie/django-rest-raml
[raml-image]: ../img/raml.png
[raml-codec]: https://github.com/core-api/python-raml-codec

View File

@ -35,7 +35,7 @@ The best way to deal with CORS in REST framework is to add the required response
[cite]: http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html
[csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax
[cors]: http://www.w3.org/TR/cors/
[ottoyiu]: https://github.com/ottoyiu/
[django-cors-headers]: https://github.com/ottoyiu/django-cors-headers/

325
docs/topics/api-clients.md Normal file
View File

@ -0,0 +1,325 @@
# API Clients
An API client handles the underlying details of how network requests are made
and how responses are decoded. They present the developer with an application
interface to work against, rather than working directly with the network interface.
The API clients documented here are not restricted to APIs built with Django REST framework.
They can be used with any API that exposes a supported schema format.
For example, [the Heroku platform API][heroku-api] exposes a schema in the JSON
Hyperschema format. As a result, the Core API command line client and Python
client library can be [used to interact with the Heroku API][heroku-example].
## Client-side Core API
[Core API][core-api] is a document specification that can be used to describe APIs. It can
be used either server-side, as is done with REST framework's [schema generation][schema-generation],
or used client-side, as described here.
When used client-side, Core API allows for *dynamically driven client libraries*
that can interact with any API that exposes a supported schema or hypermedia
format.
Using a dynamically driven client has a number of advantages over interacting
with an API by building HTTP requests directly.
#### More meaningful interaction
API interactions are presented in a more meaningful way. You're working at
the application interface layer, rather than the network interface layer.
#### Resilience & evolvability
The client determines what endpoints are available, what parameters exist
against each particular endpoint, and how HTTP requests are formed.
This also allows for a degree of API evolvability. URLs can be modified
without breaking existing clients, or more efficient encodings can be used
on-the-wire, with clients transparently upgrading.
#### Self-descriptive APIs
A dynamically driven client is able to present documentation on the API to the
end user. This documentation allows the user to discover the available endpoints
and parameters, and better understand the API they are working with.
Because this documentation is driven by the API schema it will always be fully
up to date with the most recently deployed version of the service.
---
# Command line client
The command line client allows you to inspect and interact with any API that
exposes a supported schema format.
## Getting started
To install the Core API command line client, use `pip`.
Note that the command-line client is a separate package to the
python client library. Make sure to install `coreapi-cli`.
$ pip install coreapi-cli
To start inspecting and interacting with an API the schema must first be loaded
from the network.
$ coreapi get http://api.example.org/
<Pastebin API "http://127.0.0.1:8000/">
snippets: {
create(code, [title], [linenos], [language], [style])
destroy(pk)
highlight(pk)
list([page])
partial_update(pk, [title], [code], [linenos], [language], [style])
retrieve(pk)
update(pk, code, [title], [linenos], [language], [style])
}
users: {
list([page])
retrieve(pk)
}
This will then load the schema, displaying the resulting `Document`. This
`Document` includes all the available interactions that may be made against the API.
To interact with the API, use the `action` command. This command requires a list
of keys that are used to index into the link.
$ coreapi action users list
[
{
"url": "http://127.0.0.1:8000/users/2/",
"id": 2,
"username": "aziz",
"snippets": []
},
...
]
To inspect the underlying HTTP request and response, use the `--debug` flag.
$ coreapi action users list --debug
> GET /users/ HTTP/1.1
> Accept: application/vnd.coreapi+json, */*
> Authorization: Basic bWF4Om1heA==
> Host: 127.0.0.1
> User-Agent: coreapi
< 200 OK
< Allow: GET, HEAD, OPTIONS
< Content-Type: application/json
< Date: Thu, 30 Jun 2016 10:51:46 GMT
< Server: WSGIServer/0.1 Python/2.7.10
< Vary: Accept, Cookie
<
< [{"url":"http://127.0.0.1/users/2/","id":2,"username":"aziz","snippets":[]},{"url":"http://127.0.0.1/users/3/","id":3,"username":"amy","snippets":["http://127.0.0.1/snippets/3/"]},{"url":"http://127.0.0.1/users/4/","id":4,"username":"max","snippets":["http://127.0.0.1/snippets/4/","http://127.0.0.1/snippets/5/","http://127.0.0.1/snippets/6/","http://127.0.0.1/snippets/7/"]},{"url":"http://127.0.0.1/users/5/","id":5,"username":"jose","snippets":[]},{"url":"http://127.0.0.1/users/6/","id":6,"username":"admin","snippets":["http://127.0.0.1/snippets/1/","http://127.0.0.1/snippets/2/"]}]
[
...
]
Some actions may include optional or required parameters.
$ coreapi action users create --param username=example
When using `--param`, the type of the input will be determined automatically.
If you want to be more explicit about the parameter type then use `--data` for
any null, numeric, boolean, list, or object inputs, and use `--string` for string inputs.
$ coreapi action users edit --string username=tomchristie --data is_admin=true
## Authentication & headers
The `credentials` command is used to manage the request `Authentication:` header.
Any credentials added are always linked to a particular domain, so as to ensure
that credentials are not leaked across differing APIs.
The format for adding a new credential is:
$ coreapi credentials add <domain> <credentials string>
For instance:
$ coreapi credentials add api.example.org "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
The optional `--auth` flag also allows you to add specific types of authentication,
handling the encoding for you. Currently only `"basic"` is supported as an option here.
For example:
$ coreapi credentials add api.example.org tomchristie:foobar --auth basic
You can also add specific request headers, using the `headers` command:
$ coreapi headers add api.example.org x-api-version 2
For more information and a listing of the available subcommands use `coreapi
credentials --help` or `coreapi headers --help`.
## Codecs
By default the command line client only includes support for reading Core JSON
schemas, however it includes a plugin system for installing additional codecs.
$ pip install openapi-codec jsonhyperschema-codec hal-codec
$ coreapi codecs show
Codecs
corejson application/vnd.coreapi+json encoding, decoding
hal application/hal+json encoding, decoding
openapi application/openapi+json encoding, decoding
jsonhyperschema application/schema+json decoding
json application/json data
text text/* data
## Utilities
The command line client includes functionality for bookmarking API URLs
under a memorable name. For example, you can add a bookmark for the
existing API, like so...
$ coreapi bookmarks add accountmanagement
There is also functionality for navigating forward or backward through the
history of which API URLs have been accessed.
$ coreapi history show
$ coreapi history back
For more information and a listing of the available subcommands use
`coreapi bookmarks --help` or `coreapi history --help`.
## Other commands
To display the current `Document`:
$ coreapi show
To reload the current `Document` from the network:
$ coreapi reload
To load a schema file from disk:
$ coreapi load my-api-schema.json --format corejson
To dump the current document to console in a given format:
$ coreapi dump --format openapi
To remove the current document, along with all currently saved history,
credentials, headers and bookmarks:
$ coreapi clear
---
# Python client library
The `coreapi` Python package allows you to programmatically interact with any
API that exposes a supported schema format.
## Getting started
You'll need to install the `coreapi` package using `pip` before you can get
started.
$ pip install coreapi
In order to start working with an API, we first need a `Client` instance. The
client holds any configuration around which codecs and transports are supported
when interacting with an API, which allows you to provide for more advanced
kinds of behaviour.
import coreapi
client = coreapi.Client()
Once we have a `Client` instance, we can fetch an API schema from the network.
schema = client.get('https://api.example.org/')
The object returned from this call will be a `Document` instance, which is
the internal representation of the interface that we are interacting with.
Now that we have our schema `Document`, we can now start to interact with the API:
users = client.action(schema, ['users', 'list'])
Some endpoints may include named parameters, which might be either optional or required:
new_user = client.action(schema, ['users', 'create'], params={"username": "max"})
## Codecs
Codecs are responsible for encoding or decoding Documents.
The decoding process is used by a client to take a bytestring of an API schema
definition, and returning the Core API `Document` that represents that interface.
A codec should be associated with a particular media type, such as `'application/coreapi+json'`.
This media type is used by the server in the response `Content-Type` header,
in order to indicate what kind of data is being returned in the response.
#### Configuring codecs
The codecs that are available can be configured when instantiating a client.
The keyword argument used here is `decoders`, because in the context of a
client the codecs are only for *decoding* responses.
In the following example we'll configure a client to only accept `Core JSON`
and `JSON` responses. This will allow us to receive and decode a Core JSON schema,
and subsequently to receive JSON responses made against the API.
from coreapi import codecs, Client
decoders = [codecs.CoreJSONCodec(), codecs.JSONCodec()]
client = Client(decoders=decoders)
#### Loading and saving schemas
You can use a codec directly, in order to load an existing schema definition,
and return the resulting `Document`.
input_file = open('my-api-schema.json', 'rb')
schema_definition = input_file.read()
codec = codecs.CoreJSONCodec()
schema = codec.load(schema_definition)
You can also use a codec directly to generate a schema definition given a `Document` instance:
schema_definition = codec.dump(schema)
output_file = open('my-api-schema.json', 'rb')
output_file.write(schema_definition)
## Transports
Transports are responsible for making network requests. The set of transports
that a client has installed determines which network protocols it is able to
support.
Currently the `coreapi` library only includes an HTTP/HTTPS transport, but
other protocols can also be supported.
#### Configuring transports
The behaviour of the network layer can be customized by configuring the
transports that the client is instantiated with.
import requests
from coreapi import transports, Client
credentials = {'api.example.org': 'Token 3bd44a009d16ff'}
transports = transports.HTTPTransport(credentials=credentials)
client = Client(transports=transports)
More complex customizations can also be achieved, for example modifying the
underlying `requests.Session` instance to [attach transport adaptors][transport-adaptors]
that modify the outgoing requests.
[heroku-api]: https://devcenter.heroku.com/categories/platform-api
[heroku-example]: http://www.coreapi.org/tools-and-resources/example-services/#heroku-json-hyper-schema
[core-api]: http://www.coreapi.org/
[schema-generation]: ../api-guide/schemas.md
[transport-adaptors]: http://docs.python-requests.org/en/master/user/advanced/#transport-adapters

View File

@ -163,4 +163,4 @@ Better support for autocomplete inputs is planned in future versions.
[bcomponentsnav]: http://getbootstrap.com/2.3.2/components.html#navbar
[autocomplete-packages]: https://www.djangopackages.com/grids/g/auto-complete/
[django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light
[django-autocomplete-light-install]: http://django-autocomplete-light.readthedocs.org/en/latest/#install
[django-autocomplete-light-install]: https://django-autocomplete-light.readthedocs.io/en/master/install.html

View File

@ -61,6 +61,7 @@ To run the tests, clone the repository, and then:
# Setup the virtual environment
virtualenv env
source env/bin/activate
pip install django
pip install -r requirements.txt
# Run the tests
@ -205,7 +206,7 @@ If you want to draw attention to a note or warning, use a pair of enclosing line
[pep-8]: http://www.python.org/dev/peps/pep-0008/
[travis-status]: ../img/travis-status.png
[pull-requests]: https://help.github.com/articles/using-pull-requests
[tox]: http://tox.readthedocs.org/en/latest/
[tox]: https://tox.readthedocs.io/en/latest/
[markdown]: http://daringfireball.net/projects/markdown/basics
[docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs
[mou]: http://mouapp.com/

View File

@ -38,6 +38,34 @@ Both this package and DRF docs are fully documented, well supported, and come hi
---
### DRF AutoDocs
Oleksander Mashianovs' [DRF Auto Docs][drfautodocs-repo] automated api renderer.
Collects almost all the code you written into documentation effortlessly.
Supports:
* functional view docs
* tree-like structure
* Docstrings:
* markdown
* preserve space & newlines
* formatting with nice syntax
* Fields:
* choices rendering
* help_text (to specify SerializerMethodField output, etc)
* smart read_only/required rendering
* Endpoint properties:
* filter_backends
* authentication_classes
* permission_classes
* extra url params(GET params)
![whole structure](http://joxi.ru/52aBGNI4k3oyA0.jpg)
---
#### Apiary
There are various other online tools and services for providing API documentation. One notable service is [Apiary][apiary]. With Apiary, you describe your API using a simple markdown-like syntax. The generated documentation includes API interaction, a mock server for testing & prototyping, and various other tools.
@ -77,7 +105,7 @@ If the python `markdown` library is installed, then [markdown syntax][markdown]
[ref]: http://example.com/activating-accounts
"""
Note that one constraint of using viewsets is that any documentation be used for all generated views, so for example, you cannot have differing documentation for the generated list view and detail view.
Note that when using viewsets the basic docstring is used for all generated views. To provide descriptions for each view, such as for the the list and retrieve views, use docstring sections as described in [Schemas as documentation: Examples][schemas-examples].
#### The `OPTIONS` method
@ -109,6 +137,7 @@ To implement a hypermedia API you'll need to decide on an appropriate media type
[drfdocs-repo]: https://github.com/ekonstantinidis/django-rest-framework-docs
[drfdocs-website]: http://www.drfdocs.com/
[drfdocs-demo]: http://demo.drfdocs.com/
[drfautodocs-repo]: https://github.com/iMakedonsky/drf-autodocs
[django-rest-swagger]: https://github.com/marcgibbons/django-rest-swagger
[swagger]: https://developers.helloreverb.com/swagger/
[rest-framework-docs]: https://github.com/marcgibbons/django-rest-framework-docs
@ -119,3 +148,4 @@ To implement a hypermedia API you'll need to decide on an appropriate media type
[image-django-rest-swagger]: ../img/django-rest-swagger.png
[image-apiary]: ../img/apiary.png
[image-self-describing-api]: ../img/self-describing.png
[schemas-examples]: api-guide/schemas/#examples

View File

@ -8,6 +8,22 @@ if (window.location.hostname == "www.django-rest-framework.org") {
</script>
<style>
.promo li a {
float: left;
width: 130px;
height: 20px;
text-align: center;
margin: 10px 30px;
padding: 150px 0 0 0;
background-position: 0 50%;
background-size: 130px auto;
background-repeat: no-repeat;
font-size: 120%;
color: black;
}
.promo li {
list-style: none;
}
.chart {
background-color: #e3e3e3;
background: -webkit-linear-gradient(top, #fff 0, #e3e3e3 100%);
@ -37,7 +53,7 @@ if (window.location.hostname == "www.django-rest-framework.org") {
top: -8px;
margin-left: 4px; }
.plan-name {
text-align: center;
text-align: center;
font-size: 20px;
font-weight: 400;
color: #777;
@ -47,9 +63,11 @@ if (window.location.hostname == "www.django-rest-framework.org") {
margin: 0 auto;
margin-top: 8px; }
.specs {
margin-top: 20px; }
.specs.startup {
margin-bottom: 93px }
margin-top: 20px; min-height: 130px;
}
.specs.freelancer {
min-height: 0px;
}
.spec {
font-size: 15px;
color: #474747;
@ -79,27 +97,32 @@ form.signup {
# Funding
> As a direct result of [a successful Mozilla grant application](mozilla-grant.md), I will be leaving my current role at [DabApps](http://www.dabapps.com), and attempting to secure a sustainable business model for REST framework development. I need your help in order to make this work.
>
> &mdash; Tom Christie
If you use REST framework commercially we strongly encourage you to invest in its continued development by signing up for a paid plan.
**We believe that collaboratively funded software can offer outstanding returns on investment, by allowing users and clients to collectively share the cost of development.**
**We believe that collaboratively funded software can offer outstanding returns on investment, by encouraging our users to collectively share the cost of development.**
Signing up for a paid plan will:
* Directly contribute to faster releases, more features and higher quality software.
* Allow more time to be invested in documentation, issue triage and community support.
* Directly contribute to faster releases, more features, and higher quality software.
* Allow more time to be invested in documentation, issue triage, and community support.
* Safeguard the future development of REST framework.
REST framework will always be open source and permissively licensed, but we firmly believe it is in the commercial best-interest for users of the project to fund its ongoing development.
REST framework continues to be open-source and permissively licensed, but we firmly believe it is in the commercial best-interest for users of the project to invest in its ongoing development.
---
## Making the business case
Our successful Kickstarter campaign demonstrates the cost-reward ratio of shared development funding.
Our [successful Kickstarter campaign](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) demonstrates the impressive cost-reward ratio of shared funding of open-source software.
With *typical corporate fundings of just £100-£1000 per organization* we successfully delivered:
* The comprehensive 3.0 serializer redesign.
* The comprehensive serializer redesign, and **version 3.0 release**.
* Ongoing triage and community support, **closing over 1600 tickets**.
* Substantial improvements to the Browsable API.
* The admin interface.
* A new pagination API including offset/limit and cursor pagination implementations, plus on-page controls.
@ -110,13 +133,14 @@ With *typical corporate fundings of just £100-£1000 per organization* we succe
* Internationalization support for API responses, currently with 27 languages.
* The metadata APIs for handling `OPTIONS` requests and schema endpoints.
* Numerous minor improvements and better quality throughout the codebase.
* Ongoing triage and community support, closing over 1600 tickets.
This incredible level of return on investment is *only possible through collaboratively funded models*, which is why we believe that supporting our paid plans is in everyone's best interest.
Sign up for a paid plan today, and help ensure that REST framework becomes a sustainable, full-time funded project.
---
## Individual plan
## Freelancer plan
This subscription is recommended for freelancers and other individuals with an interest in seeing REST framework continue to&nbsp;improve.
@ -126,12 +150,12 @@ If you are using REST framework as an full-time employee, consider recommending
<div class="span4">
<div class="chart first">
<div class="quantity">
<span class="dollar">$</span>
<span class="price">15</span>
<span class="period">/month</span>
<span class="dollar">{{ symbol }}</span>
<span class="price">{{ rates.personal1 }}</span>
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
</div>
<div class="plan-name">Individual</div>
<div class="specs">
<div class="plan-name">Freelancer</div>
<div class="specs freelancer">
<div class="spec">
Support ongoing development
</div>
@ -139,14 +163,14 @@ If you are using REST framework as an full-time employee, consider recommending
Credited on the site
</div>
</div>
<form class="signup" action="/signup/individual/" method="POST">
<form class="signup" action="/signup/{{ currency }}-{{ rates.personal1 }}/" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_public }}"
data-amount="1500"
data-amount="{{ stripe_amounts.personal1 }}"
data-name="Django REST framework"
data-description="Individual"
data-currency="usd"
data-currency="{{ currency }}"
data-allow-remember-me=false
data-billing-address=true
data-label='Sign up'
@ -174,9 +198,9 @@ Our professional and premium plans also include **priority support**. At any tim
<div class="span4">
<div class="chart first">
<div class="quantity">
<span class="dollar">$</span>
<span class="price">50</span>
<span class="period">/month</span>
<span class="dollar">{{ symbol }}</span>
<span class="price">{{ rates.corporate1 }}</span>
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
</div>
<div class="plan-name">Basic</div>
<div class="specs startup">
@ -187,14 +211,14 @@ Our professional and premium plans also include **priority support**. At any tim
<span class="variable">Funding page</span> ad placement
</div>
</div>
<form class="signup" action="/signup/startup/" method="POST">
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate1 }}/" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_public }}"
data-amount="5000"
data-amount="{{ stripe_amounts.corporate1 }}"
data-name="Django REST framework"
data-description="Basic"
data-currency="usd"
data-currency="{{ currency }}"
data-allow-remember-me=false
data-billing-address=true
data-label='Sign up'
@ -206,30 +230,30 @@ Our professional and premium plans also include **priority support**. At any tim
<div class="span4">
<div class="chart">
<div class="quantity">
<span class="dollar">$</span>
<span class="price">250</span>
<span class="period">/month</span>
<span class="dollar">{{ symbol }}</span>
<span class="price">{{ rates.corporate2 }}</span>
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
</div>
<div class="plan-name">Professional</div>
<div class="specs">
<div class="spec">
Support ongoing development
</div>
<div class="spec">
Add a <span class="variable">half day per&nbsp;month</span> development time to the project
</div>
<div class="spec">
<span class="variable">Homepage</span> ad placement
<span class="variable">Sidebar</span> ad placement
</div>
<div class="spec">
<span class="variable">Priority support</span> for your engineers
</div>
</div>
<form class="signup" action="/signup/professional/" method="POST">
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate2 }}/" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_public }}"
data-amount="25000"
data-amount="{{ stripe_amounts.corporate2 }}"
data-name="Django REST framework"
data-description="Professional"
data-currency="usd"
data-currency="{{ currency }}"
data-allow-remember-me=false
data-billing-address=true
data-label='Sign up'
@ -241,30 +265,33 @@ Our professional and premium plans also include **priority support**. At any tim
<div class="span4">
<div class="chart last">
<div class="quantity">
<span class="dollar">$</span>
<span class="price">500</span>
<span class="period">/month</span>
<span class="dollar">{{ symbol }}</span>
<span class="price">{{ rates.corporate3 }}</span>
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
</div>
<div class="plan-name">Premium</div>
<div class="specs">
<div class="spec">
Support ongoing development
</div>
<div class="spec">
Add <span class="variable">one full day per&nbsp;month</span> development time to the project
<span class="variable">Homepage</span> ad placement
</div>
<div class="spec">
<span class="variable">Full site</span> ad placement
<span class="variable">Sidebar</span> ad placement
</div>
<div class="spec">
<span class="variable">Priority support</span> for your engineers
</div>
</div>
<form class="signup" action="/signup/premium/" method="POST">
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate3 }}/" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_public }}"
data-amount="50000"
data-amount="{{ stripe_amounts.corporate3 }}"
data-name="Django REST framework"
data-description="Premium"
data-currency="usd"
data-currency="{{ currency }}"
data-allow-remember-me=false
data-billing-address=true
data-label='Sign up'
@ -279,36 +306,54 @@ Our professional and premium plans also include **priority support**. At any tim
*Billing is monthly and you can cancel at any time.*
Once you've signed up we'll contact you via email and arrange your ad placements on the site.
Once you've signed up I'll contact you via email and arrange your ad placements on the site.
For further enquires please contact <a href=mailto:tom@tomchristie.com>tom@tomchristie.com</a>.
For further enquires please contact <a href=mailto:funding@django-rest-framework.org>funding@django-rest-framework.org</a>.
---
## Roadmap
## Accountability
Although we're incredibly proud of REST framework in its current state we believe there is still huge scope for improvement. What we're aiming for here is a *highly polished, rock solid product*. This needs to backed up with impeccable documentation and a great third party ecosystem.
In order to ensure that I can be fully focused on trying to secure a sustainable
& well-funded open source business I will be leaving my current role at [DabApps](http://www.dabapps.com)
at the end of May 2016.
The roadmap below is a broad indication of just some of the ongoing and future work we believe is important to REST framework.
I have formed a UK limited company, [Encode](http://www.encode.io), which will
act as the business entity behind REST framework. I will be issuing monthly reports
from Encode on progress both towards the [Mozilla grant](mozilla-grant.md), and for development time
funded via the REST framework paid plans.
* Increasing our "bus factor" through documented organizational process & safeguards.
* More time towards testing and hardening releases, with only gradual, well-documented deprecations.
* A formal policy on security backports for non-current releases.
* Continuing triage & community support.
* Improved project documentation, including versioned & internationalized docs.
* Improved third party package visibility.
* Refining the admin interface, ensuring it has a fully customizable API and making it suitable as end-user facing application.
* Cleaning up internal complexities including the `BrowsableAPIRenderer` and `Request` object.
* Support for alternative backends such as SQLAlchemy.
* Support for non-database backed services.
* HTTP Caching API & support for conditional database lookups.
* Benchmarking and performance improvements.
* In depth documentation on advanced usage and best practices.
* Documentation & support for integration with realtime systems.
* Hypermedia support and client libraries.
* Support for JSON schema as endpoints or `OPTIONS` responses.
* API metric tools.
* Debug & logging tools.
* Third party GraphQL support.
<!-- Begin MailChimp Signup Form -->
<link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css">
<style type="text/css">
#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; }
/* Add your own MailChimp form style overrides in your site stylesheet or in this style block.
We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. */
</style>
<div id="mc_embed_signup">
<form action="//encode.us13.list-manage.com/subscribe/post?u=b6b66bb5e4c7cb484a85c8dd7&amp;id=e382ef68ef" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
<div id="mc_embed_signup_scroll">
<h2>Stay up to date, with our monthly progress reports...</h2>
<div class="mc-field-group">
<label for="mce-EMAIL">Email Address </label>
<input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
</div>
<div id="mce-responses" class="clear">
<div class="response" id="mce-error-response" style="display:none"></div>
<div class="response" id="mce-success-response" style="display:none"></div>
</div> <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
<div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_b6b66bb5e4c7cb484a85c8dd7_e382ef68ef" tabindex="-1" value=""></div>
<div class="clear"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
</div>
</form>
</div>
<script type='text/javascript' src='//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js'></script><script type='text/javascript'>(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[0]='EMAIL';ftypes[0]='email';fnames[1]='FNAME';ftypes[1]='text';fnames[2]='LNAME';ftypes[2]='text';}(jQuery));var $mcj = jQuery.noConflict(true);</script>
<!--End mc_embed_signup-->
By taking out a paid plan you'll be directly contributing towards making these features happen.
---
## Our sponsors
<div id="fundingInclude"></div>
<script src="https://fund.django-rest-framework.org/funding_include.js"></script>

View File

@ -108,7 +108,7 @@ Let's take a look at how to render each of the three available template packs. F
class LoginSerializer(serializers.Serializer):
email = serializers.EmailField(
max_length=100,
style={'placeholder': 'Email'}
style={'placeholder': 'Email', 'autofocus': True}
)
password = serializers.CharField(
max_length=100,
@ -207,9 +207,9 @@ Field templates can also use additional style properties, depending on their typ
The complete list of `base_template` options and their associated style options is listed below.
base_template | Valid field types | Additional style options
base_template | Valid field types | Additional style options
----|----|----
input.html | Any string, numeric or date/time field | input_type, placeholder, hide_label
input.html | Any string, numeric or date/time field | input_type, placeholder, hide_label, autofocus
textarea.html | `CharField` | rows, placeholder, hide_label
select.html | `ChoiceField` or relational field types | hide_label
radio.html | `ChoiceField` or relational field types | inline, hide_label

View File

@ -102,7 +102,7 @@ Our gold sponsors include companies large and small. Many thanks for their signi
### Silver sponsors
The serious financial contribution that our silver sponsors have made is very much appreciated. I'd like to say a particular thank&nbsp;you to individuals who have choosen to privately support the project at this level.
The serious financial contribution that our silver sponsors have made is very much appreciated. I'd like to say a particular thank&nbsp;you to individuals who have chosen to privately support the project at this level.
<ul class="sponsor silver">
<li><a href="http://www.imtapps.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-imt_computer_services.png);">IMT Computer Services</a></li>

View File

@ -0,0 +1,67 @@
# Mozilla Grant
We have recently been [awarded a Mozilla grant](https://blog.mozilla.org/blog/2016/04/13/mozilla-open-source-support-moss-update-q1-2016/), in order to fund the next major releases of REST framework. This work will focus on seamless client-side integration by introducing supporting client libraries that are able to dynamically interact with REST framework APIs. The framework will provide for either hypermedia or schema endpoints, which will expose the available interface for the client libraries to interact with.
Additionally, we will be building on the realtime support that Django Channels provides, supporting and documenting how to build realtime APIs with REST framework. Again, this will include supporting work in the associated client libraries, making it easier to build richly interactive applications.
The [Core API](http://www.coreapi.org) project will provide the foundations for our client library support, and will allow us to support interaction using a wide range of schemas and hypermedia formats. It's worth noting that these client libraries won't be tightly coupled to solely REST framework APIs either, and will be able to interact with *any* API that exposes a supported schema or hypermedia format.
Specifically, the work includes:
## Client libraries
This work will include built-in schema and hypermedia support, allowing dynamic client libraries to interact with the API. I'll also be releasing both Python and Javascript client libraries, plus a command-line client, a new tutorial section, and further documentation.
* Client library support in REST framework.
* Schema & hypermedia support for REST framework APIs.
* A test client, allowing you to write tests that emulate a client library interacting with your API.
* New tutorial sections on using client libraries to interact with REST framework APIs.
* Python client library.
* JavaScript client library.
* Command line client.
## Realtime APIs
The next goal is to build on the realtime support offered by Django Channels, adding support & documentation for building realtime API endpoints.
* Support for API subscription endpoints, using REST framework and Django Channels.
* New tutorial section on building realtime API endpoints with REST framework.
* Realtime support in the Python & Javascript client libraries.
## Accountability
In order to ensure that I can be fully focused on trying to secure a sustainable
& well-funded open source business I will be leaving my current role at [DabApps](http://www.dabapps.com)
at the end of May 2016.
I have formed a UK limited company, [Encode](http://www.encode.io), which will
act as the business entity behind REST framework. I will be issuing monthly reports
from Encode on progress both towards the Mozilla grant, and for development time
funded via the [REST framework paid plans](funding.md).
<!-- Begin MailChimp Signup Form -->
<link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css">
<style type="text/css">
#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; }
/* Add your own MailChimp form style overrides in your site stylesheet or in this style block.
We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. */
</style>
<div id="mc_embed_signup">
<form action="//encode.us13.list-manage.com/subscribe/post?u=b6b66bb5e4c7cb484a85c8dd7&amp;id=e382ef68ef" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
<div id="mc_embed_signup_scroll">
<h2>Stay up to date, with our monthly progress reports...</h2>
<div class="mc-field-group">
<label for="mce-EMAIL">Email Address </label>
<input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
</div>
<div id="mce-responses" class="clear">
<div class="response" id="mce-error-response" style="display:none"></div>
<div class="response" id="mce-success-response" style="display:none"></div>
</div> <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
<div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_b6b66bb5e4c7cb484a85c8dd7_e382ef68ef" tabindex="-1" value=""></div>
<div class="clear"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
</div>
</form>
</div>
<script type='text/javascript' src='//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js'></script><script type='text/javascript'>(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[0]='EMAIL';ftypes[0]='email';fnames[1]='FNAME';ftypes[1]='text';fnames[2]='LNAME';ftypes[2]='text';}(jQuery));var $mcj = jQuery.noConflict(true);</script>
<!--End mc_embed_signup-->

View File

@ -6,7 +6,7 @@
This document outlines our project management processes for REST framework.
The aim is to ensure that the project has a high
The aim is to ensure that the project has a high
["bus factor"][bus-factor], and can continue to remain well supported for the foreseeable future. Suggestions for improvements to our process are welcome.
---
@ -17,7 +17,7 @@ We have a quarterly maintenance cycle where new members may join the maintenance
#### Current team
The [maintenance team for Q1 2015](https://github.com/tomchristie/django-rest-framework/issues/2190):
The [maintenance team for Q4 2015](https://github.com/tomchristie/django-rest-framework/issues/2190):
* [@tomchristie](https://github.com/tomchristie/)
* [@xordoquy](https://github.com/xordoquy/) (Release manager.)
@ -38,27 +38,27 @@ Members of the maintenance team will be added as collaborators to the repository
The following template should be used for the description of the issue, and serves as the formal process for selecting the team.
This issue is for determining the maintenance team for the *** period.
Please see the [Project management](http://www.django-rest-framework.org/topics/project-management/) section of our documentation for more details.
---
#### Renewing existing members.
The following people are the current maintenance team. Please checkmark your name if you wish to continue to have write permission on the repository for the *** period.
- [ ] @***
- [ ] @***
- [ ] @***
- [ ] @***
- [ ] @***
---
#### New members.
If you wish to be considered for this or a future date, please comment against this or subsequent issues.
To modify this process for future maintenance cycles make a pull request to the [project management](http://www.django-rest-framework.org/topics/project-management/) documentation.
#### Responsibilities of team members
@ -116,7 +116,7 @@ The following template should be used for the description of the issue, and serv
- [ ] Make a release announcement on the [discussion group](https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework).
- [ ] Make a release announcement on twitter.
- [ ] Close the milestone on GitHub.
To modify this process for future releases make a pull request to the [project management](http://www.django-rest-framework.org/topics/project-management/) documentation.
When pushing the release to PyPI ensure that your environment has been installed from our development `requirement.txt`, so that documentation and PyPI installs are consistently being built against a pinned set of packages.
@ -165,7 +165,7 @@ Here's how differences between the old and new source files will be handled:
When a translator has finished translating their work needs to be downloaded from Transifex into the REST framework repository. To do this, run:
# 3. Pull the translated django.po files from Transifex.
tx pull -a
tx pull -a --minimum-perc 10
cd rest_framework
# 4. Compile the binary .mo files for all supported languages.
django-admin.py compilemessages

View File

@ -38,13 +38,261 @@ You can determine your currently installed version using `pip freeze`:
---
## 3.5.x series
### 3.5.3
**Date**: [7th November 2016][3.5.3-milestone]
* Don't raise incorrect FilterSet deprecation warnings. ([#4660][gh4660], [#4643][gh4643], [#4644][gh4644])
* Schema generation should not raise 404 when a view permission class does. ([#4645][gh4645], [#4646][gh4646])
* Add `autofocus` support for input controls. ([#4650][gh4650])
### 3.5.2
**Date**: [1st November 2016][3.5.2-milestone]
* Restore exception tracebacks in Python 2.7. ([#4631][gh4631], [#4638][gh4638])
* Properly display dicts in the admin console. ([#4532][gh4532], [#4636][gh4636])
* Fix is_simple_callable with variable args, kwargs. ([#4622][gh4622], [#4602][gh4602])
* Support 'on'/'off' literals with BooleanField. ([#4640][gh4640], [#4624][gh4624])
* Enable cursor pagination of value querysets. ([#4569][gh4569])
* Fix support of get_full_details() for Throttled exceptions. ([#4627][gh4627])
* Fix FilterSet proxy. ([#4620][gh4620])
* Make serializer fields import explicit. ([#4628][gh4628])
* Drop redundant requests adapter. ([#4639][gh4639])
### 3.5.1
**Date**: [21st October 2016][3.5.1-milestone]
* Make `rest_framework/compat.py` imports. ([#4612][gh4612], [#4608][gh4608], [#4601][gh4601])
* Fix bug in schema base path generation. ([#4611][gh4611], [#4605][gh4605])
* Fix broken case of ListSerializer with single item. ([#4609][gh4609], [#4606][gh4606])
* Remove bare `raise` for Python 3.5 compat. ([#4600][gh4600])
### 3.5.0
**Date**: [20th October 2016][3.5.0-milestone]
---
## 3.4.x series
### 3.4.7
**Date**: [21st September 2016][3.4.7-milestone]
* Fallback behavior for request parsing when request.POST already accessed. ([#3951][gh3951], [#4500][gh4500])
* Fix regression of `RegexField`. ([#4489][gh4489], [#4490][gh4490], [#2617][gh2617])
* Missing comma in `admin.html` causing CSRF error. ([#4472][gh4472], [#4473][gh4473])
* Fix response rendering with empty context. ([#4495][gh4495])
* Fix indentation regression in API listing. ([#4493][gh4493])
* Fixed an issue where the incorrect value is set to `ResolverMatch.func_name` of api_view decorated view. ([#4465][gh4465], [#4462][gh4462])
* Fix `APIClient.get()` when path contains unicode arguments ([#4458][gh4458])
### 3.4.6
**Date**: [23rd August 2016][3.4.6-milestone]
* Fix malformed Javascript in browsable API. ([#4435][gh4435])
* Skip HiddenField from Schema fields. ([#4425][gh4425], [#4429][gh4429])
* Improve Create to show the original exception traceback. ([#3508][gh3508])
* Fix `AdminRenderer` display of PK only related fields. ([#4419][gh4419], [#4423][gh4423])
### 3.4.5
**Date**: [19th August 2016][3.4.5-milestone]
* Improve debug error handling. ([#4416][gh4416], [#4409][gh4409])
* Allow custom CSRF_HEADER_NAME setting. ([#4415][gh4415], [#4410][gh4410])
* Include .action attribute on viewsets when generating schemas. ([#4408][gh4408], [#4398][gh4398])
* Do not include request.FILES items in request.POST. ([#4407][gh4407])
* Fix rendering of checkbox multiple. ([#4403][gh4403])
* Fix docstring of Field.get_default. ([#4404][gh4404])
* Replace utf8 character with its ascii counterpart in README. ([#4412][gh4412])
### 3.4.4
**Date**: [12th August 2016][3.4.4-milestone]
* Ensure views are fully initialized when generating schemas. ([#4373][gh4373], [#4382][gh4382], [#4383][gh4383], [#4279][gh4279], [#4278][gh4278])
* Add form field descriptions to schemas. ([#4387][gh4387])
* Fix category generation for schema endpoints. ([#4391][gh4391], [#4394][gh4394], [#4390][gh4390], [#4386][gh4386], [#4376][gh4376], [#4329][gh4329])
* Don't strip empty query params when paginating. ([#4392][gh4392], [#4393][gh4393], [#4260][gh4260])
* Do not re-run query for empty results with LimitOffsetPagination. ([#4201][gh4201], [#4388][gh4388])
* Stricter type validation for CharField. ([#4380][gh4380], [#3394][gh3394])
* RelatedField.choices should preserve non-string values. ([#4111][gh4111], [#4379][gh4379], [#3365][gh3365])
* Test case for rendering checkboxes in vertical form style. ([#4378][gh4378], [#3868][gh3868], [#3868][gh3868])
* Show error traceback HTML in browsable API ([#4042][gh4042], [#4172][gh4172])
* Fix handling of ALLOWED_VERSIONS and no DEFAULT_VERSION. [#4370][gh4370]
* Allow `max_digits=None` on DecimalField. ([#4377][gh4377], [#4372][gh4372])
* Limit queryset when rendering relational choices. ([#4375][gh4375], [#4122][gh4122], [#3329][gh3329], [#3330][gh3330], [#3877][gh3877])
* Resolve form display with ChoiceField, MultipleChoiceField and non-string choices. ([#4374][gh4374], [#4119][gh4119], [#4121][gh4121], [#4137][gh4137], [#4120][gh4120])
* Fix call to TemplateHTMLRenderer.resolve_context() fallback method. ([#4371][gh4371])
### 3.4.3
**Date**: [5th August 2016][3.4.3-milestone]
* Include fallaback for users of older TemplateHTMLRenderer internal API. ([#4361][gh4361])
### 3.4.2
**Date**: [5th August 2016][3.4.2-milestone]
* Include kwargs passed to 'as_view' when generating schemas. ([#4359][gh4359], [#4330][gh4330], [#4331][gh4331])
* Access `request.user.is_authenticated` as property not method, under Django 1.10+ ([#4358][gh4358], [#4354][gh4354])
* Filter HEAD out from schemas. ([#4357][gh4357])
* extra_kwargs takes precedence over uniqueness kwargs. ([#4198][gh4198], [#4199][gh4199], [#4349][gh4349])
* Correct descriptions when tabs are used in code indentation. ([#4345][gh4345], [#4347][gh4347])*
* Change template context generation in TemplateHTMLRenderer. ([#4236][gh4236])
* Serializer defaults should not be included in partial updates. ([#4346][gh4346], [#3565][gh3565])
* Consistent behavior & descriptive error from FileUploadParser when filename not included. ([#4340][gh4340], [#3610][gh3610], [#4292][gh4292], [#4296][gh4296])
* DecimalField quantizes incoming digitals. ([#4339][gh4339], [#4318][gh4318])
* Handle non-string input for IP fields. ([#4335][gh4335], [#4336][gh4336], [#4338][gh4338])
* Fix leading slash handling when Schema generation includes a root URL. ([#4332][gh4332])
* Test cases for DictField with allow_null options. ([#4348][gh4348])
* Update tests from Django 1.10 beta to Django 1.10. ([#4344][gh4344])
### 3.4.1
**Date**: [28th July 2016][3.4.1-milestone]
* Added `root_renderers` argument to `DefaultRouter`. ([#4323][gh4323], [#4268][gh4268])
* Added `url` and `schema_url` arguments. ([#4321][gh4321], [#4308][gh4308], [#4305][gh4305])
* Unique together checks should apply to read-only fields which have a default. ([#4316][gh4316], [#4294][gh4294])
* Set view.format_kwarg in schema generator. ([#4293][gh4293], [#4315][gh4315])
* Fix schema generator for views with `pagination_class = None`. ([#4314][gh4314], [#4289][gh4289])
* Fix schema generator for views with no `get_serializer_class`. ([#4265][gh4265], [#4285][gh4285])
* Fixes for media type parameters in `Accept` and `Content-Type` headers. ([#4287][gh4287], [#4313][gh4313], [#4281][gh4281])
* Use verbose_name instead of object_name in error messages. ([#4299][gh4299])
* Minor version update to Twitter Bootstrap. ([#4307][gh4307])
* SearchFilter raises error when using with related field. ([#4302][gh4302], [#4303][gh4303], [#4298][gh4298])
* Adding support for RFC 4918 status codes. ([#4291][gh4291])
* Add LICENSE.md to the built wheel. ([#4270][gh4270])
* Serializing "complex" field returns None instead of the value since 3.4 ([#4272][gh4272], [#4273][gh4273], [#4288][gh4288])
### 3.4.0
**Date**: [14th July 2016][3.4.0-milestone]
* Don't strip microseconds in JSON output. ([#4256][gh4256])
* Two slightly different iso 8601 datetime serialization. ([#4255][gh4255])
* Resolve incorrect inclusion of media type parameters. ([#4254][gh4254])
* Response Content-Type potentially malformed. ([#4253][gh4253])
* Fix setup.py error on some platforms. ([#4246][gh4246])
* Move alternate formats in coreapi into separate packages. ([#4244][gh4244])
* Add localize keyword argument to `DecimalField`. ([#4233][gh4233])
* Fix issues with routers for custom list-route and detail-routes. ([#4229][gh4229])
* Namespace versioning with nested namespaces. ([#4219][gh4219])
* Robust uniqueness checks. ([#4217][gh4217])
* Minor refactoring of `must_call_distinct`. ([#4215][gh4215])
* Overridable offset cutoff in CursorPagination. ([#4212][gh4212])
* Pass through strings as-in with date/time fields. ([#4196][gh4196])
* Add test confirming that required=False is valid on a relational field. ([#4195][gh4195])
* In LimitOffsetPagination `limit=0` should revert to default limit. ([#4194][gh4194])
* Exclude read_only=True fields from unique_together validation & add docs. ([#4192][gh4192])
* Handle bytestrings in JSON. ([#4191][gh4191])
* JSONField(binary=True) represents using binary strings, which JSONRenderer does not support. ([#4187][gh4187])
* JSONField(binary=True) represents using binary strings, which JSONRenderer does not support. ([#4185][gh4185])
* More robust form rendering in the browsable API. ([#4181][gh4181])
* Empty cases of `.validated_data` and `.errors` as lists not dicts for ListSerializer. ([#4180][gh4180])
* Schemas & client libraries. ([#4179][gh4179])
* Removed `AUTH_USER_MODEL` compat property. ([#4176][gh4176])
* Clean up existing deprecation warnings. ([#4166][gh4166])
* Django 1.10 support. ([#4158][gh4158])
* Updated jQuery version to 1.12.4. ([#4157][gh4157])
* More robust default behavior on OrderingFilter. ([#4156][gh4156])
* description.py codes and tests removal. ([#4153][gh4153])
* Wrap guardian.VERSION in tuple. ([#4149][gh4149])
* Refine validator for fields with <source=> kwargs. ([#4146][gh4146])
* Fix None values representation in childs of ListField, DictField. ([#4118][gh4118])
* Resolve TimeField representation for midnight value. ([#4107][gh4107])
* Set proper status code in AdminRenderer for the redirection after POST/DELETE requests. ([#4106][gh4106])
* TimeField render returns None instead of 00:00:00. ([#4105][gh4105])
* Fix incorrectly named zh-hans and zh-hant locale path. ([#4103][gh4103])
* Prevent raising exception when limit is 0. ([#4098][gh4098])
* TokenAuthentication: Allow custom keyword in the header. ([#4097][gh4097])
* Handle incorrectly padded HTTP basic auth header. ([#4090][gh4090])
* LimitOffset pagination crashes Browseable API when limit=0. ([#4079][gh4079])
* Fixed DecimalField arbitrary precision support. ([#4075][gh4075])
* Added support for custom CSRF cookie names. ([#4049][gh4049])
* Fix regression introduced by #4035. ([#4041][gh4041])
* No auth view failing permission should raise 403. ([#4040][gh4040])
* Fix string_types / text_types confusion. ([#4025][gh4025])
* Do not list related field choices in OPTIONS requests. ([#4021][gh4021])
* Fix typo. ([#4008][gh4008])
* Reorder initializing the view. ([#4006][gh4006])
* Type error in DjangoObjectPermissionsFilter on Python 3.4. ([#4005][gh4005])
* Fixed use of deprecated Query.aggregates. ([#4003][gh4003])
* Fix blank lines around docstrings. ([#4002][gh4002])
* Fixed admin pagination when limit is 0. ([#3990][gh3990])
* OrderingFilter adjustments. ([#3983][gh3983])
* Non-required serializer related fields. ([#3976][gh3976])
* Using safer calling way of "@api_view" in tutorial. ([#3971][gh3971])
* ListSerializer doesn't handle unique_together constraints. ([#3970][gh3970])
* Add missing migration file. ([#3968][gh3968])
* `OrderingFilter` should call `get_serializer_class()` to determine default fields. ([#3964][gh3964])
* Remove old django checks from tests and compat. ([#3953][gh3953])
* Support callable as the value of `initial` for any `serializer.Field`. ([#3943][gh3943])
* Prevented unnecessary distinct() call in SearchFilter. ([#3938][gh3938])
* Fix None UUID ForeignKey serialization. ([#3936][gh3936])
* Drop EOL Django 1.7. ([#3933][gh3933])
* Add missing space in serializer error message. ([#3926][gh3926])
* Fixed _force_text_recursive typo. ([#3908][gh3908])
* Attempt to address Django 2.0 deprecate warnings related to `field.rel`. ([#3906][gh3906])
* Fix parsing multipart data using a nested serializer with list. ([#3820][gh3820])
* Resolving APIs URL to different namespaces. ([#3816][gh3816])
* Do not HTML-escape `help_text` in Browsable API forms. ([#3812][gh3812])
* OPTIONS fetches and shows all possible foreign keys in choices field. ([#3751][gh3751])
* Django 1.9 deprecation warnings ([#3729][gh3729])
* Test case for #3598 ([#3710][gh3710])
* Adding support for multiple values for search filter. ([#3541][gh3541])
* Use get_serializer_class in ordering filter. ([#3487][gh3487])
* Serializers with many=True should return empty list rather than empty dict. ([#3476][gh3476])
* LimitOffsetPagination limit=0 fix. ([#3444][gh3444])
* Enable Validators to defer string evaluation and handle new string format. ([#3438][gh3438])
* Unique validator is executed and breaks if field is invalid. ([#3381][gh3381])
* Do not ignore overridden View.get_view_name() in breadcrumbs. ([#3273][gh3273])
* Retry form rendering when rendering with serializer fails. ([#3164][gh3164])
* Unique constraint prevents nested serializers from updating. ([#2996][gh2996])
* Uniqueness validators should not be run for excluded (read_only) fields. ([#2848][gh2848])
* UniqueValidator raises exception for nested objects. ([#2403][gh2403])
* `lookup_type` is deprecated in favor of `lookup_expr`. ([#4259][gh4259])
---
## 3.3.x series
### 3.4
### 3.3.3
**Unreleased**
**Date**: [14th March 2016][3.3.3-milestone].
* Dropped support for EOL Django 1.7 ([#3933][gh3933])
* Remove version string from templates. Thanks to @blag for the report and fixes. ([#3878][gh3878], [#3913][gh3913], [#3912][gh3912])
* Fixes vertical html layout for `BooleanField`. Thanks to Mikalai Radchuk for the fix. ([#3910][gh3910])
* Silenced deprecation warnings on Django 1.8. Thanks to Simon Charette for the fix. ([#3903][gh3903])
* Internationalization for authtoken. Thanks to Michael Nacharov for the fix. ([#3887][gh3887], [#3968][gh3968])
* Fix `Token` model as `abstract` when the authtoken application isn't declared. Thanks to Adam Thomas for the report. ([#3860][gh3860], [#3858][gh3858])
* Improve Markdown version compatibility. Thanks to Michael J. Schultz for the fix. ([#3604][gh3604], [#3842][gh3842])
* `QueryParameterVersioning` does not use `DEFAULT_VERSION` setting. Thanks to Brad Montgomery for the fix. ([#3833][gh3833])
* Add an explicit `on_delete` on the models. Thanks to Mads Jensen for the fix. ([#3832][gh3832])
* Fix `DateField.to_representation` to work with Python 2 unicode. Thanks to Mikalai Radchuk for the fix. ([#3819][gh3819])
* Fixed `TimeField` not handling string times. Thanks to Areski Belaid for the fix. ([#3809][gh3809])
* Avoid updates of `Meta.extra_kwargs`. Thanks to Kevin Massey for the report and fix. ([#3805][gh3805], [#3804][gh3804])
* Fix nested validation error being rendered incorrectly. Thanks to Craig de Stigter for the fix. ([#3801][gh3801])
* Document how to avoid CSRF and missing button issues with `django-crispy-forms`. Thanks to Emmanuelle Delescolle, José Padilla and Luis San Pablo for the report, analysis and fix. ([#3787][gh3787], [#3636][gh3636], [#3637][gh3637])
* Improve Rest Framework Settings file setup time. Thanks to Miles Hutson for the report and Mads Jensen for the fix. ([#3786][gh3786], [#3815][gh3815])
* Improve authtoken compatibility with Django 1.9. Thanks to S. Andrew Sheppard for the fix. ([#3785][gh3785])
* Fix `Min/MaxValueValidator` transfer from a model's `DecimalField`. Thanks to Kevin Brown for the fix. ([#3774][gh3774])
* Improve HTML title in the Browsable API. Thanks to Mike Lissner for the report and fix. ([#3769][gh3769])
* Fix `AutoFilterSet` to inherit from `default_filter_set`. Thanks to Tom Linford for the fix. ([#3753][gh3753])
* Fix transifex config to handle the new Chinese language codes. Thanks to @nypisces for the report and fix. ([#3739][gh3739])
* `DateTimeField` does not handle empty values correctly. Thanks to Mick Parker for the report and fix. ([#3731][gh3731], [#3726][gh3728])
* Raise error when setting a removed rest_framework setting. Thanks to Luis San Pablo for the fix. ([#3715][gh3715])
* Add missing csrf_token in AdminRenderer post form. Thanks to Piotr Śniegowski for the fix. ([#3703][gh3703])
* Refactored `_get_reverse_relationships()` to use correct `to_field`. Thanks to Benjamin Phillips for the fix. ([#3696][gh3696])
* Document the use of `get_queryset` for `RelatedField`. Thanks to Ryan Hiebert for the fix. ([#3605][gh3605])
* Fix empty pk detection in HyperlinkRelatedField.get_url. Thanks to @jslang for the fix ([#3962][gh3962])
### 3.3.2
@ -94,6 +342,8 @@ You can determine your currently installed version using `pip freeze`:
* Removed support for Django 1.5 & 1.6. ([#3421][gh3421], [#3429][gh3429])
* Removed 'south' migrations. ([#3495][gh3495])
---
## 3.2.x series
### 3.2.5
@ -348,7 +598,7 @@ For older release notes, [please see the version 2.x documentation][old-release-
[cite]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html
[deprecation-policy]: #deprecation-policy
[django-deprecation-policy]: https://docs.djangoproject.com/en/dev/internals/release-process/#internal-release-deprecation-policy
[django-deprecation-policy]: https://docs.djangoproject.com/en/stable/internals/release-process/#internal-release-deprecation-policy
[defusedxml-announce]: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html
[743]: https://github.com/tomchristie/django-rest-framework/pull/743
[staticfiles14]: https://docs.djangoproject.com/en/1.4/howto/static-files/#with-a-template-tag
@ -376,6 +626,19 @@ For older release notes, [please see the version 2.x documentation][old-release-
[3.3.0-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.3.0+Release%22
[3.3.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.3.1+Release%22
[3.3.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.3.2+Release%22
[3.3.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.3.3+Release%22
[3.4.0-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.0+Release%22
[3.4.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.1+Release%22
[3.4.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.2+Release%22
[3.4.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.3+Release%22
[3.4.4-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.4+Release%22
[3.4.5-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.5+Release%22
[3.4.6-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.6+Release%22
[3.4.7-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.7+Release%22
[3.5.0-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.5.0+Release%22
[3.5.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.5.1+Release%22
[3.5.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.5.2+Release%22
[3.5.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.5.3+Release%22
<!-- 3.0.1 -->
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
@ -655,3 +918,301 @@ For older release notes, [please see the version 2.x documentation][old-release-
[gh3714]: https://github.com/tomchristie/django-rest-framework/issues/3714
[gh3718]: https://github.com/tomchristie/django-rest-framework/issues/3718
[gh3723]: https://github.com/tomchristie/django-rest-framework/issues/3723
<!-- 3.3.3 -->
[gh3968]: https://github.com/tomchristie/django-rest-framework/issues/3968
[gh3962]: https://github.com/tomchristie/django-rest-framework/issues/3962
[gh3913]: https://github.com/tomchristie/django-rest-framework/issues/3913
[gh3912]: https://github.com/tomchristie/django-rest-framework/issues/3912
[gh3910]: https://github.com/tomchristie/django-rest-framework/issues/3910
[gh3903]: https://github.com/tomchristie/django-rest-framework/issues/3903
[gh3887]: https://github.com/tomchristie/django-rest-framework/issues/3887
[gh3878]: https://github.com/tomchristie/django-rest-framework/issues/3878
[gh3860]: https://github.com/tomchristie/django-rest-framework/issues/3860
[gh3858]: https://github.com/tomchristie/django-rest-framework/issues/3858
[gh3842]: https://github.com/tomchristie/django-rest-framework/issues/3842
[gh3833]: https://github.com/tomchristie/django-rest-framework/issues/3833
[gh3832]: https://github.com/tomchristie/django-rest-framework/issues/3832
[gh3819]: https://github.com/tomchristie/django-rest-framework/issues/3819
[gh3815]: https://github.com/tomchristie/django-rest-framework/issues/3815
[gh3809]: https://github.com/tomchristie/django-rest-framework/issues/3809
[gh3805]: https://github.com/tomchristie/django-rest-framework/issues/3805
[gh3804]: https://github.com/tomchristie/django-rest-framework/issues/3804
[gh3801]: https://github.com/tomchristie/django-rest-framework/issues/3801
[gh3787]: https://github.com/tomchristie/django-rest-framework/issues/3787
[gh3786]: https://github.com/tomchristie/django-rest-framework/issues/3786
[gh3785]: https://github.com/tomchristie/django-rest-framework/issues/3785
[gh3774]: https://github.com/tomchristie/django-rest-framework/issues/3774
[gh3769]: https://github.com/tomchristie/django-rest-framework/issues/3769
[gh3753]: https://github.com/tomchristie/django-rest-framework/issues/3753
[gh3739]: https://github.com/tomchristie/django-rest-framework/issues/3739
[gh3731]: https://github.com/tomchristie/django-rest-framework/issues/3731
[gh3728]: https://github.com/tomchristie/django-rest-framework/issues/3726
[gh3715]: https://github.com/tomchristie/django-rest-framework/issues/3715
[gh3703]: https://github.com/tomchristie/django-rest-framework/issues/3703
[gh3696]: https://github.com/tomchristie/django-rest-framework/issues/3696
[gh3637]: https://github.com/tomchristie/django-rest-framework/issues/3637
[gh3636]: https://github.com/tomchristie/django-rest-framework/issues/3636
[gh3605]: https://github.com/tomchristie/django-rest-framework/issues/3605
[gh3604]: https://github.com/tomchristie/django-rest-framework/issues/3604
<!-- 3.4.0 -->
[gh2403]: https://github.com/tomchristie/django-rest-framework/issues/2403
[gh2848]: https://github.com/tomchristie/django-rest-framework/issues/2848
[gh2996]: https://github.com/tomchristie/django-rest-framework/issues/2996
[gh3164]: https://github.com/tomchristie/django-rest-framework/issues/3164
[gh3273]: https://github.com/tomchristie/django-rest-framework/issues/3273
[gh3381]: https://github.com/tomchristie/django-rest-framework/issues/3381
[gh3438]: https://github.com/tomchristie/django-rest-framework/issues/3438
[gh3444]: https://github.com/tomchristie/django-rest-framework/issues/3444
[gh3476]: https://github.com/tomchristie/django-rest-framework/issues/3476
[gh3487]: https://github.com/tomchristie/django-rest-framework/issues/3487
[gh3541]: https://github.com/tomchristie/django-rest-framework/issues/3541
[gh3710]: https://github.com/tomchristie/django-rest-framework/issues/3710
[gh3729]: https://github.com/tomchristie/django-rest-framework/issues/3729
[gh3751]: https://github.com/tomchristie/django-rest-framework/issues/3751
[gh3812]: https://github.com/tomchristie/django-rest-framework/issues/3812
[gh3816]: https://github.com/tomchristie/django-rest-framework/issues/3816
[gh3820]: https://github.com/tomchristie/django-rest-framework/issues/3820
[gh3906]: https://github.com/tomchristie/django-rest-framework/issues/3906
[gh3908]: https://github.com/tomchristie/django-rest-framework/issues/3908
[gh3926]: https://github.com/tomchristie/django-rest-framework/issues/3926
[gh3933]: https://github.com/tomchristie/django-rest-framework/issues/3933
[gh3936]: https://github.com/tomchristie/django-rest-framework/issues/3936
[gh3938]: https://github.com/tomchristie/django-rest-framework/issues/3938
[gh3943]: https://github.com/tomchristie/django-rest-framework/issues/3943
[gh3953]: https://github.com/tomchristie/django-rest-framework/issues/3953
[gh3964]: https://github.com/tomchristie/django-rest-framework/issues/3964
[gh3968]: https://github.com/tomchristie/django-rest-framework/issues/3968
[gh3970]: https://github.com/tomchristie/django-rest-framework/issues/3970
[gh3971]: https://github.com/tomchristie/django-rest-framework/issues/3971
[gh3976]: https://github.com/tomchristie/django-rest-framework/issues/3976
[gh3983]: https://github.com/tomchristie/django-rest-framework/issues/3983
[gh3990]: https://github.com/tomchristie/django-rest-framework/issues/3990
[gh4002]: https://github.com/tomchristie/django-rest-framework/issues/4002
[gh4003]: https://github.com/tomchristie/django-rest-framework/issues/4003
[gh4005]: https://github.com/tomchristie/django-rest-framework/issues/4005
[gh4006]: https://github.com/tomchristie/django-rest-framework/issues/4006
[gh4008]: https://github.com/tomchristie/django-rest-framework/issues/4008
[gh4021]: https://github.com/tomchristie/django-rest-framework/issues/4021
[gh4025]: https://github.com/tomchristie/django-rest-framework/issues/4025
[gh4040]: https://github.com/tomchristie/django-rest-framework/issues/4040
[gh4041]: https://github.com/tomchristie/django-rest-framework/issues/4041
[gh4049]: https://github.com/tomchristie/django-rest-framework/issues/4049
[gh4075]: https://github.com/tomchristie/django-rest-framework/issues/4075
[gh4079]: https://github.com/tomchristie/django-rest-framework/issues/4079
[gh4090]: https://github.com/tomchristie/django-rest-framework/issues/4090
[gh4097]: https://github.com/tomchristie/django-rest-framework/issues/4097
[gh4098]: https://github.com/tomchristie/django-rest-framework/issues/4098
[gh4103]: https://github.com/tomchristie/django-rest-framework/issues/4103
[gh4105]: https://github.com/tomchristie/django-rest-framework/issues/4105
[gh4106]: https://github.com/tomchristie/django-rest-framework/issues/4106
[gh4107]: https://github.com/tomchristie/django-rest-framework/issues/4107
[gh4118]: https://github.com/tomchristie/django-rest-framework/issues/4118
[gh4146]: https://github.com/tomchristie/django-rest-framework/issues/4146
[gh4149]: https://github.com/tomchristie/django-rest-framework/issues/4149
[gh4153]: https://github.com/tomchristie/django-rest-framework/issues/4153
[gh4156]: https://github.com/tomchristie/django-rest-framework/issues/4156
[gh4157]: https://github.com/tomchristie/django-rest-framework/issues/4157
[gh4158]: https://github.com/tomchristie/django-rest-framework/issues/4158
[gh4166]: https://github.com/tomchristie/django-rest-framework/issues/4166
[gh4176]: https://github.com/tomchristie/django-rest-framework/issues/4176
[gh4179]: https://github.com/tomchristie/django-rest-framework/issues/4179
[gh4180]: https://github.com/tomchristie/django-rest-framework/issues/4180
[gh4181]: https://github.com/tomchristie/django-rest-framework/issues/4181
[gh4185]: https://github.com/tomchristie/django-rest-framework/issues/4185
[gh4187]: https://github.com/tomchristie/django-rest-framework/issues/4187
[gh4191]: https://github.com/tomchristie/django-rest-framework/issues/4191
[gh4192]: https://github.com/tomchristie/django-rest-framework/issues/4192
[gh4194]: https://github.com/tomchristie/django-rest-framework/issues/4194
[gh4195]: https://github.com/tomchristie/django-rest-framework/issues/4195
[gh4196]: https://github.com/tomchristie/django-rest-framework/issues/4196
[gh4212]: https://github.com/tomchristie/django-rest-framework/issues/4212
[gh4215]: https://github.com/tomchristie/django-rest-framework/issues/4215
[gh4217]: https://github.com/tomchristie/django-rest-framework/issues/4217
[gh4219]: https://github.com/tomchristie/django-rest-framework/issues/4219
[gh4229]: https://github.com/tomchristie/django-rest-framework/issues/4229
[gh4233]: https://github.com/tomchristie/django-rest-framework/issues/4233
[gh4244]: https://github.com/tomchristie/django-rest-framework/issues/4244
[gh4246]: https://github.com/tomchristie/django-rest-framework/issues/4246
[gh4253]: https://github.com/tomchristie/django-rest-framework/issues/4253
[gh4254]: https://github.com/tomchristie/django-rest-framework/issues/4254
[gh4255]: https://github.com/tomchristie/django-rest-framework/issues/4255
[gh4256]: https://github.com/tomchristie/django-rest-framework/issues/4256
[gh4259]: https://github.com/tomchristie/django-rest-framework/issues/4259
<!-- 3.4.1 -->
[gh4323]: https://github.com/tomchristie/django-rest-framework/issues/4323
[gh4268]: https://github.com/tomchristie/django-rest-framework/issues/4268
[gh4321]: https://github.com/tomchristie/django-rest-framework/issues/4321
[gh4308]: https://github.com/tomchristie/django-rest-framework/issues/4308
[gh4305]: https://github.com/tomchristie/django-rest-framework/issues/4305
[gh4316]: https://github.com/tomchristie/django-rest-framework/issues/4316
[gh4294]: https://github.com/tomchristie/django-rest-framework/issues/4294
[gh4293]: https://github.com/tomchristie/django-rest-framework/issues/4293
[gh4315]: https://github.com/tomchristie/django-rest-framework/issues/4315
[gh4314]: https://github.com/tomchristie/django-rest-framework/issues/4314
[gh4289]: https://github.com/tomchristie/django-rest-framework/issues/4289
[gh4265]: https://github.com/tomchristie/django-rest-framework/issues/4265
[gh4285]: https://github.com/tomchristie/django-rest-framework/issues/4285
[gh4287]: https://github.com/tomchristie/django-rest-framework/issues/4287
[gh4313]: https://github.com/tomchristie/django-rest-framework/issues/4313
[gh4281]: https://github.com/tomchristie/django-rest-framework/issues/4281
[gh4299]: https://github.com/tomchristie/django-rest-framework/issues/4299
[gh4307]: https://github.com/tomchristie/django-rest-framework/issues/4307
[gh4302]: https://github.com/tomchristie/django-rest-framework/issues/4302
[gh4303]: https://github.com/tomchristie/django-rest-framework/issues/4303
[gh4298]: https://github.com/tomchristie/django-rest-framework/issues/4298
[gh4291]: https://github.com/tomchristie/django-rest-framework/issues/4291
[gh4270]: https://github.com/tomchristie/django-rest-framework/issues/4270
[gh4272]: https://github.com/tomchristie/django-rest-framework/issues/4272
[gh4273]: https://github.com/tomchristie/django-rest-framework/issues/4273
[gh4288]: https://github.com/tomchristie/django-rest-framework/issues/4288
<!-- 3.4.2 -->
[gh3565]: https://github.com/tomchristie/django-rest-framework/issues/3565
[gh3610]: https://github.com/tomchristie/django-rest-framework/issues/3610
[gh4198]: https://github.com/tomchristie/django-rest-framework/issues/4198
[gh4199]: https://github.com/tomchristie/django-rest-framework/issues/4199
[gh4236]: https://github.com/tomchristie/django-rest-framework/issues/4236
[gh4292]: https://github.com/tomchristie/django-rest-framework/issues/4292
[gh4296]: https://github.com/tomchristie/django-rest-framework/issues/4296
[gh4318]: https://github.com/tomchristie/django-rest-framework/issues/4318
[gh4330]: https://github.com/tomchristie/django-rest-framework/issues/4330
[gh4331]: https://github.com/tomchristie/django-rest-framework/issues/4331
[gh4332]: https://github.com/tomchristie/django-rest-framework/issues/4332
[gh4335]: https://github.com/tomchristie/django-rest-framework/issues/4335
[gh4336]: https://github.com/tomchristie/django-rest-framework/issues/4336
[gh4338]: https://github.com/tomchristie/django-rest-framework/issues/4338
[gh4339]: https://github.com/tomchristie/django-rest-framework/issues/4339
[gh4340]: https://github.com/tomchristie/django-rest-framework/issues/4340
[gh4344]: https://github.com/tomchristie/django-rest-framework/issues/4344
[gh4345]: https://github.com/tomchristie/django-rest-framework/issues/4345
[gh4346]: https://github.com/tomchristie/django-rest-framework/issues/4346
[gh4347]: https://github.com/tomchristie/django-rest-framework/issues/4347
[gh4348]: https://github.com/tomchristie/django-rest-framework/issues/4348
[gh4349]: https://github.com/tomchristie/django-rest-framework/issues/4349
[gh4354]: https://github.com/tomchristie/django-rest-framework/issues/4354
[gh4357]: https://github.com/tomchristie/django-rest-framework/issues/4357
[gh4358]: https://github.com/tomchristie/django-rest-framework/issues/4358
[gh4359]: https://github.com/tomchristie/django-rest-framework/issues/4359
<!-- 3.4.3 -->
[gh4361]: https://github.com/tomchristie/django-rest-framework/issues/4361
<!-- 3.4.4 -->
[gh2829]: https://github.com/tomchristie/django-rest-framework/issues/2829
[gh3329]: https://github.com/tomchristie/django-rest-framework/issues/3329
[gh3330]: https://github.com/tomchristie/django-rest-framework/issues/3330
[gh3365]: https://github.com/tomchristie/django-rest-framework/issues/3365
[gh3394]: https://github.com/tomchristie/django-rest-framework/issues/3394
[gh3868]: https://github.com/tomchristie/django-rest-framework/issues/3868
[gh3868]: https://github.com/tomchristie/django-rest-framework/issues/3868
[gh3877]: https://github.com/tomchristie/django-rest-framework/issues/3877
[gh4042]: https://github.com/tomchristie/django-rest-framework/issues/4042
[gh4111]: https://github.com/tomchristie/django-rest-framework/issues/4111
[gh4119]: https://github.com/tomchristie/django-rest-framework/issues/4119
[gh4120]: https://github.com/tomchristie/django-rest-framework/issues/4120
[gh4121]: https://github.com/tomchristie/django-rest-framework/issues/4121
[gh4122]: https://github.com/tomchristie/django-rest-framework/issues/4122
[gh4137]: https://github.com/tomchristie/django-rest-framework/issues/4137
[gh4172]: https://github.com/tomchristie/django-rest-framework/issues/4172
[gh4201]: https://github.com/tomchristie/django-rest-framework/issues/4201
[gh4260]: https://github.com/tomchristie/django-rest-framework/issues/4260
[gh4278]: https://github.com/tomchristie/django-rest-framework/issues/4278
[gh4279]: https://github.com/tomchristie/django-rest-framework/issues/4279
[gh4329]: https://github.com/tomchristie/django-rest-framework/issues/4329
[gh4370]: https://github.com/tomchristie/django-rest-framework/issues/4370
[gh4371]: https://github.com/tomchristie/django-rest-framework/issues/4371
[gh4372]: https://github.com/tomchristie/django-rest-framework/issues/4372
[gh4373]: https://github.com/tomchristie/django-rest-framework/issues/4373
[gh4374]: https://github.com/tomchristie/django-rest-framework/issues/4374
[gh4375]: https://github.com/tomchristie/django-rest-framework/issues/4375
[gh4376]: https://github.com/tomchristie/django-rest-framework/issues/4376
[gh4377]: https://github.com/tomchristie/django-rest-framework/issues/4377
[gh4378]: https://github.com/tomchristie/django-rest-framework/issues/4378
[gh4379]: https://github.com/tomchristie/django-rest-framework/issues/4379
[gh4380]: https://github.com/tomchristie/django-rest-framework/issues/4380
[gh4382]: https://github.com/tomchristie/django-rest-framework/issues/4382
[gh4383]: https://github.com/tomchristie/django-rest-framework/issues/4383
[gh4386]: https://github.com/tomchristie/django-rest-framework/issues/4386
[gh4387]: https://github.com/tomchristie/django-rest-framework/issues/4387
[gh4388]: https://github.com/tomchristie/django-rest-framework/issues/4388
[gh4390]: https://github.com/tomchristie/django-rest-framework/issues/4390
[gh4391]: https://github.com/tomchristie/django-rest-framework/issues/4391
[gh4392]: https://github.com/tomchristie/django-rest-framework/issues/4392
[gh4393]: https://github.com/tomchristie/django-rest-framework/issues/4393
[gh4394]: https://github.com/tomchristie/django-rest-framework/issues/4394
<!-- 3.4.5 -->
[gh4416]: https://github.com/tomchristie/django-rest-framework/issues/4416
[gh4409]: https://github.com/tomchristie/django-rest-framework/issues/4409
[gh4415]: https://github.com/tomchristie/django-rest-framework/issues/4415
[gh4410]: https://github.com/tomchristie/django-rest-framework/issues/4410
[gh4408]: https://github.com/tomchristie/django-rest-framework/issues/4408
[gh4398]: https://github.com/tomchristie/django-rest-framework/issues/4398
[gh4407]: https://github.com/tomchristie/django-rest-framework/issues/4407
[gh4403]: https://github.com/tomchristie/django-rest-framework/issues/4403
[gh4404]: https://github.com/tomchristie/django-rest-framework/issues/4404
[gh4412]: https://github.com/tomchristie/django-rest-framework/issues/4412
<!-- 3.4.6 -->
[gh4435]: https://github.com/tomchristie/django-rest-framework/issues/4435
[gh4425]: https://github.com/tomchristie/django-rest-framework/issues/4425
[gh4429]: https://github.com/tomchristie/django-rest-framework/issues/4429
[gh3508]: https://github.com/tomchristie/django-rest-framework/issues/3508
[gh4419]: https://github.com/tomchristie/django-rest-framework/issues/4419
[gh4423]: https://github.com/tomchristie/django-rest-framework/issues/4423
<!-- 3.4.7 -->
[gh3951]: https://github.com/tomchristie/django-rest-framework/issues/3951
[gh4500]: https://github.com/tomchristie/django-rest-framework/issues/4500
[gh4489]: https://github.com/tomchristie/django-rest-framework/issues/4489
[gh4490]: https://github.com/tomchristie/django-rest-framework/issues/4490
[gh2617]: https://github.com/tomchristie/django-rest-framework/issues/2617
[gh4472]: https://github.com/tomchristie/django-rest-framework/issues/4472
[gh4473]: https://github.com/tomchristie/django-rest-framework/issues/4473
[gh4495]: https://github.com/tomchristie/django-rest-framework/issues/4495
[gh4493]: https://github.com/tomchristie/django-rest-framework/issues/4493
[gh4465]: https://github.com/tomchristie/django-rest-framework/issues/4465
[gh4462]: https://github.com/tomchristie/django-rest-framework/issues/4462
[gh4458]: https://github.com/tomchristie/django-rest-framework/issues/4458
<!-- 3.5.1 -->
[gh4612]: https://github.com/tomchristie/django-rest-framework/issues/4612
[gh4608]: https://github.com/tomchristie/django-rest-framework/issues/4608
[gh4601]: https://github.com/tomchristie/django-rest-framework/issues/4601
[gh4611]: https://github.com/tomchristie/django-rest-framework/issues/4611
[gh4605]: https://github.com/tomchristie/django-rest-framework/issues/4605
[gh4609]: https://github.com/tomchristie/django-rest-framework/issues/4609
[gh4606]: https://github.com/tomchristie/django-rest-framework/issues/4606
[gh4600]: https://github.com/tomchristie/django-rest-framework/issues/4600
<!-- 3.5.2 -->
[gh4631]: https://github.com/tomchristie/django-rest-framework/issues/4631
[gh4638]: https://github.com/tomchristie/django-rest-framework/issues/4638
[gh4532]: https://github.com/tomchristie/django-rest-framework/issues/4532
[gh4636]: https://github.com/tomchristie/django-rest-framework/issues/4636
[gh4622]: https://github.com/tomchristie/django-rest-framework/issues/4622
[gh4602]: https://github.com/tomchristie/django-rest-framework/issues/4602
[gh4640]: https://github.com/tomchristie/django-rest-framework/issues/4640
[gh4624]: https://github.com/tomchristie/django-rest-framework/issues/4624
[gh4569]: https://github.com/tomchristie/django-rest-framework/issues/4569
[gh4627]: https://github.com/tomchristie/django-rest-framework/issues/4627
[gh4620]: https://github.com/tomchristie/django-rest-framework/issues/4620
[gh4628]: https://github.com/tomchristie/django-rest-framework/issues/4628
[gh4639]: https://github.com/tomchristie/django-rest-framework/issues/4639
<!-- 3.5.3 -->
[gh4660]: https://github.com/tomchristie/django-rest-framework/issues/4660
[gh4643]: https://github.com/tomchristie/django-rest-framework/issues/4643
[gh4644]: https://github.com/tomchristie/django-rest-framework/issues/4644
[gh4645]: https://github.com/tomchristie/django-rest-framework/issues/4645
[gh4646]: https://github.com/tomchristie/django-rest-framework/issues/4646
[gh4650]: https://github.com/tomchristie/django-rest-framework/issues/4650

View File

@ -189,6 +189,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [djangorestframework-httpsignature][djangorestframework-httpsignature] - Provides an easy to use HTTP Signature Authentication mechanism.
* [djoser][djoser] - Provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation.
* [django-rest-auth][django-rest-auth] - Provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc.
* [drf-oidc-auth][drf-oidc-auth] - Implements OpenID Connect token authentication for DRF.
### Permissions
@ -203,6 +204,10 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [djangorestframework-gis][djangorestframework-gis] - Geographic add-ons
* [djangorestframework-hstore][djangorestframework-hstore] - Serializer class to support django-hstore DictionaryField model field and its schema-mode feature.
* [djangorestframework-jsonapi][djangorestframework-jsonapi] - Provides a parser, renderer, serializers, and other tools to help build an API that is compliant with the jsonapi.org spec.
* [html-json-forms][html-json-forms] - Provides an algorithm and serializer to process HTML JSON Form submissions per the (inactive) spec.
* [django-rest-framework-serializer-extensions][drf-serializer-extensions] -
Enables black/whitelisting fields, and conditionally expanding child serializers on a per-view/request basis.
* [djangorestframework-queryfields][djangorestframework-queryfields] - Serializer mixin allowing clients to control which fields will be sent in the API response.
### Serializer fields
@ -237,6 +242,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [djangorestframework-chain][djangorestframework-chain] - Allows arbitrary chaining of both relations and lookup filters.
* [django-url-filter][django-url-filter] - Allows a safe way to filter data via human-friendly URLs. It is a generic library which is not tied to DRF but it provides easy integration with DRF.
* [drf-url-filter][drf-url-filter] is a simple Django app to apply filters on drf `ModelViewSet`'s `Queryset` in a clean, simple and configurable way. It also supports validations on incoming query params and their values.
### Misc
@ -249,9 +255,11 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [ember-django-adapter][ember-django-adapter] - An adapter for working with Ember.js
* [django-versatileimagefield][django-versatileimagefield] - Provides a drop-in replacement for Django's stock `ImageField` that makes it easy to serve images in multiple sizes/renditions from a single field. For DRF-specific implementation docs, [click here][django-versatileimagefield-drf-docs].
* [drf-tracking][drf-tracking] - Utilities to track requests to DRF API views.
* [django-rest-framework-braces][django-rest-framework-braces] - Collection of utilities for working with Django Rest Framework. The most notable ones are [FormSerializer](https://django-rest-framework-braces.readthedocs.org/en/latest/overview.html#formserializer) and [SerializerForm](https://django-rest-framework-braces.readthedocs.org/en/latest/overview.html#serializerform), which are adapters between DRF serializers and Django forms.
* [drf_tweaks][drf_tweaks] - Serializers with one-step validation (and more), pagination without counts and other tweaks.
* [django-rest-framework-braces][django-rest-framework-braces] - Collection of utilities for working with Django Rest Framework. The most notable ones are [FormSerializer](https://django-rest-framework-braces.readthedocs.io/en/latest/overview.html#formserializer) and [SerializerForm](https://django-rest-framework-braces.readthedocs.io/en/latest/overview.html#serializerform), which are adapters between DRF serializers and Django forms.
* [drf-haystack][drf-haystack] - Haystack search for Django Rest Framework
* [django-rest-framework-version-transforms][django-rest-framework-version-transforms] - Enables the use of delta transformations for versioning of DRF resource representations.
* [django-rest-messaging][django-rest-messaging], [django-rest-messaging-centrifugo][django-rest-messaging-centrifugo] and [django-rest-messaging-js][django-rest-messaging-js] - A real-time pluggable messaging service using DRM.
## Other Resources
@ -265,18 +273,18 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [ViewSets and Routers - django-rest-framework part 3][viewsets-and-routers-django-rest-framework-part-3]
* [Django Rest Framework User Endpoint][django-rest-framework-user-endpoint]
* [Check credentials using Django Rest Framework][check-credentials-using-django-rest-framework]
* [Django REST Framework course][django-rest-framework-course]
### Videos
* [Ember and Django Part 1 (Video)][ember-and-django-part 1-video]
* [Django Rest Framework Part 1 (Video)][django-rest-framework-part-1-video]
* [Pyowa July 2013 - Django Rest Framework (Video)][pyowa-july-2013-django-rest-framework-video]
* [django-rest-framework and angularjs (Video)][django-rest-framework-and-angularjs-video]
### Articles
* [Web API performance: profiling Django REST framework][web-api-performance-profiling-django-rest-framework]
* [API Development with Django and Django REST Framework][api-development-with-django-and-django-rest-framework]
* [Blog posts about Django REST framework][medium-django-rest-framework]
### Documentations
* [Classy Django REST Framework][cdrf.co]
@ -289,7 +297,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[travis-profile]: https://travis-ci.org/profile
[pypi-register]: https://pypi.python.org/pypi?%3Aaction=register_form
[semver]: http://semver.org/
[tox-docs]: https://tox.readthedocs.org/en/latest/
[tox-docs]: https://tox.readthedocs.io/en/latest/
[drf-compat]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/compat.py
[rest-framework-grid]: https://www.djangopackages.com/grids/g/django-rest-framework/
[drf-create-pr]: https://github.com/tomchristie/django-rest-framework/compare
@ -331,7 +339,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[ember-django-adapter]: https://github.com/dustinfarris/ember-django-adapter
[beginners-guide-to-the-django-rest-framework]: http://code.tutsplus.com/tutorials/beginners-guide-to-the-django-rest-framework--cms-19786
[getting-started-with-django-rest-framework-and-angularjs]: http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html
[end-to-end-web-app-with-django-rest-framework-angularjs]: http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework
[end-to-end-web-app-with-django-rest-framework-angularjs]: http://mourafiq.com/2013/07/01/end-to-end-web-app-with-django-angular-1.html
[start-your-api-django-rest-framework-part-1]: https://godjango.com/41-start-your-api-django-rest-framework-part-1/
[permissions-authentication-django-rest-framework-part-2]: https://godjango.com/43-permissions-authentication-django-rest-framework-part-2/
[viewsets-and-routers-django-rest-framework-part-3]: https://godjango.com/45-viewsets-and-routers-django-rest-framework-part-3/
@ -339,19 +347,28 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[check-credentials-using-django-rest-framework]: http://richardtier.com/2014/03/06/110/
[ember-and-django-part 1-video]: http://www.neckbeardrepublic.com/screencasts/ember-and-django-part-1
[django-rest-framework-part-1-video]: http://www.neckbeardrepublic.com/screencasts/django-rest-framework-part-1
[pyowa-july-2013-django-rest-framework-video]: http://www.youtube.com/watch?v=e1zrehvxpbo
[django-rest-framework-and-angularjs-video]: http://www.youtube.com/watch?v=q8frbgtj020
[web-api-performance-profiling-django-rest-framework]: http://dabapps.com/blog/api-performance-profiling-django-rest-framework/
[api-development-with-django-and-django-rest-framework]: https://bnotions.com/api-development-with-django-and-django-rest-framework/
[django-rest-auth]: https://github.com/Tivix/django-rest-auth/
[django-versatileimagefield]: https://github.com/WGBH/django-versatileimagefield
[django-versatileimagefield-drf-docs]:http://django-versatileimagefield.readthedocs.org/en/latest/drf_integration.html
[django-versatileimagefield-drf-docs]:https://django-versatileimagefield.readthedocs.io/en/latest/drf_integration.html
[cdrf.co]:http://www.cdrf.co
[drf-tracking]: https://github.com/aschn/drf-tracking
[django-rest-framework-braces]: https://github.com/dealertrack/django-rest-framework-braces
[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions
[django-url-filter]: https://github.com/miki725/django-url-filter
[drf-url-filter]: https://github.com/manjitkumar/drf-url-filters
[cookiecutter-django-rest]: https://github.com/agconti/cookiecutter-django-rest
[drf-haystack]: http://drf-haystack.readthedocs.org/en/latest/
[drf-haystack]: https://drf-haystack.readthedocs.io/en/latest/
[django-rest-framework-version-transforms]: https://github.com/mrhwick/django-rest-framework-version-transforms
[djangorestframework-jsonapi]: https://github.com/django-json-api/django-rest-framework-json-api
[html-json-forms]: https://github.com/wq/html-json-forms
[django-rest-messaging]: https://github.com/raphaelgyory/django-rest-messaging
[django-rest-messaging-centrifugo]: https://github.com/raphaelgyory/django-rest-messaging-centrifugo
[django-rest-messaging-js]: https://github.com/raphaelgyory/django-rest-messaging-js
[medium-django-rest-framework]: https://medium.com/django-rest-framework
[django-rest-framework-course]: https://teamtreehouse.com/library/django-rest-framework
[drf_tweaks]: https://github.com/ArabellaTech/drf_tweaks
[drf-oidc-auth]: https://github.com/ByteInternet/drf-oidc-auth
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
[djangorestframework-queryfields]: https://github.com/wimglenn/djangorestframework-queryfields

View File

@ -48,6 +48,8 @@ We'll need to add our new `snippets` app and the `rest_framework` app to `INSTAL
'snippets.apps.SnippetsConfig',
)
Please note that if you're using Django <1.9, you need to replace `snippets.apps.SnippetsConfig` with `snippets`.
Okay, we're ready to roll.
## Creating a model to work with
@ -88,7 +90,7 @@ The first thing we need to get started on our Web API is to provide a way of ser
class SnippetSerializer(serializers.Serializer):
pk = serializers.IntegerField(read_only=True)
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
@ -144,13 +146,13 @@ We've now got a few snippet instances to play with. Let's take a look at serial
serializer = SnippetSerializer(snippet)
serializer.data
# {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
# {'id': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
At this point we've translated the model instance into Python native datatypes. To finalize the serialization process we render the data into `json`.
content = JSONRenderer().render(serializer.data)
content
# '{"pk": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
# '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
Deserialization is similar. First we parse a stream into Python native datatypes...
@ -175,7 +177,7 @@ We can also serialize querysets instead of model instances. To do so we simply
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('pk', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('pk', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('pk', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
## Using ModelSerializers

View File

@ -25,7 +25,7 @@ Using numeric HTTP status codes in your views doesn't always make for obvious re
REST framework provides two wrappers you can use to write API views.
1. The `@api_view` decorator for working with function based views.
2. The `APIView` class for working with class based views.
2. The `APIView` class for working with class-based views.
These wrappers provide a few bits of functionality such as making sure you receive `Request` instances in your view, and adding context to `Response` objects so that content negotiation can be performed.
@ -96,7 +96,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.
@ -186,6 +186,8 @@ Similarly, we can control the format of the request that we send, using the `Con
"style": "friendly"
}
If you add a `--debug` switch to the `http` requests above, you will be able to see the request type in request headers.
Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/snippets/][devserver].
### Browsability
@ -198,9 +200,9 @@ See the [browsable api][browsable-api] topic for more information about the brow
## What's next?
In [tutorial part 3][tut-3], we'll start using class based views, and see how generic views reduce the amount of code we need to write.
In [tutorial part 3][tut-3], we'll start using class-based views, and see how generic views reduce the amount of code we need to write.
[json-url]: http://example.com/api/items/4/.json
[json-url]: http://example.com/api/items/4.json
[devserver]: http://127.0.0.1:8000/snippets/
[browsable-api]: ../topics/browsable-api.md
[tut-1]: 1-serialization.md

View File

@ -1,10 +1,10 @@
# Tutorial 3: Class Based Views
# Tutorial 3: Class-based Views
We can also write our API views using class based views, rather than function based views. As we'll see this is a powerful pattern that allows us to reuse common functionality, and helps us keep our code [DRY][dry].
We can also write our API views using class-based views, rather than function based views. As we'll see this is a powerful pattern that allows us to reuse common functionality, and helps us keep our code [DRY][dry].
## Rewriting our API using class based views
## Rewriting our API using class-based views
We'll start by rewriting the root view as a class based view. All this involves is a little bit of refactoring of `views.py`.
We'll start by rewriting the root view as a class-based view. All this involves is a little bit of refactoring of `views.py`.
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@ -62,7 +62,7 @@ So far, so good. It looks pretty similar to the previous case, but we've got be
That's looking good. Again, it's still pretty similar to the function based view right now.
We'll also need to refactor our `urls.py` slightly now we're using class based views.
We'll also need to refactor our `urls.py` slightly now that we're using class-based views.
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
@ -79,7 +79,7 @@ Okay, we're done. If you run the development server everything should be workin
## Using mixins
One of the big wins of using class based views is that it allows us to easily compose reusable bits of behaviour.
One of the big wins of using class-based views is that it allows us to easily compose reusable bits of behaviour.
The create/retrieve/update/delete operations that we've been using so far are going to be pretty similar for any model-backed API views we create. Those bits of common behaviour are implemented in REST framework's mixin classes.
@ -124,7 +124,7 @@ The base class provides the core functionality, and the mixin classes provide th
Pretty similar. Again we're using the `GenericAPIView` class to provide the core functionality, and adding in mixins to provide the `.retrieve()`, `.update()` and `.destroy()` actions.
## Using generic class based views
## Using generic class-based views
Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use to trim down our `views.py` module even more.

View File

@ -14,7 +14,7 @@ First, let's add a couple of fields. One of those fields will be used to repres
Add the following two fields to the `Snippet` model in `models.py`.
owner = models.ForeignKey('auth.User', related_name='snippets')
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
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 highlighting library.
@ -67,7 +67,7 @@ Now that we've got some users to work with, we'd better add representations of t
Because `'snippets'` is a *reverse* relationship on the User model, it will not be included by default when using the `ModelSerializer` class, so we needed to add an explicit field for it.
We'll also add a couple of views to `views.py`. We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class based views.
We'll also add a couple of views to `views.py`. We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class-based views.
from django.contrib.auth.models import User
@ -150,7 +150,7 @@ The `r'^api-auth/'` part of pattern can actually be whatever URL you want to use
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.
Once you've created a few code snippets, navigate to the '/users/' endpoint, and notice that the representation includes a list of the snippet ids that are associated with each user, in each user's 'snippets' field.
## Object level permissions

View File

@ -18,7 +18,7 @@ Right now we have endpoints for 'snippets' and 'users', but we don't have a sing
'snippets': reverse('snippet-list', request=request, format=format)
})
Two things should be noticed here. First, we're using REST framework's `reverse` function in order to return fully-qualified URLs; second, URL patterns are identified by convenience names that we will declare later on in our `snippets/urls.py`.
Two things should be noticed here. First, we're using REST framework's `reverse` function in order to return fully-qualified URLs; second, URL patterns are identified by convenience names that we will declare later on in our `snippets/urls.py`.
## Creating an endpoint for the highlighted snippets
@ -67,7 +67,7 @@ In this case we'd like to use a hyperlinked style between entities. In order to
The `HyperlinkedModelSerializer` has the following differences from `ModelSerializer`:
* It does not include the `pk` field by default.
* It does not include the `id` field by default.
* It includes a `url` field, using `HyperlinkedIdentityField`.
* Relationships use `HyperlinkedRelatedField`,
instead of `PrimaryKeyRelatedField`.
@ -80,7 +80,7 @@ We can easily re-write our existing serializers to use hyperlinking. In your `sn
class Meta:
model = Snippet
fields = ('url', 'highlight', 'owner',
fields = ('url', 'id', 'highlight', 'owner',
'title', 'code', 'linenos', 'language', 'style')
@ -89,7 +89,7 @@ We can easily re-write our existing serializers to use hyperlinking. In your `sn
class Meta:
model = User
fields = ('url', 'username', 'snippets')
fields = ('url', 'id', 'username', 'snippets')
Notice that we've also added a new `'highlight'` field. This field is of the same type as the `url` field, except that it points to the `'snippet-highlight'` url pattern, instead of the `'snippet-detail'` url pattern.

View File

@ -51,7 +51,7 @@ This time we've used the `ModelViewSet` class in order to get the complete set o
Notice that we've also used the `@detail_route` decorator to create a custom action, named `highlight`. This decorator can be used to add any custom endpoints that don't fit into the standard `create`/`update`/`delete` style.
Custom actions which use the `@detail_route` decorator will respond to `GET` requests. We can use the `methods` argument if we wanted an action that responded to `POST` requests.
Custom actions which use the `@detail_route` decorator will respond to `GET` requests by default. We can use the `methods` argument if we wanted an action that responded to `POST` requests.
The URLs for custom actions by default depend on the method name itself. If you want to change the way url should be constructed, you can include url_path as a decorator keyword argument.
@ -130,27 +130,7 @@ Using viewsets can be a really useful abstraction. It helps ensure that URL con
That doesn't mean it's always the right approach to take. There's a similar set of trade-offs to consider as when using class-based views instead of function based views. Using viewsets is less explicit than building your views individually.
## Reviewing our work
In [part 7][tut-7] of the tutorial we'll look at how we can add an API schema,
and interact with our API using a client library or command line tool.
With an incredibly small amount of code, we've now got a complete pastebin Web API, which is fully web browsable, and comes complete with authentication, per-object permissions, and multiple renderer formats.
We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views.
You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox].
## Onwards and upwards
We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here are a few places you can start:
* Contribute on [GitHub][github] by reviewing and submitting issues, and making pull requests.
* Join the [REST framework discussion group][group], and help build the community.
* Follow [the author][twitter] on Twitter and say hi.
**Now go build awesome things.**
[repo]: https://github.com/tomchristie/rest-framework-tutorial
[sandbox]: http://restframework.herokuapp.com/
[github]: https://github.com/tomchristie/django-rest-framework
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[twitter]: https://twitter.com/_tomchristie
[tut-7]: 7-schemas-and-client-libraries.md

View File

@ -0,0 +1,228 @@
# Tutorial 7: Schemas & client libraries
A schema is a machine-readable document that describes the available API
endpoints, their URLS, and what operations they support.
Schemas can be a useful tool for auto-generated documentation, and can also
be used to drive dynamic client libraries that can interact with the API.
## Core API
In order to provide schema support REST framework uses [Core API][coreapi].
Core API is a document specification for describing APIs. It is used to provide
an internal representation format of the available endpoints and possible
interactions that an API exposes. It can either be used server-side, or
client-side.
When used server-side, Core API allows an API to support rendering to a wide
range of schema or hypermedia formats.
When used client-side, Core API allows for dynamically driven client libraries
that can interact with any API that exposes a supported schema or hypermedia
format.
## Adding a schema
REST framework supports either explicitly defined schema views, or
automatically generated schemas. Since we're using viewsets and routers,
we can simply use the automatic schema generation.
You'll need to install the `coreapi` python package in order to include an
API schema.
$ pip install coreapi
We can now include a schema for our API, by including an autogenerated schema
view in our URL configuration.
from rest_framework.schemas import get_schema_view
schema_view = get_schema_view(title='Pastebin API')
urlpatterns = [
url('^schema/$', schema_view),
...
]
If you visit the API root endpoint in a browser you should now see `corejson`
representation become available as an option.
![Schema format](../img/corejson-format.png)
We can also request the schema from the command line, by specifying the desired
content type in the `Accept` header.
$ http http://127.0.0.1:8000/schema/ Accept:application/coreapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/coreapi+json
{
"_meta": {
"title": "Pastebin API"
},
"_type": "document",
...
The default output style is to use the [Core JSON][corejson] encoding.
Other schema formats, such as [Open API][openapi] (formerly Swagger) are
also supported.
## Using a command line client
Now that our API is exposing a schema endpoint, we can use a dynamic client
library to interact with the API. To demonstrate this, let's use the
Core API command line client.
The command line client is available as the `coreapi-cli` package:
$ pip install coreapi-cli
Now check that it is available on the command line...
$ coreapi
Usage: coreapi [OPTIONS] COMMAND [ARGS]...
Command line client for interacting with CoreAPI services.
Visit http://www.coreapi.org for more information.
Options:
--version Display the package version number.
--help Show this message and exit.
Commands:
...
First we'll load the API schema using the command line client.
$ coreapi get http://127.0.0.1:8000/schema/
<Pastebin API "http://127.0.0.1:8000/schema/">
snippets: {
highlight(id)
list()
read(id)
}
users: {
list()
read(id)
}
We haven't authenticated yet, so right now we're only able to see the read only
endpoints, in line with how we've set up the permissions on the API.
Let's try listing the existing snippets, using the command line client:
$ coreapi action snippets list
[
{
"url": "http://127.0.0.1:8000/snippets/1/",
"id": 1,
"highlight": "http://127.0.0.1:8000/snippets/1/highlight/",
"owner": "lucy",
"title": "Example",
"code": "print('hello, world!')",
"linenos": true,
"language": "python",
"style": "friendly"
},
...
Some of the API endpoints require named parameters. For example, to get back
the highlight HTML for a particular snippet we need to provide an id.
$ coreapi action snippets highlight --param id=1
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Example</title>
...
## Authenticating our client
If we want to be able to create, edit and delete snippets, we'll need to
authenticate as a valid user. In this case we'll just use basic auth.
Make sure to replace the `<username>` and `<password>` below with your
actual username and password.
$ coreapi credentials add 127.0.0.1 <username>:<password> --auth basic
Added credentials
127.0.0.1 "Basic <...>"
Now if we fetch the schema again, we should be able to see the full
set of available interactions.
$ coreapi reload
Pastebin API "http://127.0.0.1:8000/schema/">
snippets: {
create(code, [title], [linenos], [language], [style])
delete(id)
highlight(id)
list()
partial_update(id, [title], [code], [linenos], [language], [style])
read(id)
update(id, code, [title], [linenos], [language], [style])
}
users: {
list()
read(id)
}
We're now able to interact with these endpoints. For example, to create a new
snippet:
$ coreapi action snippets create --param title="Example" --param code="print('hello, world')"
{
"url": "http://127.0.0.1:8000/snippets/7/",
"id": 7,
"highlight": "http://127.0.0.1:8000/snippets/7/highlight/",
"owner": "lucy",
"title": "Example",
"code": "print('hello, world')",
"linenos": false,
"language": "python",
"style": "friendly"
}
And to delete a snippet:
$ coreapi action snippets delete --param id=7
As well as the command line client, developers can also interact with your
API using client libraries. The Python client library is the first of these
to be available, and a Javascript client library is planned to be released
soon.
For more details on customizing schema generation and using Core API
client libraries you'll need to refer to the full documentation.
## Reviewing our work
With an incredibly small amount of code, we've now got a complete pastebin Web API, which is fully web browsable, includes a schema-driven client library, and comes complete with authentication, per-object permissions, and multiple renderer formats.
We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views.
You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox].
## Onwards and upwards
We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here are a few places you can start:
* Contribute on [GitHub][github] by reviewing and submitting issues, and making pull requests.
* Join the [REST framework discussion group][group], and help build the community.
* Follow [the author][twitter] on Twitter and say hi.
**Now go build awesome things.**
[coreapi]: http://www.coreapi.org
[corejson]: http://www.coreapi.org/specification/encoding/#core-json-encoding
[openapi]: https://openapis.org/
[repo]: https://github.com/tomchristie/rest-framework-tutorial
[sandbox]: http://restframework.herokuapp.com/
[github]: https://github.com/tomchristie/django-rest-framework
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[twitter]: https://twitter.com/_tomchristie

View File

@ -104,7 +104,7 @@ Okay, now let's wire up the API URLs. On to `tutorial/urls.py`...
Because we're using viewsets instead of views, we can automatically generate the URL conf for our API, by simply registering the viewsets with a router class.
Again, if we need more control over the API URLs we can simply drop down to using regular class based views, and writing the URL conf explicitly.
Again, if we need more control over the API URLs we can simply drop down to using regular class-based views, and writing the URL conf explicitly.
Finally, we're including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browsable API.
@ -118,7 +118,9 @@ We'd also like to set a few global settings. We'd like to turn on pagination, a
)
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAdminUser',
],
'PAGE_SIZE': 10
}
@ -130,7 +132,7 @@ Okay, we're done.
We're now ready to test the API we've built. Let's fire up the server from the command line.
python ./manage.py runserver
python manage.py runserver
We can now access our API, both from the command-line, using tools like `curl`...
@ -182,7 +184,7 @@ Or using the [httpie][httpie], command line tool...
}
Or directly through the browser...
Or directly through the browser, by going to the URL `http://127.0.0.1:8000/users/`...
![Quick start image][image]

View File

@ -38,15 +38,12 @@
</script>
<style>
span.fusion-wrap a {
display: block;
margin-top: 10px;
#sidebarInclude img {
margin-bottom: 10px;
}
#sidebarInclude a.promo {
color: black;
}
a.fusion-poweredby {
display: block;
margin-top: 10px;
}
}
@media (max-width: 767px) {
div.promo {
display: none;
@ -104,12 +101,10 @@
{% endfor %}
{% endfor %}
{% if current_page and current_page.is_homepage %}
<div class="promo">
<hr/>
<script type="text/javascript" src="//cdn.fusionads.net/fusion.js?zoneid=1332&serve=C6SDP2Y&placement=djangorestframework" id="_fusionads_js"></script>
<div id="sidebarInclude">
</div>
{% endif %}
</ul>
</div>
@ -146,6 +141,7 @@
<script src="{{ base_url }}/js/jquery-1.8.1-min.js"></script>
<script src="{{ base_url }}/js/prettify-1.0.js"></script>
<script src="{{ base_url }}/js/bootstrap-2.1.1-min.js"></script>
<script src="https://fund.django-rest-framework.org/sidebar_include.js"></script>
<script>var base_url = '{{ base_url }}';</script>
<script src="{{ base_url }}/mkdocs/js/require.js"></script>
<script src="{{ base_url }}/js/theme.js"></script>

View File

@ -20,6 +20,7 @@ pages:
- '4 - Authentication and permissions': 'tutorial/4-authentication-and-permissions.md'
- '5 - Relationships and hyperlinked APIs': 'tutorial/5-relationships-and-hyperlinked-apis.md'
- '6 - Viewsets and routers': 'tutorial/6-viewsets-and-routers.md'
- '7 - Schemas and client libraries': 'tutorial/7-schemas-and-client-libraries.md'
- API Guide:
- 'Requests': 'api-guide/requests.md'
- 'Responses': 'api-guide/responses.md'
@ -41,6 +42,7 @@ pages:
- 'Versioning': 'api-guide/versioning.md'
- 'Content negotiation': 'api-guide/content-negotiation.md'
- 'Metadata': 'api-guide/metadata.md'
- 'Schemas': 'api-guide/schemas.md'
- 'Format suffixes': 'api-guide/format-suffixes.md'
- 'Returning URLs': 'api-guide/reverse.md'
- 'Exceptions': 'api-guide/exceptions.md'
@ -49,6 +51,7 @@ pages:
- 'Settings': 'api-guide/settings.md'
- Topics:
- 'Documenting your API': 'topics/documenting-your-api.md'
- 'API Clients': 'topics/api-clients.md'
- 'Internationalization': 'topics/internationalization.md'
- 'AJAX, CSRF & CORS': 'topics/ajax-csrf-cors.md'
- 'HTML & Forms': 'topics/html-and-forms.md'
@ -62,5 +65,9 @@ pages:
- '3.1 Announcement': 'topics/3.1-announcement.md'
- '3.2 Announcement': 'topics/3.2-announcement.md'
- '3.3 Announcement': 'topics/3.3-announcement.md'
- '3.4 Announcement': 'topics/3.4-announcement.md'
- '3.5 Announcement': 'topics/3.5-announcement.md'
- 'Kickstarter Announcement': 'topics/kickstarter-announcement.md'
- 'Mozilla Grant': 'topics/mozilla-grant.md'
- 'Funding': 'topics/funding.md'
- 'Release Notes': 'topics/release-notes.md'

View File

@ -3,4 +3,4 @@ flake8==2.4.0
pep8==1.5.7
# Sort and lint imports
isort==4.2.2
isort==4.2.5

View File

@ -1,2 +1,2 @@
# MkDocs to build our documentation.
mkdocs==0.13.2
mkdocs==0.15.3

View File

@ -1,4 +1,5 @@
# Optional packages which may be used with REST framework.
markdown==2.6.4
django-guardian==1.3.2
django-filter==0.10.0
django-guardian==1.4.6
django-filter==1.0.0
coreapi==2.0.8

View File

@ -1,8 +1,11 @@
# Wheel for PyPI installs.
wheel==0.24.0
wheel==0.29.0
# Twine for secured PyPI uploads.
twine==1.4.0
twine==1.6.5
# Transifex client for managing translation resources.
transifex-client==0.11
# Pandoc to have a nice pypi page
pypandoc

View File

@ -1,4 +1,4 @@
# PyTest for running the tests.
pytest==2.8.5
pytest-django==2.9.1
pytest-cov==1.8.1
pytest==3.0.5
pytest-django==3.1.2
pytest-cov==2.4.0

View File

@ -1,4 +1,4 @@
"""
r"""
______ _____ _____ _____ __
| ___ \ ___/ ___|_ _| / _| | |
| |_/ / |__ \ `--. | | | |_ _ __ __ _ _ __ ___ _____ _____ _ __| |__
@ -8,7 +8,7 @@ ______ _____ _____ _____ __
"""
__title__ = 'Django REST framework'
__version__ = '3.3.2'
__version__ = '3.5.3'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2016 Tom Christie'

View File

@ -4,9 +4,11 @@ Provides various authentication policies.
from __future__ import unicode_literals
import base64
import binascii
from django.contrib.auth import authenticate, get_user_model
from django.middleware.csrf import CsrfViewMiddleware
from django.utils.six import text_type
from django.utils.translation import ugettext_lazy as _
from rest_framework import HTTP_HEADER_ENCODING, exceptions
@ -19,7 +21,7 @@ def get_authorization_header(request):
Hide some test client ickyness where the header can be unicode.
"""
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if isinstance(auth, type('')):
if isinstance(auth, text_type):
# Work around django test client oddness
auth = auth.encode(HTTP_HEADER_ENCODING)
return auth
@ -76,7 +78,7 @@ class BasicAuthentication(BaseAuthentication):
try:
auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
except (TypeError, UnicodeDecodeError):
except (TypeError, UnicodeDecodeError, binascii.Error):
msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
raise exceptions.AuthenticationFailed(msg)
@ -148,6 +150,7 @@ class TokenAuthentication(BaseAuthentication):
Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
"""
keyword = 'Token'
model = None
def get_model(self):
@ -166,7 +169,7 @@ class TokenAuthentication(BaseAuthentication):
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != b'token':
if not auth or auth[0].lower() != self.keyword.lower().encode():
return None
if len(auth) == 1:
@ -197,4 +200,4 @@ class TokenAuthentication(BaseAuthentication):
return (token.user, token)
def authenticate_header(self, request):
return 'Token'
return self.keyword

View File

@ -17,7 +17,7 @@ class Migration(migrations.Migration):
fields=[
('key', models.CharField(primary_key=True, serialize=False, max_length=40)),
('created', models.DateTimeField(auto_now_add=True)),
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='auth_token')),
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='auth_token', on_delete=models.CASCADE)),
],
options={
},

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authtoken', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='token',
options={'verbose_name_plural': 'Tokens', 'verbose_name': 'Token'},
),
migrations.AlterField(
model_name='token',
name='created',
field=models.DateTimeField(verbose_name='Created', auto_now_add=True),
),
migrations.AlterField(
model_name='token',
name='key',
field=models.CharField(verbose_name='Key', max_length=40, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='token',
name='user',
field=models.OneToOneField(to=settings.AUTH_USER_MODEL, verbose_name='User', related_name='auth_token', on_delete=models.CASCADE),
),
]

View File

@ -6,12 +6,6 @@ from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
# Prior to Django 1.5, the AUTH_USER_MODEL setting does not exist.
# Note that we don't perform this code in the compat module due to
# bug report #1297
# See: https://github.com/tomchristie/django-rest-framework/issues/1297
AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
@python_2_unicode_compatible
class Token(models.Model):
@ -19,8 +13,10 @@ class Token(models.Model):
The default authorization token model.
"""
key = models.CharField(_("Key"), max_length=40, primary_key=True)
user = models.OneToOneField(AUTH_USER_MODEL, related_name='auth_token',
on_delete=models.CASCADE, verbose_name=_("User"))
user = models.OneToOneField(
settings.AUTH_USER_MODEL, related_name='auth_token',
on_delete=models.CASCADE, verbose_name=_("User")
)
created = models.DateTimeField(_("Created"), auto_now_add=True)
class Meta:

View File

@ -16,15 +16,18 @@ class AuthTokenSerializer(serializers.Serializer):
user = authenticate(username=username, password=password)
if user:
# From Django 1.10 onwards the `authenticate` call simply
# returns `None` for is_active=False users.
# (Assuming the default `ModelBackend` authentication backend.)
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
raise serializers.ValidationError(msg, code='authorization')
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
raise serializers.ValidationError(msg, code='authorization')
else:
msg = _('Must include "username" and "password".')
raise serializers.ValidationError(msg)
raise serializers.ValidationError(msg, code='authorization')
attrs['user'] = user
return attrs

View File

@ -6,17 +6,32 @@ versions of Django/Python, and compatibility wrappers around optional packages.
# flake8: noqa
from __future__ import unicode_literals
import inspect
import django
from django.apps import apps
from django.conf import settings
from django.db import connection, transaction
from django.core.exceptions import ImproperlyConfigured
from django.db import connection, models, transaction
from django.template import Context, RequestContext, Template
from django.utils import six
from django.views.generic import View
try:
import importlib # Available in Python 3.1+
from django.urls import (
NoReverseMatch, RegexURLPattern, RegexURLResolver, ResolverMatch, Resolver404, get_script_prefix, reverse, reverse_lazy, resolve
)
except ImportError:
from django.utils import importlib # Will be removed in Django 1.9
from django.core.urlresolvers import ( # Will be removed in Django 2.0
NoReverseMatch, RegexURLPattern, RegexURLResolver, ResolverMatch, Resolver404, get_script_prefix, reverse, reverse_lazy, resolve
)
try:
import urlparse # Python 2.x
except ImportError:
import urllib.parse as urlparse
def unicode_repr(instance):
@ -58,6 +73,84 @@ def distinct(queryset, base):
return queryset.distinct()
# Obtaining manager instances and names from model options differs after 1.10.
def get_names_and_managers(options):
if django.VERSION >= (1, 10):
# Django 1.10 onwards provides a `.managers` property on the Options.
return [
(manager.name, manager)
for manager
in options.managers
]
# For Django 1.8 and 1.9, use the three-tuple information provided
# by .concrete_managers and .abstract_managers
return [
(manager_info[1], manager_info[2])
for manager_info
in (options.concrete_managers + options.abstract_managers)
]
# field.rel is deprecated from 1.9 onwards
def get_remote_field(field, **kwargs):
if 'default' in kwargs:
if django.VERSION < (1, 9):
return getattr(field, 'rel', kwargs['default'])
return getattr(field, 'remote_field', kwargs['default'])
if django.VERSION < (1, 9):
return field.rel
return field.remote_field
def _resolve_model(obj):
"""
Resolve supplied `obj` to a Django model class.
`obj` must be a Django model class itself, or a string
representation of one. Useful in situations like GH #1225 where
Django may not have resolved a string-based reference to a model in
another model's foreign key definition.
String representations should have the format:
'appname.ModelName'
"""
if isinstance(obj, six.string_types) and len(obj.split('.')) == 2:
app_name, model_name = obj.split('.')
resolved_model = apps.get_model(app_name, model_name)
if resolved_model is None:
msg = "Django did not return a model for {0}.{1}"
raise ImproperlyConfigured(msg.format(app_name, model_name))
return resolved_model
elif inspect.isclass(obj) and issubclass(obj, models.Model):
return obj
raise ValueError("{0} is not a Django model".format(obj))
def is_authenticated(user):
if django.VERSION < (1, 10):
return user.is_authenticated()
return user.is_authenticated
def is_anonymous(user):
if django.VERSION < (1, 10):
return user.is_anonymous()
return user.is_anonymous
def get_related_model(field):
if django.VERSION < (1, 9):
return _resolve_model(field.rel.to)
return field.remote_field.model
def value_from_object(field, obj):
if django.VERSION < (1, 9):
return field._get_val_from_obj(obj)
return field.value_from_object(obj)
# contrib.postgres only supported from 1.8 onwards.
try:
from django.contrib.postgres import fields as postgres_fields
@ -72,6 +165,16 @@ except ImportError:
JSONField = None
# coreapi is optional (Note that uritemplate is a dependency of coreapi)
try:
import coreapi
import uritemplate
except (ImportError, SyntaxError):
# SyntaxError is possible under python 3.2
coreapi = None
uritemplate = None
# django-filter is optional
try:
import django_filters
@ -86,13 +189,19 @@ except ImportError:
crispy_forms = None
# requests is optional
try:
import requests
except ImportError:
requests = None
# Django-guardian is optional. Import only if guardian is in INSTALLED_APPS
# Fixes (#1712). We keep the try/except for the test suite.
guardian = None
try:
if 'guardian' in settings.INSTALLED_APPS:
import guardian
import guardian.shortcuts # Fixes #1624
except ImportError:
pass
@ -108,8 +217,13 @@ try:
if markdown.version <= '2.2':
HEADERID_EXT_PATH = 'headerid'
else:
LEVEL_PARAM = 'level'
elif markdown.version < '2.6':
HEADERID_EXT_PATH = 'markdown.extensions.headerid'
LEVEL_PARAM = 'level'
else:
HEADERID_EXT_PATH = 'markdown.extensions.toc'
LEVEL_PARAM = 'baselevel'
def apply_markdown(text):
"""
@ -119,7 +233,7 @@ try:
extensions = [HEADERID_EXT_PATH]
extension_configs = {
HEADERID_EXT_PATH: {
'level': '2'
LEVEL_PARAM: '2'
}
}
md = markdown.Markdown(
@ -185,3 +299,18 @@ def template_render(template, context=None, request=None):
# backends template, e.g. django.template.backends.django.Template
else:
return template.render(context, request=request)
def set_many(instance, field, value):
if django.VERSION < (1, 10):
setattr(instance, field, value)
else:
field = getattr(instance, field)
field.set(value)
def include(module, namespace=None, app_name=None):
from django.conf.urls import include
if django.VERSION < (1,9):
return include(module, namespace, app_name)
else:
return include((module, app_name), namespace)

View File

@ -15,7 +15,7 @@ from django.utils import six
from rest_framework.views import APIView
def api_view(http_method_names=None):
def api_view(http_method_names=None, exclude_from_schema=False):
"""
Decorator that converts a function-based view into an APIView subclass.
Takes a list of allowed methods for the view as an argument.
@ -55,6 +55,7 @@ def api_view(http_method_names=None):
setattr(WrappedAPIView, method.lower(), handler)
WrappedAPIView.__name__ = func.__name__
WrappedAPIView.__module__ = func.__module__
WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes',
APIView.renderer_classes)
@ -71,6 +72,7 @@ def api_view(http_method_names=None):
WrappedAPIView.permission_classes = getattr(func, 'permission_classes',
APIView.permission_classes)
WrappedAPIView.exclude_from_schema = exclude_from_schema
return WrappedAPIView.as_view()
return decorator

View File

@ -17,27 +17,61 @@ from rest_framework import status
from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
def _force_text_recursive(data):
def _get_error_details(data, default_code=None):
"""
Descend into a nested data structure, forcing any
lazy translation strings into plain text.
lazy translation strings or strings into `ErrorDetail`.
"""
if isinstance(data, list):
ret = [
_force_text_recursive(item) for item in data
_get_error_details(item, default_code) for item in data
]
if isinstance(data, ReturnList):
return ReturnList(ret, serializer=data.serializer)
return ret
elif isinstance(data, dict):
ret = {
key: _force_text_recursive(value)
key: _get_error_details(value, default_code)
for key, value in data.items()
}
if isinstance(data, ReturnDict):
return ReturnDict(ret, serializer=data.serializer)
return ret
return force_text(data)
text = force_text(data)
code = getattr(data, 'code', default_code)
return ErrorDetail(text, code)
def _get_codes(detail):
if isinstance(detail, list):
return [_get_codes(item) for item in detail]
elif isinstance(detail, dict):
return {key: _get_codes(value) for key, value in detail.items()}
return detail.code
def _get_full_details(detail):
if isinstance(detail, list):
return [_get_full_details(item) for item in detail]
elif isinstance(detail, dict):
return {key: _get_full_details(value) for key, value in detail.items()}
return {
'message': detail,
'code': detail.code
}
class ErrorDetail(six.text_type):
"""
A string-like object that can additionally
"""
code = None
def __new__(cls, string, code=None):
self = super(ErrorDetail, cls).__new__(cls, string)
self.code = code
return self
class APIException(Exception):
@ -47,16 +81,35 @@ class APIException(Exception):
"""
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
default_detail = _('A server error occurred.')
default_code = 'error'
def __init__(self, detail=None):
if detail is not None:
self.detail = force_text(detail)
else:
self.detail = force_text(self.default_detail)
def __init__(self, detail=None, code=None):
if detail is None:
detail = self.default_detail
if code is None:
code = self.default_code
self.detail = _get_error_details(detail, code)
def __str__(self):
return self.detail
def get_codes(self):
"""
Return only the code part of the error details.
Eg. {"name": ["required"]}
"""
return _get_codes(self.detail)
def get_full_details(self):
"""
Return both the message & code parts of the error details.
Eg. {"name": [{"message": "This field is required.", "code": "required"}]}
"""
return _get_full_details(self.detail)
# The recommended style for using `ValidationError` is to keep it namespaced
# under `serializers`, in order to minimize potential confusion with Django's
@ -67,13 +120,21 @@ class APIException(Exception):
class ValidationError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _('Invalid input.')
default_code = 'invalid'
def __init__(self, detail):
# For validation errors the 'detail' key is always required.
# The details should always be coerced to a list if not already.
def __init__(self, detail, code=None):
if detail is None:
detail = self.default_detail
if code is None:
code = self.default_code
# For validation failures, we may collect may errors together, so the
# details should always be coerced to a list if not already.
if not isinstance(detail, dict) and not isinstance(detail, list):
detail = [detail]
self.detail = _force_text_recursive(detail)
self.detail = _get_error_details(detail, code)
def __str__(self):
return six.text_type(self.detail)
@ -82,62 +143,63 @@ class ValidationError(APIException):
class ParseError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _('Malformed request.')
default_code = 'parse_error'
class AuthenticationFailed(APIException):
status_code = status.HTTP_401_UNAUTHORIZED
default_detail = _('Incorrect authentication credentials.')
default_code = 'authentication_failed'
class NotAuthenticated(APIException):
status_code = status.HTTP_401_UNAUTHORIZED
default_detail = _('Authentication credentials were not provided.')
default_code = 'not_authenticated'
class PermissionDenied(APIException):
status_code = status.HTTP_403_FORBIDDEN
default_detail = _('You do not have permission to perform this action.')
default_code = 'permission_denied'
class NotFound(APIException):
status_code = status.HTTP_404_NOT_FOUND
default_detail = _('Not found.')
default_code = 'not_found'
class MethodNotAllowed(APIException):
status_code = status.HTTP_405_METHOD_NOT_ALLOWED
default_detail = _('Method "{method}" not allowed.')
default_code = 'method_not_allowed'
def __init__(self, method, detail=None):
if detail is not None:
self.detail = force_text(detail)
else:
self.detail = force_text(self.default_detail).format(method=method)
def __init__(self, method, detail=None, code=None):
if detail is None:
detail = force_text(self.default_detail).format(method=method)
super(MethodNotAllowed, self).__init__(detail, code)
class NotAcceptable(APIException):
status_code = status.HTTP_406_NOT_ACCEPTABLE
default_detail = _('Could not satisfy the request Accept header.')
default_code = 'not_acceptable'
def __init__(self, detail=None, available_renderers=None):
if detail is not None:
self.detail = force_text(detail)
else:
self.detail = force_text(self.default_detail)
def __init__(self, detail=None, code=None, available_renderers=None):
self.available_renderers = available_renderers
super(NotAcceptable, self).__init__(detail, code)
class UnsupportedMediaType(APIException):
status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
default_detail = _('Unsupported media type "{media_type}" in request.')
default_code = 'unsupported_media_type'
def __init__(self, media_type, detail=None):
if detail is not None:
self.detail = force_text(detail)
else:
self.detail = force_text(self.default_detail).format(
media_type=media_type
)
def __init__(self, media_type, detail=None, code=None):
if detail is None:
detail = force_text(self.default_detail).format(media_type=media_type)
super(UnsupportedMediaType, self).__init__(detail, code)
class Throttled(APIException):
@ -145,19 +207,17 @@ class Throttled(APIException):
default_detail = _('Request was throttled.')
extra_detail_singular = 'Expected available in {wait} second.'
extra_detail_plural = 'Expected available in {wait} seconds.'
default_code = 'throttled'
def __init__(self, wait=None, detail=None):
if detail is not None:
self.detail = force_text(detail)
else:
self.detail = force_text(self.default_detail)
if wait is None:
self.wait = None
else:
self.wait = math.ceil(wait)
self.detail += ' ' + force_text(ungettext(
self.extra_detail_singular.format(wait=self.wait),
self.extra_detail_plural.format(wait=self.wait),
self.wait
))
def __init__(self, wait=None, detail=None, code=None):
if detail is None:
detail = force_text(self.default_detail)
if wait is not None:
wait = math.ceil(wait)
detail = ' '.join((
detail,
force_text(ungettext(self.extra_detail_singular.format(wait=wait),
self.extra_detail_plural.format(wait=wait),
wait))))
self.wait = wait
super(Throttled, self).__init__(detail, code)

View File

@ -25,13 +25,17 @@ from django.utils.dateparse import (
)
from django.utils.duration import duration_string
from django.utils.encoding import is_protected_type, smart_text
from django.utils.formats import localize_input, sanitize_separators
from django.utils.functional import cached_property
from django.utils.ipv6 import clean_ipv6_address
from django.utils.timezone import utc
from django.utils.translation import ugettext_lazy as _
from rest_framework import ISO_8601
from rest_framework.compat import unicode_repr, unicode_to_repr
from rest_framework.exceptions import ValidationError
from rest_framework.compat import (
get_remote_field, unicode_repr, unicode_to_repr, value_from_object
)
from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.settings import api_settings
from rest_framework.utils import html, humanize_datetime, representation
@ -46,20 +50,39 @@ class empty:
pass
def is_simple_callable(obj):
"""
True if the object is a callable that takes no arguments.
"""
function = inspect.isfunction(obj)
method = inspect.ismethod(obj)
if six.PY3:
def is_simple_callable(obj):
"""
True if the object is a callable that takes no arguments.
"""
if not (inspect.isfunction(obj) or inspect.ismethod(obj)):
return False
if not (function or method):
return False
sig = inspect.signature(obj)
params = sig.parameters.values()
return all(
param.kind == param.VAR_POSITIONAL or
param.kind == param.VAR_KEYWORD or
param.default != param.empty
for param in params
)
args, _, _, defaults = inspect.getargspec(obj)
len_args = len(args) if function else len(args) - 1
len_defaults = len(defaults) if defaults else 0
return len_args <= len_defaults
else:
def is_simple_callable(obj):
function = inspect.isfunction(obj)
method = inspect.ismethod(obj)
if not (function or method):
return False
if method:
is_unbound = obj.im_self is None
args, _, _, defaults = inspect.getargspec(obj)
len_args = len(args) if function or is_unbound else len(args) - 1
len_defaults = len(defaults) if defaults else 0
return len_args <= len_defaults
def get_attribute(instance, attrs):
@ -127,7 +150,7 @@ def to_choices_dict(choices):
# choices = [('Category', ((1, 'First'), (2, 'Second'))), (3, 'Third')]
ret = OrderedDict()
for choice in choices:
if (not isinstance(choice, (list, tuple))):
if not isinstance(choice, (list, tuple)):
# single choice
ret[choice] = choice
else:
@ -207,6 +230,18 @@ def iter_options(grouped_choices, cutoff=None, cutoff_text=None):
yield Option(value='n/a', display_text=cutoff_text, disabled=True)
def get_error_detail(exc_info):
"""
Given a Django ValidationError, return a list of ErrorDetail,
with the `code` populated.
"""
code = getattr(exc_info, 'code', None) or 'invalid'
return [
ErrorDetail(msg, code=code)
for msg in exc_info.messages
]
class CreateOnlyDefault(object):
"""
This class may be used to provide default values that are only used
@ -249,6 +284,8 @@ class SkipField(Exception):
pass
REGEX_TYPE = type(re.compile(''))
NOT_READ_ONLY_WRITE_ONLY = 'May not set both `read_only` and `write_only`'
NOT_READ_ONLY_REQUIRED = 'May not set both `read_only` and `required`'
NOT_REQUIRED_DEFAULT = 'May not set both `required` and `default`'
@ -392,8 +429,8 @@ class Field(object):
# determine if we should use null instead.
return '' if getattr(self, 'allow_blank', False) else None
elif ret == '' and not self.required:
# If the field is blank, and emptyness is valid then
# determine if we should use emptyness instead.
# If the field is blank, and emptiness is valid then
# determine if we should use emptiness instead.
return '' if getattr(self, 'allow_blank', False) else empty
return ret
return dictionary.get(self.field_name, empty)
@ -429,10 +466,11 @@ class Field(object):
is provided for this field.
If a default has not been set for this field then this will simply
return `empty`, indicating that no value should be set in the
raise `SkipField`, indicating that no value should be set in the
validated data for this field.
"""
if self.default is empty:
if self.default is empty or getattr(self.root, 'partial', False):
# No default, or this is a partial update.
raise SkipField()
if callable(self.default):
if hasattr(self.default, 'set_context'):
@ -505,7 +543,7 @@ class Field(object):
raise
errors.extend(exc.detail)
except DjangoValidationError as exc:
errors.extend(exc.messages)
errors.extend(get_error_detail(exc))
if errors:
raise ValidationError(errors)
@ -543,7 +581,7 @@ class Field(object):
msg = MISSING_ERROR_MESSAGE.format(class_name=class_name, key=key)
raise AssertionError(msg)
message_string = msg.format(**kwargs)
raise ValidationError(message_string)
raise ValidationError(message_string, code=key)
@cached_property
def root(self):
@ -577,16 +615,17 @@ class Field(object):
When cloning fields we instantiate using the arguments it was
originally created with, rather than copying the complete state.
"""
args = copy.deepcopy(self._args)
kwargs = dict(self._kwargs)
# Bit ugly, but we need to special case 'validators' as Django's
# RegexValidator does not support deepcopy.
# We treat validator callables as immutable objects.
# Treat regexes and validators as immutable.
# See https://github.com/tomchristie/django-rest-framework/issues/1954
validators = kwargs.pop('validators', None)
kwargs = copy.deepcopy(kwargs)
if validators is not None:
kwargs['validators'] = validators
# and https://github.com/tomchristie/django-rest-framework/pull/4489
args = [
copy.deepcopy(item) if not isinstance(item, REGEX_TYPE) else item
for item in self._args
]
kwargs = {
key: (copy.deepcopy(value) if (key not in ('validators', 'regex')) else value)
for key, value in self._kwargs.items()
}
return self.__class__(*args, **kwargs)
def __repr__(self):
@ -606,8 +645,20 @@ class BooleanField(Field):
}
default_empty_html = False
initial = False
TRUE_VALUES = {'t', 'T', 'true', 'True', 'TRUE', '1', 1, True}
FALSE_VALUES = {'f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False}
TRUE_VALUES = {
't', 'T',
'true', 'True', 'TRUE',
'on', 'On', 'ON',
'1', 1,
True
}
FALSE_VALUES = {
'f', 'F',
'false', 'False', 'FALSE',
'off', 'Off', 'OFF',
'0', 0, 0.0,
False
}
def __init__(self, **kwargs):
assert 'allow_null' not in kwargs, '`allow_null` is not a valid option. Use `NullBooleanField` instead.'
@ -668,6 +719,7 @@ class NullBooleanField(Field):
class CharField(Field):
default_error_messages = {
'invalid': _('Not a valid string.'),
'blank': _('This field may not be blank.'),
'max_length': _('Ensure this field has no more than {max_length} characters.'),
'min_length': _('Ensure this field has at least {min_length} characters.')
@ -698,6 +750,11 @@ class CharField(Field):
return super(CharField, self).run_validation(data)
def to_internal_value(self, data):
# We're lenient with allowing basic numerics to be coerced into strings,
# but other types should fail. Eg. unclear if booleans should represent as `true` or `True`,
# and composites such as lists are likely user error.
if isinstance(data, bool) or not isinstance(data, six.string_types + six.integer_types + (float,)):
self.fail('invalid')
value = six.text_type(data)
return value.strip() if self.trim_whitespace else value
@ -801,7 +858,10 @@ class IPAddressField(CharField):
self.validators.extend(validators)
def to_internal_value(self, data):
if data and ':' in data:
if not isinstance(data, six.string_types):
self.fail('invalid', value=data)
if ':' in data:
try:
if self.protocol in ('both', 'ipv6'):
return clean_ipv6_address(data, self.unpack_ipv4)
@ -869,6 +929,7 @@ class FloatField(Field):
self.validators.append(MinValueValidator(self.min_value, message=message))
def to_internal_value(self, data):
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
self.fail('max_string_length')
@ -893,11 +954,15 @@ class DecimalField(Field):
}
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None, **kwargs):
def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None,
localize=False, **kwargs):
self.max_digits = max_digits
self.decimal_places = decimal_places
self.localize = localize
if coerce_to_string is not None:
self.coerce_to_string = coerce_to_string
if self.localize:
self.coerce_to_string = True
self.max_value = max_value
self.min_value = min_value
@ -921,7 +986,12 @@ class DecimalField(Field):
Validate that the input is a decimal number and return a Decimal
instance.
"""
data = smart_text(data).strip()
if self.localize:
data = sanitize_separators(data)
if len(data) > self.MAX_STRING_LENGTH:
self.fail('max_string_length')
@ -939,7 +1009,7 @@ class DecimalField(Field):
if value in (decimal.Decimal('Inf'), decimal.Decimal('-Inf')):
self.fail('invalid')
return self.validate_precision(value)
return self.quantize(self.validate_precision(value))
def validate_precision(self, value):
"""
@ -986,17 +1056,25 @@ class DecimalField(Field):
if not coerce_to_string:
return quantized
if self.localize:
return localize_input(quantized)
return '{0:f}'.format(quantized)
def quantize(self, value):
"""
Quantize the decimal value to the configured precision.
"""
if self.decimal_places is None:
return value
context = decimal.getcontext().copy()
context.prec = self.max_digits
if self.max_digits is not None:
context.prec = self.max_digits
return value.quantize(
decimal.Decimal('.1') ** self.decimal_places,
context=context)
context=context
)
# Date & time fields...
@ -1027,7 +1105,7 @@ class DateTimeField(Field):
if (field_timezone is not None) and not timezone.is_aware(value):
return timezone.make_aware(value, field_timezone)
elif (field_timezone is None) and timezone.is_aware(value):
return timezone.make_naive(value, timezone.UTC())
return timezone.make_naive(value, utc)
return value
def default_timezone(self):
@ -1068,7 +1146,7 @@ class DateTimeField(Field):
output_format = getattr(self, 'format', api_settings.DATETIME_FORMAT)
if output_format is None:
if output_format is None or isinstance(value, six.string_types):
return value
if output_format.lower() == ISO_8601:
@ -1128,7 +1206,7 @@ class DateField(Field):
output_format = getattr(self, 'format', api_settings.DATE_FORMAT)
if output_format is None:
if output_format is None or isinstance(value, six.string_types):
return value
# Applying a `DateField` to a datetime value is almost always
@ -1141,8 +1219,6 @@ class DateField(Field):
)
if output_format.lower() == ISO_8601:
if isinstance(value, six.string_types):
value = datetime.datetime.strptime(value, '%Y-%m-%d').date()
return value.isoformat()
return value.strftime(output_format)
@ -1188,12 +1264,12 @@ class TimeField(Field):
self.fail('invalid', format=humanized_format)
def to_representation(self, value):
if not value:
if value in (None, ''):
return None
output_format = getattr(self, 'format', api_settings.TIME_FORMAT)
if output_format is None:
if output_format is None or isinstance(value, six.string_types):
return value
# Applying a `TimeField` to a datetime value is almost always
@ -1206,8 +1282,6 @@ class TimeField(Field):
)
if output_format.lower() == ISO_8601:
if isinstance(value, six.string_types):
value = datetime.datetime.strptime(value, '%H:%M:%S').time()
return value.isoformat()
return value.strftime(output_format)
@ -1326,7 +1400,7 @@ class FilePathField(ChoiceField):
def __init__(self, path, match=None, recursive=False, allow_files=True,
allow_folders=False, required=None, **kwargs):
# Defer to Django's FilePathField implmentation to get the
# Defer to Django's FilePathField implementation to get the
# valid set of choices.
field = DjangoFilePathField(
path, match=match, recursive=recursive, allow_files=allow_files,
@ -1431,12 +1505,16 @@ class ListField(Field):
initial = []
default_error_messages = {
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
'empty': _('This list may not be empty.')
'empty': _('This list may not be empty.'),
'min_length': _('Ensure this field has at least {min_length} elements.'),
'max_length': _('Ensure this field has no more than {max_length} elements.')
}
def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
self.allow_empty = kwargs.pop('allow_empty', True)
self.max_length = kwargs.pop('max_length', None)
self.min_length = kwargs.pop('min_length', None)
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
assert self.child.source is None, (
@ -1446,6 +1524,12 @@ class ListField(Field):
super(ListField, self).__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
if self.max_length is not None:
message = self.error_messages['max_length'].format(max_length=self.max_length)
self.validators.append(MaxLengthValidator(self.max_length, message=message))
if self.min_length is not None:
message = self.error_messages['min_length'].format(min_length=self.min_length)
self.validators.append(MinLengthValidator(self.min_length, message=message))
def get_value(self, dictionary):
if self.field_name not in dictionary:
@ -1477,7 +1561,7 @@ class ListField(Field):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
return [self.child.to_representation(item) for item in data]
return [self.child.to_representation(item) if item is not None else None for item in data]
class DictField(Field):
@ -1524,7 +1608,7 @@ class DictField(Field):
List of object instances -> List of dicts of primitive datatypes.
"""
return {
six.text_type(key): self.child.to_representation(val)
six.text_type(key): self.child.to_representation(val) if val is not None else None
for key, val in value.items()
}
@ -1538,9 +1622,21 @@ class JSONField(Field):
self.binary = kwargs.pop('binary', False)
super(JSONField, self).__init__(*args, **kwargs)
def get_value(self, dictionary):
if html.is_html_input(dictionary) and self.field_name in dictionary:
# When HTML form input is used, mark up the input
# as being a JSON string, rather than a JSON primitive.
class JSONString(six.text_type):
def __new__(self, value):
ret = six.text_type.__new__(self, value)
ret.is_json_string = True
return ret
return JSONString(dictionary[self.field_name])
return dictionary.get(self.field_name, empty)
def to_internal_value(self, data):
try:
if self.binary:
if self.binary or getattr(data, 'is_json_string', False):
if isinstance(data, six.binary_type):
data = data.decode('utf-8')
return json.loads(data)
@ -1567,7 +1663,7 @@ class ReadOnlyField(Field):
A read-only field that simply returns the field value.
If the field is a method with no parameters, the method will be called
and it's return value used as the representation.
and its return value used as the representation.
For example, the following would call `get_expiry_date()` on the object:
@ -1629,7 +1725,7 @@ class SerializerMethodField(Field):
def bind(self, field_name, parent):
# In order to enforce a consistent style, we error if a redundant
# 'method_name' argument has been used. For example:
# my_field = serializer.CharField(source='my_field')
# my_field = serializer.SerializerMethodField(method_name='get_my_field')
default_method_name = 'get_{field_name}'.format(field_name=field_name)
assert self.method_name != default_method_name, (
"It is redundant to specify `%s` on SerializerMethodField '%s' in "
@ -1671,7 +1767,7 @@ class ModelField(Field):
self.validators.append(MaxLengthValidator(max_length, message=message))
def to_internal_value(self, data):
rel = getattr(self.model_field, 'rel', None)
rel = get_remote_field(self.model_field, default=None)
if rel is not None:
return rel.to._meta.get_field(rel.field_name).to_python(data)
return self.model_field.to_python(data)
@ -1682,7 +1778,7 @@ class ModelField(Field):
return obj
def to_representation(self, obj):
value = self.model_field._get_val_from_obj(obj)
value = value_from_object(self.model_field, obj)
if is_protected_type(value):
return value
return self.model_field.value_to_string(obj)

View File

@ -5,60 +5,21 @@ returned by list views.
from __future__ import unicode_literals
import operator
import warnings
from functools import reduce
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.db.models.constants import LOOKUP_SEP
from django.template import loader
from django.utils import six
from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import (
crispy_forms, distinct, django_filters, guardian, template_render
coreapi, distinct, django_filters, guardian, template_render
)
from rest_framework.settings import api_settings
if 'crispy_forms' in settings.INSTALLED_APPS and crispy_forms and django_filters:
# If django-crispy-forms is installed, use it to get a bootstrap3 rendering
# of the DjangoFilterBackend controls when displayed as HTML.
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit
class FilterSet(django_filters.FilterSet):
def __init__(self, *args, **kwargs):
super(FilterSet, self).__init__(*args, **kwargs)
for field in self.form.fields.values():
field.help_text = None
layout_components = list(self.form.fields.keys()) + [
Submit('', _('Submit'), css_class='btn-default'),
]
helper = FormHelper()
helper.form_method = 'GET'
helper.template_pack = 'bootstrap3'
helper.layout = Layout(*layout_components)
self.form.helper = helper
filter_template = 'rest_framework/filters/django_filter_crispyforms.html'
elif django_filters:
# If django-crispy-forms is not installed, use the standard
# 'form.as_p' rendering when DjangoFilterBackend is displayed as HTML.
class FilterSet(django_filters.FilterSet):
def __init__(self, *args, **kwargs):
super(FilterSet, self).__init__(*args, **kwargs)
for field in self.form.fields.values():
field.help_text = None
filter_template = 'rest_framework/filters/django_filter.html'
else:
FilterSet = None
filter_template = None
class BaseFilterBackend(object):
"""
@ -71,67 +32,56 @@ class BaseFilterBackend(object):
"""
raise NotImplementedError(".filter_queryset() must be overridden.")
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
return []
if django_filters:
from django_filters.rest_framework.filterset import FilterSet as DFFilterSet
class FilterSet(DFFilterSet):
def __init__(self, *args, **kwargs):
warnings.warn(
"The built in 'rest_framework.filters.FilterSet' is pending deprecation. "
"You should use 'django_filters.rest_framework.FilterSet' instead.",
PendingDeprecationWarning
)
return super(FilterSet, self).__init__(*args, **kwargs)
else:
def FilterSet():
assert False, 'django-filter must be installed to use the `FilterSet` class'
class DjangoFilterBackend(BaseFilterBackend):
"""
A filter backend that uses django-filter.
"""
default_filter_set = FilterSet
template = filter_template
def __init__(self):
def __new__(cls, *args, **kwargs):
assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed'
assert django_filters.VERSION >= (0, 15, 3), 'django-filter 0.15.3 and above is required'
def get_filter_class(self, view, queryset=None):
"""
Return the django-filters `FilterSet` used to filter the queryset.
"""
filter_class = getattr(view, 'filter_class', None)
filter_fields = getattr(view, 'filter_fields', None)
warnings.warn(
"The built in 'rest_framework.filters.DjangoFilterBackend' is pending deprecation. "
"You should use 'django_filters.rest_framework.DjangoFilterBackend' instead.",
PendingDeprecationWarning
)
if filter_class:
filter_model = filter_class.Meta.model
from django_filters.rest_framework import DjangoFilterBackend
assert issubclass(queryset.model, filter_model), \
'FilterSet model %s does not match queryset model %s' % \
(filter_model, queryset.model)
return filter_class
if filter_fields:
class AutoFilterSet(self.default_filter_set):
class Meta:
model = queryset.model
fields = filter_fields
return AutoFilterSet
return None
def filter_queryset(self, request, queryset, view):
filter_class = self.get_filter_class(view, queryset)
if filter_class:
return filter_class(request.query_params, queryset=queryset).qs
return queryset
def to_html(self, request, queryset, view):
filter_class = self.get_filter_class(view, queryset)
if not filter_class:
return None
filter_instance = filter_class(request.query_params, queryset=queryset)
context = {
'filter': filter_instance
}
template = loader.get_template(self.template)
return template_render(template, context)
return DjangoFilterBackend(*args, **kwargs)
class SearchFilter(BaseFilterBackend):
# The URL query parameter used for the search.
search_param = api_settings.SEARCH_PARAM
template = 'rest_framework/filters/search.html'
lookup_prefixes = {
'^': 'istartswith',
'=': 'iexact',
'@': 'search',
'$': 'iregex',
}
def get_search_terms(self, request):
"""
@ -142,16 +92,32 @@ class SearchFilter(BaseFilterBackend):
return params.replace(',', ' ').split()
def construct_search(self, field_name):
if field_name.startswith('^'):
return "%s__istartswith" % field_name[1:]
elif field_name.startswith('='):
return "%s__iexact" % field_name[1:]
elif field_name.startswith('@'):
return "%s__search" % field_name[1:]
if field_name.startswith('$'):
return "%s__iregex" % field_name[1:]
lookup = self.lookup_prefixes.get(field_name[0])
if lookup:
field_name = field_name[1:]
else:
return "%s__icontains" % field_name
lookup = 'icontains'
return LOOKUP_SEP.join([field_name, lookup])
def must_call_distinct(self, queryset, search_fields):
"""
Return True if 'distinct()' should be used to query the given lookups.
"""
for search_field in search_fields:
opts = queryset.model._meta
if search_field[0] in self.lookup_prefixes:
search_field = search_field[1:]
parts = search_field.split(LOOKUP_SEP)
for part in parts:
field = opts.get_field(part)
if hasattr(field, 'get_path_info'):
# This field is a relation, update opts to follow the relation
path_info = field.get_path_info()
opts = path_info[-1].to_opts
if any(path.m2m for path in path_info):
# This field is a m2m relation so we know we need to call distinct
return True
return False
def filter_queryset(self, request, queryset, view):
search_fields = getattr(view, 'search_fields', None)
@ -173,10 +139,13 @@ class SearchFilter(BaseFilterBackend):
]
queryset = queryset.filter(reduce(operator.or_, queries))
# Filtering against a many-to-many field requires us to
# call queryset.distinct() in order to avoid duplicate items
# in the resulting queryset.
return distinct(queryset, base)
if self.must_call_distinct(queryset, search_fields):
# Filtering against a many-to-many field requires us to
# call queryset.distinct() in order to avoid duplicate items
# in the resulting queryset.
# We try to avoid this if possible, for performance reasons.
queryset = distinct(queryset, base)
return queryset
def to_html(self, request, queryset, view):
if not getattr(view, 'search_fields', None):
@ -191,6 +160,10 @@ class SearchFilter(BaseFilterBackend):
template = loader.get_template(self.template)
return template_render(template, context)
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
return [coreapi.Field(name=self.search_param, required=False, location='query')]
class OrderingFilter(BaseFilterBackend):
# The URL query parameter used for the ordering.
@ -209,7 +182,7 @@ class OrderingFilter(BaseFilterBackend):
params = request.query_params.get(self.ordering_param)
if params:
fields = [param.strip() for param in params.split(',')]
ordering = self.remove_invalid_fields(queryset, fields, view)
ordering = self.remove_invalid_fields(queryset, fields, view, request)
if ordering:
return ordering
@ -222,26 +195,44 @@ class OrderingFilter(BaseFilterBackend):
return (ordering,)
return ordering
def get_valid_fields(self, queryset, view):
def get_default_valid_fields(self, queryset, view, context={}):
# If `ordering_fields` is not specified, then we determine a default
# based on the serializer class, if one exists on the view.
if hasattr(view, 'get_serializer_class'):
try:
serializer_class = view.get_serializer_class()
except AssertionError:
# Raised by the default implementation if
# no serializer_class was found
serializer_class = None
else:
serializer_class = getattr(view, 'serializer_class', None)
if serializer_class is None:
msg = (
"Cannot use %s on a view which does not have either a "
"'serializer_class', an overriding 'get_serializer_class' "
"or 'ordering_fields' attribute."
)
raise ImproperlyConfigured(msg % self.__class__.__name__)
return [
(field.source or field_name, field.label)
for field_name, field in serializer_class(context=context).fields.items()
if not getattr(field, 'write_only', False) and not field.source == '*'
]
def get_valid_fields(self, queryset, view, context={}):
valid_fields = getattr(view, 'ordering_fields', self.ordering_fields)
if valid_fields is None:
# Default to allowing filtering on serializer fields
serializer_class = getattr(view, 'serializer_class')
if serializer_class is None:
msg = ("Cannot use %s on a view which does not have either a "
"'serializer_class' or 'ordering_fields' attribute.")
raise ImproperlyConfigured(msg % self.__class__.__name__)
valid_fields = [
(field.source or field_name, field.label)
for field_name, field in serializer_class().fields.items()
if not getattr(field, 'write_only', False) and not field.source == '*'
]
return self.get_default_valid_fields(queryset, view, context)
elif valid_fields == '__all__':
# View explicitly allows filtering on any model field
valid_fields = [
(field.name, getattr(field, 'label', field.name.title()))
for field in queryset.model._meta.fields
(field.name, field.verbose_name) for field in queryset.model._meta.fields
]
valid_fields += [
(key, key.title().split('__'))
@ -255,8 +246,8 @@ class OrderingFilter(BaseFilterBackend):
return valid_fields
def remove_invalid_fields(self, queryset, fields, view):
valid_fields = [item[0] for item in self.get_valid_fields(queryset, view)]
def remove_invalid_fields(self, queryset, fields, view, request):
valid_fields = [item[0] for item in self.get_valid_fields(queryset, view, {'request': request})]
return [term for term in fields if term.lstrip('-') in valid_fields]
def filter_queryset(self, request, queryset, view):
@ -271,21 +262,26 @@ class OrderingFilter(BaseFilterBackend):
current = self.get_ordering(request, queryset, view)
current = None if current is None else current[0]
options = []
for key, label in self.get_valid_fields(queryset, view):
options.append((key, '%s - ascending' % label))
options.append(('-' + key, '%s - descending' % label))
return {
context = {
'request': request,
'current': current,
'param': self.ordering_param,
'options': options,
}
for key, label in self.get_valid_fields(queryset, view, context):
options.append((key, '%s - %s' % (label, _('ascending'))))
options.append(('-' + key, '%s - %s' % (label, _('descending'))))
context['options'] = options
return context
def to_html(self, request, queryset, view):
template = loader.get_template(self.template)
context = self.get_template_context(request, queryset, view)
return template_render(template, context)
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
return [coreapi.Field(name=self.ordering_param, required=False, location='query')]
class DjangoObjectPermissionsFilter(BaseFilterBackend):
"""
@ -298,6 +294,11 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend):
perm_format = '%(app_label)s.view_%(model_name)s'
def filter_queryset(self, request, queryset, view):
# We want to defer this import until run-time, rather than import-time.
# See https://github.com/tomchristie/django-rest-framework/issues/4608
# (Also see #1624 for why we need to make this import explicitly)
from guardian.shortcuts import get_objects_for_user
extra = {}
user = request.user
model_cls = queryset.model
@ -306,9 +307,9 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend):
'model_name': model_cls._meta.model_name
}
permission = self.perm_format % kwargs
if guardian.VERSION >= (1, 3):
if tuple(guardian.VERSION) >= (1, 3):
# Maintain behavior compatibility with versions prior to 1.3
extra = {'accept_global_perms': False}
else:
extra = {}
return guardian.shortcuts.get_objects_for_user(user, permission, queryset, **extra)
return get_objects_for_user(user, permission, queryset, **extra)

View File

@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-12-07 18:53+0100\n"
"PO-Revision-Date: 2015-12-07 17:55+0000\n"
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"POT-Creation-Date: 2016-07-12 16:13+0100\n"
"PO-Revision-Date: 2016-07-12 15:14+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
"Language-Team: Acoli (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ach/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -17,43 +17,75 @@ msgstr ""
"Language: ach\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: authentication.py:72
#: authentication.py:73
msgid "Invalid basic header. No credentials provided."
msgstr ""
#: authentication.py:75
#: authentication.py:76
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr ""
#: authentication.py:81
#: authentication.py:82
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr ""
#: authentication.py:98
#: authentication.py:99
msgid "Invalid username/password."
msgstr ""
#: authentication.py:101 authentication.py:188
#: authentication.py:102 authentication.py:198
msgid "User inactive or deleted."
msgstr ""
#: authentication.py:167
#: authentication.py:176
msgid "Invalid token header. No credentials provided."
msgstr ""
#: authentication.py:170
#: authentication.py:179
msgid "Invalid token header. Token string should not contain spaces."
msgstr ""
#: authentication.py:176
#: authentication.py:185
msgid ""
"Invalid token header. Token string should not contain invalid characters."
msgstr ""
#: authentication.py:185
#: authentication.py:195
msgid "Invalid token."
msgstr ""
#: authtoken/apps.py:7
msgid "Auth Token"
msgstr ""
#: authtoken/models.py:15
msgid "Key"
msgstr ""
#: authtoken/models.py:18
msgid "User"
msgstr ""
#: authtoken/models.py:20
msgid "Created"
msgstr ""
#: authtoken/models.py:29
msgid "Token"
msgstr ""
#: authtoken/models.py:30
msgid "Tokens"
msgstr ""
#: authtoken/serializers.py:8
msgid "Username"
msgstr ""
#: authtoken/serializers.py:9
msgid "Password"
msgstr ""
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr ""
@ -91,7 +123,6 @@ msgid "Not found."
msgstr ""
#: exceptions.py:109
#, python-brace-format
msgid "Method \"{method}\" not allowed."
msgstr ""
@ -100,7 +131,6 @@ msgid "Could not satisfy the request Accept header."
msgstr ""
#: exceptions.py:132
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request."
msgstr ""
@ -108,250 +138,233 @@ msgstr ""
msgid "Request was throttled."
msgstr ""
#: fields.py:266 relations.py:195 relations.py:228 validators.py:79
#: validators.py:162
#: fields.py:269 relations.py:206 relations.py:239 validators.py:98
#: validators.py:181
msgid "This field is required."
msgstr ""
#: fields.py:267
#: fields.py:270
msgid "This field may not be null."
msgstr ""
#: fields.py:603 fields.py:634
#, python-brace-format
#: fields.py:608 fields.py:639
msgid "\"{input}\" is not a valid boolean."
msgstr ""
#: fields.py:669
#: fields.py:674
msgid "This field may not be blank."
msgstr ""
#: fields.py:670 fields.py:1656
#, python-brace-format
#: fields.py:675 fields.py:1675
msgid "Ensure this field has no more than {max_length} characters."
msgstr ""
#: fields.py:671
#, python-brace-format
#: fields.py:676
msgid "Ensure this field has at least {min_length} characters."
msgstr ""
#: fields.py:708
#: fields.py:713
msgid "Enter a valid email address."
msgstr ""
#: fields.py:719
#: fields.py:724
msgid "This value does not match the required pattern."
msgstr ""
#: fields.py:730
#: fields.py:735
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr ""
#: fields.py:742
#: fields.py:747
msgid "Enter a valid URL."
msgstr ""
#: fields.py:755
#, python-brace-format
#: fields.py:760
msgid "\"{value}\" is not a valid UUID."
msgstr ""
#: fields.py:791
#: fields.py:796
msgid "Enter a valid IPv4 or IPv6 address."
msgstr ""
#: fields.py:816
#: fields.py:821
msgid "A valid integer is required."
msgstr ""
#: fields.py:817 fields.py:852 fields.py:885
#, python-brace-format
#: fields.py:822 fields.py:857 fields.py:891
msgid "Ensure this value is less than or equal to {max_value}."
msgstr ""
#: fields.py:818 fields.py:853 fields.py:886
#, python-brace-format
#: fields.py:823 fields.py:858 fields.py:892
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr ""
#: fields.py:819 fields.py:854 fields.py:890
#: fields.py:824 fields.py:859 fields.py:896
msgid "String value too large."
msgstr ""
#: fields.py:851 fields.py:884
#: fields.py:856 fields.py:890
msgid "A valid number is required."
msgstr ""
#: fields.py:887
#, python-brace-format
#: fields.py:893
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr ""
#: fields.py:888
#, python-brace-format
#: fields.py:894
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr ""
#: fields.py:889
#, python-brace-format
#: fields.py:895
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
#: fields.py:1004
#, python-brace-format
#: fields.py:1025
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1005
#: fields.py:1026
msgid "Expected a datetime but got a date."
msgstr ""
#: fields.py:1079
#, python-brace-format
#: fields.py:1103
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1080
#: fields.py:1104
msgid "Expected a date but got a datetime."
msgstr ""
#: fields.py:1148
#, python-brace-format
#: fields.py:1170
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1207
#, python-brace-format
#: fields.py:1232
msgid "Duration has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1232 fields.py:1281
#, python-brace-format
#: fields.py:1251 fields.py:1300
msgid "\"{input}\" is not a valid choice."
msgstr ""
#: fields.py:1235 relations.py:62 relations.py:431
#, python-brace-format
#: fields.py:1254 relations.py:71 relations.py:441
msgid "More than {count} items..."
msgstr ""
#: fields.py:1282 fields.py:1429 relations.py:427 serializers.py:520
#, python-brace-format
#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1283
#: fields.py:1302
msgid "This selection may not be empty."
msgstr ""
#: fields.py:1320
#, python-brace-format
#: fields.py:1339
msgid "\"{input}\" is not a valid path choice."
msgstr ""
#: fields.py:1339
#: fields.py:1358
msgid "No file was submitted."
msgstr ""
#: fields.py:1340
#: fields.py:1359
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr ""
#: fields.py:1341
#: fields.py:1360
msgid "No filename could be determined."
msgstr ""
#: fields.py:1342
#: fields.py:1361
msgid "The submitted file is empty."
msgstr ""
#: fields.py:1343
#, python-brace-format
#: fields.py:1362
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
#: fields.py:1391
#: fields.py:1410
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: fields.py:1430 relations.py:428 serializers.py:521
#: fields.py:1449 relations.py:438 serializers.py:525
msgid "This list may not be empty."
msgstr ""
#: fields.py:1483
#, python-brace-format
#: fields.py:1502
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1530
#: fields.py:1549
msgid "Value must be valid JSON."
msgstr ""
#: filters.py:35 templates/rest_framework/filters/django_filter.html:5
#: filters.py:36 templates/rest_framework/filters/django_filter.html:5
msgid "Submit"
msgstr ""
#: pagination.py:189
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}."
#: filters.py:336
msgid "ascending"
msgstr ""
#: pagination.py:407
#: filters.py:337
msgid "descending"
msgstr ""
#: pagination.py:193
msgid "Invalid page."
msgstr ""
#: pagination.py:427
msgid "Invalid cursor"
msgstr ""
#: relations.py:196
#, python-brace-format
#: relations.py:207
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr ""
#: relations.py:197
#, python-brace-format
#: relations.py:208
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
#: relations.py:229
#: relations.py:240
msgid "Invalid hyperlink - No URL match."
msgstr ""
#: relations.py:230
#: relations.py:241
msgid "Invalid hyperlink - Incorrect URL match."
msgstr ""
#: relations.py:231
#: relations.py:242
msgid "Invalid hyperlink - Object does not exist."
msgstr ""
#: relations.py:232
#, python-brace-format
#: relations.py:243
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
#: relations.py:391
#, python-brace-format
#: relations.py:401
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
#: relations.py:392
#: relations.py:402
msgid "Invalid value."
msgstr ""
#: serializers.py:326
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr ""
#: templates/rest_framework/admin.html:118
#: templates/rest_framework/admin.html:116
#: templates/rest_framework/base.html:128
msgid "Filters"
msgstr ""
@ -381,27 +394,23 @@ msgstr ""
msgid "No items to select."
msgstr ""
#: validators.py:24
#: validators.py:43
msgid "This field must be unique."
msgstr ""
#: validators.py:78
#, python-brace-format
#: validators.py:97
msgid "The fields {field_names} must make a unique set."
msgstr ""
#: validators.py:226
#, python-brace-format
#: validators.py:245
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
#: validators.py:241
#, python-brace-format
#: validators.py:260
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
#: validators.py:254
#, python-brace-format
#: validators.py:273
msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
@ -409,15 +418,19 @@ msgstr ""
msgid "Invalid version in \"Accept\" header."
msgstr ""
#: versioning.py:73 versioning.py:115
#: versioning.py:73
msgid "Invalid version in URL path."
msgstr ""
#: versioning.py:144
#: versioning.py:115
msgid "Invalid version in URL path. Does not match any version namespace."
msgstr ""
#: versioning.py:147
msgid "Invalid version in hostname."
msgstr ""
#: versioning.py:166
#: versioning.py:169
msgid "Invalid version in query parameter."
msgstr ""

View File

@ -3,14 +3,15 @@
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Bashar Al-Abdulhadi, 2016
# Eyad Toma <d.eyad.t@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-12-07 18:53+0100\n"
"PO-Revision-Date: 2015-12-07 17:55+0000\n"
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"POT-Creation-Date: 2016-07-12 16:13+0100\n"
"PO-Revision-Date: 2016-07-12 15:14+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
"Language-Team: Arabic (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ar/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -18,42 +19,74 @@ msgstr ""
"Language: ar\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
#: authentication.py:72
#: authentication.py:73
msgid "Invalid basic header. No credentials provided."
msgstr ""
#: authentication.py:75
#: authentication.py:76
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr ""
#: authentication.py:81
#: authentication.py:82
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr ""
#: authentication.py:98
#: authentication.py:99
msgid "Invalid username/password."
msgstr "اسم المستخدم/كلمة السر غير صحيحين."
#: authentication.py:101 authentication.py:188
#: authentication.py:102 authentication.py:198
msgid "User inactive or deleted."
msgstr "المستخدم غير مفعل او تم حذفه."
#: authentication.py:167
#: authentication.py:176
msgid "Invalid token header. No credentials provided."
msgstr ""
#: authentication.py:170
#: authentication.py:179
msgid "Invalid token header. Token string should not contain spaces."
msgstr ""
#: authentication.py:176
#: authentication.py:185
msgid ""
"Invalid token header. Token string should not contain invalid characters."
msgstr ""
#: authentication.py:185
#: authentication.py:195
msgid "Invalid token."
msgstr ""
msgstr "رمز غير صحيح"
#: authtoken/apps.py:7
msgid "Auth Token"
msgstr "رمز التفويض"
#: authtoken/models.py:15
msgid "Key"
msgstr "المفتاح"
#: authtoken/models.py:18
msgid "User"
msgstr "المستخدم"
#: authtoken/models.py:20
msgid "Created"
msgstr "أنشئ"
#: authtoken/models.py:29
msgid "Token"
msgstr "الرمز"
#: authtoken/models.py:30
msgid "Tokens"
msgstr "الرموز"
#: authtoken/serializers.py:8
msgid "Username"
msgstr "اسم المستخدم"
#: authtoken/serializers.py:9
msgid "Password"
msgstr "كلمة المرور"
#: authtoken/serializers.py:20
msgid "User account is disabled."
@ -92,7 +125,6 @@ msgid "Not found."
msgstr "غير موجود."
#: exceptions.py:109
#, python-brace-format
msgid "Method \"{method}\" not allowed."
msgstr ""
@ -101,7 +133,6 @@ msgid "Could not satisfy the request Accept header."
msgstr ""
#: exceptions.py:132
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request."
msgstr ""
@ -109,266 +140,249 @@ msgstr ""
msgid "Request was throttled."
msgstr ""
#: fields.py:266 relations.py:195 relations.py:228 validators.py:79
#: validators.py:162
#: fields.py:269 relations.py:206 relations.py:239 validators.py:98
#: validators.py:181
msgid "This field is required."
msgstr "هذا الحقل مطلوب."
#: fields.py:267
#: fields.py:270
msgid "This field may not be null."
msgstr "لا يمكن لهذا الحقل ان يكون فارغاً null."
#: fields.py:603 fields.py:634
#, python-brace-format
#: fields.py:608 fields.py:639
msgid "\"{input}\" is not a valid boolean."
msgstr "\"{input}\" ليس قيمة منطقية."
#: fields.py:669
#: fields.py:674
msgid "This field may not be blank."
msgstr "لا يمكن لهذا الحقل ان يكون فارغاً."
#: fields.py:670 fields.py:1656
#, python-brace-format
#: fields.py:675 fields.py:1675
msgid "Ensure this field has no more than {max_length} characters."
msgstr "تأكد ان الحقل لا يزيد عن {max_length} محرف."
#: fields.py:671
#, python-brace-format
#: fields.py:676
msgid "Ensure this field has at least {min_length} characters."
msgstr "تأكد ان الحقل {min_length} محرف على الاقل."
#: fields.py:708
#: fields.py:713
msgid "Enter a valid email address."
msgstr "عليك ان تدخل بريد إلكتروني صالح."
#: fields.py:719
#: fields.py:724
msgid "This value does not match the required pattern."
msgstr "هذه القيمة لا تطابق النمط المطلوب."
#: fields.py:730
#: fields.py:735
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr ""
#: fields.py:742
#: fields.py:747
msgid "Enter a valid URL."
msgstr "الرجاء إدخال رابط إلكتروني صالح."
#: fields.py:755
#, python-brace-format
#: fields.py:760
msgid "\"{value}\" is not a valid UUID."
msgstr ""
#: fields.py:791
#: fields.py:796
msgid "Enter a valid IPv4 or IPv6 address."
msgstr ""
#: fields.py:816
#: fields.py:821
msgid "A valid integer is required."
msgstr "الرجاء إدخال رقم صحيح صالح."
#: fields.py:817 fields.py:852 fields.py:885
#, python-brace-format
#: fields.py:822 fields.py:857 fields.py:891
msgid "Ensure this value is less than or equal to {max_value}."
msgstr "تأكد ان القيمة أقل أو تساوي {max_value}."
#: fields.py:818 fields.py:853 fields.py:886
#, python-brace-format
#: fields.py:823 fields.py:858 fields.py:892
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "تأكد ان القيمة أكبر أو تساوي {min_value}."
#: fields.py:819 fields.py:854 fields.py:890
#: fields.py:824 fields.py:859 fields.py:896
msgid "String value too large."
msgstr ""
#: fields.py:851 fields.py:884
#: fields.py:856 fields.py:890
msgid "A valid number is required."
msgstr "الرجاء إدخال رقم صالح."
#: fields.py:887
#, python-brace-format
#: fields.py:893
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "تأكد ان القيمة لا تحوي أكثر من {max_digits} رقم."
#: fields.py:888
#, python-brace-format
#: fields.py:894
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr ""
#: fields.py:889
#, python-brace-format
#: fields.py:895
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
#: fields.py:1004
#, python-brace-format
#: fields.py:1025
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr "صيغة التاريخ و الوقت غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
#: fields.py:1005
#: fields.py:1026
msgid "Expected a datetime but got a date."
msgstr ""
#: fields.py:1079
#, python-brace-format
#: fields.py:1103
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr "صيغة التاريخ غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
#: fields.py:1080
#: fields.py:1104
msgid "Expected a date but got a datetime."
msgstr ""
#: fields.py:1148
#, python-brace-format
#: fields.py:1170
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "صيغة الوقت غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
#: fields.py:1207
#, python-brace-format
#: fields.py:1232
msgid "Duration has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1232 fields.py:1281
#, python-brace-format
#: fields.py:1251 fields.py:1300
msgid "\"{input}\" is not a valid choice."
msgstr "\"{input}\" ليست واحدة من الخيارات الصالحة."
#: fields.py:1235 relations.py:62 relations.py:431
#, python-brace-format
#: fields.py:1254 relations.py:71 relations.py:441
msgid "More than {count} items..."
msgstr ""
#: fields.py:1282 fields.py:1429 relations.py:427 serializers.py:520
#, python-brace-format
#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1283
#: fields.py:1302
msgid "This selection may not be empty."
msgstr ""
#: fields.py:1320
#, python-brace-format
#: fields.py:1339
msgid "\"{input}\" is not a valid path choice."
msgstr ""
#: fields.py:1339
#: fields.py:1358
msgid "No file was submitted."
msgstr "لم يتم إرسال أي ملف."
#: fields.py:1340
#: fields.py:1359
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr ""
#: fields.py:1341
#: fields.py:1360
msgid "No filename could be determined."
msgstr ""
#: fields.py:1342
#: fields.py:1361
msgid "The submitted file is empty."
msgstr "الملف الذي تم إرساله فارغ."
#: fields.py:1343
#, python-brace-format
#: fields.py:1362
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr "تأكد ان اسم الملف لا يحوي أكثر من {max_length} محرف (الإسم المرسل يحوي {length} محرف)."
#: fields.py:1391
#: fields.py:1410
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: fields.py:1430 relations.py:428 serializers.py:521
#: fields.py:1449 relations.py:438 serializers.py:525
msgid "This list may not be empty."
msgstr ""
#: fields.py:1483
#, python-brace-format
#: fields.py:1502
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1530
#: fields.py:1549
msgid "Value must be valid JSON."
msgstr ""
#: filters.py:35 templates/rest_framework/filters/django_filter.html:5
#: filters.py:36 templates/rest_framework/filters/django_filter.html:5
msgid "Submit"
msgstr "أرسل"
#: filters.py:336
msgid "ascending"
msgstr ""
#: pagination.py:189
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}."
msgstr "رقم الصفحة \"{page_number}\" غير صالح : {message}."
#: filters.py:337
msgid "descending"
msgstr ""
#: pagination.py:407
#: pagination.py:193
msgid "Invalid page."
msgstr "صفحة غير صحيحة"
#: pagination.py:427
msgid "Invalid cursor"
msgstr ""
#: relations.py:196
#, python-brace-format
#: relations.py:207
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr "معرف العنصر \"{pk_value}\" غير صالح - العنصر غير موجود."
#: relations.py:197
#, python-brace-format
#: relations.py:208
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
#: relations.py:229
#: relations.py:240
msgid "Invalid hyperlink - No URL match."
msgstr ""
#: relations.py:230
#: relations.py:241
msgid "Invalid hyperlink - Incorrect URL match."
msgstr ""
#: relations.py:231
#: relations.py:242
msgid "Invalid hyperlink - Object does not exist."
msgstr ""
#: relations.py:232
#, python-brace-format
#: relations.py:243
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
#: relations.py:391
#, python-brace-format
#: relations.py:401
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
#: relations.py:392
#: relations.py:402
msgid "Invalid value."
msgstr "قيمة غير صالحة."
#: serializers.py:326
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr ""
#: templates/rest_framework/admin.html:118
#: templates/rest_framework/admin.html:116
#: templates/rest_framework/base.html:128
msgid "Filters"
msgstr ""
msgstr "مرشحات"
#: templates/rest_framework/filters/django_filter.html:2
#: templates/rest_framework/filters/django_filter_crispyforms.html:4
msgid "Field filters"
msgstr ""
msgstr "مرشحات الحقول"
#: templates/rest_framework/filters/ordering.html:3
msgid "Ordering"
msgstr ""
msgstr "الترتيب"
#: templates/rest_framework/filters/search.html:2
msgid "Search"
msgstr ""
msgstr "البحث"
#: templates/rest_framework/horizontal/radio.html:2
#: templates/rest_framework/inline/radio.html:2
@ -382,27 +396,23 @@ msgstr ""
msgid "No items to select."
msgstr ""
#: validators.py:24
#: validators.py:43
msgid "This field must be unique."
msgstr ""
#: validators.py:78
#, python-brace-format
#: validators.py:97
msgid "The fields {field_names} must make a unique set."
msgstr ""
#: validators.py:226
#, python-brace-format
#: validators.py:245
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
#: validators.py:241
#, python-brace-format
#: validators.py:260
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
#: validators.py:254
#, python-brace-format
#: validators.py:273
msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
@ -410,15 +420,19 @@ msgstr ""
msgid "Invalid version in \"Accept\" header."
msgstr ""
#: versioning.py:73 versioning.py:115
#: versioning.py:73
msgid "Invalid version in URL path."
msgstr ""
#: versioning.py:144
#: versioning.py:115
msgid "Invalid version in URL path. Does not match any version namespace."
msgstr ""
#: versioning.py:147
msgid "Invalid version in hostname."
msgstr ""
#: versioning.py:166
#: versioning.py:169
msgid "Invalid version in query parameter."
msgstr ""

View File

@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-12-07 18:53+0100\n"
"PO-Revision-Date: 2015-12-07 17:55+0000\n"
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"POT-Creation-Date: 2016-07-12 16:13+0100\n"
"PO-Revision-Date: 2016-07-12 15:14+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
"Language-Team: Belarusian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/be/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -17,43 +17,75 @@ msgstr ""
"Language: be\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
#: authentication.py:72
#: authentication.py:73
msgid "Invalid basic header. No credentials provided."
msgstr ""
#: authentication.py:75
#: authentication.py:76
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr ""
#: authentication.py:81
#: authentication.py:82
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr ""
#: authentication.py:98
#: authentication.py:99
msgid "Invalid username/password."
msgstr ""
#: authentication.py:101 authentication.py:188
#: authentication.py:102 authentication.py:198
msgid "User inactive or deleted."
msgstr ""
#: authentication.py:167
#: authentication.py:176
msgid "Invalid token header. No credentials provided."
msgstr ""
#: authentication.py:170
#: authentication.py:179
msgid "Invalid token header. Token string should not contain spaces."
msgstr ""
#: authentication.py:176
#: authentication.py:185
msgid ""
"Invalid token header. Token string should not contain invalid characters."
msgstr ""
#: authentication.py:185
#: authentication.py:195
msgid "Invalid token."
msgstr ""
#: authtoken/apps.py:7
msgid "Auth Token"
msgstr ""
#: authtoken/models.py:15
msgid "Key"
msgstr ""
#: authtoken/models.py:18
msgid "User"
msgstr ""
#: authtoken/models.py:20
msgid "Created"
msgstr ""
#: authtoken/models.py:29
msgid "Token"
msgstr ""
#: authtoken/models.py:30
msgid "Tokens"
msgstr ""
#: authtoken/serializers.py:8
msgid "Username"
msgstr ""
#: authtoken/serializers.py:9
msgid "Password"
msgstr ""
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr ""
@ -91,7 +123,6 @@ msgid "Not found."
msgstr ""
#: exceptions.py:109
#, python-brace-format
msgid "Method \"{method}\" not allowed."
msgstr ""
@ -100,7 +131,6 @@ msgid "Could not satisfy the request Accept header."
msgstr ""
#: exceptions.py:132
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request."
msgstr ""
@ -108,250 +138,233 @@ msgstr ""
msgid "Request was throttled."
msgstr ""
#: fields.py:266 relations.py:195 relations.py:228 validators.py:79
#: validators.py:162
#: fields.py:269 relations.py:206 relations.py:239 validators.py:98
#: validators.py:181
msgid "This field is required."
msgstr ""
#: fields.py:267
#: fields.py:270
msgid "This field may not be null."
msgstr ""
#: fields.py:603 fields.py:634
#, python-brace-format
#: fields.py:608 fields.py:639
msgid "\"{input}\" is not a valid boolean."
msgstr ""
#: fields.py:669
#: fields.py:674
msgid "This field may not be blank."
msgstr ""
#: fields.py:670 fields.py:1656
#, python-brace-format
#: fields.py:675 fields.py:1675
msgid "Ensure this field has no more than {max_length} characters."
msgstr ""
#: fields.py:671
#, python-brace-format
#: fields.py:676
msgid "Ensure this field has at least {min_length} characters."
msgstr ""
#: fields.py:708
#: fields.py:713
msgid "Enter a valid email address."
msgstr ""
#: fields.py:719
#: fields.py:724
msgid "This value does not match the required pattern."
msgstr ""
#: fields.py:730
#: fields.py:735
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr ""
#: fields.py:742
#: fields.py:747
msgid "Enter a valid URL."
msgstr ""
#: fields.py:755
#, python-brace-format
#: fields.py:760
msgid "\"{value}\" is not a valid UUID."
msgstr ""
#: fields.py:791
#: fields.py:796
msgid "Enter a valid IPv4 or IPv6 address."
msgstr ""
#: fields.py:816
#: fields.py:821
msgid "A valid integer is required."
msgstr ""
#: fields.py:817 fields.py:852 fields.py:885
#, python-brace-format
#: fields.py:822 fields.py:857 fields.py:891
msgid "Ensure this value is less than or equal to {max_value}."
msgstr ""
#: fields.py:818 fields.py:853 fields.py:886
#, python-brace-format
#: fields.py:823 fields.py:858 fields.py:892
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr ""
#: fields.py:819 fields.py:854 fields.py:890
#: fields.py:824 fields.py:859 fields.py:896
msgid "String value too large."
msgstr ""
#: fields.py:851 fields.py:884
#: fields.py:856 fields.py:890
msgid "A valid number is required."
msgstr ""
#: fields.py:887
#, python-brace-format
#: fields.py:893
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr ""
#: fields.py:888
#, python-brace-format
#: fields.py:894
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr ""
#: fields.py:889
#, python-brace-format
#: fields.py:895
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
#: fields.py:1004
#, python-brace-format
#: fields.py:1025
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1005
#: fields.py:1026
msgid "Expected a datetime but got a date."
msgstr ""
#: fields.py:1079
#, python-brace-format
#: fields.py:1103
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1080
#: fields.py:1104
msgid "Expected a date but got a datetime."
msgstr ""
#: fields.py:1148
#, python-brace-format
#: fields.py:1170
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1207
#, python-brace-format
#: fields.py:1232
msgid "Duration has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1232 fields.py:1281
#, python-brace-format
#: fields.py:1251 fields.py:1300
msgid "\"{input}\" is not a valid choice."
msgstr ""
#: fields.py:1235 relations.py:62 relations.py:431
#, python-brace-format
#: fields.py:1254 relations.py:71 relations.py:441
msgid "More than {count} items..."
msgstr ""
#: fields.py:1282 fields.py:1429 relations.py:427 serializers.py:520
#, python-brace-format
#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1283
#: fields.py:1302
msgid "This selection may not be empty."
msgstr ""
#: fields.py:1320
#, python-brace-format
#: fields.py:1339
msgid "\"{input}\" is not a valid path choice."
msgstr ""
#: fields.py:1339
#: fields.py:1358
msgid "No file was submitted."
msgstr ""
#: fields.py:1340
#: fields.py:1359
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr ""
#: fields.py:1341
#: fields.py:1360
msgid "No filename could be determined."
msgstr ""
#: fields.py:1342
#: fields.py:1361
msgid "The submitted file is empty."
msgstr ""
#: fields.py:1343
#, python-brace-format
#: fields.py:1362
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
#: fields.py:1391
#: fields.py:1410
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: fields.py:1430 relations.py:428 serializers.py:521
#: fields.py:1449 relations.py:438 serializers.py:525
msgid "This list may not be empty."
msgstr ""
#: fields.py:1483
#, python-brace-format
#: fields.py:1502
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1530
#: fields.py:1549
msgid "Value must be valid JSON."
msgstr ""
#: filters.py:35 templates/rest_framework/filters/django_filter.html:5
#: filters.py:36 templates/rest_framework/filters/django_filter.html:5
msgid "Submit"
msgstr ""
#: pagination.py:189
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}."
#: filters.py:336
msgid "ascending"
msgstr ""
#: pagination.py:407
#: filters.py:337
msgid "descending"
msgstr ""
#: pagination.py:193
msgid "Invalid page."
msgstr ""
#: pagination.py:427
msgid "Invalid cursor"
msgstr ""
#: relations.py:196
#, python-brace-format
#: relations.py:207
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr ""
#: relations.py:197
#, python-brace-format
#: relations.py:208
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
#: relations.py:229
#: relations.py:240
msgid "Invalid hyperlink - No URL match."
msgstr ""
#: relations.py:230
#: relations.py:241
msgid "Invalid hyperlink - Incorrect URL match."
msgstr ""
#: relations.py:231
#: relations.py:242
msgid "Invalid hyperlink - Object does not exist."
msgstr ""
#: relations.py:232
#, python-brace-format
#: relations.py:243
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
#: relations.py:391
#, python-brace-format
#: relations.py:401
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
#: relations.py:392
#: relations.py:402
msgid "Invalid value."
msgstr ""
#: serializers.py:326
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr ""
#: templates/rest_framework/admin.html:118
#: templates/rest_framework/admin.html:116
#: templates/rest_framework/base.html:128
msgid "Filters"
msgstr ""
@ -381,27 +394,23 @@ msgstr ""
msgid "No items to select."
msgstr ""
#: validators.py:24
#: validators.py:43
msgid "This field must be unique."
msgstr ""
#: validators.py:78
#, python-brace-format
#: validators.py:97
msgid "The fields {field_names} must make a unique set."
msgstr ""
#: validators.py:226
#, python-brace-format
#: validators.py:245
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
#: validators.py:241
#, python-brace-format
#: validators.py:260
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
#: validators.py:254
#, python-brace-format
#: validators.py:273
msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
@ -409,15 +418,19 @@ msgstr ""
msgid "Invalid version in \"Accept\" header."
msgstr ""
#: versioning.py:73 versioning.py:115
#: versioning.py:73
msgid "Invalid version in URL path."
msgstr ""
#: versioning.py:144
#: versioning.py:115
msgid "Invalid version in URL path. Does not match any version namespace."
msgstr ""
#: versioning.py:147
msgid "Invalid version in hostname."
msgstr ""
#: versioning.py:166
#: versioning.py:169
msgid "Invalid version in query parameter."
msgstr ""

View File

@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-12-07 18:53+0100\n"
"PO-Revision-Date: 2015-12-07 17:55+0000\n"
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"POT-Creation-Date: 2016-07-12 16:13+0100\n"
"PO-Revision-Date: 2016-07-12 15:14+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
"Language-Team: Catalan (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ca/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -17,43 +17,75 @@ msgstr ""
"Language: ca\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentication.py:72
#: authentication.py:73
msgid "Invalid basic header. No credentials provided."
msgstr "Header Basic invàlid. No hi ha disponibles les credencials."
#: authentication.py:75
#: authentication.py:76
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr "Header Basic invàlid. Les credencials no poden contenir espais."
#: authentication.py:81
#: authentication.py:82
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr "Header Basic invàlid. Les credencials no estan codificades correctament en base64."
#: authentication.py:98
#: authentication.py:99
msgid "Invalid username/password."
msgstr "Usuari/Contrasenya incorrectes."
#: authentication.py:101 authentication.py:188
#: authentication.py:102 authentication.py:198
msgid "User inactive or deleted."
msgstr "Usuari inactiu o esborrat."
#: authentication.py:167
#: authentication.py:176
msgid "Invalid token header. No credentials provided."
msgstr "Token header invàlid. No s'han indicat les credencials."
#: authentication.py:170
#: authentication.py:179
msgid "Invalid token header. Token string should not contain spaces."
msgstr "Token header invàlid. El token no ha de contenir espais."
#: authentication.py:176
#: authentication.py:185
msgid ""
"Invalid token header. Token string should not contain invalid characters."
msgstr "Token header invàlid. El token no pot contenir caràcters invàlids."
#: authentication.py:185
#: authentication.py:195
msgid "Invalid token."
msgstr "Token invàlid."
#: authtoken/apps.py:7
msgid "Auth Token"
msgstr ""
#: authtoken/models.py:15
msgid "Key"
msgstr ""
#: authtoken/models.py:18
msgid "User"
msgstr ""
#: authtoken/models.py:20
msgid "Created"
msgstr ""
#: authtoken/models.py:29
msgid "Token"
msgstr ""
#: authtoken/models.py:30
msgid "Tokens"
msgstr ""
#: authtoken/serializers.py:8
msgid "Username"
msgstr ""
#: authtoken/serializers.py:9
msgid "Password"
msgstr ""
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr "Compte d'usuari desactivat."
@ -91,7 +123,6 @@ msgid "Not found."
msgstr "No trobat."
#: exceptions.py:109
#, python-brace-format
msgid "Method \"{method}\" not allowed."
msgstr "Mètode \"{method}\" no permès."
@ -100,7 +131,6 @@ msgid "Could not satisfy the request Accept header."
msgstr "No s'ha pogut satisfer l'Accept header de la petició."
#: exceptions.py:132
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request."
msgstr "Media type \"{media_type}\" no suportat."
@ -108,250 +138,233 @@ msgstr "Media type \"{media_type}\" no suportat."
msgid "Request was throttled."
msgstr "La petició ha estat limitada pel número màxim de peticions definit."
#: fields.py:266 relations.py:195 relations.py:228 validators.py:79
#: validators.py:162
#: fields.py:269 relations.py:206 relations.py:239 validators.py:98
#: validators.py:181
msgid "This field is required."
msgstr "Aquest camp és obligatori."
#: fields.py:267
#: fields.py:270
msgid "This field may not be null."
msgstr "Aquest camp no pot ser nul."
#: fields.py:603 fields.py:634
#, python-brace-format
#: fields.py:608 fields.py:639
msgid "\"{input}\" is not a valid boolean."
msgstr "\"{input}\" no és un booleà."
#: fields.py:669
#: fields.py:674
msgid "This field may not be blank."
msgstr "Aquest camp no pot estar en blanc."
#: fields.py:670 fields.py:1656
#, python-brace-format
#: fields.py:675 fields.py:1675
msgid "Ensure this field has no more than {max_length} characters."
msgstr "Aquest camp no pot tenir més de {max_length} caràcters."
#: fields.py:671
#, python-brace-format
#: fields.py:676
msgid "Ensure this field has at least {min_length} characters."
msgstr "Aquest camp ha de tenir un mínim de {min_length} caràcters."
#: fields.py:708
#: fields.py:713
msgid "Enter a valid email address."
msgstr "Introdueixi una adreça de correu vàlida."
#: fields.py:719
#: fields.py:724
msgid "This value does not match the required pattern."
msgstr "Aquest valor no compleix el patró requerit."
#: fields.py:730
#: fields.py:735
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr "Introdueix un \"slug\" vàlid consistent en lletres, números, guions o guions baixos."
#: fields.py:742
#: fields.py:747
msgid "Enter a valid URL."
msgstr "Introdueixi una URL vàlida."
#: fields.py:755
#, python-brace-format
#: fields.py:760
msgid "\"{value}\" is not a valid UUID."
msgstr "\"{value}\" no és un UUID vàlid."
#: fields.py:791
#: fields.py:796
msgid "Enter a valid IPv4 or IPv6 address."
msgstr "Introdueixi una adreça IPv4 o IPv6 vàlida."
#: fields.py:816
#: fields.py:821
msgid "A valid integer is required."
msgstr "Es requereix un nombre enter vàlid."
#: fields.py:817 fields.py:852 fields.py:885
#, python-brace-format
#: fields.py:822 fields.py:857 fields.py:891
msgid "Ensure this value is less than or equal to {max_value}."
msgstr "Aquest valor ha de ser menor o igual a {max_value}."
#: fields.py:818 fields.py:853 fields.py:886
#, python-brace-format
#: fields.py:823 fields.py:858 fields.py:892
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "Aquest valor ha de ser més gran o igual a {min_value}."
#: fields.py:819 fields.py:854 fields.py:890
#: fields.py:824 fields.py:859 fields.py:896
msgid "String value too large."
msgstr "Valor del text massa gran."
#: fields.py:851 fields.py:884
#: fields.py:856 fields.py:890
msgid "A valid number is required."
msgstr "Es requereix un nombre vàlid."
#: fields.py:887
#, python-brace-format
#: fields.py:893
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "No pot haver-hi més de {max_digits} dígits en total."
#: fields.py:888
#, python-brace-format
#: fields.py:894
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr "No pot haver-hi més de {max_decimal_places} decimals."
#: fields.py:889
#, python-brace-format
#: fields.py:895
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr "No pot haver-hi més de {max_whole_digits} dígits abans del punt decimal."
#: fields.py:1004
#, python-brace-format
#: fields.py:1025
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr "El Datetime té un format incorrecte. Utilitzi un d'aquests formats: {format}."
#: fields.py:1005
#: fields.py:1026
msgid "Expected a datetime but got a date."
msgstr "S'espera un Datetime però s'ha rebut un Date."
#: fields.py:1079
#, python-brace-format
#: fields.py:1103
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr "El Date té un format incorrecte. Utilitzi un d'aquests formats: {format}."
#: fields.py:1080
#: fields.py:1104
msgid "Expected a date but got a datetime."
msgstr "S'espera un Date però s'ha rebut un Datetime."
#: fields.py:1148
#, python-brace-format
#: fields.py:1170
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "El Time té un format incorrecte. Utilitzi un d'aquests formats: {format}."
#: fields.py:1207
#, python-brace-format
#: fields.py:1232
msgid "Duration has wrong format. Use one of these formats instead: {format}."
msgstr "La durada té un format incorrecte. Utilitzi un d'aquests formats: {format}."
#: fields.py:1232 fields.py:1281
#, python-brace-format
#: fields.py:1251 fields.py:1300
msgid "\"{input}\" is not a valid choice."
msgstr "\"{input}\" no és una opció vàlida."
#: fields.py:1235 relations.py:62 relations.py:431
#, python-brace-format
#: fields.py:1254 relations.py:71 relations.py:441
msgid "More than {count} items..."
msgstr ""
#: fields.py:1282 fields.py:1429 relations.py:427 serializers.py:520
#, python-brace-format
#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr "S'espera una llista d'ítems però s'ha rebut el tipus \"{input_type}\"."
#: fields.py:1283
#: fields.py:1302
msgid "This selection may not be empty."
msgstr "Aquesta selecció no pot estar buida."
#: fields.py:1320
#, python-brace-format
#: fields.py:1339
msgid "\"{input}\" is not a valid path choice."
msgstr "\"{input}\" no és un path vàlid."
#: fields.py:1339
#: fields.py:1358
msgid "No file was submitted."
msgstr "No s'ha enviat cap fitxer."
#: fields.py:1340
#: fields.py:1359
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr "Les dades enviades no són un fitxer. Comproveu l'encoding type del formulari."
#: fields.py:1341
#: fields.py:1360
msgid "No filename could be determined."
msgstr "No s'ha pogut determinar el nom del fitxer."
#: fields.py:1342
#: fields.py:1361
msgid "The submitted file is empty."
msgstr "El fitxer enviat està buit."
#: fields.py:1343
#, python-brace-format
#: fields.py:1362
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr "El nom del fitxer ha de tenir com a màxim {max_length} caràcters (en té {length})."
#: fields.py:1391
#: fields.py:1410
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr "Envieu una imatge vàlida. El fitxer enviat no és una imatge o és una imatge corrompuda."
#: fields.py:1430 relations.py:428 serializers.py:521
#: fields.py:1449 relations.py:438 serializers.py:525
msgid "This list may not be empty."
msgstr "Aquesta llista no pot estar buida."
#: fields.py:1483
#, python-brace-format
#: fields.py:1502
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr "S'espera un diccionari però s'ha rebut el tipus \"{input_type}\"."
#: fields.py:1530
#: fields.py:1549
msgid "Value must be valid JSON."
msgstr ""
#: filters.py:35 templates/rest_framework/filters/django_filter.html:5
#: filters.py:36 templates/rest_framework/filters/django_filter.html:5
msgid "Submit"
msgstr ""
#: pagination.py:189
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}."
msgstr "Pàgina invàlida \"{page_number}\": {message}."
#: filters.py:336
msgid "ascending"
msgstr ""
#: pagination.py:407
#: filters.py:337
msgid "descending"
msgstr ""
#: pagination.py:193
msgid "Invalid page."
msgstr ""
#: pagination.py:427
msgid "Invalid cursor"
msgstr "Cursor invàlid."
#: relations.py:196
#, python-brace-format
#: relations.py:207
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr "PK invàlida \"{pk_value}\" - l'objecte no existeix."
#: relations.py:197
#, python-brace-format
#: relations.py:208
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr "Tipus incorrecte. S'espera el valor d'una PK, s'ha rebut {data_type}."
#: relations.py:229
#: relations.py:240
msgid "Invalid hyperlink - No URL match."
msgstr "Hyperlink invàlid - Cap match d'URL."
#: relations.py:230
#: relations.py:241
msgid "Invalid hyperlink - Incorrect URL match."
msgstr "Hyperlink invàlid - Match d'URL incorrecta."
#: relations.py:231
#: relations.py:242
msgid "Invalid hyperlink - Object does not exist."
msgstr "Hyperlink invàlid - L'objecte no existeix."
#: relations.py:232
#, python-brace-format
#: relations.py:243
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr "Tipus incorrecte. S'espera una URL, s'ha rebut {data_type}."
#: relations.py:391
#, python-brace-format
#: relations.py:401
msgid "Object with {slug_name}={value} does not exist."
msgstr "L'objecte amb {slug_name}={value} no existeix."
#: relations.py:392
#: relations.py:402
msgid "Invalid value."
msgstr "Valor invàlid."
#: serializers.py:326
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr "Dades invàlides. S'espera un diccionari però s'ha rebut {datatype}."
#: templates/rest_framework/admin.html:118
#: templates/rest_framework/admin.html:116
#: templates/rest_framework/base.html:128
msgid "Filters"
msgstr ""
@ -381,27 +394,23 @@ msgstr "Cap"
msgid "No items to select."
msgstr "Cap opció seleccionada."
#: validators.py:24
#: validators.py:43
msgid "This field must be unique."
msgstr "Aquest camp ha de ser únic."
#: validators.py:78
#, python-brace-format
#: validators.py:97
msgid "The fields {field_names} must make a unique set."
msgstr "Aquests camps {field_names} han de constituir un conjunt únic."
#: validators.py:226
#, python-brace-format
#: validators.py:245
msgid "This field must be unique for the \"{date_field}\" date."
msgstr "Aquest camp ha de ser únic per a la data \"{date_field}\"."
#: validators.py:241
#, python-brace-format
#: validators.py:260
msgid "This field must be unique for the \"{date_field}\" month."
msgstr "Aquest camp ha de ser únic per al mes \"{date_field}\"."
#: validators.py:254
#, python-brace-format
#: validators.py:273
msgid "This field must be unique for the \"{date_field}\" year."
msgstr "Aquest camp ha de ser únic per a l'any \"{date_field}\"."
@ -409,15 +418,19 @@ msgstr "Aquest camp ha de ser únic per a l'any \"{date_field}\"."
msgid "Invalid version in \"Accept\" header."
msgstr "Versió invàlida al header \"Accept\"."
#: versioning.py:73 versioning.py:115
#: versioning.py:73
msgid "Invalid version in URL path."
msgstr "Versió invàlida a la URL."
#: versioning.py:144
#: versioning.py:115
msgid "Invalid version in URL path. Does not match any version namespace."
msgstr ""
#: versioning.py:147
msgid "Invalid version in hostname."
msgstr "Versió invàlida al hostname."
#: versioning.py:166
#: versioning.py:169
msgid "Invalid version in query parameter."
msgstr "Versió invàlida al paràmetre de consulta."

View File

@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-12-07 18:53+0100\n"
"PO-Revision-Date: 2015-12-07 17:55+0000\n"
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"POT-Creation-Date: 2016-07-12 16:13+0100\n"
"PO-Revision-Date: 2016-07-12 15:14+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
"Language-Team: Catalan (Spain) (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ca_ES/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -17,43 +17,75 @@ msgstr ""
"Language: ca_ES\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentication.py:72
#: authentication.py:73
msgid "Invalid basic header. No credentials provided."
msgstr ""
#: authentication.py:75
#: authentication.py:76
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr ""
#: authentication.py:81
#: authentication.py:82
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr ""
#: authentication.py:98
#: authentication.py:99
msgid "Invalid username/password."
msgstr ""
#: authentication.py:101 authentication.py:188
#: authentication.py:102 authentication.py:198
msgid "User inactive or deleted."
msgstr ""
#: authentication.py:167
#: authentication.py:176
msgid "Invalid token header. No credentials provided."
msgstr ""
#: authentication.py:170
#: authentication.py:179
msgid "Invalid token header. Token string should not contain spaces."
msgstr ""
#: authentication.py:176
#: authentication.py:185
msgid ""
"Invalid token header. Token string should not contain invalid characters."
msgstr ""
#: authentication.py:185
#: authentication.py:195
msgid "Invalid token."
msgstr ""
#: authtoken/apps.py:7
msgid "Auth Token"
msgstr ""
#: authtoken/models.py:15
msgid "Key"
msgstr ""
#: authtoken/models.py:18
msgid "User"
msgstr ""
#: authtoken/models.py:20
msgid "Created"
msgstr ""
#: authtoken/models.py:29
msgid "Token"
msgstr ""
#: authtoken/models.py:30
msgid "Tokens"
msgstr ""
#: authtoken/serializers.py:8
msgid "Username"
msgstr ""
#: authtoken/serializers.py:9
msgid "Password"
msgstr ""
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr ""
@ -91,7 +123,6 @@ msgid "Not found."
msgstr ""
#: exceptions.py:109
#, python-brace-format
msgid "Method \"{method}\" not allowed."
msgstr ""
@ -100,7 +131,6 @@ msgid "Could not satisfy the request Accept header."
msgstr ""
#: exceptions.py:132
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request."
msgstr ""
@ -108,250 +138,233 @@ msgstr ""
msgid "Request was throttled."
msgstr ""
#: fields.py:266 relations.py:195 relations.py:228 validators.py:79
#: validators.py:162
#: fields.py:269 relations.py:206 relations.py:239 validators.py:98
#: validators.py:181
msgid "This field is required."
msgstr ""
#: fields.py:267
#: fields.py:270
msgid "This field may not be null."
msgstr ""
#: fields.py:603 fields.py:634
#, python-brace-format
#: fields.py:608 fields.py:639
msgid "\"{input}\" is not a valid boolean."
msgstr ""
#: fields.py:669
#: fields.py:674
msgid "This field may not be blank."
msgstr ""
#: fields.py:670 fields.py:1656
#, python-brace-format
#: fields.py:675 fields.py:1675
msgid "Ensure this field has no more than {max_length} characters."
msgstr ""
#: fields.py:671
#, python-brace-format
#: fields.py:676
msgid "Ensure this field has at least {min_length} characters."
msgstr ""
#: fields.py:708
#: fields.py:713
msgid "Enter a valid email address."
msgstr ""
#: fields.py:719
#: fields.py:724
msgid "This value does not match the required pattern."
msgstr ""
#: fields.py:730
#: fields.py:735
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr ""
#: fields.py:742
#: fields.py:747
msgid "Enter a valid URL."
msgstr ""
#: fields.py:755
#, python-brace-format
#: fields.py:760
msgid "\"{value}\" is not a valid UUID."
msgstr ""
#: fields.py:791
#: fields.py:796
msgid "Enter a valid IPv4 or IPv6 address."
msgstr ""
#: fields.py:816
#: fields.py:821
msgid "A valid integer is required."
msgstr ""
#: fields.py:817 fields.py:852 fields.py:885
#, python-brace-format
#: fields.py:822 fields.py:857 fields.py:891
msgid "Ensure this value is less than or equal to {max_value}."
msgstr ""
#: fields.py:818 fields.py:853 fields.py:886
#, python-brace-format
#: fields.py:823 fields.py:858 fields.py:892
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr ""
#: fields.py:819 fields.py:854 fields.py:890
#: fields.py:824 fields.py:859 fields.py:896
msgid "String value too large."
msgstr ""
#: fields.py:851 fields.py:884
#: fields.py:856 fields.py:890
msgid "A valid number is required."
msgstr ""
#: fields.py:887
#, python-brace-format
#: fields.py:893
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr ""
#: fields.py:888
#, python-brace-format
#: fields.py:894
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr ""
#: fields.py:889
#, python-brace-format
#: fields.py:895
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
#: fields.py:1004
#, python-brace-format
#: fields.py:1025
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1005
#: fields.py:1026
msgid "Expected a datetime but got a date."
msgstr ""
#: fields.py:1079
#, python-brace-format
#: fields.py:1103
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1080
#: fields.py:1104
msgid "Expected a date but got a datetime."
msgstr ""
#: fields.py:1148
#, python-brace-format
#: fields.py:1170
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1207
#, python-brace-format
#: fields.py:1232
msgid "Duration has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1232 fields.py:1281
#, python-brace-format
#: fields.py:1251 fields.py:1300
msgid "\"{input}\" is not a valid choice."
msgstr ""
#: fields.py:1235 relations.py:62 relations.py:431
#, python-brace-format
#: fields.py:1254 relations.py:71 relations.py:441
msgid "More than {count} items..."
msgstr ""
#: fields.py:1282 fields.py:1429 relations.py:427 serializers.py:520
#, python-brace-format
#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1283
#: fields.py:1302
msgid "This selection may not be empty."
msgstr ""
#: fields.py:1320
#, python-brace-format
#: fields.py:1339
msgid "\"{input}\" is not a valid path choice."
msgstr ""
#: fields.py:1339
#: fields.py:1358
msgid "No file was submitted."
msgstr ""
#: fields.py:1340
#: fields.py:1359
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr ""
#: fields.py:1341
#: fields.py:1360
msgid "No filename could be determined."
msgstr ""
#: fields.py:1342
#: fields.py:1361
msgid "The submitted file is empty."
msgstr ""
#: fields.py:1343
#, python-brace-format
#: fields.py:1362
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
#: fields.py:1391
#: fields.py:1410
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: fields.py:1430 relations.py:428 serializers.py:521
#: fields.py:1449 relations.py:438 serializers.py:525
msgid "This list may not be empty."
msgstr ""
#: fields.py:1483
#, python-brace-format
#: fields.py:1502
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1530
#: fields.py:1549
msgid "Value must be valid JSON."
msgstr ""
#: filters.py:35 templates/rest_framework/filters/django_filter.html:5
#: filters.py:36 templates/rest_framework/filters/django_filter.html:5
msgid "Submit"
msgstr ""
#: pagination.py:189
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}."
#: filters.py:336
msgid "ascending"
msgstr ""
#: pagination.py:407
#: filters.py:337
msgid "descending"
msgstr ""
#: pagination.py:193
msgid "Invalid page."
msgstr ""
#: pagination.py:427
msgid "Invalid cursor"
msgstr ""
#: relations.py:196
#, python-brace-format
#: relations.py:207
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr ""
#: relations.py:197
#, python-brace-format
#: relations.py:208
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
#: relations.py:229
#: relations.py:240
msgid "Invalid hyperlink - No URL match."
msgstr ""
#: relations.py:230
#: relations.py:241
msgid "Invalid hyperlink - Incorrect URL match."
msgstr ""
#: relations.py:231
#: relations.py:242
msgid "Invalid hyperlink - Object does not exist."
msgstr ""
#: relations.py:232
#, python-brace-format
#: relations.py:243
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
#: relations.py:391
#, python-brace-format
#: relations.py:401
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
#: relations.py:392
#: relations.py:402
msgid "Invalid value."
msgstr ""
#: serializers.py:326
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr ""
#: templates/rest_framework/admin.html:118
#: templates/rest_framework/admin.html:116
#: templates/rest_framework/base.html:128
msgid "Filters"
msgstr ""
@ -381,27 +394,23 @@ msgstr ""
msgid "No items to select."
msgstr ""
#: validators.py:24
#: validators.py:43
msgid "This field must be unique."
msgstr ""
#: validators.py:78
#, python-brace-format
#: validators.py:97
msgid "The fields {field_names} must make a unique set."
msgstr ""
#: validators.py:226
#, python-brace-format
#: validators.py:245
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
#: validators.py:241
#, python-brace-format
#: validators.py:260
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
#: validators.py:254
#, python-brace-format
#: validators.py:273
msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
@ -409,15 +418,19 @@ msgstr ""
msgid "Invalid version in \"Accept\" header."
msgstr ""
#: versioning.py:73 versioning.py:115
#: versioning.py:73
msgid "Invalid version in URL path."
msgstr ""
#: versioning.py:144
#: versioning.py:115
msgid "Invalid version in URL path. Does not match any version namespace."
msgstr ""
#: versioning.py:147
msgid "Invalid version in hostname."
msgstr ""
#: versioning.py:166
#: versioning.py:169
msgid "Invalid version in query parameter."
msgstr ""

View File

@ -9,9 +9,9 @@ msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-12-07 18:53+0100\n"
"PO-Revision-Date: 2015-12-07 17:55+0000\n"
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"POT-Creation-Date: 2016-07-12 16:13+0100\n"
"PO-Revision-Date: 2016-07-12 15:14+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
"Language-Team: Czech (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -19,43 +19,75 @@ msgstr ""
"Language: cs\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
#: authentication.py:72
#: authentication.py:73
msgid "Invalid basic header. No credentials provided."
msgstr "Chybná hlavička. Nebyly poskytnuty přihlašovací údaje."
#: authentication.py:75
#: authentication.py:76
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr "Chybná hlavička. Přihlašovací údaje by neměly obsahovat mezery."
#: authentication.py:81
#: authentication.py:82
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr "Chybná hlavička. Přihlašovací údaje nebyly správně zakódovány pomocí base64."
#: authentication.py:98
#: authentication.py:99
msgid "Invalid username/password."
msgstr "Chybné uživatelské jméno nebo heslo."
#: authentication.py:101 authentication.py:188
#: authentication.py:102 authentication.py:198
msgid "User inactive or deleted."
msgstr "Uživatelský účet je neaktivní nebo byl smazán."
#: authentication.py:167
#: authentication.py:176
msgid "Invalid token header. No credentials provided."
msgstr "Chybná hlavička tokenu. Nebyly zadány přihlašovací údaje."
#: authentication.py:170
#: authentication.py:179
msgid "Invalid token header. Token string should not contain spaces."
msgstr "Chybná hlavička tokenu. Přihlašovací údaje by neměly obsahovat mezery."
#: authentication.py:176
#: authentication.py:185
msgid ""
"Invalid token header. Token string should not contain invalid characters."
msgstr ""
#: authentication.py:185
#: authentication.py:195
msgid "Invalid token."
msgstr "Chybný token."
#: authtoken/apps.py:7
msgid "Auth Token"
msgstr ""
#: authtoken/models.py:15
msgid "Key"
msgstr ""
#: authtoken/models.py:18
msgid "User"
msgstr ""
#: authtoken/models.py:20
msgid "Created"
msgstr ""
#: authtoken/models.py:29
msgid "Token"
msgstr ""
#: authtoken/models.py:30
msgid "Tokens"
msgstr ""
#: authtoken/serializers.py:8
msgid "Username"
msgstr ""
#: authtoken/serializers.py:9
msgid "Password"
msgstr ""
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr "Uživatelský účet je uzamčen."
@ -93,7 +125,6 @@ msgid "Not found."
msgstr "Nenalezeno."
#: exceptions.py:109
#, python-brace-format
msgid "Method \"{method}\" not allowed."
msgstr "Metoda \"{method}\" není povolena."
@ -102,7 +133,6 @@ msgid "Could not satisfy the request Accept header."
msgstr "Nelze vyhovět požadavku v hlavičce Accept."
#: exceptions.py:132
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request."
msgstr "Nepodporovaný media type \"{media_type}\" v požadavku."
@ -110,250 +140,233 @@ msgstr "Nepodporovaný media type \"{media_type}\" v požadavku."
msgid "Request was throttled."
msgstr "Požadavek byl limitován kvůli omezení počtu požadavků za časovou periodu."
#: fields.py:266 relations.py:195 relations.py:228 validators.py:79
#: validators.py:162
#: fields.py:269 relations.py:206 relations.py:239 validators.py:98
#: validators.py:181
msgid "This field is required."
msgstr "Toto pole je vyžadováno."
#: fields.py:267
#: fields.py:270
msgid "This field may not be null."
msgstr "Toto pole nesmí být prázdné (null)."
#: fields.py:603 fields.py:634
#, python-brace-format
#: fields.py:608 fields.py:639
msgid "\"{input}\" is not a valid boolean."
msgstr "\"{input}\" nelze použít jako typ boolean."
#: fields.py:669
#: fields.py:674
msgid "This field may not be blank."
msgstr "Toto pole nesmí být prázdné."
#: fields.py:670 fields.py:1656
#, python-brace-format
#: fields.py:675 fields.py:1675
msgid "Ensure this field has no more than {max_length} characters."
msgstr "Zkontrolujte, že toto pole není delší než {max_length} znaků."
#: fields.py:671
#, python-brace-format
#: fields.py:676
msgid "Ensure this field has at least {min_length} characters."
msgstr "Zkontrolujte, že toto pole obsahuje alespoň {min_length} znaků."
#: fields.py:708
#: fields.py:713
msgid "Enter a valid email address."
msgstr "Vložte platnou e-mailovou adresu."
#: fields.py:719
#: fields.py:724
msgid "This value does not match the required pattern."
msgstr "Hodnota v tomto poli neodpovídá požadovanému formátu."
#: fields.py:730
#: fields.py:735
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr "Vložte platnou \"zkrácenou formu\" obsahující pouze malá písmena, čísla, spojovník nebo podtržítko."
#: fields.py:742
#: fields.py:747
msgid "Enter a valid URL."
msgstr "Vložte platný odkaz."
#: fields.py:755
#, python-brace-format
#: fields.py:760
msgid "\"{value}\" is not a valid UUID."
msgstr "\"{value}\" není platné UUID."
#: fields.py:791
#: fields.py:796
msgid "Enter a valid IPv4 or IPv6 address."
msgstr ""
#: fields.py:816
#: fields.py:821
msgid "A valid integer is required."
msgstr "Je vyžadováno celé číslo."
#: fields.py:817 fields.py:852 fields.py:885
#, python-brace-format
#: fields.py:822 fields.py:857 fields.py:891
msgid "Ensure this value is less than or equal to {max_value}."
msgstr "Zkontrolujte, že hodnota je menší nebo rovna {max_value}."
#: fields.py:818 fields.py:853 fields.py:886
#, python-brace-format
#: fields.py:823 fields.py:858 fields.py:892
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "Zkontrolujte, že hodnota je větší nebo rovna {min_value}."
#: fields.py:819 fields.py:854 fields.py:890
#: fields.py:824 fields.py:859 fields.py:896
msgid "String value too large."
msgstr "Řetězec je příliš dlouhý."
#: fields.py:851 fields.py:884
#: fields.py:856 fields.py:890
msgid "A valid number is required."
msgstr "Je vyžadováno číslo."
#: fields.py:887
#, python-brace-format
#: fields.py:893
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "Zkontrolujte, že číslo neobsahuje více než {max_digits} čislic."
#: fields.py:888
#, python-brace-format
#: fields.py:894
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr "Zkontrolujte, že číslo nemá více než {max_decimal_places} desetinných míst."
#: fields.py:889
#, python-brace-format
#: fields.py:895
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr "Zkontrolujte, že číslo neobsahuje více než {max_whole_digits} čislic před desetinnou čárkou."
#: fields.py:1004
#, python-brace-format
#: fields.py:1025
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr "Chybný formát data a času. Použijte jeden z těchto formátů: {format}."
#: fields.py:1005
#: fields.py:1026
msgid "Expected a datetime but got a date."
msgstr "Bylo zadáno pouze datum bez času."
#: fields.py:1079
#, python-brace-format
#: fields.py:1103
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr "Chybný formát data. Použijte jeden z těchto formátů: {format}."
#: fields.py:1080
#: fields.py:1104
msgid "Expected a date but got a datetime."
msgstr "Bylo zadáno datum a čas, místo samotného data."
#: fields.py:1148
#, python-brace-format
#: fields.py:1170
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "Chybný formát času. Použijte jeden z těchto formátů: {format}."
#: fields.py:1207
#, python-brace-format
#: fields.py:1232
msgid "Duration has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1232 fields.py:1281
#, python-brace-format
#: fields.py:1251 fields.py:1300
msgid "\"{input}\" is not a valid choice."
msgstr "\"{input}\" není platnou možností."
#: fields.py:1235 relations.py:62 relations.py:431
#, python-brace-format
#: fields.py:1254 relations.py:71 relations.py:441
msgid "More than {count} items..."
msgstr ""
#: fields.py:1282 fields.py:1429 relations.py:427 serializers.py:520
#, python-brace-format
#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr "Byl očekáván seznam položek ale nalezen \"{input_type}\"."
#: fields.py:1283
#: fields.py:1302
msgid "This selection may not be empty."
msgstr ""
#: fields.py:1320
#, python-brace-format
#: fields.py:1339
msgid "\"{input}\" is not a valid path choice."
msgstr ""
#: fields.py:1339
#: fields.py:1358
msgid "No file was submitted."
msgstr "Nebyl zaslán žádný soubor."
#: fields.py:1340
#: fields.py:1359
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr "Zaslaná data neobsahují soubor. Zkontrolujte typ kódování ve formuláři."
#: fields.py:1341
#: fields.py:1360
msgid "No filename could be determined."
msgstr "Nebylo možné zjistit jméno souboru."
#: fields.py:1342
#: fields.py:1361
msgid "The submitted file is empty."
msgstr "Zaslaný soubor je prázdný."
#: fields.py:1343
#, python-brace-format
#: fields.py:1362
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr "Zajistěte, aby jméno souboru obsahovalo maximálně {max_length} znaků (teď má {length} znaků)."
#: fields.py:1391
#: fields.py:1410
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr "Nahrajte platný obrázek. Nahraný soubor buď není obrázkem nebo je poškozen."
#: fields.py:1430 relations.py:428 serializers.py:521
#: fields.py:1449 relations.py:438 serializers.py:525
msgid "This list may not be empty."
msgstr ""
#: fields.py:1483
#, python-brace-format
#: fields.py:1502
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr "Byl očekáván slovník položek ale nalezen \"{input_type}\"."
#: fields.py:1530
#: fields.py:1549
msgid "Value must be valid JSON."
msgstr ""
#: filters.py:35 templates/rest_framework/filters/django_filter.html:5
#: filters.py:36 templates/rest_framework/filters/django_filter.html:5
msgid "Submit"
msgstr ""
#: pagination.py:189
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}."
msgstr "Chybné čislo stránky \"{page_number}\": {message}."
#: filters.py:336
msgid "ascending"
msgstr ""
#: pagination.py:407
#: filters.py:337
msgid "descending"
msgstr ""
#: pagination.py:193
msgid "Invalid page."
msgstr ""
#: pagination.py:427
msgid "Invalid cursor"
msgstr "Chybný kurzor."
#: relations.py:196
#, python-brace-format
#: relations.py:207
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr "Chybný primární klíč \"{pk_value}\" - objekt neexistuje."
#: relations.py:197
#, python-brace-format
#: relations.py:208
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr "Chybný typ. Byl přijat typ {data_type} místo hodnoty primárního klíče."
#: relations.py:229
#: relations.py:240
msgid "Invalid hyperlink - No URL match."
msgstr "Chybný odkaz - nebyla nalezena žádní shoda."
#: relations.py:230
#: relations.py:241
msgid "Invalid hyperlink - Incorrect URL match."
msgstr "Chybný odkaz - byla nalezena neplatná shoda."
#: relations.py:231
#: relations.py:242
msgid "Invalid hyperlink - Object does not exist."
msgstr "Chybný odkaz - objekt neexistuje."
#: relations.py:232
#, python-brace-format
#: relations.py:243
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr "Chybný typ. Byl přijat typ {data_type} místo očekávaného odkazu."
#: relations.py:391
#, python-brace-format
#: relations.py:401
msgid "Object with {slug_name}={value} does not exist."
msgstr "Objekt s {slug_name}={value} neexistuje."
#: relations.py:392
#: relations.py:402
msgid "Invalid value."
msgstr "Chybná hodnota."
#: serializers.py:326
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr "Chybná data. Byl přijat typ {datatype} místo očekávaného slovníku."
#: templates/rest_framework/admin.html:118
#: templates/rest_framework/admin.html:116
#: templates/rest_framework/base.html:128
msgid "Filters"
msgstr ""
@ -383,27 +396,23 @@ msgstr ""
msgid "No items to select."
msgstr ""
#: validators.py:24
#: validators.py:43
msgid "This field must be unique."
msgstr "Tato položka musí být unikátní."
#: validators.py:78
#, python-brace-format
#: validators.py:97
msgid "The fields {field_names} must make a unique set."
msgstr "Položka {field_names} musí tvořit unikátní množinu."
#: validators.py:226
#, python-brace-format
#: validators.py:245
msgid "This field must be unique for the \"{date_field}\" date."
msgstr "Tato položka musí být pro datum \"{date_field}\" unikátní."
#: validators.py:241
#, python-brace-format
#: validators.py:260
msgid "This field must be unique for the \"{date_field}\" month."
msgstr "Tato položka musí být pro měsíc \"{date_field}\" unikátní."
#: validators.py:254
#, python-brace-format
#: validators.py:273
msgid "This field must be unique for the \"{date_field}\" year."
msgstr "Tato položka musí být pro rok \"{date_field}\" unikátní."
@ -411,15 +420,19 @@ msgstr "Tato položka musí být pro rok \"{date_field}\" unikátní."
msgid "Invalid version in \"Accept\" header."
msgstr "Chybné číslo verze v hlavičce Accept."
#: versioning.py:73 versioning.py:115
#: versioning.py:73
msgid "Invalid version in URL path."
msgstr "Chybné číslo verze v odkazu."
#: versioning.py:144
#: versioning.py:115
msgid "Invalid version in URL path. Does not match any version namespace."
msgstr ""
#: versioning.py:147
msgid "Invalid version in hostname."
msgstr "Chybné číslo verze v hostname."
#: versioning.py:166
#: versioning.py:169
msgid "Invalid version in query parameter."
msgstr "Chybné čislo verze v URL parametru."

View File

@ -3,15 +3,15 @@
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Mads Jensen <mje@inducks.org>, 2015
# Mads Jensen <mje@inducks.org>, 2015-2016
# Mikkel Munch Mortensen <3xm@detfalskested.dk>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-12-07 18:53+0100\n"
"PO-Revision-Date: 2015-12-07 17:55+0000\n"
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"POT-Creation-Date: 2016-07-12 16:13+0100\n"
"PO-Revision-Date: 2016-07-12 15:14+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
"Language-Team: Danish (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -19,43 +19,75 @@ msgstr ""
"Language: da\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentication.py:72
#: authentication.py:73
msgid "Invalid basic header. No credentials provided."
msgstr "Ugyldig basic header. Ingen legitimation angivet."
#: authentication.py:75
#: authentication.py:76
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr "Ugyldig basic header. Legitimationsstrenge må ikke indeholde mellemrum."
#: authentication.py:81
#: authentication.py:82
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr "Ugyldig basic header. Legitimationen er ikke base64 encoded på korrekt vis."
#: authentication.py:98
#: authentication.py:99
msgid "Invalid username/password."
msgstr "Ugyldigt brugernavn/kodeord."
#: authentication.py:101 authentication.py:188
#: authentication.py:102 authentication.py:198
msgid "User inactive or deleted."
msgstr "Inaktiv eller slettet bruger."
#: authentication.py:167
#: authentication.py:176
msgid "Invalid token header. No credentials provided."
msgstr "Ugyldig token header."
#: authentication.py:170
#: authentication.py:179
msgid "Invalid token header. Token string should not contain spaces."
msgstr "Ugyldig token header. Token-strenge må ikke indeholde mellemrum."
#: authentication.py:176
#: authentication.py:185
msgid ""
"Invalid token header. Token string should not contain invalid characters."
msgstr "Ugyldig token header. Token streng bør ikke indeholde ugyldige karakterer."
#: authentication.py:185
#: authentication.py:195
msgid "Invalid token."
msgstr "Ugyldigt token."
#: authtoken/apps.py:7
msgid "Auth Token"
msgstr ""
#: authtoken/models.py:15
msgid "Key"
msgstr ""
#: authtoken/models.py:18
msgid "User"
msgstr ""
#: authtoken/models.py:20
msgid "Created"
msgstr ""
#: authtoken/models.py:29
msgid "Token"
msgstr ""
#: authtoken/models.py:30
msgid "Tokens"
msgstr ""
#: authtoken/serializers.py:8
msgid "Username"
msgstr ""
#: authtoken/serializers.py:9
msgid "Password"
msgstr ""
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr "Brugerkontoen er deaktiveret."
@ -93,7 +125,6 @@ msgid "Not found."
msgstr "Ikke fundet."
#: exceptions.py:109
#, python-brace-format
msgid "Method \"{method}\" not allowed."
msgstr "Metoden \"{method}\" er ikke tilladt."
@ -102,7 +133,6 @@ msgid "Could not satisfy the request Accept header."
msgstr "Kunne ikke efterkomme forespørgslens Accept header."
#: exceptions.py:132
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request."
msgstr "Forespørgslens media type, \"{media_type}\", er ikke understøttet."
@ -110,266 +140,249 @@ msgstr "Forespørgslens media type, \"{media_type}\", er ikke understøttet."
msgid "Request was throttled."
msgstr "Forespørgslen blev neddroslet."
#: fields.py:266 relations.py:195 relations.py:228 validators.py:79
#: validators.py:162
#: fields.py:269 relations.py:206 relations.py:239 validators.py:98
#: validators.py:181
msgid "This field is required."
msgstr "Dette felt er påkrævet."
#: fields.py:267
#: fields.py:270
msgid "This field may not be null."
msgstr "Dette felt må ikke være null."
#: fields.py:603 fields.py:634
#, python-brace-format
#: fields.py:608 fields.py:639
msgid "\"{input}\" is not a valid boolean."
msgstr "\"{input}\" er ikke en tilladt boolsk værdi."
#: fields.py:669
#: fields.py:674
msgid "This field may not be blank."
msgstr "Dette felt må ikke være tomt."
#: fields.py:670 fields.py:1656
#, python-brace-format
#: fields.py:675 fields.py:1675
msgid "Ensure this field has no more than {max_length} characters."
msgstr "Tjek at dette felt ikke indeholder flere end {max_length} tegn."
#: fields.py:671
#, python-brace-format
#: fields.py:676
msgid "Ensure this field has at least {min_length} characters."
msgstr "Tjek at dette felt indeholder mindst {min_length} tegn."
#: fields.py:708
#: fields.py:713
msgid "Enter a valid email address."
msgstr "Angiv en gyldig e-mailadresse."
#: fields.py:719
#: fields.py:724
msgid "This value does not match the required pattern."
msgstr "Denne værdi passer ikke med det påkrævede mønster."
#: fields.py:730
#: fields.py:735
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr "Indtast en gyldig \"slug\", bestående af bogstaver, tal, bund- og bindestreger."
#: fields.py:742
#: fields.py:747
msgid "Enter a valid URL."
msgstr "Indtast en gyldig URL."
#: fields.py:755
#, python-brace-format
#: fields.py:760
msgid "\"{value}\" is not a valid UUID."
msgstr "\"{value}\" er ikke et gyldigt UUID."
#: fields.py:791
#: fields.py:796
msgid "Enter a valid IPv4 or IPv6 address."
msgstr "Indtast en gyldig IPv4 eller IPv6 adresse."
#: fields.py:816
#: fields.py:821
msgid "A valid integer is required."
msgstr "Et gyldigt heltal er påkrævet."
#: fields.py:817 fields.py:852 fields.py:885
#, python-brace-format
#: fields.py:822 fields.py:857 fields.py:891
msgid "Ensure this value is less than or equal to {max_value}."
msgstr "Tjek at værdien er mindre end eller lig med {max_value}."
#: fields.py:818 fields.py:853 fields.py:886
#, python-brace-format
#: fields.py:823 fields.py:858 fields.py:892
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "Tjek at værdien er større end eller lig med {min_value}."
#: fields.py:819 fields.py:854 fields.py:890
#: fields.py:824 fields.py:859 fields.py:896
msgid "String value too large."
msgstr "Strengværdien er for stor."
#: fields.py:851 fields.py:884
#: fields.py:856 fields.py:890
msgid "A valid number is required."
msgstr "Et gyldigt tal er påkrævet."
#: fields.py:887
#, python-brace-format
#: fields.py:893
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "Tjek at der ikke er flere end {max_digits} cifre i alt."
#: fields.py:888
#, python-brace-format
#: fields.py:894
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr "Tjek at der ikke er flere end {max_decimal_places} cifre efter kommaet."
#: fields.py:889
#, python-brace-format
#: fields.py:895
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr "Tjek at der ikke er flere end {max_whole_digits} cifre før kommaet."
#: fields.py:1004
#, python-brace-format
#: fields.py:1025
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr "Datotid har et forkert format. Brug i stedet et af disse formater: {format}."
#: fields.py:1005
#: fields.py:1026
msgid "Expected a datetime but got a date."
msgstr "Forventede en datotid, men fik en dato."
#: fields.py:1079
#, python-brace-format
#: fields.py:1103
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr "Dato har et forkert format. Brug i stedet et af disse formater: {format}."
#: fields.py:1080
#: fields.py:1104
msgid "Expected a date but got a datetime."
msgstr "Forventede en dato men fik en datotid."
#: fields.py:1148
#, python-brace-format
#: fields.py:1170
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "Klokkeslæt har forkert format. Brug i stedet et af disse formater: {format}. "
#: fields.py:1207
#, python-brace-format
#: fields.py:1232
msgid "Duration has wrong format. Use one of these formats instead: {format}."
msgstr "Varighed har forkert format. Brug istedet et af følgende formater: {format}."
#: fields.py:1232 fields.py:1281
#, python-brace-format
#: fields.py:1251 fields.py:1300
msgid "\"{input}\" is not a valid choice."
msgstr "\"{input}\" er ikke et gyldigt valg."
#: fields.py:1235 relations.py:62 relations.py:431
#, python-brace-format
#: fields.py:1254 relations.py:71 relations.py:441
msgid "More than {count} items..."
msgstr "Flere end {count} objekter..."
#: fields.py:1282 fields.py:1429 relations.py:427 serializers.py:520
#, python-brace-format
#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr "Forventede en liste, men fik noget af typen \"{input_type}\"."
#: fields.py:1283
#: fields.py:1302
msgid "This selection may not be empty."
msgstr "Dette valg kan være tomt."
#: fields.py:1320
#, python-brace-format
#: fields.py:1339
msgid "\"{input}\" is not a valid path choice."
msgstr "\"{input}\" er ikke et gyldigt valg af adresse."
#: fields.py:1339
#: fields.py:1358
msgid "No file was submitted."
msgstr "Ingen medsendt fil."
#: fields.py:1340
#: fields.py:1359
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr "Det medsendte data var ikke en fil. Tjek typen af indkodning på formularen."
#: fields.py:1341
#: fields.py:1360
msgid "No filename could be determined."
msgstr "Filnavnet kunne ikke afgøres."
#: fields.py:1342
#: fields.py:1361
msgid "The submitted file is empty."
msgstr "Den medsendte fil er tom."
#: fields.py:1343
#, python-brace-format
#: fields.py:1362
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr "Sørg for at filnavnet er højst {max_length} langt (det er {length})."
#: fields.py:1391
#: fields.py:1410
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr "Medsend et gyldigt billede. Den medsendte fil var enten ikke et billede eller billedfilen var ødelagt."
#: fields.py:1430 relations.py:428 serializers.py:521
#: fields.py:1449 relations.py:438 serializers.py:525
msgid "This list may not be empty."
msgstr "Denne liste er muligvis ikke tom."
#: fields.py:1483
#, python-brace-format
#: fields.py:1502
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr "Forventede en dictionary, men fik noget af typen \"{input_type}\"."
#: fields.py:1530
#: fields.py:1549
msgid "Value must be valid JSON."
msgstr ""
msgstr "Værdi skal være gyldig JSON."
#: filters.py:35 templates/rest_framework/filters/django_filter.html:5
#: filters.py:36 templates/rest_framework/filters/django_filter.html:5
msgid "Submit"
msgstr "Indsend."
#: filters.py:336
msgid "ascending"
msgstr ""
#: pagination.py:189
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}."
msgstr "Ugyldig side \"{page_number}\": {message}."
#: filters.py:337
msgid "descending"
msgstr ""
#: pagination.py:407
#: pagination.py:193
msgid "Invalid page."
msgstr ""
#: pagination.py:427
msgid "Invalid cursor"
msgstr "Ugyldig cursor"
#: relations.py:196
#, python-brace-format
#: relations.py:207
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr "Ugyldig primærnøgle \"{pk_value}\" - objektet findes ikke."
#: relations.py:197
#, python-brace-format
#: relations.py:208
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr "Ugyldig type. Forventet værdi er primærnøgle, fik {data_type}."
#: relations.py:229
#: relations.py:240
msgid "Invalid hyperlink - No URL match."
msgstr "Ugyldigt hyperlink - intet URL match."
#: relations.py:230
#: relations.py:241
msgid "Invalid hyperlink - Incorrect URL match."
msgstr "Ugyldigt hyperlink - forkert URL match."
#: relations.py:231
#: relations.py:242
msgid "Invalid hyperlink - Object does not exist."
msgstr "Ugyldigt hyperlink - objektet findes ikke."
#: relations.py:232
#, python-brace-format
#: relations.py:243
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr "Forkert type. Forventede en URL-streng, fik {data_type}."
#: relations.py:391
#, python-brace-format
#: relations.py:401
msgid "Object with {slug_name}={value} does not exist."
msgstr "Object med {slug_name}={value} findes ikke."
#: relations.py:392
#: relations.py:402
msgid "Invalid value."
msgstr "Ugyldig værdi."
#: serializers.py:326
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr "Ugyldig data. Forventede en dictionary, men fik {datatype}."
#: templates/rest_framework/admin.html:118
#: templates/rest_framework/admin.html:116
#: templates/rest_framework/base.html:128
msgid "Filters"
msgstr ""
msgstr "Filtre"
#: templates/rest_framework/filters/django_filter.html:2
#: templates/rest_framework/filters/django_filter_crispyforms.html:4
msgid "Field filters"
msgstr ""
msgstr "Søgefiltre"
#: templates/rest_framework/filters/ordering.html:3
msgid "Ordering"
msgstr ""
msgstr "Sortering"
#: templates/rest_framework/filters/search.html:2
msgid "Search"
msgstr ""
msgstr "Søg"
#: templates/rest_framework/horizontal/radio.html:2
#: templates/rest_framework/inline/radio.html:2
@ -383,27 +396,23 @@ msgstr "Ingen"
msgid "No items to select."
msgstr "Intet at vælge."
#: validators.py:24
#: validators.py:43
msgid "This field must be unique."
msgstr "Dette felt skal være unikt."
#: validators.py:78
#, python-brace-format
#: validators.py:97
msgid "The fields {field_names} must make a unique set."
msgstr "Felterne {field_names} skal udgøre et unikt sæt."
#: validators.py:226
#, python-brace-format
#: validators.py:245
msgid "This field must be unique for the \"{date_field}\" date."
msgstr "Dette felt skal være unikt for \"{date_field}\"-datoen."
#: validators.py:241
#, python-brace-format
#: validators.py:260
msgid "This field must be unique for the \"{date_field}\" month."
msgstr "Dette felt skal være unikt for \"{date_field}\"-måneden."
#: validators.py:254
#, python-brace-format
#: validators.py:273
msgid "This field must be unique for the \"{date_field}\" year."
msgstr "Dette felt skal være unikt for \"{date_field}\"-året."
@ -411,15 +420,19 @@ msgstr "Dette felt skal være unikt for \"{date_field}\"-året."
msgid "Invalid version in \"Accept\" header."
msgstr "Ugyldig version i \"Accept\" headeren."
#: versioning.py:73 versioning.py:115
#: versioning.py:73
msgid "Invalid version in URL path."
msgstr "Ugyldig version i URL-stien."
#: versioning.py:144
#: versioning.py:115
msgid "Invalid version in URL path. Does not match any version namespace."
msgstr ""
#: versioning.py:147
msgid "Invalid version in hostname."
msgstr "Ugyldig version i hostname."
#: versioning.py:166
#: versioning.py:169
msgid "Invalid version in query parameter."
msgstr "Ugyldig version i forespørgselsparameteren."

Some files were not shown because too many files have changed in this diff Show More