Skip to content

Commit

Permalink
Merge pull request #114 from Marcelo-Theodoro/add-django-paginate
Browse files Browse the repository at this point in the history
Add pagination in the DjangoResource
  • Loading branch information
seocam authored Jan 15, 2019
2 parents 0accfd0 + 137fc61 commit b1b674a
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 20 deletions.
10 changes: 0 additions & 10 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ language: python

python:
- "2.7"
- "3.3"
- "3.4"
- "3.5"
- "pypy" # cPython 2.7
Expand All @@ -15,15 +14,6 @@ env:
- DJANGO="1.10"
- DJANGO="1.11"

matrix:
exclude:
- python: "3.3"
env: DJANGO="1.9"
- python: "3.3"
env: DJANGO="1.10"
- python: "3.3"
env: DJANGO="1.11"

# command to install dependencies
install:
- pip install six tox-travis
Expand Down
3 changes: 1 addition & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Features
* Small, fast codebase
* JSON output by default, but overridable
* RESTful
* Python 3.2+ (with shims to make broke-ass Python 2.6+ work)
* Python 3.4+ (with shims to make broke-ass Python 2.6+ work)
* Django 1.8+
* Flexible

Expand Down Expand Up @@ -158,7 +158,6 @@ The test suite uses tox_ for simultaneous support of multiple versions of both
Python and Django. The current versions of Python supported are:

* CPython 2.7
* CPython 3.3
* CPython 3.4
* CPython 3.5
* CPython 3.6
Expand Down
3 changes: 1 addition & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Features
* Small, fast codebase
* JSON output by default, but overridable
* RESTful
* Python 3.2+ (with shims to make broke-ass Python 2.6+ work)
* Python 3.4+ (with shims to make broke-ass Python 2.6+ work)
* Flexible


Expand Down Expand Up @@ -92,4 +92,3 @@ Indices and tables
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

9 changes: 9 additions & 0 deletions docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ We'll start with the most basic functional example.::


class PostResource(DjangoResource):

paginate = True
page_size = 20

