From d972df7c9c1867b4a0a57307f423a488c4d4f4b1 Mon Sep 17 00:00:00 2001
From: tanwanirahul
Date: Mon, 3 Nov 2014 14:43:53 +0100
Subject: Ability to override default method names by customizing it
---
rest_framework/routers.py | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index 169e6e8b..d1c9fa1b 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -176,23 +176,27 @@ class SimpleRouter(BaseRouter):
if isinstance(route, DynamicDetailRoute):
# Dynamic detail routes (@detail_route decorator)
for httpmethods, methodname in detail_routes:
+ method_kwargs = getattr(viewset, methodname).kwargs
+ custom_method_name = method_kwargs.pop("custom_method_name", None) or methodname
initkwargs = route.initkwargs.copy()
- initkwargs.update(getattr(viewset, methodname).kwargs)
+ initkwargs.update(method_kwargs)
ret.append(Route(
- url=replace_methodname(route.url, methodname),
+ url=replace_methodname(route.url, custom_method_name),
mapping=dict((httpmethod, methodname) for httpmethod in httpmethods),
- name=replace_methodname(route.name, methodname),
+ name=replace_methodname(route.name, custom_method_name),
initkwargs=initkwargs,
))
elif isinstance(route, DynamicListRoute):
# Dynamic list routes (@list_route decorator)
for httpmethods, methodname in list_routes:
+ method_kwargs = getattr(viewset, methodname).kwargs
+ custom_method_name = method_kwargs.pop("custom_method_name", None) or methodname
initkwargs = route.initkwargs.copy()
- initkwargs.update(getattr(viewset, methodname).kwargs)
+ initkwargs.update(method_kwargs)
ret.append(Route(
- url=replace_methodname(route.url, methodname),
+ url=replace_methodname(route.url, custom_method_name),
mapping=dict((httpmethod, methodname) for httpmethod in httpmethods),
- name=replace_methodname(route.name, methodname),
+ name=replace_methodname(route.name, custom_method_name),
initkwargs=initkwargs,
))
else:
--
cgit v1.2.3
From ea8c40520165fc33343fceb15221b770701bdedf Mon Sep 17 00:00:00 2001
From: tanwanirahul
Date: Mon, 3 Nov 2014 14:44:47 +0100
Subject: Tests for validating custom_method_name router attribute
---
tests/test_routers.py | 32 ++++++++++++++++++++++++++------
1 file changed, 26 insertions(+), 6 deletions(-)
diff --git a/tests/test_routers.py b/tests/test_routers.py
index f6f5a977..d426f832 100644
--- a/tests/test_routers.py
+++ b/tests/test_routers.py
@@ -8,6 +8,7 @@ from rest_framework.decorators import detail_route, list_route
from rest_framework.response import Response
from rest_framework.routers import SimpleRouter, DefaultRouter
from rest_framework.test import APIRequestFactory
+from collections import namedtuple
factory = APIRequestFactory()
@@ -260,6 +261,14 @@ class DynamicListAndDetailViewSet(viewsets.ViewSet):
def detail_route_get(self, request, *args, **kwargs):
return Response({'method': 'link2'})
+ @list_route(custom_method_name="list_custom-route")
+ def list_custom_route_get(self, request, *args, **kwargs):
+ return Response({'method': 'link1'})
+
+ @detail_route(custom_method_name="detail_custom-route")
+ def detail_custom_route_get(self, request, *args, **kwargs):
+ return Response({'method': 'link2'})
+
class TestDynamicListAndDetailRouter(TestCase):
def setUp(self):
@@ -268,22 +277,33 @@ class TestDynamicListAndDetailRouter(TestCase):
def test_list_and_detail_route_decorators(self):
routes = self.router.get_routes(DynamicListAndDetailViewSet)
decorator_routes = [r for r in routes if not (r.name.endswith('-list') or r.name.endswith('-detail'))]
+
+ MethodNamesMap = namedtuple('MethodNamesMap', 'method_name custom_method_name')
# Make sure all these endpoints exist and none have been clobbered
- for i, endpoint in enumerate(['list_route_get', 'list_route_post', 'detail_route_get', 'detail_route_post']):
+ for i, endpoint in enumerate([MethodNamesMap('list_custom_route_get', 'list_custom-route'),
+ MethodNamesMap('list_route_get', 'list_route_get'),
+ MethodNamesMap('list_route_post', 'list_route_post'),
+ MethodNamesMap('detail_custom_route_get', 'detail_custom-route'),
+ MethodNamesMap('detail_route_get', 'detail_route_get'),
+ MethodNamesMap('detail_route_post', 'detail_route_post')
+ ]):
route = decorator_routes[i]
# check url listing
- if endpoint.startswith('list_'):
+ method_name = endpoint.method_name
+ custom_method_name = endpoint.custom_method_name
+
+ if method_name.startswith('list_'):
self.assertEqual(route.url,
- '^{{prefix}}/{0}{{trailing_slash}}$'.format(endpoint))
+ '^{{prefix}}/{0}{{trailing_slash}}$'.format(custom_method_name))
else:
self.assertEqual(route.url,
- '^{{prefix}}/{{lookup}}/{0}{{trailing_slash}}$'.format(endpoint))
+ '^{{prefix}}/{{lookup}}/{0}{{trailing_slash}}$'.format(custom_method_name))
# check method to function mapping
- if endpoint.endswith('_post'):
+ if method_name.endswith('_post'):
method_map = 'post'
else:
method_map = 'get'
- self.assertEqual(route.mapping[method_map], endpoint)
+ self.assertEqual(route.mapping[method_map], method_name)
class TestRootWithAListlessViewset(TestCase):
--
cgit v1.2.3
From 92ebeaa040f75dbc6142355fa25d89b4c990685b Mon Sep 17 00:00:00 2001
From: tanwanirahul
Date: Fri, 19 Dec 2014 19:52:59 +0530
Subject: Change decorator attribute name to url_path per suggestions
---
rest_framework/routers.py | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index d1c9fa1b..a213f62c 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -177,26 +177,26 @@ class SimpleRouter(BaseRouter):
# Dynamic detail routes (@detail_route decorator)
for httpmethods, methodname in detail_routes:
method_kwargs = getattr(viewset, methodname).kwargs
- custom_method_name = method_kwargs.pop("custom_method_name", None) or methodname
+ url_path = method_kwargs.pop("url_path", None) or methodname
initkwargs = route.initkwargs.copy()
initkwargs.update(method_kwargs)
ret.append(Route(
- url=replace_methodname(route.url, custom_method_name),
+ url=replace_methodname(route.url, url_path),
mapping=dict((httpmethod, methodname) for httpmethod in httpmethods),
- name=replace_methodname(route.name, custom_method_name),
+ name=replace_methodname(route.name, url_path),
initkwargs=initkwargs,
))
elif isinstance(route, DynamicListRoute):
# Dynamic list routes (@list_route decorator)
for httpmethods, methodname in list_routes:
method_kwargs = getattr(viewset, methodname).kwargs
- custom_method_name = method_kwargs.pop("custom_method_name", None) or methodname
+ url_path = method_kwargs.pop("url_path", None) or methodname
initkwargs = route.initkwargs.copy()
initkwargs.update(method_kwargs)
ret.append(Route(
- url=replace_methodname(route.url, custom_method_name),
+ url=replace_methodname(route.url, url_path),
mapping=dict((httpmethod, methodname) for httpmethod in httpmethods),
- name=replace_methodname(route.name, custom_method_name),
+ name=replace_methodname(route.name, url_path),
initkwargs=initkwargs,
))
else:
--
cgit v1.2.3
From 2448cc8e856369ca6fb99b848e10f8ff0105e925 Mon Sep 17 00:00:00 2001
From: tanwanirahul
Date: Fri, 19 Dec 2014 19:53:48 +0530
Subject: Updated tests to use url_path attribute in list and detail decorators
---
tests/test_routers.py | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/tests/test_routers.py b/tests/test_routers.py
index d426f832..73d10822 100644
--- a/tests/test_routers.py
+++ b/tests/test_routers.py
@@ -261,11 +261,11 @@ class DynamicListAndDetailViewSet(viewsets.ViewSet):
def detail_route_get(self, request, *args, **kwargs):
return Response({'method': 'link2'})
- @list_route(custom_method_name="list_custom-route")
+ @list_route(url_path="list_custom-route")
def list_custom_route_get(self, request, *args, **kwargs):
return Response({'method': 'link1'})
- @detail_route(custom_method_name="detail_custom-route")
+ @detail_route(url_path="detail_custom-route")
def detail_custom_route_get(self, request, *args, **kwargs):
return Response({'method': 'link2'})
@@ -278,7 +278,7 @@ class TestDynamicListAndDetailRouter(TestCase):
routes = self.router.get_routes(DynamicListAndDetailViewSet)
decorator_routes = [r for r in routes if not (r.name.endswith('-list') or r.name.endswith('-detail'))]
- MethodNamesMap = namedtuple('MethodNamesMap', 'method_name custom_method_name')
+ MethodNamesMap = namedtuple('MethodNamesMap', 'method_name url_path')
# Make sure all these endpoints exist and none have been clobbered
for i, endpoint in enumerate([MethodNamesMap('list_custom_route_get', 'list_custom-route'),
MethodNamesMap('list_route_get', 'list_route_get'),
@@ -290,14 +290,14 @@ class TestDynamicListAndDetailRouter(TestCase):
route = decorator_routes[i]
# check url listing
method_name = endpoint.method_name
- custom_method_name = endpoint.custom_method_name
+ url_path = endpoint.url_path
if method_name.startswith('list_'):
self.assertEqual(route.url,
- '^{{prefix}}/{0}{{trailing_slash}}$'.format(custom_method_name))
+ '^{{prefix}}/{0}{{trailing_slash}}$'.format(url_path))
else:
self.assertEqual(route.url,
- '^{{prefix}}/{{lookup}}/{0}{{trailing_slash}}$'.format(custom_method_name))
+ '^{{prefix}}/{{lookup}}/{0}{{trailing_slash}}$'.format(url_path))
# check method to function mapping
if method_name.endswith('_post'):
method_map = 'post'
--
cgit v1.2.3
From a8a3fedb5c52cc62c6ecf59c4138e9a6ecf04806 Mon Sep 17 00:00:00 2001
From: tanwanirahul
Date: Fri, 19 Dec 2014 20:16:46 +0530
Subject: Add url_path documention for detail_route decorator
---
docs/tutorial/6-viewsets-and-routers.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md
index cf37a260..8e4e22f0 100644
--- a/docs/tutorial/6-viewsets-and-routers.md
+++ b/docs/tutorial/6-viewsets-and-routers.md
@@ -53,6 +53,8 @@ Notice that we've also used the `@detail_route` decorator to create a custom act
Custom actions which use the `@detail_route` decorator will respond to `GET` requests. We can use the `methods` argument if we wanted an action that responded to `POST` requests.
+The URLs for custom actions by default depends on the method name itself. If you want to change the way url should be constructed, you can use `url_path` parameter of `@detail_route` and provide the string value for the same.
+
## Binding ViewSets to URLs explicitly
The handler methods only get bound to the actions when we define the URLConf.
--
cgit v1.2.3
From 6aa0e307c99d0c17d7c48f2416472c7dbdcbbf8f Mon Sep 17 00:00:00 2001
From: Rahul
Date: Fri, 19 Dec 2014 20:31:21 +0530
Subject: Added documentation about url_path parameter for custom actions.
---
docs/api-guide/routers.md | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md
index 61a476b8..63b8b59a 100644
--- a/docs/api-guide/routers.md
+++ b/docs/api-guide/routers.md
@@ -68,6 +68,24 @@ The following URL pattern would additionally be generated:
* URL pattern: `^users/{pk}/set_password/$` Name: `'user-set-password'`
+If you did not like the default URL generated for your custom action, you could use `url_path` parameter with `@detail_route` or `@list_route` to customize it.
+
+For example, if you want to change the URL for our custom action to `^users/{pk}/change-password/$`, you could write:
+
+ from myapp.permissions import IsAdminOrIsSelf
+ from rest_framework.decorators import detail_route
+
+ class UserViewSet(ModelViewSet):
+ ...
+
+ @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf], url_path='change-password')
+ def set_password(self, request, pk=None):
+ ...
+
+Above example would instead generate following URL pattern:
+
+* URL pattern: `^users/{pk}/change-password/$` Name: `'user-change-password'`
+
For more information see the viewset documentation on [marking extra actions for routing][route-decorators].
# API Guide
--
cgit v1.2.3
From b4a3e7f64096ea7106ff0d622bdf1c6e2e4e2895 Mon Sep 17 00:00:00 2001
From: Rahul
Date: Fri, 19 Dec 2014 21:20:19 +0530
Subject: Updates url_path info per suggestion
---
docs/api-guide/routers.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md
index 63b8b59a..87b6f15a 100644
--- a/docs/api-guide/routers.md
+++ b/docs/api-guide/routers.md
@@ -68,7 +68,7 @@ The following URL pattern would additionally be generated:
* URL pattern: `^users/{pk}/set_password/$` Name: `'user-set-password'`
-If you did not like the default URL generated for your custom action, you could use `url_path` parameter with `@detail_route` or `@list_route` to customize it.
+If you do not want to use the default URL generated for your custom action, you can instead use the url_path parameter to customize it.
For example, if you want to change the URL for our custom action to `^users/{pk}/change-password/$`, you could write:
@@ -82,7 +82,7 @@ For example, if you want to change the URL for our custom action to `^users/{pk}
def set_password(self, request, pk=None):
...
-Above example would instead generate following URL pattern:
+The above example would now generate the following URL pattern:
* URL pattern: `^users/{pk}/change-password/$` Name: `'user-change-password'`
--
cgit v1.2.3
From 8f0fef4b75f5c999c13b5d37a263da3a3388142e Mon Sep 17 00:00:00 2001
From: Rahul
Date: Fri, 19 Dec 2014 21:22:10 +0530
Subject: Updated documentation on url_path per suggestions.
---
docs/tutorial/6-viewsets-and-routers.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md
index 8e4e22f0..d2ee1102 100644
--- a/docs/tutorial/6-viewsets-and-routers.md
+++ b/docs/tutorial/6-viewsets-and-routers.md
@@ -53,7 +53,7 @@ Notice that we've also used the `@detail_route` decorator to create a custom act
Custom actions which use the `@detail_route` decorator will respond to `GET` requests. We can use the `methods` argument if we wanted an action that responded to `POST` requests.
-The URLs for custom actions by default depends on the method name itself. If you want to change the way url should be constructed, you can use `url_path` parameter of `@detail_route` and provide the string value for the same.
+The URLs for custom actions by default depend on the method name itself. If you want to change the way url should be constructed, you can include url_path as a decorator keyword argument.
## Binding ViewSets to URLs explicitly
--
cgit v1.2.3
From 48d15f6ff8a13aafd5b4977c8d1b4b7fe70b4f6a Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 19 Dec 2014 16:58:35 +0000
Subject: Stub out the documentation
---
docs/api-guide/serializers.md | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index b9f0e7bc..4d3dfa31 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -567,6 +567,32 @@ The inner `Meta` class on serializers is not inherited from parent classes by de
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
+## Advanced `ModelSerializer` usage
+
+The ModelSerializer class also exposes an API that you can override in order to alter how serializer fields are automatically determined when instantiating the serializer.
+
+#### `.serializer_field_mapping`
+
+A mapping of Django model classes to REST framework serializer classes. You can override this mapping to alter the default serializer classes that should be used for each model class.
+
+#### `.serializer_relational_field`
+
+This property should be the serializer field class, that is used for relational fields by default. For `ModelSerializer` this defaults to `PrimaryKeyRelatedField`. For `HyperlinkedModelSerializer` this defaults to `HyperlinkedRelatedField`.
+
+#### The build field methods
+
+#### `build_standard_field(**kwargs)`
+
+#### `build_relational_field(**kwargs)`
+
+#### `build_nested_field(**kwargs)`
+
+#### `build_property_field(**kwargs)`
+
+#### `build_url_field(**kwargs)`
+
+#### `build_unknown_field(**kwargs)`
+
---
# HyperlinkedModelSerializer
--
cgit v1.2.3
From 2a1485e00943b8280245d19e1e1f8514b1ef18ea Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 19 Dec 2014 21:32:43 +0000
Subject: Final bits of docs for ModelSerializer fields API
---
docs/api-guide/serializers.md | 57 +++++++++++----
docs_theme/css/default.css | 4 ++
rest_framework/serializers.py | 140 +++++++++++++++++++++----------------
rest_framework/utils/model_meta.py | 10 +--
tests/test_model_serializer.py | 2 +-
5 files changed, 132 insertions(+), 81 deletions(-)
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 4d3dfa31..dcbbd5f2 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -457,7 +457,7 @@ To do so, open the Django shell, using `python manage.py shell`, then import the
name = CharField(allow_blank=True, max_length=100, required=False)
owner = PrimaryKeyRelatedField(queryset=User.objects.all())
-## Specifying which fields should be included
+## Specifying which fields to include
If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`.
@@ -499,7 +499,7 @@ You can add extra fields to a `ModelSerializer` or override the default fields b
Extra fields can correspond to any property or callable on the model.
-## Specifying which fields should be read-only
+## Specifying read only fields
You may wish to specify multiple fields as read-only. Instead of adding each field explicitly with the `read_only=True` attribute, you may use the shortcut Meta option, `read_only_fields`.
@@ -528,7 +528,7 @@ Please review the [Validators Documentation](/api-guide/validators/) for details
---
-## Specifying additional keyword arguments for fields.
+## Additional keyword arguments
There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. Similarly to `read_only_fields` this means you do not need to explicitly declare the field on the serializer.
@@ -567,31 +567,62 @@ The inner `Meta` class on serializers is not inherited from parent classes by de
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
-## Advanced `ModelSerializer` usage
+## Customizing field mappings
The ModelSerializer class also exposes an API that you can override in order to alter how serializer fields are automatically determined when instantiating the serializer.
-#### `.serializer_field_mapping`
+Normally if a `ModelSerializer` does not generate the fields you need by default the you should either add them to the class explicitly, or simply use a regular `Serializer` class instead. However in some cases you may want to create a new base class that defines how the serializer fields are created for any given model.
+
+### `.serializer_field_mapping`
A mapping of Django model classes to REST framework serializer classes. You can override this mapping to alter the default serializer classes that should be used for each model class.
-#### `.serializer_relational_field`
+### `.serializer_relational_field`
This property should be the serializer field class, that is used for relational fields by default. For `ModelSerializer` this defaults to `PrimaryKeyRelatedField`. For `HyperlinkedModelSerializer` this defaults to `HyperlinkedRelatedField`.
-#### The build field methods
+### The field_class and field_kwargs API
+
+The following methods are called to determine the class and keyword arguments for each field that should be automatically included on the serializer. Each of these methods should return a two tuple of `(field_class, field_kwargs)`.
+
+### `.build_standard_field(self, field_name, model_field)`
+
+Called to generate a serializer field that maps to a standard model field.
+
+The default implementation returns a serializer class based on the `serializer_field_mapping` attribute.
+
+### `.build_relational_field(self, field_name, relation_info)`
+
+Called to generate a serializer field that maps to a relational model field.
+
+The default implementation returns a serializer class based on the `serializer_relational_field` attribute.
+
+The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties.
+
+### `.build_nested_field(self, field_name, relation_info, nested_depth)`
+
+Called to generate a serializer field that maps to a relational model field, when the `depth` option has been set.
+
+The default implementation dynamically creates a nested serializer class based on either `ModelSerializer` or `HyperlinkedModelSerializer`.
+
+The `nested_depth` will be the value of the `depth` option, minus one.
+
+The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties.
+
+### `.build_property_field(self, field_name, model_class)`
-#### `build_standard_field(**kwargs)`
+Called to generate a serializer field that maps to a property or zero-argument method on the model class.
-#### `build_relational_field(**kwargs)`
+The default implementation returns a `ReadOnlyField` class.
-#### `build_nested_field(**kwargs)`
+### `.build_url_field(self, field_name, model_class)`
-#### `build_property_field(**kwargs)`
+Called to generate a serializer field for the serializer's own `url` field. The default implementation returns a `HyperlinkedIdentityField` class.
-#### `build_url_field(**kwargs)`
+### `.build_unknown_field(self, field_name, model_class)`
-#### `build_unknown_field(**kwargs)`
+Called when the field name did not map to any model field or model property.
+The default implementation raises an error, although subclasses may customize this behavior.
---
diff --git a/docs_theme/css/default.css b/docs_theme/css/default.css
index 8c9cd536..48d00366 100644
--- a/docs_theme/css/default.css
+++ b/docs_theme/css/default.css
@@ -239,6 +239,10 @@ body a:hover{
}
}
+h1 code, h2 code, h3 code, h4 code, h5 code {
+ color: #333;
+}
+
/* sticky footer and footer */
html, body {
height: 100%;
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 8adbafe4..623ed586 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -802,10 +802,25 @@ class ModelSerializer(Serializer):
Return the dict of field names -> field instances that should be
used for `self.fields` when instantiating the serializer.
"""
+ assert hasattr(self, 'Meta'), (
+ 'Class {serializer_class} missing "Meta" attribute'.format(
+ serializer_class=self.__class__.__name__
+ )
+ )
+ assert hasattr(self.Meta, 'model'), (
+ 'Class {serializer_class} missing "Meta.model" attribute'.format(
+ serializer_class=self.__class__.__name__
+ )
+ )
+
declared_fields = copy.deepcopy(self._declared_fields)
model = getattr(self.Meta, 'model')
depth = getattr(self.Meta, 'depth', 0)
+ if depth is not None:
+ assert depth >= 0, "'depth' may not be negative."
+ assert depth <= 10, "'depth' may not be greater than 10."
+
# Retrieve metadata about fields & relationships on the model class.
info = model_meta.get_field_info(model)
field_names = self.get_field_names(declared_fields, info)
@@ -817,27 +832,32 @@ class ModelSerializer(Serializer):
field_names, declared_fields, extra_kwargs
)
- # Now determine the fields that should be included on the serializer.
- ret = OrderedDict()
+ # Determine the fields that should be included on the serializer.
+ fields = OrderedDict()
+
for field_name in field_names:
+ # If the field is explicitly declared on the class then use that.
if field_name in declared_fields:
- # Field is explicitly declared on the class, use that.
- ret[field_name] = declared_fields[field_name]
+ fields[field_name] = declared_fields[field_name]
continue
# Determine the serializer field class and keyword arguments.
- field_cls, kwargs = self.build_field(field_name, info, model, depth)
+ field_class, field_kwargs = self.build_field(
+ field_name, info, model, depth
+ )
- # Populate any kwargs defined in `Meta.extra_kwargs`
- kwargs = self.build_field_kwargs(kwargs, extra_kwargs, field_name)
+ # Include any kwargs defined in `Meta.extra_kwargs`
+ field_kwargs = self.build_field_kwargs(
+ field_kwargs, extra_kwargs, field_name
+ )
# Create the serializer field.
- ret[field_name] = field_cls(**kwargs)
+ fields[field_name] = field_class(**field_kwargs)
# Add in any hidden fields.
- ret.update(hidden_fields)
+ fields.update(hidden_fields)
- return ret
+ return fields
# Methods for determining the set of field names to include...
@@ -916,108 +936,105 @@ class ModelSerializer(Serializer):
# Methods for constructing serializer fields...
- def build_field(self, field_name, info, model, nested_depth):
+ def build_field(self, field_name, info, model_class, nested_depth):
"""
Return a two tuple of (cls, kwargs) to build a serializer field with.
"""
if field_name in info.fields_and_pk:
- return self.build_standard_field(field_name, info, model)
+ model_field = info.fields_and_pk[field_name]
+ return self.build_standard_field(field_name, model_field)
elif field_name in info.relations:
+ relation_info = info.relations[field_name]
if not nested_depth:
- return self.build_relational_field(field_name, info, model)
+ return self.build_relational_field(field_name, relation_info)
else:
- return self.build_nested_field(field_name, info, model, nested_depth)
+ return self.build_nested_field(field_name, relation_info, nested_depth)
- elif hasattr(model, field_name):
- return self.build_property_field(field_name, info, model)
+ elif hasattr(model_class, field_name):
+ return self.build_property_field(field_name, model_class)
elif field_name == api_settings.URL_FIELD_NAME:
- return self.build_url_field(field_name, info, model)
+ return self.build_url_field(field_name, model_class)
- return self.build_unknown_field(field_name, info, model)
+ return self.build_unknown_field(field_name, model_class)
- def build_standard_field(self, field_name, info, model):
+ def build_standard_field(self, field_name, model_field):
"""
Create regular model fields.
"""
field_mapping = ClassLookupDict(self.serializer_field_mapping)
- model_field = info.fields_and_pk[field_name]
- field_cls = field_mapping[model_field]
- kwargs = get_field_kwargs(field_name, model_field)
+ field_class = field_mapping[model_field]
+ field_kwargs = get_field_kwargs(field_name, model_field)
- if 'choices' in kwargs:
+ if 'choices' in field_kwargs:
# Fields with choices get coerced into `ChoiceField`
# instead of using their regular typed field.
- field_cls = ChoiceField
- if not issubclass(field_cls, ModelField):
+ field_class = ChoiceField
+ if not issubclass(field_class, ModelField):
# `model_field` is only valid for the fallback case of
# `ModelField`, which is used when no other typed field
# matched to the model field.
- kwargs.pop('model_field', None)
- if not issubclass(field_cls, CharField) and not issubclass(field_cls, ChoiceField):
+ field_kwargs.pop('model_field', None)
+ if not issubclass(field_class, CharField) and not issubclass(field_class, ChoiceField):
# `allow_blank` is only valid for textual fields.
- kwargs.pop('allow_blank', None)
+ field_kwargs.pop('allow_blank', None)
- return field_cls, kwargs
+ return field_class, field_kwargs
- def build_relational_field(self, field_name, info, model):
+ def build_relational_field(self, field_name, relation_info):
"""
Create fields for forward and reverse relationships.
"""
- relation_info = info.relations[field_name]
-
- field_cls = self.serializer_related_class
- kwargs = get_relation_kwargs(field_name, relation_info)
+ field_class = self.serializer_related_class
+ field_kwargs = get_relation_kwargs(field_name, relation_info)
# `view_name` is only valid for hyperlinked relationships.
- if not issubclass(field_cls, HyperlinkedRelatedField):
- kwargs.pop('view_name', None)
+ if not issubclass(field_class, HyperlinkedRelatedField):
+ field_kwargs.pop('view_name', None)
- return field_cls, kwargs
+ return field_class, field_kwargs
- def build_nested_field(self, field_name, info, model, nested_depth):
+ def build_nested_field(self, field_name, relation_info, nested_depth):
"""
Create nested fields for forward and reverse relationships.
"""
- relation_info = info.relations[field_name]
-
class NestedSerializer(ModelSerializer):
class Meta:
- model = relation_info.related
- depth = nested_depth - 1
+ model = relation_info.related_model
+ depth = nested_depth
- field_cls = NestedSerializer
- kwargs = get_nested_relation_kwargs(relation_info)
+ field_class = NestedSerializer
+ field_kwargs = get_nested_relation_kwargs(relation_info)
- return field_cls, kwargs
+ return field_class, field_kwargs
- def build_property_field(self, field_name, info, model):
+ def build_property_field(self, field_name, model_class):
"""
Create a read only field for model methods and properties.
"""
- field_cls = ReadOnlyField
- kwargs = {}
+ field_class = ReadOnlyField
+ field_kwargs = {}
- return field_cls, kwargs
+ return field_class, field_kwargs
- def build_url_field(self, field_name, info, model):
+ def build_url_field(self, field_name, model_class):
"""
Create a field representing the object's own URL.
"""
- field_cls = HyperlinkedIdentityField
- kwargs = get_url_kwargs(model)
+ field_class = HyperlinkedIdentityField
+ field_kwargs = get_url_kwargs(model_class)
- return field_cls, kwargs
+ return field_class, field_kwargs
- def build_unknown_field(self, field_name, info, model):
+ def build_unknown_field(self, field_name, model_class):
"""
Raise an error on any unknown fields.
"""
raise ImproperlyConfigured(
'Field name `%s` is not valid for model `%s`.' %
- (field_name, model.__class__.__name__)
+ (field_name, model_class.__name__)
)
def build_field_kwargs(self, kwargs, extra_kwargs, field_name):
@@ -1318,17 +1335,16 @@ class HyperlinkedModelSerializer(ModelSerializer):
list(model_info.forward_relations.keys())
)
- def build_nested_field(self, field_name, info, model, nested_depth):
+ def build_nested_field(self, field_name, relation_info, nested_depth):
"""
Create nested fields for forward and reverse relationships.
"""
- relation_info = info.relations[field_name]
-
class NestedSerializer(HyperlinkedModelSerializer):
class Meta:
- model = relation_info.related
+ model = relation_info.related_model
depth = nested_depth - 1
- field_cls = NestedSerializer
- kwargs = get_nested_relation_kwargs(relation_info)
- return field_cls, kwargs
+ field_class = NestedSerializer
+ field_kwargs = get_nested_relation_kwargs(relation_info)
+
+ return field_class, field_kwargs
diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py
index c98725c6..dfc387ca 100644
--- a/rest_framework/utils/model_meta.py
+++ b/rest_framework/utils/model_meta.py
@@ -24,7 +24,7 @@ FieldInfo = namedtuple('FieldResult', [
RelationInfo = namedtuple('RelationInfo', [
'model_field',
- 'related',
+ 'related_model',
'to_many',
'has_through_model'
])
@@ -77,7 +77,7 @@ def get_field_info(model):
for field in [field for field in opts.fields if field.serialize and field.rel]:
forward_relations[field.name] = RelationInfo(
model_field=field,
- related=_resolve_model(field.rel.to),
+ related_model=_resolve_model(field.rel.to),
to_many=False,
has_through_model=False
)
@@ -86,7 +86,7 @@ def get_field_info(model):
for field in [field for field in opts.many_to_many if field.serialize]:
forward_relations[field.name] = RelationInfo(
model_field=field,
- related=_resolve_model(field.rel.to),
+ related_model=_resolve_model(field.rel.to),
to_many=True,
has_through_model=(
not field.rel.through._meta.auto_created
@@ -99,7 +99,7 @@ def get_field_info(model):
accessor_name = relation.get_accessor_name()
reverse_relations[accessor_name] = RelationInfo(
model_field=None,
- related=relation.model,
+ related_model=relation.model,
to_many=relation.field.rel.multiple,
has_through_model=False
)
@@ -109,7 +109,7 @@ def get_field_info(model):
accessor_name = relation.get_accessor_name()
reverse_relations[accessor_name] = RelationInfo(
model_field=None,
- related=relation.model,
+ related_model=relation.model,
to_many=True,
has_through_model=(
(getattr(relation.field.rel, 'through', None) is not None)
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
index 5c56c8db..603faf47 100644
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -206,7 +206,7 @@ class TestRegularFieldMappings(TestCase):
with self.assertRaises(ImproperlyConfigured) as excinfo:
TestSerializer().fields
- expected = 'Field name `invalid` is not valid for model `ModelBase`.'
+ expected = 'Field name `invalid` is not valid for model `RegularFieldsModel`.'
assert str(excinfo.exception) == expected
def test_missing_field(self):
--
cgit v1.2.3
From 77e3021fea3e30382b9770eac25371495e0b156b Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sat, 20 Dec 2014 16:26:51 +0000
Subject: Better behaviour with null and '' for blank HTML fields.
---
rest_framework/fields.py | 13 +++++--------
tests/test_fields.py | 22 +++++++++++++++-------
2 files changed, 20 insertions(+), 15 deletions(-)
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index c40dc3fb..aab80982 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -273,7 +273,11 @@ class Field(object):
return empty
return self.default_empty_html
ret = dictionary[self.field_name]
- return self.default_empty_html if (ret == '') else ret
+ if ret == '' and self.allow_null:
+ # If the field is blank, and null is a valid value then
+ # determine if we should use null instead.
+ return '' if getattr(self, 'allow_blank', False) else None
+ return ret
return dictionary.get(self.field_name, empty)
def get_attribute(self, instance):
@@ -545,8 +549,6 @@ class CharField(Field):
'min_length': _('Ensure this field has at least {min_length} characters.')
}
initial = ''
- coerce_blank_to_null = False
- default_empty_html = ''
def __init__(self, **kwargs):
self.allow_blank = kwargs.pop('allow_blank', False)
@@ -560,11 +562,6 @@ class CharField(Field):
message = self.error_messages['min_length'].format(min_length=min_length)
self.validators.append(MinLengthValidator(min_length, message=message))
- if self.allow_null and (not self.allow_blank) and (self.default is empty):
- # HTML input cannot represent `None` values, so we need to
- # forcibly coerce empty HTML values to `None` if `allow_null=True`.
- self.default_empty_html = None
-
def run_validation(self, data=empty):
# Test for the empty string here so that it does not get validated,
# and so that subclasses do not need to handle it explicitly
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 04c721d3..775d4618 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -223,8 +223,8 @@ class MockHTMLDict(dict):
getlist = None
-class TestCharHTMLInput:
- def test_empty_html_checkbox(self):
+class TestHTMLInput:
+ def test_empty_html_charfield(self):
class TestSerializer(serializers.Serializer):
message = serializers.CharField(default='happy')
@@ -232,23 +232,31 @@ class TestCharHTMLInput:
assert serializer.is_valid()
assert serializer.validated_data == {'message': 'happy'}
- def test_empty_html_checkbox_allow_null(self):
+ def test_empty_html_charfield_allow_null(self):
class TestSerializer(serializers.Serializer):
message = serializers.CharField(allow_null=True)
- serializer = TestSerializer(data=MockHTMLDict())
+ serializer = TestSerializer(data=MockHTMLDict({'message': ''}))
assert serializer.is_valid()
assert serializer.validated_data == {'message': None}
- def test_empty_html_checkbox_allow_null_allow_blank(self):
+ def test_empty_html_datefield_allow_null(self):
+ class TestSerializer(serializers.Serializer):
+ expiry = serializers.DateField(allow_null=True)
+
+ serializer = TestSerializer(data=MockHTMLDict({'expiry': ''}))
+ assert serializer.is_valid()
+ assert serializer.validated_data == {'expiry': None}
+
+ def test_empty_html_charfield_allow_null_allow_blank(self):
class TestSerializer(serializers.Serializer):
message = serializers.CharField(allow_null=True, allow_blank=True)
- serializer = TestSerializer(data=MockHTMLDict({}))
+ serializer = TestSerializer(data=MockHTMLDict({'message': ''}))
assert serializer.is_valid()
assert serializer.validated_data == {'message': ''}
- def test_empty_html_required_false(self):
+ def test_empty_html_charfield_required_false(self):
class TestSerializer(serializers.Serializer):
message = serializers.CharField(required=False)
--
cgit v1.2.3
From 03c4eb11305dcc9f366cdd008a5985bcf47c13ce Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sat, 20 Dec 2014 16:32:07 +0000
Subject: Use custom ListSerializer for pagination if one is specified on the
serializer.
---
rest_framework/pagination.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index fb451285..f46b0dfa 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -68,7 +68,12 @@ class BasePaginationSerializer(serializers.Serializer):
except AttributeError:
object_serializer = DefaultObjectSerializer
- self.fields[results_field] = serializers.ListSerializer(
+ try:
+ list_serializer_class = object_serializer.Meta.list_serializer_class
+ except AttributeError:
+ list_serializer_class = serializers.ListSerializer
+
+ self.fields[results_field] = list_serializer_class(
child=object_serializer(),
source='object_list'
)
--
cgit v1.2.3
From 35696748603665526be7947e918d41856644ec52 Mon Sep 17 00:00:00 2001
From: Brian Stearns
Date: Sun, 21 Dec 2014 18:53:35 -0500
Subject: use of double quotes broke the code highlighting.
---
docs/api-guide/fields.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index f06db56c..946e355d 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -480,7 +480,7 @@ Let's look at an example of serializing a class that represents an RGB color val
class ColorField(serializers.Field):
"""
- Color objects are serialized into "rgb(#, #, #)" notation.
+ Color objects are serialized into 'rgb(#, #, #)' notation.
"""
def to_representation(self, obj):
return "rgb(%d, %d, %d)" % (obj.red, obj.green, obj.blue)
--
cgit v1.2.3
From 6c5ff712783ae7e6edebb52508f1d43249f1aa00 Mon Sep 17 00:00:00 2001
From: Remi Paulmier
Date: Mon, 22 Dec 2014 18:05:07 +0100
Subject: fix the way to use textarea rather than input with models.TextField
---
rest_framework/utils/field_mapping.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py
index fca97b4b..b16e9df0 100644
--- a/rest_framework/utils/field_mapping.py
+++ b/rest_framework/utils/field_mapping.py
@@ -80,7 +80,7 @@ def get_field_kwargs(field_name, model_field):
kwargs['decimal_places'] = decimal_places
if isinstance(model_field, models.TextField):
- kwargs['style'] = {'type': 'textarea'}
+ kwargs['style'] = {'base_template': 'textarea.html'}
if isinstance(model_field, models.AutoField) or not model_field.editable:
# If this field is read-only, then return early.
--
cgit v1.2.3
From 18687f075d9fb998b82c6fb8f6cb37eb1ed7e5bf Mon Sep 17 00:00:00 2001
From: Matías Lang
Date: Tue, 23 Dec 2014 12:22:10 -0300
Subject: Documented an optional argument of HyperlinkedIdentityField
lookup_url_kwarg argument of HyperlinkedIdentityField wasn't documented---
docs/api-guide/relations.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md
index e56db229..50e3b7b5 100644
--- a/docs/api-guide/relations.md
+++ b/docs/api-guide/relations.md
@@ -231,6 +231,7 @@ This field is always read-only.
* `view_name` - The view name that should be used as the target of the relationship. If you're using [the standard router classes][routers] this will be a string with the format `-detail`. **required**.
* `lookup_field` - The field on the target that should be used for the lookup. Should correspond to a URL keyword argument on the referenced view. Default is `'pk'`.
+* `lookup_url_kwarg` - The name of the keyword argument defined in the URL conf that corresponds to the lookup field. Defaults to using the same value as `lookup_field`.
* `format` - If using format suffixes, hyperlinked fields will use the same format suffix for the target unless overridden by using the `format` argument.
---
--
cgit v1.2.3
From 399cb165b0ba26df6052c114eb77961dc578e686 Mon Sep 17 00:00:00 2001
From: Andrew Seier
Date: Tue, 23 Dec 2014 12:11:45 -0800
Subject: Remove commented code (warning during compression)
manage.py compress —force causes a warning here.---
rest_framework/templates/rest_framework/vertical/list_fieldset.html | 4 ----
1 file changed, 4 deletions(-)
diff --git a/rest_framework/templates/rest_framework/vertical/list_fieldset.html b/rest_framework/templates/rest_framework/vertical/list_fieldset.html
index 1d86c7f2..82d7b5f4 100644
--- a/rest_framework/templates/rest_framework/vertical/list_fieldset.html
+++ b/rest_framework/templates/rest_framework/vertical/list_fieldset.html
@@ -1,8 +1,4 @@
--
cgit v1.2.3
From 35768344db45b9fa6bd94c3fd48d5e232027434e Mon Sep 17 00:00:00 2001
From: Andrew Seier
Date: Tue, 23 Dec 2014 12:12:22 -0800
Subject: Remove ‘/‘ from inside variable block {{ }}
manage.py compress —force causes a warning here.---
rest_framework/templates/rest_framework/inline/checkbox_multiple.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rest_framework/templates/rest_framework/inline/checkbox_multiple.html b/rest_framework/templates/rest_framework/inline/checkbox_multiple.html
index 6caf6440..09349686 100644
--- a/rest_framework/templates/rest_framework/inline/checkbox_multiple.html
+++ b/rest_framework/templates/rest_framework/inline/checkbox_multiple.html
@@ -5,7 +5,7 @@
{% for key, text in field.choices.items %}
--
cgit v1.2.3
From b32ecdefbace063c5b9b465af608ac6404795dd4 Mon Sep 17 00:00:00 2001
From: Remi Paulmier
Date: Wed, 24 Dec 2014 14:07:28 +0100
Subject: modified the tests accordingly
---
tests/test_model_serializer.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
index da79164a..ee556dbc 100644
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -119,7 +119,7 @@ class TestRegularFieldMappings(TestCase):
positive_small_integer_field = IntegerField()
slug_field = SlugField(max_length=100)
small_integer_field = IntegerField()
- text_field = CharField(style={'type': 'textarea'})
+ text_field = CharField(style={'base_template': 'textarea.html'})
time_field = TimeField()
url_field = URLField(max_length=100)
custom_field = ModelField(model_field=)
--
cgit v1.2.3
From c2e00a075cb4b44c644ad5d62f2be0fd19e62c5f Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 26 Dec 2014 15:25:13 +0000
Subject: Paginated serializers should get context.
---
rest_framework/pagination.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index f46b0dfa..f31e5fa4 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -77,6 +77,7 @@ class BasePaginationSerializer(serializers.Serializer):
child=object_serializer(),
source='object_list'
)
+ self.fields[results_field].bind(field_name=results_field, parent=self)
class PaginationSerializer(BasePaginationSerializer):
--
cgit v1.2.3
From 00531ec937206e7e0af949c67872c915d0752b5a Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 26 Dec 2014 15:48:16 +0000
Subject: Release notes on non-text detail arguments. Closes #2341.
---
docs/topics/3.0-announcement.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md
index 0710766f..68d24782 100644
--- a/docs/topics/3.0-announcement.md
+++ b/docs/topics/3.0-announcement.md
@@ -940,6 +940,7 @@ The default JSON renderer will return float objects for un-coerced `Decimal` ins
* The serializer `ChoiceField` does not currently display nested choices, as was the case in 2.4. This will be address as part of 3.1.
* Due to the new templated form rendering, the 'widget' option is no longer valid. This means there's no easy way of using third party "autocomplete" widgets for rendering select inputs that contain a large number of choices. You'll either need to use a regular select or a plain text input. We may consider addressing this in 3.1 or 3.2 if there's sufficient demand.
* Some of the default validation error messages were rewritten and might no longer be pre-translated. You can still [create language files with Django][django-localization] if you wish to localize them.
+* `APIException` subclasses could previously take could previously take any arbitrary type in the `detail` argument. These exceptions now use translatable text strings, and as a result call `force_text` on the `detail` argument, which *must be a string*. If you need complex arguments to an `APIException` class, you should subclass it and override the `__init__()` method. Typically you'll instead want to use a custom exception handler to provide for non-standard error responses.
---
--
cgit v1.2.3
From 5b5652594a9c000d8e925d35efa03be27c28c077 Mon Sep 17 00:00:00 2001
From: Rocky Meza
Date: Fri, 26 Dec 2014 22:24:31 -0700
Subject: Typo manger => manager
---
docs/api-guide/serializers.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index b9f0e7bc..f88ec51f 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -384,7 +384,7 @@ This manager class now more nicely encapsulates that user instances and profile
has_support_contract=validated_data['profile']['has_support_contract']
)
-For more details on this approach see the Django documentation on [model managers](model-managers), and [this blogpost on using model and manger classes](encapsulation-blogpost).
+For more details on this approach see the Django documentation on [model managers](model-managers), and [this blogpost on using model and manager classes](encapsulation-blogpost).
## Dealing with multiple objects
--
cgit v1.2.3
From a636320ff3b381a6d7d8685f1b4fba8bdd6c8b94 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sun, 28 Dec 2014 11:02:19 +0000
Subject: Add import notes in docs. Closes #2357
---
docs/api-guide/generic-views.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md
index f5bbdfdd..6374e305 100755
--- a/docs/api-guide/generic-views.md
+++ b/docs/api-guide/generic-views.md
@@ -214,6 +214,8 @@ You won't typically need to override the following methods, although you might n
The mixin classes provide the actions that are used to provide the basic view behavior. Note that the mixin classes provide action methods rather than defining the handler methods, such as `.get()` and `.post()`, directly. This allows for more flexible composition of behavior.
+The mixin classes can be imported from `rest_framework.mixins`.
+
## ListModelMixin
Provides a `.list(request, *args, **kwargs)` method, that implements listing a queryset.
@@ -258,6 +260,8 @@ If an object is deleted this returns a `204 No Content` response, otherwise it w
The following classes are the concrete generic views. If you're using generic views this is normally the level you'll be working at unless you need heavily customized behavior.
+The view classes can be imported from `rest_framework.generics`.
+
## CreateAPIView
Used for **create-only** endpoints.
--
cgit v1.2.3
From ef2eff2abac64ffbed621bb9a72a2229841a1db1 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sun, 28 Dec 2014 11:07:38 +0000
Subject: Only pass max_length for CharField. Closes #2317.
---
rest_framework/utils/field_mapping.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py
index b16e9df0..b2f4dd80 100644
--- a/rest_framework/utils/field_mapping.py
+++ b/rest_framework/utils/field_mapping.py
@@ -106,7 +106,7 @@ def get_field_kwargs(field_name, model_field):
# Ensure that max_length is passed explicitly as a keyword arg,
# rather than as a validator.
max_length = getattr(model_field, 'max_length', None)
- if max_length is not None:
+ if max_length is not None and isinstance(model_field, models.CharField):
kwargs['max_length'] = max_length
validator_kwarg = [
validator for validator in validator_kwarg
--
cgit v1.2.3
From 7b42c5ed17a2430d66da88932ad4e81492d9b914 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sun, 28 Dec 2014 11:14:32 +0000
Subject: Remove broken test. Closes #2359.
---
tests/test_routers.py | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/tests/test_routers.py b/tests/test_routers.py
index 06ab8103..2b6cd7d2 100644
--- a/tests/test_routers.py
+++ b/tests/test_routers.py
@@ -305,19 +305,3 @@ class TestDynamicListAndDetailRouter(TestCase):
else:
method_map = 'get'
self.assertEqual(route.mapping[method_map], method_name)
-
-
-class TestRootWithAListlessViewset(TestCase):
- def setUp(self):
- class NoteViewSet(mixins.RetrieveModelMixin,
- viewsets.GenericViewSet):
- model = RouterTestModel
-
- self.router = DefaultRouter()
- self.router.register(r'notes', NoteViewSet)
- self.view = self.router.urls[0].callback
-
- def test_api_root(self):
- request = factory.get('/')
- response = self.view(request)
- self.assertEqual(response.data, {})
--
cgit v1.2.3
From 8dc95ee22181de6e38c7187426bca9fcee9d7927 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sun, 28 Dec 2014 11:24:49 +0000
Subject: Add notes on include and namespacing. Closes #2335.
---
docs/api-guide/routers.md | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md
index 6819adb6..3a8a8f6c 100644
--- a/docs/api-guide/routers.md
+++ b/docs/api-guide/routers.md
@@ -49,6 +49,38 @@ This means you'll need to explicitly set the `base_name` argument when registeri
---
+### Using `include` with routers
+
+The `.urls` attribute on a router instance is simply a standard list of URL patterns. There are a number of different styles for how you can include these URLs.
+
+For example, you can append `router.urls` to a list of existing views…
+
+ router = routers.SimpleRouter()
+ router.register(r'users', UserViewSet)
+ router.register(r'accounts', AccountViewSet)
+
+ urlpatterns = [
+ url(r'^forgot-password/$, ForgotPasswordFormView.as_view(),
+ ]
+
+ urlpatterns += router.urls
+
+Alternatively you can use Django's `include` function, like so…
+
+ urlpatterns = [
+ url(r'^forgot-password/$, ForgotPasswordFormView.as_view(),
+ url(r'^', include(router.urls))
+ ]
+
+Router URL patterns can also be namespaces.
+
+ urlpatterns = [
+ url(r'^forgot-password/$, ForgotPasswordFormView.as_view(),
+ url(r'^api/', include(router.urls, namespace='api'))
+ ]
+
+If using namespacing with hyperlinked serializers you'll also need to ensure that any `view_name` parameters on the serializers correctly reflect the namespace. In the example above you'd need to include a parameter such as `view_name='api:user-detail'` for serializer fields hyperlinked to the user detail view.
+
### Extra link and actions
Any methods on the viewset decorated with `@detail_route` or `@list_route` will also be routed.
--
cgit v1.2.3
From 67fc002f91e5dc617dab45895ded32d6be6c2a40 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sun, 28 Dec 2014 11:26:38 +0000
Subject: Drop unused import
---
tests/test_routers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/test_routers.py b/tests/test_routers.py
index 2b6cd7d2..fc22a8d9 100644
--- a/tests/test_routers.py
+++ b/tests/test_routers.py
@@ -3,7 +3,7 @@ from django.conf.urls import patterns, url, include
from django.db import models
from django.test import TestCase
from django.core.exceptions import ImproperlyConfigured
-from rest_framework import serializers, viewsets, mixins, permissions
+from rest_framework import serializers, viewsets, permissions
from rest_framework.decorators import detail_route, list_route
from rest_framework.response import Response
from rest_framework.routers import SimpleRouter, DefaultRouter
--
cgit v1.2.3
From efa5942ce1c5d2286fd91994b52fb73a5690426c Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sun, 28 Dec 2014 12:02:52 +0000
Subject: Support namespaced router URLs with DefaultRouter.
---
rest_framework/compat.py | 10 +++++
rest_framework/routers.py | 5 ++-
tests/test_routers.py | 94 +++++++++++++++++++++++++++++++----------------
3 files changed, 76 insertions(+), 33 deletions(-)
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index 69fdd793..ba26a3cd 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -50,6 +50,16 @@ except ImportError:
from django.http import HttpResponse as HttpResponseBase
+# request only provides `resolver_match` from 1.5 onwards.
+def get_resolver_match(request):
+ try:
+ return request.resolver_match
+ except AttributeError:
+ # Django < 1.5
+ from django.core.urlresolvers import resolve
+ return resolve(request.path_info)
+
+
# django-filter is optional
try:
import django_filters
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index 1cb65b1c..61f3ccab 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -21,7 +21,7 @@ from django.conf.urls import patterns, url
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import NoReverseMatch
from rest_framework import views
-from rest_framework.compat import OrderedDict
+from rest_framework.compat import get_resolver_match, OrderedDict
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.urlpatterns import format_suffix_patterns
@@ -292,7 +292,10 @@ class DefaultRouter(SimpleRouter):
def get(self, request, *args, **kwargs):
ret = OrderedDict()
+ namespace = get_resolver_match(request).namespace
for key, url_name in api_root_dict.items():
+ if namespace:
+ url_name = namespace + ':' + url_name
try:
ret[key] = reverse(
url_name,
diff --git a/tests/test_routers.py b/tests/test_routers.py
index fc22a8d9..86113f5d 100644
--- a/tests/test_routers.py
+++ b/tests/test_routers.py
@@ -1,5 +1,5 @@
from __future__ import unicode_literals
-from django.conf.urls import patterns, url, include
+from django.conf.urls import url, include
from django.db import models
from django.test import TestCase
from django.core.exceptions import ImproperlyConfigured
@@ -12,7 +12,42 @@ from collections import namedtuple
factory = APIRequestFactory()
-urlpatterns = patterns('',)
+
+class RouterTestModel(models.Model):
+ uuid = models.CharField(max_length=20)
+ text = models.CharField(max_length=200)
+
+
+class NoteSerializer(serializers.HyperlinkedModelSerializer):
+ url = serializers.HyperlinkedIdentityField(view_name='routertestmodel-detail', lookup_field='uuid')
+
+ class Meta:
+ model = RouterTestModel
+ fields = ('url', 'uuid', 'text')
+
+
+class NoteViewSet(viewsets.ModelViewSet):
+ queryset = RouterTestModel.objects.all()
+ serializer_class = NoteSerializer
+ lookup_field = 'uuid'
+
+
+class MockViewSet(viewsets.ModelViewSet):
+ queryset = None
+ serializer_class = None
+
+
+notes_router = SimpleRouter()
+notes_router.register(r'notes', NoteViewSet)
+
+namespaced_router = DefaultRouter()
+namespaced_router.register(r'example', MockViewSet, base_name='example')
+
+urlpatterns = [
+ url(r'^non-namespaced/', include(namespaced_router.urls)),
+ url(r'^namespaced/', include(namespaced_router.urls, namespace='example')),
+ url(r'^example/', include(notes_router.urls)),
+]
class BasicViewSet(viewsets.ViewSet):
@@ -64,9 +99,26 @@ class TestSimpleRouter(TestCase):
self.assertEqual(route.mapping[method], endpoint)
-class RouterTestModel(models.Model):
- uuid = models.CharField(max_length=20)
- text = models.CharField(max_length=200)
+class TestRootView(TestCase):
+ urls = 'tests.test_routers'
+
+ def test_retrieve_namespaced_root(self):
+ response = self.client.get('/namespaced/')
+ self.assertEqual(
+ response.data,
+ {
+ "example": "http://testserver/namespaced/example/",
+ }
+ )
+
+ def test_retrieve_non_namespaced_root(self):
+ response = self.client.get('/non-namespaced/')
+ self.assertEqual(
+ response.data,
+ {
+ "example": "http://testserver/non-namespaced/example/",
+ }
+ )
class TestCustomLookupFields(TestCase):
@@ -76,51 +128,29 @@ class TestCustomLookupFields(TestCase):
urls = 'tests.test_routers'
def setUp(self):
- class NoteSerializer(serializers.HyperlinkedModelSerializer):
- url = serializers.HyperlinkedIdentityField(view_name='routertestmodel-detail', lookup_field='uuid')
-
- class Meta:
- model = RouterTestModel
- fields = ('url', 'uuid', 'text')
-
- class NoteViewSet(viewsets.ModelViewSet):
- queryset = RouterTestModel.objects.all()
- serializer_class = NoteSerializer
- lookup_field = 'uuid'
-
- self.router = SimpleRouter()
- self.router.register(r'notes', NoteViewSet)
-
- from tests import test_routers
- urls = getattr(test_routers, 'urlpatterns')
- urls += patterns(
- '',
- url(r'^', include(self.router.urls)),
- )
-
RouterTestModel.objects.create(uuid='123', text='foo bar')
def test_custom_lookup_field_route(self):
- detail_route = self.router.urls[-1]
+ detail_route = notes_router.urls[-1]
detail_url_pattern = detail_route.regex.pattern
self.assertIn('', detail_url_pattern)
def test_retrieve_lookup_field_list_view(self):
- response = self.client.get('/notes/')
+ response = self.client.get('/example/notes/')
self.assertEqual(
response.data,
[{
- "url": "http://testserver/notes/123/",
+ "url": "http://testserver/example/notes/123/",
"uuid": "123", "text": "foo bar"
}]
)
def test_retrieve_lookup_field_detail_view(self):
- response = self.client.get('/notes/123/')
+ response = self.client.get('/example/notes/123/')
self.assertEqual(
response.data,
{
- "url": "http://testserver/notes/123/",
+ "url": "http://testserver/example/notes/123/",
"uuid": "123", "text": "foo bar"
}
)
--
cgit v1.2.3
From d8e66970a11ec2d4b66f0cf56950f2cc83e83224 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sun, 28 Dec 2014 12:14:07 +0000
Subject: Note on using i18n_patterns with format_suffix_patterns. Closes
#2278.
---
docs/api-guide/format-suffixes.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/docs/api-guide/format-suffixes.md b/docs/api-guide/format-suffixes.md
index 20c1e995..35dbcd39 100644
--- a/docs/api-guide/format-suffixes.md
+++ b/docs/api-guide/format-suffixes.md
@@ -55,6 +55,18 @@ The name of the kwarg used may be modified by using the `FORMAT_SUFFIX_KWARG` se
Also note that `format_suffix_patterns` does not support descending into `include` URL patterns.
+### Using with `i18n_patterns`
+
+If using the `i18n_patterns` function provided by Django, as well as `format_suffix_patterns` you should make sure that the `i18n_patterns` function is applied as the final, or outermost function. For example:
+
+ url patterns = [
+ …
+ ]
+
+ urlpatterns = i18n_patterns(
+ format_suffix_patterns(urlpatterns, allowed=['json', 'html'])
+ )
+
---
## Accept headers vs. format suffixes
--
cgit v1.2.3
From 5d8c45681a945b955d9336b0fd1e4ebccf0df895 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sun, 28 Dec 2014 18:48:42 +0000
Subject: Update copryright for 2015. Closes #2247.
---
README.md | 2 +-
docs/index.md | 2 +-
rest_framework/__init__.py | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index df0a4086..8fc11c30 100644
--- a/README.md
+++ b/README.md
@@ -156,7 +156,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
# License
-Copyright (c) 2011-2014, Tom Christie
+Copyright (c) 2011-2015, Tom Christie
All rights reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/docs/index.md b/docs/index.md
index 8a96fc9f..55129df1 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -235,7 +235,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
## License
-Copyright (c) 2011-2014, Tom Christie
+Copyright (c) 2011-2015, Tom Christie
All rights reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index 6808b74b..dec89b3e 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -11,7 +11,7 @@ __title__ = 'Django REST framework'
__version__ = '3.0.2'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
-__copyright__ = 'Copyright 2011-2014 Tom Christie'
+__copyright__ = 'Copyright 2011-2015 Tom Christie'
# Version synonym
VERSION = __version__
--
cgit v1.2.3
From a7479721c844926f377085d8c336a2f60b7b2a38 Mon Sep 17 00:00:00 2001
From: Kyle Valade
Date: Mon, 29 Dec 2014 00:35:00 -0800
Subject: First pass at refactoring get_field_info in utils.model_meta
---
rest_framework/utils/model_meta.py | 57 ++++++++++++++++++++++++++++----------
1 file changed, 43 insertions(+), 14 deletions(-)
diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py
index c98725c6..375d2e8c 100644
--- a/rest_framework/utils/model_meta.py
+++ b/rest_framework/utils/model_meta.py
@@ -35,7 +35,7 @@ def _resolve_model(obj):
Resolve supplied `obj` to a Django model class.
`obj` must be a Django model class itself, or a string
- representation of one. Useful in situtations like GH #1225 where
+ representation of one. Useful in situations like GH #1225 where
Django may not have resolved a string-based reference to a model in
another model's foreign key definition.
@@ -56,23 +56,44 @@ def _resolve_model(obj):
def get_field_info(model):
"""
- Given a model class, returns a `FieldInfo` instance containing metadata
- about the various field types on the model.
+ Given a model class, returns a `FieldInfo` instance, which is a
+ `namedtuple`, containing metadata about the various field types on the model
+ including information about their relationships.
"""
opts = model._meta.concrete_model._meta
- # Deal with the primary key.
+ pk = _get_pk(opts)
+ fields = _get_fields(opts)
+ forward_relations = _get_forward_relationships(opts)
+ reverse_relations = _get_reverse_relationships(opts)
+ fields_and_pk = _merge_fields_and_pk(pk, fields)
+ relationships = _merge_relationships(forward_relations, reverse_relations)
+
+ return FieldInfo(pk, fields, forward_relations, reverse_relations,
+ fields_and_pk, relationships)
+
+
+def _get_pk(opts):
pk = opts.pk
while pk.rel and pk.rel.parent_link:
- # If model is a child via multitable inheritance, use parent's pk.
+ # If model is a child via multi-table inheritance, use parent's pk.
pk = pk.rel.to._meta.pk
- # Deal with regular fields.
+ return pk
+
+
+def _get_fields(opts):
fields = OrderedDict()
for field in [field for field in opts.fields if field.serialize and not field.rel]:
fields[field.name] = field
- # Deal with forward relationships.
+ return fields
+
+
+def _get_forward_relationships(opts):
+ """
+ Returns an `OrderedDict` of field names to `RelationInfo`.
+ """
forward_relations = OrderedDict()
for field in [field for field in opts.fields if field.serialize and field.rel]:
forward_relations[field.name] = RelationInfo(
@@ -93,7 +114,13 @@ def get_field_info(model):
)
)
- # Deal with reverse relationships.
+ return forward_relations
+
+
+def _get_reverse_relationships(opts):
+ """
+ Returns an `OrderedDict` of field names to `RelationInfo`.
+ """
reverse_relations = OrderedDict()
for relation in opts.get_all_related_objects():
accessor_name = relation.get_accessor_name()
@@ -117,18 +144,20 @@ def get_field_info(model):
)
)
- # Shortcut that merges both regular fields and the pk,
- # for simplifying regular field lookup.
+ return reverse_relations
+
+
+def _merge_fields_and_pk(pk, fields):
fields_and_pk = OrderedDict()
fields_and_pk['pk'] = pk
fields_and_pk[pk.name] = pk
fields_and_pk.update(fields)
- # Shortcut that merges both forward and reverse relationships
+ return fields_and_pk
- relations = OrderedDict(
+
+def _merge_relationships(forward_relations, reverse_relations):
+ return OrderedDict(
list(forward_relations.items()) +
list(reverse_relations.items())
)
-
- return FieldInfo(pk, fields, forward_relations, reverse_relations, fields_and_pk, relations)
--
cgit v1.2.3
From 336faf5a861fad2e4364a68fbf0747bef4457358 Mon Sep 17 00:00:00 2001
From: Rob Terhaar
Date: Thu, 1 Jan 2015 21:01:16 -0500
Subject: fix widget style formatting
---
docs/tutorial/1-serialization.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index ff507a2b..60a3d989 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -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`.
-The field flags can also control how the serializer should be displayed in certain circumstances, such as when rendering to HTML. The `style={'type': 'textarea'}` flag above is equivelent to using `widget=widgets.Textarea` on a Django `Form` class. This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial.
+The field flags can also control how the serializer should be displayed in certain circumstances, such as when rendering to HTML. The `{'base_template': 'textarea.html'}` flag above is equivelent to using `widget=widgets.Textarea` on a Django `Form` class. This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial.
We can actually also save ourselves some time by using the `ModelSerializer` class, as we'll see later, but for now we'll keep our serializer definition explicit.
@@ -206,7 +206,7 @@ One nice property that serializers have is that you can inspect all the fields i
SnippetSerializer():
id = IntegerField(label='ID', read_only=True)
title = CharField(allow_blank=True, max_length=100, required=False)
- code = CharField(style={'type': 'textarea'})
+ code = CharField(style={'base_template': 'textarea.html'})
linenos = BooleanField(required=False)
language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
--
cgit v1.2.3
From 309b5d264166e07510d6cbc54d681268d07957aa Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Fri, 2 Jan 2015 11:07:35 +0000
Subject: instructions on how to translate REST framework error messages
---
docs/topics/internationalisation.md | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
create mode 100644 docs/topics/internationalisation.md
diff --git a/docs/topics/internationalisation.md b/docs/topics/internationalisation.md
new file mode 100644
index 00000000..01f96891
--- /dev/null
+++ b/docs/topics/internationalisation.md
@@ -0,0 +1,34 @@
+# Internationalisation
+REST framework ships with translatable error messages. You can make these appear in your language enabling [Django's standard translation mechanisms](https://docs.djangoproject.com/en/1.7/topics/i18n/translation) and by translating the messages into your language.
+
+## How to translate REST Framework errors
+
+
+This guide assumes you are already familiar with how to translate a Django app. If you're not, start by reading [Django's translation docs](https://docs.djangoproject.com/en/1.7/topics/i18n/translation).
+
+
+#### To translate REST framework error messages:
+
+1. Pick an app where you want the translations to be, for example `myapp`
+
+2. Add a symlink from that app to the installed `rest_framework`
+ ```
+ ln -s /home/user/.virtualenvs/myproject/lib/python2.7/site-packages/rest_framework/ rest_framework
+ ```
+
+ To find out where `rest_framework` is installed, run
+
+ ```
+ python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
+ ```
+
+3. Run Django's `makemessages` command in the normal way, but add the `--symlink` option. For example, if you want to translate into Brazilian Portuguese you would run
+ ```
+ manage.py makemessages --symlink -l pt_BR
+ ```
+
+4. Translate the `django.po` file which is created as normal. This will be in the folder `myapp/locale/pt_BR/LC_MESSAGES`.
+
+5. Run `manage.py compilemessages` as normal
+
+6. Restart your server
--
cgit v1.2.3
From 7ad7dd6a4292157ed5bcbaacb60b6ccc93fcf201 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 24 Dec 2014 18:26:17 +0000
Subject: match DRF style guide
---
docs/topics/internationalisation.md | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/docs/topics/internationalisation.md b/docs/topics/internationalisation.md
index 01f96891..552fdd27 100644
--- a/docs/topics/internationalisation.md
+++ b/docs/topics/internationalisation.md
@@ -1,10 +1,10 @@
# Internationalisation
-REST framework ships with translatable error messages. You can make these appear in your language enabling [Django's standard translation mechanisms](https://docs.djangoproject.com/en/1.7/topics/i18n/translation) and by translating the messages into your language.
+REST framework ships with translatable error messages. You can make these appear in your language enabling [Django's standard translation mechanisms][django-translation] and by translating the messages into your language.
## How to translate REST Framework errors
-This guide assumes you are already familiar with how to translate a Django app. If you're not, start by reading [Django's translation docs](https://docs.djangoproject.com/en/1.7/topics/i18n/translation).
+This guide assumes you are already familiar with how to translate a Django app. If you're not, start by reading [Django's translation docs][django-translation].
#### To translate REST framework error messages:
@@ -16,19 +16,28 @@ This guide assumes you are already familiar with how to translate a Django app.
ln -s /home/user/.virtualenvs/myproject/lib/python2.7/site-packages/rest_framework/ rest_framework
```
- To find out where `rest_framework` is installed, run
+ ---
+
+ **Note:** To find out where `rest_framework` is installed, run
```
python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
```
-3. Run Django's `makemessages` command in the normal way, but add the `--symlink` option. For example, if you want to translate into Brazilian Portuguese you would run
+ ---
+
+
+
+3. Run Django's `makemessages` command in the normal way, but add the `--symlink` option. For example, if you want to translate into Brazilian Portuguese you would run
```
manage.py makemessages --symlink -l pt_BR
```
-4. Translate the `django.po` file which is created as normal. This will be in the folder `myapp/locale/pt_BR/LC_MESSAGES`.
+4. Translate the `django.po` file which is created as normal. This will be in the folder `myapp/locale/pt_BR/LC_MESSAGES`.
5. Run `manage.py compilemessages` as normal
6. Restart your server
+
+
+[django-translation]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation
--
cgit v1.2.3
From 2781903a5a0be7f5314de54ff2dbc8ef393eab0a Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Fri, 26 Dec 2014 12:52:17 +0000
Subject: Add info about how django chooses which language to use
---
docs/topics/internationalisation.md | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/docs/topics/internationalisation.md b/docs/topics/internationalisation.md
index 552fdd27..a0aab753 100644
--- a/docs/topics/internationalisation.md
+++ b/docs/topics/internationalisation.md
@@ -40,4 +40,22 @@ This guide assumes you are already familiar with how to translate a Django app.
6. Restart your server
+
+## How Django chooses which language to use
+REST framework will use the same preferences to select which language to display as Django does. You can find more info in the [django docs on discovering language preferences][django-language-preference]. For reference, these are
+
+1. First, it looks for the language prefix in the requested URL
+2. Failing that, it looks for the `LANGUAGE_SESSION_KEY` key in the current user’s session.
+3. Failing that, it looks for a cookie
+4. Failing that, it looks at the `Accept-Language` HTTP header.
+5. Failing that, it uses the global `LANGUAGE_CODE` setting.
+
+---
+
+**Note:** You'll need to include the `django.middleware.locale.LocaleMiddleware` to enable any of the per-request language preferences.
+
+---
+
+
[django-translation]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation
+[django-language-preference]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#how-django-discovers-language-preference
--
cgit v1.2.3
From 0b8a83bd624673cb0a05e01c691729ccee3a8782 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Sun, 28 Dec 2014 18:20:41 +0000
Subject: update internationalisation instructions to prevent symlinking; add
base .po file
---
docs/topics/internationalisation.md | 52 +++-
rest_framework/locale/en_US/LC_MESSAGES/django.po | 277 ++++++++++++++++++++++
2 files changed, 318 insertions(+), 11 deletions(-)
create mode 100644 rest_framework/locale/en_US/LC_MESSAGES/django.po
diff --git a/docs/topics/internationalisation.md b/docs/topics/internationalisation.md
index a0aab753..6470ee03 100644
--- a/docs/topics/internationalisation.md
+++ b/docs/topics/internationalisation.md
@@ -9,13 +9,41 @@ This guide assumes you are already familiar with how to translate a Django app.
#### To translate REST framework error messages:
-1. Pick an app where you want the translations to be, for example `myapp`
+1. Make a new folder where you want to store the translated errors. Add this
+path to your [`LOCALE_PATHS`][django-locale-paths] setting.
+
+ ---
+
+ **Note:** For the rest of
+this document we will assume the path you created was
+`/home/www/project/conf/locale/`, and that you have updated your `settings.py` to include the setting:
+
+ ```
+ LOCALE_PATHS = (
+ '/home/www/project/conf/locale/',
+ )
+ ```
+
+ ---
+
+2. Now create a subfolder for the language you want to translate. The folder should be named using [locale
+name][django-locale-name] notation. E.g. `de`, `pt_BR`, `es_AR`, etc.
-2. Add a symlink from that app to the installed `rest_framework`
```
- ln -s /home/user/.virtualenvs/myproject/lib/python2.7/site-packages/rest_framework/ rest_framework
+ mkdir /home/www/project/conf/locale/pt_BR/LC_MESSAGES
+ ```
+
+3. Now copy the base translations file from the REST framework source code
+into your translations folder
+
+ ```
+ cp /home/user/.virtualenvs/myproject/lib/python2.7/site-packages/rest_framework/locale/en_US/LC_MESSAGES/django.po
+ /home/www/project/conf/locale/pt_BR/LC_MESSAGES
```
+ This should create the file
+ `/home/www/project/conf/locale/pt_BR/LC_MESSAGES/django.po`
+
---
**Note:** To find out where `rest_framework` is installed, run
@@ -27,17 +55,17 @@ This guide assumes you are already familiar with how to translate a Django app.
---
+4. Edit `/home/www/project/conf/locale/pt_BR/LC_MESSAGES/django.po` and
+translate all the error messages.
-3. Run Django's `makemessages` command in the normal way, but add the `--symlink` option. For example, if you want to translate into Brazilian Portuguese you would run
- ```
- manage.py makemessages --symlink -l pt_BR
- ```
-
-4. Translate the `django.po` file which is created as normal. This will be in the folder `myapp/locale/pt_BR/LC_MESSAGES`.
+5. Run `manage.py compilemessages -l pt_BR` to make the translations
+available for Django to use. You should see a message
-5. Run `manage.py compilemessages` as normal
+ ```
+ processing file django.po in /home/www/project/conf/locale/pt_BR/LC_MESSAGES
+ ```
-6. Restart your server
+6. Restart your server.
@@ -59,3 +87,5 @@ REST framework will use the same preferences to select which language to display
[django-translation]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation
[django-language-preference]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#how-django-discovers-language-preference
+[django-locale-paths]: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-LOCALE_PATHS
+[django-locale-name]: https://docs.djangoproject.com/en/1.7/topics/i18n/#term-locale-name
\ No newline at end of file
diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.po b/rest_framework/locale/en_US/LC_MESSAGES/django.po
new file mode 100644
index 00000000..510ce0aa
--- /dev/null
+++ b/rest_framework/locale/en_US/LC_MESSAGES/django.po
@@ -0,0 +1,277 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-12-28 17:49+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: rest_framework/authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr ""
+
+#: rest_framework/authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr ""
+
+#: rest_framework/authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\""
+msgstr ""
+
+#: rest_framework/exceptions.py:39
+msgid "A server error occured"
+msgstr ""
+
+#: rest_framework/exceptions.py:74
+msgid "Malformed request."
+msgstr ""
+
+#: rest_framework/exceptions.py:79
+msgid "Incorrect authentication credentials."
+msgstr ""
+
+#: rest_framework/exceptions.py:84
+msgid "Authentication credentials were not provided."
+msgstr ""
+
+#: rest_framework/exceptions.py:89
+msgid "You do not have permission to perform this action."
+msgstr ""
+
+#: rest_framework/exceptions.py:94
+#, python-format
+msgid "Method '%s' not allowed."
+msgstr ""
+
+#: rest_framework/exceptions.py:105
+msgid "Could not satisfy the request Accept header"
+msgstr ""
+
+#: rest_framework/exceptions.py:117
+#, python-format
+msgid "Unsupported media type '%s' in request."
+msgstr ""
+
+#: rest_framework/exceptions.py:128
+msgid "Request was throttled."
+msgstr ""
+
+#: rest_framework/exceptions.py:130
+#, python-format
+msgid "Expected available in %(wait)d second."
+msgid_plural "Expected available in %(wait)d seconds."
+msgstr[0] ""
+msgstr[1] ""
+
+#: rest_framework/fields.py:152 rest_framework/relations.py:131
+#: rest_framework/relations.py:155 rest_framework/validators.py:77
+#: rest_framework/validators.py:155
+msgid "This field is required."
+msgstr ""
+
+#: rest_framework/fields.py:153
+msgid "This field may not be null."
+msgstr ""
+
+#: rest_framework/fields.py:484 rest_framework/fields.py:512
+msgid "`{input}` is not a valid boolean."
+msgstr ""
+
+#: rest_framework/fields.py:547
+msgid "This field may not be blank."
+msgstr ""
+
+#: rest_framework/fields.py:548 rest_framework/fields.py:1250
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr ""
+
+#: rest_framework/fields.py:549
+msgid "Ensure this field has at least {min_length} characters."
+msgstr ""
+
+#: rest_framework/fields.py:584
+msgid "Enter a valid email address."
+msgstr ""
+
+#: rest_framework/fields.py:601
+msgid "This value does not match the required pattern."
+msgstr ""
+
+#: rest_framework/fields.py:612
+msgid ""
+"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
+msgstr ""
+
+#: rest_framework/fields.py:624
+msgid "Enter a valid URL."
+msgstr ""
+
+#: rest_framework/fields.py:637
+msgid "A valid integer is required."
+msgstr ""
+
+#: rest_framework/fields.py:638 rest_framework/fields.py:672
+#: rest_framework/fields.py:705
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr ""
+
+#: rest_framework/fields.py:639 rest_framework/fields.py:673
+#: rest_framework/fields.py:706
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr ""
+
+#: rest_framework/fields.py:640 rest_framework/fields.py:674
+#: rest_framework/fields.py:710
+msgid "String value too large"
+msgstr ""
+
+#: rest_framework/fields.py:671 rest_framework/fields.py:704
+msgid "A valid number is required."
+msgstr ""
+
+#: rest_framework/fields.py:707
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr ""
+
+#: rest_framework/fields.py:708
+msgid "Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr ""
+
+#: rest_framework/fields.py:709
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr ""
+
+#: rest_framework/fields.py:793
+msgid "Datetime has wrong format. Use one of these formats instead: {format}"
+msgstr ""
+
+#: rest_framework/fields.py:794
+msgid "Expected a datetime but got a date."
+msgstr ""
+
+#: rest_framework/fields.py:858
+msgid "Date has wrong format. Use one of these formats instead: {format}"
+msgstr ""
+
+#: rest_framework/fields.py:859
+msgid "Expected a date but got a datetime."
+msgstr ""
+
+#: rest_framework/fields.py:916
+msgid "Time has wrong format. Use one of these formats instead: {format}"
+msgstr ""
+
+#: rest_framework/fields.py:972 rest_framework/fields.py:1016
+msgid "`{input}` is not a valid choice."
+msgstr ""
+
+#: rest_framework/fields.py:1017 rest_framework/serializers.py:474
+msgid "Expected a list of items but got type `{input_type}`."
+msgstr ""
+
+#: rest_framework/fields.py:1047
+msgid "No file was submitted."
+msgstr ""
+
+#: rest_framework/fields.py:1048
+msgid "The submitted data was not a file. Check the encoding type on the form."
+msgstr ""
+
+#: rest_framework/fields.py:1049
+msgid "No filename could be determined."
+msgstr ""
+
+#: rest_framework/fields.py:1050
+msgid "The submitted file is empty."
+msgstr ""
+
+#: rest_framework/fields.py:1051
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr ""
+
+#: rest_framework/fields.py:1093
+msgid "Upload a valid image. The file you uploaded was either not an "
+msgstr ""
+
+#: rest_framework/fields.py:1119
+msgid "Expected a list of items but got type `{input_type}`"
+msgstr ""
+
+#: rest_framework/generics.py:122
+msgid "Page is not 'last', nor can it be converted to an int."
+msgstr ""
+
+#: rest_framework/generics.py:126
+#, python-format
+msgid "Invalid page (%(page_number)s): %(message)s"
+msgstr ""
+
+#: rest_framework/relations.py:132
+msgid "Invalid pk '{pk_value}' - object does not exist."
+msgstr ""
+
+#: rest_framework/relations.py:133
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr ""
+
+#: rest_framework/relations.py:156
+msgid "Invalid hyperlink - No URL match"
+msgstr ""
+
+#: rest_framework/relations.py:157
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr ""
+
+#: rest_framework/relations.py:158
+msgid "Invalid hyperlink - Object does not exist."
+msgstr ""
+
+#: rest_framework/relations.py:159
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr ""
+
+#: rest_framework/relations.py:294
+msgid "Object with {slug_name}={value} does not exist."
+msgstr ""
+
+#: rest_framework/relations.py:295
+msgid "Invalid value."
+msgstr ""
+
+#: rest_framework/serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr ""
+
+#: rest_framework/validators.py:22
+msgid "This field must be unique."
+msgstr ""
+
+#: rest_framework/validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr ""
+
+#: rest_framework/validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr ""
+
+#: rest_framework/validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr ""
+
+#: rest_framework/validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr ""
--
cgit v1.2.3
From faf76a4b75f12f3fa9de4e3ec455daa239af4d89 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 31 Dec 2014 12:49:20 +0000
Subject: fix spelling & grammar errors
---
rest_framework/exceptions.py | 2 +-
rest_framework/generics.py | 2 +-
rest_framework/locale/en_US/LC_MESSAGES/django.po | 8 ++++----
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index bcfd8961..2586fc33 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -36,7 +36,7 @@ class APIException(Exception):
Subclasses should provide `.status_code` and `.default_detail` properties.
"""
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
- default_detail = _('A server error occured')
+ default_detail = _('A server error occurred')
def __init__(self, detail=None):
if detail is not None:
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index e6db155e..bdbc19a7 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -119,7 +119,7 @@ class GenericAPIView(views.APIView):
if page == 'last':
page_number = paginator.num_pages
else:
- raise Http404(_("Page is not 'last', nor can it be converted to an int."))
+ raise Http404(_("Page is not 'last', and cannot be converted to an int."))
try:
page = paginator.page(page_number)
except InvalidPage as exc:
diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.po b/rest_framework/locale/en_US/LC_MESSAGES/django.po
index 510ce0aa..3bed9143 100644
--- a/rest_framework/locale/en_US/LC_MESSAGES/django.po
+++ b/rest_framework/locale/en_US/LC_MESSAGES/django.po
@@ -2,13 +2,13 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
-#
+#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-12-28 17:49+0000\n"
+"POT-Creation-Date: 2014-12-31 12:48+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -30,7 +30,7 @@ msgid "Must include \"username\" and \"password\""
msgstr ""
#: rest_framework/exceptions.py:39
-msgid "A server error occured"
+msgid "A server error occurred"
msgstr ""
#: rest_framework/exceptions.py:74
@@ -212,7 +212,7 @@ msgid "Expected a list of items but got type `{input_type}`"
msgstr ""
#: rest_framework/generics.py:122
-msgid "Page is not 'last', nor can it be converted to an int."
+msgid "Page is not 'last', and cannot be converted to an int."
msgstr ""
#: rest_framework/generics.py:126
--
cgit v1.2.3
From a90ba2bc11de5fb391b95d4fce84f87ae7f88eff Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 31 Dec 2014 13:03:16 +0000
Subject: update error messages for language and consistency
---
rest_framework/exceptions.py | 4 +--
rest_framework/fields.py | 17 ++++++------
rest_framework/generics.py | 2 +-
rest_framework/locale/en_US/LC_MESSAGES/django.po | 33 ++++++++++++-----------
4 files changed, 28 insertions(+), 28 deletions(-)
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index 2586fc33..d78b7e97 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -36,7 +36,7 @@ class APIException(Exception):
Subclasses should provide `.status_code` and `.default_detail` properties.
"""
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
- default_detail = _('A server error occurred')
+ default_detail = _('A server error occurred.')
def __init__(self, detail=None):
if detail is not None:
@@ -107,7 +107,7 @@ class MethodNotAllowed(APIException):
class NotAcceptable(APIException):
status_code = status.HTTP_406_NOT_ACCEPTABLE
- default_detail = _('Could not satisfy the request Accept header')
+ default_detail = _('Could not satisfy the request Accept header.')
def __init__(self, detail=None, available_renderers=None):
if detail is not None:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index c40dc3fb..0ff2b073 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -640,7 +640,7 @@ class IntegerField(Field):
'invalid': _('A valid integer is required.'),
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
- 'max_string_length': _('String value too large')
+ 'max_string_length': _('String value too large.')
}
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
@@ -674,7 +674,7 @@ class FloatField(Field):
'invalid': _("A valid number is required."),
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
- 'max_string_length': _('String value too large')
+ 'max_string_length': _('String value too large.')
}
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
@@ -710,7 +710,7 @@ class DecimalField(Field):
'max_digits': _('Ensure that there are no more than {max_digits} digits in total.'),
'max_decimal_places': _('Ensure that there are no more than {max_decimal_places} decimal places.'),
'max_whole_digits': _('Ensure that there are no more than {max_whole_digits} digits before the decimal point.'),
- 'max_string_length': _('String value too large')
+ 'max_string_length': _('String value too large.')
}
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
@@ -793,7 +793,7 @@ class DecimalField(Field):
class DateTimeField(Field):
default_error_messages = {
- 'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}'),
+ 'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'),
'date': _('Expected a datetime but got a date.'),
}
format = api_settings.DATETIME_FORMAT
@@ -858,7 +858,7 @@ class DateTimeField(Field):
class DateField(Field):
default_error_messages = {
- 'invalid': _('Date has wrong format. Use one of these formats instead: {format}'),
+ 'invalid': _('Date has wrong format. Use one of these formats instead: {format}.'),
'datetime': _('Expected a date but got a datetime.'),
}
format = api_settings.DATE_FORMAT
@@ -916,7 +916,7 @@ class DateField(Field):
class TimeField(Field):
default_error_messages = {
- 'invalid': _('Time has wrong format. Use one of these formats instead: {format}'),
+ 'invalid': _('Time has wrong format. Use one of these formats instead: {format}.'),
}
format = api_settings.TIME_FORMAT
input_formats = api_settings.TIME_INPUT_FORMATS
@@ -1093,8 +1093,7 @@ class FileField(Field):
class ImageField(FileField):
default_error_messages = {
'invalid_image': _(
- 'Upload a valid image. The file you uploaded was either not an '
- 'image or a corrupted image.'
+ 'Upload a valid image. The file you uploaded was either not an image or a corrupted image.'
),
}
@@ -1119,7 +1118,7 @@ class ListField(Field):
child = None
initial = []
default_error_messages = {
- 'not_a_list': _('Expected a list of items but got type `{input_type}`')
+ 'not_a_list': _('Expected a list of items but got type `{input_type}`.')
}
def __init__(self, *args, **kwargs):
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index bdbc19a7..680992d7 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -119,7 +119,7 @@ class GenericAPIView(views.APIView):
if page == 'last':
page_number = paginator.num_pages
else:
- raise Http404(_("Page is not 'last', and cannot be converted to an int."))
+ raise Http404(_("Choose a valid page number. Page numbers must be a whole number, or must be the string 'last'."))
try:
page = paginator.page(page_number)
except InvalidPage as exc:
diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.po b/rest_framework/locale/en_US/LC_MESSAGES/django.po
index 3bed9143..18f5fe18 100644
--- a/rest_framework/locale/en_US/LC_MESSAGES/django.po
+++ b/rest_framework/locale/en_US/LC_MESSAGES/django.po
@@ -2,13 +2,13 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
-#
+#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-12-31 12:48+0000\n"
+"POT-Creation-Date: 2014-12-31 13:02+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -30,7 +30,7 @@ msgid "Must include \"username\" and \"password\""
msgstr ""
#: rest_framework/exceptions.py:39
-msgid "A server error occurred"
+msgid "A server error occurred."
msgstr ""
#: rest_framework/exceptions.py:74
@@ -55,7 +55,7 @@ msgid "Method '%s' not allowed."
msgstr ""
#: rest_framework/exceptions.py:105
-msgid "Could not satisfy the request Accept header"
+msgid "Could not satisfy the request Accept header."
msgstr ""
#: rest_framework/exceptions.py:117
@@ -92,7 +92,7 @@ msgstr ""
msgid "This field may not be blank."
msgstr ""
-#: rest_framework/fields.py:548 rest_framework/fields.py:1250
+#: rest_framework/fields.py:548 rest_framework/fields.py:1249
msgid "Ensure this field has no more than {max_length} characters."
msgstr ""
@@ -133,7 +133,7 @@ msgstr ""
#: rest_framework/fields.py:640 rest_framework/fields.py:674
#: rest_framework/fields.py:710
-msgid "String value too large"
+msgid "String value too large."
msgstr ""
#: rest_framework/fields.py:671 rest_framework/fields.py:704
@@ -155,7 +155,7 @@ msgid ""
msgstr ""
#: rest_framework/fields.py:793
-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 ""
#: rest_framework/fields.py:794
@@ -163,7 +163,7 @@ msgid "Expected a datetime but got a date."
msgstr ""
#: rest_framework/fields.py:858
-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 ""
#: rest_framework/fields.py:859
@@ -171,14 +171,15 @@ msgid "Expected a date but got a datetime."
msgstr ""
#: rest_framework/fields.py:916
-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 ""
#: rest_framework/fields.py:972 rest_framework/fields.py:1016
msgid "`{input}` is not a valid choice."
msgstr ""
-#: rest_framework/fields.py:1017 rest_framework/serializers.py:474
+#: rest_framework/fields.py:1017 rest_framework/fields.py:1118
+#: rest_framework/serializers.py:474
msgid "Expected a list of items but got type `{input_type}`."
msgstr ""
@@ -204,15 +205,15 @@ msgid ""
msgstr ""
#: rest_framework/fields.py:1093
-msgid "Upload a valid image. The file you uploaded was either not an "
-msgstr ""
-
-#: rest_framework/fields.py:1119
-msgid "Expected a list of items but got type `{input_type}`"
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
msgstr ""
#: rest_framework/generics.py:122
-msgid "Page is not 'last', and cannot be converted to an int."
+msgid ""
+"Choose a valid page number. Page numbers must be a whole number, or must be "
+"the string 'last'."
msgstr ""
#: rest_framework/generics.py:126
--
cgit v1.2.3
From 32506e20756c84677abb5ae49706446a0d250371 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 31 Dec 2014 13:14:09 +0000
Subject: update expected error messages in tests
---
tests/test_fields.py | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 04c721d3..61d39aff 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -640,8 +640,8 @@ class TestDateField(FieldValues):
datetime.date(2001, 1, 1): datetime.date(2001, 1, 1),
}
invalid_inputs = {
- 'abc': ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]]'],
- '2001-99-99': ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]]'],
+ 'abc': ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].'],
+ '2001-99-99': ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].'],
datetime.datetime(2001, 1, 1, 12, 00): ['Expected a date but got a datetime.'],
}
outputs = {
@@ -658,7 +658,7 @@ class TestCustomInputFormatDateField(FieldValues):
'1 Jan 2001': datetime.date(2001, 1, 1),
}
invalid_inputs = {
- '2001-01-01': ['Date has wrong format. Use one of these formats instead: DD [Jan-Dec] YYYY']
+ '2001-01-01': ['Date has wrong format. Use one of these formats instead: DD [Jan-Dec] YYYY.']
}
outputs = {}
field = serializers.DateField(input_formats=['%d %b %Y'])
@@ -702,8 +702,8 @@ class TestDateTimeField(FieldValues):
'2001-01-01T14:00+01:00' if (django.VERSION > (1, 4)) else '2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=timezone.UTC())
}
invalid_inputs = {
- 'abc': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]'],
- '2001-99-99T99:00': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]'],
+ 'abc': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
+ '2001-99-99T99:00': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
datetime.date(2001, 1, 1): ['Expected a datetime but got a date.'],
}
outputs = {
@@ -721,7 +721,7 @@ class TestCustomInputFormatDateTimeField(FieldValues):
'1:35pm, 1 Jan 2001': datetime.datetime(2001, 1, 1, 13, 35, tzinfo=timezone.UTC()),
}
invalid_inputs = {
- '2001-01-01T20:50': ['Datetime has wrong format. Use one of these formats instead: hh:mm[AM|PM], DD [Jan-Dec] YYYY']
+ '2001-01-01T20:50': ['Datetime has wrong format. Use one of these formats instead: hh:mm[AM|PM], DD [Jan-Dec] YYYY.']
}
outputs = {}
field = serializers.DateTimeField(default_timezone=timezone.UTC(), input_formats=['%I:%M%p, %d %b %Y'])
@@ -773,8 +773,8 @@ class TestTimeField(FieldValues):
datetime.time(13, 00): datetime.time(13, 00),
}
invalid_inputs = {
- 'abc': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]]'],
- '99:99': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]]'],
+ 'abc': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].'],
+ '99:99': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].'],
}
outputs = {
datetime.time(13, 00): '13:00:00'
@@ -790,7 +790,7 @@ class TestCustomInputFormatTimeField(FieldValues):
'1:00pm': datetime.time(13, 00),
}
invalid_inputs = {
- '13:00': ['Time has wrong format. Use one of these formats instead: hh:mm[AM|PM]'],
+ '13:00': ['Time has wrong format. Use one of these formats instead: hh:mm[AM|PM].'],
}
outputs = {}
field = serializers.TimeField(input_formats=['%I:%M%p'])
@@ -1028,7 +1028,7 @@ class TestListField(FieldValues):
(['1', '2', '3'], [1, 2, 3])
]
invalid_inputs = [
- ('not a list', ['Expected a list of items but got type `str`']),
+ ('not a list', ['Expected a list of items but got type `str`.']),
([1, 2, 'error'], ['A valid integer is required.'])
]
outputs = [
--
cgit v1.2.3
From 9f169acb62a6223a5add0fee7f6d53108e42f207 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 31 Dec 2014 14:56:23 +0000
Subject: capitalise Django
---
docs/topics/internationalisation.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/topics/internationalisation.md b/docs/topics/internationalisation.md
index 6470ee03..fac3bdb7 100644
--- a/docs/topics/internationalisation.md
+++ b/docs/topics/internationalisation.md
@@ -70,7 +70,8 @@ available for Django to use. You should see a message
## How Django chooses which language to use
-REST framework will use the same preferences to select which language to display as Django does. You can find more info in the [django docs on discovering language preferences][django-language-preference]. For reference, these are
+REST framework will use the same preferences to select which language to
+display as Django does. You can find more info in the [Django docs on discovering language preferences][django-language-preference]. For reference, these are
1. First, it looks for the language prefix in the requested URL
2. Failing that, it looks for the `LANGUAGE_SESSION_KEY` key in the current user’s session.
--
cgit v1.2.3
From 6fb37207d18949031fb7203d6fd67ee503df0a34 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Fri, 2 Jan 2015 11:11:13 +0000
Subject: add missing period; update generated translations
---
rest_framework/exceptions.py | 2 +-
rest_framework/locale/en_US/LC_MESSAGES/django.po | 96 ++++++++++++++---------
2 files changed, 59 insertions(+), 39 deletions(-)
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index d78b7e97..c8cedfce 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -91,7 +91,7 @@ class PermissionDenied(APIException):
class NotFound(APIException):
status_code = status.HTTP_404_NOT_FOUND
- default_detail = _('Not found')
+ default_detail = _('Not found.')
class MethodNotAllowed(APIException):
diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.po b/rest_framework/locale/en_US/LC_MESSAGES/django.po
index 18f5fe18..56902073 100644
--- a/rest_framework/locale/en_US/LC_MESSAGES/django.po
+++ b/rest_framework/locale/en_US/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-12-31 13:02+0000\n"
+"POT-Creation-Date: 2015-01-02 11:10+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -50,24 +50,28 @@ msgid "You do not have permission to perform this action."
msgstr ""
#: rest_framework/exceptions.py:94
+msgid "Not found."
+msgstr ""
+
+#: rest_framework/exceptions.py:99
#, python-format
msgid "Method '%s' not allowed."
msgstr ""
-#: rest_framework/exceptions.py:105
+#: rest_framework/exceptions.py:110
msgid "Could not satisfy the request Accept header."
msgstr ""
-#: rest_framework/exceptions.py:117
+#: rest_framework/exceptions.py:122
#, python-format
msgid "Unsupported media type '%s' in request."
msgstr ""
-#: rest_framework/exceptions.py:128
+#: rest_framework/exceptions.py:133
msgid "Request was throttled."
msgstr ""
-#: rest_framework/exceptions.py:130
+#: rest_framework/exceptions.py:135
#, python-format
msgid "Expected available in %(wait)d second."
msgid_plural "Expected available in %(wait)d seconds."
@@ -84,127 +88,127 @@ msgstr ""
msgid "This field may not be null."
msgstr ""
-#: rest_framework/fields.py:484 rest_framework/fields.py:512
+#: rest_framework/fields.py:480 rest_framework/fields.py:508
msgid "`{input}` is not a valid boolean."
msgstr ""
-#: rest_framework/fields.py:547
+#: rest_framework/fields.py:543
msgid "This field may not be blank."
msgstr ""
-#: rest_framework/fields.py:548 rest_framework/fields.py:1249
+#: rest_framework/fields.py:544 rest_framework/fields.py:1252
msgid "Ensure this field has no more than {max_length} characters."
msgstr ""
-#: rest_framework/fields.py:549
+#: rest_framework/fields.py:545
msgid "Ensure this field has at least {min_length} characters."
msgstr ""
-#: rest_framework/fields.py:584
+#: rest_framework/fields.py:587
msgid "Enter a valid email address."
msgstr ""
-#: rest_framework/fields.py:601
+#: rest_framework/fields.py:604
msgid "This value does not match the required pattern."
msgstr ""
-#: rest_framework/fields.py:612
+#: rest_framework/fields.py:615
msgid ""
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
msgstr ""
-#: rest_framework/fields.py:624
+#: rest_framework/fields.py:627
msgid "Enter a valid URL."
msgstr ""
-#: rest_framework/fields.py:637
+#: rest_framework/fields.py:640
msgid "A valid integer is required."
msgstr ""
-#: rest_framework/fields.py:638 rest_framework/fields.py:672
-#: rest_framework/fields.py:705
+#: rest_framework/fields.py:641 rest_framework/fields.py:675
+#: rest_framework/fields.py:708
msgid "Ensure this value is less than or equal to {max_value}."
msgstr ""
-#: rest_framework/fields.py:639 rest_framework/fields.py:673
-#: rest_framework/fields.py:706
+#: rest_framework/fields.py:642 rest_framework/fields.py:676
+#: rest_framework/fields.py:709
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr ""
-#: rest_framework/fields.py:640 rest_framework/fields.py:674
-#: rest_framework/fields.py:710
+#: rest_framework/fields.py:643 rest_framework/fields.py:677
+#: rest_framework/fields.py:713
msgid "String value too large."
msgstr ""
-#: rest_framework/fields.py:671 rest_framework/fields.py:704
+#: rest_framework/fields.py:674 rest_framework/fields.py:707
msgid "A valid number is required."
msgstr ""
-#: rest_framework/fields.py:707
+#: rest_framework/fields.py:710
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr ""
-#: rest_framework/fields.py:708
+#: rest_framework/fields.py:711
msgid "Ensure that there are no more than {max_decimal_places} decimal places."
msgstr ""
-#: rest_framework/fields.py:709
+#: rest_framework/fields.py:712
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
-#: rest_framework/fields.py:793
+#: rest_framework/fields.py:796
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr ""
-#: rest_framework/fields.py:794
+#: rest_framework/fields.py:797
msgid "Expected a datetime but got a date."
msgstr ""
-#: rest_framework/fields.py:858
+#: rest_framework/fields.py:861
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr ""
-#: rest_framework/fields.py:859
+#: rest_framework/fields.py:862
msgid "Expected a date but got a datetime."
msgstr ""
-#: rest_framework/fields.py:916
+#: rest_framework/fields.py:919
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
-#: rest_framework/fields.py:972 rest_framework/fields.py:1016
+#: rest_framework/fields.py:975 rest_framework/fields.py:1019
msgid "`{input}` is not a valid choice."
msgstr ""
-#: rest_framework/fields.py:1017 rest_framework/fields.py:1118
-#: rest_framework/serializers.py:474
+#: rest_framework/fields.py:1020 rest_framework/fields.py:1121
+#: rest_framework/serializers.py:476
msgid "Expected a list of items but got type `{input_type}`."
msgstr ""
-#: rest_framework/fields.py:1047
+#: rest_framework/fields.py:1050
msgid "No file was submitted."
msgstr ""
-#: rest_framework/fields.py:1048
+#: rest_framework/fields.py:1051
msgid "The submitted data was not a file. Check the encoding type on the form."
msgstr ""
-#: rest_framework/fields.py:1049
+#: rest_framework/fields.py:1052
msgid "No filename could be determined."
msgstr ""
-#: rest_framework/fields.py:1050
+#: rest_framework/fields.py:1053
msgid "The submitted file is empty."
msgstr ""
-#: rest_framework/fields.py:1051
+#: rest_framework/fields.py:1054
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
-#: rest_framework/fields.py:1093
+#: rest_framework/fields.py:1096
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
@@ -276,3 +280,19 @@ msgstr ""
#: rest_framework/validators.py:247
msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
+
+#: rest_framework/versioning.py:39
+msgid "Invalid version in 'Accept' header."
+msgstr ""
+
+#: rest_framework/versioning.py:70 rest_framework/versioning.py:112
+msgid "Invalid version in URL path."
+msgstr ""
+
+#: rest_framework/versioning.py:138
+msgid "Invalid version in hostname."
+msgstr ""
+
+#: rest_framework/versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr ""
--
cgit v1.2.3
From 8cf37449715c32c4a692667814466c7f32e8734f Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 5 Jan 2015 10:52:18 +0000
Subject: Ensure no invalid min_length/min_value/max_value arguments. Closes
#2369.
---
rest_framework/utils/field_mapping.py | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py
index b2f4dd80..cba40d31 100644
--- a/rest_framework/utils/field_mapping.py
+++ b/rest_framework/utils/field_mapping.py
@@ -10,6 +10,11 @@ from rest_framework.validators import UniqueValidator
import inspect
+NUMERIC_FIELD_TYPES = (
+ models.IntegerField, models.FloatField, models.DecimalField
+)
+
+
class ClassLookupDict(object):
"""
Takes a dictionary with classes as keys.
@@ -119,7 +124,7 @@ def get_field_kwargs(field_name, model_field):
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MinLengthValidator)
), None)
- if min_length is not None:
+ if min_length is not None and isinstance(model_field, models.CharField):
kwargs['min_length'] = min_length
validator_kwarg = [
validator for validator in validator_kwarg
@@ -132,7 +137,7 @@ def get_field_kwargs(field_name, model_field):
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MaxValueValidator)
), None)
- if max_value is not None:
+ if max_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
kwargs['max_value'] = max_value
validator_kwarg = [
validator for validator in validator_kwarg
@@ -145,7 +150,7 @@ def get_field_kwargs(field_name, model_field):
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MinValueValidator)
), None)
- if min_value is not None:
+ if min_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
kwargs['min_value'] = min_value
validator_kwarg = [
validator for validator in validator_kwarg
--
cgit v1.2.3
From 17665aa52a9cd5599099c19fd8f54540a5d436ce Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 5 Jan 2015 12:26:15 +0000
Subject: Add docs for OAuth, XML, YAML, JSONP packages. Closes #2179.
---
README.md | 4 +-
docs/api-guide/authentication.md | 47 +++++++++++++++++++++--
docs/api-guide/parsers.md | 51 +++++++++++++++++++++++--
docs/api-guide/renderers.md | 80 +++++++++++++++++++++++++++++++++++++++-
docs/index.md | 9 ++---
5 files changed, 174 insertions(+), 17 deletions(-)
diff --git a/README.md b/README.md
index 4c9d765e..6742a7b1 100644
--- a/README.md
+++ b/README.md
@@ -190,8 +190,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[sandbox]: http://restframework.herokuapp.com/
[index]: http://www.django-rest-framework.org/
-[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication.html#oauthauthentication
-[oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication.html#oauth2authentication
+[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
+[oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
[serializer-section]: http://www.django-rest-framework.org/api-guide/serializers.html#serializers
[modelserializer-section]: http://www.django-rest-framework.org/api-guide/serializers.html#modelserializer
[functionview-section]: http://www.django-rest-framework.org/api-guide/views.html#function-based-views
diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md
index 2074f1bf..bb731817 100755
--- a/docs/api-guide/authentication.md
+++ b/docs/api-guide/authentication.md
@@ -293,13 +293,48 @@ The following example will authenticate any incoming request as the user given b
The following third party packages are also available.
-## Digest Authentication
+## Django OAuth Toolkit
-HTTP digest authentication is a widely implemented scheme that was intended to replace HTTP basic authentication, and which provides a simple encrypted authentication mechanism. [Juan Riaza][juanriaza] maintains the [djangorestframework-digestauth][djangorestframework-digestauth] package which provides HTTP digest authentication support for REST framework.
+The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+. The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**.
-## Django OAuth Toolkit
+#### Installation & configuration
+
+Install using `pip`.
+
+ pip install django-oauth-toolkit
+
+Add the package to your `INSTALLED_APPS` and modify your REST framework settings.
-The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+. The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and comes as a recommended alternative for OAuth 2.0 support.
+ INSTALLED_APPS = (
+ ...
+ 'oauth2_provider',
+ )
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_AUTHENTICATION_CLASSES': (
+ 'oauth2_provider.ext.rest_framework.OAuth2Authentication',
+ )
+ }
+
+For more details see the [Django REST framework - Getting started][django-oauth-toolkit-getting-started] documentation.
+
+## Django REST framework OAuth
+
+The [Django REST framework OAuth][django-rest-framework-oauth] package provides both OAuth1 and OAuth2 support for REST framework.
+
+This package was previously included directly in REST framework but is now supported and maintained as a third party package.
+
+#### Installation & configuration
+
+Install the package using `pip`.
+
+ pip install djangorestframework-oauth
+
+For details on configuration and usage see the Django REST framework OAuth documentation for [authentication][django-rest-framework-oauth-authentication] and [permissions][django-rest-framework-oauth-permissions].
+
+## Digest Authentication
+
+HTTP digest authentication is a widely implemented scheme that was intended to replace HTTP basic authentication, and which provides a simple encrypted authentication mechanism. [Juan Riaza][juanriaza] maintains the [djangorestframework-digestauth][djangorestframework-digestauth] package which provides HTTP digest authentication support for REST framework.
## Django OAuth2 Consumer
@@ -332,6 +367,10 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
[mod_wsgi_official]: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPassAuthorization
[custom-user-model]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#specifying-a-custom-user-model
[south-dependencies]: http://south.readthedocs.org/en/latest/dependencies.html
+[django-oauth-toolkit-getting-started]: https://django-oauth-toolkit.readthedocs.org/en/latest/rest-framework/getting_started.html
+[django-rest-framework-oauth]: http://jpadilla.github.io/django-rest-framework-oauth/
+[django-rest-framework-oauth-authentication]: http://jpadilla.github.io/django-rest-framework-oauth/authentication/
+[django-rest-framework-oauth-permissions]: http://jpadilla.github.io/django-rest-framework-oauth/permissions/
[juanriaza]: https://github.com/juanriaza
[djangorestframework-digestauth]: https://github.com/juanriaza/django-rest-framework-digestauth
[oauth-1.0a]: http://oauth.net/core/1.0a
diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md
index 9323d382..b68b33be 100644
--- a/docs/api-guide/parsers.md
+++ b/docs/api-guide/parsers.md
@@ -26,7 +26,7 @@ As an example, if you are sending `json` encoded data using jQuery with the [.aj
## Setting the parsers
-The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting. For example, the following settings would allow requests with `JSON` content.
+The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting. For example, the following settings would allow only requests with `JSON` content, instead of the default of JSON or form data.
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
@@ -37,8 +37,8 @@ The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSE
You can also set the parsers used for an individual view, or viewset,
using the `APIView` class based views.
- from rest_framework.parsers import JSONParser
- from rest_framework.response import Response
+ from rest_framework.parsers import JSONParser
+ from rest_framework.response import Response
from rest_framework.views import APIView
class ExampleView(APIView):
@@ -162,6 +162,48 @@ The following is an example plaintext parser that will populate the `request.dat
The following third party packages are also available.
+## YAML
+
+[REST framework YAML][rest-framework-yaml] provides [YAML][yaml] parsing and rendering support. It was previously included directly in the REST framework package, and is now instead supported as a third-party package.
+
+#### Installation & configuration
+
+Install using pip.
+
+ $ pip install djangorestframework-yaml
+
+Modify your REST framework settings.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_PARSER_CLASSES': (
+ 'rest_framework_yaml.parsers.YAMLParser',
+ ),
+ 'DEFAULT_RENDERER_CLASSES': (
+ 'rest_framework_yaml.renderers.YAMLRenderer',
+ ),
+ }
+
+## XML
+
+[REST Framework XML][rest-framework-xml] provides a simple informal XML format. It was previously included directly in the REST framework package, and is now instead supported as a third-party package.
+
+#### Installation & configuration
+
+Install using pip.
+
+ $ pip install djangorestframework-xml
+
+Modify your REST framework settings.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_PARSER_CLASSES': (
+ 'rest_framework_xml.parsers.XMLParser',
+ ),
+ 'DEFAULT_RENDERER_CLASSES': (
+ 'rest_framework_xml.renderers.XMLRenderer',
+ ),
+ }
+
## MessagePack
[MessagePack][messagepack] is a fast, efficient binary serialization format. [Juan Riaza][juanriaza] maintains the [djangorestframework-msgpack][djangorestframework-msgpack] package which provides MessagePack renderer and parser support for REST framework.
@@ -173,6 +215,9 @@ The following third party packages are also available.
[jquery-ajax]: http://api.jquery.com/jQuery.ajax/
[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion
[upload-handlers]: https://docs.djangoproject.com/en/dev/topics/http/file-uploads/#upload-handlers
+[rest-framework-yaml]: http://jpadilla.github.io/django-rest-framework-yaml/
+[rest-framework-xml]: http://jpadilla.github.io/django-rest-framework-xml/
+[yaml]: http://www.yaml.org/
[messagepack]: https://github.com/juanriaza/django-rest-framework-msgpack
[juanriaza]: https://github.com/juanriaza
[vbabiy]: https://github.com/vbabiy
diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md
index 69460dbc..83ded849 100644
--- a/docs/api-guide/renderers.md
+++ b/docs/api-guide/renderers.md
@@ -342,13 +342,81 @@ Templates will render with a `RequestContext` which includes the `status_code` a
The following third party packages are also available.
+## YAML
+
+[REST framework YAML][rest-framework-yaml] provides [YAML][yaml] parsing and rendering support. It was previously included directly in the REST framework package, and is now instead supported as a third-party package.
+
+#### Installation & configuration
+
+Install using pip.
+
+ $ pip install djangorestframework-yaml
+
+Modify your REST framework settings.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_PARSER_CLASSES': (
+ 'rest_framework_yaml.parsers.YAMLParser',
+ ),
+ 'DEFAULT_RENDERER_CLASSES': (
+ 'rest_framework_yaml.renderers.YAMLRenderer',
+ ),
+ }
+
+## XML
+
+[REST Framework XML][rest-framework-xml] provides a simple informal XML format. It was previously included directly in the REST framework package, and is now instead supported as a third-party package.
+
+#### Installation & configuration
+
+Install using pip.
+
+ $ pip install djangorestframework-xml
+
+Modify your REST framework settings.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_PARSER_CLASSES': (
+ 'rest_framework_xml.parsers.XMLParser',
+ ),
+ 'DEFAULT_RENDERER_CLASSES': (
+ 'rest_framework_xml.renderers.XMLRenderer',
+ ),
+ }
+
+## JSONP
+
+[REST framework JSONP][rest-framework-jsonp] provides JSONP rendering support. It was previously included directly in the REST framework package, and is now instead supported as a third-party package.
+
+---
+
+**Warning**: If you require cross-domain AJAX requests, you should generally be using the more modern approach of [CORS][cors] as an alternative to `JSONP`. See the [CORS documentation][cors-docs] for more details.
+
+The `jsonp` approach is essentially a browser hack, and is [only appropriate for globally readable API endpoints][jsonp-security], where `GET` requests are unauthenticated and do not require any user permissions.
+
+---
+
+#### Installation & configuration
+
+Install using pip.
+
+ $ pip install djangorestframework-jsonp
+
+Modify your REST framework settings.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_RENDERER_CLASSES': (
+ 'rest_framework_yaml.renderers.JSONPRenderer',
+ ),
+ }
+
## MessagePack
[MessagePack][messagepack] is a fast, efficient binary serialization format. [Juan Riaza][juanriaza] maintains the [djangorestframework-msgpack][djangorestframework-msgpack] package which provides MessagePack renderer and parser support for REST framework.
## CSV
-Comma-separated values are a plain-text tabular data format, that can be easily imported into spreadsheet applications. [Mjumbe Poe][mjumbewu] maintains the [djangorestframework-csv][djangorestframework-csv] package which provides CSV renderer support for REST framework.
+Comma-separated values are a plain-text tabular data format, that can be easily imported into spreadsheet applications. [Mjumbe Poe][mjumbewu] maintains the [djangorestframework-csv][djangorestframework-csv] package which provides CSV renderer support for REST framework.
## UltraJSON
@@ -358,7 +426,6 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[djangorestframework-camel-case] provides camel case JSON renderers and parsers for REST framework. This allows serializers to use Python-style underscored field names, but be exposed in the API as Javascript-style camel case field names. It is maintained by [Vitaly Babiy][vbabiy].
-
## Pandas (CSV, Excel, PNG)
[Django REST Pandas] provides a serializer and renderers that support additional data processing and output via the [Pandas] DataFrame API. Django REST Pandas includes renderers for Pandas-style CSV files, Excel workbooks (both `.xls` and `.xlsx`), and a number of [other formats]. It is maintained by [S. Andrew Sheppard][sheppard] as part of the [wq Project][wq].
@@ -373,10 +440,19 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[application/vnd.github+json]: http://developer.github.com/v3/media/
[application/vnd.collection+json]: http://www.amundsen.com/media-types/collection/
[django-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views
+[rest-framework-jsonp]: http://jpadilla.github.io/django-rest-framework-jsonp/
+[cors]: http://www.w3.org/TR/cors/
+[cors-docs]: http://www.django-rest-framework.org/topics/ajax-csrf-cors/
+[jsonp-security]: http://stackoverflow.com/questions/613962/is-jsonp-safe-to-use
+[rest-framework-yaml]: http://jpadilla.github.io/django-rest-framework-yaml/
+[rest-framework-xml]: http://jpadilla.github.io/django-rest-framework-xml/
[messagepack]: http://msgpack.org/
[juanriaza]: https://github.com/juanriaza
[mjumbewu]: https://github.com/mjumbewu
[vbabiy]: https://github.com/vbabiy
+[rest-framework-yaml]: http://jpadilla.github.io/django-rest-framework-yaml/
+[rest-framework-xml]: http://jpadilla.github.io/django-rest-framework-xml/
+[yaml]: http://www.yaml.org/
[djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack
[djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv
[ultrajson]: https://github.com/esnme/ultrajson
diff --git a/docs/index.md b/docs/index.md
index 7ccec12f..544204c6 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -28,13 +28,12 @@ For more details see the [3.0 release notes][3.0-announcement].
-
Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs.
Some reasons you might want to use REST framework:
* The [Web browsable API][sandbox] is a huge usability win for your developers.
-* [Authentication policies][authentication] including optional packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
+* [Authentication policies][authentication] including packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
* 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].
@@ -57,7 +56,6 @@ The following packages are optional:
* [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API.
* [django-filter][django-filter] (0.5.4+) - Filtering support.
-* [django-restframework-oauth][django-restframework-oauth] package for OAuth 1.0a and 2.0 support.
* [django-guardian][django-guardian] (1.1.1+) - Object level permissions support.
## Installation
@@ -260,13 +258,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[eventbrite]: https://www.eventbrite.co.uk/about/
[markdown]: http://pypi.python.org/pypi/Markdown/
[django-filter]: http://pypi.python.org/pypi/django-filter
-[django-restframework-oauth]: https://github.com/jlafon/django-rest-framework-oauth
[django-guardian]: https://github.com/lukaszb/django-guardian
[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
[image]: img/quickstart.png
[index]: .
-[oauth1-section]: api-guide/authentication#oauthauthentication
-[oauth2-section]: api-guide/authentication#oauth2authentication
+[oauth1-section]: api-guide/authentication/#django-rest-framework-oauth
+[oauth2-section]: api-guide/authentication/#django-oauth-toolkit
[serializer-section]: api-guide/serializers#serializers
[modelserializer-section]: api-guide/serializers#modelserializer
[functionview-section]: api-guide/views#function-based-views
--
cgit v1.2.3
From b6ca7248ebcf95a95e1911aa0b130f653b8bf690 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 5 Jan 2015 14:32:12 +0000
Subject: required=False allows omission of value for output. Closes #2342
---
docs/api-guide/fields.md | 2 ++
rest_framework/fields.py | 2 ++
rest_framework/serializers.py | 8 +++++-
tests/test_serializer.py | 62 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 73 insertions(+), 1 deletion(-)
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index 946e355d..b3d274dd 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -41,6 +41,8 @@ Defaults to `False`
Normally an error will be raised if a field is not supplied during deserialization.
Set to false if this field is not required to be present during deserialization.
+Setting this to `False` also allows the object attribute or dictionary key to be omitted from output when serializing the instance. If the key is not present it will simply not be included in the output representation.
+
Defaults to `True`.
### `allow_null`
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index aab80982..cc9410aa 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -288,6 +288,8 @@ class Field(object):
try:
return get_attribute(instance, self.source_attrs)
except (KeyError, AttributeError) as exc:
+ if not self.required and self.default is empty:
+ raise SkipField()
msg = (
'Got {exc_type} when attempting to get a value for field '
'`{field}` on serializer `{serializer}`.\nThe serializer '
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 6f89df0d..53f092d7 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -419,8 +419,14 @@ class Serializer(BaseSerializer):
fields = [field for field in self.fields.values() if not field.write_only]
for field in fields:
- attribute = field.get_attribute(instance)
+ try:
+ attribute = field.get_attribute(instance)
+ except SkipField:
+ continue
+
if attribute is None:
+ # We skip `to_representation` for `None` values so that
+ # fields do not have to explicitly deal with that case.
ret[field.field_name] = None
else:
ret[field.field_name] = field.to_representation(attribute)
diff --git a/tests/test_serializer.py b/tests/test_serializer.py
index c17b6d8c..68bbbe98 100644
--- a/tests/test_serializer.py
+++ b/tests/test_serializer.py
@@ -1,5 +1,6 @@
# coding: utf-8
from __future__ import unicode_literals
+from .utils import MockObject
from rest_framework import serializers
from rest_framework.compat import unicode_repr
import pytest
@@ -216,3 +217,64 @@ class TestUnicodeRepr:
instance = ExampleObject()
serializer = ExampleSerializer(instance)
repr(serializer) # Should not error.
+
+
+class TestNotRequiredOutput:
+ def test_not_required_output_for_dict(self):
+ """
+ 'required=False' should allow a dictionary key to be missing in output.
+ """
+ class ExampleSerializer(serializers.Serializer):
+ omitted = serializers.CharField(required=False)
+ included = serializers.CharField()
+
+ serializer = ExampleSerializer(data={'included': 'abc'})
+ serializer.is_valid()
+ assert serializer.data == {'included': 'abc'}
+
+ def test_not_required_output_for_object(self):
+ """
+ 'required=False' should allow an object attribute to be missing in output.
+ """
+ class ExampleSerializer(serializers.Serializer):
+ omitted = serializers.CharField(required=False)
+ included = serializers.CharField()
+
+ def create(self, validated_data):
+ return MockObject(**validated_data)
+
+ serializer = ExampleSerializer(data={'included': 'abc'})
+ serializer.is_valid()
+ serializer.save()
+ assert serializer.data == {'included': 'abc'}
+
+ def test_default_required_output_for_dict(self):
+ """
+ 'default="something"' should require dictionary key.
+
+ We need to handle this as the field will have an implicit
+ 'required=False', but it should still have a value.
+ """
+ class ExampleSerializer(serializers.Serializer):
+ omitted = serializers.CharField(default='abc')
+ included = serializers.CharField()
+
+ serializer = ExampleSerializer({'included': 'abc'})
+ with pytest.raises(KeyError):
+ serializer.data
+
+ def test_default_required_output_for_object(self):
+ """
+ 'default="something"' should require object attribute.
+
+ We need to handle this as the field will have an implicit
+ 'required=False', but it should still have a value.
+ """
+ class ExampleSerializer(serializers.Serializer):
+ omitted = serializers.CharField(default='abc')
+ included = serializers.CharField()
+
+ instance = MockObject(included='abc')
+ serializer = ExampleSerializer(instance)
+ with pytest.raises(AttributeError):
+ serializer.data
--
cgit v1.2.3
From 49dc037a961b618baf8eb189b094633238867b41 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 5 Jan 2015 15:03:09 +0000
Subject: Update docstring
---
rest_framework/serializers.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 623ed586..08a58433 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -236,11 +236,11 @@ class BaseSerializer(Field):
class SerializerMetaclass(type):
"""
- This metaclass sets a dictionary named `base_fields` on the class.
+ This metaclass sets a dictionary named `_declared_fields` on the class.
Any instances of `Field` included as attributes on either the class
or on any of its superclasses will be include in the
- `base_fields` dictionary.
+ `_declared_fields` dictionary.
"""
@classmethod
--
cgit v1.2.3
From 6fd33ddea9e5b8f9e979e573a27873131846ea48 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 5 Jan 2015 15:04:01 +0000
Subject: Udpate docstring
---
rest_framework/serializers.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 53f092d7..e373cd10 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -236,11 +236,11 @@ class BaseSerializer(Field):
class SerializerMetaclass(type):
"""
- This metaclass sets a dictionary named `base_fields` on the class.
+ This metaclass sets a dictionary named `_declared_fields` on the class.
Any instances of `Field` included as attributes on either the class
or on any of its superclasses will be include in the
- `base_fields` dictionary.
+ `_declared_fields` dictionary.
"""
@classmethod
--
cgit v1.2.3
From 26ac2656e5e0b3d01a67551910113a305d2a2820 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 5 Jan 2015 16:20:15 +0000
Subject: Pass init arguments through to serializer from pagination serializer.
Closes #2355.
Normally a serializer won't need these arguments on __init__, but
if a user has customized __init__ they may expect them to be available.
---
rest_framework/pagination.py | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index f31e5fa4..9c8dda8f 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -37,16 +37,13 @@ class PreviousPageField(serializers.Field):
return replace_query_param(url, self.page_field, page)
-class DefaultObjectSerializer(serializers.ReadOnlyField):
+class DefaultObjectSerializer(serializers.Serializer):
"""
If no object serializer is specified, then this serializer will be applied
as the default.
"""
-
- def __init__(self, source=None, many=None, context=None):
- # Note: Swallow context and many kwargs - only required for
- # eg. ModelSerializer.
- super(DefaultObjectSerializer, self).__init__(source=source)
+ def to_representation(self, value):
+ return value
class BasePaginationSerializer(serializers.Serializer):
@@ -74,10 +71,9 @@ class BasePaginationSerializer(serializers.Serializer):
list_serializer_class = serializers.ListSerializer
self.fields[results_field] = list_serializer_class(
- child=object_serializer(),
+ child=object_serializer(*args, **kwargs),
source='object_list'
)
- self.fields[results_field].bind(field_name=results_field, parent=self)
class PaginationSerializer(BasePaginationSerializer):
--
cgit v1.2.3
From d3b2302588f333b22d5e4aa2be6eca0e944e9494 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Mon, 5 Jan 2015 16:31:52 +0000
Subject: Minor docs update. Refs #2375.
---
docs/topics/3.0-announcement.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md
index 68d24782..5dbc5600 100644
--- a/docs/topics/3.0-announcement.md
+++ b/docs/topics/3.0-announcement.md
@@ -87,12 +87,12 @@ The resulting API changes are further detailed below.
#### The `.create()` and `.update()` methods.
-The `.restore_object()` method is now replaced with two separate methods, `.create()` and `.update()`.
-
-These methods also replace the optional `.save_object()` method, which no longer exists.
+The `.restore_object()` method is now removed, and we instead have two separate methods, `.create()` and `.update()`. These methods work slightly different to the previous `.restore_object()`.
When using the `.create()` and `.update()` methods you should both create *and save* the object instance. This is in contrast to the previous `.restore_object()` behavior that would instantiate the object but not save it.
+These methods also replace the optional `.save_object()` method, which no longer exists.
+
The following example from the tutorial previously used `restore_object()` to handle both creating and updating object instances.
def restore_object(self, attrs, instance=None):
--
cgit v1.2.3
From 271b638df10c0cf498cbc69847f388e978c4da78 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 6 Jan 2015 11:21:58 +0000
Subject: Update exception docs. Closes #2378.
---
docs/api-guide/exceptions.md | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md
index 467ad970..993134f7 100644
--- a/docs/api-guide/exceptions.md
+++ b/docs/api-guide/exceptions.md
@@ -18,7 +18,7 @@ The handled exceptions are:
In each case, REST framework will return a response with an appropriate status code and content-type. The body of the response will include any additional details regarding the nature of the error.
-By default all error responses will include a key `detail` in the body of the response, but other keys may also be included.
+Most error responses will include a key `detail` in the body of the response.
For example, the following request:
@@ -33,6 +33,16 @@ Might receive an error response indicating that the `DELETE` method is not allow
{"detail": "Method 'DELETE' not allowed."}
+Validation errors are handled slightly differently, and will include the field names as the keys in the response. If the validation error was not specific to a particular field then it will use the "non_field_errors" key, or whatever string value has been set for the `NON_FIELD_ERRORS_KEY` setting.
+
+Any example validation error might look like this:
+
+ HTTP/1.1 400 Bad Request
+ Content-Type: application/json
+ Content-Length: 94
+
+ {"amount": ["A valid integer is required."], "description": ["This field may not be blank."]}
+
## Custom exception handling
You can implement custom exception handling by creating a handler function that converts exceptions raised in your API views into response objects. This allows you to control the style of error responses used by your API.
--
cgit v1.2.3
From 07ad0474c0cef8f8e88d299eca9dffbe6d01c10d Mon Sep 17 00:00:00 2001
From: Ryan Gaffney
Date: Tue, 6 Jan 2015 14:34:36 -0800
Subject: Fix compatibility comment regarding OrderedDict
---
rest_framework/compat.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index ba26a3cd..b1f6f2fa 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -36,7 +36,7 @@ def unicode_to_repr(value):
# OrderedDict only available in Python 2.7.
# This will always be the case in Django 1.7 and above, as these versions
# no longer support Python 2.6.
-# For Django <= 1.6 and Python 2.6 fall back to OrderedDict.
+# For Django <= 1.6 and Python 2.6 fall back to SortedDict.
try:
from collections import OrderedDict
except ImportError:
--
cgit v1.2.3
From fe92a2cfee9e3a20e913500802d98a15e8b70780 Mon Sep 17 00:00:00 2001
From: JocelynDelalande
Date: Wed, 7 Jan 2015 10:42:11 +0100
Subject: fixed doc : DEFAULT_AUTHENTICATION_CLASSES -> DEFAULT_AUTHENTICATION
+ It is consistent with docs about DEFAULT_PERMISSION_CLASSES---
docs/api-guide/authentication.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md
index b04858e3..1222dbf0 100755
--- a/docs/api-guide/authentication.md
+++ b/docs/api-guide/authentication.md
@@ -34,7 +34,7 @@ The value of `request.user` and `request.auth` for unauthenticated requests can
## Setting the authentication scheme
-The default authentication schemes may be set globally, using the `DEFAULT_AUTHENTICATION` setting. For example.
+The default authentication schemes may be set globally, using the `DEFAULT_AUTHENTICATION_CLASSES` setting. For example.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
@@ -282,7 +282,7 @@ This authentication class depends on the optional [django-oauth2-provider][djang
'provider.oauth2',
)
-Then add `OAuth2Authentication` to your global `DEFAULT_AUTHENTICATION` setting:
+Then add `OAuth2Authentication` to your global `DEFAULT_AUTHENTICATION_CLASSES` setting:
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.OAuth2Authentication',
--
cgit v1.2.3
From 7913947757b0e6bd1b8828db81933c32c498e20a Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 7 Jan 2015 11:13:03 +0000
Subject: add config and documentation about uploading/downloading translations
from Transifex
---
.tx/config | 9 +++++++
CONTRIBUTING.md | 54 +++++++++++++++++++++++++++++++++++++
docs/topics/internationalisation.md | 9 ++++---
3 files changed, 69 insertions(+), 3 deletions(-)
create mode 100644 .tx/config
diff --git a/.tx/config b/.tx/config
new file mode 100644
index 00000000..271fa1e3
--- /dev/null
+++ b/.tx/config
@@ -0,0 +1,9 @@
+[main]
+host = https://www.transifex.com
+
+[django-rest-framework.djangopo]
+file_filter = rest_framework/locale//LC_MESSAGES/django.po
+source_file = rest_framework/locale/en_US/LC_MESSAGES/django.po
+source_lang = en_US
+type = PO
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b963a499..d94eb87e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -177,6 +177,57 @@ We recommend the [`django-reusable-app`][django-reusable-app] template as a good
Once your package is decently documented and available on PyPI open a pull request or issue, and we'll add a link to it from the main REST framework documentation.
+# Translations
+
+If REST framework isn't translated into your language you can request that it is at the [Transifex project][transifex].
+
+## Managing Transfiex
+The [official Transifex client][transifex-client] is used to upload and download translations to Transifex. The client is installed using pip:
+
+```
+pip install transifex-client
+```
+
+To use it you'll need a login to Transifex which has a password, and you'll need to have administrative access to the Transifex project. You'll need to create a `~/.transifexrc` file which contains your authentication information:
+
+```
+[https://www.transifex.com]
+username = user
+token =
+password = p@ssw0rd
+hostname = https://www.transifex.com
+```
+
+## Upload new source translations
+When any user-visible strings are changed, they should be uploaded to Transifex so that the translators can start to translate them. To do this, just run:
+
+```
+cd rest_framework
+django-admin.py makemessages -l en_US
+cd ..
+tx push -s
+```
+
+When pushing source files, Transifex will update the source strings of a resource to match those from the new source file.
+
+Here's how differences between the old and new source files will be handled:
+
+* New strings will be added.
+* Modified strings will be added as well.
+* Strings which do not exist in the new source file will be removed from the database, along with their translations. If that source strings gets re-added later then [Transifex Translation Memory][translation-memory] will automatically restore the translated string too.
+
+
+## Get translations
+When a translator has finished translating their work needs to be downloaded from Transifex into the source repo. To do this, run:
+
+```
+tx pull -a
+cd rest_framework
+django-admin.py compilemessages
+```
+
+You can then commit as normal.
+
[cite]: http://www.w3.org/People/Berners-Lee/FAQ.html
[code-of-conduct]: https://www.djangoproject.com/conduct/
[google-group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
@@ -190,3 +241,6 @@ Once your package is decently documented and available on PyPI open a pull reque
[docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs
[mou]: http://mouapp.com/
[django-reusable-app]: https://github.com/dabapps/django-reusable-app
+[transifex]: https://www.transifex.com/projects/p/django-rest-framework/
+[transifex-client]: https://pypi.python.org/pypi/transifex-client
+[translation-memory]: http://docs.transifex.com/guides/tm#let-tm-automatically-populate-translations
\ No newline at end of file
diff --git a/docs/topics/internationalisation.md b/docs/topics/internationalisation.md
index fac3bdb7..2a476c86 100644
--- a/docs/topics/internationalisation.md
+++ b/docs/topics/internationalisation.md
@@ -3,11 +3,13 @@ REST framework ships with translatable error messages. You can make these appea
## How to translate REST Framework errors
+REST framework translations are managed online using [Transifex.com][transifex]. To get started, checkout the guide in the [CONTRIBUTING.md guide][contributing].
-This guide assumes you are already familiar with how to translate a Django app. If you're not, start by reading [Django's translation docs][django-translation].
+Sometimes you may want to use REST Framework in a language which has not been translated yet on Transifex. If that is the case then you should translate the error messages locally.
+#### How to translate REST Framework error messages locally:
-#### To translate REST framework error messages:
+This guide assumes you are already familiar with how to translate a Django app. If you're not, start by reading [Django's translation docs][django-translation].
1. Make a new folder where you want to store the translated errors. Add this
path to your [`LOCALE_PATHS`][django-locale-paths] setting.
@@ -89,4 +91,5 @@ display as Django does. You can find more info in the [Django docs on discoveri
[django-translation]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation
[django-language-preference]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#how-django-discovers-language-preference
[django-locale-paths]: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-LOCALE_PATHS
-[django-locale-name]: https://docs.djangoproject.com/en/1.7/topics/i18n/#term-locale-name
\ No newline at end of file
+[django-locale-name]: https://docs.djangoproject.com/en/1.7/topics/i18n/#term-locale-name
+[contributing]: ../../CONTRIBUTING.md
--
cgit v1.2.3
From 9b4177b6ea38de6e86b0fe723834b6ef36af15b3 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 7 Jan 2015 11:41:06 +0000
Subject: switch to using format strings in error messages; raise NotFound when
pagination fails to provide a more useful error message
---
rest_framework/exceptions.py | 28 ++++++++++++++--------------
rest_framework/generics.py | 14 ++++++++------
2 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index c8cedfce..dfc57293 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -7,8 +7,7 @@ In addition Django's built in 403 and 404 exceptions are handled.
from __future__ import unicode_literals
from django.utils import six
from django.utils.encoding import force_text
-from django.utils.translation import ugettext_lazy as _
-from django.utils.translation import ungettext_lazy
+from django.utils.translation import ugettext_lazy as _, ungettext
from rest_framework import status
import math
@@ -96,13 +95,13 @@ class NotFound(APIException):
class MethodNotAllowed(APIException):
status_code = status.HTTP_405_METHOD_NOT_ALLOWED
- default_detail = _("Method '%s' not allowed.")
+ default_detail = _("Method {method} not allowed.")
def __init__(self, method, detail=None):
if detail is not None:
self.detail = force_text(detail)
else:
- self.detail = force_text(self.default_detail) % method
+ self.detail = force_text(self.default_detail).format(method=method)
class NotAcceptable(APIException):
@@ -119,23 +118,22 @@ class NotAcceptable(APIException):
class UnsupportedMediaType(APIException):
status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
- default_detail = _("Unsupported media type '%s' in request.")
+ default_detail = _("Unsupported media type '{media_type}' in request.")
def __init__(self, media_type, detail=None):
if detail is not None:
self.detail = force_text(detail)
else:
- self.detail = force_text(self.default_detail) % media_type
+ self.detail = force_text(self.default_detail).format(
+ media_type=media_type
+ )
class Throttled(APIException):
status_code = status.HTTP_429_TOO_MANY_REQUESTS
default_detail = _('Request was throttled.')
- extra_detail = ungettext_lazy(
- 'Expected available in %(wait)d second.',
- 'Expected available in %(wait)d seconds.',
- 'wait'
- )
+ extra_detail_singular = 'Expected available in {wait} second.'
+ extra_detail_plural = 'Expected available in {wait} seconds.'
def __init__(self, wait=None, detail=None):
if detail is not None:
@@ -147,6 +145,8 @@ class Throttled(APIException):
self.wait = None
else:
self.wait = math.ceil(wait)
- self.detail += ' ' + force_text(
- self.extra_detail % {'wait': self.wait}
- )
+ self.detail += ' ' + force_text(ungettext(
+ self.extra_detail_singular.format(wait=self.wait),
+ self.extra_detail_plural.format(wait=self.wait),
+ self.wait
+ ))
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 680992d7..fe92355d 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -10,6 +10,7 @@ from django.shortcuts import get_object_or_404 as _get_object_or_404
from django.utils import six
from django.utils.translation import ugettext as _
from rest_framework import views, mixins
+from rest_framework.exceptions import NotFound
from rest_framework.settings import api_settings
@@ -119,15 +120,16 @@ class GenericAPIView(views.APIView):
if page == 'last':
page_number = paginator.num_pages
else:
- raise Http404(_("Choose a valid page number. Page numbers must be a whole number, or must be the string 'last'."))
+ raise NotFound(_("Choose a valid page number. Page numbers must be a whole number, or must be the string 'last'."))
+
+ page_number = -1
try:
page = paginator.page(page_number)
except InvalidPage as exc:
- error_format = _('Invalid page (%(page_number)s): %(message)s')
- raise Http404(error_format % {
- 'page_number': page_number,
- 'message': six.text_type(exc)
- })
+ error_format = _('Invalid page ({page_number}): {message}')
+ raise NotFound(error_format.format(
+ page_number=page_number, message=six.text_type(exc)
+ ))
return page
--
cgit v1.2.3
From 3819ae35ac70ef25804f285b7b59edf2f67ea915 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 7 Jan 2015 11:42:36 +0000
Subject: recompile pofile with new python format strings
---
rest_framework/locale/en_US/LC_MESSAGES/django.po | 153 ++++++++++------------
1 file changed, 69 insertions(+), 84 deletions(-)
diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.po b/rest_framework/locale/en_US/LC_MESSAGES/django.po
index 56902073..7c5a6c02 100644
--- a/rest_framework/locale/en_US/LC_MESSAGES/django.po
+++ b/rest_framework/locale/en_US/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-01-02 11:10+0000\n"
+"POT-Creation-Date: 2015-01-07 11:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,282 +17,267 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: rest_framework/authtoken/serializers.py:20
+#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr ""
-#: rest_framework/authtoken/serializers.py:23
+#: authtoken/serializers.py:23
msgid "Unable to log in with provided credentials."
msgstr ""
-#: rest_framework/authtoken/serializers.py:26
+#: authtoken/serializers.py:26
msgid "Must include \"username\" and \"password\""
msgstr ""
-#: rest_framework/exceptions.py:39
+#: exceptions.py:38
msgid "A server error occurred."
msgstr ""
-#: rest_framework/exceptions.py:74
+#: exceptions.py:73
msgid "Malformed request."
msgstr ""
-#: rest_framework/exceptions.py:79
+#: exceptions.py:78
msgid "Incorrect authentication credentials."
msgstr ""
-#: rest_framework/exceptions.py:84
+#: exceptions.py:83
msgid "Authentication credentials were not provided."
msgstr ""
-#: rest_framework/exceptions.py:89
+#: exceptions.py:88
msgid "You do not have permission to perform this action."
msgstr ""
-#: rest_framework/exceptions.py:94
+#: exceptions.py:93
msgid "Not found."
msgstr ""
-#: rest_framework/exceptions.py:99
-#, python-format
-msgid "Method '%s' not allowed."
+#: exceptions.py:98
+msgid "Method {method} not allowed."
msgstr ""
-#: rest_framework/exceptions.py:110
+#: exceptions.py:109
msgid "Could not satisfy the request Accept header."
msgstr ""
-#: rest_framework/exceptions.py:122
-#, python-format
-msgid "Unsupported media type '%s' in request."
+#: exceptions.py:121
+msgid "Unsupported media type '{media_type}' in request."
msgstr ""
-#: rest_framework/exceptions.py:133
+#: exceptions.py:134
msgid "Request was throttled."
msgstr ""
-#: rest_framework/exceptions.py:135
-#, python-format
-msgid "Expected available in %(wait)d second."
-msgid_plural "Expected available in %(wait)d seconds."
-msgstr[0] ""
-msgstr[1] ""
-
-#: rest_framework/fields.py:152 rest_framework/relations.py:131
-#: rest_framework/relations.py:155 rest_framework/validators.py:77
-#: rest_framework/validators.py:155
+#: fields.py:152 relations.py:131 relations.py:155 validators.py:77
+#: validators.py:155
msgid "This field is required."
msgstr ""
-#: rest_framework/fields.py:153
+#: fields.py:153
msgid "This field may not be null."
msgstr ""
-#: rest_framework/fields.py:480 rest_framework/fields.py:508
+#: fields.py:480 fields.py:508
msgid "`{input}` is not a valid boolean."
msgstr ""
-#: rest_framework/fields.py:543
+#: fields.py:543
msgid "This field may not be blank."
msgstr ""
-#: rest_framework/fields.py:544 rest_framework/fields.py:1252
+#: fields.py:544 fields.py:1252
msgid "Ensure this field has no more than {max_length} characters."
msgstr ""
-#: rest_framework/fields.py:545
+#: fields.py:545
msgid "Ensure this field has at least {min_length} characters."
msgstr ""
-#: rest_framework/fields.py:587
+#: fields.py:587
msgid "Enter a valid email address."
msgstr ""
-#: rest_framework/fields.py:604
+#: fields.py:604
msgid "This value does not match the required pattern."
msgstr ""
-#: rest_framework/fields.py:615
+#: fields.py:615
msgid ""
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
msgstr ""
-#: rest_framework/fields.py:627
+#: fields.py:627
msgid "Enter a valid URL."
msgstr ""
-#: rest_framework/fields.py:640
+#: fields.py:640
msgid "A valid integer is required."
msgstr ""
-#: rest_framework/fields.py:641 rest_framework/fields.py:675
-#: rest_framework/fields.py:708
+#: fields.py:641 fields.py:675 fields.py:708
msgid "Ensure this value is less than or equal to {max_value}."
msgstr ""
-#: rest_framework/fields.py:642 rest_framework/fields.py:676
-#: rest_framework/fields.py:709
+#: fields.py:642 fields.py:676 fields.py:709
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr ""
-#: rest_framework/fields.py:643 rest_framework/fields.py:677
-#: rest_framework/fields.py:713
+#: fields.py:643 fields.py:677 fields.py:713
msgid "String value too large."
msgstr ""
-#: rest_framework/fields.py:674 rest_framework/fields.py:707
+#: fields.py:674 fields.py:707
msgid "A valid number is required."
msgstr ""
-#: rest_framework/fields.py:710
+#: fields.py:710
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr ""
-#: rest_framework/fields.py:711
+#: fields.py:711
msgid "Ensure that there are no more than {max_decimal_places} decimal places."
msgstr ""
-#: rest_framework/fields.py:712
+#: fields.py:712
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
-#: rest_framework/fields.py:796
+#: fields.py:796
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr ""
-#: rest_framework/fields.py:797
+#: fields.py:797
msgid "Expected a datetime but got a date."
msgstr ""
-#: rest_framework/fields.py:861
+#: fields.py:861
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr ""
-#: rest_framework/fields.py:862
+#: fields.py:862
msgid "Expected a date but got a datetime."
msgstr ""
-#: rest_framework/fields.py:919
+#: fields.py:919
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
-#: rest_framework/fields.py:975 rest_framework/fields.py:1019
+#: fields.py:975 fields.py:1019
msgid "`{input}` is not a valid choice."
msgstr ""
-#: rest_framework/fields.py:1020 rest_framework/fields.py:1121
-#: rest_framework/serializers.py:476
+#: fields.py:1020 fields.py:1121 serializers.py:476
msgid "Expected a list of items but got type `{input_type}`."
msgstr ""
-#: rest_framework/fields.py:1050
+#: fields.py:1050
msgid "No file was submitted."
msgstr ""
-#: rest_framework/fields.py:1051
+#: fields.py:1051
msgid "The submitted data was not a file. Check the encoding type on the form."
msgstr ""
-#: rest_framework/fields.py:1052
+#: fields.py:1052
msgid "No filename could be determined."
msgstr ""
-#: rest_framework/fields.py:1053
+#: fields.py:1053
msgid "The submitted file is empty."
msgstr ""
-#: rest_framework/fields.py:1054
+#: fields.py:1054
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
-#: rest_framework/fields.py:1096
+#: fields.py:1096
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
-#: rest_framework/generics.py:122
+#: generics.py:123
msgid ""
"Choose a valid page number. Page numbers must be a whole number, or must be "
"the string 'last'."
msgstr ""
-#: rest_framework/generics.py:126
-#, python-format
-msgid "Invalid page (%(page_number)s): %(message)s"
+#: generics.py:129
+msgid "Invalid page ({page_number}): {message}"
msgstr ""
-#: rest_framework/relations.py:132
+#: relations.py:132
msgid "Invalid pk '{pk_value}' - object does not exist."
msgstr ""
-#: rest_framework/relations.py:133
+#: relations.py:133
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
-#: rest_framework/relations.py:156
+#: relations.py:156
msgid "Invalid hyperlink - No URL match"
msgstr ""
-#: rest_framework/relations.py:157
+#: relations.py:157
msgid "Invalid hyperlink - Incorrect URL match."
msgstr ""
-#: rest_framework/relations.py:158
+#: relations.py:158
msgid "Invalid hyperlink - Object does not exist."
msgstr ""
-#: rest_framework/relations.py:159
+#: relations.py:159
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
-#: rest_framework/relations.py:294
+#: relations.py:294
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
-#: rest_framework/relations.py:295
+#: relations.py:295
msgid "Invalid value."
msgstr ""
-#: rest_framework/serializers.py:299
+#: serializers.py:299
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr ""
-#: rest_framework/validators.py:22
+#: validators.py:22
msgid "This field must be unique."
msgstr ""
-#: rest_framework/validators.py:76
+#: validators.py:76
msgid "The fields {field_names} must make a unique set."
msgstr ""
-#: rest_framework/validators.py:219
+#: validators.py:219
msgid "This field must be unique for the \"{date_field}\" date."
msgstr ""
-#: rest_framework/validators.py:234
+#: validators.py:234
msgid "This field must be unique for the \"{date_field}\" month."
msgstr ""
-#: rest_framework/validators.py:247
+#: validators.py:247
msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
-#: rest_framework/versioning.py:39
+#: versioning.py:39
msgid "Invalid version in 'Accept' header."
msgstr ""
-#: rest_framework/versioning.py:70 rest_framework/versioning.py:112
+#: versioning.py:70 versioning.py:112
msgid "Invalid version in URL path."
msgstr ""
-#: rest_framework/versioning.py:138
+#: versioning.py:138
msgid "Invalid version in hostname."
msgstr ""
-#: rest_framework/versioning.py:160
+#: versioning.py:160
msgid "Invalid version in query parameter."
msgstr ""
--
cgit v1.2.3
From fe5d93c8cbc5f3a9b1b6715208c70f485be68bdf Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 7 Jan 2015 11:44:18 +0000
Subject: remove hardcoded page number
---
rest_framework/generics.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index fe92355d..7c4d5e95 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -122,7 +122,6 @@ class GenericAPIView(views.APIView):
else:
raise NotFound(_("Choose a valid page number. Page numbers must be a whole number, or must be the string 'last'."))
- page_number = -1
try:
page = paginator.page(page_number)
except InvalidPage as exc:
--
cgit v1.2.3
From 4c32083b8b59a50877633910055313dad7bb117e Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 7 Jan 2015 12:01:11 +0000
Subject: use double quotes for user visible strings; end user visible strings
in full stops; add some missing translation tags
---
rest_framework/authentication.py | 17 +++++----
rest_framework/authtoken/serializers.py | 6 +--
rest_framework/exceptions.py | 24 ++++++------
rest_framework/fields.py | 68 ++++++++++++++++-----------------
rest_framework/generics.py | 2 +-
rest_framework/relations.py | 16 ++++----
rest_framework/serializers.py | 4 +-
rest_framework/validators.py | 14 +++----
rest_framework/versioning.py | 8 ++--
9 files changed, 80 insertions(+), 79 deletions(-)
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
index 124ef68a..7e86a7b9 100644
--- a/rest_framework/authentication.py
+++ b/rest_framework/authentication.py
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import base64
from django.contrib.auth import authenticate
from django.middleware.csrf import CsrfViewMiddleware
+from django.utils.translation import ugettext_lazy as _
from rest_framework import exceptions, HTTP_HEADER_ENCODING
from rest_framework.authtoken.models import Token
@@ -65,16 +66,16 @@ class BasicAuthentication(BaseAuthentication):
return None
if len(auth) == 1:
- msg = 'Invalid basic header. No credentials provided.'
+ msg = _("Invalid basic header. No credentials provided.")
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
- msg = 'Invalid basic header. Credentials string should not contain spaces.'
+ msg = _("Invalid basic header. Credentials string should not contain spaces.")
raise exceptions.AuthenticationFailed(msg)
try:
auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
except (TypeError, UnicodeDecodeError):
- msg = 'Invalid basic header. Credentials not correctly base64 encoded'
+ msg = _("Invalid basic header. Credentials not correctly base64 encoded.")
raise exceptions.AuthenticationFailed(msg)
userid, password = auth_parts[0], auth_parts[2]
@@ -86,7 +87,7 @@ class BasicAuthentication(BaseAuthentication):
"""
user = authenticate(username=userid, password=password)
if user is None or not user.is_active:
- raise exceptions.AuthenticationFailed('Invalid username/password')
+ raise exceptions.AuthenticationFailed(_("Invalid username/password."))
return (user, None)
def authenticate_header(self, request):
@@ -152,10 +153,10 @@ class TokenAuthentication(BaseAuthentication):
return None
if len(auth) == 1:
- msg = 'Invalid token header. No credentials provided.'
+ msg = _("Invalid token header. No credentials provided.")
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
- msg = 'Invalid token header. Token string should not contain spaces.'
+ msg = _("Invalid token header. Token string should not contain spaces.")
raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(auth[1])
@@ -164,10 +165,10 @@ class TokenAuthentication(BaseAuthentication):
try:
token = self.model.objects.get(key=key)
except self.model.DoesNotExist:
- raise exceptions.AuthenticationFailed('Invalid token')
+ raise exceptions.AuthenticationFailed(_("Invalid token"))
if not token.user.is_active:
- raise exceptions.AuthenticationFailed('User inactive or deleted')
+ raise exceptions.AuthenticationFailed(_("User inactive or deleted"))
return (token.user, token)
diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py
index f31dded1..78fe6a11 100644
--- a/rest_framework/authtoken/serializers.py
+++ b/rest_framework/authtoken/serializers.py
@@ -17,13 +17,13 @@ class AuthTokenSerializer(serializers.Serializer):
if user:
if not user.is_active:
- msg = _('User account is disabled.')
+ msg = _("User account is disabled.")
raise exceptions.ValidationError(msg)
else:
- msg = _('Unable to log in with provided credentials.')
+ msg = _("Unable to log in with provided credentials.")
raise exceptions.ValidationError(msg)
else:
- msg = _('Must include "username" and "password"')
+ msg = _("Must include \"username\" and \"password\"")
raise exceptions.ValidationError(msg)
attrs['user'] = user
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index dfc57293..3ca8e538 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -35,7 +35,7 @@ class APIException(Exception):
Subclasses should provide `.status_code` and `.default_detail` properties.
"""
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
- default_detail = _('A server error occurred.')
+ default_detail = _("A server error occurred.")
def __init__(self, detail=None):
if detail is not None:
@@ -52,7 +52,7 @@ class APIException(Exception):
# built in `ValidationError`. For example:
#
# from rest_framework import serializers
-# raise serializers.ValidationError('Value was invalid')
+# raise serializers.ValidationError("Value was invalid")
class ValidationError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
@@ -70,32 +70,32 @@ class ValidationError(APIException):
class ParseError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
- default_detail = _('Malformed request.')
+ default_detail = _("Malformed request.")
class AuthenticationFailed(APIException):
status_code = status.HTTP_401_UNAUTHORIZED
- default_detail = _('Incorrect authentication credentials.')
+ default_detail = _("Incorrect authentication credentials.")
class NotAuthenticated(APIException):
status_code = status.HTTP_401_UNAUTHORIZED
- default_detail = _('Authentication credentials were not provided.')
+ default_detail = _("Authentication credentials were not provided.")
class PermissionDenied(APIException):
status_code = status.HTTP_403_FORBIDDEN
- default_detail = _('You do not have permission to perform this action.')
+ default_detail = _("You do not have permission to perform this action.")
class NotFound(APIException):
status_code = status.HTTP_404_NOT_FOUND
- default_detail = _('Not found.')
+ default_detail = _("Not found.")
class MethodNotAllowed(APIException):
status_code = status.HTTP_405_METHOD_NOT_ALLOWED
- default_detail = _("Method {method} not allowed.")
+ default_detail = _("Method '{method}' not allowed.")
def __init__(self, method, detail=None):
if detail is not None:
@@ -106,7 +106,7 @@ class MethodNotAllowed(APIException):
class NotAcceptable(APIException):
status_code = status.HTTP_406_NOT_ACCEPTABLE
- default_detail = _('Could not satisfy the request Accept header.')
+ default_detail = _("Could not satisfy the request Accept header.")
def __init__(self, detail=None, available_renderers=None):
if detail is not None:
@@ -131,9 +131,9 @@ class UnsupportedMediaType(APIException):
class Throttled(APIException):
status_code = status.HTTP_429_TOO_MANY_REQUESTS
- default_detail = _('Request was throttled.')
- extra_detail_singular = 'Expected available in {wait} second.'
- extra_detail_plural = 'Expected available in {wait} seconds.'
+ default_detail = _("Request was throttled.")
+ extra_detail_singular = "Expected available in {wait} second."
+ extra_detail_plural = "Expected available in {wait} seconds."
def __init__(self, wait=None, detail=None):
if detail is not None:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 0ff2b073..8a781b35 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -149,8 +149,8 @@ class Field(object):
_creation_counter = 0
default_error_messages = {
- 'required': _('This field is required.'),
- 'null': _('This field may not be null.')
+ 'required': _("This field is required."),
+ 'null': _("This field may not be null.")
}
default_validators = []
default_empty_html = empty
@@ -477,7 +477,7 @@ class Field(object):
class BooleanField(Field):
default_error_messages = {
- 'invalid': _('`{input}` is not a valid boolean.')
+ 'invalid': _("`{input}` is not a valid boolean.")
}
default_empty_html = False
initial = False
@@ -505,7 +505,7 @@ class BooleanField(Field):
class NullBooleanField(Field):
default_error_messages = {
- 'invalid': _('`{input}` is not a valid boolean.')
+ 'invalid': _("`{input}` is not a valid boolean.")
}
initial = None
TRUE_VALUES = set(('t', 'T', 'true', 'True', 'TRUE', '1', 1, True))
@@ -540,9 +540,9 @@ class NullBooleanField(Field):
class CharField(Field):
default_error_messages = {
- 'blank': _('This field may not be blank.'),
- 'max_length': _('Ensure this field has no more than {max_length} characters.'),
- 'min_length': _('Ensure this field has at least {min_length} characters.')
+ 'blank': _("This field may not be blank."),
+ 'max_length': _("Ensure this field has no more than {max_length} characters."),
+ 'min_length': _("Ensure this field has at least {min_length} characters.")
}
initial = ''
coerce_blank_to_null = False
@@ -584,7 +584,7 @@ class CharField(Field):
class EmailField(CharField):
default_error_messages = {
- 'invalid': _('Enter a valid email address.')
+ 'invalid': _("Enter a valid email address.")
}
def __init__(self, **kwargs):
@@ -601,7 +601,7 @@ class EmailField(CharField):
class RegexField(CharField):
default_error_messages = {
- 'invalid': _('This value does not match the required pattern.')
+ 'invalid': _("This value does not match the required pattern.")
}
def __init__(self, regex, **kwargs):
@@ -637,10 +637,10 @@ class URLField(CharField):
class IntegerField(Field):
default_error_messages = {
- 'invalid': _('A valid integer is required.'),
- 'max_value': _('Ensure this value is less than or equal to {max_value}.'),
- 'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
- 'max_string_length': _('String value too large.')
+ 'invalid': _("A valid integer is required."),
+ 'max_value': _("Ensure this value is less than or equal to {max_value}."),
+ 'min_value': _("Ensure this value is greater than or equal to {min_value}."),
+ 'max_string_length': _("String value too large.")
}
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
@@ -672,9 +672,9 @@ class IntegerField(Field):
class FloatField(Field):
default_error_messages = {
'invalid': _("A valid number is required."),
- 'max_value': _('Ensure this value is less than or equal to {max_value}.'),
- 'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
- 'max_string_length': _('String value too large.')
+ 'max_value': _("Ensure this value is less than or equal to {max_value}."),
+ 'min_value': _("Ensure this value is greater than or equal to {min_value}."),
+ 'max_string_length': _("String value too large.")
}
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
@@ -704,13 +704,13 @@ class FloatField(Field):
class DecimalField(Field):
default_error_messages = {
- 'invalid': _('A valid number is required.'),
- 'max_value': _('Ensure this value is less than or equal to {max_value}.'),
- 'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
- 'max_digits': _('Ensure that there are no more than {max_digits} digits in total.'),
- 'max_decimal_places': _('Ensure that there are no more than {max_decimal_places} decimal places.'),
- 'max_whole_digits': _('Ensure that there are no more than {max_whole_digits} digits before the decimal point.'),
- 'max_string_length': _('String value too large.')
+ 'invalid': _("A valid number is required."),
+ 'max_value': _("Ensure this value is less than or equal to {max_value}."),
+ 'min_value': _("Ensure this value is greater than or equal to {min_value}."),
+ 'max_digits': _("Ensure that there are no more than {max_digits} digits in total."),
+ 'max_decimal_places': _("Ensure that there are no more than {max_decimal_places} decimal places."),
+ 'max_whole_digits': _("Ensure that there are no more than {max_whole_digits} digits before the decimal point."),
+ 'max_string_length': _("String value too large.")
}
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
@@ -793,8 +793,8 @@ class DecimalField(Field):
class DateTimeField(Field):
default_error_messages = {
- 'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'),
- 'date': _('Expected a datetime but got a date.'),
+ 'invalid': _("Datetime has wrong format. Use one of these formats instead: {format}."),
+ 'date': _("Expected a datetime but got a date."),
}
format = api_settings.DATETIME_FORMAT
input_formats = api_settings.DATETIME_INPUT_FORMATS
@@ -858,8 +858,8 @@ class DateTimeField(Field):
class DateField(Field):
default_error_messages = {
- 'invalid': _('Date has wrong format. Use one of these formats instead: {format}.'),
- 'datetime': _('Expected a date but got a datetime.'),
+ 'invalid': _("Date has wrong format. Use one of these formats instead: {format}."),
+ 'datetime': _("Expected a date but got a datetime."),
}
format = api_settings.DATE_FORMAT
input_formats = api_settings.DATE_INPUT_FORMATS
@@ -916,7 +916,7 @@ class DateField(Field):
class TimeField(Field):
default_error_messages = {
- 'invalid': _('Time has wrong format. Use one of these formats instead: {format}.'),
+ 'invalid': _("Time has wrong format. Use one of these formats instead: {format}."),
}
format = api_settings.TIME_FORMAT
input_formats = api_settings.TIME_INPUT_FORMATS
@@ -972,7 +972,7 @@ class TimeField(Field):
class ChoiceField(Field):
default_error_messages = {
- 'invalid_choice': _('`{input}` is not a valid choice.')
+ 'invalid_choice': _("`{input}` is not a valid choice.")
}
def __init__(self, choices, **kwargs):
@@ -1016,8 +1016,8 @@ class ChoiceField(Field):
class MultipleChoiceField(ChoiceField):
default_error_messages = {
- 'invalid_choice': _('`{input}` is not a valid choice.'),
- 'not_a_list': _('Expected a list of items but got type `{input_type}`.')
+ 'invalid_choice': _("`{input}` is not a valid choice."),
+ 'not_a_list': _("Expected a list of items but got type `{input_type}`.")
}
default_empty_html = []
@@ -1051,7 +1051,7 @@ class FileField(Field):
'invalid': _("The submitted data was not a file. Check the encoding type on the form."),
'no_name': _("No filename could be determined."),
'empty': _("The submitted file is empty."),
- 'max_length': _('Ensure this filename has at most {max_length} characters (it has {length}).'),
+ 'max_length': _("Ensure this filename has at most {max_length} characters (it has {length})."),
}
use_url = api_settings.UPLOADED_FILES_USE_URL
@@ -1118,7 +1118,7 @@ class ListField(Field):
child = None
initial = []
default_error_messages = {
- 'not_a_list': _('Expected a list of items but got type `{input_type}`.')
+ 'not_a_list': _("Expected a list of items but got type `{input_type}`.")
}
def __init__(self, *args, **kwargs):
@@ -1249,7 +1249,7 @@ class ModelField(Field):
that do not have a serializer field to be mapped to.
"""
default_error_messages = {
- 'max_length': _('Ensure this field has no more than {max_length} characters.'),
+ 'max_length': _("Ensure this field has no more than {max_length} characters."),
}
def __init__(self, model_field, **kwargs):
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 7c4d5e95..c7053d8f 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -125,7 +125,7 @@ class GenericAPIView(views.APIView):
try:
page = paginator.page(page_number)
except InvalidPage as exc:
- error_format = _('Invalid page ({page_number}): {message}')
+ error_format = _("Invalid page ({page_number}): {message}.")
raise NotFound(error_format.format(
page_number=page_number, message=six.text_type(exc)
))
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 7b119291..3737b21f 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -128,9 +128,9 @@ class StringRelatedField(RelatedField):
class PrimaryKeyRelatedField(RelatedField):
default_error_messages = {
- 'required': _('This field is required.'),
+ 'required': _("This field is required."),
'does_not_exist': _("Invalid pk '{pk_value}' - object does not exist."),
- 'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'),
+ 'incorrect_type': _("Incorrect type. Expected pk value, received {data_type}."),
}
def use_pk_only_optimization(self):
@@ -152,11 +152,11 @@ class HyperlinkedRelatedField(RelatedField):
lookup_field = 'pk'
default_error_messages = {
- 'required': _('This field is required.'),
- 'no_match': _('Invalid hyperlink - No URL match'),
- 'incorrect_match': _('Invalid hyperlink - Incorrect URL match.'),
- 'does_not_exist': _('Invalid hyperlink - Object does not exist.'),
- 'incorrect_type': _('Incorrect type. Expected URL string, received {data_type}.'),
+ 'required': _("This field is required."),
+ 'no_match': _("Invalid hyperlink - No URL match."),
+ 'incorrect_match': _("Invalid hyperlink - Incorrect URL match."),
+ 'does_not_exist': _("Invalid hyperlink - Object does not exist."),
+ 'incorrect_type': _("Incorrect type. Expected URL string, received {data_type}."),
}
def __init__(self, view_name=None, **kwargs):
@@ -292,7 +292,7 @@ class SlugRelatedField(RelatedField):
default_error_messages = {
'does_not_exist': _("Object with {slug_name}={value} does not exist."),
- 'invalid': _('Invalid value.'),
+ 'invalid': _("Invalid value."),
}
def __init__(self, slug_field=None, **kwargs):
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 623ed586..9d7c8884 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -296,7 +296,7 @@ def get_validation_error_detail(exc):
@six.add_metaclass(SerializerMetaclass)
class Serializer(BaseSerializer):
default_error_messages = {
- 'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.')
+ 'invalid': _("Invalid data. Expected a dictionary, but got {datatype}.")
}
@property
@@ -473,7 +473,7 @@ class ListSerializer(BaseSerializer):
many = True
default_error_messages = {
- 'not_a_list': _('Expected a list of items but got type `{input_type}`.')
+ 'not_a_list': _("Expected a list of items but got type `{input_type}`.")
}
def __init__(self, *args, **kwargs):
diff --git a/rest_framework/validators.py b/rest_framework/validators.py
index e3719b8d..cf6f0718 100644
--- a/rest_framework/validators.py
+++ b/rest_framework/validators.py
@@ -19,7 +19,7 @@ class UniqueValidator:
Should be applied to an individual field on the serializer.
"""
- message = _('This field must be unique.')
+ message = _("This field must be unique.")
def __init__(self, queryset, message=None):
self.queryset = queryset
@@ -73,8 +73,8 @@ class UniqueTogetherValidator:
Should be applied to the serializer class, not to an individual field.
"""
- message = _('The fields {field_names} must make a unique set.')
- missing_message = _('This field is required.')
+ message = _("The fields {field_names} must make a unique set.")
+ missing_message = _("This field is required.")
def __init__(self, queryset, fields, message=None):
self.queryset = queryset
@@ -152,7 +152,7 @@ class UniqueTogetherValidator:
class BaseUniqueForValidator:
message = None
- missing_message = _('This field is required.')
+ missing_message = _("This field is required.")
def __init__(self, queryset, field, date_field, message=None):
self.queryset = queryset
@@ -216,7 +216,7 @@ class BaseUniqueForValidator:
class UniqueForDateValidator(BaseUniqueForValidator):
- message = _('This field must be unique for the "{date_field}" date.')
+ message = _("This field must be unique for the \"{date_field}\" date.")
def filter_queryset(self, attrs, queryset):
value = attrs[self.field]
@@ -231,7 +231,7 @@ class UniqueForDateValidator(BaseUniqueForValidator):
class UniqueForMonthValidator(BaseUniqueForValidator):
- message = _('This field must be unique for the "{date_field}" month.')
+ message = _("This field must be unique for the \"{date_field}\" month.")
def filter_queryset(self, attrs, queryset):
value = attrs[self.field]
@@ -244,7 +244,7 @@ class UniqueForMonthValidator(BaseUniqueForValidator):
class UniqueForYearValidator(BaseUniqueForValidator):
- message = _('This field must be unique for the "{date_field}" year.')
+ message = _("This field must be unique for the \"{date_field}\" year.")
def filter_queryset(self, attrs, queryset):
value = attrs[self.field]
diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py
index 440efd13..587ba9f1 100644
--- a/rest_framework/versioning.py
+++ b/rest_framework/versioning.py
@@ -67,7 +67,7 @@ class URLPathVersioning(BaseVersioning):
Host: example.com
Accept: application/json
"""
- invalid_version_message = _('Invalid version in URL path.')
+ invalid_version_message = _("Invalid version in URL path.")
def determine_version(self, request, *args, **kwargs):
version = kwargs.get(self.version_param, self.default_version)
@@ -109,7 +109,7 @@ class NamespaceVersioning(BaseVersioning):
Host: example.com
Accept: application/json
"""
- invalid_version_message = _('Invalid version in URL path.')
+ invalid_version_message = _("Invalid version in URL path.")
def determine_version(self, request, *args, **kwargs):
resolver_match = getattr(request, 'resolver_match', None)
@@ -135,7 +135,7 @@ class HostNameVersioning(BaseVersioning):
Accept: application/json
"""
hostname_regex = re.compile(r'^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$')
- invalid_version_message = _('Invalid version in hostname.')
+ invalid_version_message = _("Invalid version in hostname.")
def determine_version(self, request, *args, **kwargs):
hostname, seperator, port = request.get_host().partition(':')
@@ -157,7 +157,7 @@ class QueryParameterVersioning(BaseVersioning):
Host: example.com
Accept: application/json
"""
- invalid_version_message = _('Invalid version in query parameter.')
+ invalid_version_message = _("Invalid version in query parameter.")
def determine_version(self, request, *args, **kwargs):
version = request.query_params.get(self.version_param)
--
cgit v1.2.3
From 662a907bdf821c29b42b60ce2b44eb8149a85bd7 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 7 Jan 2015 12:02:04 +0000
Subject: update source strings
---
rest_framework/locale/en_US/LC_MESSAGES/django.po | 42 ++++++++++++++++++++---
1 file changed, 37 insertions(+), 5 deletions(-)
diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.po b/rest_framework/locale/en_US/LC_MESSAGES/django.po
index 7c5a6c02..5d0d3a04 100644
--- a/rest_framework/locale/en_US/LC_MESSAGES/django.po
+++ b/rest_framework/locale/en_US/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-01-07 11:40+0000\n"
+"POT-Creation-Date: 2015-01-07 11:58+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,6 +17,38 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr ""
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr ""
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr ""
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr ""
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr ""
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr ""
+
+#: authentication.py:168
+msgid "Invalid token"
+msgstr ""
+
+#: authentication.py:171
+msgid "User inactive or deleted"
+msgstr ""
+
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr ""
@@ -54,7 +86,7 @@ msgid "Not found."
msgstr ""
#: exceptions.py:98
-msgid "Method {method} not allowed."
+msgid "Method '{method}' not allowed."
msgstr ""
#: exceptions.py:109
@@ -206,8 +238,8 @@ msgid ""
"the string 'last'."
msgstr ""
-#: generics.py:129
-msgid "Invalid page ({page_number}): {message}"
+#: generics.py:128
+msgid "Invalid page ({page_number}): {message}."
msgstr ""
#: relations.py:132
@@ -219,7 +251,7 @@ msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
#: relations.py:156
-msgid "Invalid hyperlink - No URL match"
+msgid "Invalid hyperlink - No URL match."
msgstr ""
#: relations.py:157
--
cgit v1.2.3
From 9a4267049ba37883e3e0c21b5d453b9551343b8d Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 7 Jan 2015 12:33:37 +0000
Subject: use double quotes in user messages
---
rest_framework/exceptions.py | 4 ++--
rest_framework/fields.py | 2 +-
rest_framework/generics.py | 4 ++--
rest_framework/relations.py | 2 +-
rest_framework/versioning.py | 2 +-
5 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index 3ca8e538..f8a43871 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -95,7 +95,7 @@ class NotFound(APIException):
class MethodNotAllowed(APIException):
status_code = status.HTTP_405_METHOD_NOT_ALLOWED
- default_detail = _("Method '{method}' not allowed.")
+ default_detail = _("Method \"{method}\" not allowed.")
def __init__(self, method, detail=None):
if detail is not None:
@@ -118,7 +118,7 @@ class NotAcceptable(APIException):
class UnsupportedMediaType(APIException):
status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
- default_detail = _("Unsupported media type '{media_type}' in request.")
+ default_detail = _("Unsupported media type \"{media_type}\" in request.")
def __init__(self, media_type, detail=None):
if detail is not None:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 8a781b35..27944608 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -612,7 +612,7 @@ class RegexField(CharField):
class SlugField(CharField):
default_error_messages = {
- 'invalid': _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.")
+ 'invalid': _("Enter a valid \"slug\" consisting of letters, numbers, underscores or hyphens.")
}
def __init__(self, **kwargs):
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index c7053d8f..738ba544 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -120,12 +120,12 @@ class GenericAPIView(views.APIView):
if page == 'last':
page_number = paginator.num_pages
else:
- raise NotFound(_("Choose a valid page number. Page numbers must be a whole number, or must be the string 'last'."))
+ raise NotFound(_("Choose a valid page number. Page numbers must be a whole number, or must be the string \"last\"."))
try:
page = paginator.page(page_number)
except InvalidPage as exc:
- error_format = _("Invalid page ({page_number}): {message}.")
+ error_format = _("Invalid page \"{page_number}\": {message}.")
raise NotFound(error_format.format(
page_number=page_number, message=six.text_type(exc)
))
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 3737b21f..42b624e7 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -129,7 +129,7 @@ class StringRelatedField(RelatedField):
class PrimaryKeyRelatedField(RelatedField):
default_error_messages = {
'required': _("This field is required."),
- 'does_not_exist': _("Invalid pk '{pk_value}' - object does not exist."),
+ 'does_not_exist': _("Invalid pk \"{pk_value}\" - object does not exist."),
'incorrect_type': _("Incorrect type. Expected pk value, received {data_type}."),
}
diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py
index 587ba9f1..819c32df 100644
--- a/rest_framework/versioning.py
+++ b/rest_framework/versioning.py
@@ -36,7 +36,7 @@ class AcceptHeaderVersioning(BaseVersioning):
Host: example.com
Accept: application/json; version=1.0
"""
- invalid_version_message = _("Invalid version in 'Accept' header.")
+ invalid_version_message = _("Invalid version in \"Accept\" header.")
def determine_version(self, request, *args, **kwargs):
media_type = _MediaType(request.accepted_media_type)
--
cgit v1.2.3
From 91e316f7810157474d6246cd0024bd7f7cc31ff7 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 7 Jan 2015 12:46:23 +0000
Subject: prefer single quotes in source and double quotes in user visible
strings; add some missing full stops to user visible strings
---
rest_framework/authentication.py | 16 ++---
rest_framework/authtoken/serializers.py | 6 +-
rest_framework/exceptions.py | 24 +++----
rest_framework/fields.py | 82 +++++++++++------------
rest_framework/generics.py | 4 +-
rest_framework/locale/en_US/LC_MESSAGES/django.po | 23 ++++---
rest_framework/relations.py | 20 +++---
rest_framework/serializers.py | 4 +-
rest_framework/validators.py | 14 ++--
rest_framework/versioning.py | 10 +--
tests/test_fields.py | 2 +-
tests/test_generics.py | 6 +-
tests/test_relations.py | 2 +-
13 files changed, 107 insertions(+), 106 deletions(-)
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
index 7e86a7b9..11db0585 100644
--- a/rest_framework/authentication.py
+++ b/rest_framework/authentication.py
@@ -66,16 +66,16 @@ class BasicAuthentication(BaseAuthentication):
return None
if len(auth) == 1:
- msg = _("Invalid basic header. No credentials provided.")
+ msg = _('Invalid basic header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
- msg = _("Invalid basic header. Credentials string should not contain spaces.")
+ msg = _('Invalid basic header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
except (TypeError, UnicodeDecodeError):
- msg = _("Invalid basic header. Credentials not correctly base64 encoded.")
+ msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
raise exceptions.AuthenticationFailed(msg)
userid, password = auth_parts[0], auth_parts[2]
@@ -87,7 +87,7 @@ class BasicAuthentication(BaseAuthentication):
"""
user = authenticate(username=userid, password=password)
if user is None or not user.is_active:
- raise exceptions.AuthenticationFailed(_("Invalid username/password."))
+ raise exceptions.AuthenticationFailed(_('Invalid username/password.'))
return (user, None)
def authenticate_header(self, request):
@@ -153,10 +153,10 @@ class TokenAuthentication(BaseAuthentication):
return None
if len(auth) == 1:
- msg = _("Invalid token header. No credentials provided.")
+ msg = _('Invalid token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
- msg = _("Invalid token header. Token string should not contain spaces.")
+ msg = _('Invalid token header. Token string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(auth[1])
@@ -165,10 +165,10 @@ class TokenAuthentication(BaseAuthentication):
try:
token = self.model.objects.get(key=key)
except self.model.DoesNotExist:
- raise exceptions.AuthenticationFailed(_("Invalid token"))
+ raise exceptions.AuthenticationFailed(_('Invalid token.'))
if not token.user.is_active:
- raise exceptions.AuthenticationFailed(_("User inactive or deleted"))
+ raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
return (token.user, token)
diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py
index 78fe6a11..37ade255 100644
--- a/rest_framework/authtoken/serializers.py
+++ b/rest_framework/authtoken/serializers.py
@@ -17,13 +17,13 @@ class AuthTokenSerializer(serializers.Serializer):
if user:
if not user.is_active:
- msg = _("User account is disabled.")
+ msg = _('User account is disabled.')
raise exceptions.ValidationError(msg)
else:
- msg = _("Unable to log in with provided credentials.")
+ msg = _('Unable to log in with provided credentials.')
raise exceptions.ValidationError(msg)
else:
- msg = _("Must include \"username\" and \"password\"")
+ msg = _('Must include "username" and "password".')
raise exceptions.ValidationError(msg)
attrs['user'] = user
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index f8a43871..f62c9fe3 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -35,7 +35,7 @@ class APIException(Exception):
Subclasses should provide `.status_code` and `.default_detail` properties.
"""
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
- default_detail = _("A server error occurred.")
+ default_detail = _('A server error occurred.')
def __init__(self, detail=None):
if detail is not None:
@@ -70,32 +70,32 @@ class ValidationError(APIException):
class ParseError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
- default_detail = _("Malformed request.")
+ default_detail = _('Malformed request.')
class AuthenticationFailed(APIException):
status_code = status.HTTP_401_UNAUTHORIZED
- default_detail = _("Incorrect authentication credentials.")
+ default_detail = _('Incorrect authentication credentials.')
class NotAuthenticated(APIException):
status_code = status.HTTP_401_UNAUTHORIZED
- default_detail = _("Authentication credentials were not provided.")
+ default_detail = _('Authentication credentials were not provided.')
class PermissionDenied(APIException):
status_code = status.HTTP_403_FORBIDDEN
- default_detail = _("You do not have permission to perform this action.")
+ default_detail = _('You do not have permission to perform this action.')
class NotFound(APIException):
status_code = status.HTTP_404_NOT_FOUND
- default_detail = _("Not found.")
+ default_detail = _('Not found.')
class MethodNotAllowed(APIException):
status_code = status.HTTP_405_METHOD_NOT_ALLOWED
- default_detail = _("Method \"{method}\" not allowed.")
+ default_detail = _('Method "{method}" not allowed.')
def __init__(self, method, detail=None):
if detail is not None:
@@ -106,7 +106,7 @@ class MethodNotAllowed(APIException):
class NotAcceptable(APIException):
status_code = status.HTTP_406_NOT_ACCEPTABLE
- default_detail = _("Could not satisfy the request Accept header.")
+ default_detail = _('Could not satisfy the request Accept header.')
def __init__(self, detail=None, available_renderers=None):
if detail is not None:
@@ -118,7 +118,7 @@ class NotAcceptable(APIException):
class UnsupportedMediaType(APIException):
status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
- default_detail = _("Unsupported media type \"{media_type}\" in request.")
+ default_detail = _('Unsupported media type "{media_type}" in request.')
def __init__(self, media_type, detail=None):
if detail is not None:
@@ -131,9 +131,9 @@ class UnsupportedMediaType(APIException):
class Throttled(APIException):
status_code = status.HTTP_429_TOO_MANY_REQUESTS
- default_detail = _("Request was throttled.")
- extra_detail_singular = "Expected available in {wait} second."
- extra_detail_plural = "Expected available in {wait} seconds."
+ default_detail = _('Request was throttled.')
+ extra_detail_singular = 'Expected available in {wait} second.'
+ extra_detail_plural = 'Expected available in {wait} seconds.'
def __init__(self, wait=None, detail=None):
if detail is not None:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 27944608..76101608 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -149,8 +149,8 @@ class Field(object):
_creation_counter = 0
default_error_messages = {
- 'required': _("This field is required."),
- 'null': _("This field may not be null.")
+ 'required': _('This field is required.'),
+ 'null': _('This field may not be null.')
}
default_validators = []
default_empty_html = empty
@@ -477,7 +477,7 @@ class Field(object):
class BooleanField(Field):
default_error_messages = {
- 'invalid': _("`{input}` is not a valid boolean.")
+ 'invalid': _('`{input}` is not a valid boolean.')
}
default_empty_html = False
initial = False
@@ -505,7 +505,7 @@ class BooleanField(Field):
class NullBooleanField(Field):
default_error_messages = {
- 'invalid': _("`{input}` is not a valid boolean.")
+ 'invalid': _('`{input}` is not a valid boolean.')
}
initial = None
TRUE_VALUES = set(('t', 'T', 'true', 'True', 'TRUE', '1', 1, True))
@@ -540,9 +540,9 @@ class NullBooleanField(Field):
class CharField(Field):
default_error_messages = {
- 'blank': _("This field may not be blank."),
- 'max_length': _("Ensure this field has no more than {max_length} characters."),
- 'min_length': _("Ensure this field has at least {min_length} characters.")
+ 'blank': _('This field may not be blank.'),
+ 'max_length': _('Ensure this field has no more than {max_length} characters.'),
+ 'min_length': _('Ensure this field has at least {min_length} characters.')
}
initial = ''
coerce_blank_to_null = False
@@ -584,7 +584,7 @@ class CharField(Field):
class EmailField(CharField):
default_error_messages = {
- 'invalid': _("Enter a valid email address.")
+ 'invalid': _('Enter a valid email address.')
}
def __init__(self, **kwargs):
@@ -601,7 +601,7 @@ class EmailField(CharField):
class RegexField(CharField):
default_error_messages = {
- 'invalid': _("This value does not match the required pattern.")
+ 'invalid': _('This value does not match the required pattern.')
}
def __init__(self, regex, **kwargs):
@@ -612,7 +612,7 @@ class RegexField(CharField):
class SlugField(CharField):
default_error_messages = {
- 'invalid': _("Enter a valid \"slug\" consisting of letters, numbers, underscores or hyphens.")
+ 'invalid': _('Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.')
}
def __init__(self, **kwargs):
@@ -624,7 +624,7 @@ class SlugField(CharField):
class URLField(CharField):
default_error_messages = {
- 'invalid': _("Enter a valid URL.")
+ 'invalid': _('Enter a valid URL.')
}
def __init__(self, **kwargs):
@@ -637,10 +637,10 @@ class URLField(CharField):
class IntegerField(Field):
default_error_messages = {
- 'invalid': _("A valid integer is required."),
- 'max_value': _("Ensure this value is less than or equal to {max_value}."),
- 'min_value': _("Ensure this value is greater than or equal to {min_value}."),
- 'max_string_length': _("String value too large.")
+ 'invalid': _('A valid integer is required.'),
+ 'max_value': _('Ensure this value is less than or equal to {max_value}.'),
+ 'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
+ 'max_string_length': _('String value too large.')
}
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
@@ -671,10 +671,10 @@ class IntegerField(Field):
class FloatField(Field):
default_error_messages = {
- 'invalid': _("A valid number is required."),
- 'max_value': _("Ensure this value is less than or equal to {max_value}."),
- 'min_value': _("Ensure this value is greater than or equal to {min_value}."),
- 'max_string_length': _("String value too large.")
+ 'invalid': _('A valid number is required.'),
+ 'max_value': _('Ensure this value is less than or equal to {max_value}.'),
+ 'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
+ 'max_string_length': _('String value too large.')
}
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
@@ -704,13 +704,13 @@ class FloatField(Field):
class DecimalField(Field):
default_error_messages = {
- 'invalid': _("A valid number is required."),
- 'max_value': _("Ensure this value is less than or equal to {max_value}."),
- 'min_value': _("Ensure this value is greater than or equal to {min_value}."),
- 'max_digits': _("Ensure that there are no more than {max_digits} digits in total."),
- 'max_decimal_places': _("Ensure that there are no more than {max_decimal_places} decimal places."),
- 'max_whole_digits': _("Ensure that there are no more than {max_whole_digits} digits before the decimal point."),
- 'max_string_length': _("String value too large.")
+ 'invalid': _('A valid number is required.'),
+ 'max_value': _('Ensure this value is less than or equal to {max_value}.'),
+ 'min_value': _('Ensure this value is greater than or equal to {min_value}.'),
+ 'max_digits': _('Ensure that there are no more than {max_digits} digits in total.'),
+ 'max_decimal_places': _('Ensure that there are no more than {max_decimal_places} decimal places.'),
+ 'max_whole_digits': _('Ensure that there are no more than {max_whole_digits} digits before the decimal point.'),
+ 'max_string_length': _('String value too large.')
}
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
@@ -793,8 +793,8 @@ class DecimalField(Field):
class DateTimeField(Field):
default_error_messages = {
- 'invalid': _("Datetime has wrong format. Use one of these formats instead: {format}."),
- 'date': _("Expected a datetime but got a date."),
+ 'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'),
+ 'date': _('Expected a datetime but got a date.'),
}
format = api_settings.DATETIME_FORMAT
input_formats = api_settings.DATETIME_INPUT_FORMATS
@@ -858,8 +858,8 @@ class DateTimeField(Field):
class DateField(Field):
default_error_messages = {
- 'invalid': _("Date has wrong format. Use one of these formats instead: {format}."),
- 'datetime': _("Expected a date but got a datetime."),
+ 'invalid': _('Date has wrong format. Use one of these formats instead: {format}.'),
+ 'datetime': _('Expected a date but got a datetime.'),
}
format = api_settings.DATE_FORMAT
input_formats = api_settings.DATE_INPUT_FORMATS
@@ -916,7 +916,7 @@ class DateField(Field):
class TimeField(Field):
default_error_messages = {
- 'invalid': _("Time has wrong format. Use one of these formats instead: {format}."),
+ 'invalid': _('Time has wrong format. Use one of these formats instead: {format}.'),
}
format = api_settings.TIME_FORMAT
input_formats = api_settings.TIME_INPUT_FORMATS
@@ -972,7 +972,7 @@ class TimeField(Field):
class ChoiceField(Field):
default_error_messages = {
- 'invalid_choice': _("`{input}` is not a valid choice.")
+ 'invalid_choice': _('`{input}` is not a valid choice.')
}
def __init__(self, choices, **kwargs):
@@ -1016,8 +1016,8 @@ class ChoiceField(Field):
class MultipleChoiceField(ChoiceField):
default_error_messages = {
- 'invalid_choice': _("`{input}` is not a valid choice."),
- 'not_a_list': _("Expected a list of items but got type `{input_type}`.")
+ 'invalid_choice': _('`{input}` is not a valid choice.'),
+ 'not_a_list': _('Expected a list of items but got type `{input_type}`.')
}
default_empty_html = []
@@ -1047,11 +1047,11 @@ class MultipleChoiceField(ChoiceField):
class FileField(Field):
default_error_messages = {
- 'required': _("No file was submitted."),
- 'invalid': _("The submitted data was not a file. Check the encoding type on the form."),
- 'no_name': _("No filename could be determined."),
- 'empty': _("The submitted file is empty."),
- 'max_length': _("Ensure this filename has at most {max_length} characters (it has {length})."),
+ 'required': _('No file was submitted.'),
+ 'invalid': _('The submitted data was not a file. Check the encoding type on the form.'),
+ 'no_name': _('No filename could be determined.'),
+ 'empty': _('The submitted file is empty.'),
+ 'max_length': _('Ensure this filename has at most {max_length} characters (it has {length}).'),
}
use_url = api_settings.UPLOADED_FILES_USE_URL
@@ -1118,7 +1118,7 @@ class ListField(Field):
child = None
initial = []
default_error_messages = {
- 'not_a_list': _("Expected a list of items but got type `{input_type}`.")
+ 'not_a_list': _('Expected a list of items but got type `{input_type}`.')
}
def __init__(self, *args, **kwargs):
@@ -1249,7 +1249,7 @@ class ModelField(Field):
that do not have a serializer field to be mapped to.
"""
default_error_messages = {
- 'max_length': _("Ensure this field has no more than {max_length} characters."),
+ 'max_length': _('Ensure this field has no more than {max_length} characters.'),
}
def __init__(self, model_field, **kwargs):
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 738ba544..7ebed032 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -120,12 +120,12 @@ class GenericAPIView(views.APIView):
if page == 'last':
page_number = paginator.num_pages
else:
- raise NotFound(_("Choose a valid page number. Page numbers must be a whole number, or must be the string \"last\"."))
+ raise NotFound(_('Choose a valid page number. Page numbers must be a whole number, or must be the string "last".'))
try:
page = paginator.page(page_number)
except InvalidPage as exc:
- error_format = _("Invalid page \"{page_number}\": {message}.")
+ error_format = _('Invalid page "{page_number}": {message}.')
raise NotFound(error_format.format(
page_number=page_number, message=six.text_type(exc)
))
diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.po b/rest_framework/locale/en_US/LC_MESSAGES/django.po
index 5d0d3a04..c8fc7f4d 100644
--- a/rest_framework/locale/en_US/LC_MESSAGES/django.po
+++ b/rest_framework/locale/en_US/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-01-07 11:58+0000\n"
+"POT-Creation-Date: 2015-01-07 12:28+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -42,11 +42,11 @@ msgid "Invalid token header. Token string should not contain spaces."
msgstr ""
#: authentication.py:168
-msgid "Invalid token"
+msgid "Invalid token."
msgstr ""
#: authentication.py:171
-msgid "User inactive or deleted"
+msgid "User inactive or deleted."
msgstr ""
#: authtoken/serializers.py:20
@@ -58,7 +58,7 @@ msgid "Unable to log in with provided credentials."
msgstr ""
#: authtoken/serializers.py:26
-msgid "Must include \"username\" and \"password\""
+msgid "Must include \"username\" and \"password\"."
msgstr ""
#: exceptions.py:38
@@ -86,7 +86,7 @@ msgid "Not found."
msgstr ""
#: exceptions.py:98
-msgid "Method '{method}' not allowed."
+msgid "Method \"{method}\" not allowed."
msgstr ""
#: exceptions.py:109
@@ -94,7 +94,7 @@ msgid "Could not satisfy the request Accept header."
msgstr ""
#: exceptions.py:121
-msgid "Unsupported media type '{media_type}' in request."
+msgid "Unsupported media type \"{media_type}\" in request."
msgstr ""
#: exceptions.py:134
@@ -136,7 +136,8 @@ msgstr ""
#: fields.py:615
msgid ""
-"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
msgstr ""
#: fields.py:627
@@ -235,15 +236,15 @@ msgstr ""
#: generics.py:123
msgid ""
"Choose a valid page number. Page numbers must be a whole number, or must be "
-"the string 'last'."
+"the string \"last\"."
msgstr ""
#: generics.py:128
-msgid "Invalid page ({page_number}): {message}."
+msgid "Invalid page \"{page_number}\": {message}."
msgstr ""
#: relations.py:132
-msgid "Invalid pk '{pk_value}' - object does not exist."
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr ""
#: relations.py:133
@@ -299,7 +300,7 @@ msgid "This field must be unique for the \"{date_field}\" year."
msgstr ""
#: versioning.py:39
-msgid "Invalid version in 'Accept' header."
+msgid "Invalid version in \"Accept\" header."
msgstr ""
#: versioning.py:70 versioning.py:112
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 42b624e7..05ac3d1c 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -128,9 +128,9 @@ class StringRelatedField(RelatedField):
class PrimaryKeyRelatedField(RelatedField):
default_error_messages = {
- 'required': _("This field is required."),
- 'does_not_exist': _("Invalid pk \"{pk_value}\" - object does not exist."),
- 'incorrect_type': _("Incorrect type. Expected pk value, received {data_type}."),
+ 'required': _('This field is required.'),
+ 'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'),
+ 'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'),
}
def use_pk_only_optimization(self):
@@ -152,11 +152,11 @@ class HyperlinkedRelatedField(RelatedField):
lookup_field = 'pk'
default_error_messages = {
- 'required': _("This field is required."),
- 'no_match': _("Invalid hyperlink - No URL match."),
- 'incorrect_match': _("Invalid hyperlink - Incorrect URL match."),
- 'does_not_exist': _("Invalid hyperlink - Object does not exist."),
- 'incorrect_type': _("Incorrect type. Expected URL string, received {data_type}."),
+ 'required': _('This field is required.'),
+ 'no_match': _('Invalid hyperlink - No URL match.'),
+ 'incorrect_match': _('Invalid hyperlink - Incorrect URL match.'),
+ 'does_not_exist': _('Invalid hyperlink - Object does not exist.'),
+ 'incorrect_type': _('Incorrect type. Expected URL string, received {data_type}.'),
}
def __init__(self, view_name=None, **kwargs):
@@ -291,8 +291,8 @@ class SlugRelatedField(RelatedField):
"""
default_error_messages = {
- 'does_not_exist': _("Object with {slug_name}={value} does not exist."),
- 'invalid': _("Invalid value."),
+ 'does_not_exist': _('Object with {slug_name}={value} does not exist.'),
+ 'invalid': _('Invalid value.'),
}
def __init__(self, slug_field=None, **kwargs):
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 9d7c8884..623ed586 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -296,7 +296,7 @@ def get_validation_error_detail(exc):
@six.add_metaclass(SerializerMetaclass)
class Serializer(BaseSerializer):
default_error_messages = {
- 'invalid': _("Invalid data. Expected a dictionary, but got {datatype}.")
+ 'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.')
}
@property
@@ -473,7 +473,7 @@ class ListSerializer(BaseSerializer):
many = True
default_error_messages = {
- 'not_a_list': _("Expected a list of items but got type `{input_type}`.")
+ 'not_a_list': _('Expected a list of items but got type `{input_type}`.')
}
def __init__(self, *args, **kwargs):
diff --git a/rest_framework/validators.py b/rest_framework/validators.py
index cf6f0718..e3719b8d 100644
--- a/rest_framework/validators.py
+++ b/rest_framework/validators.py
@@ -19,7 +19,7 @@ class UniqueValidator:
Should be applied to an individual field on the serializer.
"""
- message = _("This field must be unique.")
+ message = _('This field must be unique.')
def __init__(self, queryset, message=None):
self.queryset = queryset
@@ -73,8 +73,8 @@ class UniqueTogetherValidator:
Should be applied to the serializer class, not to an individual field.
"""
- message = _("The fields {field_names} must make a unique set.")
- missing_message = _("This field is required.")
+ message = _('The fields {field_names} must make a unique set.')
+ missing_message = _('This field is required.')
def __init__(self, queryset, fields, message=None):
self.queryset = queryset
@@ -152,7 +152,7 @@ class UniqueTogetherValidator:
class BaseUniqueForValidator:
message = None
- missing_message = _("This field is required.")
+ missing_message = _('This field is required.')
def __init__(self, queryset, field, date_field, message=None):
self.queryset = queryset
@@ -216,7 +216,7 @@ class BaseUniqueForValidator:
class UniqueForDateValidator(BaseUniqueForValidator):
- message = _("This field must be unique for the \"{date_field}\" date.")
+ message = _('This field must be unique for the "{date_field}" date.')
def filter_queryset(self, attrs, queryset):
value = attrs[self.field]
@@ -231,7 +231,7 @@ class UniqueForDateValidator(BaseUniqueForValidator):
class UniqueForMonthValidator(BaseUniqueForValidator):
- message = _("This field must be unique for the \"{date_field}\" month.")
+ message = _('This field must be unique for the "{date_field}" month.')
def filter_queryset(self, attrs, queryset):
value = attrs[self.field]
@@ -244,7 +244,7 @@ class UniqueForMonthValidator(BaseUniqueForValidator):
class UniqueForYearValidator(BaseUniqueForValidator):
- message = _("This field must be unique for the \"{date_field}\" year.")
+ message = _('This field must be unique for the "{date_field}" year.')
def filter_queryset(self, attrs, queryset):
value = attrs[self.field]
diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py
index 819c32df..e31c71e9 100644
--- a/rest_framework/versioning.py
+++ b/rest_framework/versioning.py
@@ -36,7 +36,7 @@ class AcceptHeaderVersioning(BaseVersioning):
Host: example.com
Accept: application/json; version=1.0
"""
- invalid_version_message = _("Invalid version in \"Accept\" header.")
+ invalid_version_message = _('Invalid version in "Accept" header.')
def determine_version(self, request, *args, **kwargs):
media_type = _MediaType(request.accepted_media_type)
@@ -67,7 +67,7 @@ class URLPathVersioning(BaseVersioning):
Host: example.com
Accept: application/json
"""
- invalid_version_message = _("Invalid version in URL path.")
+ invalid_version_message = _('Invalid version in URL path.')
def determine_version(self, request, *args, **kwargs):
version = kwargs.get(self.version_param, self.default_version)
@@ -109,7 +109,7 @@ class NamespaceVersioning(BaseVersioning):
Host: example.com
Accept: application/json
"""
- invalid_version_message = _("Invalid version in URL path.")
+ invalid_version_message = _('Invalid version in URL path.')
def determine_version(self, request, *args, **kwargs):
resolver_match = getattr(request, 'resolver_match', None)
@@ -135,7 +135,7 @@ class HostNameVersioning(BaseVersioning):
Accept: application/json
"""
hostname_regex = re.compile(r'^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$')
- invalid_version_message = _("Invalid version in hostname.")
+ invalid_version_message = _('Invalid version in hostname.')
def determine_version(self, request, *args, **kwargs):
hostname, seperator, port = request.get_host().partition(':')
@@ -157,7 +157,7 @@ class QueryParameterVersioning(BaseVersioning):
Host: example.com
Accept: application/json
"""
- invalid_version_message = _("Invalid version in query parameter.")
+ invalid_version_message = _('Invalid version in query parameter.')
def determine_version(self, request, *args, **kwargs):
version = request.query_params.get(self.version_param)
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 61d39aff..5ecb9857 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -439,7 +439,7 @@ class TestSlugField(FieldValues):
'slug-99': 'slug-99',
}
invalid_inputs = {
- 'slug 99': ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]
+ 'slug 99': ['Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.']
}
outputs = {}
field = serializers.SlugField()
diff --git a/tests/test_generics.py b/tests/test_generics.py
index 94023c30..fba8718f 100644
--- a/tests/test_generics.py
+++ b/tests/test_generics.py
@@ -117,7 +117,7 @@ class TestRootView(TestCase):
with self.assertNumQueries(0):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
- self.assertEqual(response.data, {"detail": "Method 'PUT' not allowed."})
+ self.assertEqual(response.data, {"detail": 'Method "PUT" not allowed.'})
def test_delete_root_view(self):
"""
@@ -127,7 +127,7 @@ class TestRootView(TestCase):
with self.assertNumQueries(0):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
- self.assertEqual(response.data, {"detail": "Method 'DELETE' not allowed."})
+ self.assertEqual(response.data, {"detail": 'Method "DELETE" not allowed.'})
def test_post_cannot_set_id(self):
"""
@@ -181,7 +181,7 @@ class TestInstanceView(TestCase):
with self.assertNumQueries(0):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
- self.assertEqual(response.data, {"detail": "Method 'POST' not allowed."})
+ self.assertEqual(response.data, {"detail": 'Method "POST" not allowed.'})
def test_put_instance_view(self):
"""
diff --git a/tests/test_relations.py b/tests/test_relations.py
index 62353dc2..08c92242 100644
--- a/tests/test_relations.py
+++ b/tests/test_relations.py
@@ -33,7 +33,7 @@ class TestPrimaryKeyRelatedField(APISimpleTestCase):
with pytest.raises(serializers.ValidationError) as excinfo:
self.field.to_internal_value(4)
msg = excinfo.value.detail[0]
- assert msg == "Invalid pk '4' - object does not exist."
+ assert msg == 'Invalid pk "4" - object does not exist.'
def test_pk_related_lookup_invalid_type(self):
with pytest.raises(serializers.ValidationError) as excinfo:
--
cgit v1.2.3
From 42f1932b520a90ae6f8e11246a0992e5f8983bd7 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Wed, 7 Jan 2015 19:10:22 +0100
Subject: Release notes for 3.0.3
---
docs/topics/release-notes.md | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index b9216e36..18a47b71 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -40,6 +40,26 @@ You can determine your currently installed version using `pip freeze`:
## 3.0.x series
+
+### 3.0.3
+
+**Date**: [8th January 2015][3.0.3-milestone].
+
+* Fix `MinValueValidator` on `models.DateField`. ([#2369][gh2369])
+* Fix serializer missing context when pagination is used. ([#2355][gh2355])
+* Namespaced router URLs are now supported by the `DefaultRouter`. ([#2351][gh2351])
+* `required=False` allows omission of value for output. ([#2342][gh2342])
+* Use textarea input for `models.TextField`. ([#2340][gh2340])
+* Use custom `ListSerializer` for pagination if required. ([#2331][gh2331], [#2327][gh2327])
+* Better behavior with null and '' for blank HTML fields. ([#2330][gh2330])
+* Ensure fields in `exclude` are model fields. ([#2319][gh2319])
+* Fix `IntegerField` and `max_length` argument incompatibility. ([#2317][gh2317])
+* Fix the YAML encoder for 3.0 serializers. ([#2315][gh2315], [#2283][gh2283])
+* Fix the behavior of empty HTML fields. ([#2311][gh2311], [#1101][gh1101])
+* Fix Metaclass attribute depth ignoring fields attribute. ([#2287][gh2287])
+* Fix `format_suffix_patterns` to work with Django's `i18n_patterns`. ([#2278][gh2278])
+* Ability to customize router URLs for custom actions, using `url_path`. ([#2010][gh2010])
+
### 3.0.2
**Date**: [17th December 2014][3.0.2-milestone].
@@ -729,3 +749,21 @@ For older release notes, [please see the GitHub repo](old-release-notes).
[gh2290]: https://github.com/tomchristie/django-rest-framework/issues/2290
[gh2291]: https://github.com/tomchristie/django-rest-framework/issues/2291
[gh2294]: https://github.com/tomchristie/django-rest-framework/issues/2294
+
+[gh1101]: https://github.com/tomchristie/django-rest-framework/issues/1101
+[gh2010]: https://github.com/tomchristie/django-rest-framework/issues/2010
+[gh2278]: https://github.com/tomchristie/django-rest-framework/issues/2278
+[gh2283]: https://github.com/tomchristie/django-rest-framework/issues/2283
+[gh2287]: https://github.com/tomchristie/django-rest-framework/issues/2287
+[gh2311]: https://github.com/tomchristie/django-rest-framework/issues/2311
+[gh2315]: https://github.com/tomchristie/django-rest-framework/issues/2315
+[gh2317]: https://github.com/tomchristie/django-rest-framework/issues/2317
+[gh2319]: https://github.com/tomchristie/django-rest-framework/issues/2319
+[gh2327]: https://github.com/tomchristie/django-rest-framework/issues/2327
+[gh2330]: https://github.com/tomchristie/django-rest-framework/issues/2330
+[gh2331]: https://github.com/tomchristie/django-rest-framework/issues/2331
+[gh2340]: https://github.com/tomchristie/django-rest-framework/issues/2340
+[gh2342]: https://github.com/tomchristie/django-rest-framework/issues/2342
+[gh2351]: https://github.com/tomchristie/django-rest-framework/issues/2351
+[gh2355]: https://github.com/tomchristie/django-rest-framework/issues/2355
+[gh2369]: https://github.com/tomchristie/django-rest-framework/issues/2369
--
cgit v1.2.3
From b7015ea8989d67617d276829ccb6a192362ee01f Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Wed, 7 Jan 2015 19:11:17 +0100
Subject: Bumped the version to 3.0.3.
---
rest_framework/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index dec89b3e..fdcebb7b 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -8,7 +8,7 @@ ______ _____ _____ _____ __
"""
__title__ = 'Django REST framework'
-__version__ = '3.0.2'
+__version__ = '3.0.3'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2015 Tom Christie'
--
cgit v1.2.3
From 60f5b5d9f364c383662fb6ae8d210f31e9621c09 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Wed, 7 Jan 2015 19:19:33 +0100
Subject: Make Django REST Framework as zip unsafe.
---
setup.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/setup.py b/setup.py
index 1e54836c..efe39d8d 100755
--- a/setup.py
+++ b/setup.py
@@ -67,6 +67,7 @@ setup(
packages=get_packages('rest_framework'),
package_data=get_package_data('rest_framework'),
install_requires=[],
+ zip_safe=False,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
--
cgit v1.2.3
From 58ec7669aed9ebd58fd6095c6a6437bf9f3cf7f1 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 7 Jan 2015 18:22:30 +0000
Subject: swap backticks for double quotes
---
rest_framework/exceptions.py | 2 +-
rest_framework/fields.py | 12 ++++++------
rest_framework/locale/en_US/LC_MESSAGES/django.po | 8 ++++----
rest_framework/serializers.py | 2 +-
4 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
index f62c9fe3..f954c13e 100644
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -52,7 +52,7 @@ class APIException(Exception):
# built in `ValidationError`. For example:
#
# from rest_framework import serializers
-# raise serializers.ValidationError("Value was invalid")
+# raise serializers.ValidationError('Value was invalid')
class ValidationError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 76101608..b80dea60 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -477,7 +477,7 @@ class Field(object):
class BooleanField(Field):
default_error_messages = {
- 'invalid': _('`{input}` is not a valid boolean.')
+ 'invalid': _('"{input}" is not a valid boolean.')
}
default_empty_html = False
initial = False
@@ -505,7 +505,7 @@ class BooleanField(Field):
class NullBooleanField(Field):
default_error_messages = {
- 'invalid': _('`{input}` is not a valid boolean.')
+ 'invalid': _('"{input}" is not a valid boolean.')
}
initial = None
TRUE_VALUES = set(('t', 'T', 'true', 'True', 'TRUE', '1', 1, True))
@@ -972,7 +972,7 @@ class TimeField(Field):
class ChoiceField(Field):
default_error_messages = {
- 'invalid_choice': _('`{input}` is not a valid choice.')
+ 'invalid_choice': _('"{input}" is not a valid choice.')
}
def __init__(self, choices, **kwargs):
@@ -1016,8 +1016,8 @@ class ChoiceField(Field):
class MultipleChoiceField(ChoiceField):
default_error_messages = {
- 'invalid_choice': _('`{input}` is not a valid choice.'),
- 'not_a_list': _('Expected a list of items but got type `{input_type}`.')
+ 'invalid_choice': _('"{input}" is not a valid choice.'),
+ 'not_a_list': _('Expected a list of items but got type "{input_type}".')
}
default_empty_html = []
@@ -1118,7 +1118,7 @@ class ListField(Field):
child = None
initial = []
default_error_messages = {
- 'not_a_list': _('Expected a list of items but got type `{input_type}`.')
+ 'not_a_list': _('Expected a list of items but got type "{input_type}".')
}
def __init__(self, *args, **kwargs):
diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.po b/rest_framework/locale/en_US/LC_MESSAGES/django.po
index c8fc7f4d..d98225ce 100644
--- a/rest_framework/locale/en_US/LC_MESSAGES/django.po
+++ b/rest_framework/locale/en_US/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-01-07 12:28+0000\n"
+"POT-Creation-Date: 2015-01-07 18:21+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -111,7 +111,7 @@ msgid "This field may not be null."
msgstr ""
#: fields.py:480 fields.py:508
-msgid "`{input}` is not a valid boolean."
+msgid "\"{input}\" is not a valid boolean."
msgstr ""
#: fields.py:543
@@ -199,11 +199,11 @@ msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
#: fields.py:975 fields.py:1019
-msgid "`{input}` is not a valid choice."
+msgid "\"{input}\" is not a valid choice."
msgstr ""
#: fields.py:1020 fields.py:1121 serializers.py:476
-msgid "Expected a list of items but got type `{input_type}`."
+msgid "Expected a list of items but got type \"{input_type}\"."
msgstr ""
#: fields.py:1050
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 623ed586..5bfbd235 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -473,7 +473,7 @@ class ListSerializer(BaseSerializer):
many = True
default_error_messages = {
- 'not_a_list': _('Expected a list of items but got type `{input_type}`.')
+ 'not_a_list': _('Expected a list of items but got type "{input_type}".')
}
def __init__(self, *args, **kwargs):
--
cgit v1.2.3
From 734f8f26678d3bd28f04bc44b0fabd146b97ddb0 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Wed, 7 Jan 2015 18:22:40 +0000
Subject: restore Django 404
---
rest_framework/generics.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 7ebed032..d52f2b6c 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -120,13 +120,13 @@ class GenericAPIView(views.APIView):
if page == 'last':
page_number = paginator.num_pages
else:
- raise NotFound(_('Choose a valid page number. Page numbers must be a whole number, or must be the string "last".'))
+ raise Http404(_('Choose a valid page number. Page numbers must be a whole number, or must be the string "last".'))
try:
page = paginator.page(page_number)
except InvalidPage as exc:
error_format = _('Invalid page "{page_number}": {message}.')
- raise NotFound(error_format.format(
+ raise Http404(error_format.format(
page_number=page_number, message=six.text_type(exc)
))
--
cgit v1.2.3
From 1102e22cb407b9069ce3301bd578ba45a775a89e Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 7 Jan 2015 21:02:42 +0000
Subject: Update project-management.md
---
docs/topics/project-management.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/topics/project-management.md b/docs/topics/project-management.md
index f581cabd..037b7182 100644
--- a/docs/topics/project-management.md
+++ b/docs/topics/project-management.md
@@ -58,6 +58,8 @@ The following template should be used for the description of the issue, and serv
#### New members.
If you wish to be considered for this or a future date, please comment against this or subsequent issues.
+
+ To modify this process for future releases make a pull request to the [project management](http://www.django-rest-framework.org/topics/project-management/) documentation.
#### Responsibilities of team members
@@ -107,6 +109,8 @@ The following template should be used for the description of the issue, and serv
- [ ] Make a release announcement on the [discussion group](https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework).
- [ ] Make a release announcement on twitter.
- [ ] Close the milestone on GitHub.
+
+ To modify this process for future releases make a pull request to the [project management](http://www.django-rest-framework.org/topics/project-management/) documentation.
When pushing the release to PyPI ensure that your environment has been installed from our development `requirement.txt`, so that documentation and PyPI installs are consistently being built against a pinned set of packages.
--
cgit v1.2.3
From 18cefad3abfa6e6a7bbd278e8d54eef66a1d1e53 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 7 Jan 2015 21:03:50 +0000
Subject: Update project-management.md
---
docs/topics/project-management.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/topics/project-management.md b/docs/topics/project-management.md
index 037b7182..bcc0330e 100644
--- a/docs/topics/project-management.md
+++ b/docs/topics/project-management.md
@@ -59,7 +59,7 @@ The following template should be used for the description of the issue, and serv
If you wish to be considered for this or a future date, please comment against this or subsequent issues.
- To modify this process for future releases make a pull request to the [project management](http://www.django-rest-framework.org/topics/project-management/) documentation.
+ To modify this process for future maintenance cycles make a pull request to the [project management](http://www.django-rest-framework.org/topics/project-management/) documentation.
#### Responsibilities of team members
--
cgit v1.2.3
From e61ef3d39f301bc62323b47af5080877e273c395 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 8 Jan 2015 11:07:47 +0000
Subject: Minor docs updates
---
docs/api-guide/filtering.md | 1 +
docs/topics/project-management.md | 1 +
2 files changed, 2 insertions(+)
diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md
index 83977048..3eb1538f 100644
--- a/docs/api-guide/filtering.md
+++ b/docs/api-guide/filtering.md
@@ -316,6 +316,7 @@ Typically you'd instead control this by setting `order_by` on the initial querys
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = (filters.OrderingFilter,)
+ ordering_fields = ('username', 'email')
ordering = ('username',)
The `ordering` attribute may be either a string or a list/tuple of strings.
diff --git a/docs/topics/project-management.md b/docs/topics/project-management.md
index f581cabd..f052aa83 100644
--- a/docs/topics/project-management.md
+++ b/docs/topics/project-management.md
@@ -126,6 +126,7 @@ The following issues still need to be addressed:
* Ensure `@jamie` has back-up access to the `django-rest-framework.org` domain setup and admin.
* Document ownership of the [live example][sandbox] API.
* Document ownership of the [mailing list][mailing-list] and IRC channel.
+* Document ownership and management of the security mailing list.
[bus-factor]: http://en.wikipedia.org/wiki/Bus_factor
[un-triaged]: https://github.com/tomchristie/django-rest-framework/issues?q=is%3Aopen+no%3Alabel
--
cgit v1.2.3
From f0ad0a88c49f1fef473ef1fbf965bcaa974ee062 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 8 Jan 2015 12:31:51 +0000
Subject: Link to Roy Fielding versioning interview.
---
docs/api-guide/versioning.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/docs/api-guide/versioning.md b/docs/api-guide/versioning.md
index 92380cc0..7463f190 100644
--- a/docs/api-guide/versioning.md
+++ b/docs/api-guide/versioning.md
@@ -10,6 +10,8 @@ API versioning allows you to alter behavior between different clients. REST fram
Versioning is determined by the incoming client request, and may either be based on the request URL, or based on the request headers.
+There are a number of valid approaches to approaching versioning. [Non-versioned systems can also be appropriate][roy-fielding-on-versioning], particularly if you're engineering for very long-term systems with multiple clients outside of your control.
+
## Versioning with REST framework
When API versioning is enabled, the `request.version` attribute will contain a string that corresponds to the version requested in the incoming client request.
@@ -195,6 +197,7 @@ The following example uses a custom `X-API-Version` header to determine the requ
If your versioning scheme is based on the request URL, you will also want to alter how versioned URLs are determined. In order to do so you should override the `.reverse()` method on the class. See the source code for examples.
[cite]: http://www.slideshare.net/evolve_conference/201308-fielding-evolve/31
+[roy-fielding-on-versioning]: http://www.infoq.com/articles/roy-fielding-on-versioning
[klabnik-guidelines]: http://blog.steveklabnik.com/posts/2011-07-03-nobody-understands-rest-or-http#i_want_my_api_to_be_versioned
[heroku-guidelines]: https://github.com/interagent/http-api-design#version-with-accepts-header
[json-parameters]: http://tools.ietf.org/html/rfc4627#section-6
--
cgit v1.2.3
From b33a6cbff16e5a28a1a696e2ac617303da181720 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 8 Jan 2015 14:16:58 +0000
Subject: Ensure urlparse is not publically exposed in compat.py - less chance
of accidental conflict.
---
rest_framework/compat.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index b1f6f2fa..971dee9c 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -10,7 +10,7 @@ import inspect
from django.core.exceptions import ImproperlyConfigured
from django.utils.encoding import force_text
-from django.utils.six.moves.urllib import parse as urlparse
+from django.utils.six.moves.urllib.parse import urlparse as _urlparse
from django.conf import settings
from django.utils import six
import django
@@ -182,7 +182,7 @@ except ImportError:
class RequestFactory(DjangoRequestFactory):
def generic(self, method, path,
data='', content_type='application/octet-stream', **extra):
- parsed = urlparse.urlparse(path)
+ parsed = _urlparse(path)
data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET)
r = {
'PATH_INFO': self._get_path(parsed),
--
cgit v1.2.3
From f529f83d3c96ca1957d7a8bfc74bd33151cc8d86 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 8 Jan 2015 14:38:23 +0000
Subject: Minimum Django 1.5 version issue 1.5.6
---
README.md | 2 +-
docs/index.md | 2 +-
tox.ini | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 8fc11c30..428a2e56 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)
-* Django (1.4.11+, 1.5.5+, 1.6, 1.7)
+* Django (1.4.11+, 1.5.6+, 1.6, 1.7)
# Installation
diff --git a/docs/index.md b/docs/index.md
index 55129df1..a621e3ec 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -51,7 +51,7 @@ Some reasons you might want to use REST framework:
REST framework requires the following:
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)
-* Django (1.4.11+, 1.5.5+, 1.6, 1.7)
+* Django (1.4.11+, 1.5.6+, 1.6, 1.7)
The following packages are optional:
diff --git a/tox.ini b/tox.ini
index 933ee560..ab258f2e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -11,7 +11,7 @@ setenv =
PYTHONDONTWRITEBYTECODE=1
deps =
django14: Django==1.4.11
- django15: Django==1.5.5
+ django15: Django==1.5.6
django16: Django==1.6.8
django17: Django==1.7.1
djangomaster: https://github.com/django/django/zipball/master
--
cgit v1.2.3
From 42c913334b0b4cd731011a07a49ff08aa03d7768 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 8 Jan 2015 14:51:08 +0000
Subject: Minimum 1.6.x version is 1.6.3
---
README.md | 2 +-
docs/index.md | 2 +-
tox.ini | 8 ++++----
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 428a2e56..cf3dc857 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)
-* Django (1.4.11+, 1.5.6+, 1.6, 1.7)
+* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7)
# Installation
diff --git a/docs/index.md b/docs/index.md
index a621e3ec..d40f8972 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -51,7 +51,7 @@ Some reasons you might want to use REST framework:
REST framework requires the following:
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)
-* Django (1.4.11+, 1.5.6+, 1.6, 1.7)
+* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7)
The following packages are optional:
diff --git a/tox.ini b/tox.ini
index ab258f2e..db2b5d13 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,10 +10,10 @@ commands = ./runtests.py --fast
setenv =
PYTHONDONTWRITEBYTECODE=1
deps =
- django14: Django==1.4.11
- django15: Django==1.5.6
- django16: Django==1.6.8
- django17: Django==1.7.1
+ django14: Django==1.4.11 # Should track minimum supported
+ django15: Django==1.5.6 # Should track minimum supported
+ django16: Django==1.6.3 # Should track minimum supported
+ django17: Django==1.7.3 # Should track maximum supported
djangomaster: https://github.com/django/django/zipball/master
{py26,py27}-django{14,15,16,17}: django-guardian==1.2.3
{py26,py27}-django{14,15,16}: oauth2==1.5.211
--
cgit v1.2.3
From 08008669886f682ba62c4d377f4d96f64808d4a5 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 8 Jan 2015 14:54:54 +0000
Subject: Fix broken 1.7.3. It's 1.7.2 - 1.7.3 is documented but not yet on
PyPI.
---
tox.ini | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tox.ini b/tox.ini
index db2b5d13..4b90c392 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,7 +13,7 @@ deps =
django14: Django==1.4.11 # Should track minimum supported
django15: Django==1.5.6 # Should track minimum supported
django16: Django==1.6.3 # Should track minimum supported
- django17: Django==1.7.3 # Should track maximum supported
+ django17: Django==1.7.2 # Should track maximum supported
djangomaster: https://github.com/django/django/zipball/master
{py26,py27}-django{14,15,16,17}: django-guardian==1.2.3
{py26,py27}-django{14,15,16}: oauth2==1.5.211
--
cgit v1.2.3
From 4d9e7a53565f6301b87999e6bafdb1c2c3c2af3b Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 8 Jan 2015 15:38:27 +0000
Subject: Ammend docstring to use python2/3 compatible example.
---
rest_framework/settings.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
index 33f84813..fc6dfecd 100644
--- a/rest_framework/settings.py
+++ b/rest_framework/settings.py
@@ -167,7 +167,7 @@ class APISettings(object):
For example:
from rest_framework.settings import api_settings
- print api_settings.DEFAULT_RENDERER_CLASSES
+ print(api_settings.DEFAULT_RENDERER_CLASSES)
Any setting with string import paths will be automatically resolved
and return the class, rather than the string literal.
--
cgit v1.2.3
From 1368c31a705a4892995f42cf5e0dcdcbfa13a1ce Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Thu, 8 Jan 2015 17:16:15 +0000
Subject: remove unused import
---
rest_framework/generics.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index d52f2b6c..0d709c37 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -10,7 +10,6 @@ from django.shortcuts import get_object_or_404 as _get_object_or_404
from django.utils import six
from django.utils.translation import ugettext as _
from rest_framework import views, mixins
-from rest_framework.exceptions import NotFound
from rest_framework.settings import api_settings
--
cgit v1.2.3
From 7f8d314101c4e6e059b00ac12658f0e1055da8f7 Mon Sep 17 00:00:00 2001
From: Craig Blaszczyk
Date: Thu, 8 Jan 2015 17:16:47 +0000
Subject: update tests to expect new error messages
---
tests/test_fields.py | 18 +++++++++---------
tests/test_serializer_bulk_update.py | 4 ++--
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/tests/test_fields.py b/tests/test_fields.py
index 5ecb9857..240827ee 100644
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -338,7 +338,7 @@ class TestBooleanField(FieldValues):
False: False,
}
invalid_inputs = {
- 'foo': ['`foo` is not a valid boolean.'],
+ 'foo': ['"foo" is not a valid boolean.'],
None: ['This field may not be null.']
}
outputs = {
@@ -368,7 +368,7 @@ class TestNullBooleanField(FieldValues):
None: None
}
invalid_inputs = {
- 'foo': ['`foo` is not a valid boolean.'],
+ 'foo': ['"foo" is not a valid boolean.'],
}
outputs = {
'true': True,
@@ -832,7 +832,7 @@ class TestChoiceField(FieldValues):
'good': 'good',
}
invalid_inputs = {
- 'amazing': ['`amazing` is not a valid choice.']
+ 'amazing': ['"amazing" is not a valid choice.']
}
outputs = {
'good': 'good',
@@ -872,8 +872,8 @@ class TestChoiceFieldWithType(FieldValues):
3: 3,
}
invalid_inputs = {
- 5: ['`5` is not a valid choice.'],
- 'abc': ['`abc` is not a valid choice.']
+ 5: ['"5" is not a valid choice.'],
+ 'abc': ['"abc" is not a valid choice.']
}
outputs = {
'1': 1,
@@ -899,7 +899,7 @@ class TestChoiceFieldWithListChoices(FieldValues):
'good': 'good',
}
invalid_inputs = {
- 'awful': ['`awful` is not a valid choice.']
+ 'awful': ['"awful" is not a valid choice.']
}
outputs = {
'good': 'good'
@@ -917,8 +917,8 @@ class TestMultipleChoiceField(FieldValues):
('aircon', 'manual'): set(['aircon', 'manual']),
}
invalid_inputs = {
- 'abc': ['Expected a list of items but got type `str`.'],
- ('aircon', 'incorrect'): ['`incorrect` is not a valid choice.']
+ 'abc': ['Expected a list of items but got type "str".'],
+ ('aircon', 'incorrect'): ['"incorrect" is not a valid choice.']
}
outputs = [
(['aircon', 'manual'], set(['aircon', 'manual']))
@@ -1028,7 +1028,7 @@ class TestListField(FieldValues):
(['1', '2', '3'], [1, 2, 3])
]
invalid_inputs = [
- ('not a list', ['Expected a list of items but got type `str`.']),
+ ('not a list', ['Expected a list of items but got type "str".']),
([1, 2, 'error'], ['A valid integer is required.'])
]
outputs = [
diff --git a/tests/test_serializer_bulk_update.py b/tests/test_serializer_bulk_update.py
index fb881a75..bc955b2e 100644
--- a/tests/test_serializer_bulk_update.py
+++ b/tests/test_serializer_bulk_update.py
@@ -101,7 +101,7 @@ class BulkCreateSerializerTests(TestCase):
serializer = self.BookSerializer(data=data, many=True)
self.assertEqual(serializer.is_valid(), False)
- expected_errors = {'non_field_errors': ['Expected a list of items but got type `int`.']}
+ expected_errors = {'non_field_errors': ['Expected a list of items but got type "int".']}
self.assertEqual(serializer.errors, expected_errors)
@@ -118,6 +118,6 @@ class BulkCreateSerializerTests(TestCase):
serializer = self.BookSerializer(data=data, many=True)
self.assertEqual(serializer.is_valid(), False)
- expected_errors = {'non_field_errors': ['Expected a list of items but got type `dict`.']}
+ expected_errors = {'non_field_errors': ['Expected a list of items but got type "dict".']}
self.assertEqual(serializer.errors, expected_errors)
--
cgit v1.2.3
From ef16c546d77d36bbddacf9b66626f7eaf9f4ff17 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Thu, 8 Jan 2015 23:29:51 +0100
Subject: Update the release note with latest fixes. Add the link to the 3.0.3
milestone.
---
docs/topics/release-notes.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 18a47b71..c49dd62c 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -59,6 +59,7 @@ You can determine your currently installed version using `pip freeze`:
* Fix Metaclass attribute depth ignoring fields attribute. ([#2287][gh2287])
* Fix `format_suffix_patterns` to work with Django's `i18n_patterns`. ([#2278][gh2278])
* Ability to customize router URLs for custom actions, using `url_path`. ([#2010][gh2010])
+* Don't install Django REST Framework as egg. ([#2386][gh2386])
### 3.0.2
@@ -700,6 +701,7 @@ For older release notes, [please see the GitHub repo](old-release-notes).
[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.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.3+Release%22
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
@@ -767,3 +769,4 @@ For older release notes, [please see the GitHub repo](old-release-notes).
[gh2351]: https://github.com/tomchristie/django-rest-framework/issues/2351
[gh2355]: https://github.com/tomchristie/django-rest-framework/issues/2355
[gh2369]: https://github.com/tomchristie/django-rest-framework/issues/2369
+[gh2386]: https://github.com/tomchristie/django-rest-framework/issues/2386
--
cgit v1.2.3
From 73feaf6299827607eab94ce96b77b73671880626 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 9 Jan 2015 15:30:36 +0000
Subject: First pass at 3.1 pagination API
---
rest_framework/generics.py | 220 ++++++++++++++-----------------------------
rest_framework/mixins.py | 13 +--
rest_framework/pagination.py | 219 +++++++++++++++++++++++++++++++-----------
rest_framework/settings.py | 4 +-
tests/test_pagination.py | 216 +-----------------------------------------
5 files changed, 242 insertions(+), 430 deletions(-)
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 0d709c37..12fb6413 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -2,29 +2,13 @@
Generic views that provide commonly needed behaviour.
"""
from __future__ import unicode_literals
-
-from django.core.paginator import Paginator, InvalidPage
from django.db.models.query import QuerySet
from django.http import Http404
from django.shortcuts import get_object_or_404 as _get_object_or_404
-from django.utils import six
-from django.utils.translation import ugettext as _
from rest_framework import views, mixins
from rest_framework.settings import api_settings
-def strict_positive_int(integer_string, cutoff=None):
- """
- Cast a string to a strictly positive integer.
- """
- ret = int(integer_string)
- if ret <= 0:
- raise ValueError()
- if cutoff:
- ret = min(ret, cutoff)
- return ret
-
-
def get_object_or_404(queryset, *filter_args, **filter_kwargs):
"""
Same as Django's standard shortcut, but make sure to also raise 404
@@ -40,7 +24,6 @@ class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
-
# You'll need to either set these attributes,
# or override `get_queryset()`/`get_serializer_class()`.
# If you are overriding a view method, it is important that you call
@@ -50,146 +33,16 @@ class GenericAPIView(views.APIView):
queryset = None
serializer_class = None
- # If you want to use object lookups other than pk, set this attribute.
+ # If you want to use object lookups other than pk, set 'lookup_field'.
# For more complex lookup requirements override `get_object()`.
lookup_field = 'pk'
lookup_url_kwarg = None
- # Pagination settings
- paginate_by = api_settings.PAGINATE_BY
- paginate_by_param = api_settings.PAGINATE_BY_PARAM
- max_paginate_by = api_settings.MAX_PAGINATE_BY
- pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS
- page_kwarg = 'page'
-
# The filter backend classes to use for queryset filtering
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
- # The following attribute may be subject to change,
- # and should be considered private API.
- paginator_class = Paginator
-
- def get_serializer_context(self):
- """
- Extra context provided to the serializer class.
- """
- return {
- 'request': self.request,
- 'format': self.format_kwarg,
- 'view': self
- }
-
- def get_serializer(self, *args, **kwargs):
- """
- Return the serializer instance that should be used for validating and
- deserializing input, and for serializing output.
- """
- serializer_class = self.get_serializer_class()
- kwargs['context'] = self.get_serializer_context()
- return serializer_class(*args, **kwargs)
-
- def get_pagination_serializer(self, page):
- """
- Return a serializer instance to use with paginated data.
- """
- class SerializerClass(self.pagination_serializer_class):
- class Meta:
- object_serializer_class = self.get_serializer_class()
-
- pagination_serializer_class = SerializerClass
- context = self.get_serializer_context()
- return pagination_serializer_class(instance=page, context=context)
-
- def paginate_queryset(self, queryset):
- """
- Paginate a queryset if required, either returning a page object,
- or `None` if pagination is not configured for this view.
- """
- page_size = self.get_paginate_by()
- if not page_size:
- return None
-
- paginator = self.paginator_class(queryset, page_size)
- page_kwarg = self.kwargs.get(self.page_kwarg)
- page_query_param = self.request.query_params.get(self.page_kwarg)
- page = page_kwarg or page_query_param or 1
- try:
- page_number = paginator.validate_number(page)
- except InvalidPage:
- if page == 'last':
- page_number = paginator.num_pages
- else:
- raise Http404(_('Choose a valid page number. Page numbers must be a whole number, or must be the string "last".'))
-
- try:
- page = paginator.page(page_number)
- except InvalidPage as exc:
- error_format = _('Invalid page "{page_number}": {message}.')
- raise Http404(error_format.format(
- page_number=page_number, message=six.text_type(exc)
- ))
-
- return page
-
- def filter_queryset(self, queryset):
- """
- Given a queryset, filter it with whichever filter backend is in use.
-
- You are unlikely to want to override this method, although you may need
- to call it either from a list view, or from a custom `get_object`
- method if you want to apply the configured filtering backend to the
- default queryset.
- """
- for backend in self.get_filter_backends():
- queryset = backend().filter_queryset(self.request, queryset, self)
- return queryset
-
- def get_filter_backends(self):
- """
- Returns the list of filter backends that this view requires.
- """
- return list(self.filter_backends)
-
- # The following methods provide default implementations
- # that you may want to override for more complex cases.
-
- def get_paginate_by(self):
- """
- Return the size of pages to use with pagination.
-
- If `PAGINATE_BY_PARAM` is set it will attempt to get the page size
- from a named query parameter in the url, eg. ?page_size=100
-
- Otherwise defaults to using `self.paginate_by`.
- """
- if self.paginate_by_param:
- try:
- return strict_positive_int(
- self.request.query_params[self.paginate_by_param],
- cutoff=self.max_paginate_by
- )
- except (KeyError, ValueError):
- pass
-
- return self.paginate_by
-
- def get_serializer_class(self):
- """
- Return the class to use for the serializer.
- Defaults to using `self.serializer_class`.
-
- You may want to override this if you need to provide different
- serializations depending on the incoming request.
-
- (Eg. admins get full serialization, others get basic serialization)
- """
- assert self.serializer_class is not None, (
- "'%s' should either include a `serializer_class` attribute, "
- "or override the `get_serializer_class()` method."
- % self.__class__.__name__
- )
-
- return self.serializer_class
+ # The style to use for queryset pagination.
+ pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get_queryset(self):
"""
@@ -246,6 +99,73 @@ class GenericAPIView(views.APIView):
return obj
+ def get_serializer(self, *args, **kwargs):
+ """
+ Return the serializer instance that should be used for validating and
+ deserializing input, and for serializing output.
+ """
+ serializer_class = self.get_serializer_class()
+ kwargs['context'] = self.get_serializer_context()
+ return serializer_class(*args, **kwargs)
+
+ def get_serializer_class(self):
+ """
+ Return the class to use for the serializer.
+ Defaults to using `self.serializer_class`.
+
+ You may want to override this if you need to provide different
+ serializations depending on the incoming request.
+
+ (Eg. admins get full serialization, others get basic serialization)
+ """
+ assert self.serializer_class is not None, (
+ "'%s' should either include a `serializer_class` attribute, "
+ "or override the `get_serializer_class()` method."
+ % self.__class__.__name__
+ )
+
+ return self.serializer_class
+
+ def get_serializer_context(self):
+ """
+ Extra context provided to the serializer class.
+ """
+ return {
+ 'request': self.request,
+ 'format': self.format_kwarg,
+ 'view': self
+ }
+
+ def filter_queryset(self, queryset):
+ """
+ Given a queryset, filter it with whichever filter backend is in use.
+
+ You are unlikely to want to override this method, although you may need
+ to call it either from a list view, or from a custom `get_object`
+ method if you want to apply the configured filtering backend to the
+ default queryset.
+ """
+ for backend in list(self.filter_backends):
+ queryset = backend().filter_queryset(self.request, queryset, self)
+ return queryset
+
+ @property
+ def pager(self):
+ if not hasattr(self, '_pager'):
+ if self.pagination_class is None:
+ self._pager = None
+ else:
+ self._pager = self.pagination_class()
+ return self._pager
+
+ def paginate_queryset(self, queryset):
+ if self.pager is None:
+ return None
+ return self.pager.paginate_queryset(queryset, self.request, view=self)
+
+ def get_paginated_response(self, objects):
+ return self.pager.get_paginated_response(objects)
+
# Concrete view classes that provide method handlers
# by composing the mixin classes with the base view.
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index 2074a107..c34cfcee 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -5,7 +5,6 @@ We don't bind behaviour to http method handlers yet,
which allows mixin classes to be composed in interesting ways.
"""
from __future__ import unicode_literals
-
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
@@ -37,12 +36,14 @@ class ListModelMixin(object):
List a queryset.
"""
def list(self, request, *args, **kwargs):
- instance = self.filter_queryset(self.get_queryset())
- page = self.paginate_queryset(instance)
+ queryset = self.filter_queryset(self.get_queryset())
+
+ page = self.paginate_queryset(queryset)
if page is not None:
- serializer = self.get_pagination_serializer(page)
- else:
- serializer = self.get_serializer(instance, many=True)
+ serializer = self.get_serializer(page, many=True)
+ return self.get_paginated_response(serializer.data)
+
+ serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index f31e5fa4..da2d60a4 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -3,87 +3,192 @@ Pagination serializers determine the structure of the output that should
be used for paginated responses.
"""
from __future__ import unicode_literals
-from rest_framework import serializers
+from django.core.paginator import InvalidPage, Paginator as DjangoPaginator
+from django.utils import six
+from django.utils.translation import ugettext as _
+from rest_framework.compat import OrderedDict
+from rest_framework.exceptions import NotFound
+from rest_framework.response import Response
+from rest_framework.settings import api_settings
from rest_framework.templatetags.rest_framework import replace_query_param
-class NextPageField(serializers.Field):
+def _strict_positive_int(integer_string, cutoff=None):
"""
- Field that returns a link to the next page in paginated results.
+ Cast a string to a strictly positive integer.
"""
- page_field = 'page'
-
- def to_representation(self, value):
- if not value.has_next():
- return None
- page = value.next_page_number()
- request = self.context.get('request')
- url = request and request.build_absolute_uri() or ''
- return replace_query_param(url, self.page_field, page)
+ ret = int(integer_string)
+ if ret <= 0:
+ raise ValueError()
+ if cutoff:
+ ret = min(ret, cutoff)
+ return ret
-class PreviousPageField(serializers.Field):
- """
- Field that returns a link to the previous page in paginated results.
- """
- page_field = 'page'
+class BasePagination(object):
+ def paginate_queryset(self, queryset, request):
+ raise NotImplemented('paginate_queryset() must be implemented.')
- def to_representation(self, value):
- if not value.has_previous():
- return None
- page = value.previous_page_number()
- request = self.context.get('request')
- url = request and request.build_absolute_uri() or ''
- return replace_query_param(url, self.page_field, page)
+ def get_paginated_response(self, data, page, request):
+ raise NotImplemented('get_paginated_response() must be implemented.')
-class DefaultObjectSerializer(serializers.ReadOnlyField):
+class PageNumberPagination(BasePagination):
"""
- If no object serializer is specified, then this serializer will be applied
- as the default.
+ A simple page number based style that supports page numbers as
+ query parameters. For example:
+
+ http://api.example.org/accounts/?page=4
+ http://api.example.org/accounts/?page=4&page_size=100
"""
+ # The default page size.
+ # Defaults to `None`, meaning pagination is disabled.
+ paginate_by = api_settings.PAGINATE_BY
- def __init__(self, source=None, many=None, context=None):
- # Note: Swallow context and many kwargs - only required for
- # eg. ModelSerializer.
- super(DefaultObjectSerializer, self).__init__(source=source)
+ # Client can control the page using this query parameter.
+ page_query_param = 'page'
+ # Client can control the page size using this query parameter.
+ # Default is 'None'. Set to eg 'page_size' to enable usage.
+ paginate_by_param = api_settings.PAGINATE_BY_PARAM
-class BasePaginationSerializer(serializers.Serializer):
- """
- A base class for pagination serializers to inherit from,
- to make implementing custom serializers more easy.
- """
- results_field = 'results'
+ # Set to an integer to limit the maximum page size the client may request.
+ # Only relevant if 'paginate_by_param' has also been set.
+ max_paginate_by = api_settings.MAX_PAGINATE_BY
- def __init__(self, *args, **kwargs):
+ def paginate_queryset(self, queryset, request, view):
"""
- Override init to add in the object serializer field on-the-fly.
+ Paginate a queryset if required, either returning a page object,
+ or `None` if pagination is not configured for this view.
"""
- super(BasePaginationSerializer, self).__init__(*args, **kwargs)
- results_field = self.results_field
+ for attr in (
+ 'paginate_by', 'page_query_param',
+ 'paginate_by_param', 'max_paginate_by'
+ ):
+ if hasattr(view, attr):
+ setattr(self, attr, getattr(view, attr))
+
+ page_size = self.get_page_size(request)
+ if not page_size:
+ return None
+ paginator = DjangoPaginator(queryset, page_size)
+ page_string = request.query_params.get(self.page_query_param, 1)
try:
- object_serializer = self.Meta.object_serializer_class
- except AttributeError:
- object_serializer = DefaultObjectSerializer
+ page_number = paginator.validate_number(page_string)
+ except InvalidPage:
+ if page_string == 'last':
+ page_number = paginator.num_pages
+ else:
+ msg = _(
+ 'Choose a valid page number. Page numbers must be a '
+ 'whole number, or must be the string "last".'
+ )
+ raise NotFound(msg)
try:
- list_serializer_class = object_serializer.Meta.list_serializer_class
- except AttributeError:
- list_serializer_class = serializers.ListSerializer
+ self.page = paginator.page(page_number)
+ except InvalidPage as exc:
+ msg = _('Invalid page "{page_number}": {message}.').format(
+ page_number=page_number, message=six.text_type(exc)
+ )
+ raise NotFound(msg)
+
+ self.request = request
+ return self.page
+
+ def get_paginated_response(self, objects):
+ return Response(OrderedDict([
+ ('count', self.page.paginator.count),
+ ('next', self.get_next_link()),
+ ('previous', self.get_previous_link()),
+ ('results', objects)
+ ]))
+
+ def get_page_size(self, request):
+ if self.paginate_by_param:
+ try:
+ return _strict_positive_int(
+ request.query_params[self.paginate_by_param],
+ cutoff=self.max_paginate_by
+ )
+ except (KeyError, ValueError):
+ pass
+
+ return self.paginate_by
+
+ def get_next_link(self):
+ if not self.page.has_next():
+ return None
+ url = self.request.build_absolute_uri()
+ page_number = self.page.next_page_number()
+ return replace_query_param(url, self.page_query_param, page_number)
- self.fields[results_field] = list_serializer_class(
- child=object_serializer(),
- source='object_list'
- )
- self.fields[results_field].bind(field_name=results_field, parent=self)
+ def get_previous_link(self):
+ if not self.page.has_previous():
+ return None
+ url = self.request.build_absolute_uri()
+ page_number = self.page.previous_page_number()
+ return replace_query_param(url, self.page_query_param, page_number)
-class PaginationSerializer(BasePaginationSerializer):
+class LimitOffsetPagination(BasePagination):
"""
- A default implementation of a pagination serializer.
+ A limit/offset based style. For example:
+
+ http://api.example.org/accounts/?limit=100
+ http://api.example.org/accounts/?offset=400&limit=100
"""
- count = serializers.ReadOnlyField(source='paginator.count')
- next = NextPageField(source='*')
- previous = PreviousPageField(source='*')
+ default_limit = api_settings.PAGINATE_BY
+ limit_query_param = 'limit'
+ offset_query_param = 'offset'
+ max_limit = None
+
+ def paginate_queryset(self, queryset, request, view):
+ self.limit = self.get_limit(request)
+ self.offset = self.get_offset(request)
+ self.count = queryset.count()
+ self.request = request
+ return queryset[self.offset:self.offset + self.limit]
+
+ def get_paginated_response(self, objects):
+ return Response(OrderedDict([
+ ('count', self.count),
+ ('next', self.get_next_link()),
+ ('previous', self.get_previous_link()),
+ ('results', objects)
+ ]))
+
+ def get_limit(self, request):
+ if self.limit_query_param:
+ try:
+ return _strict_positive_int(
+ request.query_params[self.limit_query_param],
+ cutoff=self.max_limit
+ )
+ except (KeyError, ValueError):
+ pass
+
+ return self.default_limit
+
+ def get_offset(self, request):
+ try:
+ return _strict_positive_int(
+ request.query_params[self.offset_query_param],
+ )
+ except (KeyError, ValueError):
+ return 0
+
+ def get_next_link(self, page):
+ if self.offset + self.limit >= self.count:
+ return None
+ url = self.request.build_absolute_uri()
+ offset = self.offset + self.limit
+ return replace_query_param(url, self.offset_query_param, offset)
+
+ def get_previous_link(self, page):
+ if self.offset - self.limit < 0:
+ return None
+ url = self.request.build_absolute_uri()
+ offset = self.offset - self.limit
+ return replace_query_param(url, self.offset_query_param, offset)
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
index 877d461b..3cce26b1 100644
--- a/rest_framework/settings.py
+++ b/rest_framework/settings.py
@@ -49,7 +49,7 @@ DEFAULTS = {
'DEFAULT_VERSIONING_CLASS': None,
# Generic view behavior
- 'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'rest_framework.pagination.PaginationSerializer',
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'DEFAULT_FILTER_BACKENDS': (),
# Throttling
@@ -130,7 +130,7 @@ IMPORT_STRINGS = (
'DEFAULT_CONTENT_NEGOTIATION_CLASS',
'DEFAULT_METADATA_CLASS',
'DEFAULT_VERSIONING_CLASS',
- 'DEFAULT_PAGINATION_SERIALIZER_CLASS',
+ 'DEFAULT_PAGINATION_CLASS',
'DEFAULT_FILTER_BACKENDS',
'EXCEPTION_HANDLER',
'TEST_REQUEST_RENDERER_CLASSES',
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index 1fd9cf9c..d410cd5e 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -1,10 +1,9 @@
from __future__ import unicode_literals
import datetime
from decimal import Decimal
-from django.core.paginator import Paginator
from django.test import TestCase
from django.utils import unittest
-from rest_framework import generics, serializers, status, pagination, filters
+from rest_framework import generics, serializers, status, filters
from rest_framework.compat import django_filters
from rest_framework.test import APIRequestFactory
from .models import BasicModel, FilterableItem
@@ -238,45 +237,6 @@ class IntegrationTestPaginationAndFiltering(TestCase):
self.assertEqual(response.data['previous'], None)
-class PassOnContextPaginationSerializer(pagination.PaginationSerializer):
- class Meta:
- object_serializer_class = serializers.Serializer
-
-
-class UnitTestPagination(TestCase):
- """
- Unit tests for pagination of primitive objects.
- """
-
- def setUp(self):
- self.objects = [char * 3 for char in 'abcdefghijklmnopqrstuvwxyz']
- paginator = Paginator(self.objects, 10)
- self.first_page = paginator.page(1)
- self.last_page = paginator.page(3)
-
- def test_native_pagination(self):
- serializer = pagination.PaginationSerializer(self.first_page)
- self.assertEqual(serializer.data['count'], 26)
- self.assertEqual(serializer.data['next'], '?page=2')
- self.assertEqual(serializer.data['previous'], None)
- self.assertEqual(serializer.data['results'], self.objects[:10])
-
- serializer = pagination.PaginationSerializer(self.last_page)
- self.assertEqual(serializer.data['count'], 26)
- self.assertEqual(serializer.data['next'], None)
- self.assertEqual(serializer.data['previous'], '?page=2')
- self.assertEqual(serializer.data['results'], self.objects[20:])
-
- def test_context_available_in_result(self):
- """
- Ensure context gets passed through to the object serializer.
- """
- serializer = PassOnContextPaginationSerializer(self.first_page, context={'foo': 'bar'})
- serializer.data
- results = serializer.fields[serializer.results_field]
- self.assertEqual(serializer.context, results.context)
-
-
class TestUnpaginated(TestCase):
"""
Tests for list views without pagination.
@@ -377,177 +337,3 @@ class TestMaxPaginateByParam(TestCase):
request = factory.get('/')
response = self.view(request).render()
self.assertEqual(response.data['results'], self.data[:3])
-
-
-# Tests for context in pagination serializers
-
-class CustomField(serializers.ReadOnlyField):
- def to_native(self, value):
- if 'view' not in self.context:
- raise RuntimeError("context isn't getting passed into custom field")
- return "value"
-
-
-class BasicModelSerializer(serializers.Serializer):
- text = CustomField()
-
- def to_native(self, value):
- if 'view' not in self.context:
- raise RuntimeError("context isn't getting passed into serializer")
- return super(BasicSerializer, self).to_native(value)
-
-
-class TestContextPassedToCustomField(TestCase):
- def setUp(self):
- BasicModel.objects.create(text='ala ma kota')
-
- def test_with_pagination(self):
- class ListView(generics.ListCreateAPIView):
- queryset = BasicModel.objects.all()
- serializer_class = BasicModelSerializer
- paginate_by = 1
-
- self.view = ListView.as_view()
- request = factory.get('/')
- response = self.view(request).render()
-
- self.assertEqual(response.status_code, status.HTTP_200_OK)
-
-
-# Tests for custom pagination serializers
-
-class LinksSerializer(serializers.Serializer):
- next = pagination.NextPageField(source='*')
- prev = pagination.PreviousPageField(source='*')
-
-
-class CustomPaginationSerializer(pagination.BasePaginationSerializer):
- links = LinksSerializer(source='*') # Takes the page object as the source
- total_results = serializers.ReadOnlyField(source='paginator.count')
-
- results_field = 'objects'
-
-
-class CustomFooSerializer(serializers.Serializer):
- foo = serializers.CharField()
-
-
-class CustomFooPaginationSerializer(pagination.PaginationSerializer):
- class Meta:
- object_serializer_class = CustomFooSerializer
-
-
-class TestCustomPaginationSerializer(TestCase):
- def setUp(self):
- objects = ['john', 'paul', 'george', 'ringo']
- paginator = Paginator(objects, 2)
- self.page = paginator.page(1)
-
- def test_custom_pagination_serializer(self):
- request = APIRequestFactory().get('/foobar')
- serializer = CustomPaginationSerializer(
- instance=self.page,
- context={'request': request}
- )
- expected = {
- 'links': {
- 'next': 'http://testserver/foobar?page=2',
- 'prev': None
- },
- 'total_results': 4,
- 'objects': ['john', 'paul']
- }
- self.assertEqual(serializer.data, expected)
-
- def test_custom_pagination_serializer_with_custom_object_serializer(self):
- objects = [
- {'foo': 'bar'},
- {'foo': 'spam'}
- ]
- paginator = Paginator(objects, 1)
- page = paginator.page(1)
- serializer = CustomFooPaginationSerializer(page)
- serializer.data
-
-
-class NonIntegerPage(object):
-
- def __init__(self, paginator, object_list, prev_token, token, next_token):
- self.paginator = paginator
- self.object_list = object_list
- self.prev_token = prev_token
- self.token = token
- self.next_token = next_token
-
- def has_next(self):
- return not not self.next_token
-
- def next_page_number(self):
- return self.next_token
-
- def has_previous(self):
- return not not self.prev_token
-
- def previous_page_number(self):
- return self.prev_token
-
-
-class NonIntegerPaginator(object):
-
- def __init__(self, object_list, per_page):
- self.object_list = object_list
- self.per_page = per_page
-
- def count(self):
- # pretend like we don't know how many pages we have
- return None
-
- def page(self, token=None):
- if token:
- try:
- first = self.object_list.index(token)
- except ValueError:
- first = 0
- else:
- first = 0
- n = len(self.object_list)
- last = min(first + self.per_page, n)
- prev_token = self.object_list[last - (2 * self.per_page)] if first else None
- next_token = self.object_list[last] if last < n else None
- return NonIntegerPage(self, self.object_list[first:last], prev_token, token, next_token)
-
-
-class TestNonIntegerPagination(TestCase):
- def test_custom_pagination_serializer(self):
- objects = ['john', 'paul', 'george', 'ringo']
- paginator = NonIntegerPaginator(objects, 2)
-
- request = APIRequestFactory().get('/foobar')
- serializer = CustomPaginationSerializer(
- instance=paginator.page(),
- context={'request': request}
- )
- expected = {
- 'links': {
- 'next': 'http://testserver/foobar?page={0}'.format(objects[2]),
- 'prev': None
- },
- 'total_results': None,
- 'objects': objects[:2]
- }
- self.assertEqual(serializer.data, expected)
-
- request = APIRequestFactory().get('/foobar')
- serializer = CustomPaginationSerializer(
- instance=paginator.page('george'),
- context={'request': request}
- )
- expected = {
- 'links': {
- 'next': None,
- 'prev': 'http://testserver/foobar?page={0}'.format(objects[0]),
- },
- 'total_results': None,
- 'objects': objects[2:]
- }
- self.assertEqual(serializer.data, expected)
--
cgit v1.2.3
From 8ccf5bcc0bb3455c0d71a0e0d845ef54489bb28e Mon Sep 17 00:00:00 2001
From: Travis Swientek
Date: Fri, 9 Jan 2015 11:36:21 -0800
Subject: Tweaked a few issues in the tutorial documentation.
---
docs/tutorial/1-serialization.md | 2 +-
docs/tutorial/3-class-based-views.md | 2 +-
docs/tutorial/4-authentication-and-permissions.md | 2 +-
docs/tutorial/5-relationships-and-hyperlinked-apis.md | 2 ++
4 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index 60a3d989..41ff4d07 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -191,7 +191,7 @@ Our `SnippetSerializer` class is replicating a lot of information that's also co
In the same way that Django provides both `Form` classes and `ModelForm` classes, REST framework includes both `Serializer` classes, and `ModelSerializer` classes.
Let's look at refactoring our serializer using the `ModelSerializer` class.
-Open the file `snippets/serializers.py` again, and edit the `SnippetSerializer` class.
+Open the file `snippets/serializers.py` again, and replace the `SnippetSerializer` class with the following.
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md
index 0a9ea3f1..abf82e49 100644
--- a/docs/tutorial/3-class-based-views.md
+++ b/docs/tutorial/3-class-based-views.md
@@ -64,7 +64,7 @@ That's looking good. Again, it's still pretty similar to the function based vie
We'll also need to refactor our `urls.py` slightly now we're using class based views.
- from django.conf.urls import patterns, url
+ from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md
index 592c77e8..887d1e56 100644
--- a/docs/tutorial/4-authentication-and-permissions.md
+++ b/docs/tutorial/4-authentication-and-permissions.md
@@ -177,7 +177,7 @@ In the snippets app, create a new file, `permissions.py`
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
-Now we can add that custom permission to our snippet instance endpoint, by editing the `permission_classes` property on the `SnippetDetail` class:
+Now we can add that custom permission to our snippet instance endpoint, by editing the `permission_classes` property on the `SnippetDetail` view class:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
index c21efd7f..2841f03e 100644
--- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md
+++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
@@ -106,6 +106,8 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p
After adding all those names into our URLconf, our final `snippets/urls.py` file should look something like this:
+ from django.conf.urls import url, include
+
# API endpoints
urlpatterns = format_suffix_patterns([
url(r'^$', views.api_root),
--
cgit v1.2.3
From 50b206d3739660cdf089b0a3f8a5bb21d6970e00 Mon Sep 17 00:00:00 2001
From: Steven Loria
Date: Sat, 10 Jan 2015 10:17:37 -0600
Subject: Fix broken links in README
---
README.md | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/README.md b/README.md
index cf3dc857..74bcaeef 100644
--- a/README.md
+++ b/README.md
@@ -190,18 +190,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[sandbox]: http://restframework.herokuapp.com/
[index]: http://www.django-rest-framework.org/
-[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication.html#oauthauthentication
-[oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication.html#oauth2authentication
-[serializer-section]: http://www.django-rest-framework.org/api-guide/serializers.html#serializers
-[modelserializer-section]: http://www.django-rest-framework.org/api-guide/serializers.html#modelserializer
-[functionview-section]: http://www.django-rest-framework.org/api-guide/views.html#function-based-views
-[generic-views]: http://www.django-rest-framework.org/api-guide/generic-views.html
-[viewsets]: http://www.django-rest-framework.org/api-guide/viewsets.html
-[routers]: http://www.django-rest-framework.org/api-guide/routers.html
-[serializers]: http://www.django-rest-framework.org/api-guide/serializers.html
-[authentication]: http://www.django-rest-framework.org/api-guide/authentication.html
-
-[rest-framework-2-announcement]: http://www.django-rest-framework.org/topics/rest-framework-2-announcement.html
+[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#oauthauthentication
+[oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication/#oauth2authentication
+[serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers
+[modelserializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#modelserializer
+[functionview-section]: http://www.django-rest-framework.org/api-guide/views/#function-based-views
+[generic-views]: http://www.django-rest-framework.org/api-guide/generic-views/
+[viewsets]: http://www.django-rest-framework.org/api-guide/viewsets/
+[routers]: http://www.django-rest-framework.org/api-guide/routers/
+[serializers]: http://www.django-rest-framework.org/api-guide/serializers/
+[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
--
cgit v1.2.3
From d6bff10f9829b3cef1c2773c303b172a8c7ec525 Mon Sep 17 00:00:00 2001
From: Ask Holme
Date: Sat, 10 Jan 2015 18:15:21 +0100
Subject: Make FileUploadParser work with standard django API
Output from parsers ends up in a Django MergeDict and they exists elements to be dicts - not None---
rest_framework/parsers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
index 3e3395c0..401856ec 100644
--- a/rest_framework/parsers.py
+++ b/rest_framework/parsers.py
@@ -277,7 +277,7 @@ class FileUploadParser(BaseParser):
for index, handler in enumerate(upload_handlers):
file_obj = handler.file_complete(counters[index])
if file_obj:
- return DataAndFiles(None, {'file': file_obj})
+ return DataAndFiles({}, {'file': file_obj})
raise ParseError("FileUpload parse error - "
"none of upload handlers can handle the stream")
--
cgit v1.2.3
From d6d08db0dd16f4a4a93b69ecf1c5948f375335b0 Mon Sep 17 00:00:00 2001
From: José Padilla
Date: Sun, 11 Jan 2015 10:55:56 -0400
Subject: Fix ident format when using HTTP_X_FORWARDED_FOR
If NUM_PROXIES setting is set to None,
HTTP_X_FORWARDED_FOR might be used as is, which
might contain spaces and cause errors on
cache backends like memcached.
---
rest_framework/throttling.py | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py
index 361dbddf..7dfe2f96 100644
--- a/rest_framework/throttling.py
+++ b/rest_framework/throttling.py
@@ -35,7 +35,7 @@ class BaseThrottle(object):
client_addr = addrs[-min(num_proxies, len(xff))]
return client_addr.strip()
- return xff if xff else remote_addr
+ return ''.join(xff.split()) if xff else remote_addr
def wait(self):
"""
@@ -173,12 +173,6 @@ class AnonRateThrottle(SimpleRateThrottle):
if request.user.is_authenticated():
return None # Only throttle unauthenticated requests.
- ident = request.META.get('HTTP_X_FORWARDED_FOR')
- if ident is None:
- ident = request.META.get('REMOTE_ADDR')
- else:
- ident = ''.join(ident.split())
-
return self.cache_format % {
'scope': self.scope,
'ident': self.get_ident(request)
--
cgit v1.2.3
From cc13ee0577fb3de9602da634ab9c835749da49c4 Mon Sep 17 00:00:00 2001
From: José Padilla
Date: Mon, 12 Jan 2015 08:12:24 -0400
Subject: Fix error when NUM_PROXIES is greater than one
---
rest_framework/throttling.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py
index 7dfe2f96..0f10136d 100644
--- a/rest_framework/throttling.py
+++ b/rest_framework/throttling.py
@@ -32,7 +32,7 @@ class BaseThrottle(object):
if num_proxies == 0 or xff is None:
return remote_addr
addrs = xff.split(',')
- client_addr = addrs[-min(num_proxies, len(xff))]
+ client_addr = addrs[-min(num_proxies, len(addrs))]
return client_addr.strip()
return ''.join(xff.split()) if xff else remote_addr
--
cgit v1.2.3
From 7f9a62a5bf6a86c4d0a96e5f00d7e96b22d3337f Mon Sep 17 00:00:00 2001
From: Philip Neustrom
Date: Tue, 13 Jan 2015 15:19:52 +0800
Subject: Fix link to `django-rest-framework-filters` (formerly
`django-rest-framework-chain`)
---
docs/api-guide/filtering.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md
index 07420d84..2b6d5449 100644
--- a/docs/api-guide/filtering.md
+++ b/docs/api-guide/filtering.md
@@ -388,9 +388,9 @@ We could achieve the same behavior by overriding `get_queryset()` on the views,
The following third party packages provide additional filter implementations.
-## Django REST framework chain
+## Django REST framework filters package
-The [django-rest-framework-chain package][django-rest-framework-chain] works together with the `DjangoFilterBackend` class, and allows you to easily create filters across relationships, or create multiple filter lookup types for a given field.
+The [django-rest-framework-filters package][django-rest-framework-filters] works together with the `DjangoFilterBackend` class, and allows you to easily create filters across relationships, or create multiple filter lookup types for a given field.
[cite]: https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-specific-objects-with-filters
[django-filter]: https://github.com/alex/django-filter
@@ -400,4 +400,4 @@ The [django-rest-framework-chain package][django-rest-framework-chain] works tog
[view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models
[nullbooleanselect]: https://github.com/django/django/blob/master/django/forms/widgets.py
[search-django-admin]: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
-[django-rest-framework-chain]: https://github.com/philipn/django-rest-framework-chain
+[django-rest-framework-filters]: https://github.com/philipn/django-rest-framework-filters
--
cgit v1.2.3
From 2b28026fc10c2a8d3e4c9ef1f11b2f802a40ec77 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 13 Jan 2015 10:44:40 +0000
Subject: Translation info -> project management
---
CONTRIBUTING.md | 125 ++++++++++++++------------------------
docs/topics/project-management.md | 55 ++++++++++++++++-
2 files changed, 99 insertions(+), 81 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d94eb87e..c9626ebf 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,9 +10,9 @@ There are many ways you can contribute to Django REST framework. We'd like it t
The most important thing you can do to help push the REST framework project forward is to be actively involved wherever possible. Code contributions are often overvalued as being the primary way to get involved in a project, we don't believe that needs to be the case.
-If you use REST framework, we'd love you to be vocal about your experiences with it - you might consider writing a blog post about using REST framework, or publishing a tutorial about building a project with a particular Javascript framework. Experiences from beginners can be particularly helpful because you'll be in the best position to assess which bits of REST framework are more difficult to understand and work with.
+If you use REST framework, we'd love you to be vocal about your experiences with it - you might consider writing a blog post about using REST framework, or publishing a tutorial about building a project with a particular JavaScript framework. Experiences from beginners can be particularly helpful because you'll be in the best position to assess which bits of REST framework are more difficult to understand and work with.
-Other really great ways you can help move the community forward include helping answer questions on the [discussion group][google-group], or setting up an [email alert on StackOverflow][so-filter] so that you get notified of any new questions with the `django-rest-framework` tag.
+Other really great ways you can help move the community forward include helping to answer questions on the [discussion group][google-group], or setting up an [email alert on StackOverflow][so-filter] so that you get notified of any new questions with the `django-rest-framework` tag.
When answering questions make sure to help future contributors find their way around by hyperlinking wherever possible to related threads and tickets, and include backlinks from those items if relevant.
@@ -52,7 +52,7 @@ To start developing on Django REST framework, clone the repo:
git clone git@github.com:tomchristie/django-rest-framework.git
-Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you setup your editor to automatically indicated non-conforming styles.
+Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you set up your editor to automatically indicate non-conforming styles.
## Testing
@@ -60,13 +60,47 @@ To run the tests, clone the repository, and then:
# Setup the virtual environment
virtualenv env
- env/bin/activate
+ source env/bin/activate
pip install -r requirements.txt
# Run the tests
./runtests.py
-You can also use the excellent [`tox`][tox] testing tool to run the tests against all supported versions of Python and Django. Install `tox` globally, and then simply run:
+### Test options
+
+Run using a more concise output style.
+
+ ./runtests.py -q
+
+Run the tests using a more concise output style, no coverage, no flake8.
+
+ ./runtests.py --fast
+
+Don't run the flake8 code linting.
+
+ ./runtests.py --nolint
+
+Only run the flake8 code linting, don't run the tests.
+
+ ./runtests.py --lintonly
+
+Run the tests for a given test case.
+
+ ./runtests.py MyTestCase
+
+Run the tests for a given test method.
+
+ ./runtests.py MyTestCase.test_this_method
+
+Shorter form to run the tests for a given test method.
+
+ ./runtests.py test_this_method
+
+Note: The test case and test method matching is fuzzy and will sometimes run other tests that contain a partial string match to the given command line input.
+
+### Running against multiple environments
+
+You can also use the excellent [tox][tox] testing tool to run the tests against all supported versions of Python and Django. Install `tox` globally, and then simply run:
tox
@@ -82,7 +116,7 @@ GitHub's documentation for working on pull requests is [available here][pull-req
Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible with both Python 2 and Python 3, and that they run properly on all supported versions of Django.
-Once you've made a pull request take a look at the travis build status in the GitHub interface and make sure the tests are running as you'd expect.
+Once you've made a pull request take a look at the Travis build status in the GitHub interface and make sure the tests are running as you'd expect.
![Travis status][travis-status]
@@ -96,7 +130,7 @@ Sometimes, in order to ensure your code works on various different versions of D
The documentation for REST framework is built from the [Markdown][markdown] source files in [the docs directory][docs].
-There are many great markdown editors that make working with the documentation really easy. The [Mou editor for Mac][mou] is one such editor that comes highly recommended.
+There are many great Markdown editors that make working with the documentation really easy. The [Mou editor for Mac][mou] is one such editor that comes highly recommended.
## Building the documentation
@@ -104,7 +138,7 @@ To build the documentation, install MkDocs with `pip install mkdocs` and then ru
mkdocs build
-This will build the html output into the `html` directory.
+This will build the documentation into the `site` directory.
You can build the documentation and open a preview in a browser window by using the `serve` command.
@@ -117,8 +151,7 @@ Documentation should be in American English. The tone of the documentation is v
Some other tips:
* Keep paragraphs reasonably short.
-* Use double spacing after the end of sentences.
-* Don't use the abbreviations such as 'e.g.' but instead use long form, such as 'For example'.
+* Don't use abbreviations such as 'e.g.' but instead use the long form, such as 'For example'.
## Markdown style
@@ -151,7 +184,7 @@ If you are hyperlinking to another REST framework document, you should use a rel
[authentication]: ../api-guide/authentication.md
-Linking in this style means you'll be able to click the hyperlink in your markdown editor to open the referenced document. When the documentation is built, these links will be converted into regular links to HTML pages.
+Linking in this style means you'll be able to click the hyperlink in your Markdown editor to open the referenced document. When the documentation is built, these links will be converted into regular links to HTML pages.
##### 3. Notes
@@ -163,70 +196,6 @@ If you want to draw attention to a note or warning, use a pair of enclosing line
---
-# Third party packages
-
-New features to REST framework are generally recommended to be implemented as third party libraries that are developed outside of the core framework. Ideally third party libraries should be properly documented and packaged, and made available on PyPI.
-
-## Getting started
-
-If you have some functionality that you would like to implement as a third party package it's worth contacting the [discussion group][google-group] as others may be willing to get involved. We strongly encourage third party package development and will always try to prioritize time spent helping their development, documentation and packaging.
-
-We recommend the [`django-reusable-app`][django-reusable-app] template as a good resource for getting up and running with implementing a third party Django package.
-
-## Linking to your package
-
-Once your package is decently documented and available on PyPI open a pull request or issue, and we'll add a link to it from the main REST framework documentation.
-
-# Translations
-
-If REST framework isn't translated into your language you can request that it is at the [Transifex project][transifex].
-
-## Managing Transfiex
-The [official Transifex client][transifex-client] is used to upload and download translations to Transifex. The client is installed using pip:
-
-```
-pip install transifex-client
-```
-
-To use it you'll need a login to Transifex which has a password, and you'll need to have administrative access to the Transifex project. You'll need to create a `~/.transifexrc` file which contains your authentication information:
-
-```
-[https://www.transifex.com]
-username = user
-token =
-password = p@ssw0rd
-hostname = https://www.transifex.com
-```
-
-## Upload new source translations
-When any user-visible strings are changed, they should be uploaded to Transifex so that the translators can start to translate them. To do this, just run:
-
-```
-cd rest_framework
-django-admin.py makemessages -l en_US
-cd ..
-tx push -s
-```
-
-When pushing source files, Transifex will update the source strings of a resource to match those from the new source file.
-
-Here's how differences between the old and new source files will be handled:
-
-* New strings will be added.
-* Modified strings will be added as well.
-* Strings which do not exist in the new source file will be removed from the database, along with their translations. If that source strings gets re-added later then [Transifex Translation Memory][translation-memory] will automatically restore the translated string too.
-
-
-## Get translations
-When a translator has finished translating their work needs to be downloaded from Transifex into the source repo. To do this, run:
-
-```
-tx pull -a
-cd rest_framework
-django-admin.py compilemessages
-```
-
-You can then commit as normal.
[cite]: http://www.w3.org/People/Berners-Lee/FAQ.html
[code-of-conduct]: https://www.djangoproject.com/conduct/
@@ -234,13 +203,9 @@ You can then commit as normal.
[so-filter]: http://stackexchange.com/filters/66475/rest-framework
[issues]: https://github.com/tomchristie/django-rest-framework/issues?state=open
[pep-8]: http://www.python.org/dev/peps/pep-0008/
-[travis-status]: https://raw.github.com/tomchristie/django-rest-framework/master/docs/img/travis-status.png
+[travis-status]: ../img/travis-status.png
[pull-requests]: https://help.github.com/articles/using-pull-requests
[tox]: http://tox.readthedocs.org/en/latest/
[markdown]: http://daringfireball.net/projects/markdown/basics
[docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs
[mou]: http://mouapp.com/
-[django-reusable-app]: https://github.com/dabapps/django-reusable-app
-[transifex]: https://www.transifex.com/projects/p/django-rest-framework/
-[transifex-client]: https://pypi.python.org/pypi/transifex-client
-[translation-memory]: http://docs.transifex.com/guides/tm#let-tm-automatically-populate-translations
\ No newline at end of file
diff --git a/docs/topics/project-management.md b/docs/topics/project-management.md
index f581cabd..7f705196 100644
--- a/docs/topics/project-management.md
+++ b/docs/topics/project-management.md
@@ -63,10 +63,11 @@ The following template should be used for the description of the issue, and serv
Team members have the following responsibilities.
-* Add triage labels and milestones to tickets.
* Close invalid or resolved tickets.
+* Add triage labels and milestones to tickets.
* Merge finalized pull requests.
* Build and deploy the documentation, using `mkdocs gh-deploy`.
+* Build and update the included translation packs.
Further notes for maintainers:
@@ -112,6 +113,55 @@ When pushing the release to PyPI ensure that your environment has been installed
---
+## Translations
+
+The maintenance team are responsible for managing the translation packs include in REST framework. Translating the source strings into multiple languages is managed through the [transifex service][transifex-project].
+
+### Managing Transifex
+
+The [official Transifex client][transifex-client] is used to upload and download translations to Transifex. The client is installed using pip:
+
+ pip install transifex-client
+
+To use it you'll need a login to Transifex which has a password, and you'll need to have administrative access to the Transifex project. You'll need to create a `~/.transifexrc` file which contains your credentials.
+
+ [https://www.transifex.com]
+ username = ***
+ token = ***
+ password = ***
+ hostname = https://www.transifex.com
+
+### Upload new source files
+
+When any user visible strings are changed, they should be uploaded to Transifex so that the translators can start to translate them. To do this, just run:
+
+ # 1. Update the source django.po file, which is the US English version.
+ cd rest_framework
+ django-admin.py makemessages -l en_US
+ # 2. Push the source django.po file to Transifex.
+ cd ..
+ tx push -s
+
+When pushing source files, Transifex will update the source strings of a resource to match those from the new source file.
+
+Here's how differences between the old and new source files will be handled:
+
+* New strings will be added.
+* Modified strings will be added as well.
+* Strings which do not exist in the new source file will be removed from the database, along with their translations. If that source strings gets re-added later then [Transifex Translation Memory][translation-memory] will automatically include the translation string.
+
+### Download translations
+
+When a translator has finished translating their work needs to be downloaded from Transifex into the REST framework repository. To do this, run:
+
+ # 3. Pull the translated django.po files from Transifex.
+ tx pull -a
+ cd rest_framework
+ # 4. Compile the binary .mo files for all supported languages.
+ django-admin.py compilemessages
+
+---
+
## Project ownership
The PyPI package is owned by `@tomchristie`. As a backup `@j4mie` also has ownership of the package.
@@ -129,6 +179,9 @@ The following issues still need to be addressed:
[bus-factor]: http://en.wikipedia.org/wiki/Bus_factor
[un-triaged]: https://github.com/tomchristie/django-rest-framework/issues?q=is%3Aopen+no%3Alabel
+[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/
+[transifex-client]: https://pypi.python.org/pypi/transifex-client
+[translation-memory]: http://docs.transifex.com/guides/tm#let-tm-automatically-populate-translations
[github-org]: https://github.com/tomchristie/django-rest-framework/issues/2162
[sandbox]: http://restframework.herokuapp.com/
[mailing-list]: https://groups.google.com/forum/#!forum/django-rest-framework
--
cgit v1.2.3
From 8e2dc6b26dd546f6b31aa6d1feb881b181f3ea21 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 13 Jan 2015 12:07:25 +0000
Subject: Update internationalization docs
---
docs/index.md | 1 +
docs/topics/internationalisation.md | 95 -------------------------------------
docs/topics/internationalization.md | 72 ++++++++++++++++++++++++++++
mkdocs.yml | 1 +
4 files changed, 74 insertions(+), 95 deletions(-)
delete mode 100644 docs/topics/internationalisation.md
create mode 100644 docs/topics/internationalization.md
diff --git a/docs/index.md b/docs/index.md
index 544204c6..16376985 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -305,6 +305,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[settings]: api-guide/settings.md
[documenting-your-api]: topics/documenting-your-api.md
+[internationalization]: topics/documenting-your-api.md
[ajax-csrf-cors]: topics/ajax-csrf-cors.md
[browser-enhancements]: topics/browser-enhancements.md
[browsableapi]: topics/browsable-api.md
diff --git a/docs/topics/internationalisation.md b/docs/topics/internationalisation.md
deleted file mode 100644
index 2a476c86..00000000
--- a/docs/topics/internationalisation.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# Internationalisation
-REST framework ships with translatable error messages. You can make these appear in your language enabling [Django's standard translation mechanisms][django-translation] and by translating the messages into your language.
-
-## How to translate REST Framework errors
-
-REST framework translations are managed online using [Transifex.com][transifex]. To get started, checkout the guide in the [CONTRIBUTING.md guide][contributing].
-
-Sometimes you may want to use REST Framework in a language which has not been translated yet on Transifex. If that is the case then you should translate the error messages locally.
-
-#### How to translate REST Framework error messages locally:
-
-This guide assumes you are already familiar with how to translate a Django app. If you're not, start by reading [Django's translation docs][django-translation].
-
-1. Make a new folder where you want to store the translated errors. Add this
-path to your [`LOCALE_PATHS`][django-locale-paths] setting.
-
- ---
-
- **Note:** For the rest of
-this document we will assume the path you created was
-`/home/www/project/conf/locale/`, and that you have updated your `settings.py` to include the setting:
-
- ```
- LOCALE_PATHS = (
- '/home/www/project/conf/locale/',
- )
- ```
-
- ---
-
-2. Now create a subfolder for the language you want to translate. The folder should be named using [locale
-name][django-locale-name] notation. E.g. `de`, `pt_BR`, `es_AR`, etc.
-
- ```
- mkdir /home/www/project/conf/locale/pt_BR/LC_MESSAGES
- ```
-
-3. Now copy the base translations file from the REST framework source code
-into your translations folder
-
- ```
- cp /home/user/.virtualenvs/myproject/lib/python2.7/site-packages/rest_framework/locale/en_US/LC_MESSAGES/django.po
- /home/www/project/conf/locale/pt_BR/LC_MESSAGES
- ```
-
- This should create the file
- `/home/www/project/conf/locale/pt_BR/LC_MESSAGES/django.po`
-
- ---
-
- **Note:** To find out where `rest_framework` is installed, run
-
- ```
- python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
- ```
-
- ---
-
-
-4. Edit `/home/www/project/conf/locale/pt_BR/LC_MESSAGES/django.po` and
-translate all the error messages.
-
-5. Run `manage.py compilemessages -l pt_BR` to make the translations
-available for Django to use. You should see a message
-
- ```
- processing file django.po in /home/www/project/conf/locale/pt_BR/LC_MESSAGES
- ```
-
-6. Restart your server.
-
-
-
-## How Django chooses which language to use
-REST framework will use the same preferences to select which language to
-display as Django does. You can find more info in the [Django docs on discovering language preferences][django-language-preference]. For reference, these are
-
-1. First, it looks for the language prefix in the requested URL
-2. Failing that, it looks for the `LANGUAGE_SESSION_KEY` key in the current user’s session.
-3. Failing that, it looks for a cookie
-4. Failing that, it looks at the `Accept-Language` HTTP header.
-5. Failing that, it uses the global `LANGUAGE_CODE` setting.
-
----
-
-**Note:** You'll need to include the `django.middleware.locale.LocaleMiddleware` to enable any of the per-request language preferences.
-
----
-
-
-[django-translation]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation
-[django-language-preference]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#how-django-discovers-language-preference
-[django-locale-paths]: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-LOCALE_PATHS
-[django-locale-name]: https://docs.djangoproject.com/en/1.7/topics/i18n/#term-locale-name
-[contributing]: ../../CONTRIBUTING.md
diff --git a/docs/topics/internationalization.md b/docs/topics/internationalization.md
new file mode 100644
index 00000000..fdde6c43
--- /dev/null
+++ b/docs/topics/internationalization.md
@@ -0,0 +1,72 @@
+# Internationalization
+
+> Supporting internationalization is not optional. It must be a core feature.
+>
+> — [Jannis Leidel, speaking at Django Under the Hood, 2015][cite].
+
+REST framework ships with translatable error messages. You can make these appear in your language enabling [Django's standard translation mechanisms][django-translation].
+
+Doing so will allow you to:
+
+* Select a language other than English as the default, using the standard `LANGUAGE_CODE` Django setting.
+* Allow clients to choose a language themselves, using the `LocaleMiddleware` included with Django. A typical usage for API clients would be to include an `Accept-Language` request header.
+
+Note that the translations only apply to the error strings themselves. The format of error messages, and the keys of field names will remain the same. An example `400 Bad Request` response body might look like this:
+
+ {"detail": {"username": ["Esse campo deve ser unico."]}}
+
+If you want to use different string for parts of the response such as `detail` and `non_field_errors` then you can modify this behavior by using a [custom exception handler][custom-exception-handler].
+
+## Adding new translations
+
+REST framework translations are managed online using [Transifex][transifex-project]. You can use the Transifex service to add new translation languages. The maintenance team will then ensure that these translation strings are included in the REST framework package.
+
+Sometimes you may need to add translation strings to your project locally. You may need to do this if:
+
+* You want to use REST Framework in a language which has not been translated yet on Transifex.
+* Your project includes custom error messages, which are not part of REST framework's default translation strings.
+
+#### Translating a new language locally
+
+This guide assumes you are already familiar with how to translate a Django app. If you're not, start by reading [Django's translation docs][django-translation].
+
+If you're translating a new language you'll need to translate the existing REST framework error messages:
+
+1. Make a new folder where you want to store the internationalization resources. Add this path to your [`LOCALE_PATHS`][django-locale-paths] setting.
+
+2. Now create a subfolder for the language you want to translate. The folder should be named using [locale name][django-locale-name] notation. For example: `de`, `pt_BR`, `es_AR`.
+
+3. Now copy the [base translations file][django-po-source] from the REST framework source code into your translations folder.
+
+4. Edit the `django.po` file you've just copied, translating all the error messages.
+
+5. Run `manage.py compilemessages -l pt_BR` to make the translations
+available for Django to use. You should see a message like `processing file django.po in <...>/locale/pt_BR/LC_MESSAGES`.
+
+6. Restart your development server to see the changes take effect.
+
+If you're only translating custom error messages that exist inside your project codebase you don't need to copy the REST framework source `django.po` file into a `LOCALE_PATHS` folder, and can instead simply run Django's standard `makemessages` process.
+
+## How the language is determined
+
+If you want to allow per-request language preferences you'll need to include `django.middleware.locale.LocaleMiddleware` in your `MIDDLEWARE_CLASSES` setting.
+
+You can find more information on how the language preference is determined in the [Django documentation][django-language-preference]. For reference, the method is:
+
+1. First, it looks for the language prefix in the requested URL.
+2. Failing that, it looks for the `LANGUAGE_SESSION_KEY` key in the current user’s session.
+3. Failing that, it looks for a cookie.
+4. Failing that, it looks at the `Accept-Language` HTTP header.
+5. Failing that, it uses the global `LANGUAGE_CODE` setting.
+
+For API clients the most appropriate of these will typically be to use the `Accept-Language` header; Sessions and cookies will not be available unless using session authentication, and generally better practice to prefer an `Accept-Language` header for API clients rather than using language URL prefixes.
+
+[cite]: http://youtu.be/Wa0VfS2q94Y
+[django-translation]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation
+[custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling
+[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/
+[django-po-source]: https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/rest_framework/locale/en_US/LC_MESSAGES/django.po
+[django-language-preference]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#how-django-discovers-language-preference
+[django-locale-paths]: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-LOCALE_PATHS
+[django-locale-name]: https://docs.djangoproject.com/en/1.7/topics/i18n/#term-locale-name
+[contributing]: ../../CONTRIBUTING.md
diff --git a/mkdocs.yml b/mkdocs.yml
index b394a827..89df4cea 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -42,6 +42,7 @@ pages:
- ['api-guide/testing.md', 'API Guide', 'Testing']
- ['api-guide/settings.md', 'API Guide', 'Settings']
- ['topics/documenting-your-api.md', 'Topics', 'Documenting your API']
+ - ['topics/internationalization.md', 'Topics', 'Internationalization']
- ['topics/ajax-csrf-cors.md', 'Topics', 'AJAX, CSRF & CORS']
- ['topics/browser-enhancements.md', 'Topics',]
- ['topics/browsable-api.md', 'Topics', 'The Browsable API']
--
cgit v1.2.3
From 564f845e21cd55669311db9491b85dc86a5ff628 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 13 Jan 2015 12:21:03 +0000
Subject: Lower header font weights for nicer docs style
---
docs/topics/3.1-announcement.md | 7 +++++++
docs_theme/css/default.css | 15 +++++++++++++++
2 files changed, 22 insertions(+)
create mode 100644 docs/topics/3.1-announcement.md
diff --git a/docs/topics/3.1-announcement.md b/docs/topics/3.1-announcement.md
new file mode 100644
index 00000000..a0ad9829
--- /dev/null
+++ b/docs/topics/3.1-announcement.md
@@ -0,0 +1,7 @@
+# Versioning
+
+# Pagination
+
+# Internationalization
+
+# ModelSerializer API
diff --git a/docs_theme/css/default.css b/docs_theme/css/default.css
index 48d00366..3feff0ba 100644
--- a/docs_theme/css/default.css
+++ b/docs_theme/css/default.css
@@ -171,6 +171,21 @@ body{
background-attachment: fixed;
}
+
+#main-content h1:first-of-type {
+ margin-top: 0
+}
+
+#main-content h1, #main-content h2 {
+ font-weight: 300;
+ margin-top: 20px
+}
+
+#main-content h3, #main-content h4, #main-content h5 {
+ font-weight: 500;
+ margin-top: 15px
+}
+
/* custom navigation styles */
.navbar .navbar-inner{
--
cgit v1.2.3
From 1bcec3a0ac4346b31b655a08505d3e3dc2156604 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 13 Jan 2015 17:14:13 +0000
Subject: API tweaks and pagination documentation
---
docs/api-guide/pagination.md | 162 ++++++++++++++++---------------------------
rest_framework/generics.py | 6 +-
rest_framework/pagination.py | 28 +++++---
3 files changed, 80 insertions(+), 116 deletions(-)
diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md
index 83429292..9fbeb22a 100644
--- a/docs/api-guide/pagination.md
+++ b/docs/api-guide/pagination.md
@@ -6,148 +6,101 @@ source: pagination.py
>
> — [Django documentation][cite]
-REST framework includes a `PaginationSerializer` class that makes it easy to return paginated data in a way that can then be rendered to arbitrary media types.
+REST framework includes support for customizable pagination styles. This allows you to modify how large result sets are split into individual pages of data.
-## Paginating basic data
+The pagination API can support either:
-Let's start by taking a look at an example from the Django documentation.
+* Pagination links that are provided as part of the content of the response.
+* Pagination links that are included in response headers, such as `Content-Range` or `Link`.
- from django.core.paginator import Paginator
+The built-in styles currently all use links included as part of the content of the response. This style is more accessible when using the browsable API.
- objects = ['john', 'paul', 'george', 'ringo']
- paginator = Paginator(objects, 2)
- page = paginator.page(1)
- page.object_list
- # ['john', 'paul']
+Pagination is only performed automatically if you're using the generic views or viewsets. If you're using a regular `APIView`, you'll need to call into the pagination API yourself to ensure you return a paginated response. See the source code for the `mixins.ListMixin` and `generics.GenericAPIView` classes for an example.
-At this point we've got a page object. If we wanted to return this page object as a JSON response, we'd need to provide the client with context such as next and previous links, so that it would be able to page through the remaining results.
+## Setting the pagination style
- from rest_framework.pagination import PaginationSerializer
+The default pagination style may be set globally, using the `DEFAULT_PAGINATION_CLASS` settings key. For example, to use the built-in limit/offset pagination, you would do:
- serializer = PaginationSerializer(instance=page)
- serializer.data
- # {'count': 4, 'next': '?page=2', 'previous': None, 'results': [u'john', u'paul']}
-
-The `context` argument of the `PaginationSerializer` class may optionally include the request. If the request is included in the context then the next and previous links returned by the serializer will use absolute URLs instead of relative URLs.
-
- request = RequestFactory().get('/foobar')
- serializer = PaginationSerializer(instance=page, context={'request': request})
- serializer.data
- # {'count': 4, 'next': 'http://testserver/foobar?page=2', 'previous': None, 'results': [u'john', u'paul']}
-
-We could now return that data in a `Response` object, and it would be rendered into the correct media type.
-
-## Paginating QuerySets
-
-Our first example worked because we were using primitive objects. If we wanted to paginate a queryset or other complex data, we'd need to specify a serializer to use to serialize the result set itself.
-
-We can do this using the `object_serializer_class` attribute on the inner `Meta` class of the pagination serializer. For example.
-
- class UserSerializer(serializers.ModelSerializer):
- """
- Serializes user querysets.
- """
- class Meta:
- model = User
- fields = ('username', 'email')
+ REST_FRAMEWORK = {
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination'
+ }
- class PaginatedUserSerializer(pagination.PaginationSerializer):
- """
- Serializes page objects of user querysets.
- """
- class Meta:
- object_serializer_class = UserSerializer
+You can also set the pagination class on an individual view by using the `pagination_class` attribute. Typically you'll want to use the same pagination style throughout your API, although you might want to vary individual aspects of the pagination, such as default or maximum page size, on a per-view basis.
-We could now use our pagination serializer in a view like this.
+## Modifying the pagination style
- @api_view('GET')
- def user_list(request):
- queryset = User.objects.all()
- paginator = Paginator(queryset, 20)
+If you want to modify particular aspects of the pagination style, you'll want to override one of the pagination classes, and set the attributes that you want to change.
- page = request.QUERY_PARAMS.get('page')
- try:
- users = paginator.page(page)
- except PageNotAnInteger:
- # If page is not an integer, deliver first page.
- users = paginator.page(1)
- except EmptyPage:
- # If page is out of range (e.g. 9999),
- # deliver last page of results.
- users = paginator.page(paginator.num_pages)
+ class LargeResultsSetPagination(PageNumberPagination):
+ paginate_by = 1000
+ paginate_by_param = 'page_size'
+ max_paginate_by = 10000
- serializer_context = {'request': request}
- serializer = PaginatedUserSerializer(users,
- context=serializer_context)
- return Response(serializer.data)
+ class StandardResultsSetPagination(PageNumberPagination):
+ paginate_by = 100
+ paginate_by_param = 'page_size'
+ max_paginate_by = 1000
-## Pagination in the generic views
+You can then apply your new style to a view using the `.pagination_class` attribute:
-The generic class based views `ListAPIView` and `ListCreateAPIView` provide pagination of the returned querysets by default. You can customise this behaviour by altering the pagination style, by modifying the default number of results, by allowing clients to override the page size using a query parameter, or by turning pagination off completely.
+ class BillingRecordsView(generics.ListAPIView):
+ queryset = Billing.objects.all()
+ serializer = BillingRecordsSerializer
+ pagination_class = LargeResultsSetPagination
-The default pagination style may be set globally, using the `DEFAULT_PAGINATION_SERIALIZER_CLASS`, `PAGINATE_BY`, `PAGINATE_BY_PARAM`, and `MAX_PAGINATE_BY` settings. For example.
+Or apply the style globally, using the `DEFAULT_PAGINATION_CLASS` settings key. For example:
REST_FRAMEWORK = {
- 'PAGINATE_BY': 10, # Default to 10
- 'PAGINATE_BY_PARAM': 'page_size', # Allow client to override, using `?page_size=xxx`.
- 'MAX_PAGINATE_BY': 100 # Maximum limit allowed when using `?page_size=xxx`.
- }
-
-You can also set the pagination style on a per-view basis, using the `ListAPIView` generic class-based view.
+ 'DEFAULT_PAGINATION_CLASS': 'apps.core.pagination.StandardResultsSetPagination'
}
- class PaginatedListView(ListAPIView):
- queryset = ExampleModel.objects.all()
- serializer_class = ExampleModelSerializer
- paginate_by = 10
- paginate_by_param = 'page_size'
- max_paginate_by = 100
+# API Reference
-Note that using a `paginate_by` value of `None` will turn off pagination for the view.
-Note if you use the `PAGINATE_BY_PARAM` settings, you also have to set the `paginate_by_param` attribute in your view to `None` in order to turn off pagination for those requests that contain the `paginate_by_param` parameter.
+## PageNumberPagination
-For more complex requirements such as serialization that differs depending on the requested media type you can override the `.get_paginate_by()` and `.get_pagination_serializer_class()` methods.
+## LimitOffsetPagination
---
-# Custom pagination serializers
+# Custom pagination styles
+
+To create a custom pagination serializer class you should subclass `pagination.BasePagination` and override the `paginate_queryset(self, queryset, request, view)` and `get_paginated_response(self, data)` methods:
-To create a custom pagination serializer class you should override `pagination.BasePaginationSerializer` and set the fields that you want the serializer to return.
+* The `paginate_queryset` method is passed the initial queryset and should return an iterable object that contains only the data in the requested page.
+* The `get_paginated_response` method is passed the serialized page data and should return a `Response` instance.
-You can also override the name used for the object list field, by setting the `results_field` attribute, which defaults to `'results'`.
+Note that the `paginate_queryset` method may set state on the pagination instance, that may later be used by the `get_paginated_response` method.
## Example
-For example, to nest a pair of links labelled 'prev' and 'next', and set the name for the results field to 'objects', you might use something like this.
+Let's modify the built-in `PageNumberPagination` style, so that instead of include the pagination links in the body of the response, we'll instead include a `Link` header, in a [similar style to the GitHub API][github-link-pagination].
- from rest_framework import pagination
- from rest_framework import serializers
+ class LinkHeaderPagination(PageNumberPagination)
+ def get_paginated_response(self, data):
+ next_url = self.get_next_link()
previous_url = self.get_previous_link()
- class LinksSerializer(serializers.Serializer):
- next = pagination.NextPageField(source='*')
- prev = pagination.PreviousPageField(source='*')
+ if next_url is not None and previous_url is not None:
+ link = '<{next_url}; rel="next">, <{previous_url}; rel="prev">'
+ elif next_url is not None:
+ link = '<{next_url}; rel="next">'
+ elif prev_url is not None:
+ link = '<{previous_url}; rel="prev">'
+ else:
+ link = ''
- class CustomPaginationSerializer(pagination.BasePaginationSerializer):
- links = LinksSerializer(source='*') # Takes the page object as the source
- total_results = serializers.ReadOnlyField(source='paginator.count')
+ link = link.format(next_url=next_url, previous_url=previous_url)
+ headers = {'Link': link} if link else {}
- results_field = 'objects'
+ return Response(data, headers=headers)
-## Using your custom pagination serializer
+## Using your custom pagination class
-To have your custom pagination serializer be used by default, use the `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting:
+To have your custom pagination class be used by default, use the `DEFAULT_PAGINATION_CLASS` setting:
REST_FRAMEWORK = {
- 'DEFAULT_PAGINATION_SERIALIZER_CLASS':
- 'example_app.pagination.CustomPaginationSerializer',
+ 'DEFAULT_PAGINATION_CLASS':
+ 'my_project.apps.core.pagination.LinkHeaderPagination',
}
-Alternatively, to set your custom pagination serializer on a per-view basis, use the `pagination_serializer_class` attribute on a generic class based view:
-
- class PaginatedListView(generics.ListAPIView):
- model = ExampleModel
- pagination_serializer_class = CustomPaginationSerializer
- paginate_by = 10
-
# Third party packages
The following third party packages are also available.
@@ -157,5 +110,6 @@ The following third party packages are also available.
The [`DRF-extensions` package][drf-extensions] includes a [`PaginateByMaxMixin` mixin class][paginate-by-max-mixin] that allows your API clients to specify `?page_size=max` to obtain the maximum allowed page size.
[cite]: https://docs.djangoproject.com/en/dev/topics/pagination/
+[github-link-pagination]: https://developer.github.com/guides/traversing-with-pagination/
[drf-extensions]: http://chibisov.github.io/drf-extensions/docs/
[paginate-by-max-mixin]: http://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin
diff --git a/rest_framework/generics.py b/rest_framework/generics.py
index 12fb6413..cdf6ece0 100644
--- a/rest_framework/generics.py
+++ b/rest_framework/generics.py
@@ -160,11 +160,11 @@ class GenericAPIView(views.APIView):
def paginate_queryset(self, queryset):
if self.pager is None:
- return None
+ return queryset
return self.pager.paginate_queryset(queryset, self.request, view=self)
- def get_paginated_response(self, objects):
- return self.pager.get_paginated_response(objects)
+ def get_paginated_response(self, data):
+ return self.pager.get_paginated_response(data)
# Concrete view classes that provide method handlers
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index da2d60a4..b9d48796 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -25,11 +25,21 @@ def _strict_positive_int(integer_string, cutoff=None):
return ret
+def _get_count(queryset):
+ """
+ Determine an object count, supporting either querysets or regular lists.
+ """
+ try:
+ return queryset.count()
+ except AttributeError:
+ return len(queryset)
+
+
class BasePagination(object):
- def paginate_queryset(self, queryset, request):
+ def paginate_queryset(self, queryset, request, view):
raise NotImplemented('paginate_queryset() must be implemented.')
- def get_paginated_response(self, data, page, request):
+ def get_paginated_response(self, data):
raise NotImplemented('get_paginated_response() must be implemented.')
@@ -58,8 +68,8 @@ class PageNumberPagination(BasePagination):
def paginate_queryset(self, queryset, request, view):
"""
- Paginate a queryset if required, either returning a page object,
- or `None` if pagination is not configured for this view.
+ Paginate a queryset if required, either returning a
+ page object, or `None` if pagination is not configured for this view.
"""
for attr in (
'paginate_by', 'page_query_param',
@@ -97,12 +107,12 @@ class PageNumberPagination(BasePagination):
self.request = request
return self.page
- def get_paginated_response(self, objects):
+ def get_paginated_response(self, data):
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
- ('results', objects)
+ ('results', data)
]))
def get_page_size(self, request):
@@ -147,16 +157,16 @@ class LimitOffsetPagination(BasePagination):
def paginate_queryset(self, queryset, request, view):
self.limit = self.get_limit(request)
self.offset = self.get_offset(request)
- self.count = queryset.count()
+ self.count = _get_count(queryset)
self.request = request
return queryset[self.offset:self.offset + self.limit]
- def get_paginated_response(self, objects):
+ def get_paginated_response(self, data):
return Response(OrderedDict([
('count', self.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
- ('results', objects)
+ ('results', data)
]))
def get_limit(self, request):
--
cgit v1.2.3
From 4ce4132e08ba7764f120c71eeebdbaefee281689 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 14 Jan 2015 12:56:03 +0000
Subject: Preserve ordering on relationship drop-down choices. Closes #2408.
---
rest_framework/relations.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 7b119291..aa0c2def 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -7,6 +7,7 @@ from django.utils import six
from django.utils.encoding import smart_text
from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _
+from rest_framework.compat import OrderedDict
from rest_framework.fields import get_attribute, empty, Field
from rest_framework.reverse import reverse
from rest_framework.utils import html
@@ -103,7 +104,7 @@ class RelatedField(Field):
@property
def choices(self):
- return dict([
+ return OrderedDict([
(
six.text_type(self.to_representation(item)),
six.text_type(item)
@@ -364,7 +365,7 @@ class ManyRelatedField(Field):
(item, self.child_relation.to_representation(item))
for item in iterable
]
- return dict([
+ return OrderedDict([
(
six.text_type(item_representation),
six.text_type(item) + ' - ' + six.text_type(item_representation)
--
cgit v1.2.3
From 4d287c7aef7b12086930eeb7a05cadb7e8b2cc48 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 14 Jan 2015 13:19:56 +0000
Subject: Include paragraph around view description in browable API
---
rest_framework/utils/formatting.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py
index 470af51b..173848df 100644
--- a/rest_framework/utils/formatting.py
+++ b/rest_framework/utils/formatting.py
@@ -59,4 +59,5 @@ def markup_description(description):
description = apply_markdown(description)
else:
description = escape(description).replace('\n', ' ')
+ description = '
' + description + '
'
return mark_safe(description)
--
cgit v1.2.3
From f13fcba9a9f41f7e00e0ea8956fcc65ca168c76c Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 14 Jan 2015 13:20:02 +0000
Subject: Include paragraph around view description in browable API
---
rest_framework/utils/formatting.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py
index 173848df..8b6f005e 100644
--- a/rest_framework/utils/formatting.py
+++ b/rest_framework/utils/formatting.py
@@ -2,12 +2,10 @@
Utility functions to return a formatted name and description for a given view.
"""
from __future__ import unicode_literals
-import re
-
from django.utils.html import escape
from django.utils.safestring import mark_safe
-
from rest_framework.compat import apply_markdown, force_text
+import re
def remove_trailing_string(content, trailing):
--
cgit v1.2.3
From 3833a5bb8a9174e5fb09dac59a964eff24b6065e Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 14 Jan 2015 16:51:26 +0000
Subject: Include pagination control in browsable API
---
rest_framework/pagination.py | 90 +++++++++++++++++++++-
rest_framework/renderers.py | 1 +
.../static/rest_framework/css/bootstrap-tweaks.css | 4 -
rest_framework/templates/rest_framework/base.html | 9 +++
.../rest_framework/pagination/numbers.html | 27 +++++++
rest_framework/templatetags/rest_framework.py | 17 ++++
6 files changed, 143 insertions(+), 5 deletions(-)
create mode 100644 rest_framework/templates/rest_framework/pagination/numbers.html
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index b9d48796..bd343c0d 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -3,14 +3,18 @@ Pagination serializers determine the structure of the output that should
be used for paginated responses.
"""
from __future__ import unicode_literals
+from collections import namedtuple
from django.core.paginator import InvalidPage, Paginator as DjangoPaginator
+from django.template import Context, loader
from django.utils import six
from django.utils.translation import ugettext as _
from rest_framework.compat import OrderedDict
from rest_framework.exceptions import NotFound
from rest_framework.response import Response
from rest_framework.settings import api_settings
-from rest_framework.templatetags.rest_framework import replace_query_param
+from rest_framework.templatetags.rest_framework import (
+ replace_query_param, remove_query_param
+)
def _strict_positive_int(integer_string, cutoff=None):
@@ -35,6 +39,49 @@ def _get_count(queryset):
return len(queryset)
+def _get_displayed_page_numbers(current, final):
+ """
+ This utility function determines a list of page numbers to display.
+ This gives us a nice contextually relevant set of page numbers.
+
+ For example:
+ current=14, final=16 -> [1, None, 13, 14, 15, 16]
+ """
+ assert current >= 1
+ assert final >= current
+
+ # We always include the first two pages, last two pages, and
+ # two pages either side of the current page.
+ included = set((
+ 1,
+ current - 1, current, current + 1,
+ final
+ ))
+
+ # If the break would only exclude a single page number then we
+ # may as well include the page number instead of the break.
+ if current == 4:
+ included.add(2)
+ if current == final - 3:
+ included.add(final - 1)
+
+ # Now sort the page numbers and drop anything outside the limits.
+ included = [
+ idx for idx in sorted(list(included))
+ if idx > 0 and idx <= final
+ ]
+
+ # Finally insert any `...` breaks
+ if current > 4:
+ included.insert(1, None)
+ if current < final - 3:
+ included.insert(len(included) - 1, None)
+ return included
+
+
+PageLink = namedtuple('PageLink', ['url', 'number', 'is_active', 'is_break'])
+
+
class BasePagination(object):
def paginate_queryset(self, queryset, request, view):
raise NotImplemented('paginate_queryset() must be implemented.')
@@ -66,6 +113,8 @@ class PageNumberPagination(BasePagination):
# Only relevant if 'paginate_by_param' has also been set.
max_paginate_by = api_settings.MAX_PAGINATE_BY
+ template = 'rest_framework/pagination/numbers.html'
+
def paginate_queryset(self, queryset, request, view):
"""
Paginate a queryset if required, either returning a
@@ -104,6 +153,8 @@ class PageNumberPagination(BasePagination):
)
raise NotFound(msg)
+ # Indicate that the browsable API should display pagination controls.
+ self.mark_as_used = True
self.request = request
return self.page
@@ -139,8 +190,45 @@ class PageNumberPagination(BasePagination):
return None
url = self.request.build_absolute_uri()
page_number = self.page.previous_page_number()
+ if page_number == 1:
+ return remove_query_param(url, self.page_query_param)
return replace_query_param(url, self.page_query_param, page_number)
+ def to_html(self):
+ current = self.page.number
+ final = self.page.paginator.num_pages
+
+ page_links = []
+ base_url = self.request.build_absolute_uri()
+ for page_number in _get_displayed_page_numbers(current, final):
+ if page_number is None:
+ page_link = PageLink(
+ url=None,
+ number=None,
+ is_active=False,
+ is_break=True
+ )
+ else:
+ if page_number == 1:
+ url = remove_query_param(base_url, self.page_query_param)
+ else:
+ url = replace_query_param(url, self.page_query_param, page_number)
+ page_link = PageLink(
+ url=url,
+ number=page_number,
+ is_active=(page_number == current),
+ is_break=False
+ )
+ page_links.append(page_link)
+
+ template = loader.get_template(self.template)
+ context = Context({
+ 'previous_url': self.get_previous_link(),
+ 'next_url': self.get_next_link(),
+ 'page_links': page_links
+ })
+ return template.render(context)
+
class LimitOffsetPagination(BasePagination):
"""
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index c4de30db..4c002b16 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -592,6 +592,7 @@ class BrowsableAPIRenderer(BaseRenderer):
'description': self.get_description(view),
'name': self.get_name(view),
'version': VERSION,
+ 'pager': getattr(view, 'pager', None),
'breadcrumblist': self.get_breadcrumbs(request),
'allowed_methods': view.allowed_methods,
'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes],
diff --git a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css
index 36c7be48..d4a7d31a 100644
--- a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css
+++ b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css
@@ -185,10 +185,6 @@ body a:hover {
color: #c20000;
}
-#content a span {
- text-decoration: underline;
- }
-
.request-info {
clear:both;
}
diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html
index e9668193..e0030981 100644
--- a/rest_framework/templates/rest_framework/base.html
+++ b/rest_framework/templates/rest_framework/base.html
@@ -119,9 +119,18 @@
--
cgit v1.2.3
From bf58c1265ddf06deb435d049fae01ed324a310e0 Mon Sep 17 00:00:00 2001
From: Brandon Cazander
Date: Mon, 26 Jan 2015 22:56:57 -0800
Subject: Set a version attribute on cloned requests if necessary.
---
rest_framework/request.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/rest_framework/request.py b/rest_framework/request.py
index d5b56ada..ce2fcb47 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -86,7 +86,7 @@ def clone_request(request, method):
Internal helper method to clone a request, replacing with a different
HTTP method. Used for checking permissions against other methods.
"""
- ret = Request(request=request,
+ ret = Request(request=request._request,
parsers=request.parsers,
authenticators=request.authenticators,
negotiator=request.negotiator,
@@ -107,6 +107,8 @@ def clone_request(request, method):
ret.accepted_renderer = request.accepted_renderer
if hasattr(request, 'accepted_media_type'):
ret.accepted_media_type = request.accepted_media_type
+ if hasattr(request, 'version'):
+ ret.version = request.version
return ret
--
cgit v1.2.3
From 65bca59ea548dc5e2222be06ca20b3d3fa151cf0 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 27 Jan 2015 13:51:30 +0000
Subject: Reload api_settings when using Django's 'override_settings'
---
rest_framework/settings.py | 11 +++++++++++
tests/test_filters.py | 16 +++++++++++++---
tests/utils.py | 25 -------------------------
3 files changed, 24 insertions(+), 28 deletions(-)
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
index fc6dfecd..e5e5edaf 100644
--- a/rest_framework/settings.py
+++ b/rest_framework/settings.py
@@ -18,6 +18,7 @@ REST framework settings, checking for user settings first, then falling
back to the defaults.
"""
from __future__ import unicode_literals
+from django.test.signals import setting_changed
from django.conf import settings
from django.utils import importlib, six
from rest_framework import ISO_8601
@@ -198,3 +199,13 @@ class APISettings(object):
api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS)
+
+
+def reload_api_settings(*args, **kwargs):
+ global api_settings
+ setting, value = kwargs['setting'], kwargs['value']
+ if setting == 'REST_FRAMEWORK':
+ api_settings = APISettings(value, DEFAULTS, IMPORT_STRINGS)
+
+
+setting_changed.connect(reload_api_settings)
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 5b1b6ca5..355f02ce 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -5,13 +5,15 @@ from django.db import models
from django.conf.urls import patterns, url
from django.core.urlresolvers import reverse
from django.test import TestCase
+from django.test.utils import override_settings
from django.utils import unittest
from django.utils.dateparse import parse_date
+from django.utils.six.moves import reload_module
from rest_framework import generics, serializers, status, filters
from rest_framework.compat import django_filters
from rest_framework.test import APIRequestFactory
from .models import BaseFilterableItem, FilterableItem, BasicModel
-from .utils import temporary_setting
+
factory = APIRequestFactory()
@@ -404,7 +406,9 @@ class SearchFilterTests(TestCase):
)
def test_search_with_nonstandard_search_param(self):
- with temporary_setting('SEARCH_PARAM', 'query', module=filters):
+ with override_settings(REST_FRAMEWORK={'SEARCH_PARAM': 'query'}):
+ reload_module(filters)
+
class SearchListView(generics.ListAPIView):
queryset = SearchFilterModel.objects.all()
serializer_class = SearchFilterSerializer
@@ -422,6 +426,8 @@ class SearchFilterTests(TestCase):
]
)
+ reload_module(filters)
+
class OrderingFilterModel(models.Model):
title = models.CharField(max_length=20)
@@ -642,7 +648,9 @@ class OrderingFilterTests(TestCase):
)
def test_ordering_with_nonstandard_ordering_param(self):
- with temporary_setting('ORDERING_PARAM', 'order', filters):
+ with override_settings(REST_FRAMEWORK={'ORDERING_PARAM': 'order'}):
+ reload_module(filters)
+
class OrderingListView(generics.ListAPIView):
queryset = OrderingFilterModel.objects.all()
serializer_class = OrderingFilterSerializer
@@ -662,6 +670,8 @@ class OrderingFilterTests(TestCase):
]
)
+ reload_module(filters)
+
class SensitiveOrderingFilterModel(models.Model):
username = models.CharField(max_length=20)
diff --git a/tests/utils.py b/tests/utils.py
index 5e902ba9..5b2d7586 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,30 +1,5 @@
-from contextlib import contextmanager
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import NoReverseMatch
-from django.utils import six
-from rest_framework.settings import api_settings
-
-
-@contextmanager
-def temporary_setting(setting, value, module=None):
- """
- Temporarily change value of setting for test.
-
- Optionally reload given module, useful when module uses value of setting on
- import.
- """
- original_value = getattr(api_settings, setting)
- setattr(api_settings, setting, value)
-
- if module is not None:
- six.moves.reload_module(module)
-
- yield
-
- setattr(api_settings, setting, original_value)
-
- if module is not None:
- six.moves.reload_module(module)
class MockObject(object):
--
cgit v1.2.3
From 925ea4bdc6d40a1e118087f28a09c86977dc5532 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Tue, 27 Jan 2015 19:43:38 +0100
Subject: Release notes for 3.0.4
---
docs/topics/release-notes.md | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index c49dd62c..e0894d2d 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -41,6 +41,24 @@ You can determine your currently installed version using `pip freeze`:
## 3.0.x series
+### 3.0.4
+
+**Date**: [28th January 2015][3.0.4-milestone].
+
+* Django 1.8a1 support. ([#2425][gh2425], [#2446][gh2446], [#2441][gh2441])
+* Add `DictField` and support Django 1.8 `HStoreField`. ([#2451][gh2451], [#2106][gh2106])
+* Add `UUIDField` and support Django 1.8 `UUIDField`. ([#2448][gh2448], [#2433][gh2433], [#2432][gh2432])
+* `BaseRenderer.render` now raises `NotImplementedError`. ([#2434][gh2434])
+* Fix timedelta JSON serialization on Python 2.6. ([#2430][gh2430])
+* `ResultDict` and `ResultList` now appear as standard dict/list. ([#2421][gh2421])
+* Fix visible `HiddenField` in the HTML form of the web browsable API page. ([#2410][gh2410])
+* Use `OrderedDict` for `RelatedField.choices`. ([#2408][gh2408])
+* Fix ident format when using `HTTP_X_FORWARDED_FOR`. ([#2401][gh2401])
+* Fix invalid key with memcached while using throttling. ([#2400][gh2400])
+* Fix `FileUploadParser` with version 3.x. ([#2399][gh2399])
+* Fix the serializer inheritance. ([#2388][gh2388])
+* Fix caching issues with `ReturnDict`. ([#2360][gh2360])
+
### 3.0.3
**Date**: [8th January 2015][3.0.3-milestone].
@@ -702,6 +720,7 @@ For older release notes, [please see the GitHub repo](old-release-notes).
[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.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
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
@@ -770,3 +789,22 @@ For older release notes, [please see the GitHub repo](old-release-notes).
[gh2355]: https://github.com/tomchristie/django-rest-framework/issues/2355
[gh2369]: https://github.com/tomchristie/django-rest-framework/issues/2369
[gh2386]: https://github.com/tomchristie/django-rest-framework/issues/2386
+
+[gh2425]: https://github.com/tomchristie/django-rest-framework/issues/2425
+[gh2446]: https://github.com/tomchristie/django-rest-framework/issues/2446
+[gh2441]: https://github.com/tomchristie/django-rest-framework/issues/2441
+[gh2451]: https://github.com/tomchristie/django-rest-framework/issues/2451
+[gh2106]: https://github.com/tomchristie/django-rest-framework/issues/2106
+[gh2448]: https://github.com/tomchristie/django-rest-framework/issues/2448
+[gh2433]: https://github.com/tomchristie/django-rest-framework/issues/2433
+[gh2432]: https://github.com/tomchristie/django-rest-framework/issues/2432
+[gh2434]: https://github.com/tomchristie/django-rest-framework/issues/2434
+[gh2430]: https://github.com/tomchristie/django-rest-framework/issues/2430
+[gh2421]: https://github.com/tomchristie/django-rest-framework/issues/2421
+[gh2410]: https://github.com/tomchristie/django-rest-framework/issues/2410
+[gh2408]: https://github.com/tomchristie/django-rest-framework/issues/2408
+[gh2401]: https://github.com/tomchristie/django-rest-framework/issues/2401
+[gh2400]: https://github.com/tomchristie/django-rest-framework/issues/2400
+[gh2399]: https://github.com/tomchristie/django-rest-framework/issues/2399
+[gh2388]: https://github.com/tomchristie/django-rest-framework/issues/2388
+[gh2360]: https://github.com/tomchristie/django-rest-framework/issues/2360
--
cgit v1.2.3
From 5b369bf5fe3e5e8af3a73055b3a6ebda1e88f68e Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Tue, 27 Jan 2015 19:45:37 +0100
Subject: Bumped the version.
---
rest_framework/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index fdcebb7b..57e5421b 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -8,7 +8,7 @@ ______ _____ _____ _____ __
"""
__title__ = 'Django REST framework'
-__version__ = '3.0.3'
+__version__ = '3.0.4'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2015 Tom Christie'
--
cgit v1.2.3
From 8c3f82fb18a58b8e0983612ef3cc35b3c3950b66 Mon Sep 17 00:00:00 2001
From: Susan Dreher
Date: Tue, 27 Jan 2015 16:18:51 -0500
Subject: :bug: ManyRelatedField get_value clearing field on partial update
A PATCH to a serializer's non-related CharField was clearing an ancillary StringRelatedField(many=True) field.
The issue appears to be in the ManyRelatedField's get_value method, which was returning a [] instead of empty
when the request data was a MultiDict.
This fix mirrors code in fields.py, class Field, get_value, Ln. 272, which explicitly returns empty on a partial update.
Tests added to demonstrate the issue.
---
rest_framework/relations.py | 5 +++++
tests/test_relations.py | 40 ++++++++++++++++++++++++++++++++++++++--
2 files changed, 43 insertions(+), 2 deletions(-)
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index aa0c2def..13793f37 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -338,7 +338,12 @@ class ManyRelatedField(Field):
# We override the default field access in order to support
# lists in HTML forms.
if html.is_html_input(dictionary):
+ # Don't return [] if the update is partial
+ if self.field_name not in dictionary:
+ if getattr(self.root, 'partial', False):
+ return empty
return dictionary.getlist(self.field_name)
+
return dictionary.get(self.field_name, empty)
def to_internal_value(self, data):
diff --git a/tests/test_relations.py b/tests/test_relations.py
index 62353dc2..143e835c 100644
--- a/tests/test_relations.py
+++ b/tests/test_relations.py
@@ -1,8 +1,13 @@
-from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
+import pytest
+
from django.core.exceptions import ImproperlyConfigured
+from django.utils.datastructures import MultiValueDict
+
from rest_framework import serializers
+from rest_framework.fields import empty
from rest_framework.test import APISimpleTestCase
-import pytest
+
+from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
class TestStringRelatedField(APISimpleTestCase):
@@ -134,3 +139,34 @@ class TestSlugRelatedField(APISimpleTestCase):
def test_representation(self):
representation = self.field.to_representation(self.instance)
assert representation == self.instance.name
+
+
+class TestManyRelatedField(APISimpleTestCase):
+ def setUp(self):
+ self.instance = MockObject(pk=1, name='foo')
+ self.field = serializers.StringRelatedField(many=True)
+ self.field.field_name = 'foo'
+
+ def test_get_value_regular_dictionary_full(self):
+ assert 'bar' == self.field.get_value({'foo': 'bar'})
+ assert empty == self.field.get_value({'baz': 'bar'})
+
+ def test_get_value_regular_dictionary_partial(self):
+ setattr(self.field.root, 'partial', True)
+ assert 'bar' == self.field.get_value({'foo': 'bar'})
+ assert empty == self.field.get_value({'baz': 'bar'})
+
+ def test_get_value_multi_dictionary_full(self):
+ mvd = MultiValueDict({'foo': ['bar1', 'bar2']})
+ assert ['bar1', 'bar2'] == self.field.get_value(mvd)
+
+ mvd = MultiValueDict({'baz': ['bar1', 'bar2']})
+ assert [] == self.field.get_value(mvd)
+
+ def test_get_value_multi_dictionary_partial(self):
+ setattr(self.field.root, 'partial', True)
+ mvd = MultiValueDict({'foo': ['bar1', 'bar2']})
+ assert ['bar1', 'bar2'] == self.field.get_value(mvd)
+
+ mvd = MultiValueDict({'baz': ['bar1', 'bar2']})
+ assert empty == self.field.get_value(mvd)
--
cgit v1.2.3
From 1714ceae9f468bc1479f0d7a32b0bf26ae9cf15f Mon Sep 17 00:00:00 2001
From: Susan Dreher
Date: Tue, 27 Jan 2015 16:31:25 -0500
Subject: reorganize imports
---
tests/test_relations.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tests/test_relations.py b/tests/test_relations.py
index 143e835c..67f49c6b 100644
--- a/tests/test_relations.py
+++ b/tests/test_relations.py
@@ -1,13 +1,13 @@
-import pytest
+from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
from django.core.exceptions import ImproperlyConfigured
from django.utils.datastructures import MultiValueDict
from rest_framework import serializers
from rest_framework.fields import empty
-from rest_framework.test import APISimpleTestCase
-from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
+from rest_framework.test import APISimpleTestCase
+import pytest
class TestStringRelatedField(APISimpleTestCase):
--
cgit v1.2.3
From e7da266a866adddd5c37453fab33812ee412752b Mon Sep 17 00:00:00 2001
From: Susan Dreher
Date: Tue, 27 Jan 2015 16:32:15 -0500
Subject: reorganize imports
---
tests/test_relations.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/tests/test_relations.py b/tests/test_relations.py
index 67f49c6b..d478d855 100644
--- a/tests/test_relations.py
+++ b/tests/test_relations.py
@@ -1,11 +1,8 @@
from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
-
from django.core.exceptions import ImproperlyConfigured
from django.utils.datastructures import MultiValueDict
-
from rest_framework import serializers
from rest_framework.fields import empty
-
from rest_framework.test import APISimpleTestCase
import pytest
--
cgit v1.2.3
From 0daf160946db4f2fed6a237136d738d954b841c0 Mon Sep 17 00:00:00 2001
From: Xavier Ordoquy
Date: Wed, 28 Jan 2015 00:06:34 +0100
Subject: Fix #2476
---
docs/api-guide/routers.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md
index 9c9bfb50..a9f911a9 100644
--- a/docs/api-guide/routers.md
+++ b/docs/api-guide/routers.md
@@ -60,7 +60,7 @@ For example, you can append `router.urls` to a list of existing views…
router.register(r'accounts', AccountViewSet)
urlpatterns = [
- url(r'^forgot-password/$', ForgotPasswordFormView.as_view(),
+ url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
]
urlpatterns += router.urls
@@ -68,15 +68,15 @@ For example, you can append `router.urls` to a list of existing views…
Alternatively you can use Django's `include` function, like so…
urlpatterns = [
- url(r'^forgot-password/$', ForgotPasswordFormView.as_view(),
- url(r'^', include(router.urls))
+ url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
+ url(r'^', include(router.urls)),
]
Router URL patterns can also be namespaces.
urlpatterns = [
- url(r'^forgot-password/$', ForgotPasswordFormView.as_view(),
- url(r'^api/', include(router.urls, namespace='api'))
+ url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
+ url(r'^api/', include(router.urls, namespace='api')),
]
If using namespacing with hyperlinked serializers you'll also need to ensure that any `view_name` parameters on the serializers correctly reflect the namespace. In the example above you'd need to include a parameter such as `view_name='api:user-detail'` for serializer fields hyperlinked to the user detail view.
--
cgit v1.2.3
From ac87490b91e3405d497da360afed10842a73dfd0 Mon Sep 17 00:00:00 2001
From: Brandon Cazander
Date: Tue, 27 Jan 2015 17:10:17 -0800
Subject: Clone the versioning_scheme when necessary. Fixes #2477
---
rest_framework/request.py | 2 ++
tests/test_metadata.py | 17 ++++++++++++++++-
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/rest_framework/request.py b/rest_framework/request.py
index ce2fcb47..bf6ff670 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -109,6 +109,8 @@ def clone_request(request, method):
ret.accepted_media_type = request.accepted_media_type
if hasattr(request, 'version'):
ret.version = request.version
+ if hasattr(request, 'versioning_scheme'):
+ ret.versioning_scheme = request.versioning_scheme
return ret
diff --git a/tests/test_metadata.py b/tests/test_metadata.py
index bdc84edf..5031c0f3 100644
--- a/tests/test_metadata.py
+++ b/tests/test_metadata.py
@@ -1,5 +1,5 @@
from __future__ import unicode_literals
-from rest_framework import exceptions, serializers, status, views
+from rest_framework import exceptions, serializers, status, views, versioning
from rest_framework.request import Request
from rest_framework.renderers import BrowsableAPIRenderer
from rest_framework.test import APIRequestFactory
@@ -183,3 +183,18 @@ class TestMetadata:
view = ExampleView.as_view()
view(request=request)
+
+ def test_bug_2477_clone_request(self):
+ class ExampleView(views.APIView):
+ renderer_classes = (BrowsableAPIRenderer,)
+
+ def post(self, request):
+ pass
+
+ def get_serializer(self):
+ assert hasattr(self.request, 'versioning_scheme')
+ return serializers.Serializer()
+
+ scheme = versioning.QueryParameterVersioning
+ view = ExampleView.as_view(versioning_class=scheme)
+ view(request=request)
--
cgit v1.2.3
From a1eba885e287f59dd269441dfebb3b3de3eea692 Mon Sep 17 00:00:00 2001
From: Lucas Wiman
Date: Tue, 27 Jan 2015 19:01:40 -0800
Subject: Use the proper db_table argument when constructing meta
---
rest_framework/authtoken/south_migrations/0001_initial.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rest_framework/authtoken/south_migrations/0001_initial.py b/rest_framework/authtoken/south_migrations/0001_initial.py
index 926de02b..5b927f3e 100644
--- a/rest_framework/authtoken/south_migrations/0001_initial.py
+++ b/rest_framework/authtoken/south_migrations/0001_initial.py
@@ -40,7 +40,7 @@ class Migration(SchemaMigration):
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
"%s.%s" % (User._meta.app_label, User._meta.module_name): {
- 'Meta': {'object_name': User._meta.module_name},
+ 'Meta': {'object_name': User._meta.module_name, 'db_table': repr(User._meta.db_table)},
},
'authtoken.token': {
'Meta': {'object_name': 'Token'},
--
cgit v1.2.3
From 4a2a36ef828ce0e687c48fdb597d343df65f0e2b Mon Sep 17 00:00:00 2001
From: mareknaskret
Date: Wed, 28 Jan 2015 15:17:56 +0100
Subject: Update filtering.md
Updated links for django-guardian package.---
docs/api-guide/filtering.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md
index e00560c7..b16b6be5 100644
--- a/docs/api-guide/filtering.md
+++ b/docs/api-guide/filtering.md
@@ -398,8 +398,8 @@ The [django-rest-framework-filters package][django-rest-framework-filters] works
[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-docs]: https://django-filter.readthedocs.org/en/latest/index.html
-[guardian]: http://pythonhosted.org/django-guardian/
-[view-permissions]: http://pythonhosted.org/django-guardian/userguide/assign.html
+[guardian]: https://django-guardian.readthedocs.org/
+[view-permissions]: https://django-guardian.readthedocs.org/en/latest/userguide/assign.html
[view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models
[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
--
cgit v1.2.3
From ba7dca893cd55a1d5ee928c4b10878c92c44c4f5 Mon Sep 17 00:00:00 2001
From: Tymur Maryokhin
Date: Thu, 29 Jan 2015 17:28:03 +0100
Subject: Removed router check for deprecated '.model' attribute
---
rest_framework/routers.py | 10 ++--------
tests/test_routers.py | 7 ++++---
2 files changed, 6 insertions(+), 11 deletions(-)
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index 827da034..6a4184e2 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -130,19 +130,13 @@ class SimpleRouter(BaseRouter):
If `base_name` is not specified, attempt to automatically determine
it from the viewset.
"""
- # 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(viewset, 'model', None)
queryset = getattr(viewset, 'queryset', None)
- if model_cls is None and queryset is not None:
- model_cls = queryset.model
- assert model_cls, '`base_name` argument not specified, and could ' \
+ assert queryset is not None, '`base_name` argument not specified, and could ' \
'not automatically determine the name from the viewset, as ' \
'it does not have a `.queryset` attribute.'
- return model_cls._meta.object_name.lower()
+ return queryset.model._meta.object_name.lower()
def get_routes(self, viewset):
"""
diff --git a/tests/test_routers.py b/tests/test_routers.py
index 86113f5d..948c69bb 100644
--- a/tests/test_routers.py
+++ b/tests/test_routers.py
@@ -180,7 +180,7 @@ class TestLookupValueRegex(TestCase):
class TestTrailingSlashIncluded(TestCase):
def setUp(self):
class NoteViewSet(viewsets.ModelViewSet):
- model = RouterTestModel
+ queryset = RouterTestModel.objects.all()
self.router = SimpleRouter()
self.router.register(r'notes', NoteViewSet)
@@ -195,7 +195,7 @@ class TestTrailingSlashIncluded(TestCase):
class TestTrailingSlashRemoved(TestCase):
def setUp(self):
class NoteViewSet(viewsets.ModelViewSet):
- model = RouterTestModel
+ queryset = RouterTestModel.objects.all()
self.router = SimpleRouter(trailing_slash=False)
self.router.register(r'notes', NoteViewSet)
@@ -210,7 +210,8 @@ class TestTrailingSlashRemoved(TestCase):
class TestNameableRoot(TestCase):
def setUp(self):
class NoteViewSet(viewsets.ModelViewSet):
- model = RouterTestModel
+ queryset = RouterTestModel.objects.all()
+
self.router = DefaultRouter()
self.router.root_view_name = 'nameable-root'
self.router.register(r'notes', NoteViewSet)
--
cgit v1.2.3
From e720927b78a31999f03bfa248329d623ce2c045c Mon Sep 17 00:00:00 2001
From: Tymur Maryokhin
Date: Thu, 29 Jan 2015 17:28:18 +0100
Subject: Removed deprecated '.model' docs
---
docs/api-guide/generic-views.md | 8 ++------
docs/api-guide/routers.md | 2 +-
docs/topics/3.0-announcement.md | 2 +-
3 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md
index 6374e305..61c8e8d8 100755
--- a/docs/api-guide/generic-views.md
+++ b/docs/api-guide/generic-views.md
@@ -93,17 +93,13 @@ The following attributes are used to control pagination when used with list view
* `filter_backends` - A list of filter backend classes that should be used for filtering the queryset. Defaults to the same value as the `DEFAULT_FILTER_BACKENDS` setting.
-**Deprecated attributes**:
-
-* `model` - This shortcut may be used instead of setting either (or both) of the `queryset`/`serializer_class` attributes. The explicit style is preferred over the `.model` shortcut, and usage of this attribute is now deprecated.
-
### Methods
**Base methods**:
#### `get_queryset(self)`
-Returns the queryset that should be used for list views, and that should be used as the base for lookups in detail views. Defaults to returning the queryset specified by the `queryset` attribute, or the default queryset for the model if the `model` shortcut is being used.
+Returns the queryset that should be used for list views, and that should be used as the base for lookups in detail views. Defaults to returning the queryset specified by the `queryset` attribute.
This method should always be used rather than accessing `self.queryset` directly, as `self.queryset` gets evaluated only once, and those results are cached for all subsequent requests.
@@ -153,7 +149,7 @@ For example:
#### `get_serializer_class(self)`
-Returns the class that should be used for the serializer. Defaults to returning the `serializer_class` attribute, or dynamically generating a serializer class if the `model` shortcut is being used.
+Returns the class that should be used for the serializer. Defaults to returning the `serializer_class` attribute.
May be overridden to provide dynamic behavior, such as using different serializers for read and write operations, or providing different serializers to different types of users.
diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md
index a9f911a9..592f7d66 100644
--- a/docs/api-guide/routers.md
+++ b/docs/api-guide/routers.md
@@ -28,7 +28,7 @@ There are two mandatory arguments to the `register()` method:
Optionally, you may also specify an additional argument:
-* `base_name` - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the `model` or `queryset` attribute on the viewset, if it has one. Note that if the viewset does not include a `model` or `queryset` attribute then you must set `base_name` when registering the viewset.
+* `base_name` - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the `queryset` attribute of the viewset, if it has one. Note that if the viewset does not include a `queryset` attribute then you must set `base_name` when registering the viewset.
The example above would generate the following URL patterns:
diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md
index 5dbc5600..24e69c2d 100644
--- a/docs/topics/3.0-announcement.md
+++ b/docs/topics/3.0-announcement.md
@@ -665,7 +665,7 @@ This code *would be valid* in `2.4.3`:
class Meta:
model = Account
-However this code *would not be valid* in `2.4.3`:
+However this code *would not be valid* in `3.0`:
# Missing `queryset`
class AccountSerializer(serializers.Serializer):
--
cgit v1.2.3
From 7cf9dea7f905ea6869148a68b4fa96cad0a347e8 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 30 Jan 2015 11:00:29 +0000
Subject: Docs typo. Closes #2491.
---
docs/api-guide/parsers.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md
index 73e3a705..3d44fe56 100644
--- a/docs/api-guide/parsers.md
+++ b/docs/api-guide/parsers.md
@@ -128,7 +128,7 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword
def put(self, request, filename, format=None):
file_obj = request.data['file']
# ...
- # do some staff with uploaded file
+ # do some stuff with uploaded file
# ...
return Response(status=204)
--
cgit v1.2.3
From 760b25bc20a1434cbdd69dc0b13bacdc3bbedd7c Mon Sep 17 00:00:00 2001
From: José Padilla
Date: Fri, 30 Jan 2015 11:36:03 -0400
Subject: Fix AttributeError on renamed _field_mapping
---
rest_framework/serializers.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index a3b8196b..a91fe23e 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -1330,13 +1330,13 @@ class ModelSerializer(Serializer):
if hasattr(models, 'UUIDField'):
- ModelSerializer._field_mapping[models.UUIDField] = UUIDField
+ ModelSerializer.serializer_field_mapping[models.UUIDField] = UUIDField
if postgres_fields:
class CharMappingField(DictField):
child = CharField()
- ModelSerializer._field_mapping[postgres_fields.HStoreField] = CharMappingField
+ ModelSerializer.serializer_field_mapping[postgres_fields.HStoreField] = CharMappingField
class HyperlinkedModelSerializer(ModelSerializer):
--
cgit v1.2.3
From 0d96cf2ca2e3298ed38e81482bcdc2664d060735 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 30 Jan 2015 16:27:49 +0000
Subject: Latest translation source messages.
---
docs/topics/3.1-announcement.md | 16 +++-
rest_framework/locale/en_US/LC_MESSAGES/django.po | 112 ++++++++++++----------
2 files changed, 71 insertions(+), 57 deletions(-)
diff --git a/docs/topics/3.1-announcement.md b/docs/topics/3.1-announcement.md
index a0ad9829..3eb52f4c 100644
--- a/docs/topics/3.1-announcement.md
+++ b/docs/topics/3.1-announcement.md
@@ -1,7 +1,15 @@
-# Versioning
+# Django REST framework 3.1
-# Pagination
+## Pagination
-# Internationalization
+#### Pagination controls in the browsable API.
-# ModelSerializer API
+#### New pagination schemes.
+
+#### Support for header-based pagination.
+
+## Versioning
+
+## Internationalization
+
+## ModelSerializer API
diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.po b/rest_framework/locale/en_US/LC_MESSAGES/django.po
index d98225ce..23f76ff7 100644
--- a/rest_framework/locale/en_US/LC_MESSAGES/django.po
+++ b/rest_framework/locale/en_US/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-01-07 18:21+0000\n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -49,18 +49,6 @@ msgstr ""
msgid "User inactive or deleted."
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 ""
@@ -101,28 +89,28 @@ msgstr ""
msgid "Request was throttled."
msgstr ""
-#: fields.py:152 relations.py:131 relations.py:155 validators.py:77
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155
msgid "This field is required."
msgstr ""
-#: fields.py:153
+#: fields.py:154
msgid "This field may not be null."
msgstr ""
-#: fields.py:480 fields.py:508
+#: fields.py:487 fields.py:515
msgid "\"{input}\" is not a valid boolean."
msgstr ""
-#: fields.py:543
+#: fields.py:550
msgid "This field may not be blank."
msgstr ""
-#: fields.py:544 fields.py:1252
+#: fields.py:551 fields.py:1324
msgid "Ensure this field has no more than {max_length} characters."
msgstr ""
-#: fields.py:545
+#: fields.py:552
msgid "Ensure this field has at least {min_length} characters."
msgstr ""
@@ -144,134 +132,140 @@ msgstr ""
msgid "Enter a valid URL."
msgstr ""
-#: fields.py:640
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
msgid "A valid integer is required."
msgstr ""
-#: fields.py:641 fields.py:675 fields.py:708
+#: fields.py:658 fields.py:692 fields.py:725
msgid "Ensure this value is less than or equal to {max_value}."
msgstr ""
-#: fields.py:642 fields.py:676 fields.py:709
+#: fields.py:659 fields.py:693 fields.py:726
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr ""
-#: fields.py:643 fields.py:677 fields.py:713
+#: fields.py:660 fields.py:694 fields.py:730
msgid "String value too large."
msgstr ""
-#: fields.py:674 fields.py:707
+#: fields.py:691 fields.py:724
msgid "A valid number is required."
msgstr ""
-#: fields.py:710
+#: fields.py:727
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr ""
-#: fields.py:711
+#: fields.py:728
msgid "Ensure that there are no more than {max_decimal_places} decimal places."
msgstr ""
-#: fields.py:712
+#: fields.py:729
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr ""
-#: fields.py:796
+#: fields.py:813
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr ""
-#: fields.py:797
+#: fields.py:814
msgid "Expected a datetime but got a date."
msgstr ""
-#: fields.py:861
+#: fields.py:878
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr ""
-#: fields.py:862
+#: fields.py:879
msgid "Expected a date but got a datetime."
msgstr ""
-#: fields.py:919
+#: fields.py:936
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr ""
-#: fields.py:975 fields.py:1019
+#: fields.py:992 fields.py:1036
msgid "\"{input}\" is not a valid choice."
msgstr ""
-#: fields.py:1020 fields.py:1121 serializers.py:476
+#: fields.py:1037 fields.py:1151 serializers.py:482
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr ""
-#: fields.py:1050
+#: fields.py:1067
msgid "No file was submitted."
msgstr ""
-#: fields.py:1051
+#: fields.py:1068
msgid "The submitted data was not a file. Check the encoding type on the form."
msgstr ""
-#: fields.py:1052
+#: fields.py:1069
msgid "No filename could be determined."
msgstr ""
-#: fields.py:1053
+#: fields.py:1070
msgid "The submitted file is empty."
msgstr ""
-#: fields.py:1054
+#: fields.py:1071
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr ""
-#: fields.py:1096
+#: fields.py:1113
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
-#: generics.py:123
-msgid ""
-"Choose a valid page number. Page numbers must be a whole number, or must be "
-"the string \"last\"."
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr ""
-#: generics.py:128
+#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
msgstr ""
-#: relations.py:132
-msgid "Invalid pk \"{pk_value}\" - object does not exist."
+#: pagination.py:442
+msgid "Invalid cursor"
msgstr ""
#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr ""
+
+#: relations.py:134
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
-#: relations.py:156
+#: relations.py:157
msgid "Invalid hyperlink - No URL match."
msgstr ""
-#: relations.py:157
+#: relations.py:158
msgid "Invalid hyperlink - Incorrect URL match."
msgstr ""
-#: relations.py:158
+#: relations.py:159
msgid "Invalid hyperlink - Object does not exist."
msgstr ""
-#: relations.py:159
+#: relations.py:160
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr ""
-#: relations.py:294
+#: relations.py:295
msgid "Object with {slug_name}={value} does not exist."
msgstr ""
-#: relations.py:295
+#: relations.py:296
msgid "Invalid value."
msgstr ""
@@ -314,3 +308,15 @@ msgstr ""
#: versioning.py:160
msgid "Invalid version in query parameter."
msgstr ""
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr ""
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr ""
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr ""
--
cgit v1.2.3
From 6838f17325c2149e432e4a40b945695b765f35a2 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 30 Jan 2015 16:40:54 +0000
Subject: Add built-in translations.
---
rest_framework/locale/ar/LC_MESSAGES/django.mo | Bin 0 -> 4875 bytes
rest_framework/locale/ar/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/locale/cs/LC_MESSAGES/django.mo | Bin 0 -> 8848 bytes
rest_framework/locale/cs/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/locale/da/LC_MESSAGES/django.mo | Bin 0 -> 8452 bytes
rest_framework/locale/da/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/locale/de/LC_MESSAGES/django.mo | Bin 0 -> 6575 bytes
rest_framework/locale/de/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/locale/en/LC_MESSAGES/django.mo | Bin 0 -> 8553 bytes
rest_framework/locale/en/LC_MESSAGES/django.po | 324 +++++++++++++++++++++
rest_framework/locale/en_US/LC_MESSAGES/django.mo | Bin 0 -> 378 bytes
rest_framework/locale/en_US/LC_MESSAGES/django.po | 8 +-
rest_framework/locale/es/LC_MESSAGES/django.mo | Bin 0 -> 8906 bytes
rest_framework/locale/es/LC_MESSAGES/django.po | 327 +++++++++++++++++++++
rest_framework/locale/et/LC_MESSAGES/django.mo | Bin 0 -> 2018 bytes
rest_framework/locale/et/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/locale/fr/LC_MESSAGES/django.mo | Bin 0 -> 6975 bytes
rest_framework/locale/fr/LC_MESSAGES/django.po | 326 +++++++++++++++++++++
rest_framework/locale/hu/LC_MESSAGES/django.mo | Bin 0 -> 9215 bytes
rest_framework/locale/hu/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/locale/id/LC_MESSAGES/django.mo | Bin 0 -> 497 bytes
rest_framework/locale/id/LC_MESSAGES/django.po | 324 +++++++++++++++++++++
rest_framework/locale/it/LC_MESSAGES/django.mo | Bin 0 -> 5334 bytes
rest_framework/locale/it/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/locale/ko_KR/LC_MESSAGES/django.mo | Bin 0 -> 8555 bytes
rest_framework/locale/ko_KR/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/locale/mk/LC_MESSAGES/django.mo | Bin 0 -> 10731 bytes
rest_framework/locale/mk/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/locale/nl/LC_MESSAGES/django.mo | Bin 0 -> 499 bytes
rest_framework/locale/nl/LC_MESSAGES/django.po | 324 +++++++++++++++++++++
rest_framework/locale/pl/LC_MESSAGES/django.mo | Bin 0 -> 8981 bytes
rest_framework/locale/pl/LC_MESSAGES/django.po | 326 +++++++++++++++++++++
rest_framework/locale/pt_BR/LC_MESSAGES/django.mo | Bin 0 -> 8729 bytes
rest_framework/locale/pt_BR/LC_MESSAGES/django.po | 326 +++++++++++++++++++++
rest_framework/locale/ru/LC_MESSAGES/django.mo | Bin 0 -> 9778 bytes
rest_framework/locale/ru/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/locale/sk/LC_MESSAGES/django.mo | Bin 0 -> 2748 bytes
rest_framework/locale/sk/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/locale/sv/LC_MESSAGES/django.mo | Bin 0 -> 8647 bytes
rest_framework/locale/sv/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/locale/tr/LC_MESSAGES/django.mo | Bin 0 -> 7946 bytes
rest_framework/locale/tr/LC_MESSAGES/django.po | 328 ++++++++++++++++++++++
rest_framework/locale/uk/LC_MESSAGES/django.mo | Bin 0 -> 577 bytes
rest_framework/locale/uk/LC_MESSAGES/django.po | 324 +++++++++++++++++++++
rest_framework/locale/zh_CN/LC_MESSAGES/django.mo | Bin 0 -> 8383 bytes
rest_framework/locale/zh_CN/LC_MESSAGES/django.po | 325 +++++++++++++++++++++
rest_framework/views.py | 10 +-
47 files changed, 7165 insertions(+), 7 deletions(-)
create mode 100644 rest_framework/locale/ar/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/ar/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/cs/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/cs/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/da/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/da/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/de/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/de/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/en/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/en/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/en_US/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/es/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/es/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/et/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/et/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/fr/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/fr/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/hu/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/hu/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/id/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/id/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/it/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/it/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/ko_KR/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/ko_KR/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/mk/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/mk/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/nl/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/nl/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/pl/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/pl/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/pt_BR/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/pt_BR/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/ru/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/ru/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/sk/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/sk/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/sv/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/sv/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/tr/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/tr/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/uk/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/uk/LC_MESSAGES/django.po
create mode 100644 rest_framework/locale/zh_CN/LC_MESSAGES/django.mo
create mode 100644 rest_framework/locale/zh_CN/LC_MESSAGES/django.po
diff --git a/rest_framework/locale/ar/LC_MESSAGES/django.mo b/rest_framework/locale/ar/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..fe1b676c
Binary files /dev/null and b/rest_framework/locale/ar/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/ar/LC_MESSAGES/django.po b/rest_framework/locale/ar/LC_MESSAGES/django.po
new file mode 100644
index 00000000..a910a7c9
--- /dev/null
+++ b/rest_framework/locale/ar/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Eyad Toma , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Arabic (http://www.transifex.com/projects/p/django-rest-framework/language/ar/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: ar\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr ""
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr ""
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr ""
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "اسم المستخدم/كلمة السر غير صحيحين."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr ""
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr ""
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr ""
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "المستخدم غير مفعل او تم حذفه."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "حدث خطأ في المخدم."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr ""
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "بيانات الدخول غير صحيحة."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "لم يتم تزويد بيانات الدخول."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "ليس لديك صلاحية للقيام بهذا الإجراء."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "غير موجود."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr ""
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr ""
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr ""
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr ""
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "هذا الحقل مطلوب."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "لا يمكن لهذا الحقل ان يكون فارغاً null."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" ليس قيمة منطقية."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "لا يمكن لهذا الحقل ان يكون فارغاً."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "تأكد ان الحقل لا يزيد عن {max_length} محرف."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "تأكد ان الحقل {min_length} محرف على الاقل."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "عليك ان تدخل بريد إلكتروني صالح."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "هذه القيمة لا تطابق النمط المطلوب."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr ""
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "الرجاء إدخال رابط إلكتروني صالح."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "الرجاء إدخال رقم صحيح صالح."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "تأكد ان القيمة أقل أو تساوي {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "تأكد ان القيمة أكبر أو تساوي {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr ""
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "الرجاء إدخال رقم صالح."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "تأكد ان القيمة لا تحوي أكثر من {max_digits} رقم."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr ""
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr ""
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "صيغة التاريخ و الوقت غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr ""
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "صيغة التاريخ غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr ""
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "صيغة الوقت غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" ليست واحدة من الخيارات الصالحة."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr ""
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "لم يتم إرسال أي ملف."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr ""
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr ""
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "الملف الذي تم إرساله فارغ."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "تأكد ان اسم الملف لا يحوي أكثر من {max_length} محرف (الإسم المرسل يحوي {length} محرف)."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "رقم الصفحة \"{page_number}\" غير صالح : {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "معرف العنصر \"{pk_value}\" غير صالح - العنصر غير موجود."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr ""
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr ""
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr ""
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr ""
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr ""
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr ""
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "قيمة غير صالحة."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr ""
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr ""
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr ""
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr ""
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr ""
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr ""
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr ""
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr ""
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr ""
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr ""
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "حساب المستخدم غير مفعل."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "تعذر تسجيل الدخول بالبيانات التي ادخلتها."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "يجب أن تتضمن \"اسم المستخدم\" و \"كلمة المرور\"."
diff --git a/rest_framework/locale/cs/LC_MESSAGES/django.mo b/rest_framework/locale/cs/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..a5e67713
Binary files /dev/null and b/rest_framework/locale/cs/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/cs/LC_MESSAGES/django.po b/rest_framework/locale/cs/LC_MESSAGES/django.po
new file mode 100644
index 00000000..50e7034b
--- /dev/null
+++ b/rest_framework/locale/cs/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Jirka Vejrazka , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Czech (http://www.transifex.com/projects/p/django-rest-framework/language/cs/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: cs\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "Chybná hlavička. Nebyly poskytnuty přihlašovací údaje."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "Chybná hlavička. Přihlašovací údaje by neměly obsahovat mezery."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "Chybná hlavička. Přihlašovací údaje nebyly správně zakódovány pomocí base64."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Chybné uživatelské jméno nebo heslo."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Chybná hlavička tokenu. Nebyly zadány přihlašovací údaje."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Chybná hlavička tokenu. Přihlašovací údaje by neměly obsahovat mezery."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Chybný token."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Uživatelský účet je neaktivní nebo byl smazán."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Chyba na straně serveru."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Neplatný formát požadavku."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Chybné přihlašovací údaje."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Přihlašovací údaje nebyly zadány."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "K této akci nemáte oprávnění."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Nenalezeno."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Metoda \"{method}\" není povolena."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "Nelze vyhovět požadavku v hlavičce Accept."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Nepodporovaný media type \"{media_type}\" v požadavku."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "Pořadavek byl limitován kvůli omezení počtu požadavků za časovou periodu."
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Toto pole je vyžadováno."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Toto pole nesmí být prázdné (null)."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" nelze použít jako typ boolean."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "Toto pole nesmí být prázdné.."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Zkontrolujte, že toto pole není delší než {max_length} znaků."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Zkontrolujte, že toto obsahuje alespoň {min_length} znaků"
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Vložte platnou e-mailovou adresu."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Hodnota v tomto poli neodpovídá požadovanému formátu."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Vložte platnou \"zkrácenou formu\" obsahující pouze malá písmena, čísla, spojovník nebo podtržítko."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Vložte platný odkaz."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "Je vyžadováno číslo."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Zkontrolujte, že hodnota je menší nebo rovna {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Zkontrolujte, že hodnota je větší nebo rovna {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "Řetězec je příliš dlouhý"
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "Je vyžadováno číslo."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Zkontrolujte, že číslo neobsahuje více než {max_digits} čislic."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Zkontrolujte, že číslo nemá více než {max_decimal_places} desetinných míst."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Zkontrolujte, že číslo neobsahuje více než {max_whole_digits} čislic před desetinnou čárkou."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "Chybný formát data a času. Použijte jeden z těchto formátů: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Bylo zadáno pouze datum místo data a času."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "Chybný formát data. Použijte jeden z těchto formátů: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Bylo zadáno datum a čas, místo samotného data."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Chybný formát času. Použijte jeden z těchto formátů: {format}."
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" není platnou možností."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Byl očekáván seznam položek ale nalezen \"{input_type}\"."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "Nebyl zaslán žádný soubor."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "Zaslaná data neobsahují soubor. Zkontrolujte typ kódování ve formuláři."
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "Nebylo možno zjistit jméno souboru."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "Zaslaný soubor je prázdný."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "Zajistěte, aby jméno souboru obsahovalo maximálně {max_length} znaků (teď má {length} znaků)."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "Nahrajte platný obrázek. Nahraný soubor buď není obrázkem, nebo je poškozen."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "Chybné čislo stránky \"{page_number}\": {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "Chybný primární klíč \"{pk_value}\" - objekt neexistuje."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "Chybný typ. Byl přijat typ {data_type} místo hodnoty primárního klíče."
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "Chybný odkaz - nebyla nalezena žádní shoda."
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "Chybný odkaz - byla nalezena neplatná shoda."
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "Chybný odkaz - objekt neexistuje."
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "Chybný typ. Byl přijat typ {data_type} místo očekávaného odkazu."
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "Objekt s {slug_name}={value} neexistuje."
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Chybná hodnota."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "Chybná data. Byl přijat typ {datatype} místo očakávaného slovníku."
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "Tato položka musí být unikátní."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr "Položka {field_names} musí tvořit unikátní množinu."
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr "Tato položka musí být pro datum \"{date_field}\" unikátní."
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr "Tato položka musí být pro měsíc \"{date_field}\" unikátní."
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr "Tato položka musí být pro rok \"{date_field}\" unikátní."
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr "Chybné číslo verze v hlavičce Accept"
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr "Chybné číslo verze v odkazu."
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr "Chybné číslo verze v hostname."
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr "Chybné čislo verze v URL parametru."
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "Uživatelský účet je zamčen."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "Se zadanými údaji nebylo možné se přihlásit."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "Musí obsahovat \"uživatelské jméno! a \"heslo\"."
diff --git a/rest_framework/locale/da/LC_MESSAGES/django.mo b/rest_framework/locale/da/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..f947f907
Binary files /dev/null and b/rest_framework/locale/da/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/da/LC_MESSAGES/django.po b/rest_framework/locale/da/LC_MESSAGES/django.po
new file mode 100644
index 00000000..e00ffadf
--- /dev/null
+++ b/rest_framework/locale/da/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Mikkel Munch Mortensen <3xm@detfalskested.dk>, 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Danish (http://www.transifex.com/projects/p/django-rest-framework/language/da/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: da\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "Ugyldig basic header. Ingen legitimation angivet."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "Ugyldig basic header. Legitimationsstrenge må ikke indeholde mellemrum."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "Ugyldig basic header. Legitimationen er ikke base64 encoded på korrekt vis."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Ugyldigt brugernavn/kodeord."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Ugyldig token header."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Ugyldig token header. Token-strenge må ikke indeholde mellemrum."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Ugyldigt token."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Inaktiv eller slettet bruger."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Der er sket en serverfejl."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Misdannet forespørgsel."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Ugyldig legitimation til autentificering."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Legitimation til autentificering blev ikke angivet."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "Du har ikke lov til at udføre denne handling."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Ikke fundet."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Metoden \"{method}\" er ikke tilladt."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "Kunne ikke efterkomme forespørgslens Accept header."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Forespørgslens media type, \"{media_type}\", er ikke understøttet."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "Forespørgslen blev neddroslet."
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Dette felt er påkrævet."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Dette felt må ikke være null."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" er ikke en tilladt boolsk værdi."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "Dette felt må ikke være tomt."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Tjek at dette felt ikke indeholder flere end {max_length} tegn."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Tjek at dette felt indeholder mindst {min_length} tegn."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Angiv en gyldig e-mailadresse."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Denne værdi passer ikke med det påkrævede mønster."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Indtast en gyldig \"slug\", bestående af bogstaver, tal, bund- og bindestreger."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Indtast en gyldig URL."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "Et gyldigt heltal er påkrævet."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Tjek at værdien er mindre end eller lig med {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Tjek at værdien er større end eller lig med {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "Strengværdien er for stor."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "Et gyldigt tal er påkrævet."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Tjek at der ikke er flere end {max_digits} cifre i alt."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Tjek at der ikke er flere end {max_decimal_places} cifre efter kommaet."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Tjek at der ikke er flere end {max_whole_digits} cifre før kommaet."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "Datotid har et forkert format. Brug i stedet et af disse formater: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Forventede en datotid, men fik en dato."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "Dato har et forkert format. Brug i stedet et af disse formater: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Forventede en dato men fik en datotid."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Klokkeslæt har forkert format. Brug i stedet et af disse formater: {format}. "
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" er ikke et gyldigt valg."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Forventede en liste, men fik noget af typen \"{input_type}\"."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "Ingen medsendt fil."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "Det medsendte data var ikke en fil. Tjek typen af indkodning på formularen."
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "Filnavnet kunne ikke afgøres."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "Den medsendte fil er tom."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "Sørg for at filnavnet er højst {max_length} langt (det er {length})."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "Medsend et gyldigt billede. Den medsendte fil var enten ikke et billede eller billedfilen var ødelagt."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "Ugyldig side \"{page_number}\": {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "Ugyldig primærnøgle \"{pk_value}\" - objektet findes ikke."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "Ugyldig type. Forventet værdi er primærnøgle, fik {data_type}."
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "Ugyldigt hyperlink - intet URL match."
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "Ugyldigt hyperlink - forkert URL match."
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "Ugyldigt hyperlink - objektet findes ikke."
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "Forkert type. Forventede en URL-streng, fik {data_type}."
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "Object med {slug_name}={value} findes ikke."
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Ugyldig værdi."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "Ugyldig data. Forventede en dictionary, men fik {datatype}."
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "Dette felt skal være unikt."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr "Felterne {field_names} skal udgøre et unikt sæt."
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr "Dette felt skal være unikt for \"{date_field}\"-datoen."
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr "Dette felt skal være unikt for \"{date_field}\"-måneden."
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr "Dette felt skal være unikt for \"{date_field}\"-året."
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr "Ugyldig version i \"Accept\" headeren."
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr "Ugyldig version i URL-stien."
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr "Ugyldig version i hostname."
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr "Ugyldig version i forespørgselsparameteren."
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "Brugerkontoen er deaktiveret."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "Kunne ikke logge ind med den angivne legitimation."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "Skal indeholde \"username\" og \"password\"."
diff --git a/rest_framework/locale/de/LC_MESSAGES/django.mo b/rest_framework/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..48245c60
Binary files /dev/null and b/rest_framework/locale/de/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/de/LC_MESSAGES/django.po b/rest_framework/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 00000000..74bee416
--- /dev/null
+++ b/rest_framework/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Thomas Tanner, 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: German (http://www.transifex.com/projects/p/django-rest-framework/language/de/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: de\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "Ungültiger basic header. Keine Zugangsdaten angegeben."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "Ungültiger basic header. Zugangsdaten sollen keine Leerzeichen enthalten."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "Ungültiger basic header. Zugangsdaten sind nicht korrekt mit base64 kodiert."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Ungültiger Benutzername/Passwort"
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Ungültiger token header. Keine Zugangsdaten angegeben."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Ungültiger token header. Zugangsdaten sollen keine Leerzeichen enthalten."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Ungültiges Token"
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Benutzer inaktiv oder gelöscht."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Ein Serverfehler ist aufgetreten."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Fehlerhafte Anfrage."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Falsche Anmeldedaten."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Anmeldedaten fehlen."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "Sie sind nicht berechtigt, diese Aktion durchzuführen."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Nicht gefunden."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Methode \"{method}\" nicht erlaubt."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "Kann den Accept header der Anfrage nicht erfüllen."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Nicht unterstützter Medientyp \"{media_type}\" in der Anfrage."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "Die Anfrage wurde gedrosselt."
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Dieses Feld ist erforderlich."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Dieses Feld darf nicht Null sein."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" ist kein gültiger Boole'scher Wert."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "Dieses Feld darf nicht leer sein."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Stelle sicher, dass dieses Feld nicht mehr als {max_length} Zeichen lang ist."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Stelle sicher, dass dieses Feld mindestens {min_length} Zeichen lang ist."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Gebe eine gültige E-Mail Adresse an."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Dieser Wert passt nicht zu dem erforderlichen Muster."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Gebe ein gültiges \"slug\" aus Buchstaben, Ziffern, Unterstrichen und Minuszeichen ein."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Gebe eine gültige URL ein."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "Eine gültige Ganzzahl ist erforderlich."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Stelle sicher, dass dieser Wert kleiner oder gleich {max_value} ist."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Stelle sicher, dass dieser Wert größer oder gleich {max_value} ist."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "Zeichenkette zu lang."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "Eine gültige Zahl ist erforderlich."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Stelle sicher, dass es insgesamt nicht mehr als {max_digits} Ziffern lang ist."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Stelle sicher, dass es nicht mehr als {max_decimal_places} Nachkommastellen lang ist."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Stelle sicher, dass es nicht mehr als {max_whole_places} Stellen vor dem Komma lang ist."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "Datum- und Zeitangabe hat das falsche Format. Nutze stattdessen eines dieser Formate: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Erwarte eine Datum- und Zeitangabe, erhielt aber ein Datum."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "Datum hat das falsche Format. Nutze stattdessen eines dieser Formate: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Erwarte ein Datum, erhielt aber eine Datum- und Zeitangabe."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Zeitangabe hat das falsche Format. Nutze stattdessen eines dieser Formate: {format}."
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" ist keine gültige Option."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Erwarte eine Liste von Elementen, erhielt aber den Typ \"{input_type}\"."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "Es wurde keine Datei übermittelt."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "Die übermittelten Daten sind keine Datei. Prüfe den Kodierungstyp im Formular."
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "Der Dateiname konnte nicht ermittelt werden."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "Die übermittelte Datei ist leer."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr ""
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr ""
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr ""
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr ""
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr ""
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr ""
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr ""
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr ""
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr ""
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Ungültiger Wert."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "Ungültige Daten. Dictionary erwartet, aber {datatype} erhalten."
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "Dieses Feld muss eineindeutig sein."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr ""
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr ""
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr ""
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr ""
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr ""
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr ""
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr ""
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr ""
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "Benutzerkonto ist gesperrt."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "Kann nicht mit den angegeben Zugangsdaten anmelden."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "\"username\" und \"password\" sind erforderlich."
diff --git a/rest_framework/locale/en/LC_MESSAGES/django.mo b/rest_framework/locale/en/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..746915ff
Binary files /dev/null and b/rest_framework/locale/en/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/en/LC_MESSAGES/django.po b/rest_framework/locale/en/LC_MESSAGES/django.po
new file mode 100644
index 00000000..f3db69e5
--- /dev/null
+++ b/rest_framework/locale/en/LC_MESSAGES/django.po
@@ -0,0 +1,324 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: English (http://www.transifex.com/projects/p/django-rest-framework/language/en/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "Invalid basic header. No credentials provided."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "Invalid basic header. Credentials string should not contain spaces."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "Invalid basic header. Credentials not correctly base64 encoded."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Invalid username/password."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Invalid token header. No credentials provided."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Invalid token header. Token string should not contain spaces."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Invalid token."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "User inactive or deleted."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "A server error occurred."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Malformed request."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Incorrect authentication credentials."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Authentication credentials were not provided."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "You do not have permission to perform this action."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Not found."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Method \"{method}\" not allowed."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "Could not satisfy the request Accept header."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Unsupported media type \"{media_type}\" in request."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "Request was throttled."
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "This field is required."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "This field may not be null."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" is not a valid boolean."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "This field may not be blank."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Ensure this field has no more than {max_length} characters."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Ensure this field has at least {min_length} characters."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Enter a valid email address."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "This value does not match the required pattern."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Enter a valid \"slug\" consisting of letters, numbers, underscores or hyphens."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Enter a valid URL."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr "\"{value}\" is not a valid UUID."
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "A valid integer is required."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Ensure this value is less than or equal to {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Ensure this value is greater than or equal to {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "String value too large."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "A valid number is required."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Ensure that there are no more than {max_digits} digits in total."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Ensure that there are no more than {max_decimal_places} decimal places."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Ensure that there are no more than {max_whole_digits} digits before the decimal point."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "Datetime has wrong format. Use one of these formats instead: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Expected a datetime but got a date."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "Date has wrong format. Use one of these formats instead: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Expected a date but got a datetime."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Time has wrong format. Use one of these formats instead: {format}."
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" is not a valid choice."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Expected a list of items but got type \"{input_type}\"."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "No file was submitted."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "The submitted data was not a file. Check the encoding type on the form."
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "No filename could be determined."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "The submitted file is empty."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "Ensure this filename has at most {max_length} characters (it has {length})."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "Upload a valid image. The file you uploaded was either not an image or a corrupted image."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr "Expected a dictionary of items but got type \"{input_type}\"."
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "Invalid page \"{page_number}\": {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr "Invalid cursor"
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "Invalid pk \"{pk_value}\" - object does not exist."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "Incorrect type. Expected pk value, received {data_type}."
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "Invalid hyperlink - No URL match."
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "Invalid hyperlink - Incorrect URL match."
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "Invalid hyperlink - Object does not exist."
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "Incorrect type. Expected URL string, received {data_type}."
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "Object with {slug_name}={value} does not exist."
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Invalid value."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "Invalid data. Expected a dictionary, but got {datatype}."
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "This field must be unique."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr "The fields {field_names} must make a unique set."
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr "This field must be unique for the \"{date_field}\" date."
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr "This field must be unique for the \"{date_field}\" month."
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr "This field must be unique for the \"{date_field}\" year."
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr "Invalid version in \"Accept\" header."
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr "Invalid version in URL path."
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr "Invalid version in hostname."
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr "Invalid version in query parameter."
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "User account is disabled."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "Unable to log in with provided credentials."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "Must include \"username\" and \"password\"."
diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.mo b/rest_framework/locale/en_US/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..eb60d9d7
Binary files /dev/null and b/rest_framework/locale/en_US/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.po b/rest_framework/locale/en_US/LC_MESSAGES/django.po
index 23f76ff7..11d94e9c 100644
--- a/rest_framework/locale/en_US/LC_MESSAGES/django.po
+++ b/rest_framework/locale/en_US/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"POT-Creation-Date: 2015-01-30 16:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -69,7 +69,7 @@ msgstr ""
msgid "You do not have permission to perform this action."
msgstr ""
-#: exceptions.py:93
+#: exceptions.py:93 views.py:77
msgid "Not found."
msgstr ""
@@ -309,6 +309,10 @@ msgstr ""
msgid "Invalid version in query parameter."
msgstr ""
+#: views.py:81
+msgid "Permission denied."
+msgstr ""
+
#: authtoken/serializers.py:20
msgid "User account is disabled."
msgstr ""
diff --git a/rest_framework/locale/es/LC_MESSAGES/django.mo b/rest_framework/locale/es/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..814db7be
Binary files /dev/null and b/rest_framework/locale/es/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/es/LC_MESSAGES/django.po b/rest_framework/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 00000000..28ef893d
--- /dev/null
+++ b/rest_framework/locale/es/LC_MESSAGES/django.po
@@ -0,0 +1,327 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# José Padilla , 2015
+# Miguel González , 2015
+# Sergio Infante , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Spanish (http://www.transifex.com/projects/p/django-rest-framework/language/es/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: es\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "Cabecera básica inválida. Las credenciales no fueron suministradas."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "Cabecera básica inválida. La cadena con las credenciales no debe contener espacios."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "Cabecera básica inválida. Las credenciales incorrectamente codificadas en base64."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Nombre de usuario/contraseña inválidos."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Cabecera token inválida. Las credenciales no fueron suministradas."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Cabecera token inválida. La cadena token no debe contener espacios."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Token inválido."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Usuario inactivo o borrado."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Se ha producido un error en el servidor."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Solicitud con formato incorrecto."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Credenciales de autenticación incorrectas."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Las credenciales de autenticación no se proveyeron."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "Usted no tiene permiso para realizar esta acción."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "No encontrado."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Método \"{method}\" no permitido."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "No se ha podido satisfacer la solicitud de cabecera de Accept."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Tipo de medio \"{media_type}\" incompatible en la solicitud."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "Solicitud fue regulada (throttled)."
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Este campo es requerido."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Este campo no puede ser nulo."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" no es un booleano válido."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "Este campo no puede estar en blanco."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Asegúrese de que este campo no tenga más de {max_length} caracteres."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Asegúrese de que este campo tenga al menos {min_length} caracteres."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Introduzca una dirección de correo electrónico válida."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Este valor no coincide con el patrón requerido."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Introduzca un \"slug\" válido consistente en letras, números, guiones o guiones bajos."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Introduzca una URL válida."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "Introduzca un número entero válido."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Asegúrese de que este valor es menor o igual a {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Asegúrese de que este valor es mayor o igual a {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "Cadena demasiado larga."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "Se requiere un número válido."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Asegúrese de que no haya más de {max_digits} dígitos en total."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Asegúrese de que no haya más de {max_decimal_places} decimales."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Asegúrese de que no haya más de {max_whole_digits} dígitos en la parte entera."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "Fecha/hora con formato erróneo. Use uno de los siguientes formatos en su lugar: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Se esperaba un fecha/hora en vez de una fecha."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "Fecha con formato erróneo. Use uno de los siguientes formatos en su lugar: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Se esperaba una fecha en vez de una fecha/hora."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Hora con formato erróneo. Use uno de los siguientes formatos en su lugar: {format}."
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" no es una elección válida."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Se esperaba una lista de elementos en vez del tipo \"{input_type}\"."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "No se envió ningún archivo."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "La información enviada no era un archivo. Compruebe el tipo de codificación del formulario."
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "No se pudo determinar un nombre de archivo."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "El archivo enviado está vació."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "Asegúrese de que el nombre de archivo no tenga más de {max_length} caracteres (tiene {length})."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "Adjunte una imagen válida. El archivo adjunto o bien no es una imagen o bien está dañado."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "Página \"{page_number}\" inválida: {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "Clave primaria \"{pk_value}\" inválida - objeto no existe."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "Tipo incorrecto. Se esperaba valor de clave primaria y se recibió {data_type}."
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "Hiperenlace inválido - No hay URL coincidentes."
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "Hiperenlace inválido - Coincidencia incorrecta de la URL."
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "Hiperenlace inválido - Objeto no existe."
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "Tipo incorrecto. Se esperaba una URL y se recibió {data_type}."
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "Objeto con {slug_name}={value} no existe."
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Valor inválido."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "Datos inválidos. Se esperaba un diccionario pero es un {datatype}."
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "Este campo debe ser único."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr "Los campos {field_names} deben formar un conjunto único."
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr "Este campo debe ser único para el día \"{date_field}\"."
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr "Este campo debe ser único para el mes \"{date_field}\"."
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr "Este campo debe ser único para el año \"{date_field}\"."
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr "Versión inválida en la cabecera \"Accept\"."
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr "Versión inválida en la ruta de la URL."
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr "Versión inválida en el nombre de host."
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr "Versión inválida en el parámetro de consulta."
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "Cuenta de usuario está deshabilitada."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "No puede iniciar sesión con las credenciales proporcionadas."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "Debe incluir \"username\" y \"password\"."
diff --git a/rest_framework/locale/et/LC_MESSAGES/django.mo b/rest_framework/locale/et/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..ca9b6ec4
Binary files /dev/null and b/rest_framework/locale/et/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/et/LC_MESSAGES/django.po b/rest_framework/locale/et/LC_MESSAGES/django.po
new file mode 100644
index 00000000..dec03d4d
--- /dev/null
+++ b/rest_framework/locale/et/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Tõnis Kärdi , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Estonian (http://www.transifex.com/projects/p/django-rest-framework/language/et/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: et\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr ""
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr ""
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr ""
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Vale kasutajatunnus/salasõna."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr ""
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr ""
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr ""
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Kasutaja on inaktiivne või kustutatud."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr ""
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr ""
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr ""
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr ""
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr ""
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr ""
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr ""
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr ""
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr ""
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr ""
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Väli on kohustuslik."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Väli ei tohi olla tühi."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr ""
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr ""
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr ""
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr ""
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Sisesta kehtiv e-posti aadress."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Väärtus ei ühti etteantud mustriga."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr ""
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Sisesta korrektne URL."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr ""
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Veendu, et väärtus on väiksem kui või võrdne väärtusega {max_value}. "
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Veendu, et väärtus on suurem kui või võrdne väärtusega {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "Sõne on liiga pikk."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr ""
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Veendu, et kokku pole rohkem kui {max_digits}."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Veendu, et komakohti pole rohkem kui {max_decimal_places}. "
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr ""
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr ""
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr ""
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr ""
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr ""
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr ""
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr ""
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr ""
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr ""
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr ""
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr ""
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr ""
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr ""
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr ""
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr ""
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr ""
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr ""
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr ""
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr ""
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr ""
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr ""
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr ""
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr ""
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr ""
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr ""
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr ""
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr ""
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr ""
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr ""
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "Kasutajakonto on suletud"
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "Sisselogimine antud tunnusega ebaõnnestus."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "Peab sisaldama \"kasutajatunnust\" ja \"slasõna\"."
diff --git a/rest_framework/locale/fr/LC_MESSAGES/django.mo b/rest_framework/locale/fr/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..68519d45
Binary files /dev/null and b/rest_framework/locale/fr/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/fr/LC_MESSAGES/django.po b/rest_framework/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..e8597c30
--- /dev/null
+++ b/rest_framework/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,326 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Etienne Desgagné , 2015
+# Martin Maillard , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: French (http://www.transifex.com/projects/p/django-rest-framework/language/fr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: fr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "En-tête « basic » non valide. Informations d'identification non fournies."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "En-tête « basic » non valide. Les informations d'identification ne doivent pas contenir d'espaces."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "En-tête « basic » non valide. Encodage base64 des informations d'identification incorrect."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Nom d'utilisateur et/ou mot de passe non valide(s)."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "En-tête « token » non valide. Informations d'identification non fournies."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "En-tête « token » non valide. Un token ne doit pas contenir d'espaces."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Token non valide."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Utilisateur inactif ou supprimé."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Une erreur du serveur est survenue."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Requête malformée"
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Informations d'authentification incorrectes."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Informations d'authentification non fournies."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "Vous n'avez pas la permission d'effectuer cette action."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr ""
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Méthode \"{method}\" non autorisée."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr ""
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr ""
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr ""
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Ce champ est obligatoire."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Ce champ ne peut être null."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" n'est pas un booléen valide."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "Ce champ ne peut être vide."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Assurez-vous que ce champ comporte au plus {max_length} caractères."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Assurez-vous que ce champ comporte au moins {min_length} caractères."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Saisissez une adresse email valable."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Cette valeur ne satisfait pas le motif imposé."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Ce champ ne doit contenir que des lettres, des nombres, des tirets bas _ et des traits d'union."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Saisissez une URL valide."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "Saisissez un nombre entier valide."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Assurez-vous que cette valeur est inférieure ou égale à {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Assurez-vous que cette valeur est supérieure ou égale à {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "Chaîne de caractères trop longue."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "Un nombre valide est requis."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Assurez-vous qu'il n'y a pas plus de {max_digits} chiffres au total."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Assurez-vous qu'il n'y a pas plus de {max_decimal_places} chiffres après la virgule."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Assurez-vous qu'il n'y a pas plus de {max_whole_digits} chiffres avant la virgule."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr ""
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr ""
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" n'est pas un choix valide."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr ""
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "Aucun fichier n'a été soumis."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "La donnée soumise n'est pas un fichier. Vérifiez le type d'encodage du formulaire."
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "Le nom de fichier n'a pu être déterminé."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "Le fichier soumis est vide."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "Assurez-vous que le nom de fichier comporte au plus {max_length} caractères (il en comporte {length})."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "Transférez une image valide. Le fichier que vous avez transféré n'est pas une image, ou il est corrompu."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "Page \"{page_number}\" non valide : {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "Clé primaire \"{pk_value}\" non valide - l'objet n'existe pas."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr ""
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr ""
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr ""
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr ""
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr ""
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "L'object avec {slug_name}={value} n'existe pas."
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Valeur non valide."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr ""
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "Ce champ doit être unique."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr "Les champs {field_names} doivent former un ensemble unique."
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr "Ce champ doit être unique pour la date \"{date_field}\"."
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr "Ce champ doit être unique pour le mois \"{date_field}\"."
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr "Ce champ doit être unique pour l'année \"{date_field}\"."
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr "Version non valide dans l'en-tête « Accept »."
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr "Version non valide dans l'URL."
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr "Version non valide dans le nom d'hôte."
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr "Version non valide dans le paramètre de requête."
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "Ce compte est désactivé."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "Impossible de se connecter avec les informations d'identification fournies."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "\"username\" et \"password\" doivent être inclus."
diff --git a/rest_framework/locale/hu/LC_MESSAGES/django.mo b/rest_framework/locale/hu/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..451b0b9a
Binary files /dev/null and b/rest_framework/locale/hu/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/hu/LC_MESSAGES/django.po b/rest_framework/locale/hu/LC_MESSAGES/django.po
new file mode 100644
index 00000000..14fb6544
--- /dev/null
+++ b/rest_framework/locale/hu/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Zoltan Szalai , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Hungarian (http://www.transifex.com/projects/p/django-rest-framework/language/hu/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: hu\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "Érvénytelen basic fejlécmező. Nem voltak megadva azonosítók."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "Érvénytelen basic fejlécmező. Az azonosító karakterlánc nem tartalmazhat szóközöket."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "Érvénytelen basic fejlécmező. Az azonosítók base64 kódolása nem megfelelő."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Érvénytelen felhasználónév/jelszó."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Érvénytelen token fejlécmező. Nem voltak megadva azonosítók."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Érvénytelen token fejlécmező. A token karakterlánc nem tartalmazhat szóközöket."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Érvénytelen token."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "A felhasználó nincs aktiválva vagy törölve lett."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Szerver oldali hiba történt."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Hibás kérés."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Hibás azonosítók."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Nem voltak megadva azonosítók."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "Nincs jogosultsága a művelet végrehajtásához."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Nem található."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "A \"{method}\" metódus nem megengedett."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "A kérés Accept fejlécmezőjét nem lehetett kiszolgálni."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Nem támogatott média típus \"{media_type}\" a kérésben."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "A kérés korlátozva lett."
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Ennek a mezőnek a megadása kötelező."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Ez a mező nem lehet null értékű."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "Az \"{input}\" nem egy érvényes logikai érték."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "Ez a mező nem lehet üres."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Bizonyosodjon meg arról, hogy ez a mező legfeljebb {max_length} karakterből áll."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Bizonyosodjon meg arról, hogy ez a mező legalább {min_length} karakterből áll."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Adjon meg egy érvényes e-mail címet!"
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Ez az érték nem illeszkedik a szükséges mintázatra."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Az URL barát cím csak betűket, számokat, aláhúzásokat és kötőjeleket tartalmazhat."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Adjon meg egy érvényes URL-t!"
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "Egy érvényes egész szám megadása szükséges."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Bizonyosodjon meg arról, hogy ez az érték legfeljebb {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Bizonyosodjon meg arról, hogy ez az érték legalább {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "A karakterlánc túl hosszú."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "Egy érvényes szám megadása szükséges."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Bizonyosodjon meg arról, hogy a számjegyek száma összesen legfeljebb {max_digits}."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Bizonyosodjon meg arról, hogy a tizedes tört törtrészében levő számjegyek száma összesen legfeljebb {max_decimal_places}."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Bizonyosodjon meg arról, hogy a tizedes tört egész részében levő számjegyek száma összesen legfeljebb {max_whole_digits}."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "A dátum formátuma hibás. Használja ezek valamelyikét helyette: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Időt is tartalmazó dátum helyett egy időt nem tartalmazó dátum lett elküldve."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "A dátum formátuma hibás. Használja ezek valamelyikét helyette: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Időt nem tartalmazó dátum helyett egy időt is tartalmazó dátum lett elküldve."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Az idő formátuma hibás. Használja ezek valamelyikét helyette: {format}."
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "Az \"{input}\" nem egy érvényes elem."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Elemek listája helyett \"{input_type}\" lett elküldve."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "Semmilyen fájl sem került feltöltésre."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "Az elküldött adat nem egy fájl volt. Ellenőrizze a kódolás típusát az űrlapon!"
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "A fájlnév nem megállapítható."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "A küldött fájl üres."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "Bizonyosodjon meg arról, hogy a fájlnév legfeljebb {max_length} karakterből áll (jelenlegi hossza: {length})."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "Töltsön fel egy érvényes képfájlt! A feltöltött fájl nem kép volt, vagy megsérült."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "Érvénytelen oldal \"{page_number}\": {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "Érvénytelen pk \"{pk_value}\" - az objektum nem létezik."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "Helytelen típus. pk érték helyett {data_type} lett elküldve."
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "Érvénytelen link - Nem illeszkedő URL."
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "Érvénytelen link. - Eltérő URL illeszkedés."
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "Érvénytelen link - Az objektum nem létezik."
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "Helytelen típus. URL karakterlánc helyett {data_type} lett elküldve."
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "Nem létezik olyan objektum, amelynél {slug_name}={value}."
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Érvénytelen érték."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "Érvénytelen adat. Egy dictionary helyett {datatype} lett elküldve."
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "Ennek a mezőnek egyedinek kell lennie."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr "A {field_names} mezőnevek nem tartalmazhatnak duplikátumot."
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr "A mezőnek egyedinek kell lennie a \"{date_field}\" dátumra."
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr "A mezőnek egyedinek kell lennie a \"{date_field}\" hónapra."
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr "A mezőnek egyedinek kell lennie a \"{date_field}\" évre."
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr "Érvénytelen verzió az \"Accept\" fejlécmezőben."
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr "Érvénytelen verzió az URL elérési útban."
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr "Érvénytelen verzió a hosztnévben."
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr "Érvénytelen verzió a lekérdezési paraméterben."
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "A felhasználó tiltva van."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "A megadott azonosítókkal nem lehet bejelentkezni."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "Tartalmaznia kell a \"felhasználónevet\" és a \"jelszót\"."
diff --git a/rest_framework/locale/id/LC_MESSAGES/django.mo b/rest_framework/locale/id/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..7fc98bda
Binary files /dev/null and b/rest_framework/locale/id/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/id/LC_MESSAGES/django.po b/rest_framework/locale/id/LC_MESSAGES/django.po
new file mode 100644
index 00000000..99b70546
--- /dev/null
+++ b/rest_framework/locale/id/LC_MESSAGES/django.po
@@ -0,0 +1,324 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Indonesian (http://www.transifex.com/projects/p/django-rest-framework/language/id/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: id\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr ""
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr ""
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr ""
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr ""
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr ""
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr ""
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr ""
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr ""
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr ""
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr ""
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr ""
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr ""
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr ""
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr ""
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr ""
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr ""
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr ""
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr ""
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr ""
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr ""
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr ""
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr ""
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr ""
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr ""
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr ""
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr ""
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr ""
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr ""
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr ""
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr ""
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr ""
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr ""
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr ""
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr ""
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr ""
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr ""
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr ""
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr ""
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr ""
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr ""
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr ""
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr ""
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr ""
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr ""
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr ""
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr ""
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr ""
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr ""
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr ""
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr ""
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr ""
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr ""
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr ""
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr ""
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr ""
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr ""
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr ""
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr ""
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr ""
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr ""
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr ""
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr ""
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr ""
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr ""
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr ""
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr ""
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr ""
diff --git a/rest_framework/locale/it/LC_MESSAGES/django.mo b/rest_framework/locale/it/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..82ceb810
Binary files /dev/null and b/rest_framework/locale/it/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/it/LC_MESSAGES/django.po b/rest_framework/locale/it/LC_MESSAGES/django.po
new file mode 100644
index 00000000..2cdfb877
--- /dev/null
+++ b/rest_framework/locale/it/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Mattia Procopio , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Italian (http://www.transifex.com/projects/p/django-rest-framework/language/it/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: it\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr ""
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr ""
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr ""
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Nome utente/password non validi"
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Header del token non valido. Credenziali non fornite."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Header del token non valido. Il contenuto del token non dovrebbe contenere spazi."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Token invalido."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Utente inattivo o eliminato."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Errore del server."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Richiesta malformata."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Credenziali di autenticazione incorrette."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Non sono state immesse le credenziali di autenticazione."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "Non hai l'autorizzazione per eseguire questa azione."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Non trovato."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Metodo \"{method}\" non consentito"
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "Impossibile soddisfare l'header \"Accept\" presente nella richiesta."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Tipo di media \"{media_type}\"non supportato."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr ""
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Campo obbligatorio."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Il campo non puà essere nullo."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" non è un valido valore booleano."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "Questo campo non può essere omesso."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Assicurati che questo campo non abbia più di {max_length} caratteri."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Assicurati che questo campo abbia almeno {max_length} caratteri."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Inserisci un indirizzo email valido."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr ""
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Immetti uno \"slug\" valido che consista di lettere, numeri, underscore o trattini."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Inserisci un URL valido"
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "È richiesto un numero intero valido."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Assicurati che il valore sia minore o uguale a {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Assicurati che il valore sia maggiore o uguale a {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr ""
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "È richiesto un numero valido."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Assicurati che non ci siano più di {max_digits} cifre in totale."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Assicurati che non ci siano più di {max_decimal_places} cifre decimali."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Assicurati che non ci siano più di {max_whole_digits} cifre prima del separatore decimale."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "L'oggetto di tipo datetime è in un formato errato. Usa uno dei seguenti formati: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Atteso un oggetto di tipo datetime ma l'oggetto ricevuto è di tipo date."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "La data è in un formato errato. Usa uno dei seguenti formati: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Atteso un oggetto di tipo date ma l'oggetto ricevuto è di tipo datetime."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" non è una scelta valida."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Attesa una lista di oggetti ma l'oggetto ricevuto è di tipo \"{input_type}\"."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "Non è stato inviato alcun file."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr ""
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "Il nome del file non può essere determinato."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "Il file inviato è vuoto."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr ""
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr ""
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr ""
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr ""
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr ""
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr ""
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr ""
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr ""
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr ""
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Valore non valido."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr ""
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "Questo campo deve essere unico."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr ""
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr ""
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr ""
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr ""
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr ""
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr ""
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr ""
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr ""
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "L'account dell'utente è disabilitato"
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "Impossibile eseguire il log in con le credenziali immesse."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "Deve includere \"nome utente\" e \"password\"."
diff --git a/rest_framework/locale/ko_KR/LC_MESSAGES/django.mo b/rest_framework/locale/ko_KR/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..457bb53c
Binary files /dev/null and b/rest_framework/locale/ko_KR/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/ko_KR/LC_MESSAGES/django.po b/rest_framework/locale/ko_KR/LC_MESSAGES/django.po
new file mode 100644
index 00000000..963fe89a
--- /dev/null
+++ b/rest_framework/locale/ko_KR/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# SUN CHOI , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Korean (Korea) (http://www.transifex.com/projects/p/django-rest-framework/language/ko_KR/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: ko_KR\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "기본 헤더(basic header)가 유효하지 않습니다. 인증데이터(credentials)가 제공되지 않았습니다."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "기본 헤더(basic header)가 유효하지 않습니다. 인증데이터(credentials) 문자열은 빈칸(spaces)을 포함하지 않아야 합니다."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "기본 헤더(basic header)가 유효하지 않습니다. 인증데이터(credentials)가 base64로 적절히 부호화(encode)되지 않았습니다."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "아이디/비밀번호가 유효하지 않습니다."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "토큰 헤더가 유효하지 않습니다. 인증데이터(credentials)가 제공되지 않았습니다."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "토큰 헤더가 유효하지 않습니다. 토큰 문자열은 빈칸(spaces)를 포함하지 않아야 합니다."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "토큰이 유효하지 않습니다."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "계정이 중지되었거나 삭제되었습니다."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "서버 장애가 발생했습니다."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "잘못된 요청입니다."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "자격 인증데이터(authentication credentials)가 정확하지 않습니다."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "자격 인증데이터(authentication credentials)가 제공되지 않았습니다."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "이 작업을 "
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "찾을 수 없습니다."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "메소드(Method) \"{method}\"는 허용되지 않습니다."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr ""
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "요청된 \"{media_type}\"가 지원되지 않는 미디어 형태입니다."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "요청이 지연(throttled)되었습니다."
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "이 항목을 채워주십시오."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr ""
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\"이 유효하지 않은 부울(boolean)입니다."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr ""
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "이 칸이 글자 수가 {max_length} 이하인지 확인하십시오."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "이 칸이 글자 수가 적어도 {min_length} 이상인지 확인하십시오."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "유효한 이메일 주소를 입력하십시오."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "형식에 맞지 않는 값입니다."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "문자, 숫자, 밑줄( _ ) 또는 하이픈( - )으로 이루어진 유효한 \"slug\"를 입력하십시오."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "유효한 URL을 입력하십시오."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "유효한 정수(integer)를 넣어주세요."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "이 값이 {max_value}보다 작거나 같은지 확인하십시오."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "이 값이 {min_value}보다 크거나 같은지 확인하십시오."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "문자열 값이 너무 큽니다."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "유효한 숫자를 넣어주세요."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "전체 숫자(digits)가 {max_digits} 이하인지 확인하십시오."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "소수점 자릿수가 {max_decimal_places} 이하인지 확인하십시오."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "소수점 자리 앞에 숫자(digits)가 {max_whole_digits} 이하인지 확인하십시오."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "Datetime의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "예상된 datatime 대신 date를 받았습니다."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "Date의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "예상된 date 대신 datetime을 받았습니다."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Time의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\"이 유효하지 않은 선택(choice)입니다."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "아이템 리스트가 예상되었으나 \"{input_type}\"를 받았습니다."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "파일이 제출되지 않았습니다."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "제출된 데이터는 파일이 아닙니다. 제출된 서식의 인코딩 형식을 확인하세요."
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "파일명을 알 수 없습니다."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "제출된 파일이 비어있습니다."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "이 파일명의 글자수가 최대 {max_length}를 넘지 않는지 확인하십시오. (이것은 {length}가 있습니다)."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "유효한 이미지 파일을 업로드 하십시오. 업로드 하신 파일은 이미지 파일이 아니거나 손상된 이미지 파일입니다."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "유효하지 않은 page \"{page_number}\": {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "유효하지 않은 pk \"{pk_value}\" - 객체가 존재하지 않습니다."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "잘못된 형식입니다. pk 값 대신 {data_type}를 받았습니다."
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "유효하지 않은 하이퍼링크 - 일치하는 URL이 없습니다."
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "유효하지 않은 하이퍼링크 - URL이 일치하지 않습니다."
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "유효하지 않은 하이퍼링크 - 객체가 존재하지 않습니다."
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "잘못된 형식입니다. URL 문자열을 예상했으나 {data_type}을 받았습니다."
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "{slug_name}={value} 객체가 존재하지 않습니다."
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "값이 유효하지 않습니다."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "유효하지 않은 데이터. 딕셔너리(dictionary)대신 {datatype}를 받았습니다."
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr ""
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr ""
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr ""
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr ""
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr ""
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr ""
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr ""
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr ""
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr ""
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "사용자 계정을 사용할 수 없습니다."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "제공된 인증데이터(credentials)로는 로그인할 수 없습니다."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "\"아이디\"와 \"비밀번호\"를 포함해야 합니다."
diff --git a/rest_framework/locale/mk/LC_MESSAGES/django.mo b/rest_framework/locale/mk/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..fc586626
Binary files /dev/null and b/rest_framework/locale/mk/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/mk/LC_MESSAGES/django.po b/rest_framework/locale/mk/LC_MESSAGES/django.po
new file mode 100644
index 00000000..d9a46953
--- /dev/null
+++ b/rest_framework/locale/mk/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Filip Dimitrovski , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Macedonian (http://www.transifex.com/projects/p/django-rest-framework/language/mk/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: mk\n"
+"Plural-Forms: nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "Невалиден основен header. Не се внесени податоци за автентикација."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "Невалиден основен header. Автентикационата низа не треба да содржи празни места."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "Невалиден основен header. Податоците за автентикација не се енкодирани со base64."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Невалидно корисничко име/лозинка."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Невалиден токен header. Не се внесени податоци за најава."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Невалиден токен во header. Токенот не треба да содржи празни места."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Невалиден токен."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Корисникот е деактивиран или избришан."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Настана серверска грешка."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Неправилен request."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Неточни податоци за најава."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Не се внесени податоци за најава."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "Немате дозвола да го сторите ова."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Не е пронајдено ништо."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Методата \"{method}\" не е дозволена."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "Не може да се исполни барањето на Accept header-от."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Media типот „{media_type}“ не е поддржан."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "Request-от е забранет заради ограничувања."
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Ова поле е задолжително."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Ова поле не смее да биде недефинирано."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" не е валиден boolean."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "Ова поле не смее да биде празно."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Ова поле не смее да има повеќе од {max_length} знаци."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Ова поле мора да има барем {min_length} знаци."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Внесете валидна email адреса."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Ова поле не е по правилната шема/барање."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Внесете валидно име што содржи букви, бројки, долни црти или црти."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Внесете валиден URL."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "Задолжителен е валиден цел број."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Вредноста треба да биде помала или еднаква на {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Вредноста треба да биде поголема или еднаква на {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "Вредноста е преголема."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "Задолжителен е валиден број."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Не смее да има повеќе од {max_digits} цифри вкупно."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Не смее да има повеќе од {max_decimal_places} децимални места."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Не смее да има повеќе од {max_whole_digits} цифри пред децималната точка."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "Датата и времето се со погрешен формат. Користете го овој формат: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Очекувано беше дата и време, а внесено беше само дата."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "Датата е со погрешен формат. Користете го овој формат: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Очекувана беше дата, а внесени беа и дата и време."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Времето е со погрешен формат. Користете го овој формат: {format}."
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "„{input}“ не е валиден избор."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Очекувана беше листа, а внесено беше „{input_type}“."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "Ниеден фајл не е качен (upload-иран)."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "Испратените податоци не се фајл. Проверете го encoding-от на формата."
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "Не може да се открие име на фајлот."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "Качениот (upload-иран) фајл е празен."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "Името на фајлот треба да има највеќе {max_length} знаци (а има {length})."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "Качете (upload-ирајте) валидна слика. Фајлот што го качивте не е валидна слика или е расипан."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "Невалидна страна „{page_number}“: {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "Невалиден pk „{pk_value}“ - објектот не постои."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "Неточен тип. Очекувано беше pk, а внесено {data_type}."
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "Невалиден хиперлинк - не е внесен URL."
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "Невалиден хиперлинк - внесен е неправилен URL."
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "Невалиден хиперлинк - Објектот не постои."
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "Неточен тип. Очекувано беше URL, a внесено {data_type}."
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "Објектот со {slug_name}={value} не постои."
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Невалидна вредност."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "Невалидни податоци. Очекуван беше dictionary, а внесен {datatype}."
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "Ова поле мора да биде уникатно."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr "Полињата {field_names} заедно мора да формираат уникатен збир."
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr "Ова поле мора да биде уникатно за „{date_field}“ датата."
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr "Ова поле мора да биде уникатно за „{date_field}“ месецот."
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr "Ова поле мора да биде уникатно за „{date_field}“ годината."
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr "Невалидна верзија во „Accept“ header-от."
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr "Невалидна верзија во URL патеката."
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr "Невалидна верзија во hostname-от."
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr "Невалидна верзија во query параметарот."
+
+#: 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 "Мора да се внесе „username“ и „password“."
diff --git a/rest_framework/locale/nl/LC_MESSAGES/django.mo b/rest_framework/locale/nl/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..b0e1ad77
Binary files /dev/null and b/rest_framework/locale/nl/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/nl/LC_MESSAGES/django.po b/rest_framework/locale/nl/LC_MESSAGES/django.po
new file mode 100644
index 00000000..a1215512
--- /dev/null
+++ b/rest_framework/locale/nl/LC_MESSAGES/django.po
@@ -0,0 +1,324 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Dutch (http://www.transifex.com/projects/p/django-rest-framework/language/nl/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: nl\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr ""
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr ""
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr ""
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr ""
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr ""
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr ""
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr ""
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr ""
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr ""
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr ""
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr ""
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr ""
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr ""
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr ""
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr ""
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr ""
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr ""
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr ""
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr ""
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr ""
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr ""
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr ""
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr ""
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr ""
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr ""
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr ""
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr ""
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr ""
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr ""
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr ""
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr ""
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr ""
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr ""
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr ""
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr ""
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr ""
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr ""
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr ""
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr ""
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr ""
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr ""
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr ""
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr ""
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr ""
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr ""
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr ""
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr ""
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr ""
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr ""
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr ""
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr ""
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr ""
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr ""
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr ""
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr ""
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr ""
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr ""
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr ""
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr ""
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr ""
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr ""
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr ""
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr ""
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr ""
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr ""
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr ""
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr ""
diff --git a/rest_framework/locale/pl/LC_MESSAGES/django.mo b/rest_framework/locale/pl/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..9db72cfb
Binary files /dev/null and b/rest_framework/locale/pl/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/pl/LC_MESSAGES/django.po b/rest_framework/locale/pl/LC_MESSAGES/django.po
new file mode 100644
index 00000000..8e51d754
--- /dev/null
+++ b/rest_framework/locale/pl/LC_MESSAGES/django.po
@@ -0,0 +1,326 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Janusz Harkot , 2015
+# Maciek Olko , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Polish (http://www.transifex.com/projects/p/django-rest-framework/language/pl/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: pl\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "Niepoprawny podstawowy nagłówek. Brak danych uwierzytelniających."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "Niepoprawny podstawowy nagłówek. Ciąg znaków danych uwierzytelniających nie powinien zawierać spacji."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "Niepoprawny podstawowy nagłówek. Niewłaściwe kodowanie base64 danych uwierzytelniających."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Niepoprawna nazwa użytkownika lub hasło."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Niepoprawny nagłówek tokena. Brak danych uwierzytelniających."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Niepoprawny nagłówek tokena. Token nie może zawierać odstępów."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Niepoprawny token."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Użytkownik nieaktywny lub usunięty."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Wystąpił błąd serwera."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Zniekształcone żądanie."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Błędne dane uwierzytelniające."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Nie podano danych uwierzytelniających."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "Nie masz uprawnień, by wykonać tę czynność."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Nie znaleziono."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Niedozwolona metoda \"{method}\"."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "Nie można zaspokoić nagłówka Accept żądania."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Brak wsparcia dla żądanego typu danych \"{media_type}\"."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "Żądanie zostało zdławione."
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "To pole jest wymagane."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Pole nie może mieć wartości null."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" nie jest poprawną wartością logiczną."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "To pole nie może być puste."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Upewnij się, że to pole ma nie więcej niż {max_length} znaków."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Upewnij się, że pole ma co najmniej {min_length} znaków."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Podaj poprawny adres e-mail."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Ta wartość nie pasuje do wymaganego wzorca."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Wprowadź poprawną wartość pola typu \"slug\", składającą się ze znaków łacińskich, cyfr, podkreślenia lub myślnika."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Wprowadź poprawny adres URL."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "Wymagana poprawna liczba całkowita."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Upewnij się, że ta wartość jest mniejsza lub równa {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Upewnij się, że ta wartość jest większa lub równa {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "Za długi ciąg znaków."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "Wymagana poprawna liczba."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Upewnij się, że liczba ma nie więcej niż {max_digits} cyfr."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Upewnij się, że liczba ma nie więcej niż {max_decimal_places} cyfr dziesiętnych."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Upewnij się, że liczba ma nie więcej niż {max_whole_digits} cyfr całkowitych."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "Wartość daty z czasem ma zły format. Użyj jednego z dostępnych formatów: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Oczekiwano datę z czasem, otrzymano tylko datę."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "Data ma zły format. Użyj jednego z tych formatów: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Oczekiwano daty a otrzymano datę z czasem."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Błędny format czasu. Użyj jednego z dostępnych formatów: {format}"
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" nie jest poprawnym wyborem."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Oczekiwano listy elementów, a otrzymano dane typu \"{input_type}\"."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "Nie przesłano pliku."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "Przesłane dane nie były plikiem. Sprawdź typ kodowania formatki."
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "Nie można określić nazwy pliku."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "Przesłany plik jest pusty."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "Upewnij się, że nazwa pliku ma długość co najwyżej {max_length} znaków (aktualnie ma {length})."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "Prześlij poprawny plik graficzny. Przesłany plik albo nie jest grafiką lub jest uszkodzony."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "Niepoprawna strona \"{page_number}\": {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "Błędny klucz główny \"{pk_value}\" - obiekt nie istnieje."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "Błędny typ danych. Oczekiwano wartość klucza głównego, otrzymano {data_type}."
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "Błędny hyperlink - nie znaleziono pasującego adresu URL."
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "Błędny hyperlink - błędne dopasowanie adresu URL."
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "Błędny hyperlink - obiekt nie istnieje."
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "Błędny typ danych. Oczekiwano adresu URL, otrzymano {data_type}"
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "Obiekt z polem {slug_name}={value} nie istnieje"
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Niepoprawna wartość."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "Niepoprawne dane. Oczekiwano słownika, otrzymano {datatype}."
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "Wartość dla tego pola musi być unikalna."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr "Pola {field_names} muszą tworzyć unikalny zestaw."
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr "To pole musi mieć unikalną wartość dla jednej daty z pola \"{date_field}\"."
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr "To pole musi mieć unikalną wartość dla konkretnego miesiąca z pola \"{date_field}\"."
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr "To pole musi mieć unikalną wartość dla konkretnego roku z pola \"{date_field}\"."
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr "Błędna wersja w nagłówku \"Accept\"."
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr "Błędna wersja w ścieżce URL."
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr "Błędna wersja w nazwie hosta."
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr "Błędna wersja w parametrach zapytania."
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "Konto użytkownika jest nieaktywne."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "Podane dane uwierzytelniające nie pozwalają na zalogowanie."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "Musi zawierać \"username\" i \"password\"."
diff --git a/rest_framework/locale/pt_BR/LC_MESSAGES/django.mo b/rest_framework/locale/pt_BR/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..49f01929
Binary files /dev/null and b/rest_framework/locale/pt_BR/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/pt_BR/LC_MESSAGES/django.po b/rest_framework/locale/pt_BR/LC_MESSAGES/django.po
new file mode 100644
index 00000000..3f272f71
--- /dev/null
+++ b/rest_framework/locale/pt_BR/LC_MESSAGES/django.po
@@ -0,0 +1,326 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Craig Blaszczyk , 2015
+# Filipe Rinaldi , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/django-rest-framework/language/pt_BR/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: pt_BR\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "Cabeçalho básico inválido. Credenciais não fornecidas."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "Cabeçalho básico inválido. String de credenciais não deve incluir espaços."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "Cabeçalho básico inválido. Credenciais codificadas em base64 incorretamente."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Usário ou senha inválido."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Cabeçalho de token inválido. Credenciais não fornecidas."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Cabeçalho de token inválido. String de token não deve incluir espaços."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Token inválido."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Usuário inativo ou removido."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Ocorreu um erro de servidor."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Pedido malformado."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Credenciais de autenticação incorretas."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "As credenciais de autenticação não foram fornecidas."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "Você não tem persmissao para executar essa ação."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Não encontrado."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Método \"{method}\" não é permitido."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "Não foi possível satisfazer a requisição do cabeçalho Accept."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Media type \"{media_type}\" no pedido não é suportado."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "Pedido foi limitado."
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Este campo é obrigatório."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Este campo não pode ser nulo."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" não é um valor boleano válido."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "Este campo não pode ser em branco."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Certifique-se de que este campo não tenha mais de {max_length} caracteres."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Certifique-se de que este campo tenha mais de {min_length} caracteres."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Insira um endereço de email válido."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Este valor não corresponde ao padrão exigido."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Entrar um \"slug\" válido que consista de letras, números, sublinhados ou hífens."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Entrar um URL válido."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "Um número inteiro válido é exigido."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Certifique-se de que este valor seja inferior ou igual a {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Certifque-se de que este valor seja maior ou igual a {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "Valor da string é muito grande."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "Um número válido é necessário."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Certifique-se de que não haja mais de {max_digits} dígitos no total."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Certifique-se de que não haja mais de {max_decimal_places} casas decimais."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Certifique-se de que não haja mais de {max_whole_digits} dígitos antes do ponto decimal."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "Formato inválido para data e hora. Use um dos formatos a seguir: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Data e hora são necessários mas apenas data foi encontrada."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "Formato inválido para data. Use um dos formatos a seguir: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Necessário uma data mas recebeu uma data e hora."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Tempo tem formato errado. Usa um desses em vez disso: {format}."
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" não é um escolha válido."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Necessário uma lista de itens, mas recebeu tipo \"{input_type}\"."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "Ficheiro não foi submetido."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "Os dados submetidos nao foram um ficheiro. Certifique-se do tipo de codificação no formulário."
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "Nome do arquivo não pode ser determinado."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "O arquivo submetido ésta vázio."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "Certifique-se de que o nome do ficheiro tem menos de {max_length} caracteres (tem {length})."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "Fazer upload de um imagem válido. O arquivo mandou não foi um imagem ou foi corrupto."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "Página inválido \"{page_number}\": {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "Pk inválido \"{pk_value}\" - objeto não existe."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "Tipo incorreto. Necessário valor pk, recebeu {data_type}."
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "Hyperlink inválido - URL não combinou."
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "Hyperlink inválido - URL combinou errado."
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "Hyperlink inválido - objeto não existe."
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "Tipo incorreto. Necessário string URL, recebeu {data_type}."
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "Objeto com {slug_name}={value} não existe."
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Valor inválido."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "Data inválido. Necessário um dicionário mas recebeu {datatype}."
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "Esse campo deve ser unico."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr "Os campos {field_names} devem criar um set unico."
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr "O campo deve ser unico pela data \"{date_field}\"."
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr "O campo deve ser unico pelo anô \"{date_field}\"."
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr "O campo deve ser unico pela mês \"{date_field}\"."
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr "Versão inválido no cabeçalho \"Accept\"."
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr "Versão inválido no caminho de URL."
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr "Versão inválido no hostname."
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr "Versão inválida no parâmetro de query."
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "Conta de usário desabilitada."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "Impossível fazer login com as credenciais fornecidas."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "Obrigatório incluir \"usuário\" e \"senha\"."
diff --git a/rest_framework/locale/ru/LC_MESSAGES/django.mo b/rest_framework/locale/ru/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..d1555f1f
Binary files /dev/null and b/rest_framework/locale/ru/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/ru/LC_MESSAGES/django.po b/rest_framework/locale/ru/LC_MESSAGES/django.po
new file mode 100644
index 00000000..38489747
--- /dev/null
+++ b/rest_framework/locale/ru/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Mikhail Dmitriev , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Russian (http://www.transifex.com/projects/p/django-rest-framework/language/ru/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: ru\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
+msgid "Invalid basic header. No credentials provided."
+msgstr "Недопустимый заголовок. Не предоставлены учетные данные."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "Недопустимый заголовок. Учетные данные не должны содержать пробелов."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "Недопустимый заголовок. Учетные данные некорректно закодированны в base64."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Недопустимые имя пользователя или пароль."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Недопустимый заголовок токена. Не предоставлены учетные данные."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Недопустимый заголовок токена. Токен не должен содержать пробелов."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Недопустимый токен."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Пользователь неактивен или удален."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Произошла ошибка сервера."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Искаженный запрос."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Некорректные учетные данные."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Учетные данные не были предоставлены."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "У вас нет прав для выполнения этой операции."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Не найдено."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Метод \"{method}\" не разрешен."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "Невозможно удовлетворить \"Accept\" заголовок запроса."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Неподдерживаемый тип данных \"{media_type}\" в запросе."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "Запрос был проигнорирован."
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Это поле обязательно."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Это поле не может быть null."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" не является корректным булевым значением."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "Это поле не может быть пустым."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Убедитесь что в этом поле не больше {max_length} символов."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Убедитесь что в этом поле как минимум {min_length} символов."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Введите корректный адрес электронной почты."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Значение не соответствует требуемому паттерну."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Введите корректный \"slug\", состоящий из букв, цифр, знаков подчеркивания или дефисов."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Введите корректный URL."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "Требуется целочисленное значение."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Убедитесь что значение меньше или равно {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Убедитесь что значение больше или равно {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "Слишком длинное значение."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "Требуется численное значение."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Убедитесь что в числе не больше {max_digits} знаков."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Убедитесь что в числе не больше {max_decimal_places} знаков в дробной части."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Убедитесь что в цисле не больше {max_whole_digits} знаков в целой части."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "Неправильный формат datetime. Используйте один из этих форматов: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Ожидался datetime, но был получен date."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "Неправильный формат date. Используйте один из этих форматов: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Ожидался date, но был получен datetime."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Неправильный формат времени. Используйте один из этих форматов: {format}."
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" не является корректным значением."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Ожидался list со значениями, но был получен \"{input_type}\"."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "Не был загружен файл."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "Загруженный файл не является корректным файлом. "
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "Невозможно определить имя файла."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "Загруженный файл пуст."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "Убедитесь что имя файла меньше {max_length} символов (сейчас {length})."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "Загрузите корректное изображение. Загруженный файл не является изображением, либо является испорченным."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "Недопустимая страница \"{page_number}\": {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "Недопустимый первичный ключ \"{pk_value}\" - объект не существует."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "Некорректный тип. Ожилалось значение первичного ключа, получен {data_type}."
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "Недопустимая ссылка - нет совпадения по URL."
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "Недопустимая ссылка - некорректное совпадение по URL,"
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "Недопустимая ссылка - объект не существует."
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "Некорректный тип. Ожидался URL, получен {data_type}."
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "Объект с {slug_name}={value} не существует."
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Недопустимое значение."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "Недопустимые данные. Ожидался dictionary, но был получен {datatype}."
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr ""
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr ""
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr ""
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr ""
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr ""
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr ""
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr ""
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr ""
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr ""
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "Учетная запись пользователя отключена."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "Невозможно войти с предоставленными учетными данными."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "Должен включать \"username\" и \"password\"."
diff --git a/rest_framework/locale/sk/LC_MESSAGES/django.mo b/rest_framework/locale/sk/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..53bb95d8
Binary files /dev/null and b/rest_framework/locale/sk/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/sk/LC_MESSAGES/django.po b/rest_framework/locale/sk/LC_MESSAGES/django.po
new file mode 100644
index 00000000..9dd378c0
--- /dev/null
+++ b/rest_framework/locale/sk/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Stanislav Komanec , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Slovak (http://www.transifex.com/projects/p/django-rest-framework/language/sk/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: sk\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "Nesprávna hlavička. Neboli poskytnuté prihlasovacie údaje."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "Nesprávna hlavička. Prihlasovacie údaje nesmú obsahovať medzery."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "Nesprávna hlavička. Prihlasovacie údaje nie sú správne zakódované pomocou metódy base64."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Nesprávne prihlasovacie údaje."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Nesprávna token hlavička. Neboli poskytnuté prihlasovacie údaje."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Nesprávna token hlavička. Token hlavička nesmie obsahovať medzery."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Nesprávny token."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Daný používateľ je neaktívny, alebo zmazaný."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Vyskytla sa chyba na strane servera."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Požiadavok má nesprávny formát, alebo je poškodený."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Nesprávne prihlasovacie údaje."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Prihlasovacie údaje neboli zadané."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "K danej akcii nemáte oprávnenie."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Nebolo nájdené."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Metóda \"{method}\" nie je povolená."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "Nie je možné vyhovieť požiadavku v hlavičke \"Accept\"."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Požiadavok obsahuje nepodporovaný media type: \"{media_type}\"."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr ""
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr ""
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr ""
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr ""
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr ""
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr ""
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr ""
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr ""
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr ""
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr ""
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr ""
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr ""
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr ""
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr ""
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr ""
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr ""
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr ""
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr ""
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr ""
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr ""
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr ""
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr ""
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr ""
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr ""
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr ""
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr ""
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr ""
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr ""
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr ""
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr ""
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr ""
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr ""
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr ""
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr ""
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr ""
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr ""
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr ""
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr ""
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr ""
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr ""
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr ""
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr ""
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr ""
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr ""
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr ""
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr ""
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr ""
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "Daný používateľ je zablokovaný."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "S danými prihlasovacími údajmi nebolo možné sa prihlásiť."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "Musí obsahovať parametre \"používateľské meno\" a \"heslo\"."
diff --git a/rest_framework/locale/sv/LC_MESSAGES/django.mo b/rest_framework/locale/sv/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..a33b0cc5
Binary files /dev/null and b/rest_framework/locale/sv/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/sv/LC_MESSAGES/django.po b/rest_framework/locale/sv/LC_MESSAGES/django.po
new file mode 100644
index 00000000..1602bf55
--- /dev/null
+++ b/rest_framework/locale/sv/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Joakim Soderlund, 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Swedish (http://www.transifex.com/projects/p/django-rest-framework/language/sv/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: sv\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "Ogiltig \"basic\"-header. Inga användaruppgifter tillhandahölls."
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "Ogiltig \"basic\"-header. Strängen för användaruppgifterna ska inte innehålla mellanslag."
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "Ogiltig \"basic\"-header. Användaruppgifterna är inte korrekt base64-kodade."
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Ogiltigt användarnamn/lösenord."
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Ogiltig \"token\"-header. Inga användaruppgifter tillhandahölls."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Ogiltig \"token\"-header. Strängen för referensen ska inte innehålla mellanslag."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Ogiltig \"token\"."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Användaren borttagen eller inaktiv."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Ett serverfel inträffade."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Ogiltig förfrågan."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Ogiltiga inloggningsuppgifter. "
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Autentiseringsuppgifter ej tillhandahållna."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "Du har inte tillåtelse att utföra denna förfrågan."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Hittades inte."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "Metoden \"{method}\" tillåts inte."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "Kunde inte tillfredsställa förfrågans \"Accept\"-header."
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "Medietypen \"{media_type}\" stöds inte."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "Förfrågan stoppades eftersom du har skickat för många."
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Det här fältet är obligatoriskt."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Det här fältet får inte vara null."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" är inte ett giltigt booleskt värde."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "Det här fältet får inte vara blankt."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Se till att detta fält inte har fler än {max_length} tecken."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Se till att detta fält har minst {min_length} tecken."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Ange en giltig mejladress."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Det här värdet matchar inte mallen."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Ange en giltig \"slug\" bestående av bokstäver, nummer, understreck eller bindestreck."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Ange en giltig URL."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "Ett giltigt heltal krävs."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Se till att detta värde är mindre än eller lika med {max_value}."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Se till att detta värde är större än eller lika med {min_value}."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "Textvärdet är för långt."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "Ett giltigt nummer krävs."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Se till att det inte finns fler än totalt {max_digits} siffror."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Se till att det inte finns fler än {max_decimal_places} decimaler."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Se till att det inte finns fler än {max_whole_digits} siffror före decimalpunkten."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "Datumtiden har fel format. Använd ett av dessa format istället: {format}."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Förväntade en datumtid men fick ett datum."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "Datumet har fel format. Använde ett av dessa format istället: {format}."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Förväntade ett datum men fick en datumtid."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Tiden har fel format. Använd ett av dessa format istället: {format}."
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" är inte ett giltigt val."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Förväntade en lista med element men fick typen \"{input_type}\"."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "Ingen fil skickades."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "Den skickade informationen var inte en fil. Kontrollera formulärets kodningstyp."
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "Inget filnamn kunde bestämmas."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "Den skickade filen var tom."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "Se till att det här filnamnet har högst {max_length} tecken (det har {length})."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "Ladda upp en giltig bild. Filen du laddade upp var antingen inte en bild eller en skadad bild."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "Ogiltigt sida \"{page_number}\": {message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "Ogiltigt pk \"{pk_value}\" - Objektet finns inte."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "Felaktig typ. Förväntade pk-värde, fick {data_type}."
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "Ogiltig hyperlänk - Ingen URL matchade."
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "Ogiltig hyperlänk - Felaktig URL-matching."
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "Ogiltig hyperlänk - Objektet finns inte."
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "Felaktig typ. Förväntade URL-sträng, fick {data_type}."
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "Objekt med {slug_name}={value} finns inte."
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Ogiltigt värde."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "Ogiltig data. Förväntade en dictionary, men fick {datatype}."
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "Det här fältet måste vara unikt."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr "Fälten {field_names} måste skapa ett unikt set."
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr "Det här fältet måste vara unikt för datumet \"{date_field}\"."
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr "Det här fältet måste vara unikt för månaden \"{date_field}\"."
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr "Det här fältet måste vara unikt för året \"{date_field}\"."
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr "Ogiltig version i \"Accept\"-headern."
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr "Ogiltig version i URL-resursen."
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr "Ogiltig version i värdnamnet."
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr "Ogiltig version i förfrågningsparametern."
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "Användarkontot är borttaget."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "Kunde inte logga in med de angivna inloggningsuppgifterna."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "Användarnamn och lösenord måste anges."
diff --git a/rest_framework/locale/tr/LC_MESSAGES/django.mo b/rest_framework/locale/tr/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..e6b848cf
Binary files /dev/null and b/rest_framework/locale/tr/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/tr/LC_MESSAGES/django.po b/rest_framework/locale/tr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..5aabbeba
--- /dev/null
+++ b/rest_framework/locale/tr/LC_MESSAGES/django.po
@@ -0,0 +1,328 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Ertaç Paprat , 2015
+# Mesut Can Gürle , 2015
+# Recep KIRMIZI , 2015
+# Ülgen Sarıkavak , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Turkish (http://www.transifex.com/projects/p/django-rest-framework/language/tr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: tr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr ""
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr ""
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr ""
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "Geçersiz kullanıcı adı/parola"
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "Geçersiz token başlığı. Kimlik bilgileri eksik."
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "Geçersiz token başlığı. Token'da boşluk olmamalı."
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "Geçersiz token."
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "Kullanıcı aktif değil ya da silinmiş."
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "Sunucu hatası oluştu."
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "Bozuk istek."
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "Giriş bilgileri hatalı."
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "Giriş bilgileri verilmedi."
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "Bu işlemi yapmak için izniniz bulunmuyor."
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "Bulunamadı."
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "\"{method}\" metoduna izin verilmiyor."
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr ""
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "İstekte desteklenmeyen medya tipi: \"{media_type}\"."
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr ""
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "Bu alan zorunlu."
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "Bu alan boş bırakılmamalı."
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "\"{input}\" geçerli bir boolean değil."
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "Bu alan boş bırakılmamalı."
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "Bu alanın {max_length} karakterden fazla karakter barındırmadığından emin olun."
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "Bu alanın en az {min_length} karakter barındırdığından emin olun."
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "Geçerli bir e-posta adresi girin."
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "Bu değer gereken düzenli ifade deseni ile uyuşmuyor."
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "Harf, rakam, altçizgi veya tireden oluşan geçerli bir \"slug\" giriniz."
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "Geçerli bir URL girin."
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "Geçerli bir tam sayı girin."
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "Değerin {max_value} değerinden küçük ya da eşit olduğundan emin olun."
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "Değerin {min_value} değerinden büyük ya da eşit olduğundan emin olun."
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "String değeri çok uzun."
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "Geçerli bir numara gerekiyor."
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "Toplamda {max_digits} haneden fazla hane olmadığından emin olun."
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "Ondalık basamak değerinin {max_decimal_places} haneden fazla olmadığından emin olun."
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "Ondalık ayracından önce {max_whole_digits} basamaktan fazla olmadığından emin olun."
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "Datetime alanı yanlış biçimde. {format} biçimlerinden birini kullanın."
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "Datetime değeri bekleniyor, ama date değeri geldi."
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "Tarih biçimi yanlış. {format} biçimlerinden birini kullanın."
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "Date tipi beklenmekteydi, fakat datetime tipi geldi."
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "Time biçimi yanlış. {format} biçimlerinden birini kullanın."
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "\"{input}\" geçerli bir seçim değil."
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "Elemanların listesi beklenirken \"{input_type}\" alındı."
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "Hiçbir dosya verilmedi."
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "Gönderilen veri dosya değil. Formdaki kodlama tipini kontrol edin."
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "Hiçbir dosya adı belirlenemedi."
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "Gönderilen dosya boş."
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "Bu dosya adının en fazla {max_length} karakter uzunluğunda olduğundan emin olun. (şu anda {length} karakter)."
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "Geçerli bir resim yükleyin. Yüklediğiniz dosya resim değil ya da bozuk."
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "Geçersiz sayfa \"{page_number}\":{message}."
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "Geçersiz pk \"{pk_value}\" - obje bulunamadı."
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "Hatalı tip. Pk değeri beklenirken, alınan {data_type}."
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "Geçersiz bağlantı - Hiçbir URL eşleşmedi."
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "Geçersiz bağlantı - Yanlış URL eşleşmesi."
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "Geçersiz bağlantı - Obje bulunamadı."
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "Hatalı tip. URL metni bekleniyor, {data_type} alındı."
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "{slug_name}={value} değerini taşıyan obje bulunamadı."
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "Geçersiz değer."
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "Geçersiz veri. Sözlük bekleniyordu fakat {datatype} geldi. "
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "Bu alan eşsiz olmalı."
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr "{field_names} hep birlikte eşsiz bir küme oluşturmalılar."
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr "Bu alan \"{date_field}\" tarihine göre eşsiz olmalı."
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr "Bu alan \"{date_field}\" ayına göre eşsiz olmalı."
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr "Bu alan \"{date_field}\" yılına göre eşsiz olmalı."
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr "\"Accept\" başlığındaki sürüm geçersiz."
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr "URL dizininde geçersiz versiyon."
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr "Host adında geçersiz versiyon."
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr "Sorgu parametresinde geçersiz versiyon."
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "Kullanıcı hesabı devre dışı bırakılmış."
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "Verilen bilgiler ile giriş sağlanamadı."
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "\"Kullanıcı Adı\" ve \"Parola\" eklenmeli."
diff --git a/rest_framework/locale/uk/LC_MESSAGES/django.mo b/rest_framework/locale/uk/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..fc335054
Binary files /dev/null and b/rest_framework/locale/uk/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/uk/LC_MESSAGES/django.po b/rest_framework/locale/uk/LC_MESSAGES/django.po
new file mode 100644
index 00000000..93fc2bf9
--- /dev/null
+++ b/rest_framework/locale/uk/LC_MESSAGES/django.po
@@ -0,0 +1,324 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Ukrainian (http://www.transifex.com/projects/p/django-rest-framework/language/uk/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\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"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr ""
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr ""
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr ""
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr ""
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr ""
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr ""
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr ""
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr ""
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr ""
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr ""
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr ""
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr ""
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr ""
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr ""
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr ""
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr ""
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr ""
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr ""
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr ""
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr ""
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr ""
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr ""
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr ""
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr ""
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr ""
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr ""
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr ""
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr ""
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr ""
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr ""
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr ""
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr ""
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr ""
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr ""
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr ""
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr ""
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr ""
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr ""
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr ""
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr ""
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr ""
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr ""
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr ""
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr ""
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr ""
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr ""
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr ""
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr ""
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr ""
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr ""
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr ""
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr ""
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr ""
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr ""
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr ""
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr ""
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr ""
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr ""
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr ""
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr ""
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr ""
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr ""
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr ""
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr ""
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr ""
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr ""
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr ""
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr ""
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr ""
diff --git a/rest_framework/locale/zh_CN/LC_MESSAGES/django.mo b/rest_framework/locale/zh_CN/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..6e7073bd
Binary files /dev/null and b/rest_framework/locale/zh_CN/LC_MESSAGES/django.mo differ
diff --git a/rest_framework/locale/zh_CN/LC_MESSAGES/django.po b/rest_framework/locale/zh_CN/LC_MESSAGES/django.po
new file mode 100644
index 00000000..01128859
--- /dev/null
+++ b/rest_framework/locale/zh_CN/LC_MESSAGES/django.po
@@ -0,0 +1,325 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Lele Long , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: Django REST framework\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-30 16:23+0000\n"
+"PO-Revision-Date: 2015-01-30 16:27+0000\n"
+"Last-Translator: Thomas Christie \n"
+"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/django-rest-framework/language/zh_CN/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: zh_CN\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: authentication.py:69
+msgid "Invalid basic header. No credentials provided."
+msgstr "没有提供认证信息(基本认证HTTP头无效)。"
+
+#: authentication.py:72
+msgid "Invalid basic header. Credentials string should not contain spaces."
+msgstr "认证字符串不应该包含空格(基本认证HTTP头无效)。"
+
+#: authentication.py:78
+msgid "Invalid basic header. Credentials not correctly base64 encoded."
+msgstr "认证字符串base64编码错误(基本认证HTTP头无效)。"
+
+#: authentication.py:90
+msgid "Invalid username/password."
+msgstr "用户名或者密码错误。"
+
+#: authentication.py:156
+msgid "Invalid token header. No credentials provided."
+msgstr "没有提供认证信息(认证令牌HTTP头无效)。"
+
+#: authentication.py:159
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr "认证令牌字符串不应该包含空格(无效的认证令牌HTTP头)。"
+
+#: authentication.py:168
+msgid "Invalid token."
+msgstr "认证令牌无效。"
+
+#: authentication.py:171
+msgid "User inactive or deleted."
+msgstr "用户未激活或者已删除。"
+
+#: exceptions.py:38
+msgid "A server error occurred."
+msgstr "服务器出现了错误。"
+
+#: exceptions.py:73
+msgid "Malformed request."
+msgstr "畸形的请求。"
+
+#: exceptions.py:78
+msgid "Incorrect authentication credentials."
+msgstr "不正确的身份认证凭据。"
+
+#: exceptions.py:83
+msgid "Authentication credentials were not provided."
+msgstr "身份认证凭据未提供。"
+
+#: exceptions.py:88
+msgid "You do not have permission to perform this action."
+msgstr "您没有执行该操作的权限。"
+
+#: exceptions.py:93
+msgid "Not found."
+msgstr "未找到。"
+
+#: exceptions.py:98
+msgid "Method \"{method}\" not allowed."
+msgstr "方法 “{method}” 不被允许。"
+
+#: exceptions.py:109
+msgid "Could not satisfy the request Accept header."
+msgstr "无法满足Accept HTTP头的请求。"
+
+#: exceptions.py:121
+msgid "Unsupported media type \"{media_type}\" in request."
+msgstr "不支持请求中的媒体类型 “{media_type}”。"
+
+#: exceptions.py:134
+msgid "Request was throttled."
+msgstr "请求被限速。"
+
+#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
+#: validators.py:155
+msgid "This field is required."
+msgstr "这个字段是必填项。"
+
+#: fields.py:154
+msgid "This field may not be null."
+msgstr "这个值不能为 null。"
+
+#: fields.py:487 fields.py:515
+msgid "\"{input}\" is not a valid boolean."
+msgstr "“{input}” 不是合法的布尔值。"
+
+#: fields.py:550
+msgid "This field may not be blank."
+msgstr "此字段不能为空。"
+
+#: fields.py:551 fields.py:1324
+msgid "Ensure this field has no more than {max_length} characters."
+msgstr "请确保这个字段不能超过 {max_length} 个字符。"
+
+#: fields.py:552
+msgid "Ensure this field has at least {min_length} characters."
+msgstr "请确保这个字段至少包含 {min_length} 个字符。"
+
+#: fields.py:587
+msgid "Enter a valid email address."
+msgstr "请输入合法的邮件地址。"
+
+#: fields.py:604
+msgid "This value does not match the required pattern."
+msgstr "输入值不匹配要求的模式。"
+
+#: fields.py:615
+msgid ""
+"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
+"hyphens."
+msgstr "请输入合法的“短语“,只能包含字母,数字,下划线或者中划线。"
+
+#: fields.py:627
+msgid "Enter a valid URL."
+msgstr "请输入合法的URL。"
+
+#: fields.py:638
+msgid "\"{value}\" is not a valid UUID."
+msgstr ""
+
+#: fields.py:657
+msgid "A valid integer is required."
+msgstr "请填写合法的整数值。"
+
+#: fields.py:658 fields.py:692 fields.py:725
+msgid "Ensure this value is less than or equal to {max_value}."
+msgstr "请确保该值小于或者等于 {max_value}。"
+
+#: fields.py:659 fields.py:693 fields.py:726
+msgid "Ensure this value is greater than or equal to {min_value}."
+msgstr "请确保该值大于或者等于 {min_value}。"
+
+#: fields.py:660 fields.py:694 fields.py:730
+msgid "String value too large."
+msgstr "字符值太长。"
+
+#: fields.py:691 fields.py:724
+msgid "A valid number is required."
+msgstr "请填写合法的数字。"
+
+#: fields.py:727
+msgid "Ensure that there are no more than {max_digits} digits in total."
+msgstr "请确保总计不超过 {max_digits} 个数字。"
+
+#: fields.py:728
+msgid ""
+"Ensure that there are no more than {max_decimal_places} decimal places."
+msgstr "请确保总计不超过 {max_decimal_places} 个小数位。"
+
+#: fields.py:729
+msgid ""
+"Ensure that there are no more than {max_whole_digits} digits before the "
+"decimal point."
+msgstr "请确保小数点前不超过 {max_whole_digits} 个数字。"
+
+#: fields.py:813
+msgid "Datetime has wrong format. Use one of these formats instead: {format}."
+msgstr "日期时间格式错误。请从这些格式中选择:{format}。"
+
+#: fields.py:814
+msgid "Expected a datetime but got a date."
+msgstr "期望为日期时间,得到的是日期。"
+
+#: fields.py:878
+msgid "Date has wrong format. Use one of these formats instead: {format}."
+msgstr "日期格式错误。请从这些格式中选择:{format}。"
+
+#: fields.py:879
+msgid "Expected a date but got a datetime."
+msgstr "期望为日期,得到的是日期时间。"
+
+#: fields.py:936
+msgid "Time has wrong format. Use one of these formats instead: {format}."
+msgstr "时间格式错误。请从这些格式中选择:{format}。"
+
+#: fields.py:992 fields.py:1036
+msgid "\"{input}\" is not a valid choice."
+msgstr "“{input}” 不是合法选项。"
+
+#: fields.py:1037 fields.py:1151 serializers.py:482
+msgid "Expected a list of items but got type \"{input_type}\"."
+msgstr "期望为一个包含物件的列表,得到的类型是“{input_type}”。"
+
+#: fields.py:1067
+msgid "No file was submitted."
+msgstr "没有提交任何文件。"
+
+#: fields.py:1068
+msgid ""
+"The submitted data was not a file. Check the encoding type on the form."
+msgstr "提交的数据不是一个文件。请检查表单的编码类型。"
+
+#: fields.py:1069
+msgid "No filename could be determined."
+msgstr "无法检测到文件名。"
+
+#: fields.py:1070
+msgid "The submitted file is empty."
+msgstr "提交的是空文件。"
+
+#: fields.py:1071
+msgid ""
+"Ensure this filename has at most {max_length} characters (it has {length})."
+msgstr "确保该文件名最多包含 {max_length} 个字符 ( 当前长度为{length} ) 。"
+
+#: fields.py:1113
+msgid ""
+"Upload a valid image. The file you uploaded was either not an image or a "
+"corrupted image."
+msgstr "请上传有效图片。您上传的该文件不是图片或者图片已经损坏。"
+
+#: fields.py:1188
+msgid "Expected a dictionary of items but got type \"{input_type}\"."
+msgstr ""
+
+#: pagination.py:221
+msgid "Invalid page \"{page_number}\": {message}."
+msgstr "无效页面 “{page_number}”:{message}。"
+
+#: pagination.py:442
+msgid "Invalid cursor"
+msgstr ""
+
+#: relations.py:133
+msgid "Invalid pk \"{pk_value}\" - object does not exist."
+msgstr "无效主键 “{pk_value}” - 对象不存在。"
+
+#: relations.py:134
+msgid "Incorrect type. Expected pk value, received {data_type}."
+msgstr "类型错误。期望为主键,得到的类型为 {data_type}。"
+
+#: relations.py:157
+msgid "Invalid hyperlink - No URL match."
+msgstr "无效超链接 -没有匹配的URL。"
+
+#: relations.py:158
+msgid "Invalid hyperlink - Incorrect URL match."
+msgstr "无效超链接 -错误的URL匹配。"
+
+#: relations.py:159
+msgid "Invalid hyperlink - Object does not exist."
+msgstr "无效超链接 -对象不存在。"
+
+#: relations.py:160
+msgid "Incorrect type. Expected URL string, received {data_type}."
+msgstr "类型错误。期望为URL字符串,得到的类型是 {data_type}。"
+
+#: relations.py:295
+msgid "Object with {slug_name}={value} does not exist."
+msgstr "属性 {slug_name} 为 {value} 的对象不存在。"
+
+#: relations.py:296
+msgid "Invalid value."
+msgstr "无效值。"
+
+#: serializers.py:299
+msgid "Invalid data. Expected a dictionary, but got {datatype}."
+msgstr "无效数据。期待为字典类型,得到的是 {datatype} 。"
+
+#: validators.py:22
+msgid "This field must be unique."
+msgstr "该字段必须唯一。"
+
+#: validators.py:76
+msgid "The fields {field_names} must make a unique set."
+msgstr "字段 {field_names} 必须能构成唯一集合。"
+
+#: validators.py:219
+msgid "This field must be unique for the \"{date_field}\" date."
+msgstr "该字段必须在日期 “{date_field}” 唯一。"
+
+#: validators.py:234
+msgid "This field must be unique for the \"{date_field}\" month."
+msgstr "该字段必须在月份 “{date_field}” 唯一。"
+
+#: validators.py:247
+msgid "This field must be unique for the \"{date_field}\" year."
+msgstr "该字段必须在年 “{date_field}” 唯一。"
+
+#: versioning.py:39
+msgid "Invalid version in \"Accept\" header."
+msgstr "“Accept” HTTP头包含无效版本。"
+
+#: versioning.py:70 versioning.py:112
+msgid "Invalid version in URL path."
+msgstr "URl路径包含无效版本。"
+
+#: versioning.py:138
+msgid "Invalid version in hostname."
+msgstr "主机名包含无效版本。"
+
+#: versioning.py:160
+msgid "Invalid version in query parameter."
+msgstr "请求参数里包含无效版本。"
+
+#: authtoken/serializers.py:20
+msgid "User account is disabled."
+msgstr "用户账户已禁用。"
+
+#: authtoken/serializers.py:23
+msgid "Unable to log in with provided credentials."
+msgstr "无法使用提供的认证信息登录。"
+
+#: authtoken/serializers.py:26
+msgid "Must include \"username\" and \"password\"."
+msgstr "必须包含 “用户名” 和 “密码”。"
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 12bb78bd..995ddd0f 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -2,12 +2,10 @@
Provides an APIView class that is the base of all views in REST framework.
"""
from __future__ import unicode_literals
-import inspect
-import warnings
-
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.utils.encoding import smart_text
+from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from rest_framework import status, exceptions
from rest_framework.compat import HttpResponseBase, View
@@ -15,6 +13,8 @@ from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.utils import formatting
+import inspect
+import warnings
def get_view_name(view_cls, suffix=None):
@@ -74,11 +74,11 @@ def exception_handler(exc, context):
return Response(data, status=exc.status_code, headers=headers)
elif isinstance(exc, Http404):
- data = {'detail': 'Not found'}
+ data = {'detail': _('Not found.')}
return Response(data, status=status.HTTP_404_NOT_FOUND)
elif isinstance(exc, PermissionDenied):
- data = {'detail': 'Permission denied'}
+ data = {'detail': _('Permission denied.')}
return Response(data, status=status.HTTP_403_FORBIDDEN)
# Note: Unhandled exceptions will raise a 500 error.
--
cgit v1.2.3
From 2cc4cb24652366c6622af08370a0c04b429aa4b8 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sat, 31 Jan 2015 08:53:40 +0000
Subject: Fix error text in test.
---
docs/topics/3.1-announcement.md | 4 +++-
rest_framework/views.py | 7 +++++--
tests/test_generics.py | 2 +-
3 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/docs/topics/3.1-announcement.md b/docs/topics/3.1-announcement.md
index 3eb52f4c..5f4a8d45 100644
--- a/docs/topics/3.1-announcement.md
+++ b/docs/topics/3.1-announcement.md
@@ -4,7 +4,7 @@
#### Pagination controls in the browsable API.
-#### New pagination schemes.
+#### New schemes, including cursor pagination.
#### Support for header-based pagination.
@@ -12,4 +12,6 @@
## Internationalization
+## New fields
+
## ModelSerializer API
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 995ddd0f..9445c840 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -4,6 +4,7 @@ Provides an APIView class that is the base of all views in REST framework.
from __future__ import unicode_literals
from django.core.exceptions import PermissionDenied
from django.http import Http404
+from django.utils import six
from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
@@ -74,11 +75,13 @@ def exception_handler(exc, context):
return Response(data, status=exc.status_code, headers=headers)
elif isinstance(exc, Http404):
- data = {'detail': _('Not found.')}
+ msg = _('Not found.')
+ data = {'detail': six.text_type(msg)}
return Response(data, status=status.HTTP_404_NOT_FOUND)
elif isinstance(exc, PermissionDenied):
- data = {'detail': _('Permission denied.')}
+ msg = _('Permission denied.')
+ data = {'detail': six.text_type(msg)}
return Response(data, status=status.HTTP_403_FORBIDDEN)
# Note: Unhandled exceptions will raise a 500 error.
diff --git a/tests/test_generics.py b/tests/test_generics.py
index fba8718f..88e792ce 100644
--- a/tests/test_generics.py
+++ b/tests/test_generics.py
@@ -483,7 +483,7 @@ class TestFilterBackendAppliedToViews(TestCase):
request = factory.get('/1')
response = instance_view(request, pk=1).render()
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
- self.assertEqual(response.data, {'detail': 'Not found'})
+ self.assertEqual(response.data, {'detail': 'Not found.'})
def test_get_instance_view_will_return_single_object_when_filter_does_not_exclude_it(self):
"""
--
cgit v1.2.3
From e63f49bd1d55501f766ca2e3f9c0c9fa3cfa19ab Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Sat, 31 Jan 2015 19:59:52 +0000
Subject: Fix field mappings for 1.8 fields
---
rest_framework/serializers.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index a3b8196b..a91fe23e 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -1330,13 +1330,13 @@ class ModelSerializer(Serializer):
if hasattr(models, 'UUIDField'):
- ModelSerializer._field_mapping[models.UUIDField] = UUIDField
+ ModelSerializer.serializer_field_mapping[models.UUIDField] = UUIDField
if postgres_fields:
class CharMappingField(DictField):
child = CharField()
- ModelSerializer._field_mapping[postgres_fields.HStoreField] = CharMappingField
+ ModelSerializer.serializer_field_mapping[postgres_fields.HStoreField] = CharMappingField
class HyperlinkedModelSerializer(ModelSerializer):
--
cgit v1.2.3
From 37dce89354ab2c94fefeb0a20b6265fef98caddc Mon Sep 17 00:00:00 2001
From: José Padilla
Date: Sun, 1 Feb 2015 15:33:34 -0400
Subject: Add support for Django 1.8’s ArrayField
---
rest_framework/serializers.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index a91fe23e..520b9774 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -1337,6 +1337,7 @@ if postgres_fields:
child = CharField()
ModelSerializer.serializer_field_mapping[postgres_fields.HStoreField] = CharMappingField
+ ModelSerializer.serializer_field_mapping[postgres_fields.ArrayField] = ListField
class HyperlinkedModelSerializer(ModelSerializer):
--
cgit v1.2.3
From 77d061d234e03004f34058028707ecddfc730fae Mon Sep 17 00:00:00 2001
From: Brandon Cazander
Date: Wed, 28 Jan 2015 17:08:34 -0800
Subject: Provide rest_framework.resolve. Fixes #2489
---
rest_framework/relations.py | 7 ++++---
rest_framework/reverse.py | 15 ++++++++++++-
rest_framework/versioning.py | 16 ++++++++++++++
tests/test_relations.py | 50 ++++++++++++++++++++++++++++++++++++++++++--
tests/urls.py | 4 ++--
5 files changed, 84 insertions(+), 8 deletions(-)
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 66857a41..809d3db9 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -1,7 +1,7 @@
# coding: utf-8
from __future__ import unicode_literals
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
-from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch, Resolver404
+from django.core.urlresolvers import get_script_prefix, NoReverseMatch, Resolver404
from django.db.models.query import QuerySet
from django.utils import six
from django.utils.encoding import smart_text
@@ -9,7 +9,7 @@ from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import OrderedDict
from rest_framework.fields import get_attribute, empty, Field
-from rest_framework.reverse import reverse
+from rest_framework.reverse import reverse, resolve
from rest_framework.utils import html
@@ -205,6 +205,7 @@ class HyperlinkedRelatedField(RelatedField):
return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
def to_internal_value(self, data):
+ request = self.context.get('request', None)
try:
http_prefix = data.startswith(('http:', 'https:'))
except AttributeError:
@@ -218,7 +219,7 @@ class HyperlinkedRelatedField(RelatedField):
data = '/' + data[len(prefix):]
try:
- match = self.resolve(data)
+ match = self.resolve(data, request=request)
except Resolver404:
self.fail('no_match')
diff --git a/rest_framework/reverse.py b/rest_framework/reverse.py
index 8fcca55b..0d1d94a7 100644
--- a/rest_framework/reverse.py
+++ b/rest_framework/reverse.py
@@ -1,12 +1,25 @@
"""
-Provide reverse functions that return fully qualified URLs
+Provide urlresolver functions that return fully qualified URLs or view names
"""
from __future__ import unicode_literals
from django.core.urlresolvers import reverse as django_reverse
+from django.core.urlresolvers import resolve as django_resolve
from django.utils import six
from django.utils.functional import lazy
+def resolve(path, urlconf=None, request=None):
+ """
+ If versioning is being used then we pass any `resolve` calls through
+ to the versioning scheme instance, so that the resulting view name
+ can be modified if needed.
+ """
+ scheme = getattr(request, 'versioning_scheme', None)
+ if scheme is not None:
+ return scheme.resolve(path, urlconf, request)
+ return django_resolve(path, urlconf)
+
+
def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra):
"""
If versioning is being used then we pass any `reverse` calls through
diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py
index a07b629f..a76da17a 100644
--- a/rest_framework/versioning.py
+++ b/rest_framework/versioning.py
@@ -1,6 +1,8 @@
# coding: utf-8
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
+from django.core.urlresolvers import resolve as django_resolve
+from django.core.urlresolvers import ResolverMatch
from rest_framework import exceptions
from rest_framework.compat import unicode_http_header
from rest_framework.reverse import _reverse
@@ -24,6 +26,9 @@ class BaseVersioning(object):
def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
return _reverse(viewname, args, kwargs, request, format, **extra)
+ def resolve(self, path, urlconf=None):
+ return django_resolve(path, urlconf)
+
def is_allowed_version(self, version):
if not self.allowed_versions:
return True
@@ -127,6 +132,17 @@ class NamespaceVersioning(BaseVersioning):
viewname, args, kwargs, request, format, **extra
)
+ def resolve(self, path, urlconf=None, request=None):
+ match = django_resolve(path, urlconf)
+ if match.namespace:
+ _, view_name = match.view_name.split(':')
+ return ResolverMatch(func=match.func,
+ args=match.args,
+ kwargs=match.kwargs,
+ url_name=view_name,
+ app_name=match.app_name)
+ return match
+
class HostNameVersioning(BaseVersioning):
"""
diff --git a/tests/test_relations.py b/tests/test_relations.py
index fbe176e2..b82a1f2a 100644
--- a/tests/test_relations.py
+++ b/tests/test_relations.py
@@ -1,11 +1,28 @@
from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
-from django.core.exceptions import ImproperlyConfigured
+from django.conf.urls import patterns, url, include
+from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.utils.datastructures import MultiValueDict
from rest_framework import serializers
from rest_framework.fields import empty
-from rest_framework.test import APISimpleTestCase
+from rest_framework.test import APISimpleTestCase, APIRequestFactory
+from rest_framework.versioning import NamespaceVersioning
import pytest
+factory = APIRequestFactory()
+request = factory.get('/') # Just to ensure we have a request in the serializer context
+
+dummy_view = lambda request, pk: None
+
+included_patterns = [
+ url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
+]
+
+urlpatterns = patterns(
+ '',
+ url(r'^v1/', include(included_patterns, namespace='v1')),
+ url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
+)
+
class TestStringRelatedField(APISimpleTestCase):
def setUp(self):
@@ -48,6 +65,35 @@ class TestPrimaryKeyRelatedField(APISimpleTestCase):
assert representation == self.instance.pk
+class TestHyperlinkedRelatedField(APISimpleTestCase):
+ urls = 'tests.test_relations'
+
+ def setUp(self):
+ class HyperlinkedMockQueryset(MockQueryset):
+ def get(self, **lookup):
+ for item in self.items:
+ if item.pk == int(lookup.get('pk', -1)):
+ return item
+ raise ObjectDoesNotExist()
+
+ self.queryset = HyperlinkedMockQueryset([
+ MockObject(pk=1, name='foo'),
+ MockObject(pk=2, name='bar'),
+ MockObject(pk=3, name='baz')
+ ])
+ self.field = serializers.HyperlinkedRelatedField(
+ view_name='example-detail',
+ queryset=self.queryset
+ )
+ request = factory.post('/')
+ request.versioning_scheme = NamespaceVersioning()
+ self.field._context = {'request': request}
+
+ def test_bug_2489(self):
+ self.field.to_internal_value('/example/3/')
+ self.field.to_internal_value('/v1/example/3/')
+
+
class TestHyperlinkedIdentityField(APISimpleTestCase):
def setUp(self):
self.instance = MockObject(pk=1, name='foo')
diff --git a/tests/urls.py b/tests/urls.py
index 41f527df..742e361d 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -1,6 +1,6 @@
"""
Blank URLConf just to keep the test suite happy
"""
-from django.conf.urls import patterns
+from tests import test_relations
-urlpatterns = patterns('')
+urlpatterns = test_relations.urlpatterns
--
cgit v1.2.3
From f3067a7fabdd0edb5bc5f48cfdadd2850866c189 Mon Sep 17 00:00:00 2001
From: Brandon Cazander
Date: Mon, 2 Feb 2015 20:41:06 -0800
Subject: Remove unnecessary APIRequestFactory get from tests.
---
tests/test_relations.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/tests/test_relations.py b/tests/test_relations.py
index b82a1f2a..ff377d38 100644
--- a/tests/test_relations.py
+++ b/tests/test_relations.py
@@ -9,7 +9,6 @@ from rest_framework.versioning import NamespaceVersioning
import pytest
factory = APIRequestFactory()
-request = factory.get('/') # Just to ensure we have a request in the serializer context
dummy_view = lambda request, pk: None
--
cgit v1.2.3
From d015534d530dfdc2bbd7e301ec79fed533d55032 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Tue, 3 Feb 2015 09:15:42 +0000
Subject: Fleshing out 3.1 announcement
---
docs/topics/3.1-announcement.md | 69 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 67 insertions(+), 2 deletions(-)
diff --git a/docs/topics/3.1-announcement.md b/docs/topics/3.1-announcement.md
index 5f4a8d45..73cccb1c 100644
--- a/docs/topics/3.1-announcement.md
+++ b/docs/topics/3.1-announcement.md
@@ -1,17 +1,82 @@
# Django REST framework 3.1
+The 3.1 release is an intermediate step in the Kickstarter project releases, and includes a range of new functionality.
+
## Pagination
+The pagination API has been improved, making it both easier to use, and more powerful.
+
+#### New pagination schemes.
+
+Until now, there has only been a single built-in pagination style in REST framework. We now have page, limit/offset and cursor based schemes included by default.
+
+The cursor based pagination scheme is particularly smart, and is a better approach for clients iterating through large or frequently changing result sets. The scheme supports paging against non-unique indexes, by using both cursor and limit/offset information. Credit to David Cramer for [this blog post](http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api/) on the subject.
+
#### Pagination controls in the browsable API.
-#### New schemes, including cursor pagination.
+Paginated results now include controls that render directly in the browsable API. If you're using the page or limit/offset style, then you'll see a page based control displayed in the browsable API.
+
+**IMAGE**
+
+The cursor based pagination renders a more simple 'Previous'/'Next' control.
+
+**IMAGE**
#### Support for header-based pagination.
+The pagination API was previously only able to alter the pagination style in the body of the response. The API now supports being able to write pagination information in response headers, making it possible to use pagination schemes that use the `Link` or `Content-Range` headers.
+
+**TODO**: Link to docs.
+
## Versioning
+We've made it easier to build versioned APIs. Built-in schemes for versioning include both URL based and Accept header based variations.
+
+When using a URL based scheme, hyperlinked serializers will resolve relationships to the same API version as used on the incoming request.
+
+**TODO**: Example.
+
## Internationalization
-## New fields
+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.
+
+**TODO**: Example.
+
+**TODO**: Credit.
+
+## New field types
+
+Django 1.8's new `ArrayField`, `HStoreField` and `UUIDField` are now all fully supported.
+
+This work also means that we now have both `serializers.DictField()`, and `serializers.ListField()` types, allowing you to express and validate a wider set of representations.
## ModelSerializer API
+
+The serializer redesign in 3.0 did not include any public API for modifying how ModelSerializer classes automatically generate a set of fields from a given mode class. We've now re-introduced an API for this, allowing you to create new ModelSerializer base classes that behave differently, such as using a different default style for relationships.
+
+**TODO**: Link to docs.
+
+## Moving packages out of core
+
+We've now moved a number of packages out of the core of REST framework, and into separately installable packages. If you're currently using these you don't need to worry, you simply need to `pip install` the new packages, and change any import paths.
+
+We're making this change in order to distribute the maintainance workload, and keep better focus of the core essentials of the framework.
+
+The change also means we can be more flexible with which external packages we recommend. For example, the excellently maintained [Django OAuth toolkit](https://github.com/evonove/django-oauth-toolkit) is now our recommended option for integrating OAuth support.
+
+**TODO** Links and package names
+
+* XML
+* YAML
+* JSONP
+* OAuth
+
+# What's next?
+
+The next focus will be on HTML renderings of API output and will include:
+
+* HTML form rendering of serializers.
+* Filtering controls built-in to the browsable API.
+* An alternative admin-style interface.
+
+This will either be made as a single 3.2 release, or split across two separate releases, with the HTML forms and filter controls coming in 3.2, and the admin-style interface coming in a 3.3 release.
\ No newline at end of file
--
cgit v1.2.3
From 030f01afdbcd4018a288250ef1f4c12de28e63bb Mon Sep 17 00:00:00 2001
From: Brandon Cazander
Date: Tue, 3 Feb 2015 02:14:38 -0800
Subject: Reorganize tests.
---
tests/test_relations.py | 49 ++----------------------------------------------
tests/test_versioning.py | 41 ++++++++++++++++++++++++++++++++++++++--
tests/urls.py | 4 ++--
3 files changed, 43 insertions(+), 51 deletions(-)
diff --git a/tests/test_relations.py b/tests/test_relations.py
index ff377d38..fbe176e2 100644
--- a/tests/test_relations.py
+++ b/tests/test_relations.py
@@ -1,27 +1,11 @@
from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
-from django.conf.urls import patterns, url, include
-from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
+from django.core.exceptions import ImproperlyConfigured
from django.utils.datastructures import MultiValueDict
from rest_framework import serializers
from rest_framework.fields import empty
-from rest_framework.test import APISimpleTestCase, APIRequestFactory
-from rest_framework.versioning import NamespaceVersioning
+from rest_framework.test import APISimpleTestCase
import pytest
-factory = APIRequestFactory()
-
-dummy_view = lambda request, pk: None
-
-included_patterns = [
- url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
-]
-
-urlpatterns = patterns(
- '',
- url(r'^v1/', include(included_patterns, namespace='v1')),
- url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
-)
-
class TestStringRelatedField(APISimpleTestCase):
def setUp(self):
@@ -64,35 +48,6 @@ class TestPrimaryKeyRelatedField(APISimpleTestCase):
assert representation == self.instance.pk
-class TestHyperlinkedRelatedField(APISimpleTestCase):
- urls = 'tests.test_relations'
-
- def setUp(self):
- class HyperlinkedMockQueryset(MockQueryset):
- def get(self, **lookup):
- for item in self.items:
- if item.pk == int(lookup.get('pk', -1)):
- return item
- raise ObjectDoesNotExist()
-
- self.queryset = HyperlinkedMockQueryset([
- MockObject(pk=1, name='foo'),
- MockObject(pk=2, name='bar'),
- MockObject(pk=3, name='baz')
- ])
- self.field = serializers.HyperlinkedRelatedField(
- view_name='example-detail',
- queryset=self.queryset
- )
- request = factory.post('/')
- request.versioning_scheme = NamespaceVersioning()
- self.field._context = {'request': request}
-
- def test_bug_2489(self):
- self.field.to_internal_value('/example/3/')
- self.field.to_internal_value('/v1/example/3/')
-
-
class TestHyperlinkedIdentityField(APISimpleTestCase):
def setUp(self):
self.instance = MockObject(pk=1, name='foo')
diff --git a/tests/test_versioning.py b/tests/test_versioning.py
index c44f727d..e7c8485e 100644
--- a/tests/test_versioning.py
+++ b/tests/test_versioning.py
@@ -1,9 +1,13 @@
+from .utils import MockObject, MockQueryset
from django.conf.urls import include, url
+from django.core.exceptions import ObjectDoesNotExist
+from rest_framework import serializers
from rest_framework import status, versioning
from rest_framework.decorators import APIView
from rest_framework.response import Response
from rest_framework.reverse import reverse
-from rest_framework.test import APIRequestFactory, APITestCase
+from rest_framework.test import APIRequestFactory, APITestCase, APISimpleTestCase
+from rest_framework.versioning import NamespaceVersioning
class RequestVersionView(APIView):
@@ -29,15 +33,18 @@ class RequestInvalidVersionView(APIView):
factory = APIRequestFactory()
mock_view = lambda request: None
+dummy_view = lambda request, pk: None
included_patterns = [
url(r'^namespaced/$', mock_view, name='another'),
+ url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
]
urlpatterns = [
url(r'^v1/', include(included_patterns, namespace='v1')),
url(r'^another/$', mock_view, name='another'),
- url(r'^(?P[^/]+)/another/$', mock_view, name='another')
+ url(r'^(?P[^/]+)/another/$', mock_view, name='another'),
+ url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
]
@@ -221,3 +228,33 @@ class TestInvalidVersion:
request.resolver_match = FakeResolverMatch
response = view(request, version='v3')
assert response.status_code == status.HTTP_404_NOT_FOUND
+
+
+class TestHyperlinkedRelatedField(APISimpleTestCase):
+ urls = 'tests.test_versioning'
+
+ def setUp(self):
+
+ class HyperlinkedMockQueryset(MockQueryset):
+ def get(self, **lookup):
+ for item in self.items:
+ if item.pk == int(lookup.get('pk', -1)):
+ return item
+ raise ObjectDoesNotExist()
+
+ self.queryset = HyperlinkedMockQueryset([
+ MockObject(pk=1, name='foo'),
+ MockObject(pk=2, name='bar'),
+ MockObject(pk=3, name='baz')
+ ])
+ self.field = serializers.HyperlinkedRelatedField(
+ view_name='example-detail',
+ queryset=self.queryset
+ )
+ request = factory.post('/', urlconf='tests.test_versioning')
+ request.versioning_scheme = NamespaceVersioning()
+ self.field._context = {'request': request}
+
+ def test_bug_2489(self):
+ self.field.to_internal_value('/example/3/')
+ self.field.to_internal_value('/v1/example/3/')
diff --git a/tests/urls.py b/tests/urls.py
index 742e361d..41f527df 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -1,6 +1,6 @@
"""
Blank URLConf just to keep the test suite happy
"""
-from tests import test_relations
+from django.conf.urls import patterns
-urlpatterns = test_relations.urlpatterns
+urlpatterns = patterns('')
--
cgit v1.2.3
From 8b4ce5c636a9abb33029e48f969bbdf38f97ca1f Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 4 Feb 2015 09:07:10 +0000
Subject: Minor authentication message improvement.
---
env/pip-selfcheck.json | 1 +
rest_framework/authentication.py | 7 ++++++-
2 files changed, 7 insertions(+), 1 deletion(-)
create mode 100644 env/pip-selfcheck.json
diff --git a/env/pip-selfcheck.json b/env/pip-selfcheck.json
new file mode 100644
index 00000000..ad56313b
--- /dev/null
+++ b/env/pip-selfcheck.json
@@ -0,0 +1 @@
+{"last_check":"2015-02-04T09:06:02Z","pypi_version":"6.0.7"}
\ No newline at end of file
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
index 11db0585..a75cd30c 100644
--- a/rest_framework/authentication.py
+++ b/rest_framework/authentication.py
@@ -86,8 +86,13 @@ class BasicAuthentication(BaseAuthentication):
Authenticate the userid and password against username and password.
"""
user = authenticate(username=userid, password=password)
- if user is None or not user.is_active:
+
+ if user is None:
raise exceptions.AuthenticationFailed(_('Invalid username/password.'))
+
+ if not user.is_active:
+ raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
+
return (user, None)
def authenticate_header(self, request):
--
cgit v1.2.3
From a0374e44985172e4b8f6dc91fbc22897d2b06767 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 4 Feb 2015 09:08:43 +0000
Subject: Remove erronous checkin
---
env/pip-selfcheck.json | 1 -
1 file changed, 1 deletion(-)
delete mode 100644 env/pip-selfcheck.json
diff --git a/env/pip-selfcheck.json b/env/pip-selfcheck.json
deleted file mode 100644
index ad56313b..00000000
--- a/env/pip-selfcheck.json
+++ /dev/null
@@ -1 +0,0 @@
-{"last_check":"2015-02-04T09:06:02Z","pypi_version":"6.0.7"}
\ No newline at end of file
--
cgit v1.2.3
From 41b213414df57d7e39f1bbf3aaa35a1b033e89a3 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Wed, 4 Feb 2015 23:32:25 +0000
Subject: Updating release notes
---
docs/topics/3.1-announcement.md | 28 +++++++++++++++++++++-------
1 file changed, 21 insertions(+), 7 deletions(-)
diff --git a/docs/topics/3.1-announcement.md b/docs/topics/3.1-announcement.md
index 73cccb1c..d5986360 100644
--- a/docs/topics/3.1-announcement.md
+++ b/docs/topics/3.1-announcement.md
@@ -60,16 +60,30 @@ The serializer redesign in 3.0 did not include any public API for modifying how
We've now moved a number of packages out of the core of REST framework, and into separately installable packages. If you're currently using these you don't need to worry, you simply need to `pip install` the new packages, and change any import paths.
-We're making this change in order to distribute the maintainance workload, and keep better focus of the core essentials of the framework.
+We're making this change in order to help distribute the maintainance workload, and keep better focus of the core essentials of the framework.
-The change also means we can be more flexible with which external packages we recommend. For example, the excellently maintained [Django OAuth toolkit](https://github.com/evonove/django-oauth-toolkit) is now our recommended option for integrating OAuth support.
+The change also means we can be more flexible with which external packages we recommend. For example, the excellently maintained [Django OAuth toolkit](https://github.com/evonove/django-oauth-toolkit) has now been promoted as our recommended option for integrating OAuth support.
-**TODO** Links and package names
+The following packages are now moved out of core and should be separately installed:
-* XML
-* YAML
-* JSONP
-* OAuth
+* OAuth - [djangorestframework-oauth](http://jpadilla.github.io/django-rest-framework-oauth/)
+* XML - [djangorestframework-xml](http://jpadilla.github.io/django-rest-framework-xml)
+* YAML - [djangorestframework-yaml](http://jpadilla.github.io/django-rest-framework-yaml)
+* JSONP - [djangorestframework-jsonp](http://jpadilla.github.io/django-rest-framework-jsonp)
+
+It's worth reiterating that this change in policy shouldn't mean any work in your codebase other than adding a new requirement and modifying some import paths. For example to install XML rendering, you would now do:
+
+ pip install djangorestframework-xml
+
+And modify your settings, like so:
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_RENDERER_CLASSES': [
+ 'rest_framework.renderers.JSONRenderer',
+ 'rest_framework.renderers.BrowsableAPIRenderer',
+ 'rest_framework_xml.renderers.XMLRenderer'
+ ]
+ }
# What's next?
--
cgit v1.2.3
From e1c45133126e0c47b8470b4cf7a43c6a7f4fca43 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 5 Feb 2015 00:58:09 +0000
Subject: Fix NamespaceVersioning with hyperlinked serializer fields
---
rest_framework/relations.py | 20 ++++++++++------
rest_framework/reverse.py | 13 ----------
rest_framework/versioning.py | 19 +++------------
tests/test_relations_hyperlink.py | 7 +++---
tests/test_versioning.py | 50 +++++++++++++++++++++++----------------
tests/utils.py | 24 +++++++++++++++++++
6 files changed, 72 insertions(+), 61 deletions(-)
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 809d3db9..0b7c9d86 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -1,7 +1,7 @@
# coding: utf-8
from __future__ import unicode_literals
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
-from django.core.urlresolvers import get_script_prefix, NoReverseMatch, Resolver404
+from django.core.urlresolvers import get_script_prefix, resolve, NoReverseMatch, Resolver404
from django.db.models.query import QuerySet
from django.utils import six
from django.utils.encoding import smart_text
@@ -9,7 +9,7 @@ from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import OrderedDict
from rest_framework.fields import get_attribute, empty, Field
-from rest_framework.reverse import reverse, resolve
+from rest_framework.reverse import reverse
from rest_framework.utils import html
@@ -167,11 +167,10 @@ class HyperlinkedRelatedField(RelatedField):
self.lookup_url_kwarg = kwargs.pop('lookup_url_kwarg', self.lookup_field)
self.format = kwargs.pop('format', None)
- # We include these simply for dependency injection in tests.
- # We can't add them as class attributes or they would expect an
+ # We include this simply for dependency injection in tests.
+ # We can't add it as a class attributes or it would expect an
# implicit `self` argument to be passed.
self.reverse = reverse
- self.resolve = resolve
super(HyperlinkedRelatedField, self).__init__(**kwargs)
@@ -219,11 +218,18 @@ class HyperlinkedRelatedField(RelatedField):
data = '/' + data[len(prefix):]
try:
- match = self.resolve(data, request=request)
+ match = resolve(data)
except Resolver404:
self.fail('no_match')
- if match.view_name != self.view_name:
+ try:
+ expected_viewname = request.versioning_scheme.get_versioned_viewname(
+ self.view_name, request
+ )
+ except AttributeError:
+ expected_viewname = self.view_name
+
+ if match.view_name != expected_viewname:
self.fail('incorrect_match')
try:
diff --git a/rest_framework/reverse.py b/rest_framework/reverse.py
index 0d1d94a7..a251d99d 100644
--- a/rest_framework/reverse.py
+++ b/rest_framework/reverse.py
@@ -3,23 +3,10 @@ Provide urlresolver functions that return fully qualified URLs or view names
"""
from __future__ import unicode_literals
from django.core.urlresolvers import reverse as django_reverse
-from django.core.urlresolvers import resolve as django_resolve
from django.utils import six
from django.utils.functional import lazy
-def resolve(path, urlconf=None, request=None):
- """
- If versioning is being used then we pass any `resolve` calls through
- to the versioning scheme instance, so that the resulting view name
- can be modified if needed.
- """
- scheme = getattr(request, 'versioning_scheme', None)
- if scheme is not None:
- return scheme.resolve(path, urlconf, request)
- return django_resolve(path, urlconf)
-
-
def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra):
"""
If versioning is being used then we pass any `reverse` calls through
diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py
index a76da17a..51b886f3 100644
--- a/rest_framework/versioning.py
+++ b/rest_framework/versioning.py
@@ -1,8 +1,6 @@
# coding: utf-8
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
-from django.core.urlresolvers import resolve as django_resolve
-from django.core.urlresolvers import ResolverMatch
from rest_framework import exceptions
from rest_framework.compat import unicode_http_header
from rest_framework.reverse import _reverse
@@ -26,9 +24,6 @@ class BaseVersioning(object):
def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
return _reverse(viewname, args, kwargs, request, format, **extra)
- def resolve(self, path, urlconf=None):
- return django_resolve(path, urlconf)
-
def is_allowed_version(self, version):
if not self.allowed_versions:
return True
@@ -127,21 +122,13 @@ class NamespaceVersioning(BaseVersioning):
def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
if request.version is not None:
- viewname = request.version + ':' + viewname
+ viewname = self.get_versioned_viewname(viewname, request)
return super(NamespaceVersioning, self).reverse(
viewname, args, kwargs, request, format, **extra
)
- def resolve(self, path, urlconf=None, request=None):
- match = django_resolve(path, urlconf)
- if match.namespace:
- _, view_name = match.view_name.split(':')
- return ResolverMatch(func=match.func,
- args=match.args,
- kwargs=match.kwargs,
- url_name=view_name,
- app_name=match.app_name)
- return match
+ def get_versioned_viewname(self, viewname, request):
+ return request.version + ':' + viewname
class HostNameVersioning(BaseVersioning):
diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py
index f1b882ed..aede61d2 100644
--- a/tests/test_relations_hyperlink.py
+++ b/tests/test_relations_hyperlink.py
@@ -1,5 +1,5 @@
from __future__ import unicode_literals
-from django.conf.urls import patterns, url
+from django.conf.urls import url
from django.test import TestCase
from rest_framework import serializers
from rest_framework.test import APIRequestFactory
@@ -14,8 +14,7 @@ request = factory.get('/') # Just to ensure we have a request in the serializer
dummy_view = lambda request, pk: None
-urlpatterns = patterns(
- '',
+urlpatterns = [
url(r'^dummyurl/(?P[0-9]+)/$', dummy_view, name='dummy-url'),
url(r'^manytomanysource/(?P[0-9]+)/$', dummy_view, name='manytomanysource-detail'),
url(r'^manytomanytarget/(?P[0-9]+)/$', dummy_view, name='manytomanytarget-detail'),
@@ -24,7 +23,7 @@ urlpatterns = patterns(
url(r'^nullableforeignkeysource/(?P[0-9]+)/$', dummy_view, name='nullableforeignkeysource-detail'),
url(r'^onetoonetarget/(?P[0-9]+)/$', dummy_view, name='onetoonetarget-detail'),
url(r'^nullableonetoonesource/(?P[0-9]+)/$', dummy_view, name='nullableonetoonesource-detail'),
-)
+]
# ManyToMany
diff --git a/tests/test_versioning.py b/tests/test_versioning.py
index e7c8485e..cdd10065 100644
--- a/tests/test_versioning.py
+++ b/tests/test_versioning.py
@@ -1,4 +1,4 @@
-from .utils import MockObject, MockQueryset
+from .utils import MockObject, MockQueryset, UsingURLPatterns
from django.conf.urls import include, url
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers
@@ -6,8 +6,9 @@ from rest_framework import status, versioning
from rest_framework.decorators import APIView
from rest_framework.response import Response
from rest_framework.reverse import reverse
-from rest_framework.test import APIRequestFactory, APITestCase, APISimpleTestCase
+from rest_framework.test import APIRequestFactory, APITestCase
from rest_framework.versioning import NamespaceVersioning
+import pytest
class RequestVersionView(APIView):
@@ -35,18 +36,6 @@ factory = APIRequestFactory()
mock_view = lambda request: None
dummy_view = lambda request, pk: None
-included_patterns = [
- url(r'^namespaced/$', mock_view, name='another'),
- url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
-]
-
-urlpatterns = [
- url(r'^v1/', include(included_patterns, namespace='v1')),
- url(r'^another/$', mock_view, name='another'),
- url(r'^(?P[^/]+)/another/$', mock_view, name='another'),
- url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
-]
-
class TestRequestVersion:
def test_unversioned(self):
@@ -121,8 +110,17 @@ class TestRequestVersion:
assert response.data == {'version': None}
-class TestURLReversing(APITestCase):
- urls = 'tests.test_versioning'
+class TestURLReversing(UsingURLPatterns, APITestCase):
+ included = [
+ url(r'^namespaced/$', mock_view, name='another'),
+ url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
+ ]
+
+ urlpatterns = [
+ url(r'^v1/', include(included, namespace='v1')),
+ url(r'^another/$', mock_view, name='another'),
+ url(r'^(?P[^/]+)/another/$', mock_view, name='another'),
+ ]
def test_reverse_unversioned(self):
view = ReverseView.as_view()
@@ -230,10 +228,18 @@ class TestInvalidVersion:
assert response.status_code == status.HTTP_404_NOT_FOUND
-class TestHyperlinkedRelatedField(APISimpleTestCase):
- urls = 'tests.test_versioning'
+class TestHyperlinkedRelatedField(UsingURLPatterns, APITestCase):
+ included = [
+ url(r'^namespaced/(?P\d+)/$', mock_view, name='namespaced'),
+ ]
+
+ urlpatterns = [
+ url(r'^v1/', include(included, namespace='v1')),
+ url(r'^v2/', include(included, namespace='v2'))
+ ]
def setUp(self):
+ super(TestHyperlinkedRelatedField, self).setUp()
class HyperlinkedMockQueryset(MockQueryset):
def get(self, **lookup):
@@ -248,13 +254,15 @@ class TestHyperlinkedRelatedField(APISimpleTestCase):
MockObject(pk=3, name='baz')
])
self.field = serializers.HyperlinkedRelatedField(
- view_name='example-detail',
+ view_name='namespaced',
queryset=self.queryset
)
request = factory.post('/', urlconf='tests.test_versioning')
request.versioning_scheme = NamespaceVersioning()
+ request.version = 'v1'
self.field._context = {'request': request}
def test_bug_2489(self):
- self.field.to_internal_value('/example/3/')
- self.field.to_internal_value('/v1/example/3/')
+ self.field.to_internal_value('/v1/namespaced/3/')
+ with pytest.raises(serializers.ValidationError):
+ self.field.to_internal_value('/v2/namespaced/3/')
diff --git a/tests/utils.py b/tests/utils.py
index 5b2d7586..b9034996 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -2,6 +2,30 @@ from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import NoReverseMatch
+class UsingURLPatterns(object):
+ """
+ Isolates URL patterns used during testing on the test class itself.
+ For example:
+
+ class MyTestCase(UsingURLPatterns, TestCase):
+ urlpatterns = [
+ ...
+ ]
+
+ def test_something(self):
+ ...
+ """
+ urls = __name__
+
+ def setUp(self):
+ global urlpatterns
+ urlpatterns = self.urlpatterns
+
+ def tearDown(self):
+ global urlpatterns
+ urlpatterns = []
+
+
class MockObject(object):
def __init__(self, **kwargs):
self._kwargs = kwargs
--
cgit v1.2.3
From f98f842827c6e79bbaa196482e3c3c549e8999c8 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Thu, 5 Feb 2015 01:24:55 +0000
Subject: Minor bits of test cleanup
---
tests/test_versioning.py | 39 +++++++++++++++------------------------
1 file changed, 15 insertions(+), 24 deletions(-)
diff --git a/tests/test_versioning.py b/tests/test_versioning.py
index cdd10065..553463d1 100644
--- a/tests/test_versioning.py
+++ b/tests/test_versioning.py
@@ -1,6 +1,5 @@
-from .utils import MockObject, MockQueryset, UsingURLPatterns
+from .utils import UsingURLPatterns
from django.conf.urls import include, url
-from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers
from rest_framework import status, versioning
from rest_framework.decorators import APIView
@@ -33,8 +32,8 @@ class RequestInvalidVersionView(APIView):
factory = APIRequestFactory()
-mock_view = lambda request: None
-dummy_view = lambda request, pk: None
+dummy_view = lambda request: None
+dummy_pk_view = lambda request, pk: None
class TestRequestVersion:
@@ -112,14 +111,14 @@ class TestRequestVersion:
class TestURLReversing(UsingURLPatterns, APITestCase):
included = [
- url(r'^namespaced/$', mock_view, name='another'),
- url(r'^example/(?P\d+)/$', dummy_view, name='example-detail')
+ url(r'^namespaced/$', dummy_view, name='another'),
+ url(r'^example/(?P\d+)/$', dummy_pk_view, name='example-detail')
]
urlpatterns = [
url(r'^v1/', include(included, namespace='v1')),
- url(r'^another/$', mock_view, name='another'),
- url(r'^(?P[^/]+)/another/$', mock_view, name='another'),
+ url(r'^another/$', dummy_view, name='another'),
+ url(r'^(?P[^/]+)/another/$', dummy_view, name='another'),
]
def test_reverse_unversioned(self):
@@ -230,7 +229,7 @@ class TestInvalidVersion:
class TestHyperlinkedRelatedField(UsingURLPatterns, APITestCase):
included = [
- url(r'^namespaced/(?P\d+)/$', mock_view, name='namespaced'),
+ url(r'^namespaced/(?P\d+)/$', dummy_view, name='namespaced'),
]
urlpatterns = [
@@ -241,28 +240,20 @@ class TestHyperlinkedRelatedField(UsingURLPatterns, APITestCase):
def setUp(self):
super(TestHyperlinkedRelatedField, self).setUp()
- class HyperlinkedMockQueryset(MockQueryset):
- def get(self, **lookup):
- for item in self.items:
- if item.pk == int(lookup.get('pk', -1)):
- return item
- raise ObjectDoesNotExist()
-
- self.queryset = HyperlinkedMockQueryset([
- MockObject(pk=1, name='foo'),
- MockObject(pk=2, name='bar'),
- MockObject(pk=3, name='baz')
- ])
+ class MockQueryset(object):
+ def get(self, pk):
+ return 'object %s' % pk
+
self.field = serializers.HyperlinkedRelatedField(
view_name='namespaced',
- queryset=self.queryset
+ queryset=MockQueryset()
)
- request = factory.post('/', urlconf='tests.test_versioning')
+ request = factory.get('/')
request.versioning_scheme = NamespaceVersioning()
request.version = 'v1'
self.field._context = {'request': request}
def test_bug_2489(self):
- self.field.to_internal_value('/v1/namespaced/3/')
+ assert self.field.to_internal_value('/v1/namespaced/3/') == 'object 3'
with pytest.raises(serializers.ValidationError):
self.field.to_internal_value('/v2/namespaced/3/')
--
cgit v1.2.3
From 48fa77c09e2198c7877a724a46230caedcc7b529 Mon Sep 17 00:00:00 2001
From: José Padilla
Date: Wed, 4 Feb 2015 23:33:59 -0400
Subject: Add child to ListField when using ArrayField
---
rest_framework/serializers.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 520b9774..84e4961b 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -986,15 +986,25 @@ class ModelSerializer(Serializer):
# Fields with choices get coerced into `ChoiceField`
# instead of using their regular typed field.
field_class = ChoiceField
+
if not issubclass(field_class, ModelField):
# `model_field` is only valid for the fallback case of
# `ModelField`, which is used when no other typed field
# matched to the model field.
field_kwargs.pop('model_field', None)
+
if not issubclass(field_class, CharField) and not issubclass(field_class, ChoiceField):
# `allow_blank` is only valid for textual fields.
field_kwargs.pop('allow_blank', None)
+ if postgres_fields and isinstance(model_field, postgres_fields.ArrayField):
+ child_model_field = model_field.base_field.base_field
+ child_field_class, child_field_kwargs = self.build_standard_field(
+ 'child', child_model_field
+ )
+
+ field_kwargs['child'] = child_field_class(**child_field_kwargs)
+
return field_class, field_kwargs
def build_relational_field(self, field_name, relation_info):
--
cgit v1.2.3
From c696b0ba0ced9527c8f4ad1bf6f71546d8fa65c2 Mon Sep 17 00:00:00 2001
From: José Padilla
Date: Thu, 5 Feb 2015 10:12:14 -0400
Subject: Fix possible nested array fields
---
rest_framework/serializers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 84e4961b..18821958 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -998,7 +998,7 @@ class ModelSerializer(Serializer):
field_kwargs.pop('allow_blank', None)
if postgres_fields and isinstance(model_field, postgres_fields.ArrayField):
- child_model_field = model_field.base_field.base_field
+ child_model_field = model_field.base_field
child_field_class, child_field_kwargs = self.build_standard_field(
'child', child_model_field
)
--
cgit v1.2.3
From 6c63ef13cd9ff8432f15d55a7268f2d402ae38e8 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 6 Feb 2015 00:04:20 +0000
Subject: Drop 2.x announcements
---
docs/index.md | 10 +-
docs/topics/3.1-announcement.md | 2 +-
docs/topics/release-notes.md | 583 +---------------------------------------
env/pip-selfcheck.json | 1 +
mkdocs.yml | 5 +-
5 files changed, 7 insertions(+), 594 deletions(-)
create mode 100644 env/pip-selfcheck.json
diff --git a/docs/index.md b/docs/index.md
index c3a9c2d4..cd243f35 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -195,11 +195,8 @@ General guides to using REST framework.
* [Third Party Resources][third-party-resources]
* [Contributing to REST framework][contributing]
* [Project management][project-management]
-* [2.0 Announcement][rest-framework-2-announcement]
-* [2.2 Announcement][2.2-announcement]
-* [2.3 Announcement][2.3-announcement]
-* [2.4 Announcement][2.4-announcement]
* [3.0 Announcement][3.0-announcement]
+* [3.1 Announcement][3.1-announcement]
* [Kickstarter Announcement][kickstarter-announcement]
* [Release Notes][release-notes]
* [Credits][credits]
@@ -313,11 +310,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[contributing]: topics/contributing.md
[project-management]: topics/project-management.md
[third-party-resources]: topics/third-party-resources.md
-[rest-framework-2-announcement]: topics/rest-framework-2-announcement.md
-[2.2-announcement]: topics/2.2-announcement.md
-[2.3-announcement]: topics/2.3-announcement.md
-[2.4-announcement]: topics/2.4-announcement.md
[3.0-announcement]: topics/3.0-announcement.md
+[3.1-announcement]: topics/3.1-announcement.md
[kickstarter-announcement]: topics/kickstarter-announcement.md
[release-notes]: topics/release-notes.md
[credits]: topics/credits.md
diff --git a/docs/topics/3.1-announcement.md b/docs/topics/3.1-announcement.md
index d5986360..89e99f82 100644
--- a/docs/topics/3.1-announcement.md
+++ b/docs/topics/3.1-announcement.md
@@ -26,7 +26,7 @@ The cursor based pagination renders a more simple 'Previous'/'Next' control.
The pagination API was previously only able to alter the pagination style in the body of the response. The API now supports being able to write pagination information in response headers, making it possible to use pagination schemes that use the `Link` or `Content-Range` headers.
-**TODO**: Link to docs.
+For more information, see the [custom pagination styles](../api-guide/pagination/#custom-pagination-styles) documentation.
## Versioning
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index e0894d2d..88732df8 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -124,598 +124,19 @@ For full details see the [3.0 release announcement](3.0-announcement.md).
---
-## 2.4.x series
-
-### 2.4.4
-
-**Date**: [3rd November 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.4+Release%22+).
-
-* **Security fix**: Escape URLs when replacing `format=` query parameter, as used in dropdown on `GET` button in browsable API to allow explicit selection of JSON vs HTML output.
-* Maintain ordering of URLs in API root view for `DefaultRouter`.
-* Fix `follow=True` in `APIRequestFactory`
-* Resolve issue with invalid `read_only=True`, `required=True` fields being automatically generated by `ModelSerializer` in some cases.
-* Resolve issue with `OPTIONS` requests returning incorrect information for views using `get_serializer_class` to dynamically determine serializer based on request method.
-
-### 2.4.3
-
-**Date**: [19th September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.3+Release%22+).
-
-* Support translatable view docstrings being displayed in the browsable API.
-* Support [encoded `filename*`][rfc-6266] in raw file uploads with `FileUploadParser`.
-* Allow routers to support viewsets that don't include any list routes or that don't include any detail routes.
-* Don't render an empty login control in browsable API if `login` view is not included.
-* CSRF exemption performed in `.as_view()` to prevent accidental omission if overriding `.dispatch()`.
-* Login on browsable API now displays validation errors.
-* Bugfix: Fix migration in `authtoken` application.
-* Bugfix: Allow selection of integer keys in nested choices.
-* Bugfix: Return `None` instead of `'None'` in `CharField` with `allow_none=True`.
-* Bugfix: Ensure custom model fields map to equivelent serializer fields more reliably.
-* Bugfix: `DjangoFilterBackend` no longer quietly changes queryset ordering.
-
-### 2.4.2
-
-**Date**: [3rd September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.2+Release%22+).
-
-* Bugfix: Fix broken pagination for 2.4.x series.
-
-### 2.4.1
-
-**Date**: [1st September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.1+Release%22+).
-
-* Bugfix: Fix broken login template for browsable API.
-
-### 2.4.0
-
-**Date**: [29th August 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.0+Release%22+).
-
-**Django version requirements**: The lowest supported version of Django is now 1.4.2.
-
-**South version requirements**: This note applies to any users using the optional `authtoken` application, which includes an associated database migration. You must now *either* upgrade your `south` package to version 1.0, *or* instead use the built-in migration support available with Django 1.7.
-
-* Added compatibility with Django 1.7's database migration support.
-* New test runner, using `py.test`.
-* Deprecated `.model` view attribute in favor of explicit `.queryset` and `.serializer_class` attributes. The `DEFAULT_MODEL_SERIALIZER_CLASS` setting is also deprecated.
-* `@detail_route` and `@list_route` decorators replace `@action` and `@link`.
-* Support customizable view name and description functions, using the `VIEW_NAME_FUNCTION` and `VIEW_DESCRIPTION_FUNCTION` settings.
-* Added `NUM_PROXIES` setting for smarter client IP identification.
-* Added `MAX_PAGINATE_BY` setting and `max_paginate_by` generic view attribute.
-* Added `Retry-After` header to throttled responses, as per [RFC 6585](http://tools.ietf.org/html/rfc6585). This should now be used in preference to the custom `X-Trottle-Wait-Seconds` header which will be fully deprecated in 3.0.
-* Added `cache` attribute to throttles to allow overriding of default cache.
-* Added `lookup_value_regex` attribute to routers, to allow the URL argument matching to be constrainted by the user.
-* Added `allow_none` option to `CharField`.
-* Support Django's standard `status_code` class attribute on responses.
-* More intuitive behavior on the test client, as `client.logout()` now also removes any credentials that have been set.
-* Bugfix: `?page_size=0` query parameter now falls back to default page size for view, instead of always turning pagination off.
-* Bugfix: Always uppercase `X-Http-Method-Override` methods.
-* Bugfix: Copy `filter_backends` list before returning it, in order to prevent view code from mutating the class attribute itself.
-* Bugfix: Set the `.action` attribute on viewsets when introspected by `OPTIONS` for testing permissions on the view.
-* Bugfix: Ensure `ValueError` raised during deserialization results in a error list rather than a single error. This is now consistent with other validation errors.
-* Bugfix: Fix `cache_format` typo on throttle classes, was `"throtte_%(scope)s_%(ident)s"`. Note that this will invalidate existing throttle caches.
-
----
-
-## 2.3.x series
-
-### 2.3.14
-
-**Date**: 12th June 2014
-
-* **Security fix**: Escape request path when it is include as part of the login and logout links in the browsable API.
-* `help_text` and `verbose_name` automatically set for related fields on `ModelSerializer`.
-* Fix nested serializers linked through a backward foreign key relation.
-* Fix bad links for the `BrowsableAPIRenderer` with `YAMLRenderer`.
-* Add `UnicodeYAMLRenderer` that extends `YAMLRenderer` with unicode.
-* Fix `parse_header` argument convertion.
-* Fix mediatype detection under Python 3.
-* Web browsable API now offers blank option on dropdown when the field is not required.
-* `APIException` representation improved for logging purposes.
-* Allow source="*" within nested serializers.
-* Better support for custom oauth2 provider backends.
-* Fix field validation if it's optional and has no value.
-* Add `SEARCH_PARAM` and `ORDERING_PARAM`.
-* Fix `APIRequestFactory` to support arguments within the url string for GET.
-* Allow three transport modes for access tokens when accessing a protected resource.
-* Fix `QueryDict` encoding on request objects.
-* Ensure throttle keys do not contain spaces, as those are invalid if using `memcached`.
-* Support `blank_display_value` on `ChoiceField`.
-
-### 2.3.13
-
-**Date**: 6th March 2014
-
-* Django 1.7 Support.
-* Fix `default` argument when used with serializer relation fields.
-* Display the media type of the content that is being displayed in the browsable API, rather than 'text/html'.
-* Bugfix for `urlize` template failure when URL regex is matched, but value does not `urlparse`.
-* Use `urandom` for token generation.
-* Only use `Vary: Accept` when more than one renderer exists.
-
-### 2.3.12
-
-**Date**: 15th January 2014
-
-* **Security fix**: `OrderingField` now only allows ordering on readable serializer fields, or on fields explicitly specified using `ordering_fields`. This prevents users being able to order by fields that are not visible in the API, and exploiting the ordering of sensitive data such as password hashes.
-* Bugfix: `write_only = True` fields now display in the browsable API.
-
-### 2.3.11
-
-**Date**: 14th January 2014
-
-* Added `write_only` serializer field argument.
-* Added `write_only_fields` option to `ModelSerializer` classes.
-* JSON renderer now deals with objects that implement a dict-like interface.
-* Fix compatiblity with newer versions of `django-oauth-plus`.
-* Bugfix: Refine behavior that calls model manager `all()` across nested serializer relationships, preventing erronous behavior with some non-ORM objects, and preventing unnecessary queryset re-evaluations.
-* Bugfix: Allow defaults on BooleanFields to be properly honored when values are not supplied.
-* Bugfix: Prevent double-escaping of non-latin1 URL query params when appending `format=json` params.
-
-### 2.3.10
-
-**Date**: 6th December 2013
-
-* Add in choices information for ChoiceFields in response to `OPTIONS` requests.
-* Added `pre_delete()` and `post_delete()` method hooks.
-* Added status code category helper functions.
-* Bugfix: Partial updates which erronously set a related field to `None` now correctly fail validation instead of raising an exception.
-* Bugfix: Responses without any content no longer include an HTTP `'Content-Type'` header.
-* Bugfix: Correctly handle validation errors in PUT-as-create case, responding with 400.
-
-### 2.3.9
-
-**Date**: 15th November 2013
-
-* Fix Django 1.6 exception API compatibility issue caused by `ValidationError`.
-* Include errors in HTML forms in browsable API.
-* Added JSON renderer support for numpy scalars.
-* Added `transform_` hooks on serializers for easily modifying field output.
-* Added `get_context` hook in `BrowsableAPIRenderer`.
-* Allow serializers to be passed `files` but no `data`.
-* `HTMLFormRenderer` now renders serializers directly to HTML without needing to create an intermediate form object.
-* Added `get_filter_backends` hook.
-* Added queryset aggregates to allowed fields in `OrderingFilter`.
-* Bugfix: Fix decimal suppoprt with `YAMLRenderer`.
-* Bugfix: Fix submission of unicode in browsable API through raw data form.
-
-### 2.3.8
-
-**Date**: 11th September 2013
-
-* Added `DjangoObjectPermissions`, and `DjangoObjectPermissionsFilter`.
-* Support customizable exception handling, using the `EXCEPTION_HANDLER` setting.
-* Support customizable view name and description functions, using the `VIEW_NAME_FUNCTION` and `VIEW_DESCRIPTION_FUNCTION` settings.
-* Added `MAX_PAGINATE_BY` setting and `max_paginate_by` generic view attribute.
-* Added `cache` attribute to throttles to allow overriding of default cache.
-* 'Raw data' tab in browsable API now contains pre-populated data.
-* 'Raw data' and 'HTML form' tab preference in browsable API now saved between page views.
-* Bugfix: `required=True` argument fixed for boolean serializer fields.
-* Bugfix: `client.force_authenticate(None)` should also clear session info if it exists.
-* Bugfix: Client sending empty string instead of file now clears `FileField`.
-* Bugfix: Empty values on ChoiceFields with `required=False` now consistently return `None`.
-* Bugfix: Clients setting `page_size=0` now simply returns the default page size, instead of disabling pagination. [*]
-
----
-
-[*] Note that the change in `page_size=0` behaviour fixes what is considered to be a bug in how clients can effect the pagination size. However if you were relying on this behavior you will need to add the following mixin to your list views in order to preserve the existing behavior.
-
- class DisablePaginationMixin(object):
- def get_paginate_by(self, queryset=None):
- if self.request.QUERY_PARAMS[self.paginate_by_param] == '0':
- return None
- return super(DisablePaginationMixin, self).get_paginate_by(queryset)
-
----
-
-### 2.3.7
-
-**Date**: 16th August 2013
-
-* Added `APITestClient`, `APIRequestFactory` and `APITestCase` etc...
-* Refactor `SessionAuthentication` to allow esier override for CSRF exemption.
-* Remove 'Hold down "Control" message from help_text' widget messaging when not appropriate.
-* Added admin configuration for auth tokens.
-* Bugfix: `AnonRateThrottle` fixed to not throttle authenticated users.
-* Bugfix: Don't set `X-Throttle-Wait-Seconds` when throttle does not have `wait` value.
-* Bugfix: Fixed `PATCH` button title in browsable API.
-* Bugfix: Fix issue with OAuth2 provider naive datetimes.
-
-### 2.3.6
-
-**Date**: 27th June 2013
-
-* Added `trailing_slash` option to routers.
-* Include support for `HttpStreamingResponse`.
-* Support wider range of default serializer validation when used with custom model fields.
-* UTF-8 Support for browsable API descriptions.
-* OAuth2 provider uses timezone aware datetimes when supported.
-* Bugfix: Return error correctly when OAuth non-existent consumer occurs.
-* Bugfix: Allow `FileUploadParser` to correctly filename if provided as URL kwarg.
-* Bugfix: Fix `ScopedRateThrottle`.
-
-### 2.3.5
-
-**Date**: 3rd June 2013
-
-* Added `get_url` hook to `HyperlinkedIdentityField`.
-* Serializer field `default` argument may be a callable.
-* `@action` decorator now accepts a `methods` argument.
-* Bugfix: `request.user` should be still be accessible in renderer context if authentication fails.
-* Bugfix: The `lookup_field` option on `HyperlinkedIdentityField` should apply by default to the url field on the serializer.
-* Bugfix: `HyperlinkedIdentityField` should continue to support `pk_url_kwarg`, `slug_url_kwarg`, `slug_field`, in a pending deprecation state.
-* Bugfix: Ensure we always return 404 instead of 500 if a lookup field cannot be converted to the correct lookup type. (Eg non-numeric `AutoInteger` pk lookup)
-
-### 2.3.4
-
-**Date**: 24th May 2013
-
-* Serializer fields now support `label` and `help_text`.
-* Added `UnicodeJSONRenderer`.
-* `OPTIONS` requests now return metadata about fields for `POST` and `PUT` requests.
-* Bugfix: `charset` now properly included in `Content-Type` of responses.
-* Bugfix: Blank choice now added in browsable API on nullable relationships.
-* Bugfix: Many to many relationships with `through` tables are now read-only.
-* Bugfix: Serializer fields now respect model field args such as `max_length`.
-* Bugfix: SlugField now performs slug validation.
-* Bugfix: Lazy-translatable strings now properly serialized.
-* Bugfix: Browsable API now supports bootswatch styles properly.
-* Bugfix: HyperlinkedIdentityField now uses `lookup_field` kwarg.
-
-**Note**: Responses now correctly include an appropriate charset on the `Content-Type` header. For example: `application/json; charset=utf-8`. If you have tests that check the content type of responses, you may need to update these accordingly.
-
-### 2.3.3
-
-**Date**: 16th May 2013
-
-* Added SearchFilter
-* Added OrderingFilter
-* Added GenericViewSet
-* Bugfix: Multiple `@action` and `@link` methods now allowed on viewsets.
-* Bugfix: Fix API Root view issue with DjangoModelPermissions
-
-### 2.3.2
-
-**Date**: 8th May 2013
-
-* Bugfix: Fix `TIME_FORMAT`, `DATETIME_FORMAT` and `DATE_FORMAT` settings.
-* Bugfix: Fix `DjangoFilterBackend` issue, failing when used on view with queryset attribute.
-
-### 2.3.1
-
-**Date**: 7th May 2013
-
-* Bugfix: Fix breadcrumb rendering issue.
-
-### 2.3.0
-
-**Date**: 7th May 2013
-
-* ViewSets and Routers.
-* ModelSerializers support reverse relations in 'fields' option.
-* HyperLinkedModelSerializers support 'id' field in 'fields' option.
-* Cleaner generic views.
-* Support for multiple filter classes.
-* FileUploadParser support for raw file uploads.
-* DecimalField support.
-* Made Login template easier to restyle.
-* Bugfix: Fix issue with depth>1 on ModelSerializer.
-
-**Note**: See the [2.3 announcement][2.3-announcement] for full details.
-
----
-
-## 2.2.x series
-
-### 2.2.7
-
-**Date**: 17th April 2013
-
-* Loud failure when view does not return a `Response` or `HttpResponse`.
-* Bugfix: Fix for Django 1.3 compatibility.
-* Bugfix: Allow overridden `get_object()` to work correctly.
-
-### 2.2.6
-
-**Date**: 4th April 2013
-
-* OAuth2 authentication no longer requires unnecessary URL parameters in addition to the token.
-* URL hyperlinking in browsable API now handles more cases correctly.
-* Long HTTP headers in browsable API are broken in multiple lines when possible.
-* Bugfix: Fix regression with DjangoFilterBackend not worthing correctly with single object views.
-* Bugfix: OAuth should fail hard when invalid token used.
-* Bugfix: Fix serializer potentially returning `None` object for models that define `__bool__` or `__len__`.
-
-### 2.2.5
-
-**Date**: 26th March 2013
-
-* Serializer support for bulk create and bulk update operations.
-* Regression fix: Date and time fields return date/time objects by default. Fixes regressions caused by 2.2.2. See [#743][743] for more details.
-* Bugfix: Fix 500 error is OAuth not attempted with OAuthAuthentication class installed.
-* `Serializer.save()` now supports arbitrary keyword args which are passed through to the object `.save()` method. Mixins use `force_insert` and `force_update` where appropriate, resulting in one less database query.
-
-### 2.2.4
-
-**Date**: 13th March 2013
-
-* OAuth 2 support.
-* OAuth 1.0a support.
-* Support X-HTTP-Method-Override header.
-* Filtering backends are now applied to the querysets for object lookups as well as lists. (Eg you can use a filtering backend to control which objects should 404)
-* Deal with error data nicely when deserializing lists of objects.
-* Extra override hook to configure `DjangoModelPermissions` for unauthenticated users.
-* Bugfix: Fix regression which caused extra database query on paginated list views.
-* Bugfix: Fix pk relationship bug for some types of 1-to-1 relations.
-* Bugfix: Workaround for Django bug causing case where `Authtoken` could be registered for cascade delete from `User` even if not installed.
-
-### 2.2.3
-
-**Date**: 7th March 2013
-
-* Bugfix: Fix None values for for `DateField`, `DateTimeField` and `TimeField`.
-
-### 2.2.2
-
-**Date**: 6th March 2013
-
-* Support for custom input and output formats for `DateField`, `DateTimeField` and `TimeField`.
-* Cleanup: Request authentication is no longer lazily evaluated, instead authentication is always run, which results in more consistent, obvious behavior. Eg. Supplying bad auth credentials will now always return an error response, even if no permissions are set on the view.
-* Bugfix for serializer data being uncacheable with pickle protocol 0.
-* Bugfixes for model field validation edge-cases.
-* Bugfix for authtoken migration while using a custom user model and south.
-
-### 2.2.1
-
-**Date**: 22nd Feb 2013
-
-* Security fix: Use `defusedxml` package to address XML parsing vulnerabilities.
-* Raw data tab added to browsable API. (Eg. Allow for JSON input.)
-* Added TimeField.
-* Serializer fields can be mapped to any method that takes no args, or only takes kwargs which have defaults.
-* Unicode support for view names/descriptions in browsable API.
-* Bugfix: request.DATA should return an empty `QueryDict` with no data, not `None`.
-* Bugfix: Remove unneeded field validation, which caused extra queries.
-
-**Security note**: Following the [disclosure of security vulnerabilities][defusedxml-announce] in Python's XML parsing libraries, use of the `XMLParser` class now requires the `defusedxml` package to be installed.
-
-The security vulnerabilities only affect APIs which use the `XMLParser` class, by enabling it in any views, or by having it set in the `DEFAULT_PARSER_CLASSES` setting. Note that the `XMLParser` class is not enabled by default, so this change should affect a minority of users.
-
-### 2.2.0
-
-**Date**: 13th Feb 2013
-
-* Python 3 support.
-* Added a `post_save()` hook to the generic views.
-* Allow serializers to handle dicts as well as objects.
-* Deprecate `ManyRelatedField()` syntax in favor of `RelatedField(many=True)`
-* Deprecate `null=True` on relations in favor of `required=False`.
-* Deprecate `blank=True` on CharFields, just use `required=False`.
-* Deprecate optional `obj` argument in permissions checks in favor of `has_object_permission`.
-* Deprecate implicit hyperlinked relations behavior.
-* Bugfix: Fix broken DjangoModelPermissions.
-* Bugfix: Allow serializer output to be cached.
-* Bugfix: Fix styling on browsable API login.
-* Bugfix: Fix issue with deserializing empty to-many relations.
-* Bugfix: Ensure model field validation is still applied for ModelSerializer subclasses with an custom `.restore_object()` method.
-
-**Note**: See the [2.2 announcement][2.2-announcement] for full details.
-
----
-
-## 2.1.x series
-
-### 2.1.17
-
-**Date**: 26th Jan 2013
-
-* Support proper 401 Unauthorized responses where appropriate, instead of always using 403 Forbidden.
-* Support json encoding of timedelta objects.
-* `format_suffix_patterns()` now supports `include` style URL patterns.
-* Bugfix: Fix issues with custom pagination serializers.
-* Bugfix: Nested serializers now accept `source='*'` argument.
-* Bugfix: Return proper validation errors when incorrect types supplied for relational fields.
-* Bugfix: Support nullable FKs with `SlugRelatedField`.
-* Bugfix: Don't call custom validation methods if the field has an error.
-
-**Note**: If the primary authentication class is `TokenAuthentication` or `BasicAuthentication`, a view will now correctly return 401 responses to unauthenticated access, with an appropriate `WWW-Authenticate` header, instead of 403 responses.
-
-### 2.1.16
-
-**Date**: 14th Jan 2013
-
-* Deprecate `django.utils.simplejson` in favor of Python 2.6's built-in json module.
-* Bugfix: `auto_now`, `auto_now_add` and other `editable=False` fields now default to read-only.
-* Bugfix: PK fields now only default to read-only if they are an AutoField or if `editable=False`.
-* Bugfix: Validation errors instead of exceptions when serializers receive incorrect types.
-* Bugfix: Validation errors instead of exceptions when related fields receive incorrect types.
-* Bugfix: Handle ObjectDoesNotExist exception when serializing null reverse one-to-one
-
-**Note**: Prior to 2.1.16, The Decimals would render in JSON using floating point if `simplejson` was installed, but otherwise render using string notation. Now that use of `simplejson` has been deprecated, Decimals will consistently render using string notation. See [ticket 582](ticket-582) for more details.
-
-### 2.1.15
-
-**Date**: 3rd Jan 2013
-
-* Added `PATCH` support.
-* Added `RetrieveUpdateAPIView`.
-* Remove unused internal `save_m2m` flag on `ModelSerializer.save()`.
-* Tweak behavior of hyperlinked fields with an explicit format suffix.
-* Relation changes are now persisted in `.save()` instead of in `.restore_object()`.
-* Bugfix: Fix issue with FileField raising exception instead of validation error when files=None.
-* Bugfix: Partial updates should not set default values if field is not included.
-
-### 2.1.14
-
-**Date**: 31st Dec 2012
-
-* Bugfix: ModelSerializers now include reverse FK fields on creation.
-* Bugfix: Model fields with `blank=True` are now `required=False` by default.
-* Bugfix: Nested serializers now support nullable relationships.
-
-**Note**: From 2.1.14 onwards, relational fields move out of the `fields.py` module and into the new `relations.py` module, in order to separate them from regular data type fields, such as `CharField` and `IntegerField`.
-
-This change will not affect user code, so long as it's following the recommended import style of `from rest_framework import serializers` and referring to fields using the style `serializers.PrimaryKeyRelatedField`.
-
-
-### 2.1.13
-
-**Date**: 28th Dec 2012
-
-* Support configurable `STATICFILES_STORAGE` storage.
-* Bugfix: Related fields now respect the required flag, and may be required=False.
-
-### 2.1.12
-
-**Date**: 21st Dec 2012
-
-* Bugfix: Fix bug that could occur using ChoiceField.
-* Bugfix: Fix exception in browsable API on DELETE.
-* Bugfix: Fix issue where pk was was being set to a string if set by URL kwarg.
-
-### 2.1.11
-
-**Date**: 17th Dec 2012
-
-* Bugfix: Fix issue with M2M fields in browsable API.
-
-### 2.1.10
-
-**Date**: 17th Dec 2012
-
-* Bugfix: Ensure read-only fields don't have model validation applied.
-* Bugfix: Fix hyperlinked fields in paginated results.
-
-### 2.1.9
-
-**Date**: 11th Dec 2012
-
-* Bugfix: Fix broken nested serialization.
-* Bugfix: Fix `Meta.fields` only working as tuple not as list.
-* Bugfix: Edge case if unnecessarily specifying `required=False` on read only field.
-
-### 2.1.8
-
-**Date**: 8th Dec 2012
-
-* Fix for creating nullable Foreign Keys with `''` as well as `None`.
-* Added `null=` related field option.
-
-### 2.1.7
-
-**Date**: 7th Dec 2012
-
-* Serializers now properly support nullable Foreign Keys.
-* Serializer validation now includes model field validation, such as uniqueness constraints.
-* Support 'true' and 'false' string values for BooleanField.
-* Added pickle support for serialized data.
-* Support `source='dotted.notation'` style for nested serializers.
-* Make `Request.user` settable.
-* Bugfix: Fix `RegexField` to work with `BrowsableAPIRenderer`.
-
-### 2.1.6
-
-**Date**: 23rd Nov 2012
-
-* Bugfix: Unfix DjangoModelPermissions. (I am a doofus.)
-
-### 2.1.5
-
-**Date**: 23rd Nov 2012
-
-* Bugfix: Fix DjangoModelPermissions.
-
-### 2.1.4
-
-**Date**: 22nd Nov 2012
-
-* Support for partial updates with serializers.
-* Added `RegexField`.
-* Added `SerializerMethodField`.
-* Serializer performance improvements.
-* Added `obtain_token_view` to get tokens when using `TokenAuthentication`.
-* Bugfix: Django 1.5 configurable user support for `TokenAuthentication`.
-
-### 2.1.3
-
-**Date**: 16th Nov 2012
-
-* Added `FileField` and `ImageField`. For use with `MultiPartParser`.
-* Added `URLField` and `SlugField`.
-* Support for `read_only_fields` on `ModelSerializer` classes.
-* Support for clients overriding the pagination page sizes. Use the `PAGINATE_BY_PARAM` setting or set the `paginate_by_param` attribute on a generic view.
-* 201 Responses now return a 'Location' header.
-* Bugfix: Serializer fields now respect `max_length`.
-
-### 2.1.2
-
-**Date**: 9th Nov 2012
-
-* **Filtering support.**
-* Bugfix: Support creation of objects with reverse M2M relations.
-
-### 2.1.1
-
-**Date**: 7th Nov 2012
-
-* Support use of HTML exception templates. Eg. `403.html`
-* Hyperlinked fields take optional `slug_field`, `slug_url_kwarg` and `pk_url_kwarg` arguments.
-* Bugfix: Deal with optional trailing slashes properly when generating breadcrumbs.
-* Bugfix: Make textareas same width as other fields in browsable API.
-* Private API change: `.get_serializer` now uses same `instance` and `data` ordering as serializer initialization.
-
-### 2.1.0
-
-**Date**: 5th Nov 2012
-
-* **Serializer `instance` and `data` keyword args have their position swapped.**
-* `queryset` argument is now optional on writable model fields.
-* Hyperlinked related fields optionally take `slug_field` and `slug_url_kwarg` arguments.
-* Support Django's cache framework.
-* Minor field improvements. (Don't stringify dicts, more robust many-pk fields.)
-* Bugfix: Support choice field in Browsable API.
-* Bugfix: Related fields with `read_only=True` do not require a `queryset` argument.
-
-**API-incompatible changes**: Please read [this thread][2.1.0-notes] regarding the `instance` and `data` keyword args before updating to 2.1.0.
-
----
-
-## 2.0.x series
-
-### 2.0.2
-
-**Date**: 2nd Nov 2012
-
-* Fix issues with pk related fields in the browsable API.
-
-### 2.0.1
-
-**Date**: 1st Nov 2012
-
-* Add support for relational fields in the browsable API.
-* Added SlugRelatedField and ManySlugRelatedField.
-* If PUT creates an instance return '201 Created', instead of '200 OK'.
-
-### 2.0.0
-
-**Date**: 30th Oct 2012
-
-* **Fix all of the things.** (Well, almost.)
-* For more information please see the [2.0 announcement][announcement].
-
-For older release notes, [please see the GitHub repo](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
[deprecation-policy]: #deprecation-policy
[django-deprecation-policy]: https://docs.djangoproject.com/en/dev/internals/release-process/#internal-release-deprecation-policy
[defusedxml-announce]: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html
-[2.2-announcement]: 2.2-announcement.md
-[2.3-announcement]: 2.3-announcement.md
[743]: https://github.com/tomchristie/django-rest-framework/pull/743
[staticfiles14]: https://docs.djangoproject.com/en/1.4/howto/static-files/#with-a-template-tag
[staticfiles13]: https://docs.djangoproject.com/en/1.3/howto/static-files/#with-a-template-tag
[2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion
-[announcement]: rest-framework-2-announcement.md
[ticket-582]: https://github.com/tomchristie/django-rest-framework/issues/582
[rfc-6266]: http://tools.ietf.org/html/rfc6266#section-4.3
-[old-release-notes]: https://github.com/tomchristie/django-rest-framework/blob/2.4.4/docs/topics/release-notes.md#04x-series
+[old-release-notes]: http://tomchristie.github.io/rest-framework-2-docs/topics/release-notes#24x-series
[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
diff --git a/env/pip-selfcheck.json b/env/pip-selfcheck.json
new file mode 100644
index 00000000..77237c3c
--- /dev/null
+++ b/env/pip-selfcheck.json
@@ -0,0 +1 @@
+{"last_check":"2015-02-05T23:51:53Z","pypi_version":"6.0.8"}
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
index 89df4cea..57b45054 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -50,11 +50,8 @@ pages:
- ['topics/third-party-resources.md', 'Topics', 'Third Party Resources']
- ['topics/contributing.md', 'Topics', 'Contributing to REST framework']
- ['topics/project-management.md', 'Topics', 'Project management']
- - ['topics/rest-framework-2-announcement.md', 'Topics', '2.0 Announcement']
- - ['topics/2.2-announcement.md', 'Topics', '2.2 Announcement']
- - ['topics/2.3-announcement.md', 'Topics', '2.3 Announcement']
- - ['topics/2.4-announcement.md', 'Topics', '2.4 Announcement']
- ['topics/3.0-announcement.md', 'Topics', '3.0 Announcement']
+ - ['topics/3.1-announcement.md', 'Topics', '3.1 Announcement']
- ['topics/kickstarter-announcement.md', 'Topics', 'Kickstarter Announcement']
- ['topics/release-notes.md', 'Topics', 'Release Notes']
- ['topics/credits.md', 'Topics', 'Credits']
--
cgit v1.2.3
From b11a98f1e765b0b3c11cd353405bde8379057194 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 6 Feb 2015 00:05:42 +0000
Subject: Tweak gitignore
---
.gitignore | 1 +
env/pip-selfcheck.json | 1 -
2 files changed, 1 insertion(+), 1 deletion(-)
delete mode 100644 env/pip-selfcheck.json
diff --git a/.gitignore b/.gitignore
index 2bdf8f7e..4f2b0ddf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@ bin/
include/
lib/
local/
+env/
!.gitignore
!.travis.yml
diff --git a/env/pip-selfcheck.json b/env/pip-selfcheck.json
deleted file mode 100644
index 77237c3c..00000000
--- a/env/pip-selfcheck.json
+++ /dev/null
@@ -1 +0,0 @@
-{"last_check":"2015-02-05T23:51:53Z","pypi_version":"6.0.8"}
\ No newline at end of file
--
cgit v1.2.3
From 09488ad4da321f5f15d6e3df348869b8f2116b4a Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 6 Feb 2015 00:25:03 +0000
Subject: Link to ModelSerializer API
---
docs/topics/3.1-announcement.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/topics/3.1-announcement.md b/docs/topics/3.1-announcement.md
index 89e99f82..d0975f5b 100644
--- a/docs/topics/3.1-announcement.md
+++ b/docs/topics/3.1-announcement.md
@@ -54,7 +54,7 @@ This work also means that we now have both `serializers.DictField()`, and `seria
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.
-**TODO**: Link to docs.
+For more information, see the documentation on [customizing field mappings](../api-guide/serializers/#customizing-field-mappings) for ModelSerializer classes.
## Moving packages out of core
--
cgit v1.2.3
From 53716f61527266159c285b903da98ec432e52564 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 6 Feb 2015 11:37:29 +0000
Subject: Internationalization docs
---
docs/index.md | 2 -
docs/topics/3.1-announcement.md | 77 ++++++-
docs/topics/credits.md | 404 ------------------------------------
docs/topics/internationalization.md | 41 ++++
mkdocs.yml | 1 -
5 files changed, 113 insertions(+), 412 deletions(-)
delete mode 100644 docs/topics/credits.md
diff --git a/docs/index.md b/docs/index.md
index cd243f35..23781419 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -199,7 +199,6 @@ General guides to using REST framework.
* [3.1 Announcement][3.1-announcement]
* [Kickstarter Announcement][kickstarter-announcement]
* [Release Notes][release-notes]
-* [Credits][credits]
## Development
@@ -314,7 +313,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[3.1-announcement]: topics/3.1-announcement.md
[kickstarter-announcement]: topics/kickstarter-announcement.md
[release-notes]: topics/release-notes.md
-[credits]: topics/credits.md
[tox]: http://testrun.org/tox/latest/
diff --git a/docs/topics/3.1-announcement.md b/docs/topics/3.1-announcement.md
index d0975f5b..080ef1d8 100644
--- a/docs/topics/3.1-announcement.md
+++ b/docs/topics/3.1-announcement.md
@@ -10,7 +10,7 @@ The pagination API has been improved, making it both easier to use, and more pow
Until now, there has only been a single built-in pagination style in REST framework. We now have page, limit/offset and cursor based schemes included by default.
-The cursor based pagination scheme is particularly smart, and is a better approach for clients iterating through large or frequently changing result sets. The scheme supports paging against non-unique indexes, by using both cursor and limit/offset information. Credit to David Cramer for [this blog post](http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api/) on the subject.
+The cursor based pagination scheme is particularly smart, and is a better approach for clients iterating through large or frequently changing result sets. The scheme supports paging against non-unique indexes, by using both cursor and limit/offset information. It also allows for both forward and reverse cursor pagination. Much credit goes to David Cramer for [this blog post](http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api/) on the subject.
#### Pagination controls in the browsable API.
@@ -34,15 +34,74 @@ We've made it easier to build versioned APIs. Built-in schemes for versioning in
When using a URL based scheme, hyperlinked serializers will resolve relationships to the same API version as used on the incoming request.
-**TODO**: Example.
+For example, when using `NamespaceVersioning`, and the following hyperlinked serializer:
+
+ class AccountsSerializer(serializer.HyperlinkedModelSerializer):
+ class Meta:
+ model = Accounts
+ fields = ('account_name', 'users')
+
+The output representation would match the version used on the incoming request. Like so:
+
+ GET http://example.org/v2/accounts/10 # Version 'v2'
+
+ {
+ "account_name": "europa",
+ "users": [
+ "http://example.org/v2/users/12", # Version 'v2'
+ "http://example.org/v2/users/54",
+ "http://example.org/v2/users/87"
+ ]
+ }
## Internationalization
REST framework now includes a built-in set of translations, and supports internationalized error responses. This allows you to either change the default language, or to allow clients to specify the language via the `Accept-Language` header.
-**TODO**: Example.
+You can change the default language by using the standard Django `LANGUAGE_CODE` setting:
+
+ LANGUAGE_CODE = "es-es"
+
+You can turn on per-request language requests by adding `LocalMiddleware` to your `MIDDLEWARE_CLASSES` setting:
+
+ MIDDLEWARE_CLASSES = [
+ ...
+ 'django.middleware.locale.LocaleMiddleware'
+ ]
+
+When per-request internationalization is enabled, client requests will respect the `Accept-Language` header where possible. For example, let's make a request for an unsupported media type:
+
+**Request**
+
+ GET /api/users HTTP/1.1
+ Accept: application/xml
+ Accept-Language: es-es
+ Host: example.org
+
+**Response**
-**TODO**: Credit.
+ HTTP/1.0 406 NOT ACCEPTABLE
+
+ {
+ "detail": "No se ha podido satisfacer la solicitud de cabecera de Accept."
+ }
+
+Note that the structure of the error responses is still the same. We still have a `details` key in the response. If needed you can modify this behavior too, by using a [custom exception handler][custom-exception-handler].
+
+We include built-in translations both for standard exception cases, and for serializer validation errors.
+
+The full list of supported languages can be found on our [Transifex project page](https://www.transifex.com/projects/p/django-rest-framework/).
+
+If you only wish to support a subset of the supported languages, use Django's standard `LANGUAGES` setting:
+
+ LANGUAGES = [
+ ('de', _('German')),
+ ('en', _('English')),
+ ]
+
+For more details, see the [internationalization documentation](internationalization.md).
+
+Many thanks to [Craig Blaszczyk](https://github.com/jakul) for helping push this through.
## New field types
@@ -50,6 +109,10 @@ Django 1.8's new `ArrayField`, `HStoreField` and `UUIDField` are now all fully s
This work also means that we now have both `serializers.DictField()`, and `serializers.ListField()` types, allowing you to express and validate a wider set of representations.
+If you're building a new 1.8 project, then you should probably consider using `UUIDField` as the primary keys for all your models. This style will work automatically with hyperlinked serializers, returning URLs in the following style:
+
+ http://example.org/api/purchases/9b1a433f-e90d-4948-848b-300fdc26365d
+
## ModelSerializer API
The serializer redesign in 3.0 did not include any public API for modifying how ModelSerializer classes automatically generate a set of fields from a given mode class. We've now re-introduced an API for this, allowing you to create new ModelSerializer base classes that behave differently, such as using a different default style for relationships.
@@ -85,6 +148,8 @@ And modify your settings, like so:
]
}
+Thanks go to the latest member of our maintenance team, [José Padilla](https://github.com/jpadilla/), for handling this work and taking on ownership of these packages.
+
# What's next?
The next focus will be on HTML renderings of API output and will include:
@@ -93,4 +158,6 @@ The next focus will be on HTML renderings of API output and will include:
* Filtering controls built-in to the browsable API.
* An alternative admin-style interface.
-This will either be made as a single 3.2 release, or split across two separate releases, with the HTML forms and filter controls coming in 3.2, and the admin-style interface coming in a 3.3 release.
\ No newline at end of file
+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
diff --git a/docs/topics/credits.md b/docs/topics/credits.md
deleted file mode 100644
index 5f0dc752..00000000
--- a/docs/topics/credits.md
+++ /dev/null
@@ -1,404 +0,0 @@
-# Credits
-
-The following people have helped make REST framework great.
-
-* Tom Christie - [tomchristie]
-* Marko Tibold - [markotibold]
-* Paul Miller - [paulmillr]
-* Sébastien Piquemal - [sebpiq]
-* Carmen Wick - [cwick]
-* Alex Ehlke - [aehlke]
-* Alen Mujezinovic - [flashingpumpkin]
-* Carles Barrobés - [txels]
-* Michael Fötsch - [mfoetsch]
-* David Larlet - [david]
-* Andrew Straw - [astraw]
-* Zeth - [zeth]
-* Fernando Zunino - [fzunino]
-* Jens Alm - [ulmus]
-* Craig Blaszczyk - [jakul]
-* Garcia Solero - [garciasolero]
-* Tom Drummond - [devioustree]
-* Danilo Bargen - [dbrgn]
-* Andrew McCloud - [amccloud]
-* Thomas Steinacher - [thomasst]
-* Meurig Freeman - [meurig]
-* Anthony Nemitz - [anemitz]
-* Ewoud Kohl van Wijngaarden - [ekohl]
-* Michael Ding - [yandy]
-* Mjumbe Poe - [mjumbewu]
-* Natim - [natim]
-* Sebastian Żurek - [sebzur]
-* Benoit C - [dzen]
-* Chris Pickett - [bunchesofdonald]
-* Ben Timby - [btimby]
-* Michele Lazzeri - [michelelazzeri-nextage]
-* Camille Harang - [mammique]
-* Paul Oswald - [poswald]
-* Sean C. Farley - [scfarley]
-* Daniel Izquierdo - [izquierdo]
-* Can Yavuz - [tschan]
-* Shawn Lewis - [shawnlewis]
-* Alec Perkins - [alecperkins]
-* Michael Barrett - [phobologic]
-* Mathieu Dhondt - [laundromat]
-* Johan Charpentier - [cyberj]
-* Jamie Matthews - [j4mie]
-* Mattbo - [mattbo]
-* Max Hurl - [maximilianhurl]
-* Tomi Pajunen - [eofs]
-* Rob Dobson - [rdobson]
-* Daniel Vaca Araujo - [diviei]
-* Madis Väin - [madisvain]
-* Stephan Groß - [minddust]
-* Pavel Savchenko - [asfaltboy]
-* Otto Yiu - [ottoyiu]
-* Jacob Magnusson - [jmagnusson]
-* Osiloke Harold Emoekpere - [osiloke]
-* Michael Shepanski - [mjs7231]
-* Toni Michel - [tonimichel]
-* Ben Konrath - [benkonrath]
-* Marc Aymerich - [glic3rinu]
-* Ludwig Kraatz - [ludwigkraatz]
-* Rob Romano - [robromano]
-* Eugene Mechanism - [mechanism]
-* Jonas Liljestrand - [jonlil]
-* Justin Davis - [irrelative]
-* Dustin Bachrach - [dbachrach]
-* Mark Shirley - [maspwr]
-* Olivier Aubert - [oaubert]
-* Yuri Prezument - [yprez]
-* Fabian Buechler - [fabianbuechler]
-* Mark Hughes - [mhsparks]
-* Michael van de Waeter - [mvdwaeter]
-* Reinout van Rees - [reinout]
-* Michael Richards - [justanotherbody]
-* Ben Roberts - [roberts81]
-* Venkata Subramanian Mahalingam - [annacoder]
-* George Kappel - [gkappel]
-* Colin Murtaugh - [cmurtaugh]
-* Simon Pantzare - [pilt]
-* Szymon Teżewski - [sunscrapers]
-* Joel Marcotte - [joual]
-* Trey Hunner - [treyhunner]
-* Roman Akinfold - [akinfold]
-* Toran Billups - [toranb]
-* Sébastien Béal - [sebastibe]
-* Andrew Hankinson - [ahankinson]
-* Juan Riaza - [juanriaza]
-* Michael Mior - [michaelmior]
-* Marc Tamlyn - [mjtamlyn]
-* Richard Wackerbarth - [wackerbarth]
-* Johannes Spielmann - [shezi]
-* James Cleveland - [radiosilence]
-* Steve Gregory - [steve-gregory]
-* Federico Capoano - [nemesisdesign]
-* Bruno Renié - [brutasse]
-* Kevin Stone - [kevinastone]
-* Guglielmo Celata - [guglielmo]
-* Mike Tums - [mktums]
-* Michael Elovskikh - [wronglink]
-* Michał Jaworski - [swistakm]
-* Andrea de Marco - [z4r]
-* Fernando Rocha - [fernandogrd]
-* Xavier Ordoquy - [xordoquy]
-* Adam Wentz - [floppya]
-* Andreas Pelme - [pelme]
-* Ryan Detzel - [ryanrdetzel]
-* Omer Katz - [thedrow]
-* Wiliam Souza - [waa]
-* Jonas Braun - [iekadou]
-* Ian Dash - [bitmonkey]
-* Bouke Haarsma - [bouke]
-* Pierre Dulac - [dulaccc]
-* Dave Kuhn - [kuhnza]
-* Sitong Peng - [stoneg]
-* Victor Shih - [vshih]
-* Atle Frenvik Sveen - [atlefren]
-* J Paul Reed - [preed]
-* Matt Majewski - [forgingdestiny]
-* Jerome Chen - [chenjyw]
-* Andrew Hughes - [eyepulp]
-* Daniel Hepper - [dhepper]
-* Hamish Campbell - [hamishcampbell]
-* Marlon Bailey - [avinash240]
-* James Summerfield - [jsummerfield]
-* Andy Freeland - [rouge8]
-* Craig de Stigter - [craigds]
-* Pablo Recio - [pyriku]
-* Brian Zambrano - [brianz]
-* Òscar Vilaplana - [grimborg]
-* Ryan Kaskel - [ryankask]
-* Andy McKay - [andymckay]
-* Matteo Suppo - [matteosuppo]
-* Karol Majta - [lolek09]
-* David Jones - [commonorgarden]
-* Andrew Tarzwell - [atarzwell]
-* Michal Dvořák - [mikee2185]
-* Markus Törnqvist - [mjtorn]
-* Pascal Borreli - [pborreli]
-* Alex Burgel - [aburgel]
-* David Medina - [copitux]
-* Areski Belaid - [areski]
-* Ethan Freman - [mindlace]
-* David Sanders - [davesque]
-* Philip Douglas - [freakydug]
-* Igor Kalat - [trwired]
-* Rudolf Olah - [omouse]
-* Gertjan Oude Lohuis - [gertjanol]
-* Matthias Jacob - [cyroxx]
-* Pavel Zinovkin - [pzinovkin]
-* Will Kahn-Greene - [willkg]
-* Kevin Brown - [kevin-brown]
-* Rodrigo Martell - [coderigo]
-* James Rutherford - [jimr]
-* Ricky Rosario - [rlr]
-* Veronica Lynn - [kolvia]
-* Dan Stephenson - [etos]
-* Martin Clement - [martync]
-* Jeremy Satterfield - [jsatt]
-* Christopher Paolini - [chrispaolini]
-* Filipe A Ximenes - [filipeximenes]
-* Ramiro Morales - [ramiro]
-* Krzysztof Jurewicz - [krzysiekj]
-* Eric Buehl - [ericbuehl]
-* Kristian Øllegaard - [kristianoellegaard]
-* Alexander Akhmetov - [alexander-akhmetov]
-* Andrey Antukh - [niwibe]
-* Mathieu Pillard - [diox]
-* Edmond Wong - [edmondwong]
-* Ben Reilly - [bwreilly]
-* Tai Lee - [mrmachine]
-* Markus Kaiserswerth - [mkai]
-* Henry Clifford - [hcliff]
-* Thomas Badaud - [badale]
-* Colin Huang - [tamakisquare]
-* Ross McFarland - [ross]
-* Jacek Bzdak - [jbzdak]
-* Alexander Lukanin - [alexanderlukanin13]
-* Yamila Moreno - [yamila-moreno]
-* Rob Hudson - [robhudson]
-* Alex Good - [alexjg]
-* Ian Foote - [ian-foote]
-* Chuck Harmston - [chuckharmston]
-* Philip Forget - [philipforget]
-* Artem Mezhenin - [amezhenin]
-
-Many thanks to everyone who's contributed to the project.
-
-## Additional thanks
-
-The documentation is built with [Bootstrap] and [Markdown].
-
-Project hosting is with [GitHub].
-
-Continuous integration testing is managed with [Travis CI][travis-ci].
-
-The [live sandbox][sandbox] is hosted on [Heroku].
-
-Various inspiration taken from the [Rails], [Piston], [Tastypie], [Dagny] and [django-viewsets] projects.
-
-Development of REST framework 2.0 was sponsored by [DabApps].
-
-## Contact
-
-For usage questions please see the [REST framework discussion group][group].
-
-You can also contact [@_tomchristie][twitter] directly on twitter.
-
-[twitter]: http://twitter.com/_tomchristie
-[bootstrap]: http://twitter.github.com/bootstrap/
-[markdown]: http://daringfireball.net/projects/markdown/
-[github]: https://github.com/tomchristie/django-rest-framework
-[travis-ci]: https://secure.travis-ci.org/tomchristie/django-rest-framework
-[rails]: http://rubyonrails.org/
-[piston]: https://bitbucket.org/jespern/django-piston
-[tastypie]: https://github.com/toastdriven/django-tastypie
-[dagny]: https://github.com/zacharyvoase/dagny
-[django-viewsets]: https://github.com/BertrandBordage/django-viewsets
-[dabapps]: http://lab.dabapps.com
-[sandbox]: http://restframework.herokuapp.com/
-[heroku]: http://www.heroku.com/
-[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
-
-[tomchristie]: https://github.com/tomchristie
-[markotibold]: https://github.com/markotibold
-[paulmillr]: https://github.com/paulmillr
-[sebpiq]: https://github.com/sebpiq
-[cwick]: https://github.com/cwick
-[aehlke]: https://github.com/aehlke
-[flashingpumpkin]: https://github.com/flashingpumpkin
-[txels]: https://github.com/txels
-[mfoetsch]: https://github.com/mfoetsch
-[david]: https://github.com/david
-[astraw]: https://github.com/astraw
-[zeth]: https://github.com/zeth
-[fzunino]: https://github.com/fzunino
-[ulmus]: https://github.com/ulmus
-[jakul]: https://github.com/jakul
-[garciasolero]: https://github.com/garciasolero
-[devioustree]: https://github.com/devioustree
-[dbrgn]: https://github.com/dbrgn
-[amccloud]: https://github.com/amccloud
-[thomasst]: https://github.com/thomasst
-[meurig]: https://github.com/meurig
-[anemitz]: https://github.com/anemitz
-[ekohl]: https://github.com/ekohl
-[yandy]: https://github.com/yandy
-[mjumbewu]: https://github.com/mjumbewu
-[natim]: https://github.com/natim
-[sebzur]: https://github.com/sebzur
-[dzen]: https://github.com/dzen
-[bunchesofdonald]: https://github.com/bunchesofdonald
-[btimby]: https://github.com/btimby
-[michelelazzeri-nextage]: https://github.com/michelelazzeri-nextage
-[mammique]: https://github.com/mammique
-[poswald]: https://github.com/poswald
-[scfarley]: https://github.com/scfarley
-[izquierdo]: https://github.com/izquierdo
-[tschan]: https://github.com/tschan
-[shawnlewis]: https://github.com/shawnlewis
-[alecperkins]: https://github.com/alecperkins
-[phobologic]: https://github.com/phobologic
-[laundromat]: https://github.com/laundromat
-[cyberj]: https://github.com/cyberj
-[j4mie]: https://github.com/j4mie
-[mattbo]: https://github.com/mattbo
-[maximilianhurl]: https://github.com/maximilianhurl
-[eofs]: https://github.com/eofs
-[rdobson]: https://github.com/rdobson
-[diviei]: https://github.com/diviei
-[madisvain]: https://github.com/madisvain
-[minddust]: https://github.com/minddust
-[asfaltboy]: https://github.com/asfaltboy
-[ottoyiu]: https://github.com/OttoYiu
-[jmagnusson]: https://github.com/jmagnusson
-[osiloke]: https://github.com/osiloke
-[mjs7231]: https://github.com/mjs7231
-[tonimichel]: https://github.com/tonimichel
-[benkonrath]: https://github.com/benkonrath
-[glic3rinu]: https://github.com/glic3rinu
-[ludwigkraatz]: https://github.com/ludwigkraatz
-[robromano]: https://github.com/robromano
-[mechanism]: https://github.com/mechanism
-[jonlil]: https://github.com/jonlil
-[irrelative]: https://github.com/irrelative
-[dbachrach]: https://github.com/dbachrach
-[maspwr]: https://github.com/maspwr
-[oaubert]: https://github.com/oaubert
-[yprez]: https://github.com/yprez
-[fabianbuechler]: https://github.com/fabianbuechler
-[mhsparks]: https://github.com/mhsparks
-[mvdwaeter]: https://github.com/mvdwaeter
-[reinout]: https://github.com/reinout
-[justanotherbody]: https://github.com/justanotherbody
-[roberts81]: https://github.com/roberts81
-[annacoder]: https://github.com/annacoder
-[gkappel]: https://github.com/gkappel
-[cmurtaugh]: https://github.com/cmurtaugh
-[pilt]: https://github.com/pilt
-[sunscrapers]: https://github.com/sunscrapers
-[joual]: https://github.com/joual
-[treyhunner]: https://github.com/treyhunner
-[akinfold]: https://github.com/akinfold
-[toranb]: https://github.com/toranb
-[sebastibe]: https://github.com/sebastibe
-[ahankinson]: https://github.com/ahankinson
-[juanriaza]: https://github.com/juanriaza
-[michaelmior]: https://github.com/michaelmior
-[mjtamlyn]: https://github.com/mjtamlyn
-[wackerbarth]: https://github.com/wackerbarth
-[shezi]: https://github.com/shezi
-[radiosilence]: https://github.com/radiosilence
-[steve-gregory]: https://github.com/steve-gregory
-[nemesisdesign]: https://github.com/nemesisdesign
-[brutasse]: https://github.com/brutasse
-[kevinastone]: https://github.com/kevinastone
-[guglielmo]: https://github.com/guglielmo
-[mktums]: https://github.com/mktums
-[wronglink]: https://github.com/wronglink
-[swistakm]: https://github.com/swistakm
-[z4r]: https://github.com/z4r
-[fernandogrd]: https://github.com/fernandogrd
-[xordoquy]: https://github.com/xordoquy
-[floppya]: https://github.com/floppya
-[pelme]: https://github.com/pelme
-[ryanrdetzel]: https://github.com/ryanrdetzel
-[thedrow]: https://github.com/thedrow
-[waa]: https://github.com/wiliamsouza
-[iekadou]: https://github.com/iekadou
-[bitmonkey]: https://github.com/bitmonkey
-[bouke]: https://github.com/bouke
-[dulaccc]: https://github.com/dulaccc
-[kuhnza]: https://github.com/kuhnza
-[stoneg]: https://github.com/stoneg
-[vshih]: https://github.com/vshih
-[atlefren]: https://github.com/atlefren
-[preed]: https://github.com/preed
-[forgingdestiny]: https://github.com/forgingdestiny
-[chenjyw]: https://github.com/chenjyw
-[eyepulp]: https://github.com/eyepulp
-[dhepper]: https://github.com/dhepper
-[hamishcampbell]: https://github.com/hamishcampbell
-[avinash240]: https://github.com/avinash240
-[jsummerfield]: https://github.com/jsummerfield
-[rouge8]: https://github.com/rouge8
-[craigds]: https://github.com/craigds
-[pyriku]: https://github.com/pyriku
-[brianz]: https://github.com/brianz
-[grimborg]: https://github.com/grimborg
-[ryankask]: https://github.com/ryankask
-[andymckay]: https://github.com/andymckay
-[matteosuppo]: https://github.com/matteosuppo
-[lolek09]: https://github.com/lolek09
-[commonorgarden]: https://github.com/commonorgarden
-[atarzwell]: https://github.com/atarzwell
-[mikee2185]: https://github.com/mikee2185
-[mjtorn]: https://github.com/mjtorn
-[pborreli]: https://github.com/pborreli
-[aburgel]: https://github.com/aburgel
-[copitux]: https://github.com/copitux
-[areski]: https://github.com/areski
-[mindlace]: https://github.com/mindlace
-[davesque]: https://github.com/davesque
-[freakydug]: https://github.com/freakydug
-[trwired]: https://github.com/trwired
-[omouse]: https://github.com/omouse
-[gertjanol]: https://github.com/gertjanol
-[cyroxx]: https://github.com/cyroxx
-[pzinovkin]: https://github.com/pzinovkin
-[coderigo]: https://github.com/coderigo
-[willkg]: https://github.com/willkg
-[kevin-brown]: https://github.com/kevin-brown
-[jimr]: https://github.com/jimr
-[rlr]: https://github.com/rlr
-[kolvia]: https://github.com/kolvia
-[etos]: https://github.com/etos
-[martync]: https://github.com/martync
-[jsatt]: https://github.com/jsatt
-[chrispaolini]: https://github.com/chrispaolini
-[filipeximenes]: https://github.com/filipeximenes
-[ramiro]: https://github.com/ramiro
-[krzysiekj]: https://github.com/krzysiekj
-[ericbuehl]: https://github.com/ericbuehl
-[kristianoellegaard]: https://github.com/kristianoellegaard
-[alexander-akhmetov]: https://github.com/alexander-akhmetov
-[niwibe]: https://github.com/niwibe
-[diox]: https://github.com/diox
-[edmondwong]: https://github.com/edmondwong
-[bwreilly]: https://github.com/bwreilly
-[mrmachine]: https://github.com/mrmachine
-[mkai]: https://github.com/mkai
-[hcliff]: https://github.com/hcliff
-[badale]: https://github.com/badale
-[tamakisquare]: https://github.com/tamakisquare
-[ross]: https://github.com/ross
-[jbzdak]: https://github.com/jbzdak
-[alexanderlukanin13]: https://github.com/alexanderlukanin13
-[yamila-moreno]: https://github.com/yamila-moreno
-[robhudson]: https://github.com/robhudson
-[alexjg]: https://github.com/alexjg
-[ian-foote]: https://github.com/ian-foote
-[chuckharmston]: https://github.com/chuckharmston
-[philipforget]: https://github.com/philipforget
-[amezhenin]: https://github.com/amezhenin
diff --git a/docs/topics/internationalization.md b/docs/topics/internationalization.md
index fdde6c43..3968e23d 100644
--- a/docs/topics/internationalization.md
+++ b/docs/topics/internationalization.md
@@ -11,12 +11,53 @@ Doing so will allow you to:
* Select a language other than English as the default, using the standard `LANGUAGE_CODE` Django setting.
* Allow clients to choose a language themselves, using the `LocaleMiddleware` included with Django. A typical usage for API clients would be to include an `Accept-Language` request header.
+## Enabling internationalized APIs
+
+You can change the default language by using the standard Django `LANGUAGE_CODE` setting:
+
+ LANGUAGE_CODE = "es-es"
+
+You can turn on per-request language requests by adding `LocalMiddleware` to your `MIDDLEWARE_CLASSES` setting:
+
+ MIDDLEWARE_CLASSES = [
+ ...
+ 'django.middleware.locale.LocaleMiddleware'
+ ]
+
+When per-request internationalization is enabled, client requests will respect the `Accept-Language` header where possible. For example, let's make a request for an unsupported media type:
+
+**Request**
+
+ GET /api/users HTTP/1.1
+ Accept: application/xml
+ Accept-Language: es-es
+ Host: example.org
+
+**Response**
+
+ HTTP/1.0 406 NOT ACCEPTABLE
+
+ {"detail": "No se ha podido satisfacer la solicitud de cabecera de Accept."}
+
+REST framework includes these built-in translations both for standard exception cases, and for serializer validation errors.
+
Note that the translations only apply to the error strings themselves. The format of error messages, and the keys of field names will remain the same. An example `400 Bad Request` response body might look like this:
{"detail": {"username": ["Esse campo deve ser unico."]}}
If you want to use different string for parts of the response such as `detail` and `non_field_errors` then you can modify this behavior by using a [custom exception handler][custom-exception-handler].
+#### Specifying the set of supported languages.
+
+By default all available languages will be supported.
+
+If you only wish to support a subset of the available languages, use Django's standard `LANGUAGES` setting:
+
+ LANGUAGES = [
+ ('de', _('German')),
+ ('en', _('English')),
+ ]
+
## Adding new translations
REST framework translations are managed online using [Transifex][transifex-project]. You can use the Transifex service to add new translation languages. The maintenance team will then ensure that these translation strings are included in the REST framework package.
diff --git a/mkdocs.yml b/mkdocs.yml
index 57b45054..8aacc2df 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -54,4 +54,3 @@ pages:
- ['topics/3.1-announcement.md', 'Topics', '3.1 Announcement']
- ['topics/kickstarter-announcement.md', 'Topics', 'Kickstarter Announcement']
- ['topics/release-notes.md', 'Topics', 'Release Notes']
- - ['topics/credits.md', 'Topics', 'Credits']
--
cgit v1.2.3
From 1d4956f61615ed32f52b2f92bb8ae31b09279a34 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 6 Feb 2015 13:08:04 +0000
Subject: Pagination images
---
docs/img/cursor-pagination.png | Bin 0 -> 12221 bytes
docs/img/pages-pagination.png | Bin 0 -> 10229 bytes
docs/topics/3.1-announcement.md | 33 ++++++++++++++++++++++++++++-----
docs_theme/css/default.css | 4 ++++
4 files changed, 32 insertions(+), 5 deletions(-)
create mode 100644 docs/img/cursor-pagination.png
create mode 100644 docs/img/pages-pagination.png
diff --git a/docs/img/cursor-pagination.png b/docs/img/cursor-pagination.png
new file mode 100644
index 00000000..1c9c99b6
Binary files /dev/null and b/docs/img/cursor-pagination.png differ
diff --git a/docs/img/pages-pagination.png b/docs/img/pages-pagination.png
new file mode 100644
index 00000000..4ce1a09a
Binary files /dev/null and b/docs/img/pages-pagination.png differ
diff --git a/docs/topics/3.1-announcement.md b/docs/topics/3.1-announcement.md
index 080ef1d8..fb4fa083 100644
--- a/docs/topics/3.1-announcement.md
+++ b/docs/topics/3.1-announcement.md
@@ -2,6 +2,17 @@
The 3.1 release is an intermediate step in the Kickstarter project releases, and includes a range of new functionality.
+Some highlights include:
+
+* A super-smart cursor pagination scheme.
+* An improved pagination API, supporting header or in-body pagination styles.
+* Pagination controls rendering in the browsable API.
+* Better support for API versioning.
+* Built-in internalization support.
+* Support for Django 1.8's `HStoreField` and `ArrayField`.
+
+---
+
## Pagination
The pagination API has been improved, making it both easier to use, and more powerful.
@@ -14,13 +25,13 @@ The cursor based pagination scheme is particularly smart, and is a better approa
#### Pagination controls in the browsable API.
-Paginated results now include controls that render directly in the browsable API. If you're using the page or limit/offset style, then you'll see a page based control displayed in the browsable API.
+Paginated results now include controls that render directly in the browsable API. If you're using the page or limit/offset style, then you'll see a page based control displayed in the browsable API:
-**IMAGE**
+
-The cursor based pagination renders a more simple 'Previous'/'Next' control.
+The cursor based pagination renders a more simple style of control:
-**IMAGE**
+
#### Support for header-based pagination.
@@ -28,6 +39,8 @@ The pagination API was previously only able to alter the pagination style in the
For more information, see the [custom pagination styles](../api-guide/pagination/#custom-pagination-styles) documentation.
+---
+
## Versioning
We've made it easier to build versioned APIs. Built-in schemes for versioning include both URL based and Accept header based variations.
@@ -54,6 +67,8 @@ The output representation would match the version used on the incoming request.
]
}
+---
+
## 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.
@@ -103,6 +118,8 @@ For more details, see the [internationalization documentation](internationalizat
Many thanks to [Craig Blaszczyk](https://github.com/jakul) for helping push this through.
+---
+
## New field types
Django 1.8's new `ArrayField`, `HStoreField` and `UUIDField` are now all fully supported.
@@ -113,12 +130,16 @@ If you're building a new 1.8 project, then you should probably consider using `U
http://example.org/api/purchases/9b1a433f-e90d-4948-848b-300fdc26365d
+---
+
## ModelSerializer API
The serializer redesign in 3.0 did not include any public API for modifying how ModelSerializer classes automatically generate a set of fields from a given mode class. We've now re-introduced an API for this, allowing you to create new ModelSerializer base classes that behave differently, such as using a different default style for relationships.
For more information, see the documentation on [customizing field mappings](../api-guide/serializers/#customizing-field-mappings) for ModelSerializer classes.
+---
+
## Moving packages out of core
We've now moved a number of packages out of the core of REST framework, and into separately installable packages. If you're currently using these you don't need to worry, you simply need to `pip install` the new packages, and change any import paths.
@@ -150,7 +171,9 @@ And modify your settings, like so:
Thanks go to the latest member of our maintenance team, [José Padilla](https://github.com/jpadilla/), for handling this work and taking on ownership of these packages.
-# What's next?
+---
+
+## What's next?
The next focus will be on HTML renderings of API output and will include:
diff --git a/docs_theme/css/default.css b/docs_theme/css/default.css
index 3feff0ba..d998fbee 100644
--- a/docs_theme/css/default.css
+++ b/docs_theme/css/default.css
@@ -186,6 +186,10 @@ body{
margin-top: 15px
}
+#main-content img {
+ display: block;
+ margin: 40px auto;
+}
/* custom navigation styles */
.navbar .navbar-inner{
--
cgit v1.2.3
From 1f996128458570a909d13f15c3d739fb12111984 Mon Sep 17 00:00:00 2001
From: Tom Christie
Date: Fri, 6 Feb 2015 13:21:35 +0000
Subject: Upgrade pending deprecations to deprecations
---
docs/topics/3.1-announcement.md | 10 ++++++++++
rest_framework/request.py | 12 ++++++------
rest_framework/serializers.py | 12 ++++++------
rest_framework/views.py | 2 +-
4 files changed, 23 insertions(+), 13 deletions(-)
diff --git a/docs/topics/3.1-announcement.md b/docs/topics/3.1-announcement.md
index fb4fa083..7242a032 100644
--- a/docs/topics/3.1-announcement.md
+++ b/docs/topics/3.1-announcement.md
@@ -173,6 +173,16 @@ Thanks go to the latest member of our maintenance team, [José Padilla](https://
---
+## Deprecations
+
+The `request.DATA`, `request.FILES` and `request.QUERY_PARAMS` attributes move from pending deprecation, to deprecated. Use `request.data` and `request.query_params` instead, as discussed in the 3.0 release notes.
+
+The ModelSerializer Meta options for `write_only_fields`, `view_name` and `lookup_field` are also moved from pending deprecation, to deprecated. Use `extra_kwargs` instead, as discussed in the 3.0 release notes.
+
+All these attributes and options will still work in 3.1, but their usage will raise a warning. They will be fully removed in 3.2.
+
+---
+
## What's next?
The next focus will be on HTML renderings of API output and will include:
diff --git a/rest_framework/request.py b/rest_framework/request.py
index bf6ff670..86fb1ef1 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -219,8 +219,8 @@ class Request(object):
Synonym for `.query_params`, for backwards compatibility.
"""
warnings.warn(
- "`request.QUERY_PARAMS` is pending deprecation. Use `request.query_params` instead.",
- PendingDeprecationWarning,
+ "`request.QUERY_PARAMS` is deprecated. Use `request.query_params` instead.",
+ DeprecationWarning,
stacklevel=1
)
return self._request.GET
@@ -240,8 +240,8 @@ class Request(object):
arbitrary parsers, and also works on methods other than POST (eg PUT).
"""
warnings.warn(
- "`request.DATA` is pending deprecation. Use `request.data` instead.",
- PendingDeprecationWarning,
+ "`request.DATA` is deprecated. Use `request.data` instead.",
+ DeprecationWarning,
stacklevel=1
)
if not _hasattr(self, '_data'):
@@ -257,8 +257,8 @@ class Request(object):
arbitrary parsers, and also works on methods other than POST (eg PUT).
"""
warnings.warn(
- "`request.FILES` is pending deprecation. Use `request.data` instead.",
- PendingDeprecationWarning,
+ "`request.FILES` is deprecated. Use `request.data` instead.",
+ DeprecationWarning,
stacklevel=1
)
if not _hasattr(self, '_files'):
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 18821958..18b810df 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -1103,9 +1103,9 @@ class ModelSerializer(Serializer):
write_only_fields = getattr(self.Meta, 'write_only_fields', None)
if write_only_fields is not None:
warnings.warn(
- "The `Meta.write_only_fields` option is pending deprecation. "
+ "The `Meta.write_only_fields` option is deprecated. "
"Use `Meta.extra_kwargs={: {'write_only': True}}` instead.",
- PendingDeprecationWarning,
+ DeprecationWarning,
stacklevel=3
)
for field_name in write_only_fields:
@@ -1116,9 +1116,9 @@ class ModelSerializer(Serializer):
view_name = getattr(self.Meta, 'view_name', None)
if view_name is not None:
warnings.warn(
- "The `Meta.view_name` option is pending deprecation. "
+ "The `Meta.view_name` option is deprecated. "
"Use `Meta.extra_kwargs={'url': {'view_name': ...}}` instead.",
- PendingDeprecationWarning,
+ DeprecationWarning,
stacklevel=3
)
kwargs = extra_kwargs.get(api_settings.URL_FIELD_NAME, {})
@@ -1128,9 +1128,9 @@ class ModelSerializer(Serializer):
lookup_field = getattr(self.Meta, 'lookup_field', None)
if lookup_field is not None:
warnings.warn(
- "The `Meta.lookup_field` option is pending deprecation. "
+ "The `Meta.lookup_field` option is deprecated. "
"Use `Meta.extra_kwargs={'url': {'lookup_field': ...}}` instead.",
- PendingDeprecationWarning,
+ DeprecationWarning,
stacklevel=3
)
kwargs = extra_kwargs.get(api_settings.URL_FIELD_NAME, {})
diff --git a/rest_framework/views.py b/rest_framework/views.py
index 9445c840..b4abc4d9 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -409,7 +409,7 @@ class APIView(View):
warnings.warn(
'The `exception_handler(exc)` call signature is deprecated. '
'Use `exception_handler(exc, context) instead.',
- PendingDeprecationWarning
+ DeprecationWarning
)
response = exception_handler(exc)
else:
--
cgit v1.2.3