Commit dee5fb56 by Xavier Ordoquy

Merge remote-tracking branch 'reference/master' into feature/ipaddress-fix

Conflicts:
	docs/api-guide/fields.md
parents d6effbf7 0c66c7cf
...@@ -5,6 +5,10 @@ sudo: false ...@@ -5,6 +5,10 @@ sudo: false
env: env:
- TOX_ENV=py27-flake8 - TOX_ENV=py27-flake8
- TOX_ENV=py27-docs - TOX_ENV=py27-docs
- TOX_ENV=py34-django18
- TOX_ENV=py33-django18
- TOX_ENV=py32-django18
- TOX_ENV=py27-django18
- TOX_ENV=py34-django17 - TOX_ENV=py34-django17
- TOX_ENV=py33-django17 - TOX_ENV=py33-django17
- TOX_ENV=py32-django17 - TOX_ENV=py32-django17
...@@ -21,10 +25,18 @@ env: ...@@ -21,10 +25,18 @@ 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-django18beta - TOX_ENV=py27-djangomaster
- TOX_ENV=py33-django18beta - TOX_ENV=py32-djangomaster
- TOX_ENV=py32-django18beta - TOX_ENV=py33-djangomaster
- TOX_ENV=py27-django18beta - TOX_ENV=py34-djangomaster
matrix:
fast_finish: true
allow_failures:
- env: TOX_ENV=py27-djangomaster
- env: TOX_ENV=py32-djangomaster
- env: TOX_ENV=py33-djangomaster
- env: TOX_ENV=py34-djangomaster
install: install:
- pip install tox - pip install tox
......
# License
Copyright (c) 2011-2015, Tom Christie
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
include README.md
include LICENSE.md
recursive-include rest_framework/static *.js *.css *.png *.eot *.svg *.ttf *.woff recursive-include rest_framework/static *.js *.css *.png *.eot *.svg *.ttf *.woff
recursive-include rest_framework/templates *.html recursive-include rest_framework/templates *.html
recursive-exclude * __pycache__ recursive-exclude * __pycache__
......
...@@ -9,7 +9,9 @@ Full documentation for the project is available at [http://www.django-rest-frame ...@@ -9,7 +9,9 @@ Full documentation for the project is available at [http://www.django-rest-frame
--- ---
**Note**: We have now released Django REST framework 3.0. For older codebases you may want to refer to the version 2.4.4 [source code](https://github.com/tomchristie/django-rest-framework/tree/version-2.4.x), and [documentation](http://tomchristie.github.io/rest-framework-2-docs/). **Note**: We have now released Django REST framework 3.1. For older codebases you may want to refer to the version 2.4.4 [source code](https://github.com/tomchristie/django-rest-framework/tree/version-2.4.x), and [documentation](http://tomchristie.github.io/rest-framework-2-docs/).
For more details see the [3.1 release notes][3.1-announcement]
--- ---
...@@ -23,7 +25,7 @@ Some reasons you might want to use REST framework: ...@@ -23,7 +25,7 @@ Some reasons you might want to use REST framework:
* [Authentication policies][authentication] including optional packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section]. * [Authentication policies][authentication] including optional packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources. * [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers]. * Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
* [Extensive documentation][index], and [great community support][group]. * [Extensive documentation][docs], and [great community support][group].
There is a live example API for testing purposes, [available here][sandbox]. There is a live example API for testing purposes, [available here][sandbox].
...@@ -34,7 +36,7 @@ There is a live example API for testing purposes, [available here][sandbox]. ...@@ -34,7 +36,7 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements # Requirements
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4) * Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)
* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7, 1.8-beta) * Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7, 1.8)
# Installation # Installation
...@@ -154,42 +156,15 @@ If you believe you’ve found something in Django REST framework which has secur ...@@ -154,42 +156,15 @@ If you believe you’ve found something in Django REST framework which has secur
Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure. Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
# License
Copyright (c) 2011-2015, Tom Christie
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[build-status-image]: https://secure.travis-ci.org/tomchristie/django-rest-framework.svg?branch=master [build-status-image]: https://secure.travis-ci.org/tomchristie/django-rest-framework.svg?branch=master
[travis]: http://travis-ci.org/tomchristie/django-rest-framework?branch=master [travis]: http://travis-ci.org/tomchristie/django-rest-framework?branch=master
[pypi-version]: https://pypip.in/version/djangorestframework/badge.svg [pypi-version]: https://img.shields.io/pypi/v/djangorestframework.svg
[pypi]: https://pypi.python.org/pypi/djangorestframework [pypi]: https://pypi.python.org/pypi/djangorestframework
[twitter]: https://twitter.com/_tomchristie [twitter]: https://twitter.com/_tomchristie
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework [group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
[sandbox]: http://restframework.herokuapp.com/ [sandbox]: http://restframework.herokuapp.com/
[index]: http://www.django-rest-framework.org/
[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth [oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
[oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit [oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
[serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers [serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers
...@@ -200,18 +175,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -200,18 +175,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[routers]: http://www.django-rest-framework.org/api-guide/routers/ [routers]: http://www.django-rest-framework.org/api-guide/routers/
[serializers]: http://www.django-rest-framework.org/api-guide/serializers/ [serializers]: http://www.django-rest-framework.org/api-guide/serializers/
[authentication]: http://www.django-rest-framework.org/api-guide/authentication/ [authentication]: http://www.django-rest-framework.org/api-guide/authentication/
[rest-framework-2-announcement]: http://www.django-rest-framework.org/topics/rest-framework-2-announcement/
[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
[tox]: http://testrun.org/tox/latest/
[tehjones]: https://twitter.com/tehjones/status/294986071979196416
[wlonk]: https://twitter.com/wlonk/status/261689665952833536
[laserllama]: https://twitter.com/laserllama/status/328688333750407168
[docs]: http://www.django-rest-framework.org/ [docs]: http://www.django-rest-framework.org/
[urlobject]: https://github.com/zacharyvoase/urlobject
[markdown]: http://pypi.python.org/pypi/Markdown/
[django-filter]: http://pypi.python.org/pypi/django-filter
[security-mail]: mailto:rest-framework-security@googlegroups.com [security-mail]: mailto:rest-framework-security@googlegroups.com
[3.1-announcement]: http://www.django-rest-framework.org/topics/3.1-announcement/
...@@ -168,7 +168,6 @@ The `curl` command line tool may be useful for testing token authenticated APIs. ...@@ -168,7 +168,6 @@ The `curl` command line tool may be useful for testing token authenticated APIs.
If you want every user to have an automatically generated Token, you can simply catch the User's `post_save` signal. If you want every user to have an automatically generated Token, you can simply catch the User's `post_save` signal.
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
...@@ -248,6 +247,10 @@ Unauthenticated responses that are denied permission will result in an `HTTP 403 ...@@ -248,6 +247,10 @@ 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.
**Warning**: Always use Django's standard login view when creating login pages. This will ensure your login views are properly protected.
CSRF validation in REST framework works slightly differently to standard Django due to the need to support both session and non-session based authentication to the same views. This means that only authenticated requests require CSRF tokens, and anonymous requests may be sent without CSRF tokens. This behaviour is not suitable for login views, which should always have CSRF validation applied.
# 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.
......
source: fields.py source: fields.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Serializer fields # Serializer fields
> Each field in a Form class is responsible not only for validating data, but also for "cleaning" it — normalizing it to a consistent format. > Each field in a Form class is responsible not only for validating data, but also for "cleaning" it — normalizing it to a consistent format.
...@@ -189,6 +183,15 @@ A field that ensures the input is a valid UUID string. The `to_internal_value` m ...@@ -189,6 +183,15 @@ A field that ensures the input is a valid UUID string. The `to_internal_value` m
"de305d54-75b4-431b-adb2-eb6b9e546013" "de305d54-75b4-431b-adb2-eb6b9e546013"
**Signature:** `UUIDField(format='hex_verbose')`
- `format`: Determines the representation format of the uuid value
- `'hex_verbose'` - The cannoncical hex representation, including hyphens: `"5ce0e9a5-5ffa-654b-cee0-1238041fb31a"`
- `'hex'` - The compact hex representation of the UUID, not including hyphens: `"5ce0e9a55ffa654bcee01238041fb31a"`
- `'int'` - A 128 bit integer representation of the UUID: `"123456789012312313134124512351145145114"`
- `'urn'` - RFC 4122 URN representation of the UUID: `"urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"`
Changing the `format` parameters only affects representation values. All formats are accepted by `to_internal_value`
## IPAddressField ## IPAddressField
A field that ensures the input is a valid IPv4 or IPv6 string. A field that ensures the input is a valid IPv4 or IPv6 string.
...@@ -319,6 +322,18 @@ Corresponds to `django.db.models.fields.TimeField` ...@@ -319,6 +322,18 @@ Corresponds to `django.db.models.fields.TimeField`
Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`) Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`)
## DurationField
A Duration representation.
Corresponds to `django.db.models.fields.DurationField`
The `validated_data` for these fields will contain a `datetime.timedelta` instance.
The representation is a string following this format `'[DD] [HH:[MM:]]ss[.uuuuuu]'`.
**Note:** This field is only available with Django versions >= 1.8.
**Signature:** `DurationField()`
--- ---
# Choice selection fields # Choice selection fields
...@@ -451,7 +466,7 @@ A field class that does not take a value based on user input, but instead takes ...@@ -451,7 +466,7 @@ A field class that does not take a value based on user input, but instead takes
For example, to include a field that always provides the current time as part of the serializer validated data, you would use the following: For example, to include a field that always provides the current time as part of the serializer validated data, you would use the following:
modified = serializer.HiddenField(default=timezone.now) modified = serializers.HiddenField(default=timezone.now)
The `HiddenField` class is usually only needed if you have some validation that needs to run based on some pre-provided field values, but you do not want to expose all of those fields to the end user. The `HiddenField` class is usually only needed if you have some validation that needs to run based on some pre-provided field values, but you do not want to expose all of those fields to the end user.
...@@ -498,7 +513,7 @@ If you want to create a custom field, you'll need to subclass `Field` and then o ...@@ -498,7 +513,7 @@ If you want to create a custom field, you'll need to subclass `Field` and then o
The `.to_representation()` method is called to convert the initial datatype into a primitive, serializable datatype. The `.to_representation()` method is called to convert the initial datatype into a primitive, serializable datatype.
The `to_internal_value()` method is called to restore a primitive datatype into its internal python representation. This method should raise a `serializer.ValidationError` if the data is invalid. The `to_internal_value()` method is called to restore a primitive datatype into its internal python representation. This method should raise a `serializers.ValidationError` if the data is invalid.
Note that the `WritableField` class that was present in version 2.x no longer exists. You should subclass `Field` and override `to_internal_value()` if the field supports data input. Note that the `WritableField` class that was present in version 2.x no longer exists. You should subclass `Field` and override `to_internal_value()` if the field supports data input.
......
...@@ -72,7 +72,7 @@ We can override `.get_queryset()` to deal with URLs such as `http://example.com/ ...@@ -72,7 +72,7 @@ We can override `.get_queryset()` to deal with URLs such as `http://example.com/
by filtering against a `username` query parameter in the URL. by filtering against a `username` query parameter in the URL.
""" """
queryset = Purchase.objects.all() queryset = Purchase.objects.all()
username = self.request.QUERY_PARAMS.get('username', None) username = self.request.query_params.get('username', None)
if username is not None: if username is not None:
queryset = queryset.filter(purchaser__username=username) queryset = queryset.filter(purchaser__username=username)
return queryset return queryset
...@@ -395,6 +395,10 @@ The following third party packages provide additional filter implementations. ...@@ -395,6 +395,10 @@ The following third party packages provide additional filter implementations.
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. 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.
## Django REST framework full word search filter
The [djangorestframework-word-filter][django-rest-framework-word-search-filter] developed as alternative to `filters.SearchFilter` which will search full word in text, or exact match.
[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
...@@ -404,3 +408,4 @@ The [django-rest-framework-filters package][django-rest-framework-filters] works ...@@ -404,3 +408,4 @@ The [django-rest-framework-filters package][django-rest-framework-filters] works
[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-filters]: https://github.com/philipn/django-rest-framework-filters [django-rest-framework-filters]: https://github.com/philipn/django-rest-framework-filters
[django-rest-framework-word-search-filter]: https://github.com/trollknurr/django-rest-framework-word-search-filter
source: mixins.py source: mixins.py
generics.py generics.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Generic views # Generic views
> Django’s generic views... were developed as a shortcut for common usage patterns... They take certain common idioms and patterns found in view development and abstract them so that you can quickly write common views of data without having to repeat yourself. > Django’s generic views... were developed as a shortcut for common usage patterns... They take certain common idioms and patterns found in view development and abstract them so that you can quickly write common views of data without having to repeat yourself.
...@@ -57,7 +51,7 @@ For more complex cases you might also want to override various methods on the vi ...@@ -57,7 +51,7 @@ For more complex cases you might also want to override various methods on the vi
For very simple cases you might want to pass through any class attributes using the `.as_view()` method. For example, your URLconf might include something like the following entry: For very simple cases you might want to pass through any class attributes using the `.as_view()` method. For example, your URLconf might include something like the following entry:
url(r'^/users/', ListCreateAPIView.as_view(model=User), name='user-list') url(r'^/users/', ListCreateAPIView.as_view(queryset=User.objects.all(), serializer_class=UserSerializer), name='user-list')
--- ---
...@@ -84,10 +78,9 @@ The following attributes control the basic view behavior. ...@@ -84,10 +78,9 @@ The following attributes control the basic view behavior.
The following attributes are used to control pagination when used with list views. The following attributes are used to control pagination when used with list views.
* `paginate_by` - The size of pages to use with paginated data. If set to `None` then pagination is turned off. If unset this uses the same value as the `PAGINATE_BY` setting, which defaults to `None`. * `pagination_class` - The pagination class that should be used when paginating list results. Defaults to the same value as the `DEFAULT_PAGINATION_CLASS` setting, which is `'rest_framework.pagination.PageNumberPagination'`.
* `paginate_by_param` - The name of a query parameter, which can be used by the client to override the default page size to use for pagination. If unset this uses the same value as the `PAGINATE_BY_PARAM` setting, which defaults to `None`.
* `pagination_serializer_class` - The pagination serializer class to use when determining the style of paginated responses. Defaults to the same value as the `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting. Note that usage of the `paginate_by`, `paginate_by_param` and `page_kwarg` attributes are now pending deprecation. The `pagination_serializer_class` attribute and `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting have been removed completely. Pagination settings should instead be controlled by overriding a pagination class and setting any configuration attributes there. See the pagination documentation for more details.
* `page_kwarg` - The name of a URL kwarg or URL query parameter which can be used by the client to control which page is requested. Defaults to `'page'`.
**Filtering**: **Filtering**:
...@@ -135,14 +128,14 @@ Note that if your API doesn't include any object level permissions, you may opti ...@@ -135,14 +128,14 @@ Note that if your API doesn't include any object level permissions, you may opti
Returns the classes that should be used to filter the queryset. Defaults to returning the `filter_backends` attribute. Returns the classes that should be used to filter the queryset. Defaults to returning the `filter_backends` attribute.
May be overridden to provide more complex behavior with filters, such as using different (or even exlusive) lists of filter_backends depending on different criteria. May be overridden to provide more complex behavior with filters, such as using different (or even exclusive) lists of filter_backends depending on different criteria.
For example: For example:
def get_filter_backends(self): def get_filter_backends(self):
if "geo_route" in self.request.QUERY_PARAMS: if "geo_route" in self.request.query_params:
return (GeoRouteFilter, CategoryFilter) return (GeoRouteFilter, CategoryFilter)
elif "geo_point" in self.request.QUERY_PARAMS: elif "geo_point" in self.request.query_params:
return (GeoPointFilter, CategoryFilter) return (GeoPointFilter, CategoryFilter)
return (CategoryFilter,) return (CategoryFilter,)
...@@ -200,7 +193,7 @@ You won't typically need to override the following methods, although you might n ...@@ -200,7 +193,7 @@ You won't typically need to override the following methods, although you might n
* `get_serializer_context(self)` - Returns a dictionary containing any extra context that should be supplied to the serializer. Defaults to including `'request'`, `'view'` and `'format'` keys. * `get_serializer_context(self)` - Returns a dictionary containing any extra context that should be supplied to the serializer. Defaults to including `'request'`, `'view'` and `'format'` keys.
* `get_serializer(self, instance=None, data=None, files=None, many=False, partial=False, allow_add_remove=False)` - Returns a serializer instance. * `get_serializer(self, instance=None, data=None, files=None, many=False, partial=False, allow_add_remove=False)` - Returns a serializer instance.
* `get_pagination_serializer(self, page)` - Returns a serializer instance to use with paginated data. * `get_paginated_response(self, data)` - Returns a paginated style `Response` object.
* `paginate_queryset(self, queryset)` - Paginate a queryset if required, either returning a page object, or `None` if pagination is not configured for this view. * `paginate_queryset(self, queryset)` - Paginate a queryset if required, either returning a page object, or `None` if pagination is not configured for this view.
* `filter_queryset(self, queryset)` - Given a queryset, filter it with whichever filter backends are in use, returning a new queryset. * `filter_queryset(self, queryset)` - Given a queryset, filter it with whichever filter backends are in use, returning a new queryset.
......
source: metadata.py source: metadata.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Metadata # Metadata
> [The `OPTIONS`] method allows a client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval. > [The `OPTIONS`] method allows a client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval.
......
...@@ -150,7 +150,7 @@ Similar to `DjangoModelPermissions`, but also allows unauthenticated users to ha ...@@ -150,7 +150,7 @@ Similar to `DjangoModelPermissions`, but also allows unauthenticated users to ha
This permission class ties into Django's standard [object permissions framework][objectpermissions] that allows per-object permissions on models. In order to use this permission class, you'll also need to add a permission backend that supports object-level permissions, such as [django-guardian][guardian]. This permission class ties into Django's standard [object permissions framework][objectpermissions] that allows per-object permissions on models. In order to use this permission class, you'll also need to add a permission backend that supports object-level permissions, such as [django-guardian][guardian].
As with `DjangoModelPermissions`, this permission must only be applied to views that have a `.queryset` property. Authorization will only be granted if the user *is authenticated* and has the *relevant per-object permissions* and *relevant model permissions* assigned. As with `DjangoModelPermissions`, this permission must only be applied to views that have a `.queryset` property or `.get_queryset()` method. Authorization will only be granted if the user *is authenticated* and has the *relevant per-object permissions* and *relevant model permissions* assigned.
* `POST` requests require the user to have the `add` permission on the model instance. * `POST` requests require the user to have the `add` permission on the model instance.
* `PUT` and `PATCH` requests require the user to have the `change` permission on the model instance. * `PUT` and `PATCH` requests require the user to have the `change` permission on the model instance.
...@@ -233,10 +233,6 @@ Also note that the generic views will only check the object-level permissions fo ...@@ -233,10 +233,6 @@ Also note that the generic views will only check the object-level permissions fo
The following third party packages are also available. The following third party packages are also available.
## DRF Any Permissions
The [DRF Any Permissions][drf-any-permissions] packages provides a different permission behavior in contrast to REST framework. Instead of all specified permissions being required, only one of the given permissions has to be true in order to get access to the view.
## Composed Permissions ## Composed Permissions
The [Composed Permissions][composed-permissions] package provides a simple way to define complex and multi-depth (with logic operators) permission objects, using small and reusable components. The [Composed Permissions][composed-permissions] package provides a simple way to define complex and multi-depth (with logic operators) permission objects, using small and reusable components.
......
source: relations.py source: relations.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Serializer relations # Serializer relations
> Bad programmers worry about the code. > Bad programmers worry about the code.
...@@ -52,7 +46,7 @@ In order to explain the various types of relational fields, we'll use a couple o ...@@ -52,7 +46,7 @@ In order to explain the various types of relational fields, we'll use a couple o
class Meta: class Meta:
unique_together = ('album', 'order') unique_together = ('album', 'order')
order_by = 'order' ordering = ['order']
def __unicode__(self): def __unicode__(self):
return '%d: %s' % (self.order, self.title) return '%d: %s' % (self.order, self.title)
...@@ -279,7 +273,7 @@ If you want to implement a read-write relational field, you must also implement ...@@ -279,7 +273,7 @@ If you want to implement a read-write relational field, you must also implement
## Example ## Example
For, example, we could define a relational field, to serialize a track to a custom string representation, using its ordering, title, and duration. For example, we could define a relational field to serialize a track to a custom string representation, using its ordering, title, and duration.
import time import time
......
...@@ -110,7 +110,7 @@ An example of a view that uses `TemplateHTMLRenderer`: ...@@ -110,7 +110,7 @@ An example of a view that uses `TemplateHTMLRenderer`:
class UserDetail(generics.RetrieveAPIView): class UserDetail(generics.RetrieveAPIView):
""" """
A view that returns a templated HTML representations of a given user. A view that returns a templated HTML representation of a given user.
""" """
queryset = User.objects.all() queryset = User.objects.all()
renderer_classes = (TemplateHTMLRenderer,) renderer_classes = (TemplateHTMLRenderer,)
...@@ -135,7 +135,7 @@ See also: `StaticHTMLRenderer` ...@@ -135,7 +135,7 @@ See also: `StaticHTMLRenderer`
A simple renderer that simply returns pre-rendered HTML. Unlike other renderers, the data passed to the response object should be a string representing the content to be returned. A simple renderer that simply returns pre-rendered HTML. Unlike other renderers, the data passed to the response object should be a string representing the content to be returned.
An example of a view that uses `TemplateHTMLRenderer`: An example of a view that uses `StaticHTMLRenderer`:
@api_view(('GET',)) @api_view(('GET',))
@renderer_classes((StaticHTMLRenderer,)) @renderer_classes((StaticHTMLRenderer,))
...@@ -143,7 +143,7 @@ An example of a view that uses `TemplateHTMLRenderer`: ...@@ -143,7 +143,7 @@ An example of a view that uses `TemplateHTMLRenderer`:
data = '<html><body><h1>Hello, world</h1></body></html>' data = '<html><body><h1>Hello, world</h1></body></html>'
return Response(data) return Response(data)
You can use `TemplateHTMLRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint. You can use `StaticHTMLRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint.
**.media_type**: `text/html` **.media_type**: `text/html`
...@@ -406,7 +406,7 @@ Modify your REST framework settings. ...@@ -406,7 +406,7 @@ Modify your REST framework settings.
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( 'DEFAULT_RENDERER_CLASSES': (
'rest_framework_yaml.renderers.JSONPRenderer', 'rest_framework_jsonp.renderers.JSONPRenderer',
), ),
} }
......
source: request.py source: request.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Requests # Requests
> If you're doing REST-based web service stuff ... you should ignore request.POST. > If you're doing REST-based web service stuff ... you should ignore request.POST.
......
...@@ -290,14 +290,14 @@ The following third party packages are also available. ...@@ -290,14 +290,14 @@ The following third party packages are also available.
The [drf-nested-routers package][drf-nested-routers] provides routers and relationship fields for working with nested resources. The [drf-nested-routers package][drf-nested-routers] provides routers and relationship fields for working with nested resources.
## wq.db ## ModelRouter (wq.db.rest)
The [wq.db package][wq.db] provides an advanced [Router][wq.db-router] class (and singleton instance) that extends `DefaultRouter` with a `register_model()` API. Much like Django's `admin.site.register`, the only required argument to `app.router.register_model` is a model class. Reasonable defaults for a url prefix and viewset will be inferred from the model and global configuration. The [wq.db package][wq.db] provides an advanced [ModelRouter][wq.db-router] class (and singleton instance) that extends `DefaultRouter` with a `register_model()` API. Much like Django's `admin.site.register`, the only required argument to `rest.router.register_model` is a model class. Reasonable defaults for a url prefix, serializer, and viewset will be inferred from the model and global configuration.
from wq.db.rest import app from wq.db import rest
from myapp.models import MyModel from myapp.models import MyModel
app.router.register_model(MyModel) rest.router.register_model(MyModel)
## DRF-extensions ## DRF-extensions
...@@ -307,7 +307,7 @@ The [`DRF-extensions` package][drf-extensions] provides [routers][drf-extensions ...@@ -307,7 +307,7 @@ The [`DRF-extensions` package][drf-extensions] provides [routers][drf-extensions
[route-decorators]: viewsets.md#marking-extra-actions-for-routing [route-decorators]: viewsets.md#marking-extra-actions-for-routing
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers [drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
[wq.db]: http://wq.io/wq.db [wq.db]: http://wq.io/wq.db
[wq.db-router]: http://wq.io/docs/app.py [wq.db-router]: http://wq.io/docs/router
[drf-extensions]: http://chibisov.github.io/drf-extensions/docs/ [drf-extensions]: http://chibisov.github.io/drf-extensions/docs/
[drf-extensions-routers]: http://chibisov.github.io/drf-extensions/docs/#routers [drf-extensions-routers]: http://chibisov.github.io/drf-extensions/docs/#routers
[drf-extensions-nested-viewsets]: http://chibisov.github.io/drf-extensions/docs/#nested-routes [drf-extensions-nested-viewsets]: http://chibisov.github.io/drf-extensions/docs/#nested-routes
......
source: serializers.py source: serializers.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Serializers # Serializers
> Expanding the usefulness of the serializers is something that we would > Expanding the usefulness of the serializers is something that we would
...@@ -350,7 +344,7 @@ Here's an example for an `update()` method on our previous `UserSerializer` clas ...@@ -350,7 +344,7 @@ Here's an example for an `update()` method on our previous `UserSerializer` clas
return instance return instance
Because the behavior of nested creates and updates can be ambiguous, and may require complex dependancies between related models, REST framework 3 requires you to always write these methods explicitly. The default `ModelSerializer` `.create()` and `.update()` methods do not include support for writable nested representations. Because the behavior of nested creates and updates can be ambiguous, and may require complex dependencies between related models, REST framework 3 requires you to always write these methods explicitly. The default `ModelSerializer` `.create()` and `.update()` methods do not include support for writable nested representations.
It is possible that a third party package, providing automatic support some kinds of automatic writable nested representations may be released alongside the 3.1 release. It is possible that a third party package, providing automatic support some kinds of automatic writable nested representations may be released alongside the 3.1 release.
...@@ -484,7 +478,7 @@ The default `ModelSerializer` uses primary keys for relationships, but you can a ...@@ -484,7 +478,7 @@ The default `ModelSerializer` uses primary keys for relationships, but you can a
The `depth` option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation. The `depth` option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.
If you want to customize the way the serialization is done (e.g. using `allow_add_remove`) you'll need to define the field yourself. If you want to customize the way the serialization is done you'll need to define the field yourself.
## Specifying fields explicitly ## Specifying fields explicitly
...@@ -818,7 +812,7 @@ There are four methods that can be overridden, depending on what functionality y ...@@ -818,7 +812,7 @@ There are four methods that can be overridden, depending on what functionality y
* `.to_representation()` - Override this to support serialization, for read operations. * `.to_representation()` - Override this to support serialization, for read operations.
* `.to_internal_value()` - Override this to support deserialization, for write operations. * `.to_internal_value()` - Override this to support deserialization, for write operations.
* `.create()` and `.update()` - Overide either or both of these to support saving instances. * `.create()` and `.update()` - Override either or both of these to support saving instances.
Because this class provides the same interface as the `Serializer` class, you can use it with the existing generic class based views exactly as you would for a regular `Serializer` or `ModelSerializer`. Because this class provides the same interface as the `Serializer` class, you can use it with the existing generic class based views exactly as you would for a regular `Serializer` or `ModelSerializer`.
......
...@@ -65,7 +65,7 @@ When testing views directly using a request factory, it's often convenient to be ...@@ -65,7 +65,7 @@ When testing views directly using a request factory, it's often convenient to be
To forcibly authenticate a request, use the `force_authenticate()` method. To forcibly authenticate a request, use the `force_authenticate()` method.
from rest_framework.tests import force_authenticate from rest_framework.test import force_authenticate
factory = APIRequestFactory() factory = APIRequestFactory()
user = User.objects.get(username='olivia') user = User.objects.get(username='olivia')
......
source: validators.py source: validators.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Validators # Validators
> Validators can be useful for re-using validation logic between different types of fields. > Validators can be useful for re-using validation logic between different types of fields.
...@@ -178,7 +172,7 @@ REST framework includes a couple of defaults that may be useful in this context. ...@@ -178,7 +172,7 @@ REST framework includes a couple of defaults that may be useful in this context.
A default class that can be used to represent the current user. In order to use this, the 'request' must have been provided as part of the context dictionary when instantiating the serializer. A default class that can be used to represent the current user. In order to use this, the 'request' must have been provided as part of the context dictionary when instantiating the serializer.
owner = serializers.HiddenField( owner = serializers.HiddenField(
default=CurrentUserDefault() default=serializers.CurrentUserDefault()
) )
#### CreateOnlyDefault #### CreateOnlyDefault
...@@ -215,7 +209,7 @@ To write a class based validator, use the `__call__` method. Class based validat ...@@ -215,7 +209,7 @@ To write a class based validator, use the `__call__` method. Class based validat
self.base = base self.base = base
def __call__(self, value): def __call__(self, value):
if value % self.base != 0 if value % self.base != 0:
message = 'This field must be a multiple of %d.' % self.base message = 'This field must be a multiple of %d.' % self.base
raise serializers.ValidationError(message) raise serializers.ValidationError(message)
......
...@@ -31,6 +31,8 @@ How you vary the API behavior is up to you, but one example you might typically ...@@ -31,6 +31,8 @@ How you vary the API behavior is up to you, but one example you might typically
The `reverse` function included by REST framework ties in with the versioning scheme. You need to make sure to include the current `request` as a keyword argument, like so. The `reverse` function included by REST framework ties in with the versioning scheme. You need to make sure to include the current `request` as a keyword argument, like so.
from rest_framework.reverse import reverse
reverse('bookings-list', request=request) reverse('bookings-list', request=request)
The above function will apply any URL transformations appropriate to the request version. For example: The above function will apply any URL transformations appropriate to the request version. For example:
...@@ -103,7 +105,7 @@ Your client requests would now look like this: ...@@ -103,7 +105,7 @@ Your client requests would now look like this:
Host: example.com Host: example.com
Accept: application/vnd.megacorp.bookings+json; version=1.0 Accept: application/vnd.megacorp.bookings+json; version=1.0
## URLParameterVersioning ## URLPathVersioning
This scheme requires the client to specify the version as part of the URL path. This scheme requires the client to specify the version as part of the URL path.
......
...@@ -136,8 +136,13 @@ For example: ...@@ -136,8 +136,13 @@ For example:
@list_route() @list_route()
def recent_users(self, request): def recent_users(self, request):
recent_users = User.objects.all().order('-last_login') recent_users = User.objects.all().order('-last_login')
page = self.paginate_queryset(recent_users) page = self.paginate_queryset(recent_users)
serializer = self.get_pagination_serializer(page) if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(recent_users, many=True)
return Response(serializer.data) return Response(serializer.data)
The decorators can additionally take extra arguments that will be set for the routed view only. For example... The decorators can additionally take extra arguments that will be set for the routed view only. For example...
......
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
--- ---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available. **Note**: This is the documentation for the **version 3.1** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
For more details see the [3.0 release notes][3.0-announcement]. For more details see the [3.1 release notes][3.1-announcement].
--- ---
...@@ -50,7 +50,7 @@ Some reasons you might want to use REST framework: ...@@ -50,7 +50,7 @@ Some reasons you might want to use REST framework:
REST framework requires the following: REST framework requires the following:
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4) * Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)
* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7, 1.8-beta) * Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7+, 1.8)
The following packages are optional: The following packages are optional:
......
...@@ -17,6 +17,15 @@ Some highlights include: ...@@ -17,6 +17,15 @@ Some highlights include:
The pagination API has been improved, making it both easier to use, and more powerful. The pagination API has been improved, making it both easier to use, and more powerful.
A guide to the headline features follows. For full details, see [the pagination documentation][pagination].
Note that as a result of this work a number of settings keys and generic view attributes are now moved to pending deprecation. Controlling pagination styles is now largely handled by overriding a pagination class and modifying its configuration attributes.
* The `PAGINATE_BY` settings key will continue to work but is now pending deprecation. The more obviously named `PAGE_SIZE` settings key should now be used instead.
* The `PAGINATE_BY_PARAM`, `MAX_PAGINATE_BY` settings keys will continue to work but are now pending deprecation, in favor of setting configuration attributes on the configured pagination class.
* The `paginate_by`, `page_query_param`, `paginate_by_param` and `max_paginate_by` generic view attributes will continue to work but are now pending deprecation, in favor of setting configuration attributes on the configured pagination class.
* The `pagination_serializer_class` view attribute and `DEFAULT_PAGINATION_SERIALIZER_CLASS` settings key **are no longer valid**. The pagination API does not use serializers to determine the output format, and you'll need to instead override the `get_paginated_response` method on a pagination class in order to specify how the output format is controlled.
#### New pagination schemes. #### 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. 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.
...@@ -43,7 +52,7 @@ For more information, see the [custom pagination styles](../api-guide/pagination ...@@ -43,7 +52,7 @@ For more information, see the [custom pagination styles](../api-guide/pagination
## Versioning ## Versioning
We've made it easier to build versioned APIs. Built-in schemes for versioning include both URL based and Accept header based variations. We've made it [easier to build versioned APIs][versioning]. 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. When using a URL based scheme, hyperlinked serializers will resolve relationships to the same API version as used on the incoming request.
...@@ -71,7 +80,7 @@ The output representation would match the version used on the incoming request. ...@@ -71,7 +80,7 @@ The output representation would match the version used on the incoming request.
## Internationalization ## 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. REST framework now includes a built-in set of translations, and [supports internationalized error responses][internationalization]. 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: You can change the default language by using the standard Django `LANGUAGE_CODE` setting:
...@@ -136,7 +145,7 @@ If you're building a new 1.8 project, then you should probably consider using `U ...@@ -136,7 +145,7 @@ If you're building a new 1.8 project, then you should probably consider using `U
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. 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. For more information, see the documentation on [customizing field mappings][customizing-field-mappings] for ModelSerializer classes.
--- ---
...@@ -194,3 +203,7 @@ The next focus will be on HTML renderings of API output and will include: ...@@ -194,3 +203,7 @@ The next focus will be on HTML renderings of API output and will include:
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. 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 [custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling
[pagination]: ../api-guide/pagination.md
[versioning]: ../api-guide/versioning.md
[internationalization]: internationalization.md
[customizing-field-mappings]: ../api-guide/serializers.md#customizing-field-mappings
...@@ -97,9 +97,15 @@ The following template should be used for the description of the issue, and serv ...@@ -97,9 +97,15 @@ The following template should be used for the description of the issue, and serv
Release manager is @***. Release manager is @***.
Pull request is #***. Pull request is #***.
During development cycle:
- [ ] Upload the new content to be translated to [transifex](http://www.django-rest-framework.org/topics/project-management/#translations).
Checklist: Checklist:
- [ ] Create pull request for [release notes](https://github.com/tomchristie/django-rest-framework/blob/master/docs/topics/release-notes.md) based on the [*.*.* milestone](https://github.com/tomchristie/django-rest-framework/milestones/***). - [ ] Create pull request for [release notes](https://github.com/tomchristie/django-rest-framework/blob/master/docs/topics/release-notes.md) based on the [*.*.* milestone](https://github.com/tomchristie/django-rest-framework/milestones/***).
- [ ] Update the translations from [transifex](http://www.django-rest-framework.org/topics/project-management/#translations).
- [ ] Ensure the pull request increments the version to `*.*.*` in [`restframework/__init__.py`](https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/__init__.py). - [ ] Ensure the pull request increments the version to `*.*.*` in [`restframework/__init__.py`](https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/__init__.py).
- [ ] Confirm with @tomchristie that release is finalized and ready to go. - [ ] Confirm with @tomchristie that release is finalized and ready to go.
- [ ] Ensure that release date is included in pull request. - [ ] Ensure that release date is included in pull request.
......
...@@ -38,9 +38,56 @@ You can determine your currently installed version using `pip freeze`: ...@@ -38,9 +38,56 @@ You can determine your currently installed version using `pip freeze`:
--- ---
## 3.0.x series ## 3.1.x series
### 3.1.2
**Date**: [13rd May 2015][3.1.2-milestone].
* `DateField.to_representation` can handle str and empty values. ([#2656][gh2656], [#2687][gh2687], [#2869][gh2869])
* Use default reason phrases from HTTP standard. ([#2764][gh2764], [#2763][gh2763])
* Raise error when `ModelSerializer` used with abstract model. ([#2757][gh2757], [#2630][gh2630])
* Handle reversal of non-API view_name in `HyperLinkedRelatedField` ([#2724][gh2724], [#2711][gh2711])
* Dont require pk strictly for related fields. ([#2745][gh2745], [#2754][gh2754])
* Metadata detects null boolean field type. ([#2762][gh2762])
* Proper handling of depth in nested serializers. ([#2798][gh2798])
* Display viewset without paginator. ([#2807][gh2807])
* Don't check for deprecated `.model` attribute in permissions ([#2818][gh2818])
* Restrict integer field to integers and strings. ([#2835][gh2835], [#2836][gh2836])
* Improve `IntegerField` to use compiled decimal regex. ([#2853][gh2853])
* Prevent empty `queryset` to raise AssertionError. ([#2862][gh2862])
* `DjangoModelPermissions` rely on `get_queryset`. ([#2863][gh2863])
* Check `AcceptHeaderVersioning` with content negotiation in place. ([#2868][gh2868])
* Allow `DjangoObjectPermissions` to use views that define `get_queryset`. ([#2905][gh2905])
### 3.1.1
**Date**: [23rd March 2015][3.1.1-milestone].
* **Security fix**: Escape tab switching cookie name in browsable API.
* Display input forms in browsable API if `serializer_class` is used, even when `get_serializer` method does not exist on the view. ([#2743][gh2743])
* Use a password input for the AuthTokenSerializer. ([#2741][gh2741])
* Fix missing anchor closing tag after next button. ([#2691][gh2691])
* Fix `lookup_url_kwarg` handling in viewsets. ([#2685][gh2685], [#2591][gh2591])
* Fix problem with importing `rest_framework.views` in `apps.py` ([#2678][gh2678])
* LimitOffsetPagination raises `TypeError` if PAGE_SIZE not set ([#2667][gh2667], [#2700][gh2700])
* German translation for `min_value` field error message references `max_value`. ([#2645][gh2645])
* Remove `MergeDict`. ([#2640][gh2640])
* Support serializing unsaved models with related fields. ([#2637][gh2637], [#2641][gh2641])
* Allow blank/null on radio.html choices. ([#2631][gh2631])
### 3.1.0
**Date**: [5th March 2015][3.1.0-milestone].
For full details see the [3.1 release announcement](3.1-announcement.md).
---
## 3.0.x series
### 3.0.5 ### 3.0.5
**Date**: [10th February 2015][3.0.5-milestone]. **Date**: [10th February 2015][3.0.5-milestone].
...@@ -137,7 +184,7 @@ For full details see the [3.0 release announcement](3.0-announcement.md). ...@@ -137,7 +184,7 @@ For full details see the [3.0 release announcement](3.0-announcement.md).
--- ---
For older release notes, [please see the version 2.x documentation](old-release-notes). For older release notes, [please see the version 2.x documentation][old-release-notes].
[cite]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html [cite]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html
[deprecation-policy]: #deprecation-policy [deprecation-policy]: #deprecation-policy
...@@ -149,13 +196,16 @@ For older release notes, [please see the version 2.x documentation](old-release- ...@@ -149,13 +196,16 @@ For older release notes, [please see the version 2.x documentation](old-release-
[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
[ticket-582]: https://github.com/tomchristie/django-rest-framework/issues/582 [ticket-582]: https://github.com/tomchristie/django-rest-framework/issues/582
[rfc-6266]: http://tools.ietf.org/html/rfc6266#section-4.3 [rfc-6266]: http://tools.ietf.org/html/rfc6266#section-4.3
[old-release-notes]: http://tomchristie.github.io/rest-framework-2-docs/topics/release-notes#24x-series [old-release-notes]: https://github.com/tomchristie/django-rest-framework/blob/version-2.4.x/docs/topics/release-notes.md
[3.0.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.1+Release%22 [3.0.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.1+Release%22
[3.0.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.2+Release%22 [3.0.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.2+Release%22
[3.0.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.3+Release%22 [3.0.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.3+Release%22
[3.0.4-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.4+Release%22 [3.0.4-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.4+Release%22
[3.0.5-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.5+Release%22 [3.0.5-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.5+Release%22
[3.1.0-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.1.0+Release%22
[3.1.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.1.1+Release%22
[3.1.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.1.2+Release%22
<!-- 3.0.1 --> <!-- 3.0.1 -->
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013 [gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
...@@ -254,3 +304,40 @@ For older release notes, [please see the version 2.x documentation](old-release- ...@@ -254,3 +304,40 @@ For older release notes, [please see the version 2.x documentation](old-release-
[gh2519]: https://github.com/tomchristie/django-rest-framework/issues/2519 [gh2519]: https://github.com/tomchristie/django-rest-framework/issues/2519
[gh2524]: https://github.com/tomchristie/django-rest-framework/issues/2524 [gh2524]: https://github.com/tomchristie/django-rest-framework/issues/2524
[gh2530]: https://github.com/tomchristie/django-rest-framework/issues/2530 [gh2530]: https://github.com/tomchristie/django-rest-framework/issues/2530
<!-- 3.1.1 -->
[gh2691]: https://github.com/tomchristie/django-rest-framework/issues/2691
[gh2685]: https://github.com/tomchristie/django-rest-framework/issues/2685
[gh2591]: https://github.com/tomchristie/django-rest-framework/issues/2591
[gh2678]: https://github.com/tomchristie/django-rest-framework/issues/2678
[gh2667]: https://github.com/tomchristie/django-rest-framework/issues/2667
[gh2700]: https://github.com/tomchristie/django-rest-framework/issues/2700
[gh2645]: https://github.com/tomchristie/django-rest-framework/issues/2645
[gh2640]: https://github.com/tomchristie/django-rest-framework/issues/2640
[gh2637]: https://github.com/tomchristie/django-rest-framework/issues/2637
[gh2641]: https://github.com/tomchristie/django-rest-framework/issues/2641
[gh2631]: https://github.com/tomchristie/django-rest-framework/issues/2631
[gh2741]: https://github.com/tomchristie/django-rest-framework/issues/2641
[gh2743]: https://github.com/tomchristie/django-rest-framework/issues/2643
<!-- 3.1.2 -->
[gh2656]: https://github.com/tomchristie/django-rest-framework/issues/2656
[gh2687]: https://github.com/tomchristie/django-rest-framework/issues/2687
[gh2869]: https://github.com/tomchristie/django-rest-framework/issues/2869
[gh2764]: https://github.com/tomchristie/django-rest-framework/issues/2764
[gh2763]: https://github.com/tomchristie/django-rest-framework/issues/2763
[gh2757]: https://github.com/tomchristie/django-rest-framework/issues/2757
[gh2630]: https://github.com/tomchristie/django-rest-framework/issues/2630
[gh2724]: https://github.com/tomchristie/django-rest-framework/issues/2724
[gh2711]: https://github.com/tomchristie/django-rest-framework/issues/2711
[gh2745]: https://github.com/tomchristie/django-rest-framework/issues/2745
[gh2754]: https://github.com/tomchristie/django-rest-framework/issues/2754
[gh2762]: https://github.com/tomchristie/django-rest-framework/issues/2762
[gh2798]: https://github.com/tomchristie/django-rest-framework/issues/2798
[gh2807]: https://github.com/tomchristie/django-rest-framework/issues/2807
[gh2818]: https://github.com/tomchristie/django-rest-framework/issues/2818
[gh2835]: https://github.com/tomchristie/django-rest-framework/issues/2835
[gh2836]: https://github.com/tomchristie/django-rest-framework/issues/2836
[gh2853]: https://github.com/tomchristie/django-rest-framework/issues/2853
[gh2862]: https://github.com/tomchristie/django-rest-framework/issues/2862
[gh2863]: https://github.com/tomchristie/django-rest-framework/issues/2863
[gh2868]: https://github.com/tomchristie/django-rest-framework/issues/2868
[gh2905]: https://github.com/tomchristie/django-rest-framework/issues/2905
...@@ -206,6 +206,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque ...@@ -206,6 +206,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [drf-compound-fields][drf-compound-fields] - Provides "compound" serializer fields, such as lists of simple values. * [drf-compound-fields][drf-compound-fields] - Provides "compound" serializer fields, such as lists of simple values.
* [django-extra-fields][django-extra-fields] - Provides extra serializer fields. * [django-extra-fields][django-extra-fields] - Provides extra serializer fields.
* [django-versatileimagefield][django-versatileimagefield] - Provides a drop-in replacement for Django's stock `ImageField` that makes it easy to serve images in multiple sizes/renditions from a single field. For DRF-specific implementation docs, [click here][django-versatileimagefield-drf-docs].
### Views ### Views
...@@ -239,6 +240,8 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque ...@@ -239,6 +240,8 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [gaiarestframework][gaiarestframework] - Utils for django-rest-framewok * [gaiarestframework][gaiarestframework] - Utils for django-rest-framewok
* [drf-extensions][drf-extensions] - A collection of custom extensions * [drf-extensions][drf-extensions] - A collection of custom extensions
* [ember-django-adapter][ember-django-adapter] - An adapter for working with Ember.js * [ember-django-adapter][ember-django-adapter] - An adapter for working with Ember.js
* [django-versatileimagefield][django-versatileimagefield] - Provides a drop-in replacement for Django's stock `ImageField` that makes it easy to serve images in multiple sizes/renditions from a single field. For DRF-specific implementation docs, [click here][django-versatileimagefield-drf-docs].
* [drf-tracking][drf-tracking] - Utilities to track requests to DRF API views.
## Other Resources ## Other Resources
...@@ -265,6 +268,9 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque ...@@ -265,6 +268,9 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [Web API performance: profiling Django REST framework][web-api-performance-profiling-django-rest-framework] * [Web API performance: profiling Django REST framework][web-api-performance-profiling-django-rest-framework]
* [API Development with Django and Django REST Framework][api-development-with-django-and-django-rest-framework] * [API Development with Django and Django REST Framework][api-development-with-django-and-django-rest-framework]
### Documentations
* [Classy Django REST Framework][cdrf.co]
[cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html [cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html
[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework [cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework
[new-repo]: https://github.com/new [new-repo]: https://github.com/new
...@@ -326,3 +332,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque ...@@ -326,3 +332,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[web-api-performance-profiling-django-rest-framework]: http://dabapps.com/blog/api-performance-profiling-django-rest-framework/ [web-api-performance-profiling-django-rest-framework]: http://dabapps.com/blog/api-performance-profiling-django-rest-framework/
[api-development-with-django-and-django-rest-framework]: https://bnotions.com/api-development-with-django-and-django-rest-framework/ [api-development-with-django-and-django-rest-framework]: https://bnotions.com/api-development-with-django-and-django-rest-framework/
[django-rest-auth]: https://github.com/Tivix/django-rest-auth/ [django-rest-auth]: https://github.com/Tivix/django-rest-auth/
[django-versatileimagefield]: https://github.com/WGBH/django-versatileimagefield
[django-versatileimagefield-drf-docs]:http://django-versatileimagefield.readthedocs.org/en/latest/drf_integration.html
[cdrf.co]:http://www.cdrf.co
[drf-tracking]: https://github.com/aschn/drf-tracking
...@@ -28,11 +28,11 @@ Some example output from our serializer. ...@@ -28,11 +28,11 @@ Some example output from our serializer.
{ {
'title': 'Leaving party preperations', 'title': 'Leaving party preperations',
'items': { 'items': [
{'text': 'Compile playlist', 'is_completed': True}, {'text': 'Compile playlist', 'is_completed': True},
{'text': 'Send invites', 'is_completed': False}, {'text': 'Send invites', 'is_completed': False},
{'text': 'Clean house', 'is_completed': False} {'text': 'Clean house', 'is_completed': False}
} ]
} }
Let's take a look at updating our nested one-to-many data structure. Let's take a look at updating our nested one-to-many data structure.
......
...@@ -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 `{'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. 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 equivalent 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.
......
...@@ -200,7 +200,7 @@ See the [browsable api][browsable-api] topic for more information about the brow ...@@ -200,7 +200,7 @@ See the [browsable api][browsable-api] topic for more information about the brow
In [tutorial part 3][tut-3], we'll start using class based views, and see how generic views reduce the amount of code we need to write. In [tutorial part 3][tut-3], we'll start using class based views, and see how generic views reduce the amount of code we need to write.
[json-url]: http://example.com/api/items/4.json [json-url]: http://example.com/api/items/4/.json
[devserver]: http://127.0.0.1:8000/snippets/ [devserver]: http://127.0.0.1:8000/snippets/
[browsable-api]: ../topics/browsable-api.md [browsable-api]: ../topics/browsable-api.md
[tut-1]: 1-serialization.md [tut-1]: 1-serialization.md
......
...@@ -18,7 +18,7 @@ Right now we have endpoints for 'snippets' and 'users', but we don't have a sing ...@@ -18,7 +18,7 @@ Right now we have endpoints for 'snippets' and 'users', but we don't have a sing
'snippets': reverse('snippet-list', request=request, format=format) 'snippets': reverse('snippet-list', request=request, format=format)
}) })
Notice that we're using REST framework's `reverse` function in order to return fully-qualified URLs. Two things should be noticed here. First, we're using REST framework's `reverse` function in order to return fully-qualified URLs; second, URL patterns are identified by convenience names that we will declare later on in our `snippets/urls.py`.
## Creating an endpoint for the highlighted snippets ## Creating an endpoint for the highlighted snippets
...@@ -104,9 +104,11 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p ...@@ -104,9 +104,11 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p
* Our user serializer includes a field that refers to `'snippet-detail'`. * Our user serializer includes a field that refers to `'snippet-detail'`.
* Our snippet and user serializers include `'url'` fields that by default will refer to `'{model_name}-detail'`, which in this case will be `'snippet-detail'` and `'user-detail'`. * Our snippet and user serializers include `'url'` fields that by default will refer to `'{model_name}-detail'`, which in this case will be `'snippet-detail'` and `'user-detail'`.
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 like this:
from django.conf.urls import url, include from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
# API endpoints # API endpoints
urlpatterns = format_suffix_patterns([ urlpatterns = format_suffix_patterns([
...@@ -141,7 +143,7 @@ The list views for users and code snippets could end up returning quite a lot of ...@@ -141,7 +143,7 @@ The list views for users and code snippets could end up returning quite a lot of
We can change the default list style to use pagination, by modifying our `tutorial/settings.py` file slightly. Add the following setting: We can change the default list style to use pagination, by modifying our `tutorial/settings.py` file slightly. Add the following setting:
REST_FRAMEWORK = { REST_FRAMEWORK = {
'PAGINATE_BY': 10 'PAGE_SIZE': 10
} }
Note that settings in REST framework are all namespaced into a single dictionary setting, named 'REST_FRAMEWORK', which helps keep them well separated from your other project settings. Note that settings in REST framework are all namespaced into a single dictionary setting, named 'REST_FRAMEWORK', which helps keep them well separated from your other project settings.
......
...@@ -83,8 +83,6 @@ Rather than write multiple views we're grouping together all the common behavior ...@@ -83,8 +83,6 @@ Rather than write multiple views we're grouping together all the common behavior
We can easily break these down into individual views if we need to, but using viewsets keeps the view logic nicely organized as well as being very concise. We can easily break these down into individual views if we need to, but using viewsets keeps the view logic nicely organized as well as being very concise.
Notice that our viewset classes here are a little different from those in the [frontpage example][readme-example-api], as they include `queryset` and `serializer_class` attributes, instead of a `model` attribute.
For trivial cases you can simply set a `model` attribute on the `ViewSet` class and the serializer and queryset will be automatically generated for you. Setting the `queryset` and/or `serializer_class` attributes gives you more explicit control of the API behaviour, and is the recommended style for most applications. For trivial cases you can simply set a `model` attribute on the `ViewSet` class and the serializer and queryset will be automatically generated for you. Setting the `queryset` and/or `serializer_class` attributes gives you more explicit control of the API behaviour, and is the recommended style for most applications.
## URLs ## URLs
...@@ -123,7 +121,7 @@ We'd also like to set a few global settings. We'd like to turn on pagination, a ...@@ -123,7 +121,7 @@ We'd also like to set a few global settings. We'd like to turn on pagination, a
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',), 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
'PAGINATE_BY': 10 'PAGE_SIZE': 10
} }
Okay, we're done. Okay, we're done.
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8"> <meta charset="utf-8">
<title>{{ page_title }}</title> <title>{% if page_title %}{{ page_title }} - {% endif %}{{ site_name }}</title>
<link href="{{ base_url }}/img/favicon.ico" rel="icon" type="image/x-icon"> <link href="{{ base_url }}/img/favicon.ico" rel="icon" type="image/x-icon">
<link rel="canonical" href="{{ canonical_url }}" /> <link rel="canonical" href="{{ canonical_url }}" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Django, API, REST, {{ current_page.title }}"> <meta name="description" content="Django, API, REST{% if current_page %}, {{ current_page.title }}{% endif %}">
<meta name="author" content="Tom Christie"> <meta name="author" content="Tom Christie">
<!-- Le styles --> <!-- Le styles -->
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
} }
</style> </style>
</head> </head>
<body onload="prettyPrint()" class="{% if current_page.is_homepage %}index{% endif %}-page"> <body onload="prettyPrint()" class="{% if current_page and current_page.is_homepage %}index{% endif %}-page">
<div class="wrapper"> <div class="wrapper">
...@@ -64,27 +64,19 @@ ...@@ -64,27 +64,19 @@
<div class="container-fluid"> <div class="container-fluid">
<!-- Search Modal --> <!-- Search Modal -->
<div id="searchModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div id="mkdocs_search_modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 id="myModalLabel">Documentation search</h3> <h3 id="myModalLabel">Documentation search</h3>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<!-- Custom google search --> <form role="form">
<script> <div class="form-group">
(function() { <input type="text" name="q" class="form-control" placeholder="Search..." id="mkdocs-search-query">
var cx = '015016005043623903336:rxraeohqk6w'; </div>
var gcse = document.createElement('script'); </form>
gcse.type = 'text/javascript'; <div id="mkdocs-search-results"></div>
gcse.async = true;
gcse.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') +
'//www.google.com/cse/cse.js?cx=' + cx;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
})();
</script>
<gcse:search></gcse:search>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
...@@ -104,7 +96,7 @@ ...@@ -104,7 +96,7 @@
<div id="table-of-contents"> <div id="table-of-contents">
<ul class="nav nav-list side-nav well sidebar-nav-fixed"> <ul class="nav nav-list side-nav well sidebar-nav-fixed">
{% if current_page.is_homepage %} {% if current_page and current_page.is_homepage %}
<li class="main"> <li class="main">
<a href="#">Django REST framework</a> <a href="#">Django REST framework</a>
</li> </li>
...@@ -112,7 +104,7 @@ ...@@ -112,7 +104,7 @@
{% for toc_item in toc %} {% for toc_item in toc %}
<li class="{% if not current_page.is_homepage %}main{% endif %}"> <li class="{% if current_page and not current_page.is_homepage %}main{% endif %}">
<a href="{{ toc_item.url }}">{{ toc_item.title }}</a> <a href="{{ toc_item.url }}">{{ toc_item.title }}</a>
</li> </li>
...@@ -124,7 +116,7 @@ ...@@ -124,7 +116,7 @@
{% endfor %} {% endfor %}
{% if current_page.is_homepage %} {% if current_page and current_page.is_homepage %}
<div class="promo"> <div class="promo">
<hr/> <hr/>
<script type="text/javascript" src="//cdn.fusionads.net/fusion.js?zoneid=1332&serve=C6SDP2Y&placement=djangorestframework" id="_fusionads_js"></script> <script type="text/javascript" src="//cdn.fusionads.net/fusion.js?zoneid=1332&serve=C6SDP2Y&placement=djangorestframework" id="_fusionads_js"></script>
...@@ -137,6 +129,9 @@ ...@@ -137,6 +129,9 @@
</div> </div>
<div id="main-content" class="span9"> <div id="main-content" class="span9">
{% block content %}
{% if meta.source %} {% if meta.source %}
{% for filename in meta.source %} {% for filename in meta.source %}
<a class="github" href="https://github.com/tomchristie/django-rest-framework/tree/master/rest_framework/{{ filename }}"> <a class="github" href="https://github.com/tomchristie/django-rest-framework/tree/master/rest_framework/{{ filename }}">
...@@ -146,6 +141,9 @@ ...@@ -146,6 +141,9 @@
{% endif %} {% endif %}
{{ content }} {{ content }}
{% endblock %}
</div> </div>
<!--/span--> <!--/span-->
</div> </div>
...@@ -169,6 +167,8 @@ ...@@ -169,6 +167,8 @@
<script src="{{ base_url }}/js/jquery-1.8.1-min.js"></script> <script src="{{ base_url }}/js/jquery-1.8.1-min.js"></script>
<script src="{{ base_url }}/js/prettify-1.0.js"></script> <script src="{{ base_url }}/js/prettify-1.0.js"></script>
<script src="{{ base_url }}/js/bootstrap-2.1.1-min.js"></script> <script src="{{ base_url }}/js/bootstrap-2.1.1-min.js"></script>
<script>var base_url = '{{ base_url }}';</script>
<script src="{{ base_url }}/mkdocs/js/require.js"></script>
<script src="{{ base_url }}/js/theme.js"></script> <script src="{{ base_url }}/js/theme.js"></script>
<script> <script>
......
...@@ -415,3 +415,7 @@ ul.sponsor { ...@@ -415,3 +415,7 @@ ul.sponsor {
list-style: none; list-style: none;
display: block; display: block;
} }
#mkdocs_search_modal article p{
word-wrap: break-word;
}
$(function(){ function getSearchTerm()
{
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++)
{
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == 'q')
{
return sParameterName[1];
}
}
}
$(function() {
var initialise_search = function(){
require.config({"baseUrl":"/mkdocs/js"});
require(["search",]);
}
var search_term = getSearchTerm();
if(search_term){
$('#mkdocs_search_modal').modal();
}
$('pre code').parent().addClass('prettyprint well'); $('pre code').parent().addClass('prettyprint well');
$(document).on("submit", "#mkdocs_search_modal form", function (e) {
$("#mkdocs-search-results").html("Searching...");
initialise_search();
return false;
});
}); });
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<a class="repo-link btn btn-inverse btn-small {% if not previous_page %}disabled{% endif %}" rel="next" {% if previous_page %}href="{{ previous_page.url }}"{% endif %}> <a class="repo-link btn btn-inverse btn-small {% if not previous_page %}disabled{% endif %}" rel="next" {% if previous_page %}href="{{ previous_page.url }}"{% endif %}>
<i class="icon-arrow-left icon-white"></i> Previous <i class="icon-arrow-left icon-white"></i> Previous
</a> </a>
<a class="repo-link btn btn-inverse btn-small" href="#searchModal" data-toggle="modal"><i class="icon-search icon-white"></i> Search</a> <a id="search_modal_show" class="repo-link btn btn-inverse btn-small" href="#mkdocs_search_modal" data-toggle="modal" data-target="#mkdocs_search_modal"><i class="icon-search icon-white"></i> Search</a>
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
...@@ -19,7 +19,6 @@ ...@@ -19,7 +19,6 @@
{% if include_nav %} {% if include_nav %}
<!-- Main navigation --> <!-- Main navigation -->
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li {% if current_page.is_homepage %}class="active"{% endif %}><a href="/">Home</a></li>
{% for nav_item in nav %} {% if nav_item.children %} {% for nav_item in nav %} {% if nav_item.children %}
<li class="dropdown{% if nav_item.active %} active{% endif %}"> <li class="dropdown{% if nav_item.active %} active{% endif %}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ nav_item.title }} <b class="caret"></b></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ nav_item.title }} <b class="caret"></b></a>
......
...@@ -7,50 +7,53 @@ repo_url: https://github.com/tomchristie/django-rest-framework ...@@ -7,50 +7,53 @@ repo_url: https://github.com/tomchristie/django-rest-framework
theme_dir: docs_theme theme_dir: docs_theme
pages: pages:
- ['index.md', 'Home'] - Home: 'index.md'
- ['tutorial/quickstart.md', 'Tutorial', 'Quickstart'] - Tutorial:
- ['tutorial/1-serialization.md', 'Tutorial', '1 - Serialization'] - 'Quickstart': 'tutorial/quickstart.md'
- ['tutorial/2-requests-and-responses.md', 'Tutorial', '2 - Requests and responses'] - '1 - Serialization': 'tutorial/1-serialization.md'
- ['tutorial/3-class-based-views.md', 'Tutorial', '3 - Class based views'] - '2 - Requests and responses': 'tutorial/2-requests-and-responses.md'
- ['tutorial/4-authentication-and-permissions.md', 'Tutorial', '4 - Authentication and permissions'] - '3 - Class based views': 'tutorial/3-class-based-views.md'
- ['tutorial/5-relationships-and-hyperlinked-apis.md', 'Tutorial', '5 - Relationships and hyperlinked APIs'] - '4 - Authentication and permissions': 'tutorial/4-authentication-and-permissions.md'
- ['tutorial/6-viewsets-and-routers.md', 'Tutorial', '6 - Viewsets and routers'] - '5 - Relationships and hyperlinked APIs': 'tutorial/5-relationships-and-hyperlinked-apis.md'
- ['api-guide/requests.md', 'API Guide', 'Requests'] - '6 - Viewsets and routers': 'tutorial/6-viewsets-and-routers.md'
- ['api-guide/responses.md', 'API Guide', 'Responses'] - API Guide:
- ['api-guide/views.md', 'API Guide', 'Views'] - 'Requests': 'api-guide/requests.md'
- ['api-guide/generic-views.md', 'API Guide', 'Generic views'] - 'Responses': 'api-guide/responses.md'
- ['api-guide/viewsets.md', 'API Guide', 'Viewsets'] - 'Views': 'api-guide/views.md'
- ['api-guide/routers.md', 'API Guide', 'Routers'] - 'Generic views': 'api-guide/generic-views.md'
- ['api-guide/parsers.md', 'API Guide', 'Parsers'] - 'Viewsets': 'api-guide/viewsets.md'
- ['api-guide/renderers.md', 'API Guide', 'Renderers'] - 'Routers': 'api-guide/routers.md'
- ['api-guide/serializers.md', 'API Guide', 'Serializers'] - 'Parsers': 'api-guide/parsers.md'
- ['api-guide/fields.md', 'API Guide', 'Serializer fields'] - 'Renderers': 'api-guide/renderers.md'
- ['api-guide/relations.md', 'API Guide', 'Serializer relations'] - 'Serializers': 'api-guide/serializers.md'
- ['api-guide/validators.md', 'API Guide', 'Validators'] - 'Serializer fields': 'api-guide/fields.md'
- ['api-guide/authentication.md', 'API Guide', 'Authentication'] - 'Serializer relations': 'api-guide/relations.md'
- ['api-guide/permissions.md', 'API Guide', 'Permissions'] - 'Validators': 'api-guide/validators.md'
- ['api-guide/throttling.md', 'API Guide', 'Throttling'] - 'Authentication': 'api-guide/authentication.md'
- ['api-guide/filtering.md', 'API Guide', 'Filtering'] - 'Permissions': 'api-guide/permissions.md'
- ['api-guide/pagination.md', 'API Guide', 'Pagination'] - 'Throttling': 'api-guide/throttling.md'
- ['api-guide/versioning.md', 'API Guide', 'Versioning'] - 'Filtering': 'api-guide/filtering.md'
- ['api-guide/content-negotiation.md', 'API Guide', 'Content negotiation'] - 'Pagination': 'api-guide/pagination.md'
- ['api-guide/metadata.md', 'API Guide', 'Metadata'] - 'Versioning': 'api-guide/versioning.md'
- ['api-guide/format-suffixes.md', 'API Guide', 'Format suffixes'] - 'Content negotiation': 'api-guide/content-negotiation.md'
- ['api-guide/reverse.md', 'API Guide', 'Returning URLs'] - 'Metadata': 'api-guide/metadata.md'
- ['api-guide/exceptions.md', 'API Guide', 'Exceptions'] - 'Format suffixes': 'api-guide/format-suffixes.md'
- ['api-guide/status-codes.md', 'API Guide', 'Status codes'] - 'Returning URLs': 'api-guide/reverse.md'
- ['api-guide/testing.md', 'API Guide', 'Testing'] - 'Exceptions': 'api-guide/exceptions.md'
- ['api-guide/settings.md', 'API Guide', 'Settings'] - 'Status codes': 'api-guide/status-codes.md'
- ['topics/documenting-your-api.md', 'Topics', 'Documenting your API'] - 'Testing': 'api-guide/testing.md'
- ['topics/internationalization.md', 'Topics', 'Internationalization'] - 'Settings': 'api-guide/settings.md'
- ['topics/ajax-csrf-cors.md', 'Topics', 'AJAX, CSRF & CORS'] - Topics:
- ['topics/browser-enhancements.md', 'Topics',] - 'Documenting your API': 'topics/documenting-your-api.md'
- ['topics/browsable-api.md', 'Topics', 'The Browsable API'] - 'Internationalization': 'topics/internationalization.md'
- ['topics/rest-hypermedia-hateoas.md', 'Topics', 'REST, Hypermedia & HATEOAS'] - 'AJAX, CSRF & CORS': 'topics/ajax-csrf-cors.md'
- ['topics/third-party-resources.md', 'Topics', 'Third Party Resources'] - 'Browser Enhancements': 'topics/browser-enhancements.md'
- ['topics/contributing.md', 'Topics', 'Contributing to REST framework'] - 'The Browsable API': 'topics/browsable-api.md'
- ['topics/project-management.md', 'Topics', 'Project management'] - 'REST, Hypermedia & HATEOAS': 'topics/rest-hypermedia-hateoas.md'
- ['topics/3.0-announcement.md', 'Topics', '3.0 Announcement'] - 'Third Party Resources': 'topics/third-party-resources.md'
- ['topics/3.1-announcement.md', 'Topics', '3.1 Announcement'] - 'Contributing to REST framework': 'topics/contributing.md'
- ['topics/kickstarter-announcement.md', 'Topics', 'Kickstarter Announcement'] - 'Project management': 'topics/project-management.md'
- ['topics/release-notes.md', 'Topics', 'Release Notes'] - '3.0 Announcement': 'topics/3.0-announcement.md'
- '3.1 Announcement': 'topics/3.1-announcement.md'
- 'Kickstarter Announcement': 'topics/kickstarter-announcement.md'
- 'Release Notes': 'topics/release-notes.md'
# PEP8 code linting, which we run on all commits. # PEP8 code linting, which we run on all commits.
flake8==2.3.0 flake8==2.4.0
pep8==1.6.2 pep8==1.5.7
# MkDocs to build our documentation. # MkDocs to build our documentation.
mkdocs==0.11.1 mkdocs==0.13.2
...@@ -8,7 +8,7 @@ ______ _____ _____ _____ __ ...@@ -8,7 +8,7 @@ ______ _____ _____ _____ __
""" """
__title__ = 'Django REST framework' __title__ = 'Django REST framework'
__version__ = '3.1.0' __version__ = '3.1.2'
__author__ = 'Tom Christie' __author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause' __license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2015 Tom Christie' __copyright__ = 'Copyright 2011-2015 Tom Christie'
......
...@@ -8,6 +8,7 @@ from django.middleware.csrf import CsrfViewMiddleware ...@@ -8,6 +8,7 @@ from django.middleware.csrf import CsrfViewMiddleware
from django.utils.translation import ugettext_lazy as _ 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
from rest_framework.compat import get_user_model
def get_authorization_header(request): def get_authorization_header(request):
...@@ -85,7 +86,12 @@ class BasicAuthentication(BaseAuthentication): ...@@ -85,7 +86,12 @@ 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) username_field = getattr(get_user_model(), 'USERNAME_FIELD', 'username')
credentials = {
username_field: userid,
'password': password
}
user = authenticate(**credentials)
if user is None: if user is None:
raise exceptions.AuthenticationFailed(_('Invalid username/password.')) raise exceptions.AuthenticationFailed(_('Invalid username/password.'))
......
...@@ -6,7 +6,7 @@ from rest_framework import exceptions, serializers ...@@ -6,7 +6,7 @@ from rest_framework import exceptions, serializers
class AuthTokenSerializer(serializers.Serializer): class AuthTokenSerializer(serializers.Serializer):
username = serializers.CharField() username = serializers.CharField()
password = serializers.CharField() password = serializers.CharField(style={'input_type': 'password'})
def validate(self, attrs): def validate(self, attrs):
username = attrs.get('username') username = attrs.get('username')
......
...@@ -11,9 +11,10 @@ class ObtainAuthToken(APIView): ...@@ -11,9 +11,10 @@ class ObtainAuthToken(APIView):
permission_classes = () permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,) parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,) renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
def post(self, request): def post(self, request):
serializer = AuthTokenSerializer(data=request.data) serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user'] user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user) token, created = Token.objects.get_or_create(user=user)
......
...@@ -7,6 +7,7 @@ versions of django/python, and compatibility wrappers around optional packages. ...@@ -7,6 +7,7 @@ versions of django/python, and compatibility wrappers around optional packages.
from __future__ import unicode_literals 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.db import connection, transaction
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.six.moves.urllib.parse import urlparse as _urlparse from django.utils.six.moves.urllib.parse import urlparse as _urlparse
from django.utils import six from django.utils import six
...@@ -119,6 +120,14 @@ def get_model_name(model_cls): ...@@ -119,6 +120,14 @@ def get_model_name(model_cls):
return model_cls._meta.module_name return model_cls._meta.module_name
# Support custom user models in Django 1.5+
try:
from django.contrib.auth import get_user_model
except ImportError:
from django.contrib.auth.models import User
get_user_model = lambda: User
# View._allowed_methods only present from 1.5 onwards # View._allowed_methods only present from 1.5 onwards
if django.VERSION >= (1, 5): if django.VERSION >= (1, 5):
from django.views.generic import View from django.views.generic import View
...@@ -250,3 +259,27 @@ else: ...@@ -250,3 +259,27 @@ else:
SHORT_SEPARATORS = (b',', b':') SHORT_SEPARATORS = (b',', b':')
LONG_SEPARATORS = (b', ', b': ') LONG_SEPARATORS = (b', ', b': ')
INDENT_SEPARATORS = (b',', b': ') INDENT_SEPARATORS = (b',', b': ')
if django.VERSION >= (1, 8):
from django.db.models import DurationField
from django.utils.dateparse import parse_duration
from django.utils.duration import duration_string
else:
DurationField = duration_string = parse_duration = None
def set_rollback():
if hasattr(transaction, 'set_rollback'):
if connection.settings_dict.get('ATOMIC_REQUESTS', False):
# If running in >=1.6 then mark a rollback as required,
# and allow it to be handled by Django.
transaction.set_rollback(True)
elif transaction.is_managed():
# Otherwise handle it explicitly if in managed mode.
if transaction.is_dirty():
transaction.rollback()
transaction.leave_transaction_management()
else:
# transaction not managed
pass
...@@ -13,7 +13,7 @@ from rest_framework import ISO_8601 ...@@ -13,7 +13,7 @@ from rest_framework import ISO_8601
from rest_framework.compat import ( from rest_framework.compat import (
EmailValidator, MinValueValidator, MaxValueValidator, EmailValidator, MinValueValidator, MaxValueValidator,
MinLengthValidator, MaxLengthValidator, URLValidator, OrderedDict, MinLengthValidator, MaxLengthValidator, URLValidator, OrderedDict,
unicode_repr, unicode_to_repr unicode_repr, unicode_to_repr, parse_duration, duration_string,
) )
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
...@@ -104,7 +104,7 @@ def set_value(dictionary, keys, value): ...@@ -104,7 +104,7 @@ def set_value(dictionary, keys, value):
dictionary[keys[-1]] = value dictionary[keys[-1]] = value
class CreateOnlyDefault: class CreateOnlyDefault(object):
""" """
This class may be used to provide default values that are only used This class may be used to provide default values that are only used
for create operations, but that do not return any value for update for create operations, but that do not return any value for update
...@@ -115,6 +115,8 @@ class CreateOnlyDefault: ...@@ -115,6 +115,8 @@ class CreateOnlyDefault:
def set_context(self, serializer_field): def set_context(self, serializer_field):
self.is_update = serializer_field.parent.instance is not None self.is_update = serializer_field.parent.instance is not None
if callable(self.default) and hasattr(self.default, 'set_context') and not self.is_update:
self.default.set_context(serializer_field)
def __call__(self): def __call__(self):
if self.is_update: if self.is_update:
...@@ -129,7 +131,7 @@ class CreateOnlyDefault: ...@@ -129,7 +131,7 @@ class CreateOnlyDefault:
) )
class CurrentUserDefault: class CurrentUserDefault(object):
def set_context(self, serializer_field): def set_context(self, serializer_field):
self.user = serializer_field.context['request'].user self.user = serializer_field.context['request'].user
...@@ -338,7 +340,7 @@ class Field(object): ...@@ -338,7 +340,7 @@ class Field(object):
* Raise `ValidationError`, indicating invalid data. * Raise `ValidationError`, indicating invalid data.
* Raise `SkipField`, indicating that the field should be ignored. * Raise `SkipField`, indicating that the field should be ignored.
* Return (True, data), indicating an empty value that should be * Return (True, data), indicating an empty value that should be
returned without any furhter validation being applied. returned without any further validation being applied.
* Return (False, data), indicating a non-empty value, that should * Return (False, data), indicating a non-empty value, that should
have validation applied as normal. have validation applied as normal.
""" """
...@@ -638,20 +640,37 @@ class URLField(CharField): ...@@ -638,20 +640,37 @@ class URLField(CharField):
class UUIDField(Field): class UUIDField(Field):
valid_formats = ('hex_verbose', 'hex', 'int', 'urn')
default_error_messages = { default_error_messages = {
'invalid': _('"{value}" is not a valid UUID.'), 'invalid': _('"{value}" is not a valid UUID.'),
} }
def __init__(self, **kwargs):
self.uuid_format = kwargs.pop('format', 'hex_verbose')
if self.uuid_format not in self.valid_formats:
raise ValueError(
'Invalid format for uuid representation. '
'Must be one of "{0}"'.format('", "'.join(self.valid_formats))
)
super(UUIDField, self).__init__(**kwargs)
def to_internal_value(self, data): def to_internal_value(self, data):
if not isinstance(data, uuid.UUID): if not isinstance(data, uuid.UUID):
try: try:
return uuid.UUID(data) if isinstance(data, six.integer_types):
return uuid.UUID(int=data)
else:
return uuid.UUID(hex=data)
except (ValueError, TypeError): except (ValueError, TypeError):
self.fail('invalid', value=data) self.fail('invalid', value=data)
return data return data
def to_representation(self, value): def to_representation(self, value):
if self.uuid_format == 'hex_verbose':
return str(value) return str(value)
else:
return getattr(value, self.uuid_format)
class IPAddressField(CharField): class IPAddressField(CharField):
...@@ -689,6 +708,7 @@ class IntegerField(Field): ...@@ -689,6 +708,7 @@ class IntegerField(Field):
'max_string_length': _('String value too large.') 'max_string_length': _('String value too large.')
} }
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
re_decimal = re.compile(r'\.0*\s*$') # allow e.g. '1.0' as an int, but not '1.2'
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.max_value = kwargs.pop('max_value', None) self.max_value = kwargs.pop('max_value', None)
...@@ -706,7 +726,7 @@ class IntegerField(Field): ...@@ -706,7 +726,7 @@ class IntegerField(Field):
self.fail('max_string_length') self.fail('max_string_length')
try: try:
data = int(data) data = int(self.re_decimal.sub('', str(data)))
except (ValueError, TypeError): except (ValueError, TypeError):
self.fail('invalid') self.fail('invalid')
return data return data
...@@ -805,7 +825,8 @@ class DecimalField(Field): ...@@ -805,7 +825,8 @@ class DecimalField(Field):
self.fail('invalid') self.fail('invalid')
sign, digittuple, exponent = value.as_tuple() sign, digittuple, exponent = value.as_tuple()
decimals = abs(exponent) decimals = exponent * decimal.Decimal(-1) if exponent < 0 else 0
# digittuple doesn't include any leading zeros. # digittuple doesn't include any leading zeros.
digits = len(digittuple) digits = len(digittuple)
if decimals > digits: if decimals > digits:
...@@ -948,6 +969,9 @@ class DateField(Field): ...@@ -948,6 +969,9 @@ class DateField(Field):
self.fail('invalid', format=humanized_format) self.fail('invalid', format=humanized_format)
def to_representation(self, value): def to_representation(self, value):
if not value:
return None
if self.format is None: if self.format is None:
return value return value
...@@ -961,7 +985,10 @@ class DateField(Field): ...@@ -961,7 +985,10 @@ class DateField(Field):
) )
if self.format.lower() == ISO_8601: if self.format.lower() == ISO_8601:
if (isinstance(value, str)):
value = datetime.datetime.strptime(value, '%Y-%m-%d').date()
return value.isoformat() return value.isoformat()
return value.strftime(self.format) return value.strftime(self.format)
...@@ -1019,6 +1046,29 @@ class TimeField(Field): ...@@ -1019,6 +1046,29 @@ class TimeField(Field):
return value.strftime(self.format) return value.strftime(self.format)
class DurationField(Field):
default_error_messages = {
'invalid': _('Duration has wrong format. Use one of these formats instead: {format}.'),
}
def __init__(self, *args, **kwargs):
if parse_duration is None:
raise NotImplementedError(
'DurationField not supported for django versions prior to 1.8')
return super(DurationField, self).__init__(*args, **kwargs)
def to_internal_value(self, value):
if isinstance(value, datetime.timedelta):
return value
parsed = parse_duration(value)
if parsed is not None:
return parsed
self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]')
def to_representation(self, value):
return duration_string(value)
# Choice types... # Choice types...
class ChoiceField(Field): class ChoiceField(Field):
...@@ -1062,7 +1112,7 @@ class ChoiceField(Field): ...@@ -1062,7 +1112,7 @@ class ChoiceField(Field):
def to_representation(self, value): def to_representation(self, value):
if value in ('', None): if value in ('', None):
return value return value
return self.choice_strings_to_values[six.text_type(value)] return self.choice_strings_to_values.get(six.text_type(value), value)
class MultipleChoiceField(ChoiceField): class MultipleChoiceField(ChoiceField):
...@@ -1076,7 +1126,11 @@ class MultipleChoiceField(ChoiceField): ...@@ -1076,7 +1126,11 @@ class MultipleChoiceField(ChoiceField):
# We override the default field access in order to support # We override the default field access in order to support
# lists in HTML forms. # lists in HTML forms.
if html.is_html_input(dictionary): if html.is_html_input(dictionary):
return dictionary.getlist(self.field_name) ret = dictionary.getlist(self.field_name)
if getattr(self.root, 'partial', False) and not ret:
ret = empty
return ret
return dictionary.get(self.field_name, empty) return dictionary.get(self.field_name, empty)
def to_internal_value(self, data): def to_internal_value(self, data):
...@@ -1090,7 +1144,7 @@ class MultipleChoiceField(ChoiceField): ...@@ -1090,7 +1144,7 @@ class MultipleChoiceField(ChoiceField):
def to_representation(self, value): def to_representation(self, value):
return set([ return set([
self.choice_strings_to_values[six.text_type(item)] for item in value self.choice_strings_to_values.get(six.text_type(item), item) for item in value
]) ])
......
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:40+0000\n" "POT-Creation-Date: 2015-06-01 23:48+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -17,36 +17,48 @@ msgstr "" ...@@ -17,36 +17,48 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: authentication.py:69 #: authentication.py:70
msgid "Invalid basic header. No credentials provided." msgid "Invalid basic header. No credentials provided."
msgstr "" msgstr ""
#: authentication.py:72 #: authentication.py:73
msgid "Invalid basic header. Credentials string should not contain spaces." msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr "" msgstr ""
#: authentication.py:78 #: authentication.py:79
msgid "Invalid basic header. Credentials not correctly base64 encoded." msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr "" msgstr ""
#: authentication.py:90 #: authentication.py:97
msgid "Invalid username/password." msgid "Invalid username/password."
msgstr "" msgstr ""
#: authentication.py:156 #: authentication.py:100 authentication.py:182
msgid "User inactive or deleted."
msgstr ""
#: authentication.py:167
msgid "Invalid token header. No credentials provided." msgid "Invalid token header. No credentials provided."
msgstr "" msgstr ""
#: authentication.py:159 #: authentication.py:170
msgid "Invalid token header. Token string should not contain spaces." msgid "Invalid token header. Token string should not contain spaces."
msgstr "" msgstr ""
#: authentication.py:168 #: authentication.py:179
msgid "Invalid token." msgid "Invalid token."
msgstr "" msgstr ""
#: authentication.py:171 #: authtoken/serializers.py:20
msgid "User inactive or deleted." 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 "" msgstr ""
#: exceptions.py:38 #: exceptions.py:38
...@@ -74,6 +86,7 @@ msgid "Not found." ...@@ -74,6 +86,7 @@ msgid "Not found."
msgstr "" msgstr ""
#: exceptions.py:98 #: exceptions.py:98
#, python-brace-format
msgid "Method \"{method}\" not allowed." msgid "Method \"{method}\" not allowed."
msgstr "" msgstr ""
...@@ -82,6 +95,7 @@ msgid "Could not satisfy the request Accept header." ...@@ -82,6 +95,7 @@ msgid "Could not satisfy the request Accept header."
msgstr "" msgstr ""
#: exceptions.py:121 #: exceptions.py:121
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request." msgid "Unsupported media type \"{media_type}\" in request."
msgstr "" msgstr ""
...@@ -89,159 +103,178 @@ msgstr "" ...@@ -89,159 +103,178 @@ msgstr ""
msgid "Request was throttled." msgid "Request was throttled."
msgstr "" msgstr ""
#: fields.py:153 relations.py:132 relations.py:156 validators.py:77 #: fields.py:162 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155 #: validators.py:160
msgid "This field is required." msgid "This field is required."
msgstr "" msgstr ""
#: fields.py:154 #: fields.py:163
msgid "This field may not be null." msgid "This field may not be null."
msgstr "" msgstr ""
#: fields.py:487 fields.py:515 #: fields.py:496 fields.py:524
#, python-brace-format
msgid "\"{input}\" is not a valid boolean." msgid "\"{input}\" is not a valid boolean."
msgstr "" msgstr ""
#: fields.py:550 #: fields.py:559
msgid "This field may not be blank." msgid "This field may not be blank."
msgstr "" msgstr ""
#: fields.py:551 fields.py:1324 #: fields.py:560 fields.py:1342
#, python-brace-format
msgid "Ensure this field has no more than {max_length} characters." msgid "Ensure this field has no more than {max_length} characters."
msgstr "" msgstr ""
#: fields.py:552 #: fields.py:561
#, python-brace-format
msgid "Ensure this field has at least {min_length} characters." msgid "Ensure this field has at least {min_length} characters."
msgstr "" msgstr ""
#: fields.py:587 #: fields.py:598
msgid "Enter a valid email address." msgid "Enter a valid email address."
msgstr "" msgstr ""
#: fields.py:604 #: fields.py:609
msgid "This value does not match the required pattern." msgid "This value does not match the required pattern."
msgstr "" msgstr ""
#: fields.py:615 #: fields.py:620
msgid "" msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or " "Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens." "hyphens."
msgstr "" msgstr ""
#: fields.py:627 #: fields.py:632
msgid "Enter a valid URL." msgid "Enter a valid URL."
msgstr "" msgstr ""
#: fields.py:638 #: fields.py:643
#, python-brace-format
msgid "\"{value}\" is not a valid UUID." msgid "\"{value}\" is not a valid UUID."
msgstr "" msgstr ""
#: fields.py:657 #: fields.py:662
msgid "A valid integer is required." msgid "A valid integer is required."
msgstr "" msgstr ""
#: fields.py:658 fields.py:692 fields.py:725 #: fields.py:663 fields.py:698 fields.py:731
#, python-brace-format
msgid "Ensure this value is less than or equal to {max_value}." msgid "Ensure this value is less than or equal to {max_value}."
msgstr "" msgstr ""
#: fields.py:659 fields.py:693 fields.py:726 #: fields.py:664 fields.py:699 fields.py:732
#, python-brace-format
msgid "Ensure this value is greater than or equal to {min_value}." msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "" msgstr ""
#: fields.py:660 fields.py:694 fields.py:730 #: fields.py:665 fields.py:700 fields.py:736
msgid "String value too large." msgid "String value too large."
msgstr "" msgstr ""
#: fields.py:691 fields.py:724 #: fields.py:697 fields.py:730
msgid "A valid number is required." msgid "A valid number is required."
msgstr "" msgstr ""
#: fields.py:727 #: fields.py:733
#, python-brace-format
msgid "Ensure that there are no more than {max_digits} digits in total." msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "" msgstr ""
#: fields.py:728 #: fields.py:734
#, python-brace-format
msgid "Ensure that there are no more than {max_decimal_places} decimal places." msgid "Ensure that there are no more than {max_decimal_places} decimal places."
msgstr "" msgstr ""
#: fields.py:729 #: fields.py:735
#, python-brace-format
msgid "" msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the " "Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point." "decimal point."
msgstr "" msgstr ""
#: fields.py:813 #: fields.py:825
#, python-brace-format
msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr "" msgstr ""
#: fields.py:814 #: fields.py:826
msgid "Expected a datetime but got a date." msgid "Expected a datetime but got a date."
msgstr "" msgstr ""
#: fields.py:878 #: fields.py:890
#, python-brace-format
msgid "Date has wrong format. Use one of these formats instead: {format}." msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr "" msgstr ""
#: fields.py:879 #: fields.py:891
msgid "Expected a date but got a datetime." msgid "Expected a date but got a datetime."
msgstr "" msgstr ""
#: fields.py:936 #: fields.py:954
#, python-brace-format
msgid "Time has wrong format. Use one of these formats instead: {format}." msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "" msgstr ""
#: fields.py:992 fields.py:1036 #: fields.py:1010 fields.py:1054
#, python-brace-format
msgid "\"{input}\" is not a valid choice." msgid "\"{input}\" is not a valid choice."
msgstr "" msgstr ""
#: fields.py:1037 fields.py:1151 serializers.py:482 #: fields.py:1055 fields.py:1169 serializers.py:483
#, python-brace-format
msgid "Expected a list of items but got type \"{input_type}\"." msgid "Expected a list of items but got type \"{input_type}\"."
msgstr "" msgstr ""
#: fields.py:1067 #: fields.py:1085
msgid "No file was submitted." msgid "No file was submitted."
msgstr "" msgstr ""
#: fields.py:1068 #: fields.py:1086
msgid "The submitted data was not a file. Check the encoding type on the form." msgid "The submitted data was not a file. Check the encoding type on the form."
msgstr "" msgstr ""
#: fields.py:1069 #: fields.py:1087
msgid "No filename could be determined." msgid "No filename could be determined."
msgstr "" msgstr ""
#: fields.py:1070 #: fields.py:1088
msgid "The submitted file is empty." msgid "The submitted file is empty."
msgstr "" msgstr ""
#: fields.py:1071 #: fields.py:1089
#, python-brace-format
msgid "" msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})." "Ensure this filename has at most {max_length} characters (it has {length})."
msgstr "" msgstr ""
#: fields.py:1113 #: fields.py:1131
msgid "" msgid ""
"Upload a valid image. The file you uploaded was either not an image or a " "Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image." "corrupted image."
msgstr "" msgstr ""
#: fields.py:1188 #: fields.py:1206
#, python-brace-format
msgid "Expected a dictionary of items but got type \"{input_type}\"." msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr "" msgstr ""
#: pagination.py:221 #: pagination.py:231
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}." msgid "Invalid page \"{page_number}\": {message}."
msgstr "" msgstr ""
#: pagination.py:442 #: pagination.py:492
msgid "Invalid cursor" msgid "Invalid cursor"
msgstr "" msgstr ""
#: relations.py:133 #: relations.py:133
#, python-brace-format
msgid "Invalid pk \"{pk_value}\" - object does not exist." msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr "" msgstr ""
#: relations.py:134 #: relations.py:134
#, python-brace-format
msgid "Incorrect type. Expected pk value, received {data_type}." msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr "" msgstr ""
...@@ -258,38 +291,57 @@ msgid "Invalid hyperlink - Object does not exist." ...@@ -258,38 +291,57 @@ msgid "Invalid hyperlink - Object does not exist."
msgstr "" msgstr ""
#: relations.py:160 #: relations.py:160
#, python-brace-format
msgid "Incorrect type. Expected URL string, received {data_type}." msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr "" msgstr ""
#: relations.py:295 #: relations.py:302
#, python-brace-format
msgid "Object with {slug_name}={value} does not exist." msgid "Object with {slug_name}={value} does not exist."
msgstr "" msgstr ""
#: relations.py:296 #: relations.py:303
msgid "Invalid value." msgid "Invalid value."
msgstr "" msgstr ""
#: serializers.py:299 #: serializers.py:300
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}." msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr "" msgstr ""
#: templates/rest_framework/horizontal/radio.html:2
#: templates/rest_framework/inline/radio.html:2
#: templates/rest_framework/vertical/radio.html:2
msgid "None"
msgstr ""
#: templates/rest_framework/horizontal/select_multiple.html:2
#: templates/rest_framework/inline/select_multiple.html:2
#: templates/rest_framework/vertical/select_multiple.html:2
msgid "No items to select."
msgstr ""
#: validators.py:22 #: validators.py:22
msgid "This field must be unique." msgid "This field must be unique."
msgstr "" msgstr ""
#: validators.py:76 #: validators.py:76
#, python-brace-format
msgid "The fields {field_names} must make a unique set." msgid "The fields {field_names} must make a unique set."
msgstr "" msgstr ""
#: validators.py:219 #: validators.py:224
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" date." msgid "This field must be unique for the \"{date_field}\" date."
msgstr "" msgstr ""
#: validators.py:234 #: validators.py:239
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" month." msgid "This field must be unique for the \"{date_field}\" month."
msgstr "" msgstr ""
#: validators.py:247 #: validators.py:252
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" year." msgid "This field must be unique for the \"{date_field}\" year."
msgstr "" msgstr ""
...@@ -301,26 +353,14 @@ msgstr "" ...@@ -301,26 +353,14 @@ msgstr ""
msgid "Invalid version in URL path." msgid "Invalid version in URL path."
msgstr "" msgstr ""
#: versioning.py:138 #: versioning.py:141
msgid "Invalid version in hostname." msgid "Invalid version in hostname."
msgstr "" msgstr ""
#: versioning.py:160 #: versioning.py:163
msgid "Invalid version in query parameter." msgid "Invalid version in query parameter."
msgstr "" msgstr ""
#: views.py:81 #: views.py:82
msgid "Permission denied." msgid "Permission denied."
msgstr "" 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 ""
...@@ -7,9 +7,9 @@ msgid "" ...@@ -7,9 +7,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Django REST framework\n" "Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n" "POT-Creation-Date: 2015-06-01 23:48+0100\n"
"PO-Revision-Date: 2015-01-30 16:27+0000\n" "PO-Revision-Date: 2015-06-01 22:51+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n" "Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"Language-Team: Indonesian (http://www.transifex.com/projects/p/django-rest-framework/language/id/)\n" "Language-Team: Indonesian (http://www.transifex.com/projects/p/django-rest-framework/language/id/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
...@@ -17,36 +17,48 @@ msgstr "" ...@@ -17,36 +17,48 @@ msgstr ""
"Language: id\n" "Language: id\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
#: authentication.py:69 #: authentication.py:70
msgid "Invalid basic header. No credentials provided." msgid "Invalid basic header. No credentials provided."
msgstr "" msgstr ""
#: authentication.py:72 #: authentication.py:73
msgid "Invalid basic header. Credentials string should not contain spaces." msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr "" msgstr ""
#: authentication.py:78 #: authentication.py:79
msgid "Invalid basic header. Credentials not correctly base64 encoded." msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr "" msgstr ""
#: authentication.py:90 #: authentication.py:97
msgid "Invalid username/password." msgid "Invalid username/password."
msgstr "" msgstr ""
#: authentication.py:156 #: authentication.py:100 authentication.py:182
msgid "User inactive or deleted."
msgstr ""
#: authentication.py:167
msgid "Invalid token header. No credentials provided." msgid "Invalid token header. No credentials provided."
msgstr "" msgstr ""
#: authentication.py:159 #: authentication.py:170
msgid "Invalid token header. Token string should not contain spaces." msgid "Invalid token header. Token string should not contain spaces."
msgstr "" msgstr ""
#: authentication.py:168 #: authentication.py:179
msgid "Invalid token." msgid "Invalid token."
msgstr "" msgstr ""
#: authentication.py:171 #: authtoken/serializers.py:20
msgid "User inactive or deleted." 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 "" msgstr ""
#: exceptions.py:38 #: exceptions.py:38
...@@ -69,11 +81,12 @@ msgstr "" ...@@ -69,11 +81,12 @@ msgstr ""
msgid "You do not have permission to perform this action." msgid "You do not have permission to perform this action."
msgstr "" msgstr ""
#: exceptions.py:93 #: exceptions.py:93 views.py:77
msgid "Not found." msgid "Not found."
msgstr "" msgstr ""
#: exceptions.py:98 #: exceptions.py:98
#, python-brace-format
msgid "Method \"{method}\" not allowed." msgid "Method \"{method}\" not allowed."
msgstr "" msgstr ""
...@@ -82,6 +95,7 @@ msgid "Could not satisfy the request Accept header." ...@@ -82,6 +95,7 @@ msgid "Could not satisfy the request Accept header."
msgstr "" msgstr ""
#: exceptions.py:121 #: exceptions.py:121
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request." msgid "Unsupported media type \"{media_type}\" in request."
msgstr "" msgstr ""
...@@ -89,161 +103,180 @@ msgstr "" ...@@ -89,161 +103,180 @@ msgstr ""
msgid "Request was throttled." msgid "Request was throttled."
msgstr "" msgstr ""
#: fields.py:153 relations.py:132 relations.py:156 validators.py:77 #: fields.py:162 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155 #: validators.py:160
msgid "This field is required." msgid "This field is required."
msgstr "" msgstr ""
#: fields.py:154 #: fields.py:163
msgid "This field may not be null." msgid "This field may not be null."
msgstr "" msgstr ""
#: fields.py:487 fields.py:515 #: fields.py:496 fields.py:524
#, python-brace-format
msgid "\"{input}\" is not a valid boolean." msgid "\"{input}\" is not a valid boolean."
msgstr "" msgstr ""
#: fields.py:550 #: fields.py:559
msgid "This field may not be blank." msgid "This field may not be blank."
msgstr "" msgstr ""
#: fields.py:551 fields.py:1324 #: fields.py:560 fields.py:1342
#, python-brace-format
msgid "Ensure this field has no more than {max_length} characters." msgid "Ensure this field has no more than {max_length} characters."
msgstr "" msgstr ""
#: fields.py:552 #: fields.py:561
#, python-brace-format
msgid "Ensure this field has at least {min_length} characters." msgid "Ensure this field has at least {min_length} characters."
msgstr "" msgstr ""
#: fields.py:587 #: fields.py:598
msgid "Enter a valid email address." msgid "Enter a valid email address."
msgstr "" msgstr ""
#: fields.py:604 #: fields.py:609
msgid "This value does not match the required pattern." msgid "This value does not match the required pattern."
msgstr "" msgstr ""
#: fields.py:615 #: fields.py:620
msgid "" msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or " "Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens." "hyphens."
msgstr "" msgstr ""
#: fields.py:627 #: fields.py:632
msgid "Enter a valid URL." msgid "Enter a valid URL."
msgstr "" msgstr ""
#: fields.py:638 #: fields.py:643
#, python-brace-format
msgid "\"{value}\" is not a valid UUID." msgid "\"{value}\" is not a valid UUID."
msgstr "" msgstr ""
#: fields.py:657 #: fields.py:662
msgid "A valid integer is required." msgid "A valid integer is required."
msgstr "" msgstr ""
#: fields.py:658 fields.py:692 fields.py:725 #: fields.py:663 fields.py:698 fields.py:731
#, python-brace-format
msgid "Ensure this value is less than or equal to {max_value}." msgid "Ensure this value is less than or equal to {max_value}."
msgstr "" msgstr ""
#: fields.py:659 fields.py:693 fields.py:726 #: fields.py:664 fields.py:699 fields.py:732
#, python-brace-format
msgid "Ensure this value is greater than or equal to {min_value}." msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "" msgstr ""
#: fields.py:660 fields.py:694 fields.py:730 #: fields.py:665 fields.py:700 fields.py:736
msgid "String value too large." msgid "String value too large."
msgstr "" msgstr ""
#: fields.py:691 fields.py:724 #: fields.py:697 fields.py:730
msgid "A valid number is required." msgid "A valid number is required."
msgstr "" msgstr ""
#: fields.py:727 #: fields.py:733
#, python-brace-format
msgid "Ensure that there are no more than {max_digits} digits in total." msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "" msgstr ""
#: fields.py:728 #: fields.py:734
#, python-brace-format
msgid "" msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places." "Ensure that there are no more than {max_decimal_places} decimal places."
msgstr "" msgstr ""
#: fields.py:729 #: fields.py:735
#, python-brace-format
msgid "" msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the " "Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point." "decimal point."
msgstr "" msgstr ""
#: fields.py:813 #: fields.py:825
#, python-brace-format
msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr "" msgstr ""
#: fields.py:814 #: fields.py:826
msgid "Expected a datetime but got a date." msgid "Expected a datetime but got a date."
msgstr "" msgstr ""
#: fields.py:878 #: fields.py:890
#, python-brace-format
msgid "Date has wrong format. Use one of these formats instead: {format}." msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr "" msgstr ""
#: fields.py:879 #: fields.py:891
msgid "Expected a date but got a datetime." msgid "Expected a date but got a datetime."
msgstr "" msgstr ""
#: fields.py:936 #: fields.py:954
#, python-brace-format
msgid "Time has wrong format. Use one of these formats instead: {format}." msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "" msgstr ""
#: fields.py:992 fields.py:1036 #: fields.py:1010 fields.py:1054
#, python-brace-format
msgid "\"{input}\" is not a valid choice." msgid "\"{input}\" is not a valid choice."
msgstr "" msgstr ""
#: fields.py:1037 fields.py:1151 serializers.py:482 #: fields.py:1055 fields.py:1169 serializers.py:483
#, python-brace-format
msgid "Expected a list of items but got type \"{input_type}\"." msgid "Expected a list of items but got type \"{input_type}\"."
msgstr "" msgstr ""
#: fields.py:1067 #: fields.py:1085
msgid "No file was submitted." msgid "No file was submitted."
msgstr "" msgstr ""
#: fields.py:1068 #: fields.py:1086
msgid "" msgid ""
"The submitted data was not a file. Check the encoding type on the form." "The submitted data was not a file. Check the encoding type on the form."
msgstr "" msgstr ""
#: fields.py:1069 #: fields.py:1087
msgid "No filename could be determined." msgid "No filename could be determined."
msgstr "" msgstr ""
#: fields.py:1070 #: fields.py:1088
msgid "The submitted file is empty." msgid "The submitted file is empty."
msgstr "" msgstr ""
#: fields.py:1071 #: fields.py:1089
#, python-brace-format
msgid "" msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})." "Ensure this filename has at most {max_length} characters (it has {length})."
msgstr "" msgstr ""
#: fields.py:1113 #: fields.py:1131
msgid "" msgid ""
"Upload a valid image. The file you uploaded was either not an image or a " "Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image." "corrupted image."
msgstr "" msgstr ""
#: fields.py:1188 #: fields.py:1206
#, python-brace-format
msgid "Expected a dictionary of items but got type \"{input_type}\"." msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr "" msgstr ""
#: pagination.py:221 #: pagination.py:231
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}." msgid "Invalid page \"{page_number}\": {message}."
msgstr "" msgstr ""
#: pagination.py:442 #: pagination.py:492
msgid "Invalid cursor" msgid "Invalid cursor"
msgstr "" msgstr ""
#: relations.py:133 #: relations.py:133
#, python-brace-format
msgid "Invalid pk \"{pk_value}\" - object does not exist." msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr "" msgstr ""
#: relations.py:134 #: relations.py:134
#, python-brace-format
msgid "Incorrect type. Expected pk value, received {data_type}." msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr "" msgstr ""
...@@ -260,38 +293,57 @@ msgid "Invalid hyperlink - Object does not exist." ...@@ -260,38 +293,57 @@ msgid "Invalid hyperlink - Object does not exist."
msgstr "" msgstr ""
#: relations.py:160 #: relations.py:160
#, python-brace-format
msgid "Incorrect type. Expected URL string, received {data_type}." msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr "" msgstr ""
#: relations.py:295 #: relations.py:302
#, python-brace-format
msgid "Object with {slug_name}={value} does not exist." msgid "Object with {slug_name}={value} does not exist."
msgstr "" msgstr ""
#: relations.py:296 #: relations.py:303
msgid "Invalid value." msgid "Invalid value."
msgstr "" msgstr ""
#: serializers.py:299 #: serializers.py:300
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}." msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr "" msgstr ""
#: templates/rest_framework/horizontal/radio.html:2
#: templates/rest_framework/inline/radio.html:2
#: templates/rest_framework/vertical/radio.html:2
msgid "None"
msgstr ""
#: templates/rest_framework/horizontal/select_multiple.html:2
#: templates/rest_framework/inline/select_multiple.html:2
#: templates/rest_framework/vertical/select_multiple.html:2
msgid "No items to select."
msgstr ""
#: validators.py:22 #: validators.py:22
msgid "This field must be unique." msgid "This field must be unique."
msgstr "" msgstr ""
#: validators.py:76 #: validators.py:76
#, python-brace-format
msgid "The fields {field_names} must make a unique set." msgid "The fields {field_names} must make a unique set."
msgstr "" msgstr ""
#: validators.py:219 #: validators.py:224
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" date." msgid "This field must be unique for the \"{date_field}\" date."
msgstr "" msgstr ""
#: validators.py:234 #: validators.py:239
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" month." msgid "This field must be unique for the \"{date_field}\" month."
msgstr "" msgstr ""
#: validators.py:247 #: validators.py:252
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" year." msgid "This field must be unique for the \"{date_field}\" year."
msgstr "" msgstr ""
...@@ -303,22 +355,14 @@ msgstr "" ...@@ -303,22 +355,14 @@ msgstr ""
msgid "Invalid version in URL path." msgid "Invalid version in URL path."
msgstr "" msgstr ""
#: versioning.py:138 #: versioning.py:141
msgid "Invalid version in hostname." msgid "Invalid version in hostname."
msgstr "" msgstr ""
#: versioning.py:160 #: versioning.py:163
msgid "Invalid version in query parameter." msgid "Invalid version in query parameter."
msgstr "" msgstr ""
#: authtoken/serializers.py:20 #: views.py:82
msgid "User account is disabled." msgid "Permission denied."
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 "" msgstr ""
...@@ -7,9 +7,9 @@ msgid "" ...@@ -7,9 +7,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Django REST framework\n" "Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n" "POT-Creation-Date: 2015-06-01 23:48+0100\n"
"PO-Revision-Date: 2015-01-30 16:27+0000\n" "PO-Revision-Date: 2015-06-01 22:52+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n" "Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"Language-Team: Dutch (http://www.transifex.com/projects/p/django-rest-framework/language/nl/)\n" "Language-Team: Dutch (http://www.transifex.com/projects/p/django-rest-framework/language/nl/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
...@@ -17,36 +17,48 @@ msgstr "" ...@@ -17,36 +17,48 @@ msgstr ""
"Language: nl\n" "Language: nl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentication.py:69 #: authentication.py:70
msgid "Invalid basic header. No credentials provided." msgid "Invalid basic header. No credentials provided."
msgstr "" msgstr ""
#: authentication.py:72 #: authentication.py:73
msgid "Invalid basic header. Credentials string should not contain spaces." msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr "" msgstr ""
#: authentication.py:78 #: authentication.py:79
msgid "Invalid basic header. Credentials not correctly base64 encoded." msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr "" msgstr ""
#: authentication.py:90 #: authentication.py:97
msgid "Invalid username/password." msgid "Invalid username/password."
msgstr "" msgstr ""
#: authentication.py:156 #: authentication.py:100 authentication.py:182
msgid "User inactive or deleted."
msgstr ""
#: authentication.py:167
msgid "Invalid token header. No credentials provided." msgid "Invalid token header. No credentials provided."
msgstr "" msgstr ""
#: authentication.py:159 #: authentication.py:170
msgid "Invalid token header. Token string should not contain spaces." msgid "Invalid token header. Token string should not contain spaces."
msgstr "" msgstr ""
#: authentication.py:168 #: authentication.py:179
msgid "Invalid token." msgid "Invalid token."
msgstr "" msgstr ""
#: authentication.py:171 #: authtoken/serializers.py:20
msgid "User inactive or deleted." 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 "" msgstr ""
#: exceptions.py:38 #: exceptions.py:38
...@@ -69,11 +81,12 @@ msgstr "" ...@@ -69,11 +81,12 @@ msgstr ""
msgid "You do not have permission to perform this action." msgid "You do not have permission to perform this action."
msgstr "" msgstr ""
#: exceptions.py:93 #: exceptions.py:93 views.py:77
msgid "Not found." msgid "Not found."
msgstr "" msgstr ""
#: exceptions.py:98 #: exceptions.py:98
#, python-brace-format
msgid "Method \"{method}\" not allowed." msgid "Method \"{method}\" not allowed."
msgstr "" msgstr ""
...@@ -82,6 +95,7 @@ msgid "Could not satisfy the request Accept header." ...@@ -82,6 +95,7 @@ msgid "Could not satisfy the request Accept header."
msgstr "" msgstr ""
#: exceptions.py:121 #: exceptions.py:121
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request." msgid "Unsupported media type \"{media_type}\" in request."
msgstr "" msgstr ""
...@@ -89,161 +103,180 @@ msgstr "" ...@@ -89,161 +103,180 @@ msgstr ""
msgid "Request was throttled." msgid "Request was throttled."
msgstr "" msgstr ""
#: fields.py:153 relations.py:132 relations.py:156 validators.py:77 #: fields.py:162 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155 #: validators.py:160
msgid "This field is required." msgid "This field is required."
msgstr "" msgstr ""
#: fields.py:154 #: fields.py:163
msgid "This field may not be null." msgid "This field may not be null."
msgstr "" msgstr ""
#: fields.py:487 fields.py:515 #: fields.py:496 fields.py:524
#, python-brace-format
msgid "\"{input}\" is not a valid boolean." msgid "\"{input}\" is not a valid boolean."
msgstr "" msgstr ""
#: fields.py:550 #: fields.py:559
msgid "This field may not be blank." msgid "This field may not be blank."
msgstr "" msgstr ""
#: fields.py:551 fields.py:1324 #: fields.py:560 fields.py:1342
#, python-brace-format
msgid "Ensure this field has no more than {max_length} characters." msgid "Ensure this field has no more than {max_length} characters."
msgstr "" msgstr ""
#: fields.py:552 #: fields.py:561
#, python-brace-format
msgid "Ensure this field has at least {min_length} characters." msgid "Ensure this field has at least {min_length} characters."
msgstr "" msgstr ""
#: fields.py:587 #: fields.py:598
msgid "Enter a valid email address." msgid "Enter a valid email address."
msgstr "" msgstr ""
#: fields.py:604 #: fields.py:609
msgid "This value does not match the required pattern." msgid "This value does not match the required pattern."
msgstr "" msgstr ""
#: fields.py:615 #: fields.py:620
msgid "" msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or " "Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens." "hyphens."
msgstr "" msgstr ""
#: fields.py:627 #: fields.py:632
msgid "Enter a valid URL." msgid "Enter a valid URL."
msgstr "" msgstr ""
#: fields.py:638 #: fields.py:643
#, python-brace-format
msgid "\"{value}\" is not a valid UUID." msgid "\"{value}\" is not a valid UUID."
msgstr "" msgstr ""
#: fields.py:657 #: fields.py:662
msgid "A valid integer is required." msgid "A valid integer is required."
msgstr "" msgstr ""
#: fields.py:658 fields.py:692 fields.py:725 #: fields.py:663 fields.py:698 fields.py:731
#, python-brace-format
msgid "Ensure this value is less than or equal to {max_value}." msgid "Ensure this value is less than or equal to {max_value}."
msgstr "" msgstr ""
#: fields.py:659 fields.py:693 fields.py:726 #: fields.py:664 fields.py:699 fields.py:732
#, python-brace-format
msgid "Ensure this value is greater than or equal to {min_value}." msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "" msgstr ""
#: fields.py:660 fields.py:694 fields.py:730 #: fields.py:665 fields.py:700 fields.py:736
msgid "String value too large." msgid "String value too large."
msgstr "" msgstr ""
#: fields.py:691 fields.py:724 #: fields.py:697 fields.py:730
msgid "A valid number is required." msgid "A valid number is required."
msgstr "" msgstr ""
#: fields.py:727 #: fields.py:733
#, python-brace-format
msgid "Ensure that there are no more than {max_digits} digits in total." msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "" msgstr ""
#: fields.py:728 #: fields.py:734
#, python-brace-format
msgid "" msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places." "Ensure that there are no more than {max_decimal_places} decimal places."
msgstr "" msgstr ""
#: fields.py:729 #: fields.py:735
#, python-brace-format
msgid "" msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the " "Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point." "decimal point."
msgstr "" msgstr ""
#: fields.py:813 #: fields.py:825
#, python-brace-format
msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr "" msgstr ""
#: fields.py:814 #: fields.py:826
msgid "Expected a datetime but got a date." msgid "Expected a datetime but got a date."
msgstr "" msgstr ""
#: fields.py:878 #: fields.py:890
#, python-brace-format
msgid "Date has wrong format. Use one of these formats instead: {format}." msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr "" msgstr ""
#: fields.py:879 #: fields.py:891
msgid "Expected a date but got a datetime." msgid "Expected a date but got a datetime."
msgstr "" msgstr ""
#: fields.py:936 #: fields.py:954
#, python-brace-format
msgid "Time has wrong format. Use one of these formats instead: {format}." msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "" msgstr ""
#: fields.py:992 fields.py:1036 #: fields.py:1010 fields.py:1054
#, python-brace-format
msgid "\"{input}\" is not a valid choice." msgid "\"{input}\" is not a valid choice."
msgstr "" msgstr ""
#: fields.py:1037 fields.py:1151 serializers.py:482 #: fields.py:1055 fields.py:1169 serializers.py:483
#, python-brace-format
msgid "Expected a list of items but got type \"{input_type}\"." msgid "Expected a list of items but got type \"{input_type}\"."
msgstr "" msgstr ""
#: fields.py:1067 #: fields.py:1085
msgid "No file was submitted." msgid "No file was submitted."
msgstr "" msgstr ""
#: fields.py:1068 #: fields.py:1086
msgid "" msgid ""
"The submitted data was not a file. Check the encoding type on the form." "The submitted data was not a file. Check the encoding type on the form."
msgstr "" msgstr ""
#: fields.py:1069 #: fields.py:1087
msgid "No filename could be determined." msgid "No filename could be determined."
msgstr "" msgstr ""
#: fields.py:1070 #: fields.py:1088
msgid "The submitted file is empty." msgid "The submitted file is empty."
msgstr "" msgstr ""
#: fields.py:1071 #: fields.py:1089
#, python-brace-format
msgid "" msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})." "Ensure this filename has at most {max_length} characters (it has {length})."
msgstr "" msgstr ""
#: fields.py:1113 #: fields.py:1131
msgid "" msgid ""
"Upload a valid image. The file you uploaded was either not an image or a " "Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image." "corrupted image."
msgstr "" msgstr ""
#: fields.py:1188 #: fields.py:1206
#, python-brace-format
msgid "Expected a dictionary of items but got type \"{input_type}\"." msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr "" msgstr ""
#: pagination.py:221 #: pagination.py:231
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}." msgid "Invalid page \"{page_number}\": {message}."
msgstr "" msgstr ""
#: pagination.py:442 #: pagination.py:492
msgid "Invalid cursor" msgid "Invalid cursor"
msgstr "" msgstr ""
#: relations.py:133 #: relations.py:133
#, python-brace-format
msgid "Invalid pk \"{pk_value}\" - object does not exist." msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr "" msgstr ""
#: relations.py:134 #: relations.py:134
#, python-brace-format
msgid "Incorrect type. Expected pk value, received {data_type}." msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr "" msgstr ""
...@@ -260,38 +293,57 @@ msgid "Invalid hyperlink - Object does not exist." ...@@ -260,38 +293,57 @@ msgid "Invalid hyperlink - Object does not exist."
msgstr "" msgstr ""
#: relations.py:160 #: relations.py:160
#, python-brace-format
msgid "Incorrect type. Expected URL string, received {data_type}." msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr "" msgstr ""
#: relations.py:295 #: relations.py:302
#, python-brace-format
msgid "Object with {slug_name}={value} does not exist." msgid "Object with {slug_name}={value} does not exist."
msgstr "" msgstr ""
#: relations.py:296 #: relations.py:303
msgid "Invalid value." msgid "Invalid value."
msgstr "" msgstr ""
#: serializers.py:299 #: serializers.py:300
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}." msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr "" msgstr ""
#: templates/rest_framework/horizontal/radio.html:2
#: templates/rest_framework/inline/radio.html:2
#: templates/rest_framework/vertical/radio.html:2
msgid "None"
msgstr ""
#: templates/rest_framework/horizontal/select_multiple.html:2
#: templates/rest_framework/inline/select_multiple.html:2
#: templates/rest_framework/vertical/select_multiple.html:2
msgid "No items to select."
msgstr ""
#: validators.py:22 #: validators.py:22
msgid "This field must be unique." msgid "This field must be unique."
msgstr "" msgstr ""
#: validators.py:76 #: validators.py:76
#, python-brace-format
msgid "The fields {field_names} must make a unique set." msgid "The fields {field_names} must make a unique set."
msgstr "" msgstr ""
#: validators.py:219 #: validators.py:224
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" date." msgid "This field must be unique for the \"{date_field}\" date."
msgstr "" msgstr ""
#: validators.py:234 #: validators.py:239
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" month." msgid "This field must be unique for the \"{date_field}\" month."
msgstr "" msgstr ""
#: validators.py:247 #: validators.py:252
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" year." msgid "This field must be unique for the \"{date_field}\" year."
msgstr "" msgstr ""
...@@ -303,22 +355,14 @@ msgstr "" ...@@ -303,22 +355,14 @@ msgstr ""
msgid "Invalid version in URL path." msgid "Invalid version in URL path."
msgstr "" msgstr ""
#: versioning.py:138 #: versioning.py:141
msgid "Invalid version in hostname." msgid "Invalid version in hostname."
msgstr "" msgstr ""
#: versioning.py:160 #: versioning.py:163
msgid "Invalid version in query parameter." msgid "Invalid version in query parameter."
msgstr "" msgstr ""
#: authtoken/serializers.py:20 #: views.py:82
msgid "User account is disabled." msgid "Permission denied."
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 "" 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:
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-06-01 23:48+0100\n"
"PO-Revision-Date: 2015-06-01 22:51+0000\n"
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"Language-Team: Portuguese (Portugal) (http://www.transifex.com/projects/p/django-rest-framework/language/pt_PT/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pt_PT\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentication.py:70
msgid "Invalid basic header. No credentials provided."
msgstr ""
#: authentication.py:73
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr ""
#: authentication.py:79
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr ""
#: authentication.py:97
msgid "Invalid username/password."
msgstr ""
#: authentication.py:100 authentication.py:182
msgid "User inactive or deleted."
msgstr ""
#: authentication.py:167
msgid "Invalid token header. No credentials provided."
msgstr ""
#: authentication.py:170
msgid "Invalid token header. Token string should not contain spaces."
msgstr ""
#: authentication.py:179
msgid "Invalid token."
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 ""
#: 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
#, python-brace-format
msgid "Method \"{method}\" not allowed."
msgstr ""
#: exceptions.py:109
msgid "Could not satisfy the request Accept header."
msgstr ""
#: exceptions.py:121
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request."
msgstr ""
#: exceptions.py:134
msgid "Request was throttled."
msgstr ""
#: fields.py:162 relations.py:132 relations.py:156 validators.py:77
#: validators.py:160
msgid "This field is required."
msgstr ""
#: fields.py:163
msgid "This field may not be null."
msgstr ""
#: fields.py:496 fields.py:524
#, python-brace-format
msgid "\"{input}\" is not a valid boolean."
msgstr ""
#: fields.py:559
msgid "This field may not be blank."
msgstr ""
#: fields.py:560 fields.py:1342
#, python-brace-format
msgid "Ensure this field has no more than {max_length} characters."
msgstr ""
#: fields.py:561
#, python-brace-format
msgid "Ensure this field has at least {min_length} characters."
msgstr ""
#: fields.py:598
msgid "Enter a valid email address."
msgstr ""
#: fields.py:609
msgid "This value does not match the required pattern."
msgstr ""
#: fields.py:620
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr ""
#: fields.py:632
msgid "Enter a valid URL."
msgstr ""
#: fields.py:643
#, python-brace-format
msgid "\"{value}\" is not a valid UUID."
msgstr ""
#: fields.py:662
msgid "A valid integer is required."
msgstr ""
#: fields.py:663 fields.py:698 fields.py:731
#, python-brace-format
msgid "Ensure this value is less than or equal to {max_value}."
msgstr ""
#: fields.py:664 fields.py:699 fields.py:732
#, python-brace-format
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr ""
#: fields.py:665 fields.py:700 fields.py:736
msgid "String value too large."
msgstr ""
#: fields.py:697 fields.py:730
msgid "A valid number is required."
msgstr ""
#: fields.py:733
#, python-brace-format
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr ""
#: fields.py:734
#, python-brace-format
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr ""
#: fields.py:735
#, python-brace-format
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
#: fields.py:825
#, python-brace-format
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:826
msgid "Expected a datetime but got a date."
msgstr ""
#: fields.py:890
#, python-brace-format
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:891
msgid "Expected a date but got a datetime."
msgstr ""
#: fields.py:954
#, python-brace-format
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1010 fields.py:1054
#, python-brace-format
msgid "\"{input}\" is not a valid choice."
msgstr ""
#: fields.py:1055 fields.py:1169 serializers.py:483
#, python-brace-format
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1085
msgid "No file was submitted."
msgstr ""
#: fields.py:1086
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr ""
#: fields.py:1087
msgid "No filename could be determined."
msgstr ""
#: fields.py:1088
msgid "The submitted file is empty."
msgstr ""
#: fields.py:1089
#, python-brace-format
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
#: fields.py:1131
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: fields.py:1206
#, python-brace-format
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
#: pagination.py:231
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}."
msgstr ""
#: pagination.py:492
msgid "Invalid cursor"
msgstr ""
#: relations.py:133
#, python-brace-format
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr ""
#: relations.py:134
#, python-brace-format
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
#, python-brace-format
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
#: relations.py:302
#, python-brace-format
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
#: relations.py:303
msgid "Invalid value."
msgstr ""
#: serializers.py:300
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr ""
#: templates/rest_framework/horizontal/radio.html:2
#: templates/rest_framework/inline/radio.html:2
#: templates/rest_framework/vertical/radio.html:2
msgid "None"
msgstr ""
#: templates/rest_framework/horizontal/select_multiple.html:2
#: templates/rest_framework/inline/select_multiple.html:2
#: templates/rest_framework/vertical/select_multiple.html:2
msgid "No items to select."
msgstr ""
#: validators.py:22
msgid "This field must be unique."
msgstr ""
#: validators.py:76
#, python-brace-format
msgid "The fields {field_names} must make a unique set."
msgstr ""
#: validators.py:224
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
#: validators.py:239
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
#: validators.py:252
#, python-brace-format
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:141
msgid "Invalid version in hostname."
msgstr ""
#: versioning.py:163
msgid "Invalid version in query parameter."
msgstr ""
#: views.py:82
msgid "Permission denied."
msgstr ""
...@@ -7,9 +7,9 @@ msgid "" ...@@ -7,9 +7,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Django REST framework\n" "Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n" "POT-Creation-Date: 2015-06-01 23:48+0100\n"
"PO-Revision-Date: 2015-01-30 16:27+0000\n" "PO-Revision-Date: 2015-06-01 22:51+0000\n"
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n" "Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"Language-Team: Ukrainian (http://www.transifex.com/projects/p/django-rest-framework/language/uk/)\n" "Language-Team: Ukrainian (http://www.transifex.com/projects/p/django-rest-framework/language/uk/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
...@@ -17,36 +17,48 @@ msgstr "" ...@@ -17,36 +17,48 @@ msgstr ""
"Language: uk\n" "Language: uk\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: authentication.py:69 #: authentication.py:70
msgid "Invalid basic header. No credentials provided." msgid "Invalid basic header. No credentials provided."
msgstr "" msgstr ""
#: authentication.py:72 #: authentication.py:73
msgid "Invalid basic header. Credentials string should not contain spaces." msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr "" msgstr ""
#: authentication.py:78 #: authentication.py:79
msgid "Invalid basic header. Credentials not correctly base64 encoded." msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr "" msgstr ""
#: authentication.py:90 #: authentication.py:97
msgid "Invalid username/password." msgid "Invalid username/password."
msgstr "" msgstr ""
#: authentication.py:156 #: authentication.py:100 authentication.py:182
msgid "User inactive or deleted."
msgstr ""
#: authentication.py:167
msgid "Invalid token header. No credentials provided." msgid "Invalid token header. No credentials provided."
msgstr "" msgstr ""
#: authentication.py:159 #: authentication.py:170
msgid "Invalid token header. Token string should not contain spaces." msgid "Invalid token header. Token string should not contain spaces."
msgstr "" msgstr ""
#: authentication.py:168 #: authentication.py:179
msgid "Invalid token." msgid "Invalid token."
msgstr "" msgstr ""
#: authentication.py:171 #: authtoken/serializers.py:20
msgid "User inactive or deleted." 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 "" msgstr ""
#: exceptions.py:38 #: exceptions.py:38
...@@ -69,11 +81,12 @@ msgstr "" ...@@ -69,11 +81,12 @@ msgstr ""
msgid "You do not have permission to perform this action." msgid "You do not have permission to perform this action."
msgstr "" msgstr ""
#: exceptions.py:93 #: exceptions.py:93 views.py:77
msgid "Not found." msgid "Not found."
msgstr "" msgstr ""
#: exceptions.py:98 #: exceptions.py:98
#, python-brace-format
msgid "Method \"{method}\" not allowed." msgid "Method \"{method}\" not allowed."
msgstr "" msgstr ""
...@@ -82,6 +95,7 @@ msgid "Could not satisfy the request Accept header." ...@@ -82,6 +95,7 @@ msgid "Could not satisfy the request Accept header."
msgstr "" msgstr ""
#: exceptions.py:121 #: exceptions.py:121
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request." msgid "Unsupported media type \"{media_type}\" in request."
msgstr "" msgstr ""
...@@ -89,161 +103,180 @@ msgstr "" ...@@ -89,161 +103,180 @@ msgstr ""
msgid "Request was throttled." msgid "Request was throttled."
msgstr "" msgstr ""
#: fields.py:153 relations.py:132 relations.py:156 validators.py:77 #: fields.py:162 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155 #: validators.py:160
msgid "This field is required." msgid "This field is required."
msgstr "" msgstr ""
#: fields.py:154 #: fields.py:163
msgid "This field may not be null." msgid "This field may not be null."
msgstr "" msgstr ""
#: fields.py:487 fields.py:515 #: fields.py:496 fields.py:524
#, python-brace-format
msgid "\"{input}\" is not a valid boolean." msgid "\"{input}\" is not a valid boolean."
msgstr "" msgstr ""
#: fields.py:550 #: fields.py:559
msgid "This field may not be blank." msgid "This field may not be blank."
msgstr "" msgstr ""
#: fields.py:551 fields.py:1324 #: fields.py:560 fields.py:1342
#, python-brace-format
msgid "Ensure this field has no more than {max_length} characters." msgid "Ensure this field has no more than {max_length} characters."
msgstr "" msgstr ""
#: fields.py:552 #: fields.py:561
#, python-brace-format
msgid "Ensure this field has at least {min_length} characters." msgid "Ensure this field has at least {min_length} characters."
msgstr "" msgstr ""
#: fields.py:587 #: fields.py:598
msgid "Enter a valid email address." msgid "Enter a valid email address."
msgstr "" msgstr ""
#: fields.py:604 #: fields.py:609
msgid "This value does not match the required pattern." msgid "This value does not match the required pattern."
msgstr "" msgstr ""
#: fields.py:615 #: fields.py:620
msgid "" msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or " "Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens." "hyphens."
msgstr "" msgstr ""
#: fields.py:627 #: fields.py:632
msgid "Enter a valid URL." msgid "Enter a valid URL."
msgstr "" msgstr ""
#: fields.py:638 #: fields.py:643
#, python-brace-format
msgid "\"{value}\" is not a valid UUID." msgid "\"{value}\" is not a valid UUID."
msgstr "" msgstr ""
#: fields.py:657 #: fields.py:662
msgid "A valid integer is required." msgid "A valid integer is required."
msgstr "" msgstr ""
#: fields.py:658 fields.py:692 fields.py:725 #: fields.py:663 fields.py:698 fields.py:731
#, python-brace-format
msgid "Ensure this value is less than or equal to {max_value}." msgid "Ensure this value is less than or equal to {max_value}."
msgstr "" msgstr ""
#: fields.py:659 fields.py:693 fields.py:726 #: fields.py:664 fields.py:699 fields.py:732
#, python-brace-format
msgid "Ensure this value is greater than or equal to {min_value}." msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "" msgstr ""
#: fields.py:660 fields.py:694 fields.py:730 #: fields.py:665 fields.py:700 fields.py:736
msgid "String value too large." msgid "String value too large."
msgstr "" msgstr ""
#: fields.py:691 fields.py:724 #: fields.py:697 fields.py:730
msgid "A valid number is required." msgid "A valid number is required."
msgstr "" msgstr ""
#: fields.py:727 #: fields.py:733
#, python-brace-format
msgid "Ensure that there are no more than {max_digits} digits in total." msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "" msgstr ""
#: fields.py:728 #: fields.py:734
#, python-brace-format
msgid "" msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places." "Ensure that there are no more than {max_decimal_places} decimal places."
msgstr "" msgstr ""
#: fields.py:729 #: fields.py:735
#, python-brace-format
msgid "" msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the " "Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point." "decimal point."
msgstr "" msgstr ""
#: fields.py:813 #: fields.py:825
#, python-brace-format
msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr "" msgstr ""
#: fields.py:814 #: fields.py:826
msgid "Expected a datetime but got a date." msgid "Expected a datetime but got a date."
msgstr "" msgstr ""
#: fields.py:878 #: fields.py:890
#, python-brace-format
msgid "Date has wrong format. Use one of these formats instead: {format}." msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr "" msgstr ""
#: fields.py:879 #: fields.py:891
msgid "Expected a date but got a datetime." msgid "Expected a date but got a datetime."
msgstr "" msgstr ""
#: fields.py:936 #: fields.py:954
#, python-brace-format
msgid "Time has wrong format. Use one of these formats instead: {format}." msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "" msgstr ""
#: fields.py:992 fields.py:1036 #: fields.py:1010 fields.py:1054
#, python-brace-format
msgid "\"{input}\" is not a valid choice." msgid "\"{input}\" is not a valid choice."
msgstr "" msgstr ""
#: fields.py:1037 fields.py:1151 serializers.py:482 #: fields.py:1055 fields.py:1169 serializers.py:483
#, python-brace-format
msgid "Expected a list of items but got type \"{input_type}\"." msgid "Expected a list of items but got type \"{input_type}\"."
msgstr "" msgstr ""
#: fields.py:1067 #: fields.py:1085
msgid "No file was submitted." msgid "No file was submitted."
msgstr "" msgstr ""
#: fields.py:1068 #: fields.py:1086
msgid "" msgid ""
"The submitted data was not a file. Check the encoding type on the form." "The submitted data was not a file. Check the encoding type on the form."
msgstr "" msgstr ""
#: fields.py:1069 #: fields.py:1087
msgid "No filename could be determined." msgid "No filename could be determined."
msgstr "" msgstr ""
#: fields.py:1070 #: fields.py:1088
msgid "The submitted file is empty." msgid "The submitted file is empty."
msgstr "" msgstr ""
#: fields.py:1071 #: fields.py:1089
#, python-brace-format
msgid "" msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})." "Ensure this filename has at most {max_length} characters (it has {length})."
msgstr "" msgstr ""
#: fields.py:1113 #: fields.py:1131
msgid "" msgid ""
"Upload a valid image. The file you uploaded was either not an image or a " "Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image." "corrupted image."
msgstr "" msgstr ""
#: fields.py:1188 #: fields.py:1206
#, python-brace-format
msgid "Expected a dictionary of items but got type \"{input_type}\"." msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr "" msgstr ""
#: pagination.py:221 #: pagination.py:231
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}." msgid "Invalid page \"{page_number}\": {message}."
msgstr "" msgstr ""
#: pagination.py:442 #: pagination.py:492
msgid "Invalid cursor" msgid "Invalid cursor"
msgstr "" msgstr ""
#: relations.py:133 #: relations.py:133
#, python-brace-format
msgid "Invalid pk \"{pk_value}\" - object does not exist." msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr "" msgstr ""
#: relations.py:134 #: relations.py:134
#, python-brace-format
msgid "Incorrect type. Expected pk value, received {data_type}." msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr "" msgstr ""
...@@ -260,38 +293,57 @@ msgid "Invalid hyperlink - Object does not exist." ...@@ -260,38 +293,57 @@ msgid "Invalid hyperlink - Object does not exist."
msgstr "" msgstr ""
#: relations.py:160 #: relations.py:160
#, python-brace-format
msgid "Incorrect type. Expected URL string, received {data_type}." msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr "" msgstr ""
#: relations.py:295 #: relations.py:302
#, python-brace-format
msgid "Object with {slug_name}={value} does not exist." msgid "Object with {slug_name}={value} does not exist."
msgstr "" msgstr ""
#: relations.py:296 #: relations.py:303
msgid "Invalid value." msgid "Invalid value."
msgstr "" msgstr ""
#: serializers.py:299 #: serializers.py:300
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}." msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr "" msgstr ""
#: templates/rest_framework/horizontal/radio.html:2
#: templates/rest_framework/inline/radio.html:2
#: templates/rest_framework/vertical/radio.html:2
msgid "None"
msgstr ""
#: templates/rest_framework/horizontal/select_multiple.html:2
#: templates/rest_framework/inline/select_multiple.html:2
#: templates/rest_framework/vertical/select_multiple.html:2
msgid "No items to select."
msgstr ""
#: validators.py:22 #: validators.py:22
msgid "This field must be unique." msgid "This field must be unique."
msgstr "" msgstr ""
#: validators.py:76 #: validators.py:76
#, python-brace-format
msgid "The fields {field_names} must make a unique set." msgid "The fields {field_names} must make a unique set."
msgstr "" msgstr ""
#: validators.py:219 #: validators.py:224
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" date." msgid "This field must be unique for the \"{date_field}\" date."
msgstr "" msgstr ""
#: validators.py:234 #: validators.py:239
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" month." msgid "This field must be unique for the \"{date_field}\" month."
msgstr "" msgstr ""
#: validators.py:247 #: validators.py:252
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" year." msgid "This field must be unique for the \"{date_field}\" year."
msgstr "" msgstr ""
...@@ -303,22 +355,14 @@ msgstr "" ...@@ -303,22 +355,14 @@ msgstr ""
msgid "Invalid version in URL path." msgid "Invalid version in URL path."
msgstr "" msgstr ""
#: versioning.py:138 #: versioning.py:141
msgid "Invalid version in hostname." msgid "Invalid version in hostname."
msgstr "" msgstr ""
#: versioning.py:160 #: versioning.py:163
msgid "Invalid version in query parameter." msgid "Invalid version in query parameter."
msgstr "" msgstr ""
#: authtoken/serializers.py:20 #: views.py:82
msgid "User account is disabled." msgid "Permission denied."
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 "" 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:
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-06-01 23:48+0100\n"
"PO-Revision-Date: 2015-06-01 22:51+0000\n"
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"Language-Team: Vietnamese (http://www.transifex.com/projects/p/django-rest-framework/language/vi/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: vi\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: authentication.py:70
msgid "Invalid basic header. No credentials provided."
msgstr ""
#: authentication.py:73
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr ""
#: authentication.py:79
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr ""
#: authentication.py:97
msgid "Invalid username/password."
msgstr ""
#: authentication.py:100 authentication.py:182
msgid "User inactive or deleted."
msgstr ""
#: authentication.py:167
msgid "Invalid token header. No credentials provided."
msgstr ""
#: authentication.py:170
msgid "Invalid token header. Token string should not contain spaces."
msgstr ""
#: authentication.py:179
msgid "Invalid token."
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 ""
#: 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
#, python-brace-format
msgid "Method \"{method}\" not allowed."
msgstr ""
#: exceptions.py:109
msgid "Could not satisfy the request Accept header."
msgstr ""
#: exceptions.py:121
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request."
msgstr ""
#: exceptions.py:134
msgid "Request was throttled."
msgstr ""
#: fields.py:162 relations.py:132 relations.py:156 validators.py:77
#: validators.py:160
msgid "This field is required."
msgstr ""
#: fields.py:163
msgid "This field may not be null."
msgstr ""
#: fields.py:496 fields.py:524
#, python-brace-format
msgid "\"{input}\" is not a valid boolean."
msgstr ""
#: fields.py:559
msgid "This field may not be blank."
msgstr ""
#: fields.py:560 fields.py:1342
#, python-brace-format
msgid "Ensure this field has no more than {max_length} characters."
msgstr ""
#: fields.py:561
#, python-brace-format
msgid "Ensure this field has at least {min_length} characters."
msgstr ""
#: fields.py:598
msgid "Enter a valid email address."
msgstr ""
#: fields.py:609
msgid "This value does not match the required pattern."
msgstr ""
#: fields.py:620
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr ""
#: fields.py:632
msgid "Enter a valid URL."
msgstr ""
#: fields.py:643
#, python-brace-format
msgid "\"{value}\" is not a valid UUID."
msgstr ""
#: fields.py:662
msgid "A valid integer is required."
msgstr ""
#: fields.py:663 fields.py:698 fields.py:731
#, python-brace-format
msgid "Ensure this value is less than or equal to {max_value}."
msgstr ""
#: fields.py:664 fields.py:699 fields.py:732
#, python-brace-format
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr ""
#: fields.py:665 fields.py:700 fields.py:736
msgid "String value too large."
msgstr ""
#: fields.py:697 fields.py:730
msgid "A valid number is required."
msgstr ""
#: fields.py:733
#, python-brace-format
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr ""
#: fields.py:734
#, python-brace-format
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr ""
#: fields.py:735
#, python-brace-format
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
#: fields.py:825
#, python-brace-format
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:826
msgid "Expected a datetime but got a date."
msgstr ""
#: fields.py:890
#, python-brace-format
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:891
msgid "Expected a date but got a datetime."
msgstr ""
#: fields.py:954
#, python-brace-format
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:1010 fields.py:1054
#, python-brace-format
msgid "\"{input}\" is not a valid choice."
msgstr ""
#: fields.py:1055 fields.py:1169 serializers.py:483
#, python-brace-format
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1085
msgid "No file was submitted."
msgstr ""
#: fields.py:1086
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr ""
#: fields.py:1087
msgid "No filename could be determined."
msgstr ""
#: fields.py:1088
msgid "The submitted file is empty."
msgstr ""
#: fields.py:1089
#, python-brace-format
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
#: fields.py:1131
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: fields.py:1206
#, python-brace-format
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
#: pagination.py:231
#, python-brace-format
msgid "Invalid page \"{page_number}\": {message}."
msgstr ""
#: pagination.py:492
msgid "Invalid cursor"
msgstr ""
#: relations.py:133
#, python-brace-format
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr ""
#: relations.py:134
#, python-brace-format
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
#, python-brace-format
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
#: relations.py:302
#, python-brace-format
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
#: relations.py:303
msgid "Invalid value."
msgstr ""
#: serializers.py:300
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr ""
#: templates/rest_framework/horizontal/radio.html:2
#: templates/rest_framework/inline/radio.html:2
#: templates/rest_framework/vertical/radio.html:2
msgid "None"
msgstr ""
#: templates/rest_framework/horizontal/select_multiple.html:2
#: templates/rest_framework/inline/select_multiple.html:2
#: templates/rest_framework/vertical/select_multiple.html:2
msgid "No items to select."
msgstr ""
#: validators.py:22
msgid "This field must be unique."
msgstr ""
#: validators.py:76
#, python-brace-format
msgid "The fields {field_names} must make a unique set."
msgstr ""
#: validators.py:224
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
#: validators.py:239
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
#: validators.py:252
#, python-brace-format
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:141
msgid "Invalid version in hostname."
msgstr ""
#: versioning.py:163
msgid "Invalid version in query parameter."
msgstr ""
#: views.py:82
msgid "Permission denied."
msgstr ""
...@@ -36,6 +36,7 @@ class SimpleMetadata(BaseMetadata): ...@@ -36,6 +36,7 @@ class SimpleMetadata(BaseMetadata):
label_lookup = ClassLookupDict({ label_lookup = ClassLookupDict({
serializers.Field: 'field', serializers.Field: 'field',
serializers.BooleanField: 'boolean', serializers.BooleanField: 'boolean',
serializers.NullBooleanField: 'boolean',
serializers.CharField: 'string', serializers.CharField: 'string',
serializers.URLField: 'url', serializers.URLField: 'url',
serializers.EmailField: 'email', serializers.EmailField: 'email',
...@@ -126,7 +127,7 @@ class SimpleMetadata(BaseMetadata): ...@@ -126,7 +127,7 @@ class SimpleMetadata(BaseMetadata):
if value is not None and value != '': if value is not None and value != '':
field_info[attr] = force_text(value, strings_only=True) field_info[attr] = force_text(value, strings_only=True)
if hasattr(field, 'choices'): if not field_info.get('read_only') and hasattr(field, 'choices'):
field_info['choices'] = [ field_info['choices'] = [
{ {
'value': choice_value, 'value': choice_value,
......
...@@ -4,7 +4,7 @@ incoming request. Typically this will be based on the request's Accept header. ...@@ -4,7 +4,7 @@ incoming request. Typically this will be based on the request's Accept header.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
from django.http import Http404 from django.http import Http404
from rest_framework import exceptions from rest_framework import HTTP_HEADER_ENCODING, exceptions
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches
from rest_framework.utils.mediatypes import _MediaType from rest_framework.utils.mediatypes import _MediaType
...@@ -54,13 +54,19 @@ class DefaultContentNegotiation(BaseContentNegotiation): ...@@ -54,13 +54,19 @@ class DefaultContentNegotiation(BaseContentNegotiation):
for media_type in media_type_set: for media_type in media_type_set:
if media_type_matches(renderer.media_type, media_type): if media_type_matches(renderer.media_type, media_type):
# Return the most specific media type as accepted. # Return the most specific media type as accepted.
media_type_wrapper = _MediaType(media_type)
if ( if (
_MediaType(renderer.media_type).precedence > _MediaType(renderer.media_type).precedence >
_MediaType(media_type).precedence media_type_wrapper.precedence
): ):
# Eg client requests '*/*' # Eg client requests '*/*'
# Accepted media type is 'application/json' # Accepted media type is 'application/json'
return renderer, renderer.media_type full_media_type = ';'.join(
(renderer.media_type,) +
tuple('{0}={1}'.format(
key, value.decode(HTTP_HEADER_ENCODING))
for key, value in media_type_wrapper.params.items()))
return renderer, full_media_type
else: else:
# Eg client requests 'application/json; indent=8' # Eg client requests 'application/json; indent=8'
# Accepted media type is 'application/json; indent=8' # Accepted media type is 'application/json; indent=8'
......
...@@ -10,7 +10,7 @@ from django.core.paginator import InvalidPage, Paginator as DjangoPaginator ...@@ -10,7 +10,7 @@ from django.core.paginator import InvalidPage, Paginator as DjangoPaginator
from django.template import Context, loader from django.template import Context, loader
from django.utils import six from django.utils import six
from django.utils.six.moves.urllib import parse as urlparse from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import OrderedDict from rest_framework.compat import OrderedDict
from rest_framework.exceptions import NotFound from rest_framework.exceptions import NotFound
from rest_framework.response import Response from rest_framework.response import Response
...@@ -18,6 +18,7 @@ from rest_framework.settings import api_settings ...@@ -18,6 +18,7 @@ from rest_framework.settings import api_settings
from rest_framework.utils.urls import ( from rest_framework.utils.urls import (
replace_query_param, remove_query_param replace_query_param, remove_query_param
) )
import warnings
def _positive_int(integer_string, strict=False, cutoff=None): def _positive_int(integer_string, strict=False, cutoff=None):
...@@ -130,12 +131,19 @@ def _decode_cursor(encoded): ...@@ -130,12 +131,19 @@ def _decode_cursor(encoded):
""" """
Given a string representing an encoded cursor, return a `Cursor` instance. Given a string representing an encoded cursor, return a `Cursor` instance.
""" """
# The offset in the cursor is used in situations where we have a
# nearly-unique index. (Eg millisecond precision creation timestamps)
# We guard against malicious users attempting to cause expensive database
# queries, by having a hard cap on the maximum possible size of the offset.
OFFSET_CUTOFF = 1000
try: try:
querystring = b64decode(encoded.encode('ascii')).decode('ascii') querystring = b64decode(encoded.encode('ascii')).decode('ascii')
tokens = urlparse.parse_qs(querystring, keep_blank_values=True) tokens = urlparse.parse_qs(querystring, keep_blank_values=True)
offset = tokens.get('o', ['0'])[0] offset = tokens.get('o', ['0'])[0]
offset = _positive_int(offset) offset = _positive_int(offset, cutoff=OFFSET_CUTOFF)
reverse = tokens.get('r', ['0'])[0] reverse = tokens.get('r', ['0'])[0]
reverse = bool(int(reverse)) reverse = bool(int(reverse))
...@@ -203,18 +211,18 @@ class PageNumberPagination(BasePagination): ...@@ -203,18 +211,18 @@ class PageNumberPagination(BasePagination):
""" """
# The default page size. # The default page size.
# Defaults to `None`, meaning pagination is disabled. # Defaults to `None`, meaning pagination is disabled.
paginate_by = api_settings.PAGINATE_BY page_size = api_settings.PAGE_SIZE
# Client can control the page using this query parameter. # Client can control the page using this query parameter.
page_query_param = 'page' page_query_param = 'page'
# Client can control the page size using this query parameter. # Client can control the page size using this query parameter.
# Default is 'None'. Set to eg 'page_size' to enable usage. # Default is 'None'. Set to eg 'page_size' to enable usage.
paginate_by_param = api_settings.PAGINATE_BY_PARAM page_size_query_param = None
# Set to an integer to limit the maximum page size the client may request. # Set to an integer to limit the maximum page size the client may request.
# Only relevant if 'paginate_by_param' has also been set. # Only relevant if 'page_size_query_param' has also been set.
max_paginate_by = api_settings.MAX_PAGINATE_BY max_page_size = None
last_page_strings = ('last',) last_page_strings = ('last',)
...@@ -228,12 +236,48 @@ class PageNumberPagination(BasePagination): ...@@ -228,12 +236,48 @@ class PageNumberPagination(BasePagination):
attributes were set there. The attributes should now be set on attributes were set there. The attributes should now be set on
the pagination class, but the old style is still pending deprecation. the pagination class, but the old style is still pending deprecation.
""" """
for attr in ( assert not (
'paginate_by', 'page_query_param', getattr(view, 'pagination_serializer_class', None) or
'paginate_by_param', 'max_paginate_by' getattr(api_settings, 'DEFAULT_PAGINATION_SERIALIZER_CLASS', None)
), (
"The pagination_serializer_class attribute and "
"DEFAULT_PAGINATION_SERIALIZER_CLASS setting have been removed as "
"part of the 3.1 pagination API improvement. See the pagination "
"documentation for details on the new API."
)
for (settings_key, attr_name) in (
('PAGINATE_BY', 'page_size'),
('PAGINATE_BY_PARAM', 'page_size_query_param'),
('MAX_PAGINATE_BY', 'max_page_size')
): ):
if hasattr(view, attr): value = getattr(api_settings, settings_key, None)
setattr(self, attr, getattr(view, attr)) if value is not None:
setattr(self, attr_name, value)
warnings.warn(
"The `%s` settings key is pending deprecation. "
"Use the `%s` attribute on the pagination class instead." % (
settings_key, attr_name
),
PendingDeprecationWarning,
)
for (view_attr, attr_name) in (
('paginate_by', 'page_size'),
('page_query_param', 'page_query_param'),
('paginate_by_param', 'page_size_query_param'),
('max_paginate_by', 'max_page_size')
):
value = getattr(view, view_attr, None)
if value is not None:
setattr(self, attr_name, value)
warnings.warn(
"The `%s` view attribute is pending deprecation. "
"Use the `%s` attribute on the pagination class instead." % (
view_attr, attr_name
),
PendingDeprecationWarning,
)
def paginate_queryset(self, queryset, request, view=None): def paginate_queryset(self, queryset, request, view=None):
""" """
...@@ -264,7 +308,7 @@ class PageNumberPagination(BasePagination): ...@@ -264,7 +308,7 @@ class PageNumberPagination(BasePagination):
self.display_page_controls = True self.display_page_controls = True
self.request = request self.request = request
return self.page return list(self.page)
def get_paginated_response(self, data): def get_paginated_response(self, data):
return Response(OrderedDict([ return Response(OrderedDict([
...@@ -275,17 +319,17 @@ class PageNumberPagination(BasePagination): ...@@ -275,17 +319,17 @@ class PageNumberPagination(BasePagination):
])) ]))
def get_page_size(self, request): def get_page_size(self, request):
if self.paginate_by_param: if self.page_size_query_param:
try: try:
return _positive_int( return _positive_int(
request.query_params[self.paginate_by_param], request.query_params[self.page_size_query_param],
strict=True, strict=True,
cutoff=self.max_paginate_by cutoff=self.max_page_size
) )
except (KeyError, ValueError): except (KeyError, ValueError):
pass pass
return self.paginate_by return self.page_size
def get_next_link(self): def get_next_link(self):
if not self.page.has_next(): if not self.page.has_next():
...@@ -336,7 +380,7 @@ class LimitOffsetPagination(BasePagination): ...@@ -336,7 +380,7 @@ class LimitOffsetPagination(BasePagination):
http://api.example.org/accounts/?limit=100 http://api.example.org/accounts/?limit=100
http://api.example.org/accounts/?offset=400&limit=100 http://api.example.org/accounts/?offset=400&limit=100
""" """
default_limit = api_settings.PAGINATE_BY default_limit = api_settings.PAGE_SIZE
limit_query_param = 'limit' limit_query_param = 'limit'
offset_query_param = 'offset' offset_query_param = 'offset'
max_limit = None max_limit = None
...@@ -344,12 +388,15 @@ class LimitOffsetPagination(BasePagination): ...@@ -344,12 +388,15 @@ class LimitOffsetPagination(BasePagination):
def paginate_queryset(self, queryset, request, view=None): def paginate_queryset(self, queryset, request, view=None):
self.limit = self.get_limit(request) self.limit = self.get_limit(request)
if self.limit is None:
return None
self.offset = self.get_offset(request) self.offset = self.get_offset(request)
self.count = _get_count(queryset) self.count = _get_count(queryset)
self.request = request self.request = request
if self.count > self.limit and self.template is not None: if self.count > self.limit and self.template is not None:
self.display_page_controls = True self.display_page_controls = True
return queryset[self.offset:self.offset + self.limit] return list(queryset[self.offset:self.offset + self.limit])
def get_paginated_response(self, data): def get_paginated_response(self, data):
return Response(OrderedDict([ return Response(OrderedDict([
...@@ -435,17 +482,21 @@ class LimitOffsetPagination(BasePagination): ...@@ -435,17 +482,21 @@ class LimitOffsetPagination(BasePagination):
class CursorPagination(BasePagination): class CursorPagination(BasePagination):
# Determine how/if True, False and None positions work - do the string """
# encodings work with Django queryset filters? The cursor pagination implementation is neccessarily complex.
# Consider a max offset cap. For an overview of the position/offset style we use, see this post:
# Tidy up the `get_ordering` API (eg remove queryset from it) http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api/
"""
cursor_query_param = 'cursor' cursor_query_param = 'cursor'
page_size = api_settings.PAGINATE_BY page_size = api_settings.PAGE_SIZE
invalid_cursor_message = _('Invalid cursor') invalid_cursor_message = _('Invalid cursor')
ordering = None ordering = '-created'
template = 'rest_framework/pagination/previous_and_next.html' template = 'rest_framework/pagination/previous_and_next.html'
def paginate_queryset(self, queryset, request, view=None): def paginate_queryset(self, queryset, request, view=None):
if self.page_size is None:
return None
self.base_url = request.build_absolute_uri() self.base_url = request.build_absolute_uri()
self.ordering = self.get_ordering(request, queryset, view) self.ordering = self.get_ordering(request, queryset, view)
...@@ -484,7 +535,7 @@ class CursorPagination(BasePagination): ...@@ -484,7 +535,7 @@ class CursorPagination(BasePagination):
# We also always fetch an extra item in order to determine if there is a # We also always fetch an extra item in order to determine if there is a
# page following on from this one. # page following on from this one.
results = list(queryset[offset:offset + self.page_size + 1]) results = list(queryset[offset:offset + self.page_size + 1])
self.page = results[:self.page_size] self.page = list(results[:self.page_size])
# Determine the position of the final item following the page. # Determine the position of the final item following the page.
if len(results) > len(self.page): if len(results) > len(self.page):
...@@ -643,12 +694,12 @@ class CursorPagination(BasePagination): ...@@ -643,12 +694,12 @@ class CursorPagination(BasePagination):
) )
) )
else: else:
# The default case is to check for an `ordering` attribute, # The default case is to check for an `ordering` attribute
# first on the view instance, and then on this pagination instance. # on this pagination instance.
ordering = getattr(view, 'ordering', getattr(self, 'ordering', None)) ordering = self.ordering
assert ordering is not None, ( assert ordering is not None, (
'Using cursor pagination, but no ordering attribute was declared ' 'Using cursor pagination, but no ordering attribute was declared '
'on the view or on the pagination class.' 'on the pagination class.'
) )
assert isinstance(ordering, (six.string_types, list, tuple)), ( assert isinstance(ordering, (six.string_types, list, tuple)), (
......
...@@ -5,7 +5,7 @@ from __future__ import unicode_literals ...@@ -5,7 +5,7 @@ from __future__ import unicode_literals
from django.http import Http404 from django.http import Http404
from rest_framework.compat import get_model_name from rest_framework.compat import get_model_name
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'] SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
class BasePermission(object): class BasePermission(object):
...@@ -77,7 +77,7 @@ class DjangoModelPermissions(BasePermission): ...@@ -77,7 +77,7 @@ class DjangoModelPermissions(BasePermission):
`add`/`change`/`delete` permissions on the model. `add`/`change`/`delete` permissions on the model.
This permission can only be applied against view classes that This permission can only be applied against view classes that
provide a `.model` or `.queryset` attribute. provide a `.queryset` attribute.
""" """
# Map methods into required permission codes. # Map methods into required permission codes.
...@@ -107,24 +107,22 @@ class DjangoModelPermissions(BasePermission): ...@@ -107,24 +107,22 @@ class DjangoModelPermissions(BasePermission):
return [perm % kwargs for perm in self.perms_map[method]] return [perm % kwargs for perm in self.perms_map[method]]
def has_permission(self, request, view): def has_permission(self, request, view):
# Note that `.model` attribute on views is deprecated, although we
# enforce the deprecation on the view `get_serializer_class()` and
# `get_queryset()` methods, rather than here.
model_cls = getattr(view, 'model', None)
queryset = getattr(view, 'queryset', None)
if model_cls is None and queryset is not None:
model_cls = queryset.model
# Workaround to ensure DjangoModelPermissions are not applied # Workaround to ensure DjangoModelPermissions are not applied
# to the root view when using DefaultRouter. # to the root view when using DefaultRouter.
if model_cls is None and getattr(view, '_ignore_model_permissions', False): if getattr(view, '_ignore_model_permissions', False):
return True return True
assert model_cls, ('Cannot apply DjangoModelPermissions on a view that' try:
' does not have `.model` or `.queryset` property.') queryset = view.get_queryset()
except AttributeError:
queryset = getattr(view, 'queryset', None)
perms = self.get_required_permissions(request.method, model_cls) assert queryset is not None, (
'Cannot apply DjangoModelPermissions on a view that '
'does not have `.queryset` property or overrides the '
'`.get_queryset()` method.')
perms = self.get_required_permissions(request.method, queryset.model)
return ( return (
request.user and request.user and
...@@ -150,7 +148,7 @@ class DjangoObjectPermissions(DjangoModelPermissions): ...@@ -150,7 +148,7 @@ class DjangoObjectPermissions(DjangoModelPermissions):
`add`/`change`/`delete` permissions on the object using .has_perms. `add`/`change`/`delete` permissions on the object using .has_perms.
This permission can only be applied against view classes that This permission can only be applied against view classes that
provide a `.model` or `.queryset` attribute. provide a `.queryset` attribute.
""" """
perms_map = { perms_map = {
...@@ -171,21 +169,27 @@ class DjangoObjectPermissions(DjangoModelPermissions): ...@@ -171,21 +169,27 @@ class DjangoObjectPermissions(DjangoModelPermissions):
return [perm % kwargs for perm in self.perms_map[method]] return [perm % kwargs for perm in self.perms_map[method]]
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
model_cls = getattr(view, 'model', None) try:
queryset = view.get_queryset()
except AttributeError:
queryset = getattr(view, 'queryset', None) queryset = getattr(view, 'queryset', None)
if model_cls is None and queryset is not None: assert queryset is not None, (
'Cannot apply DjangoObjectPermissions on a view that '
'does not have `.queryset` property or overrides the '
'`.get_queryset()` method.')
model_cls = queryset.model model_cls = queryset.model
user = request.user
perms = self.get_required_object_permissions(request.method, model_cls) perms = self.get_required_object_permissions(request.method, model_cls)
user = request.user
if not user.has_perms(perms, obj): if not user.has_perms(perms, obj):
# If the user does not have permissions we need to determine if # If the user does not have permissions we need to determine if
# they have read permissions to see 403, or not, and simply see # they have read permissions to see 403, or not, and simply see
# a 404 response. # a 404 response.
if request.method in ('GET', 'OPTIONS', 'HEAD'): if request.method in SAFE_METHODS:
# Read permissions already checked and failed, no need # Read permissions already checked and failed, no need
# to make another lookup. # to make another lookup.
raise Http404 raise Http404
......
...@@ -196,7 +196,7 @@ class HyperlinkedRelatedField(RelatedField): ...@@ -196,7 +196,7 @@ class HyperlinkedRelatedField(RelatedField):
attributes are not configured to correctly match the URL conf. attributes are not configured to correctly match the URL conf.
""" """
# Unsaved objects will not yet have a valid URL. # Unsaved objects will not yet have a valid URL.
if obj.pk is None: if hasattr(obj, 'pk') and obj.pk is None:
return None return None
lookup_value = getattr(obj, self.lookup_field) lookup_value = getattr(obj, self.lookup_field)
...@@ -360,6 +360,10 @@ class ManyRelatedField(Field): ...@@ -360,6 +360,10 @@ class ManyRelatedField(Field):
] ]
def get_attribute(self, instance): def get_attribute(self, instance):
# Can't have any relationships if not created
if hasattr(instance, 'pk') and instance.pk is None:
return []
relationship = get_attribute(instance, self.source_attrs) relationship = get_attribute(instance, self.source_attrs)
return relationship.all() if (hasattr(relationship, 'all')) else relationship return relationship.all() if (hasattr(relationship, 'all')) else relationship
......
...@@ -421,6 +421,14 @@ class BrowsableAPIRenderer(BaseRenderer): ...@@ -421,6 +421,14 @@ class BrowsableAPIRenderer(BaseRenderer):
return False # Doesn't have permissions return False # Doesn't have permissions
return True return True
def _get_serializer(self, serializer_class, view_instance, request, *args, **kwargs):
kwargs['context'] = {
'request': request,
'format': self.format,
'view': view_instance
}
return serializer_class(*args, **kwargs)
def get_rendered_html_form(self, data, view, method, request): def get_rendered_html_form(self, data, view, method, request):
""" """
Return a string representing a rendered HTML form, possibly bound to Return a string representing a rendered HTML form, possibly bound to
...@@ -457,8 +465,11 @@ class BrowsableAPIRenderer(BaseRenderer): ...@@ -457,8 +465,11 @@ class BrowsableAPIRenderer(BaseRenderer):
if method in ('DELETE', 'OPTIONS'): if method in ('DELETE', 'OPTIONS'):
return True # Don't actually need to return a form return True # Don't actually need to return a form
has_serializer = getattr(view, 'get_serializer', None)
has_serializer_class = getattr(view, 'serializer_class', None)
if ( if (
not getattr(view, 'get_serializer', None) or (not has_serializer and not has_serializer_class) or
not any(is_form_media_type(parser.media_type) for parser in view.parser_classes) not any(is_form_media_type(parser.media_type) for parser in view.parser_classes)
): ):
return return
...@@ -466,10 +477,19 @@ class BrowsableAPIRenderer(BaseRenderer): ...@@ -466,10 +477,19 @@ class BrowsableAPIRenderer(BaseRenderer):
if existing_serializer is not None: if existing_serializer is not None:
serializer = existing_serializer serializer = existing_serializer
else: else:
if has_serializer:
if method in ('PUT', 'PATCH'): if method in ('PUT', 'PATCH'):
serializer = view.get_serializer(instance=instance, **kwargs) serializer = view.get_serializer(instance=instance, **kwargs)
else: else:
serializer = view.get_serializer(**kwargs) serializer = view.get_serializer(**kwargs)
else:
# at this point we must have a serializer_class
if method in ('PUT', 'PATCH'):
serializer = self._get_serializer(view.serializer_class, view,
request, instance=instance, **kwargs)
else:
serializer = self._get_serializer(view.serializer_class, view,
request, **kwargs)
if hasattr(serializer, 'initial_data'): if hasattr(serializer, 'initial_data'):
serializer.is_valid() serializer.is_valid()
...@@ -591,7 +611,7 @@ class BrowsableAPIRenderer(BaseRenderer): ...@@ -591,7 +611,7 @@ class BrowsableAPIRenderer(BaseRenderer):
renderer_content_type += ' ;%s' % renderer.charset renderer_content_type += ' ;%s' % renderer.charset
response_headers['Content-Type'] = renderer_content_type response_headers['Content-Type'] = renderer_content_type
if hasattr(view, 'paginator') and view.paginator.display_page_controls: if getattr(view, 'paginator', None) and view.paginator.display_page_controls:
paginator = view.paginator paginator = view.paginator
else: else:
paginator = None paginator = None
......
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