Commit 3dff9a4f by Tom Christie

Resolve merge conflict

parents c06a82d0 1f996128
...@@ -15,6 +15,7 @@ bin/ ...@@ -15,6 +15,7 @@ bin/
include/ include/
lib/ lib/
local/ local/
env/
!.gitignore !.gitignore
!.travis.yml !.travis.yml
...@@ -21,6 +21,10 @@ env: ...@@ -21,6 +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=py33-django18alpha
- TOX_ENV=py32-django18alpha
- TOX_ENV=py27-django18alpha
- TOX_ENV=py34-djangomaster - TOX_ENV=py34-djangomaster
- TOX_ENV=py33-djangomaster - TOX_ENV=py33-djangomaster
- TOX_ENV=py32-djangomaster - TOX_ENV=py32-djangomaster
......
[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
...@@ -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.5+, 1.6, 1.7) * Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7)
# Installation # Installation
...@@ -156,7 +156,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou ...@@ -156,7 +156,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
# License # License
Copyright (c) 2011-2014, 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
...@@ -190,18 +190,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -190,18 +190,17 @@ 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.html#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.html#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.html#serializers [serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers
[modelserializer-section]: http://www.django-rest-framework.org/api-guide/serializers.html#modelserializer [modelserializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#modelserializer
[functionview-section]: http://www.django-rest-framework.org/api-guide/views.html#function-based-views [functionview-section]: http://www.django-rest-framework.org/api-guide/views/#function-based-views
[generic-views]: http://www.django-rest-framework.org/api-guide/generic-views.html [generic-views]: http://www.django-rest-framework.org/api-guide/generic-views/
[viewsets]: http://www.django-rest-framework.org/api-guide/viewsets.html [viewsets]: http://www.django-rest-framework.org/api-guide/viewsets/
[routers]: http://www.django-rest-framework.org/api-guide/routers.html [routers]: http://www.django-rest-framework.org/api-guide/routers/
[serializers]: http://www.django-rest-framework.org/api-guide/serializers.html [serializers]: http://www.django-rest-framework.org/api-guide/serializers/
[authentication]: http://www.django-rest-framework.org/api-guide/authentication.html [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.html
[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
......
...@@ -34,7 +34,7 @@ The value of `request.user` and `request.auth` for unauthenticated requests can ...@@ -34,7 +34,7 @@ The value of `request.user` and `request.auth` for unauthenticated requests can
## Setting the authentication scheme ## Setting the authentication scheme
The default authentication schemes may be set globally, using the `DEFAULT_AUTHENTICATION` setting. For example. The default authentication schemes may be set globally, using the `DEFAULT_AUTHENTICATION_CLASSES` setting. For example.
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ( 'DEFAULT_AUTHENTICATION_CLASSES': (
...@@ -126,7 +126,6 @@ To use the `TokenAuthentication` scheme you'll need to [configure the authentica ...@@ -126,7 +126,6 @@ To use the `TokenAuthentication` scheme you'll need to [configure the authentica
'rest_framework.authtoken' 'rest_framework.authtoken'
) )
--- ---
**Note:** Make sure to run `manage.py syncdb` after changing your settings. The `rest_framework.authtoken` app provides both Django (from v1.7) and South database migrations. See [Schema migrations](#schema-migrations) below. **Note:** Make sure to run `manage.py syncdb` after changing your settings. The `rest_framework.authtoken` app provides both Django (from v1.7) and South database migrations. See [Schema migrations](#schema-migrations) below.
...@@ -249,8 +248,6 @@ Unauthenticated responses that are denied permission will result in an `HTTP 403 ...@@ -249,8 +248,6 @@ Unauthenticated responses that are denied permission will result in an `HTTP 403
If you're using an AJAX style API with SessionAuthentication, you'll need to make sure you include a valid CSRF token for any "unsafe" HTTP method calls, such as `PUT`, `PATCH`, `POST` or `DELETE` requests. See the [Django CSRF documentation][csrf-ajax] for more details. If you're using an AJAX style API with SessionAuthentication, you'll need to make sure you include a valid CSRF token for any "unsafe" HTTP method calls, such as `PUT`, `PATCH`, `POST` or `DELETE` requests. See the [Django CSRF documentation][csrf-ajax] for more details.
---
# Custom authentication # Custom authentication
To implement a custom authentication scheme, subclass `BaseAuthentication` and override the `.authenticate(self, request)` method. The method should return a two-tuple of `(user, auth)` if authentication succeeds, or `None` otherwise. To implement a custom authentication scheme, subclass `BaseAuthentication` and override the `.authenticate(self, request)` method. The method should return a two-tuple of `(user, auth)` if authentication succeeds, or `None` otherwise.
...@@ -293,13 +290,48 @@ The following example will authenticate any incoming request as the user given b ...@@ -293,13 +290,48 @@ The following example will authenticate any incoming request as the user given b
The following third party packages are also available. The following third party packages are also available.
## Digest Authentication ## Django OAuth Toolkit
HTTP digest authentication is a widely implemented scheme that was intended to replace HTTP basic authentication, and which provides a simple encrypted authentication mechanism. [Juan Riaza][juanriaza] maintains the [djangorestframework-digestauth][djangorestframework-digestauth] package which provides HTTP digest authentication support for REST framework. The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+. The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**.
## Django OAuth Toolkit #### Installation & configuration
Install using `pip`.
pip install django-oauth-toolkit
Add the package to your `INSTALLED_APPS` and modify your REST framework settings.
INSTALLED_APPS = (
...
'oauth2_provider',
)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.ext.rest_framework.OAuth2Authentication',
)
}
For more details see the [Django REST framework - Getting started][django-oauth-toolkit-getting-started] documentation.
## Django REST framework OAuth
The [Django REST framework OAuth][django-rest-framework-oauth] package provides both OAuth1 and OAuth2 support for REST framework.
This package was previously included directly in REST framework but is now supported and maintained as a third party package.
The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+. The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and comes as a recommended alternative for OAuth 2.0 support. #### Installation & configuration
Install the package using `pip`.
pip install djangorestframework-oauth
For details on configuration and usage see the Django REST framework OAuth documentation for [authentication][django-rest-framework-oauth-authentication] and [permissions][django-rest-framework-oauth-permissions].
## Digest Authentication
HTTP digest authentication is a widely implemented scheme that was intended to replace HTTP basic authentication, and which provides a simple encrypted authentication mechanism. [Juan Riaza][juanriaza] maintains the [djangorestframework-digestauth][djangorestframework-digestauth] package which provides HTTP digest authentication support for REST framework.
## Django OAuth2 Consumer ## Django OAuth2 Consumer
...@@ -328,10 +360,14 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a ...@@ -328,10 +360,14 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
[oauth]: http://oauth.net/2/ [oauth]: http://oauth.net/2/
[permission]: permissions.md [permission]: permissions.md
[throttling]: throttling.md [throttling]: throttling.md
[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax [csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
[mod_wsgi_official]: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPassAuthorization [mod_wsgi_official]: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPassAuthorization
[custom-user-model]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#specifying-a-custom-user-model [custom-user-model]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#specifying-a-custom-user-model
[south-dependencies]: http://south.readthedocs.org/en/latest/dependencies.html [south-dependencies]: http://south.readthedocs.org/en/latest/dependencies.html
[django-oauth-toolkit-getting-started]: https://django-oauth-toolkit.readthedocs.org/en/latest/rest-framework/getting_started.html
[django-rest-framework-oauth]: http://jpadilla.github.io/django-rest-framework-oauth/
[django-rest-framework-oauth-authentication]: http://jpadilla.github.io/django-rest-framework-oauth/authentication/
[django-rest-framework-oauth-permissions]: http://jpadilla.github.io/django-rest-framework-oauth/permissions/
[juanriaza]: https://github.com/juanriaza [juanriaza]: https://github.com/juanriaza
[djangorestframework-digestauth]: https://github.com/juanriaza/django-rest-framework-digestauth [djangorestframework-digestauth]: https://github.com/juanriaza/django-rest-framework-digestauth
[oauth-1.0a]: http://oauth.net/core/1.0a [oauth-1.0a]: http://oauth.net/core/1.0a
......
...@@ -18,7 +18,7 @@ The handled exceptions are: ...@@ -18,7 +18,7 @@ The handled exceptions are:
In each case, REST framework will return a response with an appropriate status code and content-type. The body of the response will include any additional details regarding the nature of the error. In each case, REST framework will return a response with an appropriate status code and content-type. The body of the response will include any additional details regarding the nature of the error.
By default all error responses will include a key `detail` in the body of the response, but other keys may also be included. Most error responses will include a key `detail` in the body of the response.
For example, the following request: For example, the following request:
...@@ -33,6 +33,16 @@ Might receive an error response indicating that the `DELETE` method is not allow ...@@ -33,6 +33,16 @@ Might receive an error response indicating that the `DELETE` method is not allow
{"detail": "Method 'DELETE' not allowed."} {"detail": "Method 'DELETE' not allowed."}
Validation errors are handled slightly differently, and will include the field names as the keys in the response. If the validation error was not specific to a particular field then it will use the "non_field_errors" key, or whatever string value has been set for the `NON_FIELD_ERRORS_KEY` setting.
Any example validation error might look like this:
HTTP/1.1 400 Bad Request
Content-Type: application/json
Content-Length: 94
{"amount": ["A valid integer is required."], "description": ["This field may not be blank."]}
## Custom exception handling ## Custom exception handling
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.
......
...@@ -41,6 +41,8 @@ Defaults to `False` ...@@ -41,6 +41,8 @@ Defaults to `False`
Normally an error will be raised if a field is not supplied during deserialization. Normally an error will be raised if a field is not supplied during deserialization.
Set to false if this field is not required to be present during deserialization. Set to false if this field is not required to be present during deserialization.
Setting this to `False` also allows the object attribute or dictionary key to be omitted from output when serializing the instance. If the key is not present it will simply not be included in the output representation.
Defaults to `True`. Defaults to `True`.
### `allow_null` ### `allow_null`
...@@ -180,6 +182,12 @@ Corresponds to `django.db.models.fields.URLField`. Uses Django's `django.core.v ...@@ -180,6 +182,12 @@ Corresponds to `django.db.models.fields.URLField`. Uses Django's `django.core.v
**Signature:** `URLField(max_length=200, min_length=None, allow_blank=False)` **Signature:** `URLField(max_length=200, min_length=None, allow_blank=False)`
## UUIDField
A field that ensures the input is a valid UUID string. The `to_internal_value` method will return a `uuid.UUID` instance. On output the field will return a string in the canonical hyphenated format, for example:
"de305d54-75b4-431b-adb2-eb6b9e546013"
--- ---
# Numeric fields # Numeric fields
...@@ -318,7 +326,7 @@ Both the `allow_blank` and `allow_null` are valid options on `ChoiceField`, alth ...@@ -318,7 +326,7 @@ Both the `allow_blank` and `allow_null` are valid options on `ChoiceField`, alth
## MultipleChoiceField ## MultipleChoiceField
A field that can accept a set of zero, one or many values, chosen from a limited set of choices. Takes a single mandatory argument. `to_internal_representation` returns a `set` containing the selected values. A field that can accept a set of zero, one or many values, chosen from a limited set of choices. Takes a single mandatory argument. `to_internal_value` returns a `set` containing the selected values.
**Signature:** `MultipleChoiceField(choices)` **Signature:** `MultipleChoiceField(choices)`
...@@ -372,7 +380,7 @@ A field class that validates a list of objects. ...@@ -372,7 +380,7 @@ A field class that validates a list of objects.
**Signature**: `ListField(child)` **Signature**: `ListField(child)`
- `child` - A field instance that should be used for validating the objects in the list. - `child` - A field instance that should be used for validating the objects in the list. If this argument is not provided then objects in the list will not be validated.
For example, to validate a list of integers you might use something like the following: For example, to validate a list of integers you might use something like the following:
...@@ -387,6 +395,23 @@ The `ListField` class also supports a declarative style that allows you to write ...@@ -387,6 +395,23 @@ The `ListField` class also supports a declarative style that allows you to write
We can now reuse our custom `StringListField` class throughout our application, without having to provide a `child` argument to it. We can now reuse our custom `StringListField` class throughout our application, without having to provide a `child` argument to it.
## DictField
A field class that validates a dictionary of objects. The keys in `DictField` are always assumed to be string values.
**Signature**: `DictField(child)`
- `child` - A field instance that should be used for validating the values in the dictionary. If this argument is not provided then values in the mapping will not be validated.
For example, to create a field that validates a mapping of strings to strings, you would write something like this:
document = DictField(child=CharField())
You can also use the declarative style, as with `ListField`. For example:
class DocumentField(DictField):
child = CharField()
--- ---
# Miscellaneous fields # Miscellaneous fields
...@@ -436,7 +461,7 @@ This is a read-only field. It gets its value by calling a method on the serializ ...@@ -436,7 +461,7 @@ This is a read-only field. It gets its value by calling a method on the serializ
**Signature**: `SerializerMethodField(method_name=None)` **Signature**: `SerializerMethodField(method_name=None)`
- `method-name` - The name of the method on the serializer to be called. If not included this defaults to `get_<field_name>`. - `method_name` - The name of the method on the serializer to be called. If not included this defaults to `get_<field_name>`.
The serializer method referred to by the `method_name` argument should accept a single argument (in addition to `self`), which is the object being serialized. It should return whatever you want to be included in the serialized representation of the object. For example: The serializer method referred to by the `method_name` argument should accept a single argument (in addition to `self`), which is the object being serialized. It should return whatever you want to be included in the serialized representation of the object. For example:
...@@ -480,7 +505,7 @@ Let's look at an example of serializing a class that represents an RGB color val ...@@ -480,7 +505,7 @@ Let's look at an example of serializing a class that represents an RGB color val
class ColorField(serializers.Field): class ColorField(serializers.Field):
""" """
Color objects are serialized into "rgb(#, #, #)" notation. Color objects are serialized into 'rgb(#, #, #)' notation.
""" """
def to_representation(self, obj): def to_representation(self, obj):
return "rgb(%d, %d, %d)" % (obj.red, obj.green, obj.blue) return "rgb(%d, %d, %d)" % (obj.red, obj.green, obj.blue)
......
...@@ -316,6 +316,7 @@ Typically you'd instead control this by setting `order_by` on the initial querys ...@@ -316,6 +316,7 @@ Typically you'd instead control this by setting `order_by` on the initial querys
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserSerializer serializer_class = UserSerializer
filter_backends = (filters.OrderingFilter,) filter_backends = (filters.OrderingFilter,)
ordering_fields = ('username', 'email')
ordering = ('username',) ordering = ('username',)
The `ordering` attribute may be either a string or a list/tuple of strings. The `ordering` attribute may be either a string or a list/tuple of strings.
...@@ -390,16 +391,16 @@ We could achieve the same behavior by overriding `get_queryset()` on the views, ...@@ -390,16 +391,16 @@ We could achieve the same behavior by overriding `get_queryset()` on the views,
The following third party packages provide additional filter implementations. The following third party packages provide additional filter implementations.
## Django REST framework chain ## Django REST framework filters package
The [django-rest-framework-chain package][django-rest-framework-chain] works together with the `DjangoFilterBackend` class, and allows you to easily create filters across relationships, or create multiple filter lookup types for a given field. The [django-rest-framework-filters package][django-rest-framework-filters] works together with the `DjangoFilterBackend` class, and allows you to easily create filters across relationships, or create multiple filter lookup types for a given field.
[cite]: https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-specific-objects-with-filters [cite]: https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-specific-objects-with-filters
[django-filter]: https://github.com/alex/django-filter [django-filter]: https://github.com/alex/django-filter
[django-filter-docs]: https://django-filter.readthedocs.org/en/latest/index.html [django-filter-docs]: https://django-filter.readthedocs.org/en/latest/index.html
[guardian]: http://pythonhosted.org/django-guardian/ [guardian]: https://django-guardian.readthedocs.org/
[view-permissions]: http://pythonhosted.org/django-guardian/userguide/assign.html [view-permissions]: https://django-guardian.readthedocs.org/en/latest/userguide/assign.html
[view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models [view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models
[nullbooleanselect]: https://github.com/django/django/blob/master/django/forms/widgets.py [nullbooleanselect]: https://github.com/django/django/blob/master/django/forms/widgets.py
[search-django-admin]: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields [search-django-admin]: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
[django-rest-framework-chain]: https://github.com/philipn/django-rest-framework-chain [django-rest-framework-filters]: https://github.com/philipn/django-rest-framework-filters
...@@ -55,6 +55,18 @@ The name of the kwarg used may be modified by using the `FORMAT_SUFFIX_KWARG` se ...@@ -55,6 +55,18 @@ The name of the kwarg used may be modified by using the `FORMAT_SUFFIX_KWARG` se
Also note that `format_suffix_patterns` does not support descending into `include` URL patterns. Also note that `format_suffix_patterns` does not support descending into `include` URL patterns.
### Using with `i18n_patterns`
If using the `i18n_patterns` function provided by Django, as well as `format_suffix_patterns` you should make sure that the `i18n_patterns` function is applied as the final, or outermost function. For example:
url patterns = [
]
urlpatterns = i18n_patterns(
format_suffix_patterns(urlpatterns, allowed=['json', 'html'])
)
--- ---
## Accept headers vs. format suffixes ## Accept headers vs. format suffixes
......
...@@ -93,17 +93,13 @@ The following attributes are used to control pagination when used with list view ...@@ -93,17 +93,13 @@ The following attributes are used to control pagination when used with list view
* `filter_backends` - A list of filter backend classes that should be used for filtering the queryset. Defaults to the same value as the `DEFAULT_FILTER_BACKENDS` setting. * `filter_backends` - A list of filter backend classes that should be used for filtering the queryset. Defaults to the same value as the `DEFAULT_FILTER_BACKENDS` setting.
**Deprecated attributes**:
* `model` - This shortcut may be used instead of setting either (or both) of the `queryset`/`serializer_class` attributes. The explicit style is preferred over the `.model` shortcut, and usage of this attribute is now deprecated.
### Methods ### Methods
**Base methods**: **Base methods**:
#### `get_queryset(self)` #### `get_queryset(self)`
Returns the queryset that should be used for list views, and that should be used as the base for lookups in detail views. Defaults to returning the queryset specified by the `queryset` attribute, or the default queryset for the model if the `model` shortcut is being used. Returns the queryset that should be used for list views, and that should be used as the base for lookups in detail views. Defaults to returning the queryset specified by the `queryset` attribute.
This method should always be used rather than accessing `self.queryset` directly, as `self.queryset` gets evaluated only once, and those results are cached for all subsequent requests. This method should always be used rather than accessing `self.queryset` directly, as `self.queryset` gets evaluated only once, and those results are cached for all subsequent requests.
...@@ -153,7 +149,7 @@ For example: ...@@ -153,7 +149,7 @@ For example:
#### `get_serializer_class(self)` #### `get_serializer_class(self)`
Returns the class that should be used for the serializer. Defaults to returning the `serializer_class` attribute, or dynamically generating a serializer class if the `model` shortcut is being used. Returns the class that should be used for the serializer. Defaults to returning the `serializer_class` attribute.
May be overridden to provide dynamic behavior, such as using different serializers for read and write operations, or providing different serializers to different types of users. May be overridden to provide dynamic behavior, such as using different serializers for read and write operations, or providing different serializers to different types of users.
...@@ -214,6 +210,8 @@ You won't typically need to override the following methods, although you might n ...@@ -214,6 +210,8 @@ You won't typically need to override the following methods, although you might n
The mixin classes provide the actions that are used to provide the basic view behavior. Note that the mixin classes provide action methods rather than defining the handler methods, such as `.get()` and `.post()`, directly. This allows for more flexible composition of behavior. The mixin classes provide the actions that are used to provide the basic view behavior. Note that the mixin classes provide action methods rather than defining the handler methods, such as `.get()` and `.post()`, directly. This allows for more flexible composition of behavior.
The mixin classes can be imported from `rest_framework.mixins`.
## ListModelMixin ## ListModelMixin
Provides a `.list(request, *args, **kwargs)` method, that implements listing a queryset. Provides a `.list(request, *args, **kwargs)` method, that implements listing a queryset.
...@@ -258,6 +256,8 @@ If an object is deleted this returns a `204 No Content` response, otherwise it w ...@@ -258,6 +256,8 @@ If an object is deleted this returns a `204 No Content` response, otherwise it w
The following classes are the concrete generic views. If you're using generic views this is normally the level you'll be working at unless you need heavily customized behavior. The following classes are the concrete generic views. If you're using generic views this is normally the level you'll be working at unless you need heavily customized behavior.
The view classes can be imported from `rest_framework.generics`.
## CreateAPIView ## CreateAPIView
Used for **create-only** endpoints. Used for **create-only** endpoints.
......
...@@ -26,7 +26,7 @@ As an example, if you are sending `json` encoded data using jQuery with the [.aj ...@@ -26,7 +26,7 @@ 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 `JSON` 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': (
...@@ -108,7 +108,7 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword ...@@ -108,7 +108,7 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword
def put(self, request, filename, format=None): def put(self, request, filename, format=None):
file_obj = request.data['file'] file_obj = request.data['file']
# ... # ...
# do some staff with uploaded file # do some stuff with uploaded file
# ... # ...
return Response(status=204) return Response(status=204)
...@@ -162,6 +162,48 @@ The following is an example plaintext parser that will populate the `request.dat ...@@ -162,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.
...@@ -173,6 +215,9 @@ The following third party packages are also available. ...@@ -173,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
......
...@@ -231,6 +231,7 @@ This field is always read-only. ...@@ -231,6 +231,7 @@ This field is always read-only.
* `view_name` - The view name that should be used as the target of the relationship. If you're using [the standard router classes][routers] this will be a string with the format `<model_name>-detail`. **required**. * `view_name` - The view name that should be used as the target of the relationship. If you're using [the standard router classes][routers] this will be a string with the format `<model_name>-detail`. **required**.
* `lookup_field` - The field on the target that should be used for the lookup. Should correspond to a URL keyword argument on the referenced view. Default is `'pk'`. * `lookup_field` - The field on the target that should be used for the lookup. Should correspond to a URL keyword argument on the referenced view. Default is `'pk'`.
* `lookup_url_kwarg` - The name of the keyword argument defined in the URL conf that corresponds to the lookup field. Defaults to using the same value as `lookup_field`.
* `format` - If using format suffixes, hyperlinked fields will use the same format suffix for the target unless overridden by using the `format` argument. * `format` - If using format suffixes, hyperlinked fields will use the same format suffix for the target unless overridden by using the `format` argument.
--- ---
......
...@@ -342,6 +342,74 @@ Templates will render with a `RequestContext` which includes the `status_code` a ...@@ -342,6 +342,74 @@ Templates will render with a `RequestContext` which includes the `status_code` a
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',
),
}
## JSONP
[REST framework JSONP][rest-framework-jsonp] provides JSONP rendering support. It was previously included directly in the REST framework package, and is now instead supported as a third-party package.
---
**Warning**: If you require cross-domain AJAX requests, you should generally be using the more modern approach of [CORS][cors] as an alternative to `JSONP`. See the [CORS documentation][cors-docs] for more details.
The `jsonp` approach is essentially a browser hack, and is [only appropriate for globally readable API endpoints][jsonp-security], where `GET` requests are unauthenticated and do not require any user permissions.
---
#### Installation & configuration
Install using pip.
$ pip install djangorestframework-jsonp
Modify your REST framework settings.
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework_yaml.renderers.JSONPRenderer',
),
}
## 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.
...@@ -358,7 +426,6 @@ Comma-separated values are a plain-text tabular data format, that can be easily ...@@ -358,7 +426,6 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[djangorestframework-camel-case] provides camel case JSON renderers and parsers for REST framework. This allows serializers to use Python-style underscored field names, but be exposed in the API as Javascript-style camel case field names. It is maintained by [Vitaly Babiy][vbabiy]. [djangorestframework-camel-case] provides camel case JSON renderers and parsers for REST framework. This allows serializers to use Python-style underscored field names, but be exposed in the API as Javascript-style camel case field names. It is maintained by [Vitaly Babiy][vbabiy].
## Pandas (CSV, Excel, PNG) ## Pandas (CSV, Excel, PNG)
[Django REST Pandas] provides a serializer and renderers that support additional data processing and output via the [Pandas] DataFrame API. Django REST Pandas includes renderers for Pandas-style CSV files, Excel workbooks (both `.xls` and `.xlsx`), and a number of [other formats]. It is maintained by [S. Andrew Sheppard][sheppard] as part of the [wq Project][wq]. [Django REST Pandas] provides a serializer and renderers that support additional data processing and output via the [Pandas] DataFrame API. Django REST Pandas includes renderers for Pandas-style CSV files, Excel workbooks (both `.xls` and `.xlsx`), and a number of [other formats]. It is maintained by [S. Andrew Sheppard][sheppard] as part of the [wq Project][wq].
...@@ -373,10 +440,19 @@ Comma-separated values are a plain-text tabular data format, that can be easily ...@@ -373,10 +440,19 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[application/vnd.github+json]: http://developer.github.com/v3/media/ [application/vnd.github+json]: http://developer.github.com/v3/media/
[application/vnd.collection+json]: http://www.amundsen.com/media-types/collection/ [application/vnd.collection+json]: http://www.amundsen.com/media-types/collection/
[django-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views [django-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views
[rest-framework-jsonp]: http://jpadilla.github.io/django-rest-framework-jsonp/
[cors]: http://www.w3.org/TR/cors/
[cors-docs]: http://www.django-rest-framework.org/topics/ajax-csrf-cors/
[jsonp-security]: http://stackoverflow.com/questions/613962/is-jsonp-safe-to-use
[rest-framework-yaml]: http://jpadilla.github.io/django-rest-framework-yaml/
[rest-framework-xml]: http://jpadilla.github.io/django-rest-framework-xml/
[messagepack]: http://msgpack.org/ [messagepack]: http://msgpack.org/
[juanriaza]: https://github.com/juanriaza [juanriaza]: https://github.com/juanriaza
[mjumbewu]: https://github.com/mjumbewu [mjumbewu]: https://github.com/mjumbewu
[vbabiy]: https://github.com/vbabiy [vbabiy]: https://github.com/vbabiy
[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/
[djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack [djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack
[djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv [djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv
[ultrajson]: https://github.com/esnme/ultrajson [ultrajson]: https://github.com/esnme/ultrajson
......
...@@ -28,7 +28,7 @@ There are two mandatory arguments to the `register()` method: ...@@ -28,7 +28,7 @@ There are two mandatory arguments to the `register()` method:
Optionally, you may also specify an additional argument: Optionally, you may also specify an additional argument:
* `base_name` - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the `model` or `queryset` attribute on the viewset, if it has one. Note that if the viewset does not include a `model` or `queryset` attribute then you must set `base_name` when registering the viewset. * `base_name` - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the `queryset` attribute of the viewset, if it has one. Note that if the viewset does not include a `queryset` attribute then you must set `base_name` when registering the viewset.
The example above would generate the following URL patterns: The example above would generate the following URL patterns:
...@@ -49,6 +49,38 @@ This means you'll need to explicitly set the `base_name` argument when registeri ...@@ -49,6 +49,38 @@ This means you'll need to explicitly set the `base_name` argument when registeri
--- ---
### Using `include` with routers
The `.urls` attribute on a router instance is simply a standard list of URL patterns. There are a number of different styles for how you can include these URLs.
For example, you can append `router.urls` to a list of existing views…
router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
]
urlpatterns += router.urls
Alternatively you can use Django's `include` function, like so…
urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
url(r'^', include(router.urls)),
]
Router URL patterns can also be namespaces.
urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
url(r'^api/', include(router.urls, namespace='api')),
]
If using namespacing with hyperlinked serializers you'll also need to ensure that any `view_name` parameters on the serializers correctly reflect the namespace. In the example above you'd need to include a parameter such as `view_name='api:user-detail'` for serializer fields hyperlinked to the user detail view.
### Extra link and actions ### Extra link and actions
Any methods on the viewset decorated with `@detail_route` or `@list_route` will also be routed. Any methods on the viewset decorated with `@detail_route` or `@list_route` will also be routed.
...@@ -68,6 +100,24 @@ The following URL pattern would additionally be generated: ...@@ -68,6 +100,24 @@ The following URL pattern would additionally be generated:
* URL pattern: `^users/{pk}/set_password/$` Name: `'user-set-password'` * URL pattern: `^users/{pk}/set_password/$` Name: `'user-set-password'`
If you do not want to use the default URL generated for your custom action, you can instead use the url_path parameter to customize it.
For example, if you want to change the URL for our custom action to `^users/{pk}/change-password/$`, you could write:
from myapp.permissions import IsAdminOrIsSelf
from rest_framework.decorators import detail_route
class UserViewSet(ModelViewSet):
...
@detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf], url_path='change-password')
def set_password(self, request, pk=None):
...
The above example would now generate the following URL pattern:
* URL pattern: `^users/{pk}/change-password/$` Name: `'user-change-password'`
For more information see the viewset documentation on [marking extra actions for routing][route-decorators]. For more information see the viewset documentation on [marking extra actions for routing][route-decorators].
# API Guide # API Guide
......
...@@ -384,7 +384,7 @@ This manager class now more nicely encapsulates that user instances and profile ...@@ -384,7 +384,7 @@ This manager class now more nicely encapsulates that user instances and profile
has_support_contract=validated_data['profile']['has_support_contract'] has_support_contract=validated_data['profile']['has_support_contract']
) )
For more details on this approach see the Django documentation on [model managers](model-managers), and [this blogpost on using model and manger classes](encapsulation-blogpost). For more details on this approach see the Django documentation on [model managers](model-managers), and [this blogpost on using model and manager classes](encapsulation-blogpost).
## Dealing with multiple objects ## Dealing with multiple objects
...@@ -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,63 @@ The inner `Meta` class on serializers is not inherited from parent classes by de ...@@ -567,6 +567,63 @@ 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_relational_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 `HyperlinkedRelatedField`.
### 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
......
...@@ -10,6 +10,8 @@ API versioning allows you to alter behavior between different clients. REST fram ...@@ -10,6 +10,8 @@ API versioning allows you to alter behavior between different clients. REST fram
Versioning is determined by the incoming client request, and may either be based on the request URL, or based on the request headers. 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 ## 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. When API versioning is enabled, the `request.version` attribute will contain a string that corresponds to the version requested in the incoming client request.
...@@ -195,6 +197,7 @@ The following example uses a custom `X-API-Version` header to determine the requ ...@@ -195,6 +197,7 @@ The following example uses a custom `X-API-Version` header to determine the requ
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. 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 [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 [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 [heroku-guidelines]: https://github.com/interagent/http-api-design#version-with-accepts-header
[json-parameters]: http://tools.ietf.org/html/rfc4627#section-6 [json-parameters]: http://tools.ietf.org/html/rfc4627#section-6
......
...@@ -146,7 +146,7 @@ The decorators can additionally take extra arguments that will be set for the ro ...@@ -146,7 +146,7 @@ The decorators can additionally take extra arguments that will be set for the ro
def set_password(self, request, pk=None): def set_password(self, request, pk=None):
... ...
Theses decorators will route `GET` requests by default, but may also accept other HTTP methods, by using the `methods` argument. For example: These decorators will route `GET` requests by default, but may also accept other HTTP methods, by using the `methods` argument. For example:
@detail_route(methods=['post', 'delete']) @detail_route(methods=['post', 'delete'])
def unset_password(self, request, pk=None): def unset_password(self, request, pk=None):
......
...@@ -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 optional packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section]. * [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,13 +50,12 @@ Some reasons you might want to use REST framework: ...@@ -51,13 +50,12 @@ 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.5+, 1.6, 1.7) * Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7)
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.
* [django-filter][django-filter] (0.5.4+) - Filtering support. * [django-filter][django-filter] (0.9.2+) - Filtering support.
* [django-restframework-oauth][django-restframework-oauth] package for OAuth 1.0a and 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.
## Installation ## Installation
...@@ -197,14 +195,10 @@ General guides to using REST framework. ...@@ -197,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
...@@ -231,7 +225,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou ...@@ -231,7 +225,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
## License ## License
Copyright (c) 2011-2014, 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
...@@ -260,13 +254,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -260,13 +254,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[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/
[django-filter]: http://pypi.python.org/pypi/django-filter [django-filter]: http://pypi.python.org/pypi/django-filter
[django-restframework-oauth]: https://github.com/jlafon/django-rest-framework-oauth
[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
...@@ -308,6 +301,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -308,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
...@@ -315,14 +309,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -315,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/
......
...@@ -87,12 +87,12 @@ The resulting API changes are further detailed below. ...@@ -87,12 +87,12 @@ The resulting API changes are further detailed below.
#### The `.create()` and `.update()` methods. #### The `.create()` and `.update()` methods.
The `.restore_object()` method is now replaced with two separate methods, `.create()` and `.update()`. The `.restore_object()` method is now removed, and we instead have two separate methods, `.create()` and `.update()`. These methods work slightly different to the previous `.restore_object()`.
These methods also replace the optional `.save_object()` method, which no longer exists.
When using the `.create()` and `.update()` methods you should both create *and save* the object instance. This is in contrast to the previous `.restore_object()` behavior that would instantiate the object but not save it. When using the `.create()` and `.update()` methods you should both create *and save* the object instance. This is in contrast to the previous `.restore_object()` behavior that would instantiate the object but not save it.
These methods also replace the optional `.save_object()` method, which no longer exists.
The following example from the tutorial previously used `restore_object()` to handle both creating and updating object instances. The following example from the tutorial previously used `restore_object()` to handle both creating and updating object instances.
def restore_object(self, attrs, instance=None): def restore_object(self, attrs, instance=None):
...@@ -665,7 +665,7 @@ This code *would be valid* in `2.4.3`: ...@@ -665,7 +665,7 @@ This code *would be valid* in `2.4.3`:
class Meta: class Meta:
model = Account model = Account
However this code *would not be valid* in `2.4.3`: However this code *would not be valid* in `3.0`:
# Missing `queryset` # Missing `queryset`
class AccountSerializer(serializers.Serializer): class AccountSerializer(serializers.Serializer):
...@@ -940,6 +940,7 @@ The default JSON renderer will return float objects for un-coerced `Decimal` ins ...@@ -940,6 +940,7 @@ The default JSON renderer will return float objects for un-coerced `Decimal` ins
* The serializer `ChoiceField` does not currently display nested choices, as was the case in 2.4. This will be address as part of 3.1. * The serializer `ChoiceField` does not currently display nested choices, as was the case in 2.4. This will be address as part of 3.1.
* Due to the new templated form rendering, the 'widget' option is no longer valid. This means there's no easy way of using third party "autocomplete" widgets for rendering select inputs that contain a large number of choices. You'll either need to use a regular select or a plain text input. We may consider addressing this in 3.1 or 3.2 if there's sufficient demand. * Due to the new templated form rendering, the 'widget' option is no longer valid. This means there's no easy way of using third party "autocomplete" widgets for rendering select inputs that contain a large number of choices. You'll either need to use a regular select or a plain text input. We may consider addressing this in 3.1 or 3.2 if there's sufficient demand.
* Some of the default validation error messages were rewritten and might no longer be pre-translated. You can still [create language files with Django][django-localization] if you wish to localize them. * Some of the default validation error messages were rewritten and might no longer be pre-translated. You can still [create language files with Django][django-localization] if you wish to localize them.
* `APIException` subclasses could previously take could previously take any arbitrary type in the `detail` argument. These exceptions now use translatable text strings, and as a result call `force_text` on the `detail` argument, which *must be a string*. If you need complex arguments to an `APIException` class, you should subclass it and override the `__init__()` method. Typically you'll instead want to use a custom exception handler to provide for non-standard error responses.
--- ---
......
# 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 internalization 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
...@@ -84,7 +84,7 @@ Our gold sponsors include companies large and small. Many thanks for their signi ...@@ -84,7 +84,7 @@ Our gold sponsors include companies large and small. Many thanks for their signi
<li><a href="http://pulsecode.ca" rel="nofollow" style="background-image:url(../../img/sponsors/2-pulsecode.png);">Pulsecode Inc.</a></li> <li><a href="http://pulsecode.ca" rel="nofollow" style="background-image:url(../../img/sponsors/2-pulsecode.png);">Pulsecode Inc.</a></li>
<li><a href="http://singinghorsestudio.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-singing-horse.png);">Singing Horse Studio Ltd.</a></li> <li><a href="http://singinghorsestudio.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-singing-horse.png);">Singing Horse Studio Ltd.</a></li>
<li><a href="https://www.heroku.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-heroku.png);">Heroku</a></li> <li><a href="https://www.heroku.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-heroku.png);">Heroku</a></li>
<li><a href="https://www.galileo-press.de/" rel="nofollow" style="background-image:url(../../img/sponsors/2-galileo_press.png);">Galileo Press</a></li> <li><a href="https://www.rheinwerk-verlag.de/" rel="nofollow" style="background-image:url(../../img/sponsors/2-rheinwerk_verlag.png);">Rheinwerk Verlag</a></li>
<li><a href="http://www.securitycompass.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-security_compass.png);">Security Compass</a></li> <li><a href="http://www.securitycompass.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-security_compass.png);">Security Compass</a></li>
<li><a href="https://www.djangoproject.com/foundation/" rel="nofollow" style="background-image:url(../../img/sponsors/2-django.png);">Django Software Foundation</a></li> <li><a href="https://www.djangoproject.com/foundation/" rel="nofollow" style="background-image:url(../../img/sponsors/2-django.png);">Django Software Foundation</a></li>
<li><a href="http://www.hipflaskapp.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-hipflask.png);">Hipflask</a></li> <li><a href="http://www.hipflaskapp.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-hipflask.png);">Hipflask</a></li>
......
...@@ -59,14 +59,17 @@ The following template should be used for the description of the issue, and serv ...@@ -59,14 +59,17 @@ The following template should be used for the description of the issue, and serv
If you wish to be considered for this or a future date, please comment against this or subsequent issues. If you wish to be considered for this or a future date, please comment against this or subsequent issues.
To modify this process for future maintenance cycles make a pull request to the [project management](http://www.django-rest-framework.org/topics/project-management/) documentation.
#### Responsibilities of team members #### Responsibilities of team members
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:
...@@ -108,10 +111,61 @@ The following template should be used for the description of the issue, and serv ...@@ -108,10 +111,61 @@ The following template should be used for the description of the issue, and serv
- [ ] Make a release announcement on twitter. - [ ] Make a release announcement on twitter.
- [ ] Close the milestone on GitHub. - [ ] Close the milestone on GitHub.
To modify this process for future releases make a pull request to the [project management](http://www.django-rest-framework.org/topics/project-management/) documentation.
When pushing the release to PyPI ensure that your environment has been installed from our development `requirement.txt`, so that documentation and PyPI installs are consistently being built against a pinned set of packages. When pushing the release to PyPI ensure that your environment has been installed from our development `requirement.txt`, so that documentation and PyPI installs are consistently being built against a pinned set of packages.
--- ---
## 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 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.
...@@ -126,9 +180,13 @@ The following issues still need to be addressed: ...@@ -126,9 +180,13 @@ The following issues still need to be addressed:
* Ensure `@jamie` has back-up access to the `django-rest-framework.org` domain setup and admin. * Ensure `@jamie` has back-up access to the `django-rest-framework.org` domain setup and admin.
* Document ownership of the [live example][sandbox] API. * Document ownership of the [live example][sandbox] API.
* Document ownership of the [mailing list][mailing-list] and IRC channel. * Document ownership of the [mailing list][mailing-list] and IRC channel.
* Document ownership and management of the security mailing list.
[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
...@@ -124,7 +124,7 @@ The first part of the serializer class defines the fields that get serialized/de ...@@ -124,7 +124,7 @@ The first part of the serializer class defines the fields that get serialized/de
A serializer class is very similar to a Django `Form` class, and includes similar validation flags on the various fields, such as `required`, `max_length` and `default`. A serializer class is very similar to a Django `Form` class, and includes similar validation flags on the various fields, such as `required`, `max_length` and `default`.
The field flags can also control how the serializer should be displayed in certain circumstances, such as when rendering to HTML. The `style={'type': 'textarea'}` flag above is equivelent to using `widget=widgets.Textarea` on a Django `Form` class. This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial. The field flags can also control how the serializer should be displayed in certain circumstances, such as when rendering to HTML. The `{'base_template': 'textarea.html'}` flag above is equivelent to using `widget=widgets.Textarea` on a Django `Form` class. This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial.
We can actually also save ourselves some time by using the `ModelSerializer` class, as we'll see later, but for now we'll keep our serializer definition explicit. We can actually also save ourselves some time by using the `ModelSerializer` class, as we'll see later, but for now we'll keep our serializer definition explicit.
...@@ -191,14 +191,14 @@ Our `SnippetSerializer` class is replicating a lot of information that's also co ...@@ -191,14 +191,14 @@ Our `SnippetSerializer` class is replicating a lot of information that's also co
In the same way that Django provides both `Form` classes and `ModelForm` classes, REST framework includes both `Serializer` classes, and `ModelSerializer` classes. In the same way that Django provides both `Form` classes and `ModelForm` classes, REST framework includes both `Serializer` classes, and `ModelSerializer` classes.
Let's look at refactoring our serializer using the `ModelSerializer` class. Let's look at refactoring our serializer using the `ModelSerializer` class.
Open the file `snippets/serializers.py` again, and edit the `SnippetSerializer` class. Open the file `snippets/serializers.py` again, and replace the `SnippetSerializer` class with the following.
class SnippetSerializer(serializers.ModelSerializer): class SnippetSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Snippet model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style') fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
One nice property that serializers have is that you can inspect all the fields in a serializer instance, by printing it's representation. Open the Django shell with `python manage.py shell`, then try the following: One nice property that serializers have is that you can inspect all the fields in a serializer instance, by printing its representation. Open the Django shell with `python manage.py shell`, then try the following:
>>> from snippets.serializers import SnippetSerializer >>> from snippets.serializers import SnippetSerializer
>>> serializer = SnippetSerializer() >>> serializer = SnippetSerializer()
...@@ -206,7 +206,7 @@ One nice property that serializers have is that you can inspect all the fields i ...@@ -206,7 +206,7 @@ One nice property that serializers have is that you can inspect all the fields i
SnippetSerializer(): SnippetSerializer():
id = IntegerField(label='ID', read_only=True) id = IntegerField(label='ID', read_only=True)
title = CharField(allow_blank=True, max_length=100, required=False) title = CharField(allow_blank=True, max_length=100, required=False)
code = CharField(style={'type': 'textarea'}) code = CharField(style={'base_template': 'textarea.html'})
linenos = BooleanField(required=False) linenos = BooleanField(required=False)
language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')... language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')... style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
......
...@@ -64,7 +64,7 @@ That's looking good. Again, it's still pretty similar to the function based vie ...@@ -64,7 +64,7 @@ That's looking good. Again, it's still pretty similar to the function based vie
We'll also need to refactor our `urls.py` slightly now we're using class based views. We'll also need to refactor our `urls.py` slightly now we're using class based views.
from django.conf.urls import patterns, url from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views from snippets import views
......
...@@ -177,7 +177,7 @@ In the snippets app, create a new file, `permissions.py` ...@@ -177,7 +177,7 @@ In the snippets app, create a new file, `permissions.py`
# Write permissions are only allowed to the owner of the snippet. # Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user return obj.owner == request.user
Now we can add that custom permission to our snippet instance endpoint, by editing the `permission_classes` property on the `SnippetDetail` class: Now we can add that custom permission to our snippet instance endpoint, by editing the `permission_classes` property on the `SnippetDetail` view class:
permission_classes = (permissions.IsAuthenticatedOrReadOnly, permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,) IsOwnerOrReadOnly,)
......
...@@ -106,6 +106,8 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p ...@@ -106,6 +106,8 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p
After adding all those names into our URLconf, our final `snippets/urls.py` file should look something like this: After adding all those names into our URLconf, our final `snippets/urls.py` file should look something like this:
from django.conf.urls import url, include
# API endpoints # API endpoints
urlpatterns = format_suffix_patterns([ urlpatterns = format_suffix_patterns([
url(r'^$', views.api_root), url(r'^$', views.api_root),
......
...@@ -53,6 +53,8 @@ Notice that we've also used the `@detail_route` decorator to create a custom act ...@@ -53,6 +53,8 @@ Notice that we've also used the `@detail_route` decorator to create a custom act
Custom actions which use the `@detail_route` decorator will respond to `GET` requests. We can use the `methods` argument if we wanted an action that responded to `POST` requests. Custom actions which use the `@detail_route` decorator will respond to `GET` requests. We can use the `methods` argument if we wanted an action that responded to `POST` requests.
The URLs for custom actions by default depend on the method name itself. If you want to change the way url should be constructed, you can include url_path as a decorator keyword argument.
## Binding ViewSets to URLs explicitly ## Binding ViewSets to URLs explicitly
The handler methods only get bound to the actions when we define the URLConf. The handler methods only get bound to the actions when we define the URLConf.
......
...@@ -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%;
......
...@@ -42,6 +42,7 @@ pages: ...@@ -42,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']
...@@ -49,11 +50,7 @@ pages: ...@@ -49,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']
...@@ -2,15 +2,15 @@ ...@@ -2,15 +2,15 @@
Django>=1.4.11 Django>=1.4.11
# Test requirements # Test requirements
pytest-django==2.6 pytest-django==2.8.0
pytest==2.5.2 pytest==2.6.4
pytest-cov==1.6 pytest-cov==1.6
flake8==2.2.2 flake8==2.2.2
# Optional packages # Optional packages
markdown>=2.1.0 markdown>=2.1.0
django-guardian==1.2.4 django-guardian==1.2.4
django-filter>=0.5.4 django-filter>=0.9.2
# wheel for PyPI installs # wheel for PyPI installs
wheel==0.24.0 wheel==0.24.0
......
...@@ -8,10 +8,10 @@ ______ _____ _____ _____ __ ...@@ -8,10 +8,10 @@ ______ _____ _____ _____ __
""" """
__title__ = 'Django REST framework' __title__ = 'Django REST framework'
__version__ = '3.0.2' __version__ = '3.0.4'
__author__ = 'Tom Christie' __author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause' __license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2014 Tom Christie' __copyright__ = 'Copyright 2011-2015 Tom Christie'
# Version synonym # Version synonym
VERSION = __version__ VERSION = __version__
......
...@@ -5,6 +5,7 @@ from __future__ import unicode_literals ...@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import base64 import base64
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.middleware.csrf import CsrfViewMiddleware from django.middleware.csrf import CsrfViewMiddleware
from django.utils.translation import ugettext_lazy as _
from rest_framework import exceptions, HTTP_HEADER_ENCODING from rest_framework import exceptions, HTTP_HEADER_ENCODING
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
...@@ -65,16 +66,16 @@ class BasicAuthentication(BaseAuthentication): ...@@ -65,16 +66,16 @@ class BasicAuthentication(BaseAuthentication):
return None return None
if len(auth) == 1: if len(auth) == 1:
msg = 'Invalid basic header. No credentials provided.' msg = _('Invalid basic header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg) raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2: elif len(auth) > 2:
msg = 'Invalid basic header. Credentials string should not contain spaces.' msg = _('Invalid basic header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg) raise exceptions.AuthenticationFailed(msg)
try: try:
auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':') auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
except (TypeError, UnicodeDecodeError): except (TypeError, UnicodeDecodeError):
msg = 'Invalid basic header. Credentials not correctly base64 encoded' msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
raise exceptions.AuthenticationFailed(msg) raise exceptions.AuthenticationFailed(msg)
userid, password = auth_parts[0], auth_parts[2] userid, password = auth_parts[0], auth_parts[2]
...@@ -85,8 +86,13 @@ class BasicAuthentication(BaseAuthentication): ...@@ -85,8 +86,13 @@ class BasicAuthentication(BaseAuthentication):
Authenticate the userid and password against username and password. Authenticate the userid and password against username and password.
""" """
user = authenticate(username=userid, password=password) user = authenticate(username=userid, password=password)
if user is None or not user.is_active:
raise exceptions.AuthenticationFailed('Invalid username/password') if user is None:
raise exceptions.AuthenticationFailed(_('Invalid username/password.'))
if not user.is_active:
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
return (user, None) return (user, None)
def authenticate_header(self, request): def authenticate_header(self, request):
...@@ -152,10 +158,10 @@ class TokenAuthentication(BaseAuthentication): ...@@ -152,10 +158,10 @@ class TokenAuthentication(BaseAuthentication):
return None return None
if len(auth) == 1: if len(auth) == 1:
msg = 'Invalid token header. No credentials provided.' msg = _('Invalid token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg) raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2: elif len(auth) > 2:
msg = 'Invalid token header. Token string should not contain spaces.' msg = _('Invalid token header. Token string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg) raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(auth[1]) return self.authenticate_credentials(auth[1])
...@@ -164,10 +170,10 @@ class TokenAuthentication(BaseAuthentication): ...@@ -164,10 +170,10 @@ class TokenAuthentication(BaseAuthentication):
try: try:
token = self.model.objects.get(key=key) token = self.model.objects.get(key=key)
except self.model.DoesNotExist: except self.model.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token') raise exceptions.AuthenticationFailed(_('Invalid token.'))
if not token.user.is_active: if not token.user.is_active:
raise exceptions.AuthenticationFailed('User inactive or deleted') raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
return (token.user, token) return (token.user, token)
......
...@@ -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
......
...@@ -40,7 +40,7 @@ class Migration(SchemaMigration): ...@@ -40,7 +40,7 @@ class Migration(SchemaMigration):
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
}, },
"%s.%s" % (User._meta.app_label, User._meta.module_name): { "%s.%s" % (User._meta.app_label, User._meta.module_name): {
'Meta': {'object_name': User._meta.module_name}, 'Meta': {'object_name': User._meta.module_name, 'db_table': repr(User._meta.db_table)},
}, },
'authtoken.token': { 'authtoken.token': {
'Meta': {'object_name': 'Token'}, 'Meta': {'object_name': 'Token'},
......
...@@ -8,7 +8,7 @@ from __future__ import unicode_literals ...@@ -8,7 +8,7 @@ from __future__ import unicode_literals
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.conf import settings 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 import parse as urlparse from django.utils.six.moves.urllib.parse import urlparse as _urlparse
from django.utils import six from django.utils import six
import django import django
import inspect import inspect
...@@ -18,7 +18,7 @@ def unicode_repr(instance): ...@@ -18,7 +18,7 @@ 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
# on both python 3 (already the case) and 2 (not the case). # on both python 3 (already the case) and 2 (not the case).
if six.PY2: if six.PY2:
repr(instance).decode('utf-8') return repr(instance).decode('utf-8')
return repr(instance) return repr(instance)
...@@ -38,10 +38,18 @@ def unicode_http_header(value): ...@@ -38,10 +38,18 @@ def unicode_http_header(value):
return value return value
def total_seconds(timedelta):
# TimeDelta.total_seconds() is only available in Python 2.7
if hasattr(timedelta, 'total_seconds'):
return timedelta.total_seconds()
else:
return (timedelta.days * 86400.0) + float(timedelta.seconds) + (timedelta.microseconds / 1000000.0)
# OrderedDict only available in Python 2.7. # OrderedDict only available in Python 2.7.
# This will always be the case in Django 1.7 and above, as these versions # This will always be the case in Django 1.7 and above, as these versions
# no longer support Python 2.6. # no longer support Python 2.6.
# For Django <= 1.6 and Python 2.6 fall back to OrderedDict. # For Django <= 1.6 and Python 2.6 fall back to SortedDict.
try: try:
from collections import OrderedDict from collections import OrderedDict
except ImportError: except ImportError:
...@@ -55,6 +63,23 @@ except ImportError: ...@@ -55,6 +63,23 @@ except ImportError:
from django.http import HttpResponse as HttpResponseBase from django.http import HttpResponse as HttpResponseBase
# contrib.postgres only supported from 1.8 onwards.
try:
from django.contrib.postgres import fields as postgres_fields
except ImportError:
postgres_fields = None
# request only provides `resolver_match` from 1.5 onwards.
def get_resolver_match(request):
try:
return request.resolver_match
except AttributeError:
# Django < 1.5
from django.core.urlresolvers import resolve
return resolve(request.path_info)
# django-filter is optional # django-filter is optional
try: try:
import django_filters import django_filters
...@@ -177,7 +202,7 @@ except ImportError: ...@@ -177,7 +202,7 @@ except ImportError:
class RequestFactory(DjangoRequestFactory): class RequestFactory(DjangoRequestFactory):
def generic(self, method, path, def generic(self, method, path,
data='', content_type='application/octet-stream', **extra): data='', content_type='application/octet-stream', **extra):
parsed = urlparse.urlparse(path) parsed = _urlparse(path)
data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET) data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET)
r = { r = {
'PATH_INFO': self._get_path(parsed), 'PATH_INFO': self._get_path(parsed),
...@@ -217,6 +242,8 @@ except ImportError: ...@@ -217,6 +242,8 @@ except ImportError:
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:
...@@ -91,23 +90,23 @@ class PermissionDenied(APIException): ...@@ -91,23 +90,23 @@ class PermissionDenied(APIException):
class NotFound(APIException): class NotFound(APIException):
status_code = status.HTTP_404_NOT_FOUND status_code = status.HTTP_404_NOT_FOUND
default_detail = _('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:
...@@ -119,23 +118,22 @@ class NotAcceptable(APIException): ...@@ -119,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:
...@@ -147,6 +145,8 @@ class Throttled(APIException): ...@@ -147,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
))
...@@ -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\"."
# 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: Dutch (http://www.transifex.com/projects/p/django-rest-framework/language/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: nl\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 ""
#: 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:
# Stanislav Komanec <stano@videoflot.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: Slovak (http://www.transifex.com/projects/p/django-rest-framework/language/sk/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: sk\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
#: authentication.py:69
msgid "Invalid basic header. No credentials provided."
msgstr "Nesprávna hlavička. Neboli poskytnuté prihlasovacie údaje."
#: authentication.py:72
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr "Nesprávna hlavička. Prihlasovacie údaje nesmú obsahovať medzery."
#: authentication.py:78
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr "Nesprávna hlavička. Prihlasovacie údaje nie sú správne zakódované pomocou metódy base64."
#: authentication.py:90
msgid "Invalid username/password."
msgstr "Nesprávne prihlasovacie údaje."
#: authentication.py:156
msgid "Invalid token header. No credentials provided."
msgstr "Nesprávna token hlavička. Neboli poskytnuté prihlasovacie údaje."
#: authentication.py:159
msgid "Invalid token header. Token string should not contain spaces."
msgstr "Nesprávna token hlavička. Token hlavička nesmie obsahovať medzery."
#: authentication.py:168
msgid "Invalid token."
msgstr "Nesprávny token."
#: authentication.py:171
msgid "User inactive or deleted."
msgstr "Daný používateľ je neaktívny, alebo zmazaný."
#: exceptions.py:38
msgid "A server error occurred."
msgstr "Vyskytla sa chyba na strane servera."
#: exceptions.py:73
msgid "Malformed request."
msgstr "Požiadavok má nesprávny formát, alebo je poškodený."
#: exceptions.py:78
msgid "Incorrect authentication credentials."
msgstr "Nesprávne prihlasovacie údaje."
#: exceptions.py:83
msgid "Authentication credentials were not provided."
msgstr "Prihlasovacie údaje neboli zadané."
#: exceptions.py:88
msgid "You do not have permission to perform this action."
msgstr "K danej akcii nemáte oprávnenie."
#: exceptions.py:93
msgid "Not found."
msgstr "Nebolo nájdené."
#: exceptions.py:98
msgid "Method \"{method}\" not allowed."
msgstr "Metóda \"{method}\" nie je povolená."
#: exceptions.py:109
msgid "Could not satisfy the request Accept header."
msgstr "Nie je možné vyhovieť požiadavku v hlavičke \"Accept\"."
#: exceptions.py:121
msgid "Unsupported media type \"{media_type}\" in request."
msgstr "Požiadavok obsahuje nepodporovaný media type: \"{media_type}\"."
#: 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 "Daný používateľ je zablokovaný."
#: authtoken/serializers.py:23
msgid "Unable to log in with provided credentials."
msgstr "S danými prihlasovacími údajmi nebolo možné sa prihlásiť."
#: authtoken/serializers.py:26
msgid "Must include \"username\" and \"password\"."
msgstr "Musí obsahovať parametre \"používateľské meno\" a \"heslo\"."
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