From 322a3fb97db344f4721bdc41d208b75019179cdc Mon Sep 17 00:00:00 2001 From: Clemens Rudert Date: Tue, 6 Mar 2018 17:39:30 +0100 Subject: [PATCH] fix paging bug, add sorting via url params --- CHANGES.md | 5 +++ pyramid_georest/lib/description.py | 13 +++++++ pyramid_georest/lib/rest.py | 56 +++++++++++++++++++++++++----- setup.py | 2 +- 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1509cf1..cca8835 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ Pyramid REST Changelog ====================== +## 3.1.0-rc4 + +* fix paging which was called in wrong order for LIMIT and OFFSET +* implement sorting of one column via URL params _order_by_ and _direction_ + ## 3.1.0-rc3 * fix filtering for non ascii charsets when LIKE is used diff --git a/pyramid_georest/lib/description.py b/pyramid_georest/lib/description.py index 8b5ebf8..a80c524 100644 --- a/pyramid_georest/lib/description.py +++ b/pyramid_georest/lib/description.py @@ -327,3 +327,16 @@ def as_dict(self): 'column_count': self.column_count, 'columns': self.column_descriptions } + + def is_valid_column(self, column_name): + """ + + Args: + column_name (unicode): The column name which should be validated. + Returns: + bool: Whether the column name was valid for this model or not. + """ + if self.column_descriptions.get(column_name): + return True + else: + return False diff --git a/pyramid_georest/lib/rest.py b/pyramid_georest/lib/rest.py index d3e088b..1c6ffc2 100644 --- a/pyramid_georest/lib/rest.py +++ b/pyramid_georest/lib/rest.py @@ -24,9 +24,7 @@ from pyramid_georest.lib.renderer import RenderProxy, AdapterProxy from pyramid_georest.lib.database import Connection from pyramid_georest.routes import create_api_routing, check_route_prefix -from sqlalchemy import or_, and_ -from sqlalchemy import cast -from sqlalchemy import String +from sqlalchemy import or_, and_, cast, String, desc, asc from sqlalchemy.sql.expression import text from sqlalchemy.orm import Session from sqlalchemy.orm.exc import MultipleResultsFound @@ -38,6 +36,9 @@ log = logging.getLogger('pyramid_georest') +DIRECTION_ASC = ['ASC', 'asc', 'ascending'] +DIRECTION_DESC = ['DESC', 'desc', 'descending'] + class Clause(object): @@ -586,7 +587,8 @@ def geometry_treatment_(self, key, value): else: return value - def read(self, session, request, rest_filter=None, offset=None, limit=None): + def read(self, session, request, rest_filter=None, offset=None, limit=None, order_by=None, + direction=None): """ The method which is used by the api to read a bunch of records from the database. @@ -600,17 +602,27 @@ def read(self, session, request, rest_filter=None, offset=None, limit=None): present too. limit (int or None): The limit which is used for paging reason. It is only applied of offest is present too. + order_by (unicode or None): The column name which the sort is assigned to. It is only used if + direction is present too. + direction (unicode or None): The direction which is used for sorting. It is only used if order_by + is present too. Returns: list of sqlalchemy.ext.declarative.DeclarativeMeta: A list of database records found for the request. """ query = session.query(self.orm_model) + if rest_filter is not None: + query = rest_filter.filter(query) + if isinstance(order_by, unicode) and isinstance(direction, unicode): + column = self.model_description.column_classes.get(order_by) + if direction in DIRECTION_ASC: + query = query.order_by(asc(column)) + elif direction in DIRECTION_DESC: + query = query.order_by(desc(column)) if isinstance(offset, int) and isinstance(limit, int): query = query.offset(offset) query = query.limit(limit) - if rest_filter is not None: - query = rest_filter.filter(query) results = query.all() return results @@ -926,6 +938,8 @@ def read(self, request): rest_filter = Filter(service.model_description, **request.json_body.get('filter')) offset = request.params.get('offset') limit = request.params.get('limit') + order_by = request.params.get('order_by') + direction = request.params.get('direction') if offset and limit: try: offset = int(offset) @@ -941,9 +955,35 @@ def read(self, request): log.error(e) log.error(hint_txt) raise HTTPBadRequest(hint_txt) - results = service.read(session, request, rest_filter, offset=offset, limit=limit) else: - results = service.read(session, request, rest_filter) + offset = None + limit = None + + if order_by and direction: + if not isinstance(order_by, unicode): + hint_txt = 'Value for order_by has to be string.' + log.error(hint_txt) + raise HTTPBadRequest(hint_txt) + if not isinstance(direction, unicode): + hint_txt = 'Value for direction has to be string.' + log.error(hint_txt) + raise HTTPBadRequest(hint_txt) + if direction not in DIRECTION_ASC + DIRECTION_DESC: + raise HTTPBadRequest( + 'The parameter direction has to be one of the values: {0}'.format( + DIRECTION_ASC + DIRECTION_DESC + ) + ) + if not service.model_description.is_valid_column(order_by): + raise HTTPBadRequest('The parameter order_by has to be one of the models columns. The passed ' + 'column name was {}'.format(order_by) + ) + else: + order_by = None + direction = None + + results = service.read(session, request, rest_filter, offset=offset, limit=limit, + order_by=order_by, direction=direction) return service.renderer_proxy.render(request, results, service.model_description) def count(self, request): diff --git a/setup.py b/setup.py index d2d7baa..4c5932b 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ setup( name='pyramid_georest', - version='3.1.0-rc3', + version='3.1.0-rc4', description='pyramid_georest, extension for pyramid web frame work to provide rest interface for ' 'sql-alchemy mappers', long_description=README + '\n\n' + CHANGES,