Update from upstream

This commit is contained in:
Rense VanderHoek 2015-03-09 12:14:36 +01:00
commit b172ed0d35
22 changed files with 202 additions and 142 deletions

View File

@ -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]. * [Authentication policies][authentication] including optional packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources. * [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers]. * Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
* [Extensive documentation][index], and [great community support][group]. * [Extensive documentation][docs], and [great community support][group].
There is a live example API for testing purposes, [available here][sandbox]. There is a live example API for testing purposes, [available here][sandbox].
@ -186,10 +188,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[pypi]: https://pypi.python.org/pypi/djangorestframework [pypi]: https://pypi.python.org/pypi/djangorestframework
[twitter]: https://twitter.com/_tomchristie [twitter]: https://twitter.com/_tomchristie
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework [group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
[sandbox]: http://restframework.herokuapp.com/ [sandbox]: http://restframework.herokuapp.com/
[index]: http://www.django-rest-framework.org/
[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth [oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
[oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit [oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
[serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers [serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers
@ -200,18 +200,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[routers]: http://www.django-rest-framework.org/api-guide/routers/ [routers]: http://www.django-rest-framework.org/api-guide/routers/
[serializers]: http://www.django-rest-framework.org/api-guide/serializers/ [serializers]: http://www.django-rest-framework.org/api-guide/serializers/
[authentication]: http://www.django-rest-framework.org/api-guide/authentication/ [authentication]: http://www.django-rest-framework.org/api-guide/authentication/
[rest-framework-2-announcement]: http://www.django-rest-framework.org/topics/rest-framework-2-announcement/
[2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion
[image]: http://www.django-rest-framework.org/img/quickstart.png [image]: http://www.django-rest-framework.org/img/quickstart.png
[tox]: http://testrun.org/tox/latest/
[tehjones]: https://twitter.com/tehjones/status/294986071979196416
[wlonk]: https://twitter.com/wlonk/status/261689665952833536
[laserllama]: https://twitter.com/laserllama/status/328688333750407168
[docs]: http://www.django-rest-framework.org/ [docs]: http://www.django-rest-framework.org/
[urlobject]: https://github.com/zacharyvoase/urlobject
[markdown]: http://pypi.python.org/pypi/Markdown/
[django-filter]: http://pypi.python.org/pypi/django-filter
[security-mail]: mailto:rest-framework-security@googlegroups.com [security-mail]: mailto:rest-framework-security@googlegroups.com
[3.1-announcement]: http://www.django-rest-framework.org/topics/3.1-announcement/

0
docs/api-guide/authentication.md Executable file → Normal file
View File

View File

@ -1,11 +1,5 @@
source: fields.py source: fields.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Serializer fields # Serializer fields
> Each field in a Form class is responsible not only for validating data, but also for "cleaning" it — normalizing it to a consistent format. > Each field in a Form class is responsible not only for validating data, but also for "cleaning" it — normalizing it to a consistent format.

13
docs/api-guide/generic-views.md Executable file → Normal file
View File

@ -1,12 +1,6 @@
source: mixins.py source: mixins.py
generics.py generics.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Generic views # Generic views
> Djangos 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. > Djangos 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. The following attributes are used to control pagination when used with list views.
* `paginate_by` - The size of pages to use with paginated data. If set to `None` then pagination is turned off. If unset this uses the same value as the `PAGINATE_BY` setting, which defaults to `None`. * `pagination_class` - The pagination class that should be used when paginating list results. Defaults to the same value as the `DEFAULT_PAGINATION_CLASS` setting, which is `'rest_framework.pagination.PageNumberPagination'`.
* `paginate_by_param` - The name of a query parameter, which can be used by the client to override the default page size to use for pagination. If unset this uses the same value as the `PAGINATE_BY_PARAM` setting, which defaults to `None`.
* `pagination_serializer_class` - The pagination serializer class to use when determining the style of paginated responses. Defaults to the same value as the `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting. Note that usage of the `paginate_by`, `paginate_by_param` and `page_kwarg` attributes are now pending deprecation. The `pagination_serializer_class` attribute and `DEFAULT_PAGINATION_SERIALIZER_CLASS` setting have been removed completely. Pagination settings should instead be controlled by overriding a pagination class and setting any configuration attributes there. See the pagination documentation for more details.
* `page_kwarg` - The name of a URL kwarg or URL query parameter which can be used by the client to control which page is requested. Defaults to `'page'`.
**Filtering**: **Filtering**:

View File

@ -1,11 +1,5 @@
source: metadata.py source: metadata.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Metadata # Metadata
> [The `OPTIONS`] method allows a client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval. > [The `OPTIONS`] method allows a client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval.
@ -59,7 +53,7 @@ Or you can set the metadata class individually for a view:
class APIRoot(APIView): class APIRoot(APIView):
metadata_class = APIRootMetadata metadata_class = APIRootMetadata
def get(self, request, format=None): def get(self, request, format=None):
return Response({ return Response({
... ...

View File

@ -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: Or apply the style globally, using the `DEFAULT_PAGINATION_CLASS` settings key. For example:
REST_FRAMEWORK = { 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 #### 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 = { REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', '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. 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. 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_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. * `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. * `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.
@ -132,7 +133,7 @@ To enable the `PageNumberPagination` style globally, use the following configura
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination' '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. 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. 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'`. * `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'`. * `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`. * `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: 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. * 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 #### 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. 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 #### 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 = { REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination', '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. 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. * `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'`. * `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"`. * `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 ## 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]. 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): class LinkHeaderPagination(pagination.PageNumberPagination):
def get_paginated_response(self, data): 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: if next_url is not None and previous_url is not None:
link = '<{next_url}; rel="next">, <{previous_url}; rel="prev">' 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 = { REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.LinkHeaderPagination', '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: 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 [link-header]: ../img/link-header-pagination.png
[drf-extensions]: http://chibisov.github.io/drf-extensions/docs/ [drf-extensions]: http://chibisov.github.io/drf-extensions/docs/
[paginate-by-max-mixin]: http://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin [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/ [disqus-cursor-api]: http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api/

View File

@ -1,11 +1,5 @@
source: relations.py source: relations.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Serializer relations # Serializer relations
> Bad programmers worry about the code. > Bad programmers worry about the code.

View File

@ -1,11 +1,5 @@
source: request.py source: request.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Requests # Requests
> If you're doing REST-based web service stuff ... you should ignore request.POST. > If you're doing REST-based web service stuff ... you should ignore request.POST.

View File

@ -1,11 +1,5 @@
source: serializers.py source: serializers.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Serializers # Serializers
> Expanding the usefulness of the serializers is something that we would > Expanding the usefulness of the serializers is something that we would
@ -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: Let's start by creating a simple object we can use for example purposes:
from datetime import datetime from datetime import datetime
class Comment(object): class Comment(object):
def __init__(self, email, content, created=None): def __init__(self, email, content, created=None):
self.email = email self.email = email

View File

@ -1,11 +1,5 @@
source: validators.py source: validators.py
---
**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
---
# Validators # Validators
> Validators can be useful for re-using validation logic between different types of fields. > Validators can be useful for re-using validation logic between different types of fields.
@ -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. 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): 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) reference = models.CharField(unique=True, max_length=20)
description = models.TextField() description = models.TextField()
@ -43,7 +37,7 @@ Here's a basic `ModelSerializer` that we can use for creating or updating instan
class Meta: class Meta:
model = CustomerReportRecord 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 >>> from project.example.serializers import CustomerReportSerializer
>>> serializer = CustomerReportSerializer() >>> serializer = CustomerReportSerializer()
@ -204,7 +198,7 @@ A validator may be any callable that raises a `serializers.ValidationError` on f
def even_number(value): def even_number(value):
if value % 2 != 0: 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 ## Class based
@ -213,7 +207,7 @@ To write a class based validator, use the `__call__` method. Class based validat
class MultipleOf: class MultipleOf:
def __init__(self, base): def __init__(self, base):
self.base = base self.base = base
def __call__(self, value): def __call__(self, value):
if value % self.base != 0 if value % self.base != 0
message = 'This field must be a multiple of %d.' % self.base message = 'This field must be a multiple of %d.' % self.base

View File

@ -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].
--- ---

View File

@ -17,6 +17,15 @@ Some highlights include:
The pagination API has been improved, making it both easier to use, and more powerful. The pagination API has been improved, making it both easier to use, and more powerful.
A guide to the headline features follows. For full details, see [the pagination documentation][pagination].
Note that as a result of this work a number of settings keys and generic view attributes are now moved to pending deprecation. Controlling pagination styles is now largely handled by overriding a pagination class and modifying its configuration attributes.
* The `PAGINATE_BY` settings key will continue to work but is now pending deprecation. The more obviously named `PAGE_SIZE` settings key should now be used instead.
* The `PAGINATE_BY_PARAM`, `MAX_PAGINATE_BY` settings keys will continue to work but are now pending deprecation, in favor of setting configuration attributes on the configured pagination class.
* The `paginate_by`, `page_query_param`, `paginate_by_param` and `max_paginate_by` generic view attributes will continue to work but are now pending deprecation, in favor of setting configuration attributes on the configured pagination class.
* The `pagination_serializer_class` view attribute and `DEFAULT_PAGINATION_SERIALIZER_CLASS` settings key **are no longer valid**. The pagination API does not use serializers to determine the output format, and you'll need to instead override the `get_paginated_response` method on a pagination class in order to specify how the output format is controlled.
#### New pagination schemes. #### New pagination schemes.
Until now, there has only been a single built-in pagination style in REST framework. We now have page, limit/offset and cursor based schemes included by default. Until now, there has only been a single built-in pagination style in REST framework. We now have page, limit/offset and cursor based schemes included by default.
@ -43,7 +52,7 @@ For more information, see the [custom pagination styles](../api-guide/pagination
## Versioning ## Versioning
We've made it easier to build versioned APIs. Built-in schemes for versioning include both URL based and Accept header based variations. We've made it [easier to build versioned APIs][versioning]. Built-in schemes for versioning include both URL based and Accept header based variations.
When using a URL based scheme, hyperlinked serializers will resolve relationships to the same API version as used on the incoming request. When using a URL based scheme, hyperlinked serializers will resolve relationships to the same API version as used on the incoming request.
@ -71,7 +80,7 @@ The output representation would match the version used on the incoming request.
## Internationalization ## Internationalization
REST framework now includes a built-in set of translations, and supports internationalized error responses. This allows you to either change the default language, or to allow clients to specify the language via the `Accept-Language` header. REST framework now includes a built-in set of translations, and [supports internationalized error responses][internationalization]. This allows you to either change the default language, or to allow clients to specify the language via the `Accept-Language` header.
You can change the default language by using the standard Django `LANGUAGE_CODE` setting: You can change the default language by using the standard Django `LANGUAGE_CODE` setting:
@ -136,7 +145,7 @@ If you're building a new 1.8 project, then you should probably consider using `U
The serializer redesign in 3.0 did not include any public API for modifying how ModelSerializer classes automatically generate a set of fields from a given mode class. We've now re-introduced an API for this, allowing you to create new ModelSerializer base classes that behave differently, such as using a different default style for relationships. The serializer redesign in 3.0 did not include any public API for modifying how ModelSerializer classes automatically generate a set of fields from a given mode class. We've now re-introduced an API for this, allowing you to create new ModelSerializer base classes that behave differently, such as using a different default style for relationships.
For more information, see the documentation on [customizing field mappings](../api-guide/serializers/#customizing-field-mappings) for ModelSerializer classes. For more information, see the documentation on [customizing field mappings][customizing-field-mappings] for ModelSerializer classes.
--- ---
@ -194,3 +203,7 @@ The next focus will be on HTML renderings of API output and will include:
This will either be made as a single 3.2 release, or split across two separate releases, with the HTML forms and filter controls coming in 3.2, and the admin-style interface coming in a 3.3 release. This will either be made as a single 3.2 release, or split across two separate releases, with the HTML forms and filter controls coming in 3.2, and the admin-style interface coming in a 3.3 release.
[custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling [custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling
[pagination]: ../api-guide/pagination.md
[versioning]: ../api-guide/versioning.md
[internationalization]: internationalization.md
[customizing-field-mappings]: ../api-guide/serializers.md/#customizing-field-mappings

View File

@ -40,6 +40,11 @@ You can determine your currently installed version using `pip freeze`:
## 3.0.x series ## 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 ### 3.0.5

View File

@ -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: We can change the default list style to use pagination, by modifying our `tutorial/settings.py` file slightly. Add the following setting:
REST_FRAMEWORK = { REST_FRAMEWORK = {
'PAGINATE_BY': 10 'PAGE_SIZE': 10
} }
Note that settings in REST framework are all namespaced into a single dictionary setting, named 'REST_FRAMEWORK', which helps keep them well separated from your other project settings. Note that settings in REST framework are all namespaced into a single dictionary setting, named 'REST_FRAMEWORK', which helps keep them well separated from your other project settings.

View File

@ -123,7 +123,7 @@ We'd also like to set a few global settings. We'd like to turn on pagination, a
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',), 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
'PAGINATE_BY': 10 'PAGE_SIZE': 10
} }
Okay, we're done. Okay, we're done.

View File

@ -103,7 +103,7 @@ def set_value(dictionary, keys, value):
dictionary[keys[-1]] = value dictionary[keys[-1]] = value
class CreateOnlyDefault: class CreateOnlyDefault(object):
""" """
This class may be used to provide default values that are only used This class may be used to provide default values that are only used
for create operations, but that do not return any value for update for create operations, but that do not return any value for update
@ -114,7 +114,7 @@ class CreateOnlyDefault:
def set_context(self, serializer_field): def set_context(self, serializer_field):
self.is_update = serializer_field.parent.instance is not None self.is_update = serializer_field.parent.instance is not None
if callable(self.default) and hasattr(self.default, 'set_context'): if callable(self.default) and hasattr(self.default, 'set_context') and not self.is_update:
self.default.set_context(serializer_field) self.default.set_context(serializer_field)
def __call__(self): def __call__(self):
@ -130,7 +130,7 @@ class CreateOnlyDefault:
) )
class CurrentUserDefault: class CurrentUserDefault(object):
def set_context(self, serializer_field): def set_context(self, serializer_field):
self.user = serializer_field.context['request'].user self.user = serializer_field.context['request'].user

View File

@ -18,6 +18,7 @@ from rest_framework.settings import api_settings
from rest_framework.utils.urls import ( from rest_framework.utils.urls import (
replace_query_param, remove_query_param replace_query_param, remove_query_param
) )
import warnings
def _positive_int(integer_string, strict=False, cutoff=None): def _positive_int(integer_string, strict=False, cutoff=None):
@ -130,12 +131,19 @@ def _decode_cursor(encoded):
""" """
Given a string representing an encoded cursor, return a `Cursor` instance. Given a string representing an encoded cursor, return a `Cursor` instance.
""" """
# The offset in the cursor is used in situations where we have a
# nearly-unique index. (Eg millisecond precision creation timestamps)
# We guard against malicious users attempting to cause expensive database
# queries, by having a hard cap on the maximum possible size of the offset.
OFFSET_CUTOFF = 1000
try: try:
querystring = b64decode(encoded.encode('ascii')).decode('ascii') querystring = b64decode(encoded.encode('ascii')).decode('ascii')
tokens = urlparse.parse_qs(querystring, keep_blank_values=True) tokens = urlparse.parse_qs(querystring, keep_blank_values=True)
offset = tokens.get('o', ['0'])[0] offset = tokens.get('o', ['0'])[0]
offset = _positive_int(offset) offset = _positive_int(offset, cutoff=OFFSET_CUTOFF)
reverse = tokens.get('r', ['0'])[0] reverse = tokens.get('r', ['0'])[0]
reverse = bool(int(reverse)) reverse = bool(int(reverse))
@ -203,18 +211,18 @@ class PageNumberPagination(BasePagination):
""" """
# The default page size. # The default page size.
# Defaults to `None`, meaning pagination is disabled. # Defaults to `None`, meaning pagination is disabled.
paginate_by = api_settings.PAGINATE_BY page_size = api_settings.PAGE_SIZE
# Client can control the page using this query parameter. # Client can control the page using this query parameter.
page_query_param = 'page' page_query_param = 'page'
# Client can control the page size using this query parameter. # Client can control the page size using this query parameter.
# Default is 'None'. Set to eg 'page_size' to enable usage. # Default is 'None'. Set to eg 'page_size' to enable usage.
paginate_by_param = api_settings.PAGINATE_BY_PARAM page_size_query_param = None
# Set to an integer to limit the maximum page size the client may request. # Set to an integer to limit the maximum page size the client may request.
# Only relevant if 'paginate_by_param' has also been set. # Only relevant if 'page_size_query_param' has also been set.
max_paginate_by = api_settings.MAX_PAGINATE_BY max_page_size = None
last_page_strings = ('last',) last_page_strings = ('last',)
@ -228,12 +236,48 @@ class PageNumberPagination(BasePagination):
attributes were set there. The attributes should now be set on attributes were set there. The attributes should now be set on
the pagination class, but the old style is still pending deprecation. the pagination class, but the old style is still pending deprecation.
""" """
for attr in ( assert not (
'paginate_by', 'page_query_param', getattr(view, 'pagination_serializer_class', None) or
'paginate_by_param', 'max_paginate_by' getattr(api_settings, 'DEFAULT_PAGINATION_SERIALIZER_CLASS', None)
), (
"The pagination_serializer_class attribute and "
"DEFAULT_PAGINATION_SERIALIZER_CLASS setting have been removed as "
"part of the 3.1 pagination API improvement. See the pagination "
"documentation for details on the new API."
)
for (settings_key, attr_name) in (
('PAGINATE_BY', 'page_size'),
('PAGINATE_BY_PARAM', 'page_size_query_param'),
('MAX_PAGINATE_BY', 'max_page_size')
): ):
if hasattr(view, attr): value = getattr(api_settings, settings_key, None)
setattr(self, attr, getattr(view, attr)) if value is not None:
setattr(self, attr_name, value)
warnings.warn(
"The `%s` settings key is pending deprecation. "
"Use the `%s` attribute on the pagination class instead." % (
settings_key, attr_name
),
PendingDeprecationWarning,
)
for (view_attr, attr_name) in (
('paginate_by', 'page_size'),
('page_query_param', 'page_query_param'),
('paginate_by_param', 'page_size_query_param'),
('max_paginate_by', 'max_page_size')
):
value = getattr(view, view_attr, None)
if value is not None:
setattr(self, attr_name, value)
warnings.warn(
"The `%s` view attribute is pending deprecation. "
"Use the `%s` attribute on the pagination class instead." % (
view_attr, attr_name
),
PendingDeprecationWarning,
)
def paginate_queryset(self, queryset, request, view=None): def paginate_queryset(self, queryset, request, view=None):
""" """
@ -264,7 +308,7 @@ class PageNumberPagination(BasePagination):
self.display_page_controls = True self.display_page_controls = True
self.request = request self.request = request
return self.page return list(self.page)
def get_paginated_response(self, data): def get_paginated_response(self, data):
return Response(OrderedDict([ return Response(OrderedDict([
@ -275,17 +319,17 @@ class PageNumberPagination(BasePagination):
])) ]))
def get_page_size(self, request): def get_page_size(self, request):
if self.paginate_by_param: if self.page_size_query_param:
try: try:
return _positive_int( return _positive_int(
request.query_params[self.paginate_by_param], request.query_params[self.page_size_query_param],
strict=True, strict=True,
cutoff=self.max_paginate_by cutoff=self.max_page_size
) )
except (KeyError, ValueError): except (KeyError, ValueError):
pass pass
return self.paginate_by return self.page_size
def get_next_link(self): def get_next_link(self):
if not self.page.has_next(): if not self.page.has_next():
@ -336,7 +380,7 @@ class LimitOffsetPagination(BasePagination):
http://api.example.org/accounts/?limit=100 http://api.example.org/accounts/?limit=100
http://api.example.org/accounts/?offset=400&limit=100 http://api.example.org/accounts/?offset=400&limit=100
""" """
default_limit = api_settings.PAGINATE_BY default_limit = api_settings.PAGE_SIZE
limit_query_param = 'limit' limit_query_param = 'limit'
offset_query_param = 'offset' offset_query_param = 'offset'
max_limit = None max_limit = None
@ -349,7 +393,7 @@ class LimitOffsetPagination(BasePagination):
self.request = request self.request = request
if self.count > self.limit and self.template is not None: if self.count > self.limit and self.template is not None:
self.display_page_controls = True self.display_page_controls = True
return queryset[self.offset:self.offset + self.limit] return list(queryset[self.offset:self.offset + self.limit])
def get_paginated_response(self, data): def get_paginated_response(self, data):
return Response(OrderedDict([ return Response(OrderedDict([
@ -435,14 +479,15 @@ class LimitOffsetPagination(BasePagination):
class CursorPagination(BasePagination): class CursorPagination(BasePagination):
# Determine how/if True, False and None positions work - do the string """
# encodings work with Django queryset filters? The cursor pagination implementation is neccessarily complex.
# Consider a max offset cap. For an overview of the position/offset style we use, see this post:
# Tidy up the `get_ordering` API (eg remove queryset from it) http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api/
"""
cursor_query_param = 'cursor' cursor_query_param = 'cursor'
page_size = api_settings.PAGINATE_BY page_size = api_settings.PAGE_SIZE
invalid_cursor_message = _('Invalid cursor') invalid_cursor_message = _('Invalid cursor')
ordering = None ordering = '-created'
template = 'rest_framework/pagination/previous_and_next.html' template = 'rest_framework/pagination/previous_and_next.html'
def paginate_queryset(self, queryset, request, view=None): def paginate_queryset(self, queryset, request, view=None):
@ -484,7 +529,7 @@ class CursorPagination(BasePagination):
# We also always fetch an extra item in order to determine if there is a # We also always fetch an extra item in order to determine if there is a
# page following on from this one. # page following on from this one.
results = list(queryset[offset:offset + self.page_size + 1]) results = list(queryset[offset:offset + self.page_size + 1])
self.page = results[:self.page_size] self.page = list(results[:self.page_size])
# Determine the position of the final item following the page. # Determine the position of the final item following the page.
if len(results) > len(self.page): if len(results) > len(self.page):
@ -643,12 +688,12 @@ class CursorPagination(BasePagination):
) )
) )
else: else:
# The default case is to check for an `ordering` attribute, # The default case is to check for an `ordering` attribute
# first on the view instance, and then on this pagination instance. # on this pagination instance.
ordering = getattr(view, 'ordering', getattr(self, 'ordering', None)) ordering = self.ordering
assert ordering is not None, ( assert ordering is not None, (
'Using cursor pagination, but no ordering attribute was declared ' 'Using cursor pagination, but no ordering attribute was declared '
'on the view or on the pagination class.' 'on the pagination class.'
) )
assert isinstance(ordering, (six.string_types, list, tuple)), ( assert isinstance(ordering, (six.string_types, list, tuple)), (

View File

@ -360,6 +360,10 @@ class ManyRelatedField(Field):
] ]
def get_attribute(self, instance): def get_attribute(self, instance):
# Can't have any relationships if not created
if not instance.pk:
return []
relationship = get_attribute(instance, self.source_attrs) relationship = get_attribute(instance, self.source_attrs)
return relationship.all() if (hasattr(relationship, 'all')) else relationship return relationship.all() if (hasattr(relationship, 'all')) else relationship

View File

@ -14,7 +14,6 @@ from django.http import QueryDict
from django.http.multipartparser import parse_header from django.http.multipartparser import parse_header
from django.utils import six from django.utils import six
from django.utils.datastructures import MultiValueDict 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 HTTP_HEADER_ENCODING
from rest_framework import exceptions from rest_framework import exceptions
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
@ -61,15 +60,6 @@ class override_method(object):
self.view.action = self.action 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): class Empty(object):
""" """
Placeholder for unset attributes. Placeholder for unset attributes.
@ -328,7 +318,8 @@ class Request(object):
if not _hasattr(self, '_data'): if not _hasattr(self, '_data'):
self._data, self._files = self._parse() self._data, self._files = self._parse()
if self._files: if self._files:
self._full_data = MergeDict(self._data, self._files) self._full_data = self._data.copy()
self._full_data.update(self._files)
else: else:
self._full_data = self._data 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. # At this point we're committed to parsing the request as form data.
self._data = self._request.POST self._data = self._request.POST
self._files = self._request.FILES 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. # Method overloading - change the method and remove the param from the content.
if ( if (

View File

@ -61,9 +61,7 @@ DEFAULTS = {
'NUM_PROXIES': None, 'NUM_PROXIES': None,
# Pagination # Pagination
'PAGINATE_BY': None, 'PAGE_SIZE': None,
'PAGINATE_BY_PARAM': None,
'MAX_PAGINATE_BY': None,
# Filtering # Filtering
'SEARCH_PARAM': 'search', 'SEARCH_PARAM': 'search',
@ -117,7 +115,12 @@ DEFAULTS = {
'UNICODE_JSON': True, 'UNICODE_JSON': True,
'COMPACT_JSON': True, 'COMPACT_JSON': True,
'COERCE_DECIMAL_TO_STRING': 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
} }

View File

@ -24,9 +24,9 @@ class TestPaginationIntegration:
return [item for item in queryset if item % 2 == 0] return [item for item in queryset if item % 2 == 0]
class BasicPagination(pagination.PageNumberPagination): class BasicPagination(pagination.PageNumberPagination):
paginate_by = 5 page_size = 5
paginate_by_param = 'page_size' page_size_query_param = 'page_size'
max_paginate_by = 20 max_page_size = 20
self.view = generics.ListAPIView.as_view( self.view = generics.ListAPIView.as_view(
serializer_class=PassThroughSerializer, serializer_class=PassThroughSerializer,
@ -185,7 +185,7 @@ class TestPageNumberPagination:
def setup(self): def setup(self):
class ExamplePagination(pagination.PageNumberPagination): class ExamplePagination(pagination.PageNumberPagination):
paginate_by = 5 page_size = 5
self.pagination = ExamplePagination() self.pagination = ExamplePagination()
self.queryset = range(1, 101) self.queryset = range(1, 101)

View File

@ -143,6 +143,16 @@ class PKManyToManyTests(TestCase):
] ]
self.assertEqual(serializer.data, expected) 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): def test_reverse_many_to_many_create(self):
data = {'id': 4, 'name': 'target-4', 'sources': [1, 3]} data = {'id': 4, 'name': 'target-4', 'sources': [1, 3]}
serializer = ManyToManyTargetSerializer(data=data) serializer = ManyToManyTargetSerializer(data=data)
@ -296,6 +306,16 @@ class PKForeignKeyTests(TestCase):
self.assertFalse(serializer.is_valid()) self.assertFalse(serializer.is_valid())
self.assertEqual(serializer.errors, {'target': ['This field may not be null.']}) 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): def test_foreign_key_with_empty(self):
""" """
Regression test for #1072 Regression test for #1072