aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md20
-rw-r--r--[-rwxr-xr-x]docs/api-guide/authentication.md0
-rw-r--r--docs/api-guide/fields.md6
-rw-r--r--[-rwxr-xr-x]docs/api-guide/generic-views.md13
-rw-r--r--docs/api-guide/metadata.md8
-rw-r--r--docs/api-guide/pagination.md67
-rw-r--r--docs/api-guide/relations.md6
-rw-r--r--docs/api-guide/requests.md6
-rw-r--r--docs/api-guide/serializers.md8
-rw-r--r--docs/api-guide/testing.md2
-rw-r--r--docs/api-guide/validators.md14
-rw-r--r--docs/index.md4
-rw-r--r--docs/topics/3.1-announcement.md19
-rw-r--r--docs/topics/release-notes.md7
-rw-r--r--docs/tutorial/2-requests-and-responses.md2
-rw-r--r--docs/tutorial/5-relationships-and-hyperlinked-apis.md2
-rw-r--r--docs/tutorial/quickstart.md2
-rw-r--r--rest_framework/locale/de/LC_MESSAGES/django.mobin6575 -> 9184 bytes
-rw-r--r--rest_framework/locale/de/LC_MESSAGES/django.po69
-rw-r--r--rest_framework/locale/en_US/LC_MESSAGES/django.mobin378 -> 378 bytes
-rw-r--r--rest_framework/locale/es/LC_MESSAGES/django.mobin8906 -> 9201 bytes
-rw-r--r--rest_framework/locale/es/LC_MESSAGES/django.po12
-rw-r--r--rest_framework/locale/et/LC_MESSAGES/django.mobin2018 -> 8823 bytes
-rw-r--r--rest_framework/locale/et/LC_MESSAGES/django.po138
-rw-r--r--rest_framework/locale/pl/LC_MESSAGES/django.mobin8981 -> 9260 bytes
-rw-r--r--rest_framework/locale/pl/LC_MESSAGES/django.po11
-rw-r--r--rest_framework/locale/ru/LC_MESSAGES/django.mobin9778 -> 10429 bytes
-rw-r--r--rest_framework/locale/ru/LC_MESSAGES/django.po15
-rw-r--r--rest_framework/locale/tr/LC_MESSAGES/django.mobin7946 -> 9039 bytes
-rw-r--r--rest_framework/locale/tr/LC_MESSAGES/django.po21
-rw-r--r--rest_framework/pagination.py103
-rw-r--r--rest_framework/relations.py4
-rw-r--r--rest_framework/request.py16
-rw-r--r--rest_framework/routers.py5
-rw-r--r--rest_framework/settings.py11
-rw-r--r--rest_framework/templates/rest_framework/pagination/previous_and_next.html2
-rw-r--r--tests/conftest.py7
-rw-r--r--tests/test_pagination.py8
-rw-r--r--tests/test_relations_pk.py20
-rw-r--r--tests/test_routers.py38
40 files changed, 381 insertions, 285 deletions
diff --git a/README.md b/README.md
index 045cdbc4..5044ee0f 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,9 @@ Full documentation for the project is available at [http://www.django-rest-frame
---
-**Note**: We have now released Django REST framework 3.0. For older codebases you may want to refer to the version 2.4.4 [source code](https://github.com/tomchristie/django-rest-framework/tree/version-2.4.x), and [documentation](http://tomchristie.github.io/rest-framework-2-docs/).
+**Note**: We have now released Django REST framework 3.1. For older codebases you may want to refer to the version 2.4.4 [source code](https://github.com/tomchristie/django-rest-framework/tree/version-2.4.x), and [documentation](http://tomchristie.github.io/rest-framework-2-docs/).
+
+For more details see the [3.1 release notes][3.1-announcement]
---
@@ -23,7 +25,7 @@ Some reasons you might want to use REST framework:
* [Authentication policies][authentication] including optional packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
-* [Extensive documentation][index], and [great community support][group].
+* [Extensive documentation][docs], and [great community support][group].
There is a live example API for testing purposes, [available here][sandbox].
@@ -186,10 +188,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[pypi]: https://pypi.python.org/pypi/djangorestframework
[twitter]: https://twitter.com/_tomchristie
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
-[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
[sandbox]: http://restframework.herokuapp.com/
-[index]: http://www.django-rest-framework.org/
[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
[oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
[serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers
@@ -200,18 +200,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[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
-[tox]: http://testrun.org/tox/latest/
-
-[tehjones]: https://twitter.com/tehjones/status/294986071979196416
-[wlonk]: https://twitter.com/wlonk/status/261689665952833536
-[laserllama]: https://twitter.com/laserllama/status/328688333750407168
-
[docs]: http://www.django-rest-framework.org/
-[urlobject]: https://github.com/zacharyvoase/urlobject
-[markdown]: http://pypi.python.org/pypi/Markdown/
-[django-filter]: http://pypi.python.org/pypi/django-filter
[security-mail]: mailto:rest-framework-security@googlegroups.com
+[3.1-announcement]: http://www.django-rest-framework.org/topics/3.1-announcement/
diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md
index fe1be7bf..fe1be7bf 100755..100644
--- a/docs/api-guide/authentication.md
+++ b/docs/api-guide/authentication.md
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index f113bb23..5edc997a 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -1,11 +1,5 @@
source: fields.py
----
-
-**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
-
----
-
# Serializer fields
> Each field in a Form class is responsible not only for validating data, but also for "cleaning" it — normalizing it to a consistent format.
diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md
index 61c8e8d8..7df3d6ff 100755..100644
--- a/docs/api-guide/generic-views.md
+++ b/docs/api-guide/generic-views.md
@@ -1,12 +1,6 @@
source: mixins.py
generics.py
----
-
-**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
-
----
-
# Generic views
> Django’s generic views... were developed as a shortcut for common usage patterns... They take certain common idioms and patterns found in view development and abstract them so that you can quickly write common views of data without having to repeat yourself.
@@ -84,10 +78,9 @@ The following attributes control the basic view behavior.
The following attributes are used to control pagination when used with list views.
-* `paginate_by` - The size of pages to use with paginated data. If set to `None` then pagination is turned off. If unset this uses the same value as the `PAGINATE_BY` setting, which defaults to `None`.
-* `paginate_by_param` - The name of a query parameter, which can be used by the client to override the default page size to use for pagination. If unset this uses the same value as the `PAGINATE_BY_PARAM` setting, which defaults to `None`.
-* `pagination_serializer_class` - The pagination serializer class to use when determining the style of paginated responses. Defaults to the same value as the `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting.
-* `page_kwarg` - The name of a URL kwarg or URL query parameter which can be used by the client to control which page is requested. Defaults to `'page'`.
+* `pagination_class` - The pagination class that should be used when paginating list results. Defaults to the same value as the `DEFAULT_PAGINATION_CLASS` setting, which is `'rest_framework.pagination.PageNumberPagination'`.
+
+Note that usage of the `paginate_by`, `paginate_by_param` and `page_kwarg` attributes are now pending deprecation. The `pagination_serializer_class` attribute and `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting have been removed completely. Pagination settings should instead be controlled by overriding a pagination class and setting any configuration attributes there. See the pagination documentation for more details.
**Filtering**:
diff --git a/docs/api-guide/metadata.md b/docs/api-guide/metadata.md
index 247ae988..01727440 100644
--- a/docs/api-guide/metadata.md
+++ b/docs/api-guide/metadata.md
@@ -1,11 +1,5 @@
source: metadata.py
----
-
-**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
-
----
-
# Metadata
> [The `OPTIONS`] method allows a client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval.
@@ -59,7 +53,7 @@ Or you can set the metadata class individually for a view:
class APIRoot(APIView):
metadata_class = APIRootMetadata
-
+
def get(self, request, format=None):
return Response({
...
diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md
index 697ba38d..eca468b8 100644
--- a/docs/api-guide/pagination.md
+++ b/docs/api-guide/pagination.md
@@ -51,7 +51,8 @@ You can then apply your new style to a view using the `.pagination_class` attrib
Or apply the style globally, using the `DEFAULT_PAGINATION_CLASS` settings key. For example:
REST_FRAMEWORK = {
- 'DEFAULT_PAGINATION_CLASS': 'apps.core.pagination.StandardResultsSetPagination' }
+ 'DEFAULT_PAGINATION_CLASS': 'apps.core.pagination.StandardResultsSetPagination'
+ }
---
@@ -79,11 +80,11 @@ This pagination style accepts a single number page number in the request query p
#### Setup
-To enable the `PageNumberPagination` style globally, use the following configuration, modifying the `DEFAULT_PAGE_SIZE` as desired:
+To enable the `PageNumberPagination` style globally, use the following configuration, modifying the `PAGE_SIZE` as desired:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
- 'DEFAULT_PAGE_SIZE': 100
+ 'PAGE_SIZE': 100
}
On `GenericAPIView` subclasses you may also set the `pagination_class` attribute to select `PageNumberPagination` on a per-view basis.
@@ -94,7 +95,7 @@ The `PageNumberPagination` class includes a number of attributes that may be ove
To set these attributes you should override the `PageNumberPagination` class, and then enable your custom pagination class as above.
-* `page_size` - A numeric value indicating the page size. If set, this overrides the `DEFAULT_PAGE_SIZE` setting. Defaults to the same value as the `DEFAULT_PAGE_SIZE` settings key.
+* `page_size` - A numeric value indicating the page size. If set, this overrides the `PAGE_SIZE` setting. Defaults to the same value as the `PAGE_SIZE` settings key.
* `page_query_param` - A string value indicating the name of the query parameter to use for the pagination control.
* `page_size_query_param` - If set, this is a string value indicating the name of a query parameter that allows the client to set the page size on a per-request basis. Defaults to `None`, indicating that the client may not control the requested page size.
* `max_page_size` - If set, this is a numeric value indicating the maximum allowable requested page size. This attribute is only valid if `page_size_query_param` is also set.
@@ -126,13 +127,13 @@ This pagination style mirrors the syntax used when looking up multiple database
#### Setup
-To enable the `PageNumberPagination` style globally, use the following configuration:
+To enable the `LimitOffsetPagination` style globally, use the following configuration:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination'
}
-Optionally, you may also set a `DEFAULT_PAGE_SIZE` key. If the `DEFAULT_PAGE_SIZE` parameter is also used then the `limit` query parameter will be optional, and may be omitted by the client.
+Optionally, you may also set a `PAGE_SIZE` key. If the `PAGE_SIZE` parameter is also used then the `limit` query parameter will be optional, and may be omitted by the client.
On `GenericAPIView` subclasses you may also set the `pagination_class` attribute to select `LimitOffsetPagination` on a per-view basis.
@@ -142,7 +143,7 @@ The `LimitOffsetPagination` class includes a number of attributes that may be ov
To set these attributes you should override the `LimitOffsetPagination` class, and then enable your custom pagination class as above.
-* `default_limit` - A numeric value indicating the limit to use if one is not provided by the client in a query parameter. Defaults to the same value as the `DEFAULT_PAGE_SIZE` settings key.
+* `default_limit` - A numeric value indicating the limit to use if one is not provided by the client in a query parameter. Defaults to the same value as the `PAGE_SIZE` settings key.
* `limit_query_param` - A string value indicating the name of the "limit" query parameter. Defaults to `'limit'`.
* `offset_query_param` - A string value indicating the name of the "offset" query parameter. Defaults to `'offset'`.
* `max_limit` - If set this is a numeric value indicating the maximum allowable limit that may be requested by the client. Defaults to `None`.
@@ -158,28 +159,33 @@ Cursor based pagination requires that there is a unique, unchanging ordering of
Cursor based pagination is more complex than other schemes. It also requires that the result set presents a fixed ordering, and does not allow the client to arbitrarily index into the result set. However it does provide the following benefits:
-* Provides a consistent pagination view. When used properly `CursorPagination` ensures that the client will never see the same item twice when paging through records.
+* Provides a consistent pagination view. When used properly `CursorPagination` ensures that the client will never see the same item twice when paging through records, even when new items are being inserted by other clients during the pagination process.
* Supports usage with very large datasets. With extremely large datasets pagination using offset-based pagination styles may become inefficient or unusable. Cursor based pagination schemes instead have fixed-time properties, and do not slow down as the dataset size increases.
#### Details and limitations
-This implementation of cursor pagination uses a smart "position plus offset" style that allows it to properly support not-strictly-unique values as the ordering.
+Proper use of cursor based pagination requires a little attention to detail. You'll need to think about what ordering you want the scheme to be applied against. The default is to order by `"-created"`. This assumes that **there must be a 'created' timestamp field** on the model instances, and will present a "timeline" style paginated view, with the most recently added items first.
-It should be noted that using non-unique values the ordering does introduce the possibility of paging artifacts, where pagination consistency is no longer 100% guaranteed.
+You can modify the ordering by overriding the `'ordering'` attribute on the pagination class, or by using the `OrderingFilter` filter class together with `CursorPagination`. When used with `OrderingFilter` you should strongly consider restricting the fields that the user may order by.
-**TODO**: Notes on `None`.
+Proper usage of cursor pagination should have an ordering field that satisfies the following:
-The implementation also supports both forward and reverse pagination, which is often not supported in other implementations.
+* Should be an unchanging value, such as a timestamp, slug, or other field that is only set once, on creation.
+* Should be unique, or nearly unique. Millisecond precision timestamps are a good example. This implementation of cursor pagination uses a smart "position plus offset" style that allows it to properly support not-strictly-unique values as the ordering.
+* Should be a non-nullable value that can be coerced to a string.
+* The field should have a database index.
+
+Using an ordering field that does not satisfy these constraints will generally still work, but you'll be loosing some of the benefits of cursor pagination.
For more technical details on the implementation we use for cursor pagination, the ["Building cursors for the Disqus API"][disqus-cursor-api] blog post gives a good overview of the basic approach.
#### Setup
-To enable the `CursorPagination` style globally, use the following configuration, modifying the `DEFAULT_PAGE_SIZE` as desired:
+To enable the `CursorPagination` style globally, use the following configuration, modifying the `PAGE_SIZE` as desired:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination',
- 'DEFAULT_PAGE_SIZE': 100
+ 'PAGE_SIZE': 100
}
On `GenericAPIView` subclasses you may also set the `pagination_class` attribute to select `CursorPagination` on a per-view basis.
@@ -192,7 +198,7 @@ To set these attributes you should override the `CursorPagination` class, and th
* `page_size` = A numeric value indicating the page size. If set, this overrides the `DEFAULT_PAGE_SIZE` setting. Defaults to the same value as the `DEFAULT_PAGE_SIZE` settings key.
* `cursor_query_param` = A string value indicating the name of the "cursor" query parameter. Defaults to `'cursor'`.
-* `ordering` = This should be a string, or list of strings, indicating the field against which the cursor based pagination will be applied. For example: `ordering = 'created'`. Any filters on the view which define a `get_ordering` will override this attribute. Defaults to `None`.
+* `ordering` = This should be a string, or list of strings, indicating the field against which the cursor based pagination will be applied. For example: `ordering = 'slug'`. Defaults to `-created`. This value may also be overridden by using `OrderingFilter` on the view.
* `template` = The name of a template to use when rendering pagination controls in the browsable API. May be overridden to modify the rendering style, or set to `None` to disable HTML pagination controls completely. Defaults to `"rest_framework/pagination/previous_and_next.html"`.
---
@@ -208,11 +214,36 @@ Note that the `paginate_queryset` method may set state on the pagination instanc
## Example
+Suppose we want to replace the default pagination output style with a modified format that includes the next and previous links under in a nested 'links' key. We could specify a custom pagination class like so:
+
+ class CustomPagination(pagination.PageNumberPagination):
+ def get_paginated_response(self, data):
+ return Response({
+ 'links': {
+ 'next': self.get_next_link(),
+ 'previous': self.get_previous_link()
+ },
+ 'count': self.page.paginator.count,
+ 'results': data
+ })
+
+We'd then need to setup the custom class in our configuration:
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.CustomPagination',
+ 'PAGE_SIZE': 100
+ }
+
+Note that if you care about how the ordering of keys is displayed in responses in the browsable API you might choose to use an `OrderedDict` when constructing the body of paginated responses, but this is optional.
+
+## Header based pagination
+
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].
class LinkHeaderPagination(pagination.PageNumberPagination):
def get_paginated_response(self, data):
- next_url = self.get_next_link() previous_url = self.get_previous_link()
+ next_url = self.get_next_link()
+ previous_url = self.get_previous_link()
if next_url is not None and previous_url is not None:
link = '<{next_url}; rel="next">, <{previous_url}; rel="prev">'
@@ -234,7 +265,7 @@ To have your custom pagination class be used by default, use the `DEFAULT_PAGINA
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.LinkHeaderPagination',
- 'DEFAULT_PAGE_SIZE': 10
+ 'PAGE_SIZE': 100
}
API responses for list endpoints will now include a `Link` header, instead of including the pagination links as part of the body of the response, for example:
@@ -283,4 +314,4 @@ The [`DRF-extensions` package][drf-extensions] includes a [`PaginateByMaxMixin`
[link-header]: ../img/link-header-pagination.png
[drf-extensions]: http://chibisov.github.io/drf-extensions/docs/
[paginate-by-max-mixin]: http://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin
-[disqus-cursor-api]: http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api/ \ No newline at end of file
+[disqus-cursor-api]: http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api/
diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md
index 50e3b7b5..093bbdd0 100644
--- a/docs/api-guide/relations.md
+++ b/docs/api-guide/relations.md
@@ -1,11 +1,5 @@
source: relations.py
----
-
-**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
-
----
-
# Serializer relations
> Bad programmers worry about the code.
diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md
index c993dfae..658a5ffd 100644
--- a/docs/api-guide/requests.md
+++ b/docs/api-guide/requests.md
@@ -1,11 +1,5 @@
source: request.py
----
-
-**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
-
----
-
# Requests
> If you're doing REST-based web service stuff ... you should ignore request.POST.
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 940eb424..aad2236f 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -1,11 +1,5 @@
source: serializers.py
----
-
-**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
-
----
-
# Serializers
> Expanding the usefulness of the serializers is something that we would
@@ -23,7 +17,7 @@ The serializers in REST framework work very similarly to Django's `Form` and `Mo
Let's start by creating a simple object we can use for example purposes:
from datetime import datetime
-
+
class Comment(object):
def __init__(self, email, content, created=None):
self.email = email
diff --git a/docs/api-guide/testing.md b/docs/api-guide/testing.md
index 8a848c20..dd3295c4 100644
--- a/docs/api-guide/testing.md
+++ b/docs/api-guide/testing.md
@@ -65,7 +65,7 @@ When testing views directly using a request factory, it's often convenient to be
To forcibly authenticate a request, use the `force_authenticate()` method.
- from rest_framework.tests import force_authenticate
+ from rest_framework.test import force_authenticate
factory = APIRequestFactory()
user = User.objects.get(username='olivia')
diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md
index 8f5a8929..40ad4857 100644
--- a/docs/api-guide/validators.md
+++ b/docs/api-guide/validators.md
@@ -1,11 +1,5 @@
source: validators.py
----
-
-**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
-
----
-
# Validators
> Validators can be useful for re-using validation logic between different types of fields.
@@ -33,7 +27,7 @@ When you're using `ModelSerializer` all of this is handled automatically for you
As an example of how REST framework uses explicit validation, we'll take a simple model class that has a field with a uniqueness constraint.
class CustomerReportRecord(models.Model):
- time_raised = models.DateTimeField(default=timezone.now, editable=False)
+ time_raised = models.DateTimeField(default=timezone.now, editable=False)
reference = models.CharField(unique=True, max_length=20)
description = models.TextField()
@@ -43,7 +37,7 @@ Here's a basic `ModelSerializer` that we can use for creating or updating instan
class Meta:
model = CustomerReportRecord
-If we open up the Django shell using `manage.py shell` we can now
+If we open up the Django shell using `manage.py shell` we can now
>>> from project.example.serializers import CustomerReportSerializer
>>> serializer = CustomerReportSerializer()
@@ -204,7 +198,7 @@ A validator may be any callable that raises a `serializers.ValidationError` on f
def even_number(value):
if value % 2 != 0:
- raise serializers.ValidationError('This field must be an even number.')
+ raise serializers.ValidationError('This field must be an even number.')
## Class based
@@ -213,7 +207,7 @@ To write a class based validator, use the `__call__` method. Class based validat
class MultipleOf:
def __init__(self, base):
self.base = base
-
+
def __call__(self, value):
if value % self.base != 0
message = 'This field must be a multiple of %d.' % self.base
diff --git a/docs/index.md b/docs/index.md
index 91766a0b..13f4d0c4 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -9,9 +9,9 @@
---
-**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
+**Note**: This is the documentation for the **version 3.1** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
-For more details see the [3.0 release notes][3.0-announcement].
+For more details see the [3.1 release notes][3.1-announcement].
---
diff --git a/docs/topics/3.1-announcement.md b/docs/topics/3.1-announcement.md
index f500101c..80d4007e 100644
--- a/docs/topics/3.1-announcement.md
+++ b/docs/topics/3.1-announcement.md
@@ -17,6 +17,15 @@ Some highlights include:
The pagination API has been improved, making it both easier to use, and more powerful.
+A guide to the headline features follows. For full details, see [the pagination documentation][pagination].
+
+Note that as a result of this work a number of settings keys and generic view attributes are now moved to pending deprecation. Controlling pagination styles is now largely handled by overriding a pagination class and modifying its configuration attributes.
+
+* The `PAGINATE_BY` settings key will continue to work but is now pending deprecation. The more obviously named `PAGE_SIZE` settings key should now be used instead.
+* The `PAGINATE_BY_PARAM`, `MAX_PAGINATE_BY` settings keys will continue to work but are now pending deprecation, in favor of setting configuration attributes on the configured pagination class.
+* The `paginate_by`, `page_query_param`, `paginate_by_param` and `max_paginate_by` generic view attributes will continue to work but are now pending deprecation, in favor of setting configuration attributes on the configured pagination class.
+* The `pagination_serializer_class` view attribute and `DEFAULT_PAGINATION_SERIALIZER_CLASS` settings key **are no longer valid**. The pagination API does not use serializers to determine the output format, and you'll need to instead override the `get_paginated_response` method on a pagination class in order to specify how the output format is controlled.
+
#### New pagination schemes.
Until now, there has only been a single built-in pagination style in REST framework. We now have page, limit/offset and cursor based schemes included by default.
@@ -43,7 +52,7 @@ For more information, see the [custom pagination styles](../api-guide/pagination
## Versioning
-We've made it easier to build versioned APIs. Built-in schemes for versioning include both URL based and Accept header based variations.
+We've made it [easier to build versioned APIs][versioning]. Built-in schemes for versioning include both URL based and Accept header based variations.
When using a URL based scheme, hyperlinked serializers will resolve relationships to the same API version as used on the incoming request.
@@ -71,7 +80,7 @@ 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.
+REST framework now includes a built-in set of translations, and [supports internationalized error responses][internationalization]. This allows you to either change the default language, or to allow clients to specify the language via the `Accept-Language` header.
You can change the default language by using the standard Django `LANGUAGE_CODE` setting:
@@ -136,7 +145,7 @@ If you're building a new 1.8 project, then you should probably consider using `U
The serializer redesign in 3.0 did not include any public API for modifying how ModelSerializer classes automatically generate a set of fields from a given mode class. We've now re-introduced an API for this, allowing you to create new ModelSerializer base classes that behave differently, such as using a different default style for relationships.
-For more information, see the documentation on [customizing field mappings](../api-guide/serializers/#customizing-field-mappings) for ModelSerializer classes.
+For more information, see the documentation on [customizing field mappings][customizing-field-mappings] for ModelSerializer classes.
---
@@ -194,3 +203,7 @@ The next focus will be on HTML renderings of API output and will include:
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
+[pagination]: ../api-guide/pagination.md
+[versioning]: ../api-guide/versioning.md
+[internationalization]: internationalization.md
+[customizing-field-mappings]: ../api-guide/serializers.md#customizing-field-mappings
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 51eb45c3..84d310c2 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -40,6 +40,11 @@ You can determine your currently installed version using `pip freeze`:
## 3.0.x series
+### 3.1.0
+
+**Date**: [5th March 2015][3.1.0-milestone].
+
+For full details see the [3.1 release announcement](3.1-announcement.md).
### 3.0.5
@@ -149,7 +154,7 @@ For older release notes, [please see the version 2.x documentation](old-release-
[2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion
[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]: http://tomchristie.github.io/rest-framework-2-docs/topics/release-notes#24x-series
+[old-release-notes]: https://github.com/tomchristie/django-rest-framework/blob/version-2.4.x/docs/topics/release-notes.md
[3.0.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.1+Release%22
[3.0.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.2+Release%22
diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md
index e2c173d6..2cad1e5d 100644
--- a/docs/tutorial/2-requests-and-responses.md
+++ b/docs/tutorial/2-requests-and-responses.md
@@ -200,7 +200,7 @@ See the [browsable api][browsable-api] topic for more information about the brow
In [tutorial part 3][tut-3], we'll start using class based views, and see how generic views reduce the amount of code we need to write.
-[json-url]: http://example.com/api/items/4.json
+[json-url]: http://example.com/api/items/4/.json
[devserver]: http://127.0.0.1:8000/snippets/
[browsable-api]: ../topics/browsable-api.md
[tut-1]: 1-serialization.md
diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
index 740a4ce2..91cdd6f1 100644
--- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md
+++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
@@ -141,7 +141,7 @@ The list views for users and code snippets could end up returning quite a lot of
We can change the default list style to use pagination, by modifying our `tutorial/settings.py` file slightly. Add the following setting:
REST_FRAMEWORK = {
- 'PAGINATE_BY': 10
+ 'PAGE_SIZE': 10
}
Note that settings in REST framework are all namespaced into a single dictionary setting, named 'REST_FRAMEWORK', which helps keep them well separated from your other project settings.
diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
index a4474c34..fe0ecbc7 100644
--- a/docs/tutorial/quickstart.md
+++ b/docs/tutorial/quickstart.md
@@ -123,7 +123,7 @@ We'd also like to set a few global settings. We'd like to turn on pagination, a
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
- 'PAGINATE_BY': 10
+ 'PAGE_SIZE': 10
}
Okay, we're done.
diff --git a/rest_framework/locale/de/LC_MESSAGES/django.mo b/rest_framework/locale/de/LC_MESSAGES/django.mo
index 48245c60..91f102eb 100644
--- a/rest_framework/locale/de/LC_MESSAGES/django.mo
+++ b/rest_framework/locale/de/LC_MESSAGES/django.mo
Binary files differ
diff --git a/rest_framework/locale/de/LC_MESSAGES/django.po b/rest_framework/locale/de/LC_MESSAGES/django.po
index 74bee416..387e916b 100644
--- a/rest_framework/locale/de/LC_MESSAGES/django.po
+++ b/rest_framework/locale/de/LC_MESSAGES/django.po
@@ -3,14 +3,15 @@
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
+# Fabian Büchler <fabian@buechler.io>, 2015
# Thomas Tanner, 2015
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n"
-"PO-Revision-Date: 2015-01-30 16:27+0000\n"
-"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
+"PO-Revision-Date: 2015-03-07 19:50+0000\n"
+"Last-Translator: Fabian Büchler <fabian@buechler.io>\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"
@@ -80,7 +81,7 @@ 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."
+msgstr "Kann die Accept Kopfzeile der Anfrage nicht erfüllen."
#: exceptions.py:121
msgid "Unsupported media type \"{media_type}\" in request."
@@ -101,7 +102,7 @@ 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."
+msgstr "\"{input}\" ist kein gültiger Wahrheitswert."
#: fields.py:550
msgid "This field may not be blank."
@@ -117,7 +118,7 @@ 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."
+msgstr "Gib eine gültige E-Mail Adresse an."
#: fields.py:604
msgid "This value does not match the required pattern."
@@ -127,15 +128,15 @@ msgstr "Dieser Wert passt nicht zu dem erforderlichen Muster."
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."
+msgstr "Gib 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."
+msgstr "Gib eine gültige URL ein."
#: fields.py:638
msgid "\"{value}\" is not a valid UUID."
-msgstr ""
+msgstr "\"{value}\" ist keine gültige UUID."
#: fields.py:657
msgid "A valid integer is required."
@@ -147,7 +148,7 @@ 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."
+msgstr "Stelle sicher, dass dieser Wert größer oder gleich {min_value} ist."
#: fields.py:660 fields.py:694 fields.py:730
msgid "String value too large."
@@ -174,11 +175,11 @@ msgstr "Stelle sicher, dass es nicht mehr als {max_whole_places} Stellen vor dem
#: 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}."
+msgstr "Datums- 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."
+msgstr "Erwarte eine Datums- und Zeitangabe, erhielt aber ein Datum."
#: fields.py:878
msgid "Date has wrong format. Use one of these formats instead: {format}."
@@ -186,7 +187,7 @@ msgstr "Datum hat das falsche Format. Nutze stattdessen eines dieser Formate: {f
#: fields.py:879
msgid "Expected a date but got a datetime."
-msgstr "Erwarte ein Datum, erhielt aber eine Datum- und Zeitangabe."
+msgstr "Erwarte ein Datum, erhielt aber eine Datums- und Zeitangabe."
#: fields.py:936
msgid "Time has wrong format. Use one of these formats instead: {format}."
@@ -207,7 +208,7 @@ 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."
+msgstr "Die übermittelten Daten stellen keine Datei dar. Prüfe den Kodierungstyp im Formular."
#: fields.py:1069
msgid "No filename could be determined."
@@ -220,53 +221,53 @@ msgstr "Die übermittelte Datei ist leer."
#: fields.py:1071
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
-msgstr ""
+msgstr "Stelle sicher, dass dieser Dateiname höchstens {max_length} Zeichen lang ist (er hat {length})."
#: fields.py:1113
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
-msgstr ""
+msgstr "Lade ein gültiges Bild hoch. Die hochgeladene Datei ist entweder kein Bild oder ein beschädigtes Bild."
#: fields.py:1188
msgid "Expected a dictionary of items but got type \"{input_type}\"."
-msgstr ""
+msgstr "Erwarte ein Dictionary mit Elementen, erhielt aber den Typ \"{input_type}\"."
#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
-msgstr ""
+msgstr "Ungültige Seite \"{page_number}\": {message}."
#: pagination.py:442
msgid "Invalid cursor"
-msgstr ""
+msgstr "Ungültiger Zeiger"
#: relations.py:133
msgid "Invalid pk \"{pk_value}\" - object does not exist."
-msgstr ""
+msgstr "Ungültiger pk \"{pk_value}\" - Object existiert nicht."
#: relations.py:134
msgid "Incorrect type. Expected pk value, received {data_type}."
-msgstr ""
+msgstr "Falscher Typ. Erwarte pk Wert, erhielt aber {data_type}."
#: relations.py:157
msgid "Invalid hyperlink - No URL match."
-msgstr ""
+msgstr "Ungültiger Hyperlink - entspricht keiner URL."
#: relations.py:158
msgid "Invalid hyperlink - Incorrect URL match."
-msgstr ""
+msgstr "Ungültiger Hyperlink - URL stimmt nicht überein."
#: relations.py:159
msgid "Invalid hyperlink - Object does not exist."
-msgstr ""
+msgstr "Ungültiger Hyperlink - Objekt existiert nicht."
#: relations.py:160
msgid "Incorrect type. Expected URL string, received {data_type}."
-msgstr ""
+msgstr "Falscher Typ. Erwarte URL Zeichenkette, erhielt aber {data_type}."
#: relations.py:295
msgid "Object with {slug_name}={value} does not exist."
-msgstr ""
+msgstr "Objekt mit {slug_name}={value} existiert nicht."
#: relations.py:296
msgid "Invalid value."
@@ -278,39 +279,39 @@ msgstr "Ungültige Daten. Dictionary erwartet, aber {datatype} erhalten."
#: validators.py:22
msgid "This field must be unique."
-msgstr "Dieses Feld muss eineindeutig sein."
+msgstr "Dieses Feld muss eindeutig sein."
#: validators.py:76
msgid "The fields {field_names} must make a unique set."
-msgstr ""
+msgstr "Die Felder {field_names} müssen eine eindeutige Menge bilden."
#: validators.py:219
msgid "This field must be unique for the \"{date_field}\" date."
-msgstr ""
+msgstr "Dieses Feld muss bezüglich des \"{date_field}\" Datums eindeutig sein."
#: validators.py:234
msgid "This field must be unique for the \"{date_field}\" month."
-msgstr ""
+msgstr "Dieses Feld muss bezüglich des \"{date_field}\" Monats eindeutig sein."
#: validators.py:247
msgid "This field must be unique for the \"{date_field}\" year."
-msgstr ""
+msgstr "Dieses Feld muss bezüglich des \"{date_field}\" Jahrs eindeutig sein."
#: versioning.py:39
msgid "Invalid version in \"Accept\" header."
-msgstr ""
+msgstr "Ungültige Version in der \"Accept\" Kopfzeile."
#: versioning.py:70 versioning.py:112
msgid "Invalid version in URL path."
-msgstr ""
+msgstr "Ungültige Version im URL Pfad."
#: versioning.py:138
msgid "Invalid version in hostname."
-msgstr ""
+msgstr "Ungültige Version im Hostname."
#: versioning.py:160
msgid "Invalid version in query parameter."
-msgstr ""
+msgstr "Ungültige Version im Anfrageparameter."
#: authtoken/serializers.py:20
msgid "User account is disabled."
diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.mo b/rest_framework/locale/en_US/LC_MESSAGES/django.mo
index eb60d9d7..8a8b9d55 100644
--- a/rest_framework/locale/en_US/LC_MESSAGES/django.mo
+++ b/rest_framework/locale/en_US/LC_MESSAGES/django.mo
Binary files differ
diff --git a/rest_framework/locale/es/LC_MESSAGES/django.mo b/rest_framework/locale/es/LC_MESSAGES/django.mo
index 814db7be..f8a064e3 100644
--- a/rest_framework/locale/es/LC_MESSAGES/django.mo
+++ b/rest_framework/locale/es/LC_MESSAGES/django.mo
Binary files differ
diff --git a/rest_framework/locale/es/LC_MESSAGES/django.po b/rest_framework/locale/es/LC_MESSAGES/django.po
index 28ef893d..9c097e76 100644
--- a/rest_framework/locale/es/LC_MESSAGES/django.po
+++ b/rest_framework/locale/es/LC_MESSAGES/django.po
@@ -4,15 +4,15 @@
#
# Translators:
# José Padilla <jpadilla@webapplicate.com>, 2015
-# Miguel González <migonzalvar@activitycentral.com>, 2015
+# Miguel González <migonzalvar@gmail.com>, 2015
# Sergio Infante <rsinfante@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n"
-"PO-Revision-Date: 2015-01-30 16:27+0000\n"
-"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
+"PO-Revision-Date: 2015-03-06 19:51+0000\n"
+"Last-Translator: Miguel González <migonzalvar@gmail.com>\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"
@@ -137,7 +137,7 @@ msgstr "Introduzca una URL válida."
#: fields.py:638
msgid "\"{value}\" is not a valid UUID."
-msgstr ""
+msgstr "\"{value}\" no es un UUID válido."
#: fields.py:657
msgid "A valid integer is required."
@@ -232,7 +232,7 @@ msgstr "Adjunte una imagen válida. El archivo adjunto o bien no es una imagen o
#: fields.py:1188
msgid "Expected a dictionary of items but got type \"{input_type}\"."
-msgstr ""
+msgstr "Se esperaba un diccionario de elementos en vez del tipo \"{input_type}\"."
#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
@@ -240,7 +240,7 @@ msgstr "Página \"{page_number}\" inválida: {message}."
#: pagination.py:442
msgid "Invalid cursor"
-msgstr ""
+msgstr "Cursor inválido"
#: relations.py:133
msgid "Invalid pk \"{pk_value}\" - object does not exist."
diff --git a/rest_framework/locale/et/LC_MESSAGES/django.mo b/rest_framework/locale/et/LC_MESSAGES/django.mo
index ca9b6ec4..74a159d4 100644
--- a/rest_framework/locale/et/LC_MESSAGES/django.mo
+++ b/rest_framework/locale/et/LC_MESSAGES/django.mo
Binary files differ
diff --git a/rest_framework/locale/et/LC_MESSAGES/django.po b/rest_framework/locale/et/LC_MESSAGES/django.po
index dec03d4d..7a4837e8 100644
--- a/rest_framework/locale/et/LC_MESSAGES/django.po
+++ b/rest_framework/locale/et/LC_MESSAGES/django.po
@@ -9,8 +9,8 @@ msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n"
-"PO-Revision-Date: 2015-01-30 16:27+0000\n"
-"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
+"PO-Revision-Date: 2015-02-27 16:24+0000\n"
+"Last-Translator: Tõnis Kärdi <tonis.kardi@gmail.com>\n"
"Language-Team: Estonian (http://www.transifex.com/projects/p/django-rest-framework/language/et/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,31 +20,31 @@ msgstr ""
#: authentication.py:69
msgid "Invalid basic header. No credentials provided."
-msgstr ""
+msgstr "Sobimatu lihtpäis. Kasutajatunnus on esitamata."
#: authentication.py:72
msgid "Invalid basic header. Credentials string should not contain spaces."
-msgstr ""
+msgstr "Sobimatu lihtpäis. Kasutajatunnus ei tohi sisaldada tühikuid."
#: authentication.py:78
msgid "Invalid basic header. Credentials not correctly base64 encoded."
-msgstr ""
+msgstr "Sobimatu lihtpäis. Kasutajatunnus pole korrektselt base64-kodeeritud."
#: authentication.py:90
msgid "Invalid username/password."
-msgstr "Vale kasutajatunnus/salasõna."
+msgstr "Sobimatu kasutajatunnus/salasõna."
#: authentication.py:156
msgid "Invalid token header. No credentials provided."
-msgstr ""
+msgstr "Sobimatu lubakaardi päis. Kasutajatunnus on esitamata."
#: authentication.py:159
msgid "Invalid token header. Token string should not contain spaces."
-msgstr ""
+msgstr "Sobimatu lubakaardi päis. Loa sõne ei tohi sisaldada tühikuid."
#: authentication.py:168
msgid "Invalid token."
-msgstr ""
+msgstr "Sobimatu lubakaart."
#: authentication.py:171
msgid "User inactive or deleted."
@@ -52,43 +52,43 @@ msgstr "Kasutaja on inaktiivne või kustutatud."
#: exceptions.py:38
msgid "A server error occurred."
-msgstr ""
+msgstr "Viga serveril."
#: exceptions.py:73
msgid "Malformed request."
-msgstr ""
+msgstr "Väändunud päring."
#: exceptions.py:78
msgid "Incorrect authentication credentials."
-msgstr ""
+msgstr "Ebakorrektne autentimistunnus."
#: exceptions.py:83
msgid "Authentication credentials were not provided."
-msgstr ""
+msgstr "Autentimistunnus on esitamata."
#: exceptions.py:88
msgid "You do not have permission to perform this action."
-msgstr ""
+msgstr "Teil puuduvad piisavad õigused selle tegevuse teostamiseks."
#: exceptions.py:93
msgid "Not found."
-msgstr ""
+msgstr "Ei leidnud."
#: exceptions.py:98
msgid "Method \"{method}\" not allowed."
-msgstr ""
+msgstr "Meetod \"{method}\" pole lubatud."
#: exceptions.py:109
msgid "Could not satisfy the request Accept header."
-msgstr ""
+msgstr "Päringu Accept-päist ei suutnud täita."
#: exceptions.py:121
msgid "Unsupported media type \"{media_type}\" in request."
-msgstr ""
+msgstr "Meedia tüüpi {media_type} päringus ei toetata."
#: exceptions.py:134
msgid "Request was throttled."
-msgstr ""
+msgstr "Liiga palju päringuid."
#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155
@@ -101,23 +101,23 @@ msgstr "Väli ei tohi olla tühi."
#: fields.py:487 fields.py:515
msgid "\"{input}\" is not a valid boolean."
-msgstr ""
+msgstr "\"{input}\" pole kehtiv kahendarv."
#: fields.py:550
msgid "This field may not be blank."
-msgstr ""
+msgstr "See väli ei tohi olla tühi."
#: fields.py:551 fields.py:1324
msgid "Ensure this field has no more than {max_length} characters."
-msgstr ""
+msgstr "Veendu, et see väli poleks pikem kui {max_length} tähemärki."
#: fields.py:552
msgid "Ensure this field has at least {min_length} characters."
-msgstr ""
+msgstr "Veendu, et see väli oleks vähemalt {min_length} tähemärki pikk."
#: fields.py:587
msgid "Enter a valid email address."
-msgstr "Sisesta kehtiv e-posti aadress."
+msgstr "Sisestage kehtiv e-posti aadress."
#: fields.py:604
msgid "This value does not match the required pattern."
@@ -127,27 +127,27 @@ msgstr "Väärtus ei ühti etteantud mustriga."
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
-msgstr ""
+msgstr "Sisestage kehtiv \"slug\", mis koosneks tähtedest, numbritest, ala- või sidekriipsudest."
#: fields.py:627
msgid "Enter a valid URL."
-msgstr "Sisesta korrektne URL."
+msgstr "Sisestage korrektne URL."
#: fields.py:638
msgid "\"{value}\" is not a valid UUID."
-msgstr ""
+msgstr "\"{value}\" pole kehtiv UUID."
#: fields.py:657
msgid "A valid integer is required."
-msgstr ""
+msgstr "Sisendiks peab olema täisarv."
#: 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}. "
+msgstr "Veenduge, 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}."
+msgstr "Veenduge, 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."
@@ -155,166 +155,166 @@ msgstr "Sõne on liiga pikk."
#: fields.py:691 fields.py:724
msgid "A valid number is required."
-msgstr ""
+msgstr "Sisendiks peab olema arv."
#: 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}."
+msgstr "Veenduge, et kokku pole rohkem kui {max_digits} numbit."
#: 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}. "
+msgstr "Veenduge, 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 ""
+msgstr "Veenduge, et täiskohti poleks rohkem kui {max_whole_digits}."
#: fields.py:813
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
-msgstr ""
+msgstr "Valesti formaaditud kuupäev-kellaaeg. Kasutage mõnda neist: {format}."
#: fields.py:814
msgid "Expected a datetime but got a date."
-msgstr ""
+msgstr "Ootasin kuupäev-kellaaeg andmetüüpi, kuid sain hoopis kuupäeva."
#: fields.py:878
msgid "Date has wrong format. Use one of these formats instead: {format}."
-msgstr ""
+msgstr "Valesti formaaditud kuupäev. Kasutage mõnda neist: {format}."
#: fields.py:879
msgid "Expected a date but got a datetime."
-msgstr ""
+msgstr "Ootasin kuupäeva andmetüüpi, kuid sain hoopis kuupäev-kellaaja."
#: fields.py:936
msgid "Time has wrong format. Use one of these formats instead: {format}."
-msgstr ""
+msgstr "Valesti formaaditud kellaaeg. Kasutage mõnda neist: {format}."
#: fields.py:992 fields.py:1036
msgid "\"{input}\" is not a valid choice."
-msgstr ""
+msgstr "\"{input}\" on sobimatu valik."
#: fields.py:1037 fields.py:1151 serializers.py:482
msgid "Expected a list of items but got type \"{input_type}\"."
-msgstr ""
+msgstr "Ootasin kirjete järjendit, kuid sain \"{input_type}\" - tüübi."
#: fields.py:1067
msgid "No file was submitted."
-msgstr ""
+msgstr "Ühtegi faili ei esitatud."
#: fields.py:1068
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
-msgstr ""
+msgstr "Esitatud andmetes ei olnud faili. Kontrollige vormi kodeeringut."
#: fields.py:1069
msgid "No filename could be determined."
-msgstr ""
+msgstr "Ei suutnud tuvastada failinime."
#: fields.py:1070
msgid "The submitted file is empty."
-msgstr ""
+msgstr "Esitatud fail oli tühi."
#: fields.py:1071
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
-msgstr ""
+msgstr "Veenduge, et failinimi oleks maksimaalselt {max_length} tähemärki pikk (praegu on {length})."
#: fields.py:1113
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
-msgstr ""
+msgstr "Laadige üles kehtiv pildifail. Üles laetud fail ei olnud pilt või oli see katki."
#: fields.py:1188
msgid "Expected a dictionary of items but got type \"{input_type}\"."
-msgstr ""
+msgstr "Ootasin kirjete sõnastikku, kuid sain \"{input_type}\"."
#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
-msgstr ""
+msgstr "Sobimatu lehekülg \"{page_number}\": {message}."
#: pagination.py:442
msgid "Invalid cursor"
-msgstr ""
+msgstr "Sobimatu kursor."
#: relations.py:133
msgid "Invalid pk \"{pk_value}\" - object does not exist."
-msgstr ""
+msgstr "Sobimatu primaarvõti \"{pk_value}\" - objekti pole olemas."
#: relations.py:134
msgid "Incorrect type. Expected pk value, received {data_type}."
-msgstr ""
+msgstr "Sobimatu andmetüüp. Ootasin primaarvõtit, sain {data_type}."
#: relations.py:157
msgid "Invalid hyperlink - No URL match."
-msgstr ""
+msgstr "Sobimatu hüperlink - ei leidnud URLi vastet."
#: relations.py:158
msgid "Invalid hyperlink - Incorrect URL match."
-msgstr ""
+msgstr "Sobimatu hüperlink - vale URLi vaste."
#: relations.py:159
msgid "Invalid hyperlink - Object does not exist."
-msgstr ""
+msgstr "Sobimatu hüperlink - objekti ei eksisteeri."
#: relations.py:160
msgid "Incorrect type. Expected URL string, received {data_type}."
-msgstr ""
+msgstr "Sobimatu andmetüüp. Ootasin URLi sõne, sain {data_type}."
#: relations.py:295
msgid "Object with {slug_name}={value} does not exist."
-msgstr ""
+msgstr "Objekti {slug_name}={value} ei eksisteeri."
#: relations.py:296
msgid "Invalid value."
-msgstr ""
+msgstr "Sobimatu väärtus."
#: serializers.py:299
msgid "Invalid data. Expected a dictionary, but got {datatype}."
-msgstr ""
+msgstr "Sobimatud andmed. Ootasin sõnastikku, kuid sain {datatype}."
#: validators.py:22
msgid "This field must be unique."
-msgstr ""
+msgstr "Selle välja väärtus peab olema unikaalne."
#: validators.py:76
msgid "The fields {field_names} must make a unique set."
-msgstr ""
+msgstr "Veerud {field_names} peavad moodustama unikaalse hulga."
#: validators.py:219
msgid "This field must be unique for the \"{date_field}\" date."
-msgstr ""
+msgstr "Selle välja väärtus peab olema unikaalne veerus \"{date_field}\" märgitud kuupäeval."
#: validators.py:234
msgid "This field must be unique for the \"{date_field}\" month."
-msgstr ""
+msgstr "Selle välja väärtus peab olema unikaalneveerus \"{date_field}\" märgitud kuul."
#: validators.py:247
msgid "This field must be unique for the \"{date_field}\" year."
-msgstr ""
+msgstr "Selle välja väärtus peab olema unikaalneveerus \"{date_field}\" märgitud aastal."
#: versioning.py:39
msgid "Invalid version in \"Accept\" header."
-msgstr ""
+msgstr "Sobimatu versioon \"Accept\" päises."
#: versioning.py:70 versioning.py:112
msgid "Invalid version in URL path."
-msgstr ""
+msgstr "Sobimatu versioon URLi rajas."
#: versioning.py:138
msgid "Invalid version in hostname."
-msgstr ""
+msgstr "Sobimatu versioon hostinimes."
#: versioning.py:160
msgid "Invalid version in query parameter."
-msgstr ""
+msgstr "Sobimatu versioon päringu parameetris."
#: authtoken/serializers.py:20
msgid "User account is disabled."
-msgstr "Kasutajakonto on suletud"
+msgstr "Kasutajakonto on suletud."
#: authtoken/serializers.py:23
msgid "Unable to log in with provided credentials."
diff --git a/rest_framework/locale/pl/LC_MESSAGES/django.mo b/rest_framework/locale/pl/LC_MESSAGES/django.mo
index 9db72cfb..a4e0d1f1 100644
--- a/rest_framework/locale/pl/LC_MESSAGES/django.mo
+++ b/rest_framework/locale/pl/LC_MESSAGES/django.mo
Binary files differ
diff --git a/rest_framework/locale/pl/LC_MESSAGES/django.po b/rest_framework/locale/pl/LC_MESSAGES/django.po
index 8e51d754..dbf2a077 100644
--- a/rest_framework/locale/pl/LC_MESSAGES/django.po
+++ b/rest_framework/locale/pl/LC_MESSAGES/django.po
@@ -4,14 +4,15 @@
#
# Translators:
# Janusz Harkot <jh@blueice.pl>, 2015
+# piotrjakimiak <legolass71@gmail.com>, 2015
# Maciek Olko <maciej.olko@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n"
-"PO-Revision-Date: 2015-01-30 16:27+0000\n"
-"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
+"PO-Revision-Date: 2015-03-04 17:03+0000\n"
+"Last-Translator: piotrjakimiak <legolass71@gmail.com>\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"
@@ -136,7 +137,7 @@ msgstr "Wprowadź poprawny adres URL."
#: fields.py:638
msgid "\"{value}\" is not a valid UUID."
-msgstr ""
+msgstr "\"{value}\" nie jest poprawnym UUID."
#: fields.py:657
msgid "A valid integer is required."
@@ -231,7 +232,7 @@ msgstr "Prześlij poprawny plik graficzny. Przesłany plik albo nie jest grafik
#: fields.py:1188
msgid "Expected a dictionary of items but got type \"{input_type}\"."
-msgstr ""
+msgstr "Oczekiwano słownika, ale otrzymano \"{input_type}\"."
#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
@@ -239,7 +240,7 @@ msgstr "Niepoprawna strona \"{page_number}\": {message}."
#: pagination.py:442
msgid "Invalid cursor"
-msgstr ""
+msgstr "Niepoprawny wskaźnik"
#: relations.py:133
msgid "Invalid pk \"{pk_value}\" - object does not exist."
diff --git a/rest_framework/locale/ru/LC_MESSAGES/django.mo b/rest_framework/locale/ru/LC_MESSAGES/django.mo
index d1555f1f..4d5145d3 100644
--- a/rest_framework/locale/ru/LC_MESSAGES/django.mo
+++ b/rest_framework/locale/ru/LC_MESSAGES/django.mo
Binary files differ
diff --git a/rest_framework/locale/ru/LC_MESSAGES/django.po b/rest_framework/locale/ru/LC_MESSAGES/django.po
index 38489747..dc2a37c1 100644
--- a/rest_framework/locale/ru/LC_MESSAGES/django.po
+++ b/rest_framework/locale/ru/LC_MESSAGES/django.po
@@ -3,14 +3,15 @@
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
+# Kirill Tarasenko, 2015
# Mikhail Dmitriev <mktums@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n"
-"PO-Revision-Date: 2015-01-30 16:27+0000\n"
-"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
+"PO-Revision-Date: 2015-02-23 10:40+0000\n"
+"Last-Translator: Kirill Tarasenko\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"
@@ -135,7 +136,7 @@ msgstr "Введите корректный URL."
#: fields.py:638
msgid "\"{value}\" is not a valid UUID."
-msgstr ""
+msgstr "\"{value}\" не является корректным UUID."
#: fields.py:657
msgid "A valid integer is required."
@@ -230,7 +231,7 @@ msgstr "Загрузите корректное изображение. Загр
#: fields.py:1188
msgid "Expected a dictionary of items but got type \"{input_type}\"."
-msgstr ""
+msgstr "Ожидался словарь со значениями, но был получен \"{input_type}\"."
#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
@@ -238,7 +239,7 @@ msgstr "Недопустимая страница \"{page_number}\": {message}."
#: pagination.py:442
msgid "Invalid cursor"
-msgstr ""
+msgstr "Не корректный курсор"
#: relations.py:133
msgid "Invalid pk \"{pk_value}\" - object does not exist."
@@ -278,11 +279,11 @@ msgstr "Недопустимые данные. Ожидался dictionary, но
#: validators.py:22
msgid "This field must be unique."
-msgstr ""
+msgstr "Это поле должно быть уникально."
#: validators.py:76
msgid "The fields {field_names} must make a unique set."
-msgstr ""
+msgstr "Поля {field_names} должны производить массив с уникальными значениями."
#: validators.py:219
msgid "This field must be unique for the \"{date_field}\" date."
diff --git a/rest_framework/locale/tr/LC_MESSAGES/django.mo b/rest_framework/locale/tr/LC_MESSAGES/django.mo
index e6b848cf..02f0e3a6 100644
--- a/rest_framework/locale/tr/LC_MESSAGES/django.mo
+++ b/rest_framework/locale/tr/LC_MESSAGES/django.mo
Binary files differ
diff --git a/rest_framework/locale/tr/LC_MESSAGES/django.po b/rest_framework/locale/tr/LC_MESSAGES/django.po
index 5aabbeba..348585d6 100644
--- a/rest_framework/locale/tr/LC_MESSAGES/django.po
+++ b/rest_framework/locale/tr/LC_MESSAGES/django.po
@@ -3,6 +3,7 @@
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
+# Emrah BİLBAY <emrahbilbay@gmail.com>, 2015
# Ertaç Paprat <epaprat@gmail.com>, 2015
# Mesut Can Gürle <mesutcang@gmail.com>, 2015
# Recep KIRMIZI <rkirmizi@gmail.com>, 2015
@@ -12,8 +13,8 @@ msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-30 16:23+0000\n"
-"PO-Revision-Date: 2015-01-30 16:27+0000\n"
-"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
+"PO-Revision-Date: 2015-03-10 23:50+0000\n"
+"Last-Translator: Emrah BİLBAY <emrahbilbay@gmail.com>\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"
@@ -23,15 +24,15 @@ msgstr ""
#: authentication.py:69
msgid "Invalid basic header. No credentials provided."
-msgstr ""
+msgstr "Geçersiz yetkilendirme başlığı. Gerekli uygunluk kriterleri sağlanmamış."
#: authentication.py:72
msgid "Invalid basic header. Credentials string should not contain spaces."
-msgstr ""
+msgstr "Geçersiz yetkilendirme başlığı. Uygunluk kriterine ait veri boşluk karakteri içermemeli."
#: authentication.py:78
msgid "Invalid basic header. Credentials not correctly base64 encoded."
-msgstr ""
+msgstr "Geçersiz yetkilendirme başlığı. Uygunluk kriterleri base64 formatına uygun olarak kodlanmamış."
#: authentication.py:90
msgid "Invalid username/password."
@@ -83,7 +84,7 @@ msgstr "\"{method}\" metoduna izin verilmiyor."
#: exceptions.py:109
msgid "Could not satisfy the request Accept header."
-msgstr ""
+msgstr "İsteğe ait Accept başlık bilgisi yanıt verilecek başlık bilgileri arasında değil."
#: exceptions.py:121
msgid "Unsupported media type \"{media_type}\" in request."
@@ -91,7 +92,7 @@ msgstr "İstekte desteklenmeyen medya tipi: \"{media_type}\"."
#: exceptions.py:134
msgid "Request was throttled."
-msgstr ""
+msgstr "Üst üste çok fazla istek yapıldı."
#: fields.py:153 relations.py:132 relations.py:156 validators.py:77
#: validators.py:155
@@ -138,7 +139,7 @@ msgstr "Geçerli bir URL girin."
#: fields.py:638
msgid "\"{value}\" is not a valid UUID."
-msgstr ""
+msgstr "\"{value}\" geçerli bir UUID değil."
#: fields.py:657
msgid "A valid integer is required."
@@ -233,7 +234,7 @@ msgstr "Geçerli bir resim yükleyin. Yüklediğiniz dosya resim değil ya da bo
#: fields.py:1188
msgid "Expected a dictionary of items but got type \"{input_type}\"."
-msgstr ""
+msgstr "Sözlük tipi bir değişken beklenirken \"{input_type}\" tipi bir değişken alındı."
#: pagination.py:221
msgid "Invalid page \"{page_number}\": {message}."
@@ -241,7 +242,7 @@ msgstr "Geçersiz sayfa \"{page_number}\":{message}."
#: pagination.py:442
msgid "Invalid cursor"
-msgstr ""
+msgstr "Sayfalandırma imleci geçersiz"
#: relations.py:133
msgid "Invalid pk \"{pk_value}\" - object does not exist."
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index 80985873..5e60448d 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -10,7 +10,7 @@ from django.core.paginator import InvalidPage, Paginator as DjangoPaginator
from django.template import Context, loader
from django.utils import six
from django.utils.six.moves.urllib import parse as urlparse
-from django.utils.translation import ugettext as _
+from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import OrderedDict
from rest_framework.exceptions import NotFound
from rest_framework.response import Response
@@ -18,6 +18,7 @@ from rest_framework.settings import api_settings
from rest_framework.utils.urls import (
replace_query_param, remove_query_param
)
+import warnings
def _positive_int(integer_string, strict=False, cutoff=None):
@@ -130,12 +131,19 @@ def _decode_cursor(encoded):
"""
Given a string representing an encoded cursor, return a `Cursor` instance.
"""
+
+ # The offset in the cursor is used in situations where we have a
+ # nearly-unique index. (Eg millisecond precision creation timestamps)
+ # We guard against malicious users attempting to cause expensive database
+ # queries, by having a hard cap on the maximum possible size of the offset.
+ OFFSET_CUTOFF = 1000
+
try:
querystring = b64decode(encoded.encode('ascii')).decode('ascii')
tokens = urlparse.parse_qs(querystring, keep_blank_values=True)
offset = tokens.get('o', ['0'])[0]
- offset = _positive_int(offset)
+ offset = _positive_int(offset, cutoff=OFFSET_CUTOFF)
reverse = tokens.get('r', ['0'])[0]
reverse = bool(int(reverse))
@@ -203,18 +211,18 @@ class PageNumberPagination(BasePagination):
"""
# The default page size.
# Defaults to `None`, meaning pagination is disabled.
- paginate_by = api_settings.PAGINATE_BY
+ page_size = api_settings.PAGE_SIZE
# Client can control the page using this query parameter.
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
+ page_size_query_param = None
# 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
+ # Only relevant if 'page_size_query_param' has also been set.
+ max_page_size = None
last_page_strings = ('last',)
@@ -228,12 +236,48 @@ class PageNumberPagination(BasePagination):
attributes were set there. The attributes should now be set on
the pagination class, but the old style is still pending deprecation.
"""
- for attr in (
- 'paginate_by', 'page_query_param',
- 'paginate_by_param', 'max_paginate_by'
+ assert not (
+ getattr(view, 'pagination_serializer_class', None) or
+ getattr(api_settings, 'DEFAULT_PAGINATION_SERIALIZER_CLASS', None)
+ ), (
+ "The pagination_serializer_class attribute and "
+ "DEFAULT_PAGINATION_SERIALIZER_CLASS setting have been removed as "
+ "part of the 3.1 pagination API improvement. See the pagination "
+ "documentation for details on the new API."
+ )
+
+ for (settings_key, attr_name) in (
+ ('PAGINATE_BY', 'page_size'),
+ ('PAGINATE_BY_PARAM', 'page_size_query_param'),
+ ('MAX_PAGINATE_BY', 'max_page_size')
):
- if hasattr(view, attr):
- setattr(self, attr, getattr(view, attr))
+ value = getattr(api_settings, settings_key, None)
+ if value is not None:
+ setattr(self, attr_name, value)
+ warnings.warn(
+ "The `%s` settings key is pending deprecation. "
+ "Use the `%s` attribute on the pagination class instead." % (
+ settings_key, attr_name
+ ),
+ PendingDeprecationWarning,
+ )
+
+ for (view_attr, attr_name) in (
+ ('paginate_by', 'page_size'),
+ ('page_query_param', 'page_query_param'),
+ ('paginate_by_param', 'page_size_query_param'),
+ ('max_paginate_by', 'max_page_size')
+ ):
+ value = getattr(view, view_attr, None)
+ if value is not None:
+ setattr(self, attr_name, value)
+ warnings.warn(
+ "The `%s` view attribute is pending deprecation. "
+ "Use the `%s` attribute on the pagination class instead." % (
+ view_attr, attr_name
+ ),
+ PendingDeprecationWarning,
+ )
def paginate_queryset(self, queryset, request, view=None):
"""
@@ -264,7 +308,7 @@ class PageNumberPagination(BasePagination):
self.display_page_controls = True
self.request = request
- return self.page
+ return list(self.page)
def get_paginated_response(self, data):
return Response(OrderedDict([
@@ -275,17 +319,17 @@ class PageNumberPagination(BasePagination):
]))
def get_page_size(self, request):
- if self.paginate_by_param:
+ if self.page_size_query_param:
try:
return _positive_int(
- request.query_params[self.paginate_by_param],
+ request.query_params[self.page_size_query_param],
strict=True,
- cutoff=self.max_paginate_by
+ cutoff=self.max_page_size
)
except (KeyError, ValueError):
pass
- return self.paginate_by
+ return self.page_size
def get_next_link(self):
if not self.page.has_next():
@@ -336,7 +380,7 @@ class LimitOffsetPagination(BasePagination):
http://api.example.org/accounts/?limit=100
http://api.example.org/accounts/?offset=400&limit=100
"""
- default_limit = api_settings.PAGINATE_BY
+ default_limit = api_settings.PAGE_SIZE
limit_query_param = 'limit'
offset_query_param = 'offset'
max_limit = None
@@ -349,7 +393,7 @@ class LimitOffsetPagination(BasePagination):
self.request = request
if self.count > self.limit and self.template is not None:
self.display_page_controls = True
- return queryset[self.offset:self.offset + self.limit]
+ return list(queryset[self.offset:self.offset + self.limit])
def get_paginated_response(self, data):
return Response(OrderedDict([
@@ -435,14 +479,15 @@ class LimitOffsetPagination(BasePagination):
class CursorPagination(BasePagination):
- # Determine how/if True, False and None positions work - do the string
- # encodings work with Django queryset filters?
- # Consider a max offset cap.
- # Tidy up the `get_ordering` API (eg remove queryset from it)
+ """
+ The cursor pagination implementation is neccessarily complex.
+ For an overview of the position/offset style we use, see this post:
+ http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api/
+ """
cursor_query_param = 'cursor'
- page_size = api_settings.PAGINATE_BY
+ page_size = api_settings.PAGE_SIZE
invalid_cursor_message = _('Invalid cursor')
- ordering = None
+ ordering = '-created'
template = 'rest_framework/pagination/previous_and_next.html'
def paginate_queryset(self, queryset, request, view=None):
@@ -484,7 +529,7 @@ class CursorPagination(BasePagination):
# We also always fetch an extra item in order to determine if there is a
# page following on from this one.
results = list(queryset[offset:offset + self.page_size + 1])
- self.page = results[:self.page_size]
+ self.page = list(results[:self.page_size])
# Determine the position of the final item following the page.
if len(results) > len(self.page):
@@ -643,12 +688,12 @@ class CursorPagination(BasePagination):
)
)
else:
- # The default case is to check for an `ordering` attribute,
- # first on the view instance, and then on this pagination instance.
- ordering = getattr(view, 'ordering', getattr(self, 'ordering', None))
+ # The default case is to check for an `ordering` attribute
+ # on this pagination instance.
+ ordering = self.ordering
assert ordering is not None, (
'Using cursor pagination, but no ordering attribute was declared '
- 'on the view or on the pagination class.'
+ 'on the pagination class.'
)
assert isinstance(ordering, (six.string_types, list, tuple)), (
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 0b7c9d86..3a966c5b 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -360,6 +360,10 @@ class ManyRelatedField(Field):
]
def get_attribute(self, instance):
+ # Can't have any relationships if not created
+ if not instance.pk:
+ return []
+
relationship = get_attribute(instance, self.source_attrs)
return relationship.all() if (hasattr(relationship, 'all')) else relationship
diff --git a/rest_framework/request.py b/rest_framework/request.py
index fd4f6a3e..e4b5bc26 100644
--- a/rest_framework/request.py
+++ b/rest_framework/request.py
@@ -14,7 +14,6 @@ from django.http import QueryDict
from django.http.multipartparser import parse_header
from django.utils import six
from django.utils.datastructures import MultiValueDict
-from django.utils.datastructures import MergeDict as DjangoMergeDict
from rest_framework import HTTP_HEADER_ENCODING
from rest_framework import exceptions
from rest_framework.settings import api_settings
@@ -61,15 +60,6 @@ class override_method(object):
self.view.action = self.action
-class MergeDict(DjangoMergeDict, dict):
- """
- Using this as a workaround until the parsers API is properly
- addressed in 3.1.
- """
- def __init__(self, *dicts):
- self.dicts = dicts
-
-
class Empty(object):
"""
Placeholder for unset attributes.
@@ -328,7 +318,8 @@ class Request(object):
if not _hasattr(self, '_data'):
self._data, self._files = self._parse()
if self._files:
- self._full_data = MergeDict(self._data, self._files)
+ self._full_data = self._data.copy()
+ self._full_data.update(self._files)
else:
self._full_data = self._data
@@ -392,7 +383,8 @@ class Request(object):
# At this point we're committed to parsing the request as form data.
self._data = self._request.POST
self._files = self._request.FILES
- self._full_data = MergeDict(self._data, self._files)
+ self._full_data = self._data.copy()
+ self._full_data.update(self._files)
# Method overloading - change the method and remove the param from the content.
if (
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index b1e39ff7..4df852bf 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -218,14 +218,15 @@ class SimpleRouter(BaseRouter):
https://github.com/alanjds/drf-nested-routers
"""
- base_regex = '(?P<{lookup_prefix}{lookup_field}>{lookup_value})'
+ base_regex = '(?P<{lookup_prefix}{lookup_url_kwarg}>{lookup_value})'
# Use `pk` as default field, unset set. Default regex should not
# consume `.json` style suffixes and should break at '/' boundaries.
lookup_field = getattr(viewset, 'lookup_field', 'pk')
+ lookup_url_kwarg = getattr(viewset, 'lookup_url_kwarg', None) or lookup_field
lookup_value = getattr(viewset, 'lookup_value_regex', '[^/.]+')
return base_regex.format(
lookup_prefix=lookup_prefix,
- lookup_field=lookup_field,
+ lookup_url_kwarg=lookup_url_kwarg,
lookup_value=lookup_value
)
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
index 394b1262..a3e9f590 100644
--- a/rest_framework/settings.py
+++ b/rest_framework/settings.py
@@ -61,9 +61,7 @@ DEFAULTS = {
'NUM_PROXIES': None,
# Pagination
- 'PAGINATE_BY': None,
- 'PAGINATE_BY_PARAM': None,
- 'MAX_PAGINATE_BY': None,
+ 'PAGE_SIZE': None,
# Filtering
'SEARCH_PARAM': 'search',
@@ -117,7 +115,12 @@ DEFAULTS = {
'UNICODE_JSON': True,
'COMPACT_JSON': True,
'COERCE_DECIMAL_TO_STRING': True,
- 'UPLOADED_FILES_USE_URL': True
+ 'UPLOADED_FILES_USE_URL': True,
+
+ # Pending deprecation:
+ 'PAGINATE_BY': None,
+ 'PAGINATE_BY_PARAM': None,
+ 'MAX_PAGINATE_BY': None
}
diff --git a/rest_framework/templates/rest_framework/pagination/previous_and_next.html b/rest_framework/templates/rest_framework/pagination/previous_and_next.html
index eacbfff4..08c17709 100644
--- a/rest_framework/templates/rest_framework/pagination/previous_and_next.html
+++ b/rest_framework/templates/rest_framework/pagination/previous_and_next.html
@@ -7,6 +7,6 @@
{% if next_url %}
<li class="next"><a href="{{ next_url }}">Next &raquo;</a></li>
{% else %}
- <li class="next disabled"><a href="#">Next &raquo;</li>
+ <li class="next disabled"><a href="#">Next &raquo;</a></li>
{% endif %}
</ul>
diff --git a/tests/conftest.py b/tests/conftest.py
index 44ed070b..03b4e328 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -18,7 +18,6 @@ def pytest_configure():
MIDDLEWARE_CLASSES=(
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
),
@@ -27,7 +26,6 @@ def pytest_configure():
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
- 'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
@@ -35,12 +33,7 @@ def pytest_configure():
'tests',
),
PASSWORD_HASHERS=(
- 'django.contrib.auth.hashers.SHA1PasswordHasher',
- 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
- 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
- 'django.contrib.auth.hashers.BCryptPasswordHasher',
'django.contrib.auth.hashers.MD5PasswordHasher',
- 'django.contrib.auth.hashers.CryptPasswordHasher',
),
)
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index 13bfb627..6b39a6f2 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -24,9 +24,9 @@ class TestPaginationIntegration:
return [item for item in queryset if item % 2 == 0]
class BasicPagination(pagination.PageNumberPagination):
- paginate_by = 5
- paginate_by_param = 'page_size'
- max_paginate_by = 20
+ page_size = 5
+ page_size_query_param = 'page_size'
+ max_page_size = 20
self.view = generics.ListAPIView.as_view(
serializer_class=PassThroughSerializer,
@@ -185,7 +185,7 @@ class TestPageNumberPagination:
def setup(self):
class ExamplePagination(pagination.PageNumberPagination):
- paginate_by = 5
+ page_size = 5
self.pagination = ExamplePagination()
self.queryset = range(1, 101)
diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py
index f872a8dc..ca43272b 100644
--- a/tests/test_relations_pk.py
+++ b/tests/test_relations_pk.py
@@ -143,6 +143,16 @@ class PKManyToManyTests(TestCase):
]
self.assertEqual(serializer.data, expected)
+ def test_many_to_many_unsaved(self):
+ source = ManyToManySource(name='source-unsaved')
+
+ serializer = ManyToManySourceSerializer(source)
+
+ expected = {'id': None, 'name': 'source-unsaved', 'targets': []}
+ # no query if source hasn't been created yet
+ with self.assertNumQueries(0):
+ self.assertEqual(serializer.data, expected)
+
def test_reverse_many_to_many_create(self):
data = {'id': 4, 'name': 'target-4', 'sources': [1, 3]}
serializer = ManyToManyTargetSerializer(data=data)
@@ -296,6 +306,16 @@ class PKForeignKeyTests(TestCase):
self.assertFalse(serializer.is_valid())
self.assertEqual(serializer.errors, {'target': ['This field may not be null.']})
+ def test_foreign_key_with_unsaved(self):
+ source = ForeignKeySource(name='source-unsaved')
+ expected = {'id': None, 'name': 'source-unsaved', 'target': None}
+
+ serializer = ForeignKeySourceSerializer(source)
+
+ # no query if source hasn't been created yet
+ with self.assertNumQueries(0):
+ self.assertEqual(serializer.data, expected)
+
def test_foreign_key_with_empty(self):
"""
Regression test for #1072
diff --git a/tests/test_routers.py b/tests/test_routers.py
index 08c58ec7..19eeb868 100644
--- a/tests/test_routers.py
+++ b/tests/test_routers.py
@@ -32,6 +32,13 @@ class NoteViewSet(viewsets.ModelViewSet):
lookup_field = 'uuid'
+class KWargedNoteViewSet(viewsets.ModelViewSet):
+ queryset = RouterTestModel.objects.all()
+ serializer_class = NoteSerializer
+ lookup_field = 'text__contains'
+ lookup_url_kwarg = 'text'
+
+
class MockViewSet(viewsets.ModelViewSet):
queryset = None
serializer_class = None
@@ -40,6 +47,9 @@ class MockViewSet(viewsets.ModelViewSet):
notes_router = SimpleRouter()
notes_router.register(r'notes', NoteViewSet)
+kwarged_notes_router = SimpleRouter()
+kwarged_notes_router.register(r'notes', KWargedNoteViewSet)
+
namespaced_router = DefaultRouter()
namespaced_router.register(r'example', MockViewSet, base_name='example')
@@ -47,6 +57,7 @@ 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)),
+ url(r'^example2/', include(kwarged_notes_router.urls)),
]
@@ -177,6 +188,33 @@ class TestLookupValueRegex(TestCase):
self.assertEqual(expected[idx], self.urls[idx].regex.pattern)
+class TestLookupUrlKwargs(TestCase):
+ """
+ Ensure the router honors lookup_url_kwarg.
+
+ Setup a deep lookup_field, but map it to a simple URL kwarg.
+ """
+ urls = 'tests.test_routers'
+
+ def setUp(self):
+ RouterTestModel.objects.create(uuid='123', text='foo bar')
+
+ def test_custom_lookup_url_kwarg_route(self):
+ detail_route = kwarged_notes_router.urls[-1]
+ detail_url_pattern = detail_route.regex.pattern
+ self.assertIn('^notes/(?P<text>', detail_url_pattern)
+
+ def test_retrieve_lookup_url_kwarg_detail_view(self):
+ response = self.client.get('/example2/notes/fo/')
+ self.assertEqual(
+ response.data,
+ {
+ "url": "http://testserver/example/notes/123/",
+ "uuid": "123", "text": "foo bar"
+ }
+ )
+
+
class TestTrailingSlashIncluded(TestCase):
def setUp(self):
class NoteViewSet(viewsets.ModelViewSet):