Skip to content

Add CursorPagination, enhance test client to handle client.post better #322

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ record count and a `links` object with the next, previous, first, and last links
Pages can be selected with the `page` GET parameter. Page size can be controlled
per request via the `PAGINATE_BY_PARAM` query parameter (`page_size` by default).


### Serializers

It is recommended to import the base serializer classes from this package
Expand All @@ -46,6 +47,37 @@ class MyModelSerializer(serializers.ModelSerializers):
# ...
```


### Pagination

This pacakge provides three paginators that correspond to the defaults
provided by django-rest-framework: PageNumberPagination, LimitOffsetPagination,
and CursorPagination.

Each of these paginators will format the serialized data to conform to the
json-api spec for pagination as defined
[here](http://jsonapi.org/format/#fetching-pagination).

To enable one of these paginators as the project default, you may add the
the following to your settings file:

```python
REST_FRAMEWORK = {
...
'DEFAULT_PAGINATION_CLASS':
'rest_framework_json_api.pagination.PageNumberPagination',
}
```

Alternatly you may define the paginator on the view itself as such:

```python
from rest_framework_json_api import pagination
class FooViewSet(viewsets.ModelViewSet):
pagination_class = pagination.CursorPagination
```


### Setting the resource_name

You may manually set the `resource_name` property on views, serializers, or
Expand Down
10 changes: 5 additions & 5 deletions example/tests/integration/test_pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ def test_pagination_with_single_entry(single_entry, client):
}
}],
"links": {
"first": "http://testserver/entries?page=1",
"last": "http://testserver/entries?page=1",
"next": None,
"prev": None,
},
"first": "http://testserver/entries?page=1",
"last": "http://testserver/entries?page=1",
"next": None,
"prev": None,
},
"meta":
{
"pagination":
Expand Down
29 changes: 21 additions & 8 deletions rest_framework_json_api/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@
Pagination fields
"""
from collections import OrderedDict
from rest_framework import serializers
from rest_framework import pagination
from rest_framework.views import Response
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination
from rest_framework.utils.urls import remove_query_param, replace_query_param


class PageNumberPagination(PageNumberPagination):
class PageNumberPagination(pagination.PageNumberPagination):
"""
A json-api compatible pagination format
"""

page_size_query_param = 'page_size'
max_page_size = 100

Expand Down Expand Up @@ -49,7 +47,7 @@ def get_paginated_response(self, data):
})


class LimitOffsetPagination(LimitOffsetPagination):
class LimitOffsetPagination(pagination.LimitOffsetPagination):
"""
A limit/offset based style. For example:
http://api.example.org/accounts/?page[limit]=100
Expand All @@ -75,10 +73,10 @@ def get_last_link(self):
def get_first_link(self):
if self.count == 0:
return None

url = self.request.build_absolute_uri()
return remove_query_param(url, self.offset_query_param)

def get_paginated_response(self, data):
return Response({
'results': data,
Expand All @@ -95,4 +93,19 @@ def get_paginated_response(self, data):
('next', self.get_next_link()),
('prev', self.get_previous_link())
])
})
})


class CursorPagination(pagination.CursorPagination):
"""
Cursor paginator that adheres to the json-api spec.
(http://jsonapi.org/examples/#pagination)
"""
def get_paginated_response(self, data):
return Response({
'results': data,
'links': OrderedDict([
('next', self.get_next_link()),
('previous', self.get_previous_link()),
])
})
8 changes: 4 additions & 4 deletions rest_framework_json_api/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from collections import OrderedDict

import inflection
from django.db.models import Manager, QuerySet
from django.db.models import Manager
from django.utils import six, encoding
from rest_framework import relations
from rest_framework import renderers
Expand Down Expand Up @@ -396,7 +396,7 @@ def build_json_resource_obj(cls, fields, resource, resource_instance, resource_n
resource_data.append(('links', {'self': resource[api_settings.URL_FIELD_NAME]}))
return OrderedDict(resource_data)

def render_relationship_view(self, data, accepted_media_type=None, renderer_context=None):
def render_relationship_view(self, data, accepted_media_type=None, renderer_context={}):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a mutable default parameter is generally a bad idea in python, due to:

Python's default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.

more details

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@santiavenda2 fair point, I guess this package needs to add better exception handling then since right off the bat if renderer_context is the default of None, the first line of code will fail. We're looking at other options though as this package lacks documentation and doesn't appear to be active anymore. The added complexity layer on top of DRF which is on top of Django just makes it very hard to deal with.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean you're ok closing this PR? This comment makes it seem to me like this is not important to you anymore, but I might be misreading. I'm wondering if I should spend the time to review or not.

# Special case for RelationshipView
view = renderer_context.get("view", None)
render_data = OrderedDict([
Expand All @@ -409,12 +409,12 @@ def render_relationship_view(self, data, accepted_media_type=None, renderer_cont
render_data, accepted_media_type, renderer_context
)

def render_errors(self, data, accepted_media_type=None, renderer_context=None):
def render_errors(self, data, accepted_media_type=None, renderer_context={}):
return super(JSONRenderer, self).render(
utils.format_errors(data), accepted_media_type, renderer_context
)

def render(self, data, accepted_media_type=None, renderer_context=None):
def render(self, data, accepted_media_type=None, renderer_context={}):

view = renderer_context.get("view", None)
request = renderer_context.get("request", None)
Expand Down