preparer = FieldsPreparer(fields={
'id': 'id',
'title': 'title',
Expand All @@ -194,6 +198,11 @@ is kinda important.
The name of the class isn't particularly important, but it should be
descriptive (and can play a role in hooking up URLs later on).

The ``paginate`` defines if the results should be paginated or not.
You can configure how many results per page setting ``page_size`` in
your resource, or using the configuring ``RESTLESS_PAGE_SIZE`` in your settings.
By default, is 10 objects per page.

Fields
------

Expand Down
42 changes: 41 additions & 1 deletion restless/dj.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
from django.conf import settings
from django.conf.urls import url
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator
from django.http import HttpResponse, Http404
from django.views.decorators.csrf import csrf_exempt

from .constants import OK, NO_CONTENT
from .exceptions import NotFound
from .exceptions import NotFound, BadRequest
from .resources import Resource


Expand All @@ -18,6 +19,45 @@ class DjangoResource(Resource):
Doesn't require any special configuration, but helps when working in a
Django environment.
"""

def serialize_list(self, data):
if data is None:
return super(DjangoResource, self).serialize_list(data)

if getattr(self, 'paginate', False):
page_size = getattr(self, 'page_size', getattr(settings, 'RESTLESS_PAGE_SIZE', 10))
paginator = Paginator(data, page_size)

page_number = self.request.GET.get('p', 1)

if page_number not in paginator.page_range:
raise BadRequest('Invalid page number')

self.page = paginator.page(page_number)
data = self.page.object_list

return super(DjangoResource, self).serialize_list(data)

def wrap_list_response(self, data):
response_dict = super(DjangoResource, self).wrap_list_response(data)

if hasattr(self, 'page'):
next_page = self.page.has_next() and self.page.next_page_number() or None
previous_page = self.page.has_previous() and self.page.previous_page_number() or None

response_dict['pagination'] = {
'num_pages': self.page.paginator.num_pages,
'count': self.page.paginator.count,
'page': self.page.number,
'start_index': self.page.start_index(),
'end_index': self.page.end_index(),
'next_page': next_page,
'previous_page': previous_page,
'per_page': self.page.paginator.per_page,
}

return response_dict

# Because Django.
@classmethod
def as_list(self, *args, **kwargs):
Expand Down
4 changes: 3 additions & 1 deletion tests/fakes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@


class FakeHttpRequest(object):
def __init__(self, method='GET', body=''):
def __init__(self, method='GET', body='', **kwargs):
self.method = method.upper()
self.body = body
if six.PY3:
self.body = body.encode('utf-8')
if self.method == 'GET':
self.GET = kwargs.get('get_request', {})


class FakeHttpResponse(object):
Expand Down
92 changes: 92 additions & 0 deletions tests/test_dj.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ def schema(self):
}


class DjTestResourcePaginated(DjTestResource):
paginate = True


class DjTestResourcePaginatedOnePerPage(DjTestResourcePaginated):
page_size = 1


class DjTestResourceHttp404Handling(DjTestResource):
def detail(self, pk):
for item in self.fake_db:
Expand Down Expand Up @@ -187,6 +195,90 @@ def test_as_list(self):
]
})

def test_as_list_paginated(self):
list_endpoint = DjTestResourcePaginated().as_list()
req = FakeHttpRequest('GET')

resp = list_endpoint(req)
self.assertEqual(resp['Content-Type'], 'application/json')
self.assertEqual(resp.status_code, 200)
self.assertEqual(
json.loads(resp.content.decode('utf-8')),
{
'objects': [
{
'author': 'daniel',
'body': 'Hello world!',
'id': 'dead-beef',
'title': 'First post',
},
{
'author': 'daniel',
'body': 'Stuff here.',
'id': 'de-faced',
'title': 'Another',
},
{
'author': 'daniel',
'body': "G'bye!",
'id': 'bad-f00d',
'title': 'Last',
},
],
'pagination': {
'num_pages': 1,
'count': 3,
'page': 1,
'start_index': 1,
'end_index': 3,
'next_page': None,
'previous_page': None,
'per_page': 10,
},
},
)

def test_as_list_paginated_second_page(self):
list_endpoint = DjTestResourcePaginatedOnePerPage(page_size=1).as_list()

req = FakeHttpRequest('GET', get_request={'p': 2})

resp = list_endpoint(req)
self.assertEqual(resp['Content-Type'], 'application/json')
self.assertEqual(resp.status_code, 200)

self.assertEqual(
json.loads(resp.content.decode('utf-8')),
{
'objects': [
{
'author': 'daniel',
'body': 'Stuff here.',
'id': 'de-faced',
'title': 'Another',
},
],
'pagination': {
'num_pages': 3,
'count': 3,
'page': 2,
'start_index': 2,
'end_index': 2,
'next_page': 3,
'previous_page': 1,
'per_page': 1,
},
},
)

def test_as_list_paginated_invalid_page(self):
list_endpoint = DjTestResourcePaginated().as_list()
req = FakeHttpRequest('GET', get_request={'p': 2})

resp = list_endpoint(req)
self.assertEqual(resp['Content-Type'], 'application/json')
self.assertEqual(resp.status_code, 400)

def test_as_detail(self):
detail_endpoint = DjTestResource.as_detail()
req = FakeHttpRequest('GET')
Expand Down
6 changes: 2 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
[tox]
envlist =
py{27,33,34,35,py2}-{dj18}
py{27,34,35,py2}-{dj18}
py{27,34,35,py2}-{dj19,dj110,dj111}
py{36}-{dj111}

[testenv]
basepython =
py27: python2.7
py33: python3.3
py34: python3.4
py35: python3.5
py36: python3.6
Expand All @@ -19,7 +18,7 @@ deps =
WebOb>=1.3.1,<1.7
Pyramid<1.8
tornado
py{27,33,34,35}: Flask>=0.10
py{27,34,35}: Flask>=0.10
dj18: Django>=1.8,<1.9
dj19: Django>=1.9,<1.10
dj110: Django>=1.10,<1.11
Expand All @@ -30,7 +29,6 @@ commands =
[travis]
python =
2.7: py27
3.3: py33
3.4: py34
3.5: py35
3.6: py36
Expand Down

0 comments on commit b1b674a

Please sign in to comment.