Commit de72022f by Aider Ibragimov

Merge branch 'master' of https://github.com/Ins1ne/django-rest-framework into feature/ipaddress

parents 313b3d7c 33c4278e
...@@ -21,10 +21,10 @@ env: ...@@ -21,10 +21,10 @@ env:
- TOX_ENV=py26-django15 - TOX_ENV=py26-django15
- TOX_ENV=py27-django14 - TOX_ENV=py27-django14
- TOX_ENV=py26-django14 - TOX_ENV=py26-django14
- TOX_ENV=py34-django18alpha - TOX_ENV=py34-django18beta
- TOX_ENV=py33-django18alpha - TOX_ENV=py33-django18beta
- TOX_ENV=py32-django18alpha - TOX_ENV=py32-django18beta
- TOX_ENV=py27-django18alpha - TOX_ENV=py27-django18beta
install: install:
- pip install tox - pip install tox
......
[main]
host = https://www.transifex.com
[django-rest-framework.djangopo]
file_filter = rest_framework/locale/<lang>/LC_MESSAGES/django.po
source_file = rest_framework/locale/en_US/LC_MESSAGES/django.po
source_lang = en_US
type = PO
...@@ -10,9 +10,9 @@ There are many ways you can contribute to Django REST framework. We'd like it t ...@@ -10,9 +10,9 @@ There are many ways you can contribute to Django REST framework. We'd like it t
The most important thing you can do to help push the REST framework project forward is to be actively involved wherever possible. Code contributions are often overvalued as being the primary way to get involved in a project, we don't believe that needs to be the case. The most important thing you can do to help push the REST framework project forward is to be actively involved wherever possible. Code contributions are often overvalued as being the primary way to get involved in a project, we don't believe that needs to be the case.
If you use REST framework, we'd love you to be vocal about your experiences with it - you might consider writing a blog post about using REST framework, or publishing a tutorial about building a project with a particular Javascript framework. Experiences from beginners can be particularly helpful because you'll be in the best position to assess which bits of REST framework are more difficult to understand and work with. If you use REST framework, we'd love you to be vocal about your experiences with it - you might consider writing a blog post about using REST framework, or publishing a tutorial about building a project with a particular JavaScript framework. Experiences from beginners can be particularly helpful because you'll be in the best position to assess which bits of REST framework are more difficult to understand and work with.
Other really great ways you can help move the community forward include helping answer questions on the [discussion group][google-group], or setting up an [email alert on StackOverflow][so-filter] so that you get notified of any new questions with the `django-rest-framework` tag. Other really great ways you can help move the community forward include helping to answer questions on the [discussion group][google-group], or setting up an [email alert on StackOverflow][so-filter] so that you get notified of any new questions with the `django-rest-framework` tag.
When answering questions make sure to help future contributors find their way around by hyperlinking wherever possible to related threads and tickets, and include backlinks from those items if relevant. When answering questions make sure to help future contributors find their way around by hyperlinking wherever possible to related threads and tickets, and include backlinks from those items if relevant.
...@@ -52,7 +52,7 @@ To start developing on Django REST framework, clone the repo: ...@@ -52,7 +52,7 @@ To start developing on Django REST framework, clone the repo:
git clone git@github.com:tomchristie/django-rest-framework.git git clone git@github.com:tomchristie/django-rest-framework.git
Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you setup your editor to automatically indicated non-conforming styles. Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you set up your editor to automatically indicate non-conforming styles.
## Testing ## Testing
...@@ -60,13 +60,47 @@ To run the tests, clone the repository, and then: ...@@ -60,13 +60,47 @@ To run the tests, clone the repository, and then:
# Setup the virtual environment # Setup the virtual environment
virtualenv env virtualenv env
env/bin/activate source env/bin/activate
pip install -r requirements.txt pip install -r requirements.txt
# Run the tests # Run the tests
./runtests.py ./runtests.py
You can also use the excellent [`tox`][tox] testing tool to run the tests against all supported versions of Python and Django. Install `tox` globally, and then simply run: ### Test options
Run using a more concise output style.
./runtests.py -q
Run the tests using a more concise output style, no coverage, no flake8.
./runtests.py --fast
Don't run the flake8 code linting.
./runtests.py --nolint
Only run the flake8 code linting, don't run the tests.
./runtests.py --lintonly
Run the tests for a given test case.
./runtests.py MyTestCase
Run the tests for a given test method.
./runtests.py MyTestCase.test_this_method
Shorter form to run the tests for a given test method.
./runtests.py test_this_method
Note: The test case and test method matching is fuzzy and will sometimes run other tests that contain a partial string match to the given command line input.
### Running against multiple environments
You can also use the excellent [tox][tox] testing tool to run the tests against all supported versions of Python and Django. Install `tox` globally, and then simply run:
tox tox
...@@ -82,7 +116,7 @@ GitHub's documentation for working on pull requests is [available here][pull-req ...@@ -82,7 +116,7 @@ GitHub's documentation for working on pull requests is [available here][pull-req
Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible with both Python 2 and Python 3, and that they run properly on all supported versions of Django. Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible with both Python 2 and Python 3, and that they run properly on all supported versions of Django.
Once you've made a pull request take a look at the travis build status in the GitHub interface and make sure the tests are running as you'd expect. Once you've made a pull request take a look at the Travis build status in the GitHub interface and make sure the tests are running as you'd expect.
![Travis status][travis-status] ![Travis status][travis-status]
...@@ -96,7 +130,7 @@ Sometimes, in order to ensure your code works on various different versions of D ...@@ -96,7 +130,7 @@ Sometimes, in order to ensure your code works on various different versions of D
The documentation for REST framework is built from the [Markdown][markdown] source files in [the docs directory][docs]. The documentation for REST framework is built from the [Markdown][markdown] source files in [the docs directory][docs].
There are many great markdown editors that make working with the documentation really easy. The [Mou editor for Mac][mou] is one such editor that comes highly recommended. There are many great Markdown editors that make working with the documentation really easy. The [Mou editor for Mac][mou] is one such editor that comes highly recommended.
## Building the documentation ## Building the documentation
...@@ -104,7 +138,7 @@ To build the documentation, install MkDocs with `pip install mkdocs` and then ru ...@@ -104,7 +138,7 @@ To build the documentation, install MkDocs with `pip install mkdocs` and then ru
mkdocs build mkdocs build
This will build the html output into the `html` directory. This will build the documentation into the `site` directory.
You can build the documentation and open a preview in a browser window by using the `serve` command. You can build the documentation and open a preview in a browser window by using the `serve` command.
...@@ -117,8 +151,7 @@ Documentation should be in American English. The tone of the documentation is v ...@@ -117,8 +151,7 @@ Documentation should be in American English. The tone of the documentation is v
Some other tips: Some other tips:
* Keep paragraphs reasonably short. * Keep paragraphs reasonably short.
* Use double spacing after the end of sentences. * Don't use abbreviations such as 'e.g.' but instead use the long form, such as 'For example'.
* Don't use the abbreviations such as 'e.g.' but instead use long form, such as 'For example'.
## Markdown style ## Markdown style
...@@ -151,7 +184,7 @@ If you are hyperlinking to another REST framework document, you should use a rel ...@@ -151,7 +184,7 @@ If you are hyperlinking to another REST framework document, you should use a rel
[authentication]: ../api-guide/authentication.md [authentication]: ../api-guide/authentication.md
Linking in this style means you'll be able to click the hyperlink in your markdown editor to open the referenced document. When the documentation is built, these links will be converted into regular links to HTML pages. Linking in this style means you'll be able to click the hyperlink in your Markdown editor to open the referenced document. When the documentation is built, these links will be converted into regular links to HTML pages.
##### 3. Notes ##### 3. Notes
...@@ -163,19 +196,6 @@ If you want to draw attention to a note or warning, use a pair of enclosing line ...@@ -163,19 +196,6 @@ If you want to draw attention to a note or warning, use a pair of enclosing line
--- ---
# Third party packages
New features to REST framework are generally recommended to be implemented as third party libraries that are developed outside of the core framework. Ideally third party libraries should be properly documented and packaged, and made available on PyPI.
## Getting started
If you have some functionality that you would like to implement as a third party package it's worth contacting the [discussion group][google-group] as others may be willing to get involved. We strongly encourage third party package development and will always try to prioritize time spent helping their development, documentation and packaging.
We recommend the [`django-reusable-app`][django-reusable-app] template as a good resource for getting up and running with implementing a third party Django package.
## Linking to your package
Once your package is decently documented and available on PyPI open a pull request or issue, and we'll add a link to it from the main REST framework documentation.
[cite]: http://www.w3.org/People/Berners-Lee/FAQ.html [cite]: http://www.w3.org/People/Berners-Lee/FAQ.html
[code-of-conduct]: https://www.djangoproject.com/conduct/ [code-of-conduct]: https://www.djangoproject.com/conduct/
...@@ -183,10 +203,9 @@ Once your package is decently documented and available on PyPI open a pull reque ...@@ -183,10 +203,9 @@ Once your package is decently documented and available on PyPI open a pull reque
[so-filter]: http://stackexchange.com/filters/66475/rest-framework [so-filter]: http://stackexchange.com/filters/66475/rest-framework
[issues]: https://github.com/tomchristie/django-rest-framework/issues?state=open [issues]: https://github.com/tomchristie/django-rest-framework/issues?state=open
[pep-8]: http://www.python.org/dev/peps/pep-0008/ [pep-8]: http://www.python.org/dev/peps/pep-0008/
[travis-status]: https://raw.github.com/tomchristie/django-rest-framework/master/docs/img/travis-status.png [travis-status]: ../img/travis-status.png
[pull-requests]: https://help.github.com/articles/using-pull-requests [pull-requests]: https://help.github.com/articles/using-pull-requests
[tox]: http://tox.readthedocs.org/en/latest/ [tox]: http://tox.readthedocs.org/en/latest/
[markdown]: http://daringfireball.net/projects/markdown/basics [markdown]: http://daringfireball.net/projects/markdown/basics
[docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs [docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs
[mou]: http://mouapp.com/ [mou]: http://mouapp.com/
[django-reusable-app]: https://github.com/dabapps/django-reusable-app
...@@ -20,7 +20,7 @@ Django REST framework is a powerful and flexible toolkit for building Web APIs. ...@@ -20,7 +20,7 @@ Django REST framework is a powerful and flexible toolkit for building Web APIs.
Some reasons you might want to use REST framework: Some reasons you might want to use REST framework:
* The [Web browsable API][sandbox] is a huge usability win for your developers. * The [Web browsable API][sandbox] is a huge usability win for your developers.
* [Authentication policies][authentication] including [OAuth1a][oauth1-section] and [OAuth2][oauth2-section] out of the box. * [Authentication policies][authentication] including optional packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources. * [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]. * 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]. * [Extensive documentation][index], and [great community support][group].
...@@ -34,7 +34,7 @@ There is a live example API for testing purposes, [available here][sandbox]. ...@@ -34,7 +34,7 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements # Requirements
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4) * Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)
* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7, 1.8-alpha) * Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7, 1.8-beta)
# Installation # Installation
...@@ -53,7 +53,7 @@ Add `'rest_framework'` to your `INSTALLED_APPS` setting. ...@@ -53,7 +53,7 @@ Add `'rest_framework'` to your `INSTALLED_APPS` setting.
Let's take a look at a quick example of using REST framework to build a simple model-backed API for accessing users and groups. Let's take a look at a quick example of using REST framework to build a simple model-backed API for accessing users and groups.
Startup up a new project like so... Startup up a new project like so...
pip install django pip install django
pip install djangorestframework pip install djangorestframework
...@@ -79,7 +79,7 @@ class UserViewSet(viewsets.ModelViewSet): ...@@ -79,7 +79,7 @@ class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserSerializer serializer_class = UserSerializer
# Routers provide a way of automatically determining the URL conf. # Routers provide a way of automatically determining the URL conf.
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register(r'users', UserViewSet) router.register(r'users', UserViewSet)
...@@ -100,7 +100,7 @@ Add the following to your `settings.py` module: ...@@ -100,7 +100,7 @@ Add the following to your `settings.py` module:
```python ```python
INSTALLED_APPS = ( INSTALLED_APPS = (
... # Make sure to include the default installed apps here. ... # Make sure to include the default installed apps here.
'rest_framework', 'rest_framework',
) )
REST_FRAMEWORK = { REST_FRAMEWORK = {
...@@ -123,10 +123,10 @@ You can also interact with the API using command line tools such as [`curl`](htt ...@@ -123,10 +123,10 @@ You can also interact with the API using command line tools such as [`curl`](htt
$ curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/ $ curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/
[ [
{ {
"url": "http://127.0.0.1:8000/users/1/", "url": "http://127.0.0.1:8000/users/1/",
"username": "admin", "username": "admin",
"email": "admin@example.com", "email": "admin@example.com",
"is_staff": true, "is_staff": true,
} }
] ]
...@@ -134,10 +134,10 @@ Or to create a new user: ...@@ -134,10 +134,10 @@ Or to create a new user:
$ curl -X POST -d username=new -d email=new@example.com -d is_staff=false -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/ $ curl -X POST -d username=new -d email=new@example.com -d is_staff=false -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/
{ {
"url": "http://127.0.0.1:8000/users/2/", "url": "http://127.0.0.1:8000/users/2/",
"username": "new", "username": "new",
"email": "new@example.com", "email": "new@example.com",
"is_staff": false, "is_staff": false,
} }
# Documentation & Support # Documentation & Support
...@@ -159,24 +159,24 @@ Send a description of the issue via email to [rest-framework-security@googlegrou ...@@ -159,24 +159,24 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
Copyright (c) 2011-2015, Tom Christie Copyright (c) 2011-2015, Tom Christie
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution. other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 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. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
...@@ -190,8 +190,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -190,8 +190,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[sandbox]: http://restframework.herokuapp.com/ [sandbox]: http://restframework.herokuapp.com/
[index]: http://www.django-rest-framework.org/ [index]: http://www.django-rest-framework.org/
[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#oauthauthentication [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/#oauth2authentication [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 [serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers
[modelserializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#modelserializer [modelserializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#modelserializer
[functionview-section]: http://www.django-rest-framework.org/api-guide/views/#function-based-views [functionview-section]: http://www.django-rest-framework.org/api-guide/views/#function-based-views
...@@ -200,8 +200,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -200,8 +200,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[routers]: http://www.django-rest-framework.org/api-guide/routers/ [routers]: http://www.django-rest-framework.org/api-guide/routers/
[serializers]: http://www.django-rest-framework.org/api-guide/serializers/ [serializers]: http://www.django-rest-framework.org/api-guide/serializers/
[authentication]: http://www.django-rest-framework.org/api-guide/authentication/ [authentication]: http://www.django-rest-framework.org/api-guide/authentication/
[rest-framework-2-announcement]: http://www.django-rest-framework.org/topics/rest-framework-2-announcement/
[rest-framework-2-announcement]: http://www.django-rest-framework.org/topics/rest-framework-2-announcement
[2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion [2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion
[image]: http://www.django-rest-framework.org/img/quickstart.png [image]: http://www.django-rest-framework.org/img/quickstart.png
...@@ -214,7 +213,5 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -214,7 +213,5 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[docs]: http://www.django-rest-framework.org/ [docs]: http://www.django-rest-framework.org/
[urlobject]: https://github.com/zacharyvoase/urlobject [urlobject]: https://github.com/zacharyvoase/urlobject
[markdown]: http://pypi.python.org/pypi/Markdown/ [markdown]: http://pypi.python.org/pypi/Markdown/
[pyyaml]: http://pypi.python.org/pypi/PyYAML
[defusedxml]: https://pypi.python.org/pypi/defusedxml
[django-filter]: http://pypi.python.org/pypi/django-filter [django-filter]: http://pypi.python.org/pypi/django-filter
[security-mail]: mailto:rest-framework-security@googlegroups.com [security-mail]: mailto:rest-framework-security@googlegroups.com
...@@ -47,7 +47,7 @@ Any example validation error might look like this: ...@@ -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. 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 single argument, which is the exception to be handled, and 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, 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.
For example, you might want to ensure that all error responses include the HTTP status code in the body of the response, like so: For example, you might want to ensure that all error responses include the HTTP status code in the body of the response, like so:
...@@ -61,10 +61,10 @@ In order to alter the style of the response, you could write the following custo ...@@ -61,10 +61,10 @@ In order to alter the style of the response, you could write the following custo
from rest_framework.views import exception_handler from rest_framework.views import exception_handler
def custom_exception_handler(exc): def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first, # Call REST framework's default exception handler first,
# to get the standard error response. # to get the standard error response.
response = exception_handler(exc) response = exception_handler(exc, context)
# Now add the HTTP status code to the response. # Now add the HTTP status code to the response.
if response is not None: if response is not None:
...@@ -72,6 +72,8 @@ In order to alter the style of the response, you could write the following custo ...@@ -72,6 +72,8 @@ In order to alter the style of the response, you could write the following custo
return response return response
The context argument is not used by the default handler, but can be useful if the exception handler needs further information such as the view currently being handled, which can be accessed as `context['view']`.
The exception handler must also be configured in your settings, using the `EXCEPTION_HANDLER` setting key. For example: The exception handler must also be configured in your settings, using the `EXCEPTION_HANDLER` setting key. For example:
REST_FRAMEWORK = { REST_FRAMEWORK = {
...@@ -138,6 +140,14 @@ Raised when an authenticated request fails the permission checks. ...@@ -138,6 +140,14 @@ Raised when an authenticated request fails the permission checks.
By default this exception results in a response with the HTTP status code "403 Forbidden". By default this exception results in a response with the HTTP status code "403 Forbidden".
## NotFound
**Signature:** `NotFound(detail=None)`
Raised when a resource does not exists at the given URL. This exception is equivalent to the standard `Http404` Django exception.
By default this exception results in a response with the HTTP status code "404 Not Found".
## MethodNotAllowed ## MethodNotAllowed
**Signature:** `MethodNotAllowed(method, detail=None)` **Signature:** `MethodNotAllowed(method, detail=None)`
...@@ -146,6 +156,14 @@ Raised when an incoming request occurs that does not map to a handler method on ...@@ -146,6 +156,14 @@ Raised when an incoming request occurs that does not map to a handler method on
By default this exception results in a response with the HTTP status code "405 Method Not Allowed". By default this exception results in a response with the HTTP status code "405 Method Not Allowed".
## NotAcceptable
**Signature:** `NotAcceptable(detail=None)`
Raised when an incoming request occurs with an `Accept` header that cannot be satisfied by any of the available renderers.
By default this exception results in a response with the HTTP status code "406 Not Acceptable".
## UnsupportedMediaType ## UnsupportedMediaType
**Signature:** `UnsupportedMediaType(media_type, detail=None)` **Signature:** `UnsupportedMediaType(media_type, detail=None)`
......
...@@ -138,11 +138,12 @@ A text representation. Optionally validates the text to be shorter than `max_len ...@@ -138,11 +138,12 @@ A text representation. Optionally validates the text to be shorter than `max_len
Corresponds to `django.db.models.fields.CharField` or `django.db.models.fields.TextField`. Corresponds to `django.db.models.fields.CharField` or `django.db.models.fields.TextField`.
**Signature:** `CharField(max_length=None, min_length=None, allow_blank=False)` **Signature:** `CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)`
- `max_length` - Validates that the input contains no more than this number of characters. - `max_length` - Validates that the input contains no more than this number of characters.
- `min_length` - Validates that the input contains no fewer than this number of characters. - `min_length` - Validates that the input contains no fewer than this number of characters.
- `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`. - `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`.
- `trim_whitespace` - If set to `True` then leading and trailing whitespace is trimmed. Defaults to `True`.
The `allow_null` option is also available for string fields, although its usage is discouraged in favor of `allow_blank`. It is valid to set both `allow_blank=True` and `allow_null=True`, but doing so means that there will be two differing types of empty value permissible for string representations, which can lead to data inconsistencies and subtle application bugs. The `allow_null` option is also available for string fields, although its usage is discouraged in favor of `allow_blank`. It is valid to set both `allow_blank=True` and `allow_null=True`, but doing so means that there will be two differing types of empty value permissible for string representations, which can lead to data inconsistencies and subtle application bugs.
......
...@@ -26,26 +26,26 @@ As an example, if you are sending `json` encoded data using jQuery with the [.aj ...@@ -26,26 +26,26 @@ As an example, if you are sending `json` encoded data using jQuery with the [.aj
## Setting the parsers ## Setting the parsers
The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting. For example, the following settings would allow requests with `YAML` content. The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting. For example, the following settings would allow only requests with `JSON` content, instead of the default of JSON or form data.
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': ( 'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.YAMLParser', 'rest_framework.parsers.JSONParser',
) )
} }
You can also set the parsers used for an individual view, or viewset, 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 YAMLParser from rest_framework.parsers import JSONParser
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
class ExampleView(APIView): class ExampleView(APIView):
""" """
A view that can accept POST requests with YAML content. A view that can accept POST requests with JSON content.
""" """
parser_classes = (YAMLParser,) parser_classes = (JSONParser,)
def post(self, request, format=None): def post(self, request, format=None):
return Response({'received data': request.data}) return Response({'received data': request.data})
...@@ -53,10 +53,10 @@ using the `APIView` class based views. ...@@ -53,10 +53,10 @@ using the `APIView` class based views.
Or, if you're using the `@api_view` decorator with function based views. Or, if you're using the `@api_view` decorator with function based views.
@api_view(['POST']) @api_view(['POST'])
@parser_classes((YAMLParser,)) @parser_classes((JSONParser,))
def example_view(request, format=None): def example_view(request, format=None):
""" """
A view that can accept POST requests with YAML content. A view that can accept POST requests with JSON content.
""" """
return Response({'received data': request.data}) return Response({'received data': request.data})
...@@ -70,26 +70,6 @@ Parses `JSON` request content. ...@@ -70,26 +70,6 @@ Parses `JSON` request content.
**.media_type**: `application/json` **.media_type**: `application/json`
## YAMLParser
Parses `YAML` request content.
Requires the `pyyaml` package to be installed.
**.media_type**: `application/yaml`
## XMLParser
Parses REST framework's default style of `XML` request content.
Note that the `XML` markup language is typically used as the base language for more strictly defined domain-specific languages, such as `RSS`, `Atom`, and `XHTML`.
If you are considering using `XML` for your API, you may want to consider implementing a custom renderer and parser for your specific requirements, and using an existing domain-specific media-type, or creating your own custom XML-based media-type.
Requires the `defusedxml` package to be installed.
**.media_type**: `application/xml`
## FormParser ## FormParser
Parses HTML form content. `request.data` will be populated with a `QueryDict` of data. Parses HTML form content. `request.data` will be populated with a `QueryDict` of data.
...@@ -161,7 +141,7 @@ By default this will include the following keys: `view`, `request`, `args`, `kwa ...@@ -161,7 +141,7 @@ By default this will include the following keys: `view`, `request`, `args`, `kwa
## Example ## Example
The following is an example plaintext parser that will populate the `request.data` property with a string representing the body of the request. The following is an example plaintext parser that will populate the `request.data` property with a string representing the body of the request.
class PlainTextParser(BaseParser): class PlainTextParser(BaseParser):
""" """
...@@ -182,6 +162,48 @@ The following is an example plaintext parser that will populate the `request.dat ...@@ -182,6 +162,48 @@ The following is an example plaintext parser that will populate the `request.dat
The following third party packages are also available. The following third party packages are also available.
## YAML
[REST framework YAML][rest-framework-yaml] provides [YAML][yaml] parsing and rendering support. It was previously included directly in the REST framework package, and is now instead supported as a third-party package.
#### Installation & configuration
Install using pip.
$ pip install djangorestframework-yaml
Modify your REST framework settings.
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
'rest_framework_yaml.parsers.YAMLParser',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework_yaml.renderers.YAMLRenderer',
),
}
## XML
[REST Framework XML][rest-framework-xml] provides a simple informal XML format. It was previously included directly in the REST framework package, and is now instead supported as a third-party package.
#### Installation & configuration
Install using pip.
$ pip install djangorestframework-xml
Modify your REST framework settings.
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
'rest_framework_xml.parsers.XMLParser',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework_xml.renderers.XMLRenderer',
),
}
## MessagePack ## MessagePack
[MessagePack][messagepack] is a fast, efficient binary serialization format. [Juan Riaza][juanriaza] maintains the [djangorestframework-msgpack][djangorestframework-msgpack] package which provides MessagePack renderer and parser support for REST framework. [MessagePack][messagepack] is a fast, efficient binary serialization format. [Juan Riaza][juanriaza] maintains the [djangorestframework-msgpack][djangorestframework-msgpack] package which provides MessagePack renderer and parser support for REST framework.
...@@ -193,6 +215,9 @@ The following third party packages are also available. ...@@ -193,6 +215,9 @@ The following third party packages are also available.
[jquery-ajax]: http://api.jquery.com/jQuery.ajax/ [jquery-ajax]: http://api.jquery.com/jQuery.ajax/
[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion [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/dev/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/
[messagepack]: https://github.com/juanriaza/django-rest-framework-msgpack [messagepack]: https://github.com/juanriaza/django-rest-framework-msgpack
[juanriaza]: https://github.com/juanriaza [juanriaza]: https://github.com/juanriaza
[vbabiy]: https://github.com/vbabiy [vbabiy]: https://github.com/vbabiy
......
...@@ -166,21 +166,6 @@ As with `DjangoModelPermissions` you can use custom model permissions by overrid ...@@ -166,21 +166,6 @@ As with `DjangoModelPermissions` you can use custom model permissions by overrid
--- ---
## TokenHasReadWriteScope
This permission class is intended for use with either of the `OAuthAuthentication` and `OAuth2Authentication` classes, and ties into the scoping that their backends provide.
Requests with a safe methods of `GET`, `OPTIONS` or `HEAD` will be allowed if the authenticated token has read permission.
Requests for `POST`, `PUT`, `PATCH` and `DELETE` will be allowed if the authenticated token has write permission.
This permission class relies on the implementations of the [django-oauth-plus][django-oauth-plus] and [django-oauth2-provider][django-oauth2-provider] libraries, which both provide limited support for controlling the scope of access tokens:
* `django-oauth-plus`: Tokens are associated with a `Resource` class which has a `name`, `url` and `is_readonly` properties.
* `django-oauth2-provider`: Tokens are associated with a bitwise `scope` attribute, that defaults to providing bitwise values for `read` and/or `write`.
If you require more advanced scoping for your API, such as restricting tokens to accessing a subset of functionality of your API then you will need to provide a custom permission class. See the source of the `django-oauth-plus` or `django-oauth2-provider` package for more details on scoping token access.
--- ---
# Custom permissions # Custom permissions
...@@ -268,8 +253,6 @@ The [REST Condition][rest-condition] package is another extension for building c ...@@ -268,8 +253,6 @@ The [REST Condition][rest-condition] package is another extension for building c
[objectpermissions]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#handling-object-permissions [objectpermissions]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#handling-object-permissions
[guardian]: https://github.com/lukaszb/django-guardian [guardian]: https://github.com/lukaszb/django-guardian
[get_objects_for_user]: http://pythonhosted.org/django-guardian/api/guardian.shortcuts.html#get-objects-for-user [get_objects_for_user]: http://pythonhosted.org/django-guardian/api/guardian.shortcuts.html#get-objects-for-user
[django-oauth-plus]: http://code.larlet.fr/django-oauth-plus
[django-oauth2-provider]: https://github.com/caffeinehit/django-oauth2-provider
[2.2-announcement]: ../topics/2.2-announcement.md [2.2-announcement]: ../topics/2.2-announcement.md
[filtering]: filtering.md [filtering]: filtering.md
[drf-any-permissions]: https://github.com/kevin-brown/drf-any-permissions [drf-any-permissions]: https://github.com/kevin-brown/drf-any-permissions
......
...@@ -38,7 +38,7 @@ For clarity inside your code, we recommend using `request.query_params` instead ...@@ -38,7 +38,7 @@ For clarity inside your code, we recommend using `request.query_params` instead
## .DATA and .FILES ## .DATA and .FILES
The old-style version 2.x `request.data` and `request.FILES` attributes are still available, but are now pending deprecation in favor of the unified `request.data` attribute. The old-style version 2.x `request.DATA` and `request.FILES` attributes are still available, but are now pending deprecation in favor of the unified `request.data` attribute.
## .QUERY_PARAMS ## .QUERY_PARAMS
......
...@@ -457,7 +457,7 @@ To do so, open the Django shell, using `python manage.py shell`, then import the ...@@ -457,7 +457,7 @@ To do so, open the Django shell, using `python manage.py shell`, then import the
name = CharField(allow_blank=True, max_length=100, required=False) name = CharField(allow_blank=True, max_length=100, required=False)
owner = PrimaryKeyRelatedField(queryset=User.objects.all()) owner = PrimaryKeyRelatedField(queryset=User.objects.all())
## Specifying which fields should be included ## Specifying which fields to include
If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`. If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`.
...@@ -499,7 +499,7 @@ You can add extra fields to a `ModelSerializer` or override the default fields b ...@@ -499,7 +499,7 @@ You can add extra fields to a `ModelSerializer` or override the default fields b
Extra fields can correspond to any property or callable on the model. Extra fields can correspond to any property or callable on the model.
## Specifying which fields should be read-only ## Specifying read only fields
You may wish to specify multiple fields as read-only. Instead of adding each field explicitly with the `read_only=True` attribute, you may use the shortcut Meta option, `read_only_fields`. You may wish to specify multiple fields as read-only. Instead of adding each field explicitly with the `read_only=True` attribute, you may use the shortcut Meta option, `read_only_fields`.
...@@ -528,7 +528,7 @@ Please review the [Validators Documentation](/api-guide/validators/) for details ...@@ -528,7 +528,7 @@ Please review the [Validators Documentation](/api-guide/validators/) for details
--- ---
## Specifying additional keyword arguments for fields. ## Additional keyword arguments
There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. Similarly to `read_only_fields` this means you do not need to explicitly declare the field on the serializer. There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. Similarly to `read_only_fields` this means you do not need to explicitly declare the field on the serializer.
...@@ -567,6 +567,79 @@ The inner `Meta` class on serializers is not inherited from parent classes by de ...@@ -567,6 +567,79 @@ The inner `Meta` class on serializers is not inherited from parent classes by de
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly. 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.
Normally if a `ModelSerializer` does not generate the fields you need by default the you should either add them to the class explicitly, or simply use a regular `Serializer` class instead. However in some cases you may want to create a new base class that defines how the serializer fields are created for any given model.
### `.serializer_field_mapping`
A mapping of Django model classes to REST framework serializer classes. You can override this mapping to alter the default serializer classes that should be used for each model class.
### `.serializer_related_field`
This property should be the serializer field class, that is used for relational fields by default.
For `ModelSerializer` this defaults to `PrimaryKeyRelatedField`.
For `HyperlinkedModelSerializer` this defaults to `serializers.HyperlinkedRelatedField`.
### `serializer_url_field`
The serializer field class that should be used for any `url` field on the serializer.
Defaults to `serializers.HyperlinkedIdentityField`
### `serializer_choice_field`
The serializer field class that should be used for any choice fields on the serializer.
Defaults to `serializers.ChoiceField`
### The field_class and field_kwargs API
The following methods are called to determine the class and keyword arguments for each field that should be automatically included on the serializer. Each of these methods should return a two tuple of `(field_class, field_kwargs)`.
### `.build_standard_field(self, field_name, model_field)`
Called to generate a serializer field that maps to a standard model field.
The default implementation returns a serializer class based on the `serializer_field_mapping` attribute.
### `.build_relational_field(self, field_name, relation_info)`
Called to generate a serializer field that maps to a relational model field.
The default implementation returns a serializer class based on the `serializer_relational_field` attribute.
The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties.
### `.build_nested_field(self, field_name, relation_info, nested_depth)`
Called to generate a serializer field that maps to a relational model field, when the `depth` option has been set.
The default implementation dynamically creates a nested serializer class based on either `ModelSerializer` or `HyperlinkedModelSerializer`.
The `nested_depth` will be the value of the `depth` option, minus one.
The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties.
### `.build_property_field(self, field_name, model_class)`
Called to generate a serializer field that maps to a property or zero-argument method on the model class.
The default implementation returns a `ReadOnlyField` class.
### `.build_url_field(self, field_name, model_class)`
Called to generate a serializer field for the serializer's own `url` field. The default implementation returns a `HyperlinkedIdentityField` class.
### `.build_unknown_field(self, field_name, model_class)`
Called when the field name did not map to any model field or model property.
The default implementation raises an error, although subclasses may customize this behavior.
--- ---
# HyperlinkedModelSerializer # HyperlinkedModelSerializer
......
...@@ -12,10 +12,10 @@ For example your project's `settings.py` file might include something like this: ...@@ -12,10 +12,10 @@ For example your project's `settings.py` file might include something like this:
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( 'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.YAMLRenderer', 'rest_framework.renderers.JSONRenderer',
), ),
'DEFAULT_PARSER_CLASSES': ( 'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.YAMLParser', 'rest_framework.parsers.JSONParser',
) )
} }
...@@ -166,6 +166,28 @@ Default: `ordering` ...@@ -166,6 +166,28 @@ Default: `ordering`
--- ---
## Versioning settings
#### DEFAULT_VERSION
The value that should be used for `request.version` when no versioning information is present.
Default: `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.
Default: `None`
#### VERSION_PARAMETER
The string that should used for any versioning parameters, such as in the media type or URL query parameters.
Default: `'version'`
---
## Authentication settings ## Authentication settings
*The following settings control the behavior of unauthenticated requests.* *The following settings control the behavior of unauthenticated requests.*
...@@ -393,7 +415,7 @@ This setting can be changed to support error responses other than the default `{ ...@@ -393,7 +415,7 @@ This setting can be changed to support error responses other than the default `{
This should be a function with the following signature: This should be a function with the following signature:
exception_handler(exc) exception_handler(exc, context)
* `exc`: The exception. * `exc`: The exception.
......
...@@ -14,7 +14,7 @@ Extends [Django's existing `RequestFactory` class][requestfactory]. ...@@ -14,7 +14,7 @@ Extends [Django's existing `RequestFactory` class][requestfactory].
## Creating test requests ## Creating test requests
The `APIRequestFactory` class supports an almost identical API to Django's standard `RequestFactory` class. This means the that standard `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`, `.head()` and `.options()` methods are all available. The `APIRequestFactory` class supports an almost identical API to Django's standard `RequestFactory` class. This means that the standard `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`, `.head()` and `.options()` methods are all available.
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
...@@ -65,6 +65,8 @@ When testing views directly using a request factory, it's often convenient to be ...@@ -65,6 +65,8 @@ When testing views directly using a request factory, it's often convenient to be
To forcibly authenticate a request, use the `force_authenticate()` method. To forcibly authenticate a request, use the `force_authenticate()` method.
from rest_framework.tests import force_authenticate
factory = APIRequestFactory() factory = APIRequestFactory()
user = User.objects.get(username='olivia') user = User.objects.get(username='olivia')
view = AccountDetail.as_view() view = AccountDetail.as_view()
...@@ -113,7 +115,7 @@ Extends [Django's existing `Client` class][client]. ...@@ -113,7 +115,7 @@ Extends [Django's existing `Client` class][client].
## Making requests ## Making requests
The `APIClient` class supports the same request interface as `APIRequestFactory`. This means the that standard `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`, `.head()` and `.options()` methods are all available. For example: The `APIClient` class supports the same request interface as Django's standard `Client` class. This means the that standard `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`, `.head()` and `.options()` methods are all available. For example:
from rest_framework.test import APIClient from rest_framework.test import APIClient
...@@ -255,18 +257,18 @@ The default format used to make test requests may be set using the `TEST_REQUEST ...@@ -255,18 +257,18 @@ The default format used to make test requests may be set using the `TEST_REQUEST
If you need to test requests using something other than multipart or json requests, you can do so by setting the `TEST_REQUEST_RENDERER_CLASSES` setting. If you need to test requests using something other than multipart or json requests, you can do so by setting the `TEST_REQUEST_RENDERER_CLASSES` setting.
For example, to add support for using `format='yaml'` in test requests, you might have something like this in your `settings.py` file. For example, to add support for using `format='html'` in test requests, you might have something like this in your `settings.py` file.
REST_FRAMEWORK = { REST_FRAMEWORK = {
... ...
'TEST_REQUEST_RENDERER_CLASSES': ( 'TEST_REQUEST_RENDERER_CLASSES': (
'rest_framework.renderers.MultiPartRenderer', 'rest_framework.renderers.MultiPartRenderer',
'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.YAMLRenderer' 'rest_framework.renderers.TemplateHTMLRenderer'
) )
} }
[cite]: http://jacobian.org/writing/django-apps-with-buildout/#s-create-a-test-wrapper [cite]: http://jacobian.org/writing/django-apps-with-buildout/#s-create-a-test-wrapper
[client]: https://docs.djangoproject.com/en/dev/topics/testing/overview/#module-django.test.client [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 [requestfactory]: https://docs.djangoproject.com/en/dev/topics/testing/advanced/#django.test.client.RequestFactory
[configuration]: #configuration [configuration]: #configuration
source: versioning.py
# Versioning
> Versioning an interface is just a "polite" way to kill deployed clients.
>
> &mdash; [Roy Fielding][cite].
API versioning allows you to alter behavior between different clients. REST framework provides for a number of different versioning schemes.
Versioning is determined by the incoming client request, and may either be based on the request URL, or based on the request headers.
There are a number of valid approaches to approaching versioning. [Non-versioned systems can also be appropriate][roy-fielding-on-versioning], particularly if you're engineering for very long-term systems with multiple clients outside of your control.
## Versioning with REST framework
When API versioning is enabled, the `request.version` attribute will contain a string that corresponds to the version requested in the incoming client request.
By default, versioning is not enabled, and `request.version` will always return `None`.
#### Varying behavior based on the version
How you vary the API behavior is up to you, but one example you might typically want is to switch to a different serialization style in a newer version. For example:
def get_serializer_class(self):
if self.request.version == 'v1':
return AccountSerializerVersion1
return AccountSerializer
#### Reversing URLs for versioned APIs
The `reverse` function included by REST framework ties in with the versioning scheme. You need to make sure to include the current `request` as a keyword argument, like so.
reverse('bookings-list', request=request)
The above function will apply any URL transformations appropriate to the request version. For example:
* If `NamespacedVersioning` was being used, and the API version was 'v1', then the URL lookup used would be `'v1:bookings-list'`, which might resolve to a URL like `http://example.org/v1/bookings/`.
* If `QueryParameterVersioning` was being used, and the API version was `1.0`, then the returned URL might be something like `http://example.org/bookings/?version=1.0`
#### Versioned APIs and hyperlinked serializers
When using hyperlinked serialization styles together with a URL based versioning scheme make sure to include the request as context to the serializer.
def get(self, request):
queryset = Booking.objects.all()
serializer = BookingsSerializer(queryset, many=True, context={'request': request})
return Response({'all_bookings': serializer.data})
Doing so will allow any returned URLs to include the appropriate versioning.
## Configuring the versioning scheme
The versioning scheme is defined by the `DEFAULT_VERSIONING_CLASS` settings key.
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning'
}
Unless it is explicitly set, the value for `DEFAULT_VERSIONING_CLASS` will be `None`. In this case the `request.version` attribute will always return `None`.
You can also set the versioning scheme on an individual view. Typically you won't need to do this, as it makes more sense to have a single versioning scheme used globally. If you do need to do so, use the `versioning_class` attribute.
class ProfileList(APIView):
versioning_class = versioning.QueryParameterVersioning
#### Other versioning settings
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. Defaults to `None`.
* `VERSION_PARAMETER`. The string that should used for any versioning parameters, such as in the media type or URL query parameters. Defaults to `'version'`.
---
# API Reference
## AcceptHeaderVersioning
This scheme requires the client to specify the version as part of the media type in the `Accept` header. The version is included as a media type parameter, that supplements the main media type.
Here's an example HTTP request using the accept header versioning style.
GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/json; version=1.0
In the example request above `request.version` attribute would return the string `'1.0'`.
Versioning based on accept headers is [generally considered][klabnik-guidelines] as [best practice][heroku-guidelines], although other styles may be suitable depending on your client requirements.
#### Using accept headers with vendor media types
Strictly speaking the `json` media type is not specified as [including additional parameters][json-parameters]. If you are building a well-specified public API you might consider using a [vendor media type][vendor-media-type]. To do so, configure your renderers to use a JSON based renderer with a custom media type:
class BookingsAPIRenderer(JSONRenderer):
media_type = 'application/vnd.megacorp.bookings+json'
Your client requests would now look like this:
GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/vnd.megacorp.bookings+json; version=1.0
## URLParameterVersioning
This scheme requires the client to specify the version as part of the URL path.
GET /v1/bookings/ HTTP/1.1
Host: example.com
Accept: application/json
Your URL conf must include a pattern that matches the version with a `'version'` keyword argument, so that this information is available to the versioning scheme.
urlpatterns = [
url(
r'^(?P<version>{v1,v2})/bookings/$',
bookings_list,
name='bookings-list'
),
url(
r'^(?P<version>{v1,v2})/bookings/(?P<pk>[0-9]+)/$',
bookings_detail,
name='bookings-detail'
)
]
## NamespaceVersioning
To the client, this scheme is the same as `URLParameterVersioning`. The only difference is how it is configured in your Django application, as it uses URL namespacing, instead of URL keyword arguments.
GET /v1/something/ HTTP/1.1
Host: example.com
Accept: application/json
With this scheme the `request.version` attribute is determined based on the `namespace` that matches the incoming request path.
In the following example we're giving a set of views two different possible URL prefixes, each under a different namespace:
# bookings/urls.py
urlpatterns = [
url(r'^$', bookings_list, name='bookings-list'),
url(r'^(?P<pk>[0-9]+)/$', bookings_detail, name='bookings-detail')
]
# urls.py
urlpatterns = [
url(r'^v1/bookings/', include('bookings.urls', namespace='v1')),
url(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
]
Both `URLParameterVersioning` and `NamespaceVersioning` are reasonable if you just need a simple versioning scheme. The `URLParameterVersioning` approach might be better suitable for small ad-hoc projects, and the `NamespaceVersioning` is probably easier to manage for larger projects.
## HostNameVersioning
The hostname versioning scheme requires the client to specify the requested version as part of the hostname in the URL.
For example the following is an HTTP request to the `http://v1.example.com/bookings/` URL:
GET /bookings/ HTTP/1.1
Host: v1.example.com
Accept: application/json
By default this implementation expects the hostname to match this simple regular expression:
^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$
Note that the first group is enclosed in brackets, indicating that this is the matched portion of the hostname.
The `HostNameVersioning` scheme can be awkward to use in debug mode as you will typically be accessing a raw IP address such as `127.0.0.1`. There are various online services which you to [access localhost with a custom subdomain][lvh] which you may find helpful in this case.
Hostname based versioning can be particularly useful if you have requirements to route incoming requests to different servers based on the version, as you can configure different DNS records for different API versions.
## QueryParameterVersioning
This scheme is a simple style that includes the version as a query parameter in the URL. For example:
GET /something/?version=0.1 HTTP/1.1
Host: example.com
Accept: application/json
---
# Custom versioning schemes
To implement a custom versioning scheme, subclass `BaseVersioning` and override the `.determine_version` method.
## Example
The following example uses a custom `X-API-Version` header to determine the requested version.
class XAPIVersionScheme(versioning.BaseVersioning):
def determine_version(self, request, *args, **kwargs):
return request.META.get('HTTP_X_API_VERSION', None)
If your versioning scheme is based on the request URL, you will also want to alter how versioned URLs are determined. In order to do so you should override the `.reverse()` method on the class. See the source code for examples.
[cite]: http://www.slideshare.net/evolve_conference/201308-fielding-evolve/31
[roy-fielding-on-versioning]: http://www.infoq.com/articles/roy-fielding-on-versioning
[klabnik-guidelines]: http://blog.steveklabnik.com/posts/2011-07-03-nobody-understands-rest-or-http#i_want_my_api_to_be_versioned
[heroku-guidelines]: https://github.com/interagent/http-api-design#version-with-accepts-header
[json-parameters]: http://tools.ietf.org/html/rfc4627#section-6
[vendor-media-type]: http://en.wikipedia.org/wiki/Internet_media_type#Vendor_tree
[lvh]: https://reinteractive.net/posts/199-developing-and-testing-rails-applications-with-subdomains
...@@ -178,7 +178,7 @@ The actions provided by the `ModelViewSet` class are `.list()`, `.retrieve()`, ...@@ -178,7 +178,7 @@ The actions provided by the `ModelViewSet` class are `.list()`, `.retrieve()`,
#### Example #### Example
Because `ModelViewSet` extends `GenericAPIView`, you'll normally need to provide at least the `queryset` and `serializer_class` attributes, or the `model` attribute shortcut. For example: Because `ModelViewSet` extends `GenericAPIView`, you'll normally need to provide at least the `queryset` and `serializer_class` attributes. For example:
class AccountViewSet(viewsets.ModelViewSet): class AccountViewSet(viewsets.ModelViewSet):
""" """
......
...@@ -28,13 +28,12 @@ For more details see the [3.0 release notes][3.0-announcement]. ...@@ -28,13 +28,12 @@ For more details see the [3.0 release notes][3.0-announcement].
<img alt="Django REST Framework" title="Logo by Jake 'Sid' Smith" src="img/logo.png" width="600px" style="display: block; margin: 0 auto 0 auto"> <img alt="Django REST Framework" title="Logo by Jake 'Sid' Smith" src="img/logo.png" width="600px" style="display: block; margin: 0 auto 0 auto">
</p> </p>
Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs. Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs.
Some reasons you might want to use REST framework: Some reasons you might want to use REST framework:
* The [Web browsable API][sandbox] is a huge usability win for your developers. * The [Web browsable API][sandbox] is a huge usability win for your developers.
* [Authentication policies][authentication] including [OAuth1a][oauth1-section] and [OAuth2][oauth2-section] out of the box. * [Authentication policies][authentication] including packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources. * [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]. * 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]. * [Extensive documentation][index], and [great community support][group].
...@@ -51,20 +50,14 @@ Some reasons you might want to use REST framework: ...@@ -51,20 +50,14 @@ Some reasons you might want to use REST framework:
REST framework requires the following: REST framework requires the following:
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4) * Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)
* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7) * Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7, 1.8-beta)
The following packages are optional: The following packages are optional:
* [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API. * [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API.
* [PyYAML][yaml] (3.10+) - YAML content-type support.
* [defusedxml][defusedxml] (0.3+) - XML content-type support.
* [django-filter][django-filter] (0.9.2+) - Filtering support. * [django-filter][django-filter] (0.9.2+) - Filtering support.
* [django-oauth-plus][django-oauth-plus] (2.0+) and [oauth2][oauth2] (1.5.211+) - OAuth 1.0a support.
* [django-oauth2-provider][django-oauth2-provider] (0.2.3+) - OAuth 2.0 support.
* [django-guardian][django-guardian] (1.1.1+) - Object level permissions support. * [django-guardian][django-guardian] (1.1.1+) - Object level permissions support.
**Note**: The `oauth2` Python package is badly misnamed, and actually provides OAuth 1.0a support. Also note that packages required for both OAuth 1.0a, and OAuth 2.0 are not yet Python 3 compatible.
## Installation ## Installation
Install using `pip`, including any optional packages you want... Install using `pip`, including any optional packages you want...
...@@ -180,6 +173,7 @@ The API guide is your complete reference manual to all the functionality provide ...@@ -180,6 +173,7 @@ The API guide is your complete reference manual to all the functionality provide
* [Throttling][throttling] * [Throttling][throttling]
* [Filtering][filtering] * [Filtering][filtering]
* [Pagination][pagination] * [Pagination][pagination]
* [Versioning][versioning]
* [Content negotiation][contentnegotiation] * [Content negotiation][contentnegotiation]
* [Metadata][metadata] * [Metadata][metadata]
* [Format suffixes][formatsuffixes] * [Format suffixes][formatsuffixes]
...@@ -201,14 +195,10 @@ General guides to using REST framework. ...@@ -201,14 +195,10 @@ General guides to using REST framework.
* [Third Party Resources][third-party-resources] * [Third Party Resources][third-party-resources]
* [Contributing to REST framework][contributing] * [Contributing to REST framework][contributing]
* [Project management][project-management] * [Project management][project-management]
* [2.0 Announcement][rest-framework-2-announcement]
* [2.2 Announcement][2.2-announcement]
* [2.3 Announcement][2.3-announcement]
* [2.4 Announcement][2.4-announcement]
* [3.0 Announcement][3.0-announcement] * [3.0 Announcement][3.0-announcement]
* [3.1 Announcement][3.1-announcement]
* [Kickstarter Announcement][kickstarter-announcement] * [Kickstarter Announcement][kickstarter-announcement]
* [Release Notes][release-notes] * [Release Notes][release-notes]
* [Credits][credits]
## Development ## Development
...@@ -263,18 +253,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -263,18 +253,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[mozilla]: http://www.mozilla.org/en-US/about/ [mozilla]: http://www.mozilla.org/en-US/about/
[eventbrite]: https://www.eventbrite.co.uk/about/ [eventbrite]: https://www.eventbrite.co.uk/about/
[markdown]: http://pypi.python.org/pypi/Markdown/ [markdown]: http://pypi.python.org/pypi/Markdown/
[yaml]: http://pypi.python.org/pypi/PyYAML
[defusedxml]: https://pypi.python.org/pypi/defusedxml
[django-filter]: http://pypi.python.org/pypi/django-filter [django-filter]: http://pypi.python.org/pypi/django-filter
[oauth2]: https://github.com/simplegeo/python-oauth2
[django-oauth-plus]: https://bitbucket.org/david/django-oauth-plus/wiki/Home
[django-oauth2-provider]: https://github.com/caffeinehit/django-oauth2-provider
[django-guardian]: https://github.com/lukaszb/django-guardian [django-guardian]: https://github.com/lukaszb/django-guardian
[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X [0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
[image]: img/quickstart.png [image]: img/quickstart.png
[index]: . [index]: .
[oauth1-section]: api-guide/authentication#oauthauthentication [oauth1-section]: api-guide/authentication/#django-rest-framework-oauth
[oauth2-section]: api-guide/authentication#oauth2authentication [oauth2-section]: api-guide/authentication/#django-oauth-toolkit
[serializer-section]: api-guide/serializers#serializers [serializer-section]: api-guide/serializers#serializers
[modelserializer-section]: api-guide/serializers#modelserializer [modelserializer-section]: api-guide/serializers#modelserializer
[functionview-section]: api-guide/views#function-based-views [functionview-section]: api-guide/views#function-based-views
...@@ -305,6 +290,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -305,6 +290,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[throttling]: api-guide/throttling.md [throttling]: api-guide/throttling.md
[filtering]: api-guide/filtering.md [filtering]: api-guide/filtering.md
[pagination]: api-guide/pagination.md [pagination]: api-guide/pagination.md
[versioning]: api-guide/versioning.md
[contentnegotiation]: api-guide/content-negotiation.md [contentnegotiation]: api-guide/content-negotiation.md
[metadata]: api-guide/metadata.md [metadata]: api-guide/metadata.md
[formatsuffixes]: api-guide/format-suffixes.md [formatsuffixes]: api-guide/format-suffixes.md
...@@ -315,6 +301,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -315,6 +301,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[settings]: api-guide/settings.md [settings]: api-guide/settings.md
[documenting-your-api]: topics/documenting-your-api.md [documenting-your-api]: topics/documenting-your-api.md
[internationalization]: topics/documenting-your-api.md
[ajax-csrf-cors]: topics/ajax-csrf-cors.md [ajax-csrf-cors]: topics/ajax-csrf-cors.md
[browser-enhancements]: topics/browser-enhancements.md [browser-enhancements]: topics/browser-enhancements.md
[browsableapi]: topics/browsable-api.md [browsableapi]: topics/browsable-api.md
...@@ -322,14 +309,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -322,14 +309,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[contributing]: topics/contributing.md [contributing]: topics/contributing.md
[project-management]: topics/project-management.md [project-management]: topics/project-management.md
[third-party-resources]: topics/third-party-resources.md [third-party-resources]: topics/third-party-resources.md
[rest-framework-2-announcement]: topics/rest-framework-2-announcement.md
[2.2-announcement]: topics/2.2-announcement.md
[2.3-announcement]: topics/2.3-announcement.md
[2.4-announcement]: topics/2.4-announcement.md
[3.0-announcement]: topics/3.0-announcement.md [3.0-announcement]: topics/3.0-announcement.md
[3.1-announcement]: topics/3.1-announcement.md
[kickstarter-announcement]: topics/kickstarter-announcement.md [kickstarter-announcement]: topics/kickstarter-announcement.md
[release-notes]: topics/release-notes.md [release-notes]: topics/release-notes.md
[credits]: topics/credits.md
[tox]: http://testrun.org/tox/latest/ [tox]: http://testrun.org/tox/latest/
......
# Django REST framework 3.1
The 3.1 release is an intermediate step in the Kickstarter project releases, and includes a range of new functionality.
Some highlights include:
* A super-smart cursor pagination scheme.
* An improved pagination API, supporting header or in-body pagination styles.
* Pagination controls rendering in the browsable API.
* Better support for API versioning.
* Built-in internationalization support.
* Support for Django 1.8's `HStoreField` and `ArrayField`.
---
## Pagination
The pagination API has been improved, making it both easier to use, and more powerful.
#### New pagination schemes.
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.
#### Pagination controls in the browsable API.
Paginated results now include controls that render directly in the browsable API. If you're using the page or limit/offset style, then you'll see a page based control displayed in the browsable API:
![page number based pagination](../img/pages-pagination.png )
The cursor based pagination renders a more simple style of control:
![cursor based pagination](../img/cursor-pagination.png )
#### Support for header-based pagination.
The pagination API was previously only able to alter the pagination style in the body of the response. The API now supports being able to write pagination information in response headers, making it possible to use pagination schemes that use the `Link` or `Content-Range` headers.
For more information, see the [custom pagination styles](../api-guide/pagination/#custom-pagination-styles) documentation.
---
## Versioning
We've made it easier to build versioned APIs. Built-in schemes for versioning include both URL based and Accept header based variations.
When using a URL based scheme, hyperlinked serializers will resolve relationships to the same API version as used on the incoming request.
For example, when using `NamespaceVersioning`, and the following hyperlinked serializer:
class AccountsSerializer(serializer.HyperlinkedModelSerializer):
class Meta:
model = Accounts
fields = ('account_name', 'users')
The output representation would match the version used on the incoming request. Like so:
GET http://example.org/v2/accounts/10 # Version 'v2'
{
"account_name": "europa",
"users": [
"http://example.org/v2/users/12", # Version 'v2'
"http://example.org/v2/users/54",
"http://example.org/v2/users/87"
]
}
---
## Internationalization
REST framework now includes a built-in set of translations, and supports internationalized error responses. This allows you to either change the default language, or to allow clients to specify the language via the `Accept-Language` header.
You can change the default language by using the standard Django `LANGUAGE_CODE` setting:
LANGUAGE_CODE = "es-es"
You can turn on per-request language requests by adding `LocalMiddleware` to your `MIDDLEWARE_CLASSES` setting:
MIDDLEWARE_CLASSES = [
...
'django.middleware.locale.LocaleMiddleware'
]
When per-request internationalization is enabled, client requests will respect the `Accept-Language` header where possible. For example, let's make a request for an unsupported media type:
**Request**
GET /api/users HTTP/1.1
Accept: application/xml
Accept-Language: es-es
Host: example.org
**Response**
HTTP/1.0 406 NOT ACCEPTABLE
{
"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].
We include built-in translations both for standard exception cases, and for serializer validation errors.
The full list of supported languages can be found on our [Transifex project page](https://www.transifex.com/projects/p/django-rest-framework/).
If you only wish to support a subset of the supported languages, use Django's standard `LANGUAGES` setting:
LANGUAGES = [
('de', _('German')),
('en', _('English')),
]
For more details, see the [internationalization documentation](internationalization.md).
Many thanks to [Craig Blaszczyk](https://github.com/jakul) for helping push this through.
---
## New field types
Django 1.8's new `ArrayField`, `HStoreField` and `UUIDField` are now all fully supported.
This work also means that we now have both `serializers.DictField()`, and `serializers.ListField()` types, allowing you to express and validate a wider set of representations.
If you're building a new 1.8 project, then you should probably consider using `UUIDField` as the primary keys for all your models. This style will work automatically with hyperlinked serializers, returning URLs in the following style:
http://example.org/api/purchases/9b1a433f-e90d-4948-848b-300fdc26365d
---
## ModelSerializer API
The serializer redesign in 3.0 did not include any public API for modifying how ModelSerializer classes automatically generate a set of fields from a given mode class. We've now re-introduced an API for this, allowing you to create new ModelSerializer base classes that behave differently, such as using a different default style for relationships.
For more information, see the documentation on [customizing field mappings](../api-guide/serializers/#customizing-field-mappings) for ModelSerializer classes.
---
## Moving packages out of core
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.
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.
The following packages are now moved out of core and should be separately installed:
* OAuth - [djangorestframework-oauth](http://jpadilla.github.io/django-rest-framework-oauth/)
* XML - [djangorestframework-xml](http://jpadilla.github.io/django-rest-framework-xml)
* YAML - [djangorestframework-yaml](http://jpadilla.github.io/django-rest-framework-yaml)
* JSONP - [djangorestframework-jsonp](http://jpadilla.github.io/django-rest-framework-jsonp)
It's worth reiterating that this change in policy shouldn't mean any work in your codebase other than adding a new requirement and modifying some import paths. For example to install XML rendering, you would now do:
pip install djangorestframework-xml
And modify your settings, like so:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
'rest_framework_xml.renderers.XMLRenderer'
]
}
Thanks go to the latest member of our maintenance team, [José Padilla](https://github.com/jpadilla/), for handling this work and taking on ownership of these packages.
---
## Deprecations
The `request.DATA`, `request.FILES` and `request.QUERY_PARAMS` attributes move from pending deprecation, to deprecated. Use `request.data` and `request.query_params` instead, as discussed in the 3.0 release notes.
The ModelSerializer Meta options for `write_only_fields`, `view_name` and `lookup_field` are also moved from pending deprecation, to deprecated. Use `extra_kwargs` instead, as discussed in the 3.0 release notes.
All these attributes and options will still work in 3.1, but their usage will raise a warning. They will be fully removed in 3.2.
---
## What's next?
The next focus will be on HTML renderings of API output and will include:
* HTML form rendering of serializers.
* Filtering controls built-in to the browsable API.
* An alternative admin-style interface.
This will either be made as a single 3.2 release, or split across two separate releases, with the HTML forms and filter controls coming in 3.2, and the admin-style interface coming in a 3.3 release.
[custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling
# Internationalization
> Supporting internationalization is not optional. It must be a core feature.
>
> &mdash; [Jannis Leidel, speaking at Django Under the Hood, 2015][cite].
REST framework ships with translatable error messages. You can make these appear in your language enabling [Django's standard translation mechanisms][django-translation].
Doing so will allow you to:
* Select a language other than English as the default, using the standard `LANGUAGE_CODE` Django setting.
* Allow clients to choose a language themselves, using the `LocaleMiddleware` included with Django. A typical usage for API clients would be to include an `Accept-Language` request header.
## Enabling internationalized APIs
You can change the default language by using the standard Django `LANGUAGE_CODE` setting:
LANGUAGE_CODE = "es-es"
You can turn on per-request language requests by adding `LocalMiddleware` to your `MIDDLEWARE_CLASSES` setting:
MIDDLEWARE_CLASSES = [
...
'django.middleware.locale.LocaleMiddleware'
]
When per-request internationalization is enabled, client requests will respect the `Accept-Language` header where possible. For example, let's make a request for an unsupported media type:
**Request**
GET /api/users HTTP/1.1
Accept: application/xml
Accept-Language: es-es
Host: example.org
**Response**
HTTP/1.0 406 NOT ACCEPTABLE
{"detail": "No se ha podido satisfacer la solicitud de cabecera de Accept."}
REST framework includes these built-in translations both for standard exception cases, and for serializer validation errors.
Note that the translations only apply to the error strings themselves. The format of error messages, and the keys of field names will remain the same. An example `400 Bad Request` response body might look like this:
{"detail": {"username": ["Esse campo deve ser unico."]}}
If you want to use different string for parts of the response such as `detail` and `non_field_errors` then you can modify this behavior by using a [custom exception handler][custom-exception-handler].
#### Specifying the set of supported languages.
By default all available languages will be supported.
If you only wish to support a subset of the available languages, use Django's standard `LANGUAGES` setting:
LANGUAGES = [
('de', _('German')),
('en', _('English')),
]
## Adding new translations
REST framework translations are managed online using [Transifex][transifex-project]. You can use the Transifex service to add new translation languages. The maintenance team will then ensure that these translation strings are included in the REST framework package.
Sometimes you may need to add translation strings to your project locally. You may need to do this if:
* You want to use REST Framework in a language which has not been translated yet on Transifex.
* Your project includes custom error messages, which are not part of REST framework's default translation strings.
#### Translating a new language locally
This guide assumes you are already familiar with how to translate a Django app. If you're not, start by reading [Django's translation docs][django-translation].
If you're translating a new language you'll need to translate the existing REST framework error messages:
1. Make a new folder where you want to store the internationalization resources. Add this path to your [`LOCALE_PATHS`][django-locale-paths] setting.
2. Now create a subfolder for the language you want to translate. The folder should be named using [locale name][django-locale-name] notation. For example: `de`, `pt_BR`, `es_AR`.
3. Now copy the [base translations file][django-po-source] from the REST framework source code into your translations folder.
4. Edit the `django.po` file you've just copied, translating all the error messages.
5. Run `manage.py compilemessages -l pt_BR` to make the translations
available for Django to use. You should see a message like `processing file django.po in <...>/locale/pt_BR/LC_MESSAGES`.
6. Restart your development server to see the changes take effect.
If you're only translating custom error messages that exist inside your project codebase you don't need to copy the REST framework source `django.po` file into a `LOCALE_PATHS` folder, and can instead simply run Django's standard `makemessages` process.
## How the language is determined
If you want to allow per-request language preferences you'll need to include `django.middleware.locale.LocaleMiddleware` in your `MIDDLEWARE_CLASSES` setting.
You can find more information on how the language preference is determined in the [Django documentation][django-language-preference]. For reference, the method is:
1. First, it looks for the language prefix in the requested URL.
2. Failing that, it looks for the `LANGUAGE_SESSION_KEY` key in the current user’s session.
3. Failing that, it looks for a cookie.
4. Failing that, it looks at the `Accept-Language` HTTP header.
5. Failing that, it uses the global `LANGUAGE_CODE` setting.
For API clients the most appropriate of these will typically be to use the `Accept-Language` header; Sessions and cookies will not be available unless using session authentication, and generally better practice to prefer an `Accept-Language` header for API clients rather than using language URL prefixes.
[cite]: http://youtu.be/Wa0VfS2q94Y
[django-translation]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation
[custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling
[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/
[django-po-source]: https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/rest_framework/locale/en_US/LC_MESSAGES/django.po
[django-language-preference]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#how-django-discovers-language-preference
[django-locale-paths]: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-LOCALE_PATHS
[django-locale-name]: https://docs.djangoproject.com/en/1.7/topics/i18n/#term-locale-name
[contributing]: ../../CONTRIBUTING.md
...@@ -65,10 +65,11 @@ The following template should be used for the description of the issue, and serv ...@@ -65,10 +65,11 @@ The following template should be used for the description of the issue, and serv
Team members have the following responsibilities. Team members have the following responsibilities.
* Add triage labels and milestones to tickets.
* Close invalid or resolved tickets. * Close invalid or resolved tickets.
* Add triage labels and milestones to tickets.
* Merge finalized pull requests. * Merge finalized pull requests.
* Build and deploy the documentation, using `mkdocs gh-deploy`. * Build and deploy the documentation, using `mkdocs gh-deploy`.
* Build and update the included translation packs.
Further notes for maintainers: Further notes for maintainers:
...@@ -116,6 +117,63 @@ When pushing the release to PyPI ensure that your environment has been installed ...@@ -116,6 +117,63 @@ When pushing the release to PyPI ensure that your environment has been installed
--- ---
## Translations
The maintenance team are responsible for managing the translation packs include in REST framework. Translating the source strings into multiple languages is managed through the [transifex service][transifex-project].
### Managing Transifex
The [official Transifex client][transifex-client] is used to upload and download translations to Transifex. The client is installed using pip:
pip install transifex-client
To use it you'll need a login to Transifex which has a password, and you'll need to have administrative access to the Transifex project. You'll need to create a `~/.transifexrc` file which contains your credentials.
[https://www.transifex.com]
username = ***
token = ***
password = ***
hostname = https://www.transifex.com
### Upload new source files
When any user visible strings are changed, they should be uploaded to Transifex so that the translators can start to translate them. To do this, just run:
# 1. Update the source django.po file, which is the US English version.
cd rest_framework
django-admin.py makemessages -l en_US
# 2. Push the source django.po file to Transifex.
cd ..
tx push -s
When pushing source files, Transifex will update the source strings of a resource to match those from the new source file.
Here's how differences between the old and new source files will be handled:
* New strings will be added.
* Modified strings will be added as well.
* Strings which do not exist in the new source file will be removed from the database, along with their translations. If that source strings gets re-added later then [Transifex Translation Memory][translation-memory] will automatically include the translation string.
### Download translations
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
cd rest_framework
# 4. Compile the binary .mo files for all supported languages.
django-admin.py compilemessages
---
## Project requirements
All our test requirements are pinned to exact versions, in order to ensure that our test runs are reproducible. We maintain the requirements in the `requirements` directory. The requirements files are referenced from the `tox.ini` configuration file, ensuring we have a single source of truth for package versions used in testing.
Package upgrades should generally be treated as isolated pull requests. You can check if there are any packages available at a newer version, by using the `pip list --outdated`.
---
## Project ownership ## Project ownership
The PyPI package is owned by `@tomchristie`. As a backup `@j4mie` also has ownership of the package. The PyPI package is owned by `@tomchristie`. As a backup `@j4mie` also has ownership of the package.
...@@ -134,6 +192,9 @@ The following issues still need to be addressed: ...@@ -134,6 +192,9 @@ The following issues still need to be addressed:
[bus-factor]: http://en.wikipedia.org/wiki/Bus_factor [bus-factor]: http://en.wikipedia.org/wiki/Bus_factor
[un-triaged]: https://github.com/tomchristie/django-rest-framework/issues?q=is%3Aopen+no%3Alabel [un-triaged]: https://github.com/tomchristie/django-rest-framework/issues?q=is%3Aopen+no%3Alabel
[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/
[transifex-client]: https://pypi.python.org/pypi/transifex-client
[translation-memory]: http://docs.transifex.com/guides/tm#let-tm-automatically-populate-translations
[github-org]: https://github.com/tomchristie/django-rest-framework/issues/2162 [github-org]: https://github.com/tomchristie/django-rest-framework/issues/2162
[sandbox]: http://restframework.herokuapp.com/ [sandbox]: http://restframework.herokuapp.com/
[mailing-list]: https://groups.google.com/forum/#!forum/django-rest-framework [mailing-list]: https://groups.google.com/forum/#!forum/django-rest-framework
...@@ -188,6 +188,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque ...@@ -188,6 +188,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [hawkrest][hawkrest] - Provides Hawk HTTP Authorization. * [hawkrest][hawkrest] - Provides Hawk HTTP Authorization.
* [djangorestframework-httpsignature][djangorestframework-httpsignature] - Provides an easy to use HTTP Signature Authentication mechanism. * [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. * [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.
### Permissions ### Permissions
...@@ -324,3 +325,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque ...@@ -324,3 +325,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[django-rest-framework-and-angularjs-video]: http://www.youtube.com/watch?v=q8frbgtj020 [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/ [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/ [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/
...@@ -92,7 +92,7 @@ Here is the view for an individual snippet, in the `views.py` module. ...@@ -92,7 +92,7 @@ Here is the view for an individual snippet, in the `views.py` module.
This should all feel very familiar - it is not a lot different from working with regular Django views. This should all feel very familiar - it is not a lot different from working with regular Django views.
Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.data` can handle incoming `json` requests, but it can also handle `yaml` and other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us. Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.data` can handle incoming `json` requests, but it can also handle other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us.
## Adding optional format suffixes to our URLs ## Adding optional format suffixes to our URLs
......
...@@ -171,6 +171,25 @@ body{ ...@@ -171,6 +171,25 @@ body{
background-attachment: fixed; background-attachment: fixed;
} }
#main-content h1:first-of-type {
margin-top: 0
}
#main-content h1, #main-content h2 {
font-weight: 300;
margin-top: 20px
}
#main-content h3, #main-content h4, #main-content h5 {
font-weight: 500;
margin-top: 15px
}
#main-content img {
display: block;
margin: 40px auto;
}
/* custom navigation styles */ /* custom navigation styles */
.navbar .navbar-inner{ .navbar .navbar-inner{
...@@ -239,6 +258,10 @@ body a:hover{ ...@@ -239,6 +258,10 @@ body a:hover{
} }
} }
h1 code, h2 code, h3 code, h4 code, h5 code {
color: #333;
}
/* sticky footer and footer */ /* sticky footer and footer */
html, body { html, body {
height: 100%; height: 100%;
......
...@@ -32,6 +32,7 @@ pages: ...@@ -32,6 +32,7 @@ pages:
- ['api-guide/throttling.md', 'API Guide', 'Throttling'] - ['api-guide/throttling.md', 'API Guide', 'Throttling']
- ['api-guide/filtering.md', 'API Guide', 'Filtering'] - ['api-guide/filtering.md', 'API Guide', 'Filtering']
- ['api-guide/pagination.md', 'API Guide', 'Pagination'] - ['api-guide/pagination.md', 'API Guide', 'Pagination']
- ['api-guide/versioning.md', 'API Guide', 'Versioning']
- ['api-guide/content-negotiation.md', 'API Guide', 'Content negotiation'] - ['api-guide/content-negotiation.md', 'API Guide', 'Content negotiation']
- ['api-guide/metadata.md', 'API Guide', 'Metadata'] - ['api-guide/metadata.md', 'API Guide', 'Metadata']
- ['api-guide/format-suffixes.md', 'API Guide', 'Format suffixes'] - ['api-guide/format-suffixes.md', 'API Guide', 'Format suffixes']
...@@ -41,6 +42,7 @@ pages: ...@@ -41,6 +42,7 @@ pages:
- ['api-guide/testing.md', 'API Guide', 'Testing'] - ['api-guide/testing.md', 'API Guide', 'Testing']
- ['api-guide/settings.md', 'API Guide', 'Settings'] - ['api-guide/settings.md', 'API Guide', 'Settings']
- ['topics/documenting-your-api.md', 'Topics', 'Documenting your API'] - ['topics/documenting-your-api.md', 'Topics', 'Documenting your API']
- ['topics/internationalization.md', 'Topics', 'Internationalization']
- ['topics/ajax-csrf-cors.md', 'Topics', 'AJAX, CSRF & CORS'] - ['topics/ajax-csrf-cors.md', 'Topics', 'AJAX, CSRF & CORS']
- ['topics/browser-enhancements.md', 'Topics',] - ['topics/browser-enhancements.md', 'Topics',]
- ['topics/browsable-api.md', 'Topics', 'The Browsable API'] - ['topics/browsable-api.md', 'Topics', 'The Browsable API']
...@@ -48,11 +50,7 @@ pages: ...@@ -48,11 +50,7 @@ pages:
- ['topics/third-party-resources.md', 'Topics', 'Third Party Resources'] - ['topics/third-party-resources.md', 'Topics', 'Third Party Resources']
- ['topics/contributing.md', 'Topics', 'Contributing to REST framework'] - ['topics/contributing.md', 'Topics', 'Contributing to REST framework']
- ['topics/project-management.md', 'Topics', 'Project management'] - ['topics/project-management.md', 'Topics', 'Project management']
- ['topics/rest-framework-2-announcement.md', 'Topics', '2.0 Announcement']
- ['topics/2.2-announcement.md', 'Topics', '2.2 Announcement']
- ['topics/2.3-announcement.md', 'Topics', '2.3 Announcement']
- ['topics/2.4-announcement.md', 'Topics', '2.4 Announcement']
- ['topics/3.0-announcement.md', 'Topics', '3.0 Announcement'] - ['topics/3.0-announcement.md', 'Topics', '3.0 Announcement']
- ['topics/3.1-announcement.md', 'Topics', '3.1 Announcement']
- ['topics/kickstarter-announcement.md', 'Topics', 'Kickstarter Announcement'] - ['topics/kickstarter-announcement.md', 'Topics', 'Kickstarter Announcement']
- ['topics/release-notes.md', 'Topics', 'Release Notes'] - ['topics/release-notes.md', 'Topics', 'Release Notes']
- ['topics/credits.md', 'Topics', 'Credits']
# Minimum Django version # The base set of requirements for REST framework is actually
Django>=1.4.11 # just Django, but for the purposes of development and testing
# there are a number of packages that it is useful to install.
# Test requirements
pytest-django==2.8.0 # Laying these out as seperate requirements files, allows us to
pytest==2.6.4 # only included the relevent sets when running tox, and ensures
pytest-cov==1.6 # we are only ever declaring out dependancies in one place.
flake8==2.2.2
-r requirements/requirements-optionals.txt
# Optional packages -r requirements/requirements-testing.txt
markdown>=2.1.0 -r requirements/requirements-documentation.txt
PyYAML>=3.10 -r requirements/requirements-codestyle.txt
defusedxml>=0.3 -r requirements/requirements-packaging.txt
django-guardian==1.2.4
django-filter>=0.9.2
django-oauth-plus>=2.2.1
oauth2>=1.5.211
django-oauth2-provider>=0.2.4
# wheel for PyPI installs
wheel==0.24.0
# twine for secured PyPI uploads
twine==1.4.0
# MkDocs for documentation previews/deploys
mkdocs==0.11.1
# PEP8 code linting, which we run on all commits.
flake8==2.3.0
pep8==1.6.2
# MkDocs to build our documentation.
mkdocs==0.11.1
# Optional packages which may be used with REST framework.
markdown==2.5.2
django-guardian==1.2.5
django-filter==0.9.2
# Wheel for PyPI installs.
wheel==0.24.0
# Twine for secured PyPI uploads.
twine==1.4.0
# Transifex client for managing translation resources.
transifex-client==0.10
# PyTest for running the tests.
pytest==2.6.4
pytest-django==2.8.0
...@@ -8,7 +8,7 @@ ______ _____ _____ _____ __ ...@@ -8,7 +8,7 @@ ______ _____ _____ _____ __
""" """
__title__ = 'Django REST framework' __title__ = 'Django REST framework'
__version__ = '3.0.4' __version__ = '3.1.0'
__author__ = 'Tom Christie' __author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause' __license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2015 Tom Christie' __copyright__ = 'Copyright 2011-2015 Tom Christie'
......
...@@ -23,7 +23,7 @@ class AuthTokenSerializer(serializers.Serializer): ...@@ -23,7 +23,7 @@ class AuthTokenSerializer(serializers.Serializer):
msg = _('Unable to log in with provided credentials.') msg = _('Unable to log in with provided credentials.')
raise exceptions.ValidationError(msg) raise exceptions.ValidationError(msg)
else: else:
msg = _('Must include "username" and "password"') msg = _('Must include "username" and "password".')
raise exceptions.ValidationError(msg) raise exceptions.ValidationError(msg)
attrs['user'] = user attrs['user'] = user
......
...@@ -5,16 +5,17 @@ versions of django/python, and compatibility wrappers around optional packages. ...@@ -5,16 +5,17 @@ versions of django/python, and compatibility wrappers around optional packages.
# flake8: noqa # flake8: noqa
from __future__ import unicode_literals from __future__ import unicode_literals
import inspect
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.conf import settings
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.six.moves.urllib.parse import urlparse as _urlparse from django.utils.six.moves.urllib.parse import urlparse as _urlparse
from django.conf import settings
from django.utils import six from django.utils import six
import django import django
import inspect
try:
import importlib
except ImportError:
from django.utils import importlib
def unicode_repr(instance): def unicode_repr(instance):
# Get the repr of an instance, but ensure it is a unicode string # Get the repr of an instance, but ensure it is a unicode string
...@@ -33,6 +34,13 @@ def unicode_to_repr(value): ...@@ -33,6 +34,13 @@ def unicode_to_repr(value):
return value return value
def unicode_http_header(value):
# Coerce HTTP header value to unicode.
if isinstance(value, six.binary_type):
return value.decode('iso-8859-1')
return value
def total_seconds(timedelta): def total_seconds(timedelta):
# TimeDelta.total_seconds() is only available in Python 2.7 # TimeDelta.total_seconds() is only available in Python 2.7
if hasattr(timedelta, 'total_seconds'): if hasattr(timedelta, 'total_seconds'):
...@@ -232,77 +240,13 @@ except ImportError: ...@@ -232,77 +240,13 @@ except ImportError:
apply_markdown = None apply_markdown = None
# Yaml is optional
try:
import yaml
except ImportError:
yaml = None
# XML is optional
try:
import defusedxml.ElementTree as etree
except ImportError:
etree = None
# OAuth2 is optional
try:
# Note: The `oauth2` package actually provides oauth1.0a support. Urg.
import oauth2 as oauth
except ImportError:
oauth = None
# OAuthProvider is optional
try:
import oauth_provider
from oauth_provider.store import store as oauth_provider_store
# check_nonce's calling signature in django-oauth-plus changes sometime
# between versions 2.0 and 2.2.1
def check_nonce(request, oauth_request, oauth_nonce, oauth_timestamp):
check_nonce_args = inspect.getargspec(oauth_provider_store.check_nonce).args
if 'timestamp' in check_nonce_args:
return oauth_provider_store.check_nonce(
request, oauth_request, oauth_nonce, oauth_timestamp
)
return oauth_provider_store.check_nonce(
request, oauth_request, oauth_nonce
)
except (ImportError, ImproperlyConfigured):
oauth_provider = None
oauth_provider_store = None
check_nonce = None
# OAuth 2 support is optional
try:
import provider as oauth2_provider
from provider import scope as oauth2_provider_scope
from provider import constants as oauth2_constants
if oauth2_provider.__version__ in ('0.2.3', '0.2.4'):
# 0.2.3 and 0.2.4 are supported version that do not support
# timezone aware datetimes
import datetime
provider_now = datetime.datetime.now
else:
# Any other supported version does use timezone aware datetimes
from django.utils.timezone import now as provider_now
except ImportError:
oauth2_provider = None
oauth2_provider_scope = None
oauth2_constants = None
provider_now = None
# `separators` argument to `json.dumps()` differs between 2.x and 3.x # `separators` argument to `json.dumps()` differs between 2.x and 3.x
# See: http://bugs.python.org/issue22767 # See: http://bugs.python.org/issue22767
if six.PY3: if six.PY3:
SHORT_SEPARATORS = (',', ':') SHORT_SEPARATORS = (',', ':')
LONG_SEPARATORS = (', ', ': ') LONG_SEPARATORS = (', ', ': ')
INDENT_SEPARATORS = (',', ': ')
else: else:
SHORT_SEPARATORS = (b',', b':') SHORT_SEPARATORS = (b',', b':')
LONG_SEPARATORS = (b', ', b': ') LONG_SEPARATORS = (b', ', b': ')
INDENT_SEPARATORS = (b',', b': ')
...@@ -7,8 +7,7 @@ In addition Django's built in 403 and 404 exceptions are handled. ...@@ -7,8 +7,7 @@ In addition Django's built in 403 and 404 exceptions are handled.
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils import six from django.utils import six
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _, ungettext
from django.utils.translation import ungettext_lazy
from rest_framework import status from rest_framework import status
import math import math
...@@ -36,7 +35,7 @@ class APIException(Exception): ...@@ -36,7 +35,7 @@ class APIException(Exception):
Subclasses should provide `.status_code` and `.default_detail` properties. Subclasses should provide `.status_code` and `.default_detail` properties.
""" """
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
default_detail = _('A server error occured') default_detail = _('A server error occurred.')
def __init__(self, detail=None): def __init__(self, detail=None):
if detail is not None: if detail is not None:
...@@ -89,20 +88,25 @@ class PermissionDenied(APIException): ...@@ -89,20 +88,25 @@ class PermissionDenied(APIException):
default_detail = _('You do not have permission to perform this action.') default_detail = _('You do not have permission to perform this action.')
class NotFound(APIException):
status_code = status.HTTP_404_NOT_FOUND
default_detail = _('Not found.')
class MethodNotAllowed(APIException): class MethodNotAllowed(APIException):
status_code = status.HTTP_405_METHOD_NOT_ALLOWED status_code = status.HTTP_405_METHOD_NOT_ALLOWED
default_detail = _("Method '%s' not allowed.") default_detail = _('Method "{method}" not allowed.')
def __init__(self, method, detail=None): def __init__(self, method, detail=None):
if detail is not None: if detail is not None:
self.detail = force_text(detail) self.detail = force_text(detail)
else: else:
self.detail = force_text(self.default_detail) % method self.detail = force_text(self.default_detail).format(method=method)
class NotAcceptable(APIException): class NotAcceptable(APIException):
status_code = status.HTTP_406_NOT_ACCEPTABLE status_code = status.HTTP_406_NOT_ACCEPTABLE
default_detail = _('Could not satisfy the request Accept header') default_detail = _('Could not satisfy the request Accept header.')
def __init__(self, detail=None, available_renderers=None): def __init__(self, detail=None, available_renderers=None):
if detail is not None: if detail is not None:
...@@ -114,23 +118,22 @@ class NotAcceptable(APIException): ...@@ -114,23 +118,22 @@ class NotAcceptable(APIException):
class UnsupportedMediaType(APIException): class UnsupportedMediaType(APIException):
status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
default_detail = _("Unsupported media type '%s' in request.") default_detail = _('Unsupported media type "{media_type}" in request.')
def __init__(self, media_type, detail=None): def __init__(self, media_type, detail=None):
if detail is not None: if detail is not None:
self.detail = force_text(detail) self.detail = force_text(detail)
else: else:
self.detail = force_text(self.default_detail) % media_type self.detail = force_text(self.default_detail).format(
media_type=media_type
)
class Throttled(APIException): class Throttled(APIException):
status_code = status.HTTP_429_TOO_MANY_REQUESTS status_code = status.HTTP_429_TOO_MANY_REQUESTS
default_detail = _('Request was throttled.') default_detail = _('Request was throttled.')
extra_detail = ungettext_lazy( extra_detail_singular = 'Expected available in {wait} second.'
'Expected available in %(wait)d second.', extra_detail_plural = 'Expected available in {wait} seconds.'
'Expected available in %(wait)d seconds.',
'wait'
)
def __init__(self, wait=None, detail=None): def __init__(self, wait=None, detail=None):
if detail is not None: if detail is not None:
...@@ -142,6 +145,8 @@ class Throttled(APIException): ...@@ -142,6 +145,8 @@ class Throttled(APIException):
self.wait = None self.wait = None
else: else:
self.wait = math.ceil(wait) self.wait = math.ceil(wait)
self.detail += ' ' + force_text( self.detail += ' ' + force_text(ungettext(
self.extra_detail % {'wait': self.wait} self.extra_detail_singular.format(wait=self.wait),
) self.extra_detail_plural.format(wait=self.wait),
self.wait
))
...@@ -104,7 +104,7 @@ class SearchFilter(BaseFilterBackend): ...@@ -104,7 +104,7 @@ class SearchFilter(BaseFilterBackend):
for search_term in self.get_search_terms(request): for search_term in self.get_search_terms(request):
or_queries = [models.Q(**{orm_lookup: search_term}) or_queries = [models.Q(**{orm_lookup: search_term})
for orm_lookup in orm_lookups] for orm_lookup in orm_lookups]
queryset = queryset.filter(reduce(operator.or_, or_queries)) queryset = queryset.filter(reduce(operator.or_, or_queries)).distinct()
return queryset return queryset
...@@ -114,7 +114,7 @@ class OrderingFilter(BaseFilterBackend): ...@@ -114,7 +114,7 @@ class OrderingFilter(BaseFilterBackend):
ordering_param = api_settings.ORDERING_PARAM ordering_param = api_settings.ORDERING_PARAM
ordering_fields = None ordering_fields = None
def get_ordering(self, request): def get_ordering(self, request, queryset, view):
""" """
Ordering is set by a comma delimited ?ordering=... query parameter. Ordering is set by a comma delimited ?ordering=... query parameter.
...@@ -124,7 +124,13 @@ class OrderingFilter(BaseFilterBackend): ...@@ -124,7 +124,13 @@ class OrderingFilter(BaseFilterBackend):
""" """
params = request.query_params.get(self.ordering_param) params = request.query_params.get(self.ordering_param)
if params: if params:
return [param.strip() for param in params.split(',')] fields = [param.strip() for param in params.split(',')]
ordering = self.remove_invalid_fields(queryset, fields, view)
if ordering:
return ordering
# No ordering was included, or all the ordering fields were invalid
return self.get_default_ordering(view)
def get_default_ordering(self, view): def get_default_ordering(self, view):
ordering = getattr(view, 'ordering', None) ordering = getattr(view, 'ordering', None)
...@@ -132,7 +138,7 @@ class OrderingFilter(BaseFilterBackend): ...@@ -132,7 +138,7 @@ class OrderingFilter(BaseFilterBackend):
return (ordering,) return (ordering,)
return ordering return ordering
def remove_invalid_fields(self, queryset, ordering, view): def remove_invalid_fields(self, queryset, fields, view):
valid_fields = getattr(view, 'ordering_fields', self.ordering_fields) valid_fields = getattr(view, 'ordering_fields', self.ordering_fields)
if valid_fields is None: if valid_fields is None:
...@@ -152,18 +158,10 @@ class OrderingFilter(BaseFilterBackend): ...@@ -152,18 +158,10 @@ class OrderingFilter(BaseFilterBackend):
valid_fields = [field.name for field in queryset.model._meta.fields] valid_fields = [field.name for field in queryset.model._meta.fields]
valid_fields += queryset.query.aggregates.keys() valid_fields += queryset.query.aggregates.keys()
return [term for term in ordering if term.lstrip('-') in valid_fields] return [term for term in fields if term.lstrip('-') in valid_fields]
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
ordering = self.get_ordering(request) ordering = self.get_ordering(request, queryset, view)
if ordering:
# Skip any incorrect parameters
ordering = self.remove_invalid_fields(queryset, ordering, view)
if not ordering:
# Use 'ordering' attribute by default
ordering = self.get_default_ordering(view)
if ordering: if ordering:
return queryset.order_by(*ordering) return queryset.order_by(*ordering)
......
...@@ -2,29 +2,13 @@ ...@@ -2,29 +2,13 @@
Generic views that provide commonly needed behaviour. Generic views that provide commonly needed behaviour.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.paginator import Paginator, InvalidPage
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.http import Http404 from django.http import Http404
from django.shortcuts import get_object_or_404 as _get_object_or_404 from django.shortcuts import get_object_or_404 as _get_object_or_404
from django.utils import six
from django.utils.translation import ugettext as _
from rest_framework import views, mixins from rest_framework import views, mixins
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
def strict_positive_int(integer_string, cutoff=None):
"""
Cast a string to a strictly positive integer.
"""
ret = int(integer_string)
if ret <= 0:
raise ValueError()
if cutoff:
ret = min(ret, cutoff)
return ret
def get_object_or_404(queryset, *filter_args, **filter_kwargs): def get_object_or_404(queryset, *filter_args, **filter_kwargs):
""" """
Same as Django's standard shortcut, but make sure to also raise 404 Same as Django's standard shortcut, but make sure to also raise 404
...@@ -40,7 +24,6 @@ class GenericAPIView(views.APIView): ...@@ -40,7 +24,6 @@ class GenericAPIView(views.APIView):
""" """
Base class for all other generic views. Base class for all other generic views.
""" """
# You'll need to either set these attributes, # You'll need to either set these attributes,
# or override `get_queryset()`/`get_serializer_class()`. # or override `get_queryset()`/`get_serializer_class()`.
# If you are overriding a view method, it is important that you call # If you are overriding a view method, it is important that you call
...@@ -50,146 +33,16 @@ class GenericAPIView(views.APIView): ...@@ -50,146 +33,16 @@ class GenericAPIView(views.APIView):
queryset = None queryset = None
serializer_class = None serializer_class = None
# If you want to use object lookups other than pk, set this attribute. # If you want to use object lookups other than pk, set 'lookup_field'.
# For more complex lookup requirements override `get_object()`. # For more complex lookup requirements override `get_object()`.
lookup_field = 'pk' lookup_field = 'pk'
lookup_url_kwarg = None lookup_url_kwarg = None
# Pagination settings
paginate_by = api_settings.PAGINATE_BY
paginate_by_param = api_settings.PAGINATE_BY_PARAM
max_paginate_by = api_settings.MAX_PAGINATE_BY
pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS
page_kwarg = 'page'
# The filter backend classes to use for queryset filtering # The filter backend classes to use for queryset filtering
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
# The following attribute may be subject to change, # The style to use for queryset pagination.
# and should be considered private API. pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
paginator_class = Paginator
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
def get_pagination_serializer(self, page):
"""
Return a serializer instance to use with paginated data.
"""
class SerializerClass(self.pagination_serializer_class):
class Meta:
object_serializer_class = self.get_serializer_class()
pagination_serializer_class = SerializerClass
context = self.get_serializer_context()
return pagination_serializer_class(instance=page, context=context)
def paginate_queryset(self, queryset):
"""
Paginate a queryset if required, either returning a page object,
or `None` if pagination is not configured for this view.
"""
page_size = self.get_paginate_by()
if not page_size:
return None
paginator = self.paginator_class(queryset, page_size)
page_kwarg = self.kwargs.get(self.page_kwarg)
page_query_param = self.request.query_params.get(self.page_kwarg)
page = page_kwarg or page_query_param or 1
try:
page_number = paginator.validate_number(page)
except InvalidPage:
if page == 'last':
page_number = paginator.num_pages
else:
raise Http404(_("Page is not 'last', nor can it be converted to an int."))
try:
page = paginator.page(page_number)
except InvalidPage as exc:
error_format = _('Invalid page (%(page_number)s): %(message)s')
raise Http404(error_format % {
'page_number': page_number,
'message': six.text_type(exc)
})
return page
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
You are unlikely to want to override this method, although you may need
to call it either from a list view, or from a custom `get_object`
method if you want to apply the configured filtering backend to the
default queryset.
"""
for backend in self.get_filter_backends():
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
def get_filter_backends(self):
"""
Returns the list of filter backends that this view requires.
"""
return list(self.filter_backends)
# The following methods provide default implementations
# that you may want to override for more complex cases.
def get_paginate_by(self):
"""
Return the size of pages to use with pagination.
If `PAGINATE_BY_PARAM` is set it will attempt to get the page size
from a named query parameter in the url, eg. ?page_size=100
Otherwise defaults to using `self.paginate_by`.
"""
if self.paginate_by_param:
try:
return strict_positive_int(
self.request.query_params[self.paginate_by_param],
cutoff=self.max_paginate_by
)
except (KeyError, ValueError):
pass
return self.paginate_by
def get_serializer_class(self):
"""
Return the class to use for the serializer.
Defaults to using `self.serializer_class`.
You may want to override this if you need to provide different
serializations depending on the incoming request.
(Eg. admins get full serialization, others get basic serialization)
"""
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
return self.serializer_class
def get_queryset(self): def get_queryset(self):
""" """
...@@ -246,6 +99,83 @@ class GenericAPIView(views.APIView): ...@@ -246,6 +99,83 @@ class GenericAPIView(views.APIView):
return obj return obj
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
def get_serializer_class(self):
"""
Return the class to use for the serializer.
Defaults to using `self.serializer_class`.
You may want to override this if you need to provide different
serializations depending on the incoming request.
(Eg. admins get full serialization, others get basic serialization)
"""
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
return self.serializer_class
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
You are unlikely to want to override this method, although you may need
to call it either from a list view, or from a custom `get_object`
method if you want to apply the configured filtering backend to the
default queryset.
"""
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
@property
def paginator(self):
"""
The paginator instance associated with the view, or `None`.
"""
if not hasattr(self, '_paginator'):
if self.pagination_class is None:
self._paginator = None
else:
self._paginator = self.pagination_class()
return self._paginator
def paginate_queryset(self, queryset):
"""
Return a single page of results, or `None` if pagination is disabled.
"""
if self.paginator is None:
return None
return self.paginator.paginate_queryset(queryset, self.request, view=self)
def get_paginated_response(self, data):
"""
Return a paginated style `Response` object for the given output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data)
# Concrete view classes that provide method handlers # Concrete view classes that provide method handlers
# by composing the mixin classes with the base view. # by composing the mixin classes with the base view.
......
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# 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-01-30 16:23+0000\n"
"PO-Revision-Date: 2015-01-30 16:27+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
"Language-Team: Arabic (http://www.transifex.com/projects/p/django-rest-framework/language/ar/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"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:69
msgid "Invalid basic header. No credentials provided."
msgstr ""
#: authentication.py:72
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr ""
#: authentication.py:78
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr ""
#: authentication.py:90
msgid "Invalid username/password."
msgstr "اسم المستخدم/كلمة السر غير صحيحين."
#: authentication.py:156
msgid "Invalid token header. No credentials provided."
msgstr ""
#: authentication.py:159
msgid "Invalid token header. Token string should not contain spaces."
msgstr ""
#: authentication.py:168
msgid "Invalid token."
msgstr ""
#: authentication.py:171
msgid "User inactive or deleted."
msgstr "المستخدم غير مفعل او تم حذفه."
#: exceptions.py:38
msgid "A server error occurred."
msgstr "حدث خطأ في المخدم."
#: exceptions.py:73
msgid "Malformed request."
msgstr ""
#: exceptions.py:78
msgid "Incorrect authentication credentials."
msgstr "بيانات الدخول غير صحيحة."
#: exceptions.py:83
msgid "Authentication credentials were not provided."
msgstr "لم يتم تزويد بيانات الدخول."
#: exceptions.py:88
msgid "You do not have permission to perform this action."
msgstr "ليس لديك صلاحية للقيام بهذا الإجراء."
#: exceptions.py:93
msgid "Not found."
msgstr "غير موجود."
#: exceptions.py:98
msgid "Method \"{method}\" not allowed."
msgstr ""
#: exceptions.py:109
msgid "Could not satisfy the request Accept header."
msgstr ""
#: exceptions.py:121
msgid "Unsupported media type \"{media_type}\" in request."
msgstr ""
#: exceptions.py:134
msgid "Request was throttled."
msgstr ""
#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155
msgid "This field is required."
msgstr "هذا الحقل مطلوب."
#: fields.py:154
msgid "This field may not be null."
msgstr "لا يمكن لهذا الحقل ان يكون فارغاً null."
#: fields.py:487 fields.py:515
msgid "\"{input}\" is not a valid boolean."
msgstr "\"{input}\" ليس قيمة منطقية."
#: fields.py:550
msgid "This field may not be blank."
msgstr "لا يمكن لهذا الحقل ان يكون فارغاً."
#: fields.py:551 fields.py:1324
msgid "Ensure this field has no more than {max_length} characters."
msgstr "تأكد ان الحقل لا يزيد عن {max_length} محرف."
#: fields.py:552
msgid "Ensure this field has at least {min_length} characters."
msgstr "تأكد ان الحقل {min_length} محرف على الاقل."
#: fields.py:587
msgid "Enter a valid email address."
msgstr "عليك ان تدخل بريد إلكتروني صالح."
#: fields.py:604
msgid "This value does not match the required pattern."
msgstr "هذه القيمة لا تطابق النمط المطلوب."
#: fields.py:615
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr ""
#: fields.py:627
msgid "Enter a valid URL."
msgstr "الرجاء إدخال رابط إلكتروني صالح."
#: fields.py:638
msgid "\"{value}\" is not a valid UUID."
msgstr ""
#: fields.py:657
msgid "A valid integer is required."
msgstr "الرجاء إدخال رقم صحيح صالح."
#: fields.py:658 fields.py:692 fields.py:725
msgid "Ensure this value is less than or equal to {max_value}."
msgstr "تأكد ان القيمة أقل أو تساوي {max_value}."
#: fields.py:659 fields.py:693 fields.py:726
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "تأكد ان القيمة أكبر أو تساوي {min_value}."
#: fields.py:660 fields.py:694 fields.py:730
msgid "String value too large."
msgstr ""
#: fields.py:691 fields.py:724
msgid "A valid number is required."
msgstr "الرجاء إدخال رقم صالح."
#: fields.py:727
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "تأكد ان القيمة لا تحوي أكثر من {max_digits} رقم."
#: fields.py:728
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr ""
#: fields.py:729
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
#: fields.py:813
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr "صيغة التاريخ و الوقت غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
#: fields.py:814
msgid "Expected a datetime but got a date."
msgstr ""
#: fields.py:878
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr "صيغة التاريخ غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
#: fields.py:879
msgid "Expected a date but got a datetime."
msgstr ""
#: fields.py:936
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "صيغة الوقت غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
#: fields.py:992 fields.py:1036
msgid "\"{input}\" is not a valid choice."
msgstr "\"{input}\" ليست واحدة من الخيارات الصالحة."
#: fields.py:1037 fields.py:1151 serializers.py:482
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1067
msgid "No file was submitted."
msgstr "لم يتم إرسال أي ملف."
#: fields.py:1068
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr ""
#: fields.py:1069
msgid "No filename could be determined."
msgstr ""
#: fields.py:1070
msgid "The submitted file is empty."
msgstr "الملف الذي تم إرساله فارغ."
#: fields.py:1071
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr "تأكد ان اسم الملف لا يحوي أكثر من {max_length} محرف (الإسم المرسل يحوي {length} محرف)."
#: fields.py:1113
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: fields.py:1188
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
msgstr "رقم الصفحة \"{page_number}\" غير صالح : {message}."
#: pagination.py:442
msgid "Invalid cursor"
msgstr ""
#: relations.py:133
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr "معرف العنصر \"{pk_value}\" غير صالح - العنصر غير موجود."
#: relations.py:134
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
#: relations.py:157
msgid "Invalid hyperlink - No URL match."
msgstr ""
#: relations.py:158
msgid "Invalid hyperlink - Incorrect URL match."
msgstr ""
#: relations.py:159
msgid "Invalid hyperlink - Object does not exist."
msgstr ""
#: relations.py:160
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
#: relations.py:295
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
#: relations.py:296
msgid "Invalid value."
msgstr "قيمة غير صالحة."
#: serializers.py:299
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr ""
#: validators.py:22
msgid "This field must be unique."
msgstr ""
#: validators.py:76
msgid "The fields {field_names} must make a unique set."
msgstr ""
#: validators.py:219
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
#: validators.py:234
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
#: validators.py:247
msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
#: versioning.py:39
msgid "Invalid version in \"Accept\" header."
msgstr ""
#: versioning.py:70 versioning.py:112
msgid "Invalid version in URL path."
msgstr ""
#: versioning.py:138
msgid "Invalid version in hostname."
msgstr ""
#: versioning.py:160
msgid "Invalid version in query parameter."
msgstr ""
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr "حساب المستخدم غير مفعل."
#: authtoken/serializers.py:23
msgid "Unable to log in with provided credentials."
msgstr "تعذر تسجيل الدخول بالبيانات التي ادخلتها."
#: authtoken/serializers.py:26
msgid "Must include \"username\" and \"password\"."
msgstr "يجب أن تتضمن \"اسم المستخدم\" و \"كلمة المرور\"."
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Thomas Tanner, 2015
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n"
"PO-Revision-Date: 2015-01-30 16:27+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
"Language-Team: German (http://www.transifex.com/projects/p/django-rest-framework/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentication.py:69
msgid "Invalid basic header. No credentials provided."
msgstr "Ungültiger basic header. Keine Zugangsdaten angegeben."
#: authentication.py:72
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr "Ungültiger basic header. Zugangsdaten sollen keine Leerzeichen enthalten."
#: authentication.py:78
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr "Ungültiger basic header. Zugangsdaten sind nicht korrekt mit base64 kodiert."
#: authentication.py:90
msgid "Invalid username/password."
msgstr "Ungültiger Benutzername/Passwort"
#: authentication.py:156
msgid "Invalid token header. No credentials provided."
msgstr "Ungültiger token header. Keine Zugangsdaten angegeben."
#: authentication.py:159
msgid "Invalid token header. Token string should not contain spaces."
msgstr "Ungültiger token header. Zugangsdaten sollen keine Leerzeichen enthalten."
#: authentication.py:168
msgid "Invalid token."
msgstr "Ungültiges Token"
#: authentication.py:171
msgid "User inactive or deleted."
msgstr "Benutzer inaktiv oder gelöscht."
#: exceptions.py:38
msgid "A server error occurred."
msgstr "Ein Serverfehler ist aufgetreten."
#: exceptions.py:73
msgid "Malformed request."
msgstr "Fehlerhafte Anfrage."
#: exceptions.py:78
msgid "Incorrect authentication credentials."
msgstr "Falsche Anmeldedaten."
#: exceptions.py:83
msgid "Authentication credentials were not provided."
msgstr "Anmeldedaten fehlen."
#: exceptions.py:88
msgid "You do not have permission to perform this action."
msgstr "Sie sind nicht berechtigt, diese Aktion durchzuführen."
#: exceptions.py:93
msgid "Not found."
msgstr "Nicht gefunden."
#: exceptions.py:98
msgid "Method \"{method}\" not allowed."
msgstr "Methode \"{method}\" nicht erlaubt."
#: exceptions.py:109
msgid "Could not satisfy the request Accept header."
msgstr "Kann den Accept header der Anfrage nicht erfüllen."
#: exceptions.py:121
msgid "Unsupported media type \"{media_type}\" in request."
msgstr "Nicht unterstützter Medientyp \"{media_type}\" in der Anfrage."
#: exceptions.py:134
msgid "Request was throttled."
msgstr "Die Anfrage wurde gedrosselt."
#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155
msgid "This field is required."
msgstr "Dieses Feld ist erforderlich."
#: fields.py:154
msgid "This field may not be null."
msgstr "Dieses Feld darf nicht Null sein."
#: fields.py:487 fields.py:515
msgid "\"{input}\" is not a valid boolean."
msgstr "\"{input}\" ist kein gültiger Boole'scher Wert."
#: fields.py:550
msgid "This field may not be blank."
msgstr "Dieses Feld darf nicht leer sein."
#: fields.py:551 fields.py:1324
msgid "Ensure this field has no more than {max_length} characters."
msgstr "Stelle sicher, dass dieses Feld nicht mehr als {max_length} Zeichen lang ist."
#: fields.py:552
msgid "Ensure this field has at least {min_length} characters."
msgstr "Stelle sicher, dass dieses Feld mindestens {min_length} Zeichen lang ist."
#: fields.py:587
msgid "Enter a valid email address."
msgstr "Gebe eine gültige E-Mail Adresse an."
#: fields.py:604
msgid "This value does not match the required pattern."
msgstr "Dieser Wert passt nicht zu dem erforderlichen Muster."
#: fields.py:615
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr "Gebe ein gültiges \"slug\" aus Buchstaben, Ziffern, Unterstrichen und Minuszeichen ein."
#: fields.py:627
msgid "Enter a valid URL."
msgstr "Gebe eine gültige URL ein."
#: fields.py:638
msgid "\"{value}\" is not a valid UUID."
msgstr ""
#: fields.py:657
msgid "A valid integer is required."
msgstr "Eine gültige Ganzzahl ist erforderlich."
#: fields.py:658 fields.py:692 fields.py:725
msgid "Ensure this value is less than or equal to {max_value}."
msgstr "Stelle sicher, dass dieser Wert kleiner oder gleich {max_value} ist."
#: fields.py:659 fields.py:693 fields.py:726
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "Stelle sicher, dass dieser Wert größer oder gleich {max_value} ist."
#: fields.py:660 fields.py:694 fields.py:730
msgid "String value too large."
msgstr "Zeichenkette zu lang."
#: fields.py:691 fields.py:724
msgid "A valid number is required."
msgstr "Eine gültige Zahl ist erforderlich."
#: fields.py:727
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "Stelle sicher, dass es insgesamt nicht mehr als {max_digits} Ziffern lang ist."
#: fields.py:728
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr "Stelle sicher, dass es nicht mehr als {max_decimal_places} Nachkommastellen lang ist."
#: fields.py:729
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr "Stelle sicher, dass es nicht mehr als {max_whole_places} Stellen vor dem Komma lang ist."
#: fields.py:813
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr "Datum- und Zeitangabe hat das falsche Format. Nutze stattdessen eines dieser Formate: {format}."
#: fields.py:814
msgid "Expected a datetime but got a date."
msgstr "Erwarte eine Datum- und Zeitangabe, erhielt aber ein Datum."
#: fields.py:878
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr "Datum hat das falsche Format. Nutze stattdessen eines dieser Formate: {format}."
#: fields.py:879
msgid "Expected a date but got a datetime."
msgstr "Erwarte ein Datum, erhielt aber eine Datum- und Zeitangabe."
#: fields.py:936
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "Zeitangabe hat das falsche Format. Nutze stattdessen eines dieser Formate: {format}."
#: fields.py:992 fields.py:1036
msgid "\"{input}\" is not a valid choice."
msgstr "\"{input}\" ist keine gültige Option."
#: fields.py:1037 fields.py:1151 serializers.py:482
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr "Erwarte eine Liste von Elementen, erhielt aber den Typ \"{input_type}\"."
#: fields.py:1067
msgid "No file was submitted."
msgstr "Es wurde keine Datei übermittelt."
#: fields.py:1068
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr "Die übermittelten Daten sind keine Datei. Prüfe den Kodierungstyp im Formular."
#: fields.py:1069
msgid "No filename could be determined."
msgstr "Der Dateiname konnte nicht ermittelt werden."
#: fields.py:1070
msgid "The submitted file is empty."
msgstr "Die übermittelte Datei ist leer."
#: fields.py:1071
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
#: fields.py:1113
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: fields.py:1188
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
msgstr ""
#: pagination.py:442
msgid "Invalid cursor"
msgstr ""
#: relations.py:133
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr ""
#: relations.py:134
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
#: relations.py:157
msgid "Invalid hyperlink - No URL match."
msgstr ""
#: relations.py:158
msgid "Invalid hyperlink - Incorrect URL match."
msgstr ""
#: relations.py:159
msgid "Invalid hyperlink - Object does not exist."
msgstr ""
#: relations.py:160
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
#: relations.py:295
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
#: relations.py:296
msgid "Invalid value."
msgstr "Ungültiger Wert."
#: serializers.py:299
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr "Ungültige Daten. Dictionary erwartet, aber {datatype} erhalten."
#: validators.py:22
msgid "This field must be unique."
msgstr "Dieses Feld muss eineindeutig sein."
#: validators.py:76
msgid "The fields {field_names} must make a unique set."
msgstr ""
#: validators.py:219
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
#: validators.py:234
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
#: validators.py:247
msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
#: versioning.py:39
msgid "Invalid version in \"Accept\" header."
msgstr ""
#: versioning.py:70 versioning.py:112
msgid "Invalid version in URL path."
msgstr ""
#: versioning.py:138
msgid "Invalid version in hostname."
msgstr ""
#: versioning.py:160
msgid "Invalid version in query parameter."
msgstr ""
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr "Benutzerkonto ist gesperrt."
#: authtoken/serializers.py:23
msgid "Unable to log in with provided credentials."
msgstr "Kann nicht mit den angegeben Zugangsdaten anmelden."
#: authtoken/serializers.py:26
msgid "Must include \"username\" and \"password\"."
msgstr "\"username\" und \"password\" sind erforderlich."
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: authentication.py:69
msgid "Invalid basic header. No credentials provided."
msgstr ""
#: authentication.py:72
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr ""
#: authentication.py:78
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr ""
#: authentication.py:90
msgid "Invalid username/password."
msgstr ""
#: authentication.py:156
msgid "Invalid token header. No credentials provided."
msgstr ""
#: authentication.py:159
msgid "Invalid token header. Token string should not contain spaces."
msgstr ""
#: authentication.py:168
msgid "Invalid token."
msgstr ""
#: authentication.py:171
msgid "User inactive or deleted."
msgstr ""
#: exceptions.py:38
msgid "A server error occurred."
msgstr ""
#: exceptions.py:73
msgid "Malformed request."
msgstr ""
#: exceptions.py:78
msgid "Incorrect authentication credentials."
msgstr ""
#: exceptions.py:83
msgid "Authentication credentials were not provided."
msgstr ""
#: exceptions.py:88
msgid "You do not have permission to perform this action."
msgstr ""
#: exceptions.py:93 views.py:77
msgid "Not found."
msgstr ""
#: exceptions.py:98
msgid "Method \"{method}\" not allowed."
msgstr ""
#: exceptions.py:109
msgid "Could not satisfy the request Accept header."
msgstr ""
#: exceptions.py:121
msgid "Unsupported media type \"{media_type}\" in request."
msgstr ""
#: exceptions.py:134
msgid "Request was throttled."
msgstr ""
#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155
msgid "This field is required."
msgstr ""
#: fields.py:154
msgid "This field may not be null."
msgstr ""
#: fields.py:487 fields.py:515
msgid "\"{input}\" is not a valid boolean."
msgstr ""
#: fields.py:550
msgid "This field may not be blank."
msgstr ""
#: fields.py:551 fields.py:1324
msgid "Ensure this field has no more than {max_length} characters."
msgstr ""
#: fields.py:552
msgid "Ensure this field has at least {min_length} characters."
msgstr ""
#: fields.py:587
msgid "Enter a valid email address."
msgstr ""
#: fields.py:604
msgid "This value does not match the required pattern."
msgstr ""
#: fields.py:615
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr ""
#: fields.py:627
msgid "Enter a valid URL."
msgstr ""
#: fields.py:638
msgid "\"{value}\" is not a valid UUID."
msgstr ""
#: fields.py:657
msgid "A valid integer is required."
msgstr ""
#: fields.py:658 fields.py:692 fields.py:725
msgid "Ensure this value is less than or equal to {max_value}."
msgstr ""
#: fields.py:659 fields.py:693 fields.py:726
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr ""
#: fields.py:660 fields.py:694 fields.py:730
msgid "String value too large."
msgstr ""
#: fields.py:691 fields.py:724
msgid "A valid number is required."
msgstr ""
#: fields.py:727
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr ""
#: fields.py:728
msgid "Ensure that there are no more than {max_decimal_places} decimal places."
msgstr ""
#: fields.py:729
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
#: fields.py:813
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:814
msgid "Expected a datetime but got a date."
msgstr ""
#: fields.py:878
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:879
msgid "Expected a date but got a datetime."
msgstr ""
#: fields.py:936
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:992 fields.py:1036
msgid "\"{input}\" is not a valid choice."
msgstr ""
#: fields.py:1037 fields.py:1151 serializers.py:482
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1067
msgid "No file was submitted."
msgstr ""
#: fields.py:1068
msgid "The submitted data was not a file. Check the encoding type on the form."
msgstr ""
#: fields.py:1069
msgid "No filename could be determined."
msgstr ""
#: fields.py:1070
msgid "The submitted file is empty."
msgstr ""
#: fields.py:1071
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
#: fields.py:1113
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: fields.py:1188
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
msgstr ""
#: pagination.py:442
msgid "Invalid cursor"
msgstr ""
#: relations.py:133
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr ""
#: relations.py:134
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
#: relations.py:157
msgid "Invalid hyperlink - No URL match."
msgstr ""
#: relations.py:158
msgid "Invalid hyperlink - Incorrect URL match."
msgstr ""
#: relations.py:159
msgid "Invalid hyperlink - Object does not exist."
msgstr ""
#: relations.py:160
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
#: relations.py:295
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
#: relations.py:296
msgid "Invalid value."
msgstr ""
#: serializers.py:299
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr ""
#: validators.py:22
msgid "This field must be unique."
msgstr ""
#: validators.py:76
msgid "The fields {field_names} must make a unique set."
msgstr ""
#: validators.py:219
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
#: validators.py:234
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
#: validators.py:247
msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
#: versioning.py:39
msgid "Invalid version in \"Accept\" header."
msgstr ""
#: versioning.py:70 versioning.py:112
msgid "Invalid version in URL path."
msgstr ""
#: versioning.py:138
msgid "Invalid version in hostname."
msgstr ""
#: versioning.py:160
msgid "Invalid version in query parameter."
msgstr ""
#: views.py:81
msgid "Permission denied."
msgstr ""
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr ""
#: authtoken/serializers.py:23
msgid "Unable to log in with provided credentials."
msgstr ""
#: authtoken/serializers.py:26
msgid "Must include \"username\" and \"password\"."
msgstr ""
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Tõnis Kärdi <tonis.kardi@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n"
"PO-Revision-Date: 2015-01-30 16:27+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
"Language-Team: Estonian (http://www.transifex.com/projects/p/django-rest-framework/language/et/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: et\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentication.py:69
msgid "Invalid basic header. No credentials provided."
msgstr ""
#: authentication.py:72
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr ""
#: authentication.py:78
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr ""
#: authentication.py:90
msgid "Invalid username/password."
msgstr "Vale kasutajatunnus/salasõna."
#: authentication.py:156
msgid "Invalid token header. No credentials provided."
msgstr ""
#: authentication.py:159
msgid "Invalid token header. Token string should not contain spaces."
msgstr ""
#: authentication.py:168
msgid "Invalid token."
msgstr ""
#: authentication.py:171
msgid "User inactive or deleted."
msgstr "Kasutaja on inaktiivne või kustutatud."
#: exceptions.py:38
msgid "A server error occurred."
msgstr ""
#: exceptions.py:73
msgid "Malformed request."
msgstr ""
#: exceptions.py:78
msgid "Incorrect authentication credentials."
msgstr ""
#: exceptions.py:83
msgid "Authentication credentials were not provided."
msgstr ""
#: exceptions.py:88
msgid "You do not have permission to perform this action."
msgstr ""
#: exceptions.py:93
msgid "Not found."
msgstr ""
#: exceptions.py:98
msgid "Method \"{method}\" not allowed."
msgstr ""
#: exceptions.py:109
msgid "Could not satisfy the request Accept header."
msgstr ""
#: exceptions.py:121
msgid "Unsupported media type \"{media_type}\" in request."
msgstr ""
#: exceptions.py:134
msgid "Request was throttled."
msgstr ""
#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155
msgid "This field is required."
msgstr "Väli on kohustuslik."
#: fields.py:154
msgid "This field may not be null."
msgstr "Väli ei tohi olla tühi."
#: fields.py:487 fields.py:515
msgid "\"{input}\" is not a valid boolean."
msgstr ""
#: fields.py:550
msgid "This field may not be blank."
msgstr ""
#: fields.py:551 fields.py:1324
msgid "Ensure this field has no more than {max_length} characters."
msgstr ""
#: fields.py:552
msgid "Ensure this field has at least {min_length} characters."
msgstr ""
#: fields.py:587
msgid "Enter a valid email address."
msgstr "Sisesta kehtiv e-posti aadress."
#: fields.py:604
msgid "This value does not match the required pattern."
msgstr "Väärtus ei ühti etteantud mustriga."
#: fields.py:615
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr ""
#: fields.py:627
msgid "Enter a valid URL."
msgstr "Sisesta korrektne URL."
#: fields.py:638
msgid "\"{value}\" is not a valid UUID."
msgstr ""
#: fields.py:657
msgid "A valid integer is required."
msgstr ""
#: fields.py:658 fields.py:692 fields.py:725
msgid "Ensure this value is less than or equal to {max_value}."
msgstr "Veendu, et väärtus on väiksem kui või võrdne väärtusega {max_value}. "
#: fields.py:659 fields.py:693 fields.py:726
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "Veendu, et väärtus on suurem kui või võrdne väärtusega {min_value}."
#: fields.py:660 fields.py:694 fields.py:730
msgid "String value too large."
msgstr "Sõne on liiga pikk."
#: fields.py:691 fields.py:724
msgid "A valid number is required."
msgstr ""
#: fields.py:727
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "Veendu, et kokku pole rohkem kui {max_digits}."
#: fields.py:728
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr "Veendu, et komakohti pole rohkem kui {max_decimal_places}. "
#: fields.py:729
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
#: fields.py:813
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:814
msgid "Expected a datetime but got a date."
msgstr ""
#: fields.py:878
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:879
msgid "Expected a date but got a datetime."
msgstr ""
#: fields.py:936
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:992 fields.py:1036
msgid "\"{input}\" is not a valid choice."
msgstr ""
#: fields.py:1037 fields.py:1151 serializers.py:482
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1067
msgid "No file was submitted."
msgstr ""
#: fields.py:1068
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr ""
#: fields.py:1069
msgid "No filename could be determined."
msgstr ""
#: fields.py:1070
msgid "The submitted file is empty."
msgstr ""
#: fields.py:1071
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
#: fields.py:1113
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: fields.py:1188
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
msgstr ""
#: pagination.py:442
msgid "Invalid cursor"
msgstr ""
#: relations.py:133
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr ""
#: relations.py:134
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
#: relations.py:157
msgid "Invalid hyperlink - No URL match."
msgstr ""
#: relations.py:158
msgid "Invalid hyperlink - Incorrect URL match."
msgstr ""
#: relations.py:159
msgid "Invalid hyperlink - Object does not exist."
msgstr ""
#: relations.py:160
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
#: relations.py:295
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
#: relations.py:296
msgid "Invalid value."
msgstr ""
#: serializers.py:299
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr ""
#: validators.py:22
msgid "This field must be unique."
msgstr ""
#: validators.py:76
msgid "The fields {field_names} must make a unique set."
msgstr ""
#: validators.py:219
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
#: validators.py:234
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
#: validators.py:247
msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
#: versioning.py:39
msgid "Invalid version in \"Accept\" header."
msgstr ""
#: versioning.py:70 versioning.py:112
msgid "Invalid version in URL path."
msgstr ""
#: versioning.py:138
msgid "Invalid version in hostname."
msgstr ""
#: versioning.py:160
msgid "Invalid version in query parameter."
msgstr ""
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr "Kasutajakonto on suletud"
#: authtoken/serializers.py:23
msgid "Unable to log in with provided credentials."
msgstr "Sisselogimine antud tunnusega ebaõnnestus."
#: authtoken/serializers.py:26
msgid "Must include \"username\" and \"password\"."
msgstr "Peab sisaldama \"kasutajatunnust\" ja \"slasõna\"."
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n"
"PO-Revision-Date: 2015-01-30 16:27+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
"Language-Team: Indonesian (http://www.transifex.com/projects/p/django-rest-framework/language/id/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: id\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: authentication.py:69
msgid "Invalid basic header. No credentials provided."
msgstr ""
#: authentication.py:72
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr ""
#: authentication.py:78
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr ""
#: authentication.py:90
msgid "Invalid username/password."
msgstr ""
#: authentication.py:156
msgid "Invalid token header. No credentials provided."
msgstr ""
#: authentication.py:159
msgid "Invalid token header. Token string should not contain spaces."
msgstr ""
#: authentication.py:168
msgid "Invalid token."
msgstr ""
#: authentication.py:171
msgid "User inactive or deleted."
msgstr ""
#: exceptions.py:38
msgid "A server error occurred."
msgstr ""
#: exceptions.py:73
msgid "Malformed request."
msgstr ""
#: exceptions.py:78
msgid "Incorrect authentication credentials."
msgstr ""
#: exceptions.py:83
msgid "Authentication credentials were not provided."
msgstr ""
#: exceptions.py:88
msgid "You do not have permission to perform this action."
msgstr ""
#: exceptions.py:93
msgid "Not found."
msgstr ""
#: exceptions.py:98
msgid "Method \"{method}\" not allowed."
msgstr ""
#: exceptions.py:109
msgid "Could not satisfy the request Accept header."
msgstr ""
#: exceptions.py:121
msgid "Unsupported media type \"{media_type}\" in request."
msgstr ""
#: exceptions.py:134
msgid "Request was throttled."
msgstr ""
#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155
msgid "This field is required."
msgstr ""
#: fields.py:154
msgid "This field may not be null."
msgstr ""
#: fields.py:487 fields.py:515
msgid "\"{input}\" is not a valid boolean."
msgstr ""
#: fields.py:550
msgid "This field may not be blank."
msgstr ""
#: fields.py:551 fields.py:1324
msgid "Ensure this field has no more than {max_length} characters."
msgstr ""
#: fields.py:552
msgid "Ensure this field has at least {min_length} characters."
msgstr ""
#: fields.py:587
msgid "Enter a valid email address."
msgstr ""
#: fields.py:604
msgid "This value does not match the required pattern."
msgstr ""
#: fields.py:615
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr ""
#: fields.py:627
msgid "Enter a valid URL."
msgstr ""
#: fields.py:638
msgid "\"{value}\" is not a valid UUID."
msgstr ""
#: fields.py:657
msgid "A valid integer is required."
msgstr ""
#: fields.py:658 fields.py:692 fields.py:725
msgid "Ensure this value is less than or equal to {max_value}."
msgstr ""
#: fields.py:659 fields.py:693 fields.py:726
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr ""
#: fields.py:660 fields.py:694 fields.py:730
msgid "String value too large."
msgstr ""
#: fields.py:691 fields.py:724
msgid "A valid number is required."
msgstr ""
#: fields.py:727
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr ""
#: fields.py:728
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr ""
#: fields.py:729
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
#: fields.py:813
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:814
msgid "Expected a datetime but got a date."
msgstr ""
#: fields.py:878
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:879
msgid "Expected a date but got a datetime."
msgstr ""
#: fields.py:936
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:992 fields.py:1036
msgid "\"{input}\" is not a valid choice."
msgstr ""
#: fields.py:1037 fields.py:1151 serializers.py:482
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1067
msgid "No file was submitted."
msgstr ""
#: fields.py:1068
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr ""
#: fields.py:1069
msgid "No filename could be determined."
msgstr ""
#: fields.py:1070
msgid "The submitted file is empty."
msgstr ""
#: fields.py:1071
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
#: fields.py:1113
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: fields.py:1188
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
msgstr ""
#: pagination.py:442
msgid "Invalid cursor"
msgstr ""
#: relations.py:133
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr ""
#: relations.py:134
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
#: relations.py:157
msgid "Invalid hyperlink - No URL match."
msgstr ""
#: relations.py:158
msgid "Invalid hyperlink - Incorrect URL match."
msgstr ""
#: relations.py:159
msgid "Invalid hyperlink - Object does not exist."
msgstr ""
#: relations.py:160
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
#: relations.py:295
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
#: relations.py:296
msgid "Invalid value."
msgstr ""
#: serializers.py:299
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr ""
#: validators.py:22
msgid "This field must be unique."
msgstr ""
#: validators.py:76
msgid "The fields {field_names} must make a unique set."
msgstr ""
#: validators.py:219
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
#: validators.py:234
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
#: validators.py:247
msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
#: versioning.py:39
msgid "Invalid version in \"Accept\" header."
msgstr ""
#: versioning.py:70 versioning.py:112
msgid "Invalid version in URL path."
msgstr ""
#: versioning.py:138
msgid "Invalid version in hostname."
msgstr ""
#: versioning.py:160
msgid "Invalid version in query parameter."
msgstr ""
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr ""
#: authtoken/serializers.py:23
msgid "Unable to log in with provided credentials."
msgstr ""
#: authtoken/serializers.py:26
msgid "Must include \"username\" and \"password\"."
msgstr ""
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Mattia Procopio <promat85@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n"
"PO-Revision-Date: 2015-01-30 16:27+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
"Language-Team: Italian (http://www.transifex.com/projects/p/django-rest-framework/language/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: it\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentication.py:69
msgid "Invalid basic header. No credentials provided."
msgstr ""
#: authentication.py:72
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr ""
#: authentication.py:78
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr ""
#: authentication.py:90
msgid "Invalid username/password."
msgstr "Nome utente/password non validi"
#: authentication.py:156
msgid "Invalid token header. No credentials provided."
msgstr "Header del token non valido. Credenziali non fornite."
#: authentication.py:159
msgid "Invalid token header. Token string should not contain spaces."
msgstr "Header del token non valido. Il contenuto del token non dovrebbe contenere spazi."
#: authentication.py:168
msgid "Invalid token."
msgstr "Token invalido."
#: authentication.py:171
msgid "User inactive or deleted."
msgstr "Utente inattivo o eliminato."
#: exceptions.py:38
msgid "A server error occurred."
msgstr "Errore del server."
#: exceptions.py:73
msgid "Malformed request."
msgstr "Richiesta malformata."
#: exceptions.py:78
msgid "Incorrect authentication credentials."
msgstr "Credenziali di autenticazione incorrette."
#: exceptions.py:83
msgid "Authentication credentials were not provided."
msgstr "Non sono state immesse le credenziali di autenticazione."
#: exceptions.py:88
msgid "You do not have permission to perform this action."
msgstr "Non hai l'autorizzazione per eseguire questa azione."
#: exceptions.py:93
msgid "Not found."
msgstr "Non trovato."
#: exceptions.py:98
msgid "Method \"{method}\" not allowed."
msgstr "Metodo \"{method}\" non consentito"
#: exceptions.py:109
msgid "Could not satisfy the request Accept header."
msgstr "Impossibile soddisfare l'header \"Accept\" presente nella richiesta."
#: exceptions.py:121
msgid "Unsupported media type \"{media_type}\" in request."
msgstr "Tipo di media \"{media_type}\"non supportato."
#: exceptions.py:134
msgid "Request was throttled."
msgstr ""
#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155
msgid "This field is required."
msgstr "Campo obbligatorio."
#: fields.py:154
msgid "This field may not be null."
msgstr "Il campo non puà essere nullo."
#: fields.py:487 fields.py:515
msgid "\"{input}\" is not a valid boolean."
msgstr "\"{input}\" non è un valido valore booleano."
#: fields.py:550
msgid "This field may not be blank."
msgstr "Questo campo non può essere omesso."
#: fields.py:551 fields.py:1324
msgid "Ensure this field has no more than {max_length} characters."
msgstr "Assicurati che questo campo non abbia più di {max_length} caratteri."
#: fields.py:552
msgid "Ensure this field has at least {min_length} characters."
msgstr "Assicurati che questo campo abbia almeno {max_length} caratteri."
#: fields.py:587
msgid "Enter a valid email address."
msgstr "Inserisci un indirizzo email valido."
#: fields.py:604
msgid "This value does not match the required pattern."
msgstr ""
#: fields.py:615
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr "Immetti uno \"slug\" valido che consista di lettere, numeri, underscore o trattini."
#: fields.py:627
msgid "Enter a valid URL."
msgstr "Inserisci un URL valido"
#: fields.py:638
msgid "\"{value}\" is not a valid UUID."
msgstr ""
#: fields.py:657
msgid "A valid integer is required."
msgstr "È richiesto un numero intero valido."
#: fields.py:658 fields.py:692 fields.py:725
msgid "Ensure this value is less than or equal to {max_value}."
msgstr "Assicurati che il valore sia minore o uguale a {max_value}."
#: fields.py:659 fields.py:693 fields.py:726
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "Assicurati che il valore sia maggiore o uguale a {min_value}."
#: fields.py:660 fields.py:694 fields.py:730
msgid "String value too large."
msgstr ""
#: fields.py:691 fields.py:724
msgid "A valid number is required."
msgstr "È richiesto un numero valido."
#: fields.py:727
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "Assicurati che non ci siano più di {max_digits} cifre in totale."
#: fields.py:728
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr "Assicurati che non ci siano più di {max_decimal_places} cifre decimali."
#: fields.py:729
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr "Assicurati che non ci siano più di {max_whole_digits} cifre prima del separatore decimale."
#: fields.py:813
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr "L'oggetto di tipo datetime è in un formato errato. Usa uno dei seguenti formati: {format}."
#: fields.py:814
msgid "Expected a datetime but got a date."
msgstr "Atteso un oggetto di tipo datetime ma l'oggetto ricevuto è di tipo date."
#: fields.py:878
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr "La data è in un formato errato. Usa uno dei seguenti formati: {format}."
#: fields.py:879
msgid "Expected a date but got a datetime."
msgstr "Atteso un oggetto di tipo date ma l'oggetto ricevuto è di tipo datetime."
#: fields.py:936
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:992 fields.py:1036
msgid "\"{input}\" is not a valid choice."
msgstr "\"{input}\" non è una scelta valida."
#: fields.py:1037 fields.py:1151 serializers.py:482
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr "Attesa una lista di oggetti ma l'oggetto ricevuto è di tipo \"{input_type}\"."
#: fields.py:1067
msgid "No file was submitted."
msgstr "Non è stato inviato alcun file."
#: fields.py:1068
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr ""
#: fields.py:1069
msgid "No filename could be determined."
msgstr "Il nome del file non può essere determinato."
#: fields.py:1070
msgid "The submitted file is empty."
msgstr "Il file inviato è vuoto."
#: fields.py:1071
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
#: fields.py:1113
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: fields.py:1188
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
msgstr ""
#: pagination.py:442
msgid "Invalid cursor"
msgstr ""
#: relations.py:133
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr ""
#: relations.py:134
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
#: relations.py:157
msgid "Invalid hyperlink - No URL match."
msgstr ""
#: relations.py:158
msgid "Invalid hyperlink - Incorrect URL match."
msgstr ""
#: relations.py:159
msgid "Invalid hyperlink - Object does not exist."
msgstr ""
#: relations.py:160
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
#: relations.py:295
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
#: relations.py:296
msgid "Invalid value."
msgstr "Valore non valido."
#: serializers.py:299
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr ""
#: validators.py:22
msgid "This field must be unique."
msgstr "Questo campo deve essere unico."
#: validators.py:76
msgid "The fields {field_names} must make a unique set."
msgstr ""
#: validators.py:219
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
#: validators.py:234
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
#: validators.py:247
msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
#: versioning.py:39
msgid "Invalid version in \"Accept\" header."
msgstr ""
#: versioning.py:70 versioning.py:112
msgid "Invalid version in URL path."
msgstr ""
#: versioning.py:138
msgid "Invalid version in hostname."
msgstr ""
#: versioning.py:160
msgid "Invalid version in query parameter."
msgstr ""
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr "L'account dell'utente è disabilitato"
#: authtoken/serializers.py:23
msgid "Unable to log in with provided credentials."
msgstr "Impossibile eseguire il log in con le credenziali immesse."
#: authtoken/serializers.py:26
msgid "Must include \"username\" and \"password\"."
msgstr "Deve includere \"nome utente\" e \"password\"."
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment