Polishing to page size query parameters & more docs

This commit is contained in:
Tom Christie 2012-11-16 22:45:57 +00:00
parent 9973cf329a
commit 31f01bd631
7 changed files with 79 additions and 111 deletions

View File

@ -123,18 +123,36 @@ Each of the generic views provided is built by combining one of the base views b
Extends REST framework's `APIView` class, adding support for serialization of model instances and model querysets.
**Attributes**:
* `model` - The model that should be used for this view. Used as a fallback for determining the serializer if `serializer_class` is not set, and as a fallback for determining the queryset if `queryset` is not set. Otherwise not required.
* `serializer_class` - The serializer class that should be used for validating and deserializing input, and for serializing output. If unset, this defaults to creating a serializer class using `self.model`, with the `DEFAULT_MODEL_SERIALIZER_CLASS` setting as the base serializer class.
## MultipleObjectAPIView
Provides a base view for acting on a single object, by combining REST framework's `APIView`, and Django's [MultipleObjectMixin].
**See also:** ccbv.co.uk documentation for [MultipleObjectMixin][multiple-object-mixin-classy].
**Attributes**:
* `queryset` - The queryset that should be used for returning objects from this view. If unset, defaults to the default queryset manager for `self.model`.
* `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 overide 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`.
## SingleObjectAPIView
Provides a base view for acting on a single object, by combining REST framework's `APIView`, and Django's [SingleObjectMixin].
**See also:** ccbv.co.uk documentation for [SingleObjectMixin][single-object-mixin-classy].
**Attributes**:
* `queryset` - The queryset that should be used when retrieving an object from this view. If unset, defaults to the default queryset manager for `self.model`.
* `pk_kwarg` - The URL kwarg that should be used to look up objects by primary key. Defaults to `'pk'`. [Can only be set to non-default on Django 1.4+]
* `slug_kwarg` - The URL kwarg that should be used to look up objects by a slug. Defaults to `'slug'`. [Can only be set to non-default on Django 1.4+]
* `slug_field` - The field on the model that should be used to look up objects by a slug. If used, this should typically be set to a field with `unique=True`. Defaults to `'slug'`.
---
# Mixins
@ -147,10 +165,6 @@ Provides a `.list(request, *args, **kwargs)` method, that implements listing a q
Should be mixed in with [MultipleObjectAPIView].
**Arguments**:
* `page_size_kwarg` - Allows you to overwrite the global settings `PAGE_SIZE_KWARG` for a specific view. You can also turn it off for a specific view by setting it to `None`. Default is `page_size`.
## CreateModelMixin
Provides a `.create(request, *args, **kwargs)` method, that implements creating and saving a new model instance.

View File

@ -96,11 +96,21 @@ Default: `rest_framework.serializers.ModelSerializer`
Default: `rest_framework.pagination.PaginationSerializer`
## FORMAT_SUFFIX_KWARG
## FILTER_BACKEND
**TODO**
The filter backend class that should be used for generic filtering. If set to `None` then generic filtering is disabled.
Default: `'format'`
## PAGINATE_BY
The default page size to use for pagination. If set to `None`, pagination is disabled by default.
Default: `None`
## PAGINATE_BY_KWARG
The name of a query parameter, which can be used by the client to overide the default page size to use for pagination. If set to `None`, clients may not override the default page size.
Default: `None`
## UNAUTHENTICATED_USER
@ -150,14 +160,10 @@ Default: `'accept'`
Default: `'format'`
## PAGE_SIZE_KWARG
## FORMAT_SUFFIX_KWARG
Allows you to globally pass a page size parameter for an individual request.
**TODO**
The name of the GET parameter of views which inherit ListModelMixin for requesting data with an individual page size.
If the value if this setting is `None` the passing a page size is turned off by default.
Default: `'page_size'`
Default: `'format'`
[cite]: http://www.python.org/dev/peps/pep-0020/

View File

@ -7,7 +7,8 @@
## Master
* Support for `read_only_fields` on `ModelSerializer` classes.
* Support for individual page sizes per request via `page_size` GET parameter in views which inherit ListModelMixin.
* Support for clients overriding the pagination page sizes. Use the `PAGINATE_BY_PARAM` setting or set the `paginate_by_param` attribute on a generic view.
* 201 Responses now return a 'Location' header.
## 2.1.2

View File

@ -14,6 +14,7 @@ class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
model = None
serializer_class = None
model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS
@ -30,8 +31,10 @@ class GenericAPIView(views.APIView):
def get_serializer_class(self):
"""
Return the class to use for the serializer.
Use `self.serializer_class`, falling back to constructing a
model serializer class from `self.model_serializer_class`
Defaults to using `self.serializer_class`, falls back to constructing a
model serializer class using `self.model_serializer_class`, with
`self.model` as the model.
"""
serializer_class = self.serializer_class
@ -58,29 +61,42 @@ class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView):
pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS
paginate_by = api_settings.PAGINATE_BY
paginate_by_param = api_settings.PAGINATE_BY_PARAM
filter_backend = api_settings.FILTER_BACKEND
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
"""
if not self.filter_backend:
return queryset
backend = self.filter_backend()
return backend.filter_queryset(self.request, queryset, self)
def get_pagination_serializer_class(self):
def get_pagination_serializer(self, page=None):
"""
Return the class to use for the pagination serializer.
Return a serializer instance to use with paginated data.
"""
class SerializerClass(self.pagination_serializer_class):
class Meta:
object_serializer_class = self.get_serializer_class()
return SerializerClass
def get_pagination_serializer(self, page=None):
pagination_serializer_class = self.get_pagination_serializer_class()
pagination_serializer_class = SerializerClass
context = self.get_serializer_context()
return pagination_serializer_class(instance=page, context=context)
def get_paginate_by(self, queryset):
"""
Return the size of pages to use with pagination.
"""
if self.paginate_by_param:
params = self.request.QUERY_PARAMS
try:
return int(params[self.paginate_by_param])
except (KeyError, ValueError):
pass
return self.paginate_by
class SingleObjectAPIView(SingleObjectMixin, GenericAPIView):
"""

View File

@ -7,7 +7,6 @@ which allows mixin classes to be composed in interesting ways.
from django.http import Http404
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
class CreateModelMixin(object):
@ -40,7 +39,6 @@ class ListModelMixin(object):
Should be mixed in with `MultipleObjectAPIView`.
"""
empty_error = u"Empty list and '%(class_name)s.allow_empty' is False."
page_size_kwarg = api_settings.PAGE_SIZE_KWARG
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
@ -66,17 +64,6 @@ class ListModelMixin(object):
return Response(serializer.data)
def get_paginate_by(self, queryset):
if self.page_size_kwarg is not None:
page_size_kwarg = self.request.QUERY_PARAMS.get(self.page_size_kwarg)
if page_size_kwarg:
try:
page_size = int(page_size_kwarg)
return page_size
except ValueError:
pass
return super(ListModelMixin, self).get_paginate_by(queryset)
class RetrieveModelMixin(object):
"""

View File

@ -54,12 +54,19 @@ DEFAULTS = {
'user': None,
'anon': None,
},
# Pagination
'PAGINATE_BY': None,
'PAGINATE_BY_PARAM': None,
# Filtering
'FILTER_BACKEND': None,
# Authentication
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
'UNAUTHENTICATED_TOKEN': None,
# Browser enhancements
'FORM_METHOD_OVERRIDE': '_method',
'FORM_CONTENT_OVERRIDE': '_content',
'FORM_CONTENTTYPE_OVERRIDE': '_content_type',
@ -67,8 +74,6 @@ DEFAULTS = {
'URL_FORMAT_OVERRIDE': 'format',
'FORMAT_SUFFIX_KWARG': 'format',
'PAGE_SIZE_KWARG': 'page_size'
}

View File

@ -36,25 +36,17 @@ if django_filters:
class DefaultPageSizeKwargView(generics.ListAPIView):
"""
View for testing default page_size usage
View for testing default paginate_by_param usage
"""
model = BasicModel
class CustomPageSizeKwargView(generics.ListAPIView):
class PaginateByParamView(generics.ListAPIView):
"""
View for testing custom page_size usage
View for testing custom paginate_by_param usage
"""
model = BasicModel
page_size_kwarg = 'ps'
class NonePageSizeKwargView(generics.ListAPIView):
"""
View for testing None page_size usage
"""
model = BasicModel
page_size_kwarg = None
paginate_by_param = 'page_size'
class IntegrationTestPagination(TestCase):
@ -181,9 +173,9 @@ class UnitTestPagination(TestCase):
self.assertEquals(serializer.data['results'], self.objects[20:])
class TestDefaultPageSizeKwarg(TestCase):
class TestUnpaginated(TestCase):
"""
Tests for list views with default page size kwarg
Tests for list views without pagination.
"""
def setUp(self):
@ -199,26 +191,17 @@ class TestDefaultPageSizeKwarg(TestCase):
]
self.view = DefaultPageSizeKwargView.as_view()
def test_default_page_size(self):
def test_unpaginated(self):
"""
Tests the default page size for this view.
no page size --> no limit --> no meta data
"""
request = factory.get('/')
response = self.view(request).render()
response = self.view(request)
self.assertEquals(response.data, self.data)
def test_default_page_size_kwarg(self):
"""
If page_size_kwarg is set not set, the default page_size kwarg should limit per view requests.
"""
request = factory.get('/?page_size=5')
response = self.view(request).render()
self.assertEquals(response.data['count'], 13)
self.assertEquals(response.data['results'], self.data[:5])
class TestCustomPageSizeKwarg(TestCase):
class TestCustomPaginateByParam(TestCase):
"""
Tests for list views with default page size kwarg
"""
@ -234,7 +217,7 @@ class TestCustomPageSizeKwarg(TestCase):
{'id': obj.id, 'text': obj.text}
for obj in self.objects.all()
]
self.view = CustomPageSizeKwargView.as_view()
self.view = PaginateByParamView.as_view()
def test_default_page_size(self):
"""
@ -245,55 +228,11 @@ class TestCustomPageSizeKwarg(TestCase):
response = self.view(request).render()
self.assertEquals(response.data, self.data)
def test_disabled_default_page_size_kwarg(self):
def test_paginate_by_param(self):
"""
If page_size_kwarg is set set, the default page_size kwarg should not work.
If paginate_by_param is set, the new kwarg should limit per view requests.
"""
request = factory.get('/?page_size=5')
response = self.view(request).render()
self.assertEquals(response.data, self.data)
def test_custom_page_size_kwarg(self):
"""
If page_size_kwarg is set set, the new kwarg should limit per view requests.
"""
request = factory.get('/?ps=5')
response = self.view(request).render()
self.assertEquals(response.data['count'], 13)
self.assertEquals(response.data['results'], self.data[:5])
class TestNonePageSizeKwarg(TestCase):
"""
Tests for list views with default page size kwarg
"""
def setUp(self):
"""
Create 13 BasicModel instances.
"""
for i in range(13):
BasicModel(text=i).save()
self.objects = BasicModel.objects
self.data = [
{'id': obj.id, 'text': obj.text}
for obj in self.objects.all()
]
self.view = NonePageSizeKwargView.as_view()
def test_default_page_size(self):
"""
Tests the default page size for this view.
no page size --> no limit --> no meta data
"""
request = factory.get('/')
response = self.view(request).render()
self.assertEquals(response.data, self.data)
def test_none_page_size_kwarg(self):
"""
If page_size_kwarg is set to None, custom page_size per request should be disabled.
"""
request = factory.get('/?page_size=5')
response = self.view(request).render()
self.assertEquals(response.data, self.data)