Skip to content

Commit 824fc10

Browse files
authored
Merge pull request AdCombo#55 from igieon/pagination_improvement
Pagination improvement use dict of ints and expect it everywhere
2 parents 2e9c546 + 98b71a5 commit 824fc10

File tree

5 files changed

+59
-24
lines changed

5 files changed

+59
-24
lines changed

docs/configuration.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Configuration
44
=============
55

6-
You have access to 5 configration keys:
6+
You have access to 5 configuration keys:
77

88
* PAGE_SIZE: the number of items in a page (default is 30)
99
* MAX_PAGE_SIZE: the maximum page size. If you specify a page size greater than this value you will receive a 400 Bad Request response.

flask_combo_jsonapi/data_layers/alchemy.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from marshmallow import class_registry
1313
from marshmallow.base import SchemaABC
1414

15-
from flask import current_app
1615
from flask_combo_jsonapi.data_layers.base import BaseDataLayer
1716
from flask_combo_jsonapi.data_layers.sorting.alchemy import create_sorts
1817
from flask_combo_jsonapi.exceptions import (
@@ -649,13 +648,13 @@ def paginate_query(self, query, paginate_info):
649648
:param dict paginate_info: pagination information
650649
:return Query: the paginated query
651650
"""
652-
if int(paginate_info.get("size", 1)) == 0:
651+
if paginate_info.get("size") == 0:
653652
return query
654653

655-
page_size = int(paginate_info.get("size", 0)) or current_app.config["PAGE_SIZE"]
654+
page_size = paginate_info.get("size")
656655
query = query.limit(page_size)
657656
if paginate_info.get("number"):
658-
query = query.offset((int(paginate_info["number"]) - 1) * page_size)
657+
query = query.offset((paginate_info["number"] - 1) * page_size)
659658

660659
return query
661660

flask_combo_jsonapi/pagination.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
"""Helper to create pagination links according to jsonapi specification"""
22

33
from __future__ import division
4-
from urllib.parse import urlencode
5-
from math import ceil
6-
from copy import copy
74

8-
from flask import current_app
5+
from copy import copy
6+
from math import ceil
7+
from urllib.parse import urlencode
98

109

1110
def add_pagination_links(data, object_count, querystring, base_url):
@@ -25,9 +24,9 @@ def add_pagination_links(data, object_count, querystring, base_url):
2524
if all_qs_args:
2625
links['self'] += '?' + urlencode(all_qs_args)
2726

28-
if querystring.pagination.get('size') != '0' and object_count > 1:
27+
if querystring.pagination.get('size') != 0 and object_count > 1:
2928
# compute last link
30-
page_size = int(querystring.pagination.get('size', 1)) or current_app.config['PAGE_SIZE']
29+
page_size = querystring.pagination.get('size')
3130
last_page = int(ceil(object_count / page_size))
3231

3332
if last_page > 1:
@@ -43,7 +42,7 @@ def add_pagination_links(data, object_count, querystring, base_url):
4342
links['last'] += '?' + urlencode(all_qs_args)
4443

4544
# compute previous and next link
46-
current_page = int(querystring.pagination.get('number', 0)) or 1
45+
current_page = querystring.pagination.get('number') or 1
4746
if current_page > 1:
4847
all_qs_args.update({'page[number]': current_page - 1})
4948
links['prev'] = '?'.join((base_url, urlencode(all_qs_args)))

flask_combo_jsonapi/querystring.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -91,34 +91,30 @@ def filters(self):
9191

9292
@property
9393
def pagination(self):
94-
"""Return all page parameters as a dict.
94+
"""Return parameters page[size] and page[number) as a dict.
95+
If missing parmeter `size` then default parameter PAGE_SIZE is used.
9596
9697
:return dict: a dict of pagination information
9798
98-
To allow multiples strategies, all parameters starting with `page` will be included. e.g::
99-
100-
{
101-
"number": '25',
102-
"size": '150',
103-
}
104-
10599
Example with number strategy::
106100
107101
>>> query_string = {'page[number]': '25', 'page[size]': '10'}
108102
>>> parsed_query.pagination
109-
{'number': '25', 'size': '10'}
103+
{'number': 25, 'size': 10}
110104
"""
111105
# check values type
112106
result = self._get_key_values('page')
113107
for key, value in result.items():
114108
if key not in ('number', 'size'):
115109
raise BadRequest("{} is not a valid parameter of pagination".format(key), source={'parameter': 'page'})
116110
try:
117-
int(value)
111+
result[key] = int(value)
118112
except ValueError:
119113
raise BadRequest("Parse error", source={'parameter': 'page[{}]'.format(key)})
120114

121-
if current_app.config.get('ALLOW_DISABLE_PAGINATION', True) is False and int(result.get('size', 1)) == 0:
115+
result.setdefault('size', current_app.config.get('PAGE_SIZE', 30))
116+
117+
if current_app.config.get('ALLOW_DISABLE_PAGINATION', True) is False and result.get('size') == 0:
122118
raise BadRequest("You are not allowed to disable pagination", source={'parameter': 'page[size]'})
123119

124120
if current_app.config.get('MAX_PAGE_SIZE') is not None and 'size' in result:

tests/test_sqlalchemy_data_layer.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,21 @@ def person_2(session, person_model):
198198
session_.commit()
199199

200200

201+
@pytest.fixture()
202+
def persons(session, person_model):
203+
persons = []
204+
session_ = session
205+
for i in range(100):
206+
person_ = person_model(name=f"test{i}")
207+
session_.add(person_)
208+
persons.append(person_)
209+
session_.commit()
210+
yield persons
211+
for person_ in persons:
212+
session_.delete(person_)
213+
session_.commit()
214+
215+
201216
@pytest.fixture()
202217
def computer(session, computer_model):
203218
computer_ = computer_model(serial="1")
@@ -621,6 +636,12 @@ def api_blueprint(client):
621636
yield bp
622637

623638

639+
@pytest.fixture()
640+
def app_disabled_pagination(app):
641+
app.config['PAGE_SIZE'] = 0
642+
yield app
643+
app.config['PAGE_SIZE'] = 30
644+
624645
@pytest.fixture(scope="module")
625646
def register_routes_custom_qs(
626647
client,
@@ -633,7 +654,6 @@ def register_routes_custom_qs(
633654
api.route(person_list_2, "person_list_qs", "/qs/persons")
634655
api.init_app(app)
635656

636-
637657
@pytest.fixture(scope="module")
638658
def register_routes(
639659
client,
@@ -838,6 +858,27 @@ def test_get_list(client, register_routes, person, person_2):
838858
assert response.status_code == 200
839859

840860

861+
def test_get_list_default_pagination(client, register_routes, persons):
862+
with client:
863+
response = client.get("/persons", content_type="application/vnd.api+json")
864+
assert response.status_code == 200
865+
assert len(response.json['data']) == 30
866+
assert response.json['meta']['count'] == len(persons)
867+
assert 'last' in response.json['links']
868+
assert 'first' in response.json['links']
869+
assert 'next' in response.json['links']
870+
871+
872+
def test_get_list_default_pagination_default_disabled(client, app_disabled_pagination, register_routes, persons):
873+
with client:
874+
response = client.get("/persons", content_type="application/vnd.api+json")
875+
assert response.status_code == 200
876+
assert len(response.json['data']) == len(persons)
877+
assert 'last' not in response.json['links']
878+
assert 'first' not in response.json['links']
879+
assert 'next' not in response.json['links']
880+
881+
841882
def test_get_list_with_simple_filter(client, register_routes, person, person_2):
842883
with client:
843884
querystring = urlencode(

0 commit comments

Comments
 (0)