From 2da7eaf25c2b367ed91936e8c27628f2b3232d9a Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Fri, 24 May 2013 00:12:06 +0200 Subject: [PATCH] Optional dictification of objects in JSON responses --- tests/test_rest_json.py | 105 +++++++++++++++++++++++++++++++++++++++ tgext/crud/controller.py | 57 ++++++++++++++------- tgext/crud/utils.py | 13 ++++- 3 files changed, 156 insertions(+), 19 deletions(-) diff --git a/tests/test_rest_json.py b/tests/test_rest_json.py index ff760ed..a796a82 100644 --- a/tests/test_rest_json.py +++ b/tests/test_rest_json.py @@ -55,6 +55,23 @@ def test_put_validation(self): movie = DBSession.query(Movie).first() assert movie.title == 'Movie Test' + def test_put_relationship(self): + result = self.app.post('/movies.json', params={'title':'Movie Test'}) + movie = result.json['value'] + + actors = [Actor(name='James Who'), Actor(name='John Doe'), Actor(name='Man Alone')] + map(DBSession.add, actors) + DBSession.flush() + actor_ids = [actor.actor_id for actor in actors[:2]] + transaction.commit() + + result = self.app.put('/movies/%s.json' % movie['movie_id'], + params={'title':'Movie Test', + 'actors':actor_ids}) + assert len(result.json['value']['actors']) == 2, result + + assert DBSession.query(Actor).filter_by(movie_id=movie['movie_id']).count() == 2 + def test_delete(self): DBSession.add(Movie(title='Fifth Movie')) DBSession.flush() @@ -173,3 +190,91 @@ def test_get_one___json__(self): assert result['model'] == 'Actor', result assert result['value']['name'] == actor.name assert result['value']['movie_title'] == movie_title + + +class TestRestJsonReadDictified(CrudTest): + def controller_factory(self): + class MovieController(EasyCrudRestController): + model = Movie + pagination = {'items_per_page': 3} + json_dictify = True + + class ActorController(EasyCrudRestController): + model = Actor + json_dictify = True + + class RestJsonController(TGController): + movies = MovieController(DBSession) + actors = ActorController(DBSession) + + return RestJsonController() + + def setUp(self): + super(TestRestJsonReadDictified, self).setUp() + genre = Genre(name='action') + DBSession.add(genre) + + actors = [Actor(name='James Who'), Actor(name='John Doe'), Actor(name='Man Alone')] + map(DBSession.add, actors) + + DBSession.add(Movie(title='First Movie', genre=genre, actors=actors[:2])) + DBSession.add(Movie(title='Second Movie', genre=genre, actors=actors[:2])) + DBSession.add(Movie(title='Third Movie', genre=genre)) + DBSession.add(Movie(title='Fourth Movie', genre=genre)) + DBSession.add(Movie(title='Fifth Movie')) + DBSession.add(Movie(title='Sixth Movie')) + DBSession.flush() + transaction.commit() + + def test_get_all(self): + result = self.app.get('/movies.json?order_by=movie_id') + result = result.json['value_list'] + assert result['total'] == 6, result + assert result['page'] == 1, result + assert result['entries'][0]['title'] == 'First Movie', result + assert len(result['entries'][0]['actors']) == 2, result + + result = self.app.get('/movies.json?page=2&order_by=movie_id') + result = result.json['value_list'] + assert result['total'] == 6, result + assert result['page'] == 2, result + assert result['entries'][0]['title'] == 'Fourth Movie', result + + def test_get_all_filter(self): + actor = DBSession.query(Actor).first() + + result = self.app.get('/actors.json?movie_id=%s' % actor.movie_id) + result = result.json['value_list'] + assert result['total'] == 2, result + + def test_get_all___json__(self): + actor = DBSession.query(Actor).filter(Actor.movie_id!=None).first() + movie_title = actor.movie.title + + result = self.app.get('/actors.json?movie_id=%s' % actor.movie_id) + result = result.json['value_list'] + assert result['total'] > 0, result + + for entry in result['entries']: + assert entry['movie_title'] == movie_title + + def test_get_one(self): + movie = DBSession.query(Movie).first() + movie_actors_count = len(movie.actors) + + result = self.app.get('/movies/%s.json' % movie.movie_id) + result = result.json + assert result['model'] == 'Movie', result + assert result['value']['title'] == movie.title + assert result['value']['movie_id'] == movie.movie_id + assert len(result['value']['actors']) == movie_actors_count + + def test_get_one___json__(self): + actor = DBSession.query(Actor).filter(Actor.movie_id!=None).first() + movie_title = actor.movie.title + + result = self.app.get('/actors/%s.json' % actor.actor_id) + result = result.json + assert result['model'] == 'Actor', result + assert result['value']['name'] == actor.name + assert result['value']['movie_title'] == movie_title diff --git a/tgext/crud/controller.py b/tgext/crud/controller.py index 4cde9f8..92723a2 100644 --- a/tgext/crud/controller.py +++ b/tgext/crud/controller.py @@ -8,7 +8,8 @@ from tgext.crud.decorators import (registered_validate, register_validators, catch_errors, optional_paginate) from tgext.crud.utils import (SmartPaginationCollection, RequestLocalTableFiller, create_setter, - set_table_filler_getter, SortableTableBase, map_args_to_pks) + set_table_filler_getter, SortableTableBase, map_args_to_pks, + adapt_params_for_pagination) from sprox.providerselector import ProviderTypeSelector from sprox.fillerbase import TableFiller from sprox.formbase import AddRecordForm, EditableForm @@ -79,6 +80,7 @@ class CrudRestController(RestController): remember_values = [] substring_filters = [] search_fields = True # True for automagic + json_dictify = False # True is slower but provides relations pagination = {'items_per_page': 7} style = Markup(''' #menu_items { @@ -200,6 +202,23 @@ def _get_current_search(self, search_fields): return (field, value) return (search_fields[0][0], '') + def _dictify(self, value, length=None): + json_dictify = self.json_dictify + if json_dictify is False: + return value + + def _dictify(entity): + if hasattr(entity, '__json__'): + return entity.__json__() + else: + return self.provider.dictify(entity, **json_dictify) + + if length is not None: + #return a generator, we don't want to consume the whole query if it is paginated + return (_dictify(entity) for entity in value) + else: + return _dictify(value) + def __init__(self, session, menu_items=None): if menu_items is None: menu_items = {} @@ -209,6 +228,10 @@ def __init__(self, session, menu_items=None): self.session = session + self.pagination_enabled = (self.pagination and isinstance(self.table_filler, RequestLocalTableFiller)) + if self.json_dictify is True: + self.json_dictify = {} + #this makes crc declarative check_types = ['new_form', 'edit_form', 'table', 'table_filler', 'edit_filler'] for type_ in check_types: @@ -241,29 +264,24 @@ def get_all(self, *args, **kw): paginator.paginate_page = 0 if tg.request.response_type == 'application/json': - count, entries = self.table_filler._do_get_provider_count_and_objs(**kw) - return dict(value_list=entries) + adapt_params_for_pagination(kw, self.pagination_enabled) + count, values = self.table_filler._do_get_provider_count_and_objs(**kw) + values = self._dictify(values, length=count) + if self.pagination_enabled: + values = SmartPaginationCollection(values, count) + return dict(value_list=values) if not getattr(self.table.__class__, '__retrieves_own_value__', False): - kw.pop('limit', None) - kw.pop('offset', None) kw.pop('substring_filters', None) - if self.substring_filters is True: substring_filters = kw.keys() else: substring_filters = self.substring_filters - if self.pagination and isinstance(self.table_filler, RequestLocalTableFiller): - paginator = request.paginators['value_list'] - page = paginator.paginate_page - 1 - values = self.table_filler.get_value(offset=page*paginator.paginate_items_per_page, - limit=paginator.paginate_items_per_page, - substring_filters=substring_filters, - **kw) + adapt_params_for_pagination(kw, self.pagination_enabled) + values = self.table_filler.get_value(substring_filters=substring_filters, **kw) + if self.pagination_enabled: values = SmartPaginationCollection(values, self.table_filler.__count__) - else: - values = self.table_filler.get_value(substring_filters=substring_filters, **kw) else: values = [] @@ -285,8 +303,9 @@ def get_one(self, *args, **kw): kw = map_args_to_pks(args, {}) if tg.request.response_type == 'application/json': + obj = self.provider.get_obj(self.model, kw) return dict(model=self.model.__name__, - value=self.provider.get_obj(self.model, kw)) + value=self._dictify(obj)) tmpl_context.widget = self.edit_form value = self.edit_filler.get_value(kw) @@ -322,7 +341,8 @@ def post(self, *args, **kw): obj = self.provider.create(self.model, params=kw) if tg.request.response_type == 'application/json': - return dict(model=self.model.__name__, value=obj) + return dict(model=self.model.__name__, + value=self._dictify(obj)) return redirect('./', params=self._kept_params()) @@ -344,7 +364,8 @@ def put(self, *args, **kw): obj = self.provider.update(self.model, params=kw, omit_fields=omit_fields) if tg.request.response_type == 'application/json': - return dict(model=self.model.__name__, value=obj) + return dict(model=self.model.__name__, + value=self._dictify(obj)) pks = self.provider.get_primary_fields(self.model) return redirect('../' * len(pks), params=self._kept_params()) diff --git a/tgext/crud/utils.py b/tgext/crud/utils.py index b035612..9dec71f 100644 --- a/tgext/crud/utils.py +++ b/tgext/crud/utils.py @@ -151,4 +151,15 @@ def map_args_to_pks(remainder, params): if pk not in params and i < len(remainder): params[pk] = remainder[i] - return params \ No newline at end of file + return params + +def adapt_params_for_pagination(params, pagination_enabled=True): + params.pop('limit', None) + params.pop('offset', None) + + if pagination_enabled: + paginator = request.paginators['value_list'] + page = paginator.paginate_page - 1 + + params['offset'] = page*paginator.paginate_items_per_page + params['limit'] = paginator.paginate_items_per_page \ No newline at end of file