Commit 31f01bd6 by Tom Christie

Polishing to page size query parameters & more docs

parent 9973cf32
...@@ -123,18 +123,36 @@ Each of the generic views provided is built by combining one of the base views b ...@@ -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. 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 ## MultipleObjectAPIView
Provides a base view for acting on a single object, by combining REST framework's `APIView`, and Django's [MultipleObjectMixin]. 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]. **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 ## SingleObjectAPIView
Provides a base view for acting on a single object, by combining REST framework's `APIView`, and Django's [SingleObjectMixin]. 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]. **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 # Mixins
...@@ -147,10 +165,6 @@ Provides a `.list(request, *args, **kwargs)` method, that implements listing a q ...@@ -147,10 +165,6 @@ Provides a `.list(request, *args, **kwargs)` method, that implements listing a q
Should be mixed in with [MultipleObjectAPIView]. 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 ## CreateModelMixin
Provides a `.create(request, *args, **kwargs)` method, that implements creating and saving a new model instance. Provides a `.create(request, *args, **kwargs)` method, that implements creating and saving a new model instance.
......
...@@ -96,11 +96,21 @@ Default: `rest_framework.serializers.ModelSerializer` ...@@ -96,11 +96,21 @@ Default: `rest_framework.serializers.ModelSerializer`
Default: `rest_framework.pagination.PaginationSerializer` 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 ## UNAUTHENTICATED_USER
...@@ -150,14 +160,10 @@ Default: `'accept'` ...@@ -150,14 +160,10 @@ Default: `'accept'`
Default: `'format'` Default: `'format'`
## PAGE_SIZE_KWARG ## FORMAT_SUFFIX_KWARG
Allows you to globally pass a page size parameter for an individual request.
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. **TODO**
Default: `'page_size'` Default: `'format'`
[cite]: http://www.python.org/dev/peps/pep-0020/ [cite]: http://www.python.org/dev/peps/pep-0020/
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
## Master ## Master
* Support for `read_only_fields` on `ModelSerializer` classes. * 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 ## 2.1.2
......
...@@ -14,6 +14,7 @@ class GenericAPIView(views.APIView): ...@@ -14,6 +14,7 @@ class GenericAPIView(views.APIView):
""" """
Base class for all other generic views. Base class for all other generic views.
""" """
model = None
serializer_class = None serializer_class = None
model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS
...@@ -30,8 +31,10 @@ class GenericAPIView(views.APIView): ...@@ -30,8 +31,10 @@ class GenericAPIView(views.APIView):
def get_serializer_class(self): def get_serializer_class(self):
""" """
Return the class to use for the serializer. 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 serializer_class = self.serializer_class
...@@ -58,29 +61,42 @@ class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView): ...@@ -58,29 +61,42 @@ class MultipleObjectAPIView(MultipleObjectMixin, GenericAPIView):
pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS
paginate_by = api_settings.PAGINATE_BY paginate_by = api_settings.PAGINATE_BY
paginate_by_param = api_settings.PAGINATE_BY_PARAM
filter_backend = api_settings.FILTER_BACKEND filter_backend = api_settings.FILTER_BACKEND
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
"""
if not self.filter_backend: if not self.filter_backend:
return queryset return queryset
backend = self.filter_backend() backend = self.filter_backend()
return backend.filter_queryset(self.request, queryset, self) 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 SerializerClass(self.pagination_serializer_class):
class Meta: class Meta:
object_serializer_class = self.get_serializer_class() object_serializer_class = self.get_serializer_class()
return SerializerClass pagination_serializer_class = SerializerClass
def get_pagination_serializer(self, page=None):
pagination_serializer_class = self.get_pagination_serializer_class()
context = self.get_serializer_context() context = self.get_serializer_context()
return pagination_serializer_class(instance=page, context=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): class SingleObjectAPIView(SingleObjectMixin, GenericAPIView):
""" """
......
...@@ -7,7 +7,6 @@ which allows mixin classes to be composed in interesting ways. ...@@ -7,7 +7,6 @@ which allows mixin classes to be composed in interesting ways.
from django.http import Http404 from django.http import Http404
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.settings import api_settings
class CreateModelMixin(object): class CreateModelMixin(object):
...@@ -40,7 +39,6 @@ class ListModelMixin(object): ...@@ -40,7 +39,6 @@ class ListModelMixin(object):
Should be mixed in with `MultipleObjectAPIView`. Should be mixed in with `MultipleObjectAPIView`.
""" """
empty_error = u"Empty list and '%(class_name)s.allow_empty' is False." 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): def list(self, request, *args, **kwargs):
queryset = self.get_queryset() queryset = self.get_queryset()
...@@ -66,17 +64,6 @@ class ListModelMixin(object): ...@@ -66,17 +64,6 @@ class ListModelMixin(object):
return Response(serializer.data) 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): class RetrieveModelMixin(object):
""" """
......
...@@ -54,12 +54,19 @@ DEFAULTS = { ...@@ -54,12 +54,19 @@ DEFAULTS = {
'user': None, 'user': None,
'anon': None, 'anon': None,
}, },
# Pagination
'PAGINATE_BY': None, 'PAGINATE_BY': None,
'PAGINATE_BY_PARAM': None,
# Filtering
'FILTER_BACKEND': None, 'FILTER_BACKEND': None,
# Authentication
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser', 'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
'UNAUTHENTICATED_TOKEN': None, 'UNAUTHENTICATED_TOKEN': None,
# Browser enhancements
'FORM_METHOD_OVERRIDE': '_method', 'FORM_METHOD_OVERRIDE': '_method',
'FORM_CONTENT_OVERRIDE': '_content', 'FORM_CONTENT_OVERRIDE': '_content',
'FORM_CONTENTTYPE_OVERRIDE': '_content_type', 'FORM_CONTENTTYPE_OVERRIDE': '_content_type',
...@@ -67,8 +74,6 @@ DEFAULTS = { ...@@ -67,8 +74,6 @@ DEFAULTS = {
'URL_FORMAT_OVERRIDE': 'format', 'URL_FORMAT_OVERRIDE': 'format',
'FORMAT_SUFFIX_KWARG': 'format', 'FORMAT_SUFFIX_KWARG': 'format',
'PAGE_SIZE_KWARG': 'page_size'
} }
......
...@@ -36,25 +36,17 @@ if django_filters: ...@@ -36,25 +36,17 @@ if django_filters:
class DefaultPageSizeKwargView(generics.ListAPIView): class DefaultPageSizeKwargView(generics.ListAPIView):
""" """
View for testing default page_size usage View for testing default paginate_by_param usage
""" """
model = BasicModel 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 model = BasicModel
page_size_kwarg = 'ps' paginate_by_param = 'page_size'
class NonePageSizeKwargView(generics.ListAPIView):
"""
View for testing None page_size usage
"""
model = BasicModel
page_size_kwarg = None
class IntegrationTestPagination(TestCase): class IntegrationTestPagination(TestCase):
...@@ -181,9 +173,9 @@ class UnitTestPagination(TestCase): ...@@ -181,9 +173,9 @@ class UnitTestPagination(TestCase):
self.assertEquals(serializer.data['results'], self.objects[20:]) 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): def setUp(self):
...@@ -199,26 +191,17 @@ class TestDefaultPageSizeKwarg(TestCase): ...@@ -199,26 +191,17 @@ class TestDefaultPageSizeKwarg(TestCase):
] ]
self.view = DefaultPageSizeKwargView.as_view() self.view = DefaultPageSizeKwargView.as_view()
def test_default_page_size(self): def test_unpaginated(self):
""" """
Tests the default page size for this view. Tests the default page size for this view.
no page size --> no limit --> no meta data no page size --> no limit --> no meta data
""" """
request = factory.get('/') request = factory.get('/')
response = self.view(request).render() response = self.view(request)
self.assertEquals(response.data, self.data) 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 Tests for list views with default page size kwarg
""" """
...@@ -234,7 +217,7 @@ class TestCustomPageSizeKwarg(TestCase): ...@@ -234,7 +217,7 @@ class TestCustomPageSizeKwarg(TestCase):
{'id': obj.id, 'text': obj.text} {'id': obj.id, 'text': obj.text}
for obj in self.objects.all() for obj in self.objects.all()
] ]
self.view = CustomPageSizeKwargView.as_view() self.view = PaginateByParamView.as_view()
def test_default_page_size(self): def test_default_page_size(self):
""" """
...@@ -245,55 +228,11 @@ class TestCustomPageSizeKwarg(TestCase): ...@@ -245,55 +228,11 @@ class TestCustomPageSizeKwarg(TestCase):
response = self.view(request).render() response = self.view(request).render()
self.assertEquals(response.data, self.data) 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') request = factory.get('/?page_size=5')
response = self.view(request).render() 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['count'], 13)
self.assertEquals(response.data['results'], self.data[:5]) 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)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment