From 854c4e18caf67fc26d0b77b9f5f5116afc6cd416 Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Sun, 26 May 2013 16:24:36 +0200 Subject: [PATCH] Enable submitting values with a JSON body and refactor test suite --- tests/test_rest_json.py | 66 +++++++++++++++++++++ tests/test_rest_json_body.py | 70 ++++++++++++++++++++++ tests/test_rest_json_dictified.py | 98 +++++++++++++++++++++++++++++++ tgext/crud/controller.py | 4 +- tgext/crud/utils.py | 6 +- 5 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 tests/test_rest_json_body.py create mode 100644 tests/test_rest_json_dictified.py diff --git a/tests/test_rest_json.py b/tests/test_rest_json.py index 37a9867..f8ab5f7 100644 --- a/tests/test_rest_json.py +++ b/tests/test_rest_json.py @@ -5,6 +5,8 @@ import transaction class TestRestJsonEditCreateDelete(CrudTest): + """Basic tests for POST, PUT & DELETE using urlencoded parameters and requesting for JSON responses""" + def controller_factory(self): class MovieController(EasyCrudRestController): model = Movie @@ -128,6 +130,8 @@ def test_delete_nofilter(self): assert movie is not None class TestRestJsonRead(CrudTest): + """Basic tests for GET requests with JSON responses""" + def controller_factory(self): class MovieController(EasyCrudRestController): model = Movie @@ -311,3 +315,65 @@ 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 TestRestJsonEditCreateJsonBody(CrudTest): + def controller_factory(self): + class MovieController(EasyCrudRestController): + model = Movie + + class RestJsonController(TGController): + movies = MovieController(DBSession) + + return RestJsonController() + + def test_post(self): + result = self.app.post_json('/movies.json', params={'title':'Movie Test'}) + + movie = DBSession.query(Movie).first() + assert movie is not None, result + + assert movie.movie_id == result.json['value']['movie_id'] + + def test_post_validation(self): + result = self.app.post_json('/movies.json', params={'title':''}, status=400) + assert result.json['title'] is not None #there is an error for required title + assert result.json['description'] is None #there isn't any error for optional description + + assert DBSession.query(Movie).first() is None + + def test_put(self): + result = self.app.post_json('/movies.json', params={'title':'Movie Test'}) + movie = result.json['value'] + + result = self.app.put_json('/movies/%s.json' % movie['movie_id'], + params={'title':'New Title'}) + assert result.json['value']['title'] == 'New Title' + + def test_put_validation(self): + result = self.app.post_json('/movies.json', params={'title':'Movie Test'}) + movie = result.json['value'] + + result = self.app.put_json('/movies/%s.json' % movie['movie_id'], + params={'title':''}, status=400) + assert result.json['title'] is not None #there is an error for required title + assert result.json['description'] is None #there isn't any error for optional description + + movie = DBSession.query(Movie).first() + assert movie.title == 'Movie Test' + + def test_put_relationship(self): + result = self.app.post_json('/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_json('/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 diff --git a/tests/test_rest_json_body.py b/tests/test_rest_json_body.py new file mode 100644 index 0000000..e9b2760 --- /dev/null +++ b/tests/test_rest_json_body.py @@ -0,0 +1,70 @@ +from tg import TGController +from tgext.crud import EasyCrudRestController +from .base import CrudTest, Movie, DBSession, metadata, Genre, Actor + +import transaction + + +class TestRestJsonEditCreateJsonBody(CrudTest): + """Tests for submitting POST & PUT values through a JSON body instead of parameters""" + + def controller_factory(self): + class MovieController(EasyCrudRestController): + model = Movie + + class RestJsonController(TGController): + movies = MovieController(DBSession) + + return RestJsonController() + + def test_post(self): + result = self.app.post_json('/movies.json', params={'title':'Movie Test'}) + + movie = DBSession.query(Movie).first() + assert movie is not None, result + + assert movie.movie_id == result.json['value']['movie_id'] + + def test_post_validation(self): + result = self.app.post_json('/movies.json', params={'title':''}, status=400) + assert result.json['title'] is not None #there is an error for required title + assert result.json['description'] is None #there isn't any error for optional description + + assert DBSession.query(Movie).first() is None + + def test_put(self): + result = self.app.post_json('/movies.json', params={'title':'Movie Test'}) + movie = result.json['value'] + + result = self.app.put_json('/movies/%s.json' % movie['movie_id'], + params={'title':'New Title'}) + assert result.json['value']['title'] == 'New Title' + + def test_put_validation(self): + result = self.app.post_json('/movies.json', params={'title':'Movie Test'}) + movie = result.json['value'] + + result = self.app.put_json('/movies/%s.json' % movie['movie_id'], + params={'title':''}, status=400) + assert result.json['title'] is not None #there is an error for required title + assert result.json['description'] is None #there isn't any error for optional description + + movie = DBSession.query(Movie).first() + assert movie.title == 'Movie Test' + + def test_put_relationship(self): + result = self.app.post_json('/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_json('/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 diff --git a/tests/test_rest_json_dictified.py b/tests/test_rest_json_dictified.py new file mode 100644 index 0000000..4b40d39 --- /dev/null +++ b/tests/test_rest_json_dictified.py @@ -0,0 +1,98 @@ +from tg import TGController +from tgext.crud import EasyCrudRestController +from .base import CrudTest, Movie, DBSession, metadata, Genre, Actor + +import transaction + + +class TestRestJsonReadDictified(CrudTest): + """ + Tests for GET requests that enabled dictification, this will rely on + sprox provider dictify function to resolve also relationships. + """ + + 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)) + 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 e446d2d..78b1ef4 100644 --- a/tgext/crud/controller.py +++ b/tgext/crud/controller.py @@ -9,7 +9,7 @@ optional_paginate) from tgext.crud.utils import (SmartPaginationCollection, RequestLocalTableFiller, create_setter, set_table_filler_getter, SortableTableBase, map_args_to_pks, - adapt_params_for_pagination) + adapt_params_for_pagination, allow_json_parameters) from sprox.providerselector import ProviderTypeSelector from sprox.fillerbase import TableFiller from sprox.formbase import AddRecordForm, EditableForm @@ -337,6 +337,7 @@ def new(self, *args, **kw): @expose(content_type='text/html') @expose('json:', content_type='application/json') + @before_validate(allow_json_parameters) @catch_errors(errors, error_handler=new) @registered_validate(error_handler=new) def post(self, *args, **kw): @@ -350,6 +351,7 @@ def post(self, *args, **kw): @expose(content_type='text/html') @expose('json:', content_type='application/json') + @before_validate(allow_json_parameters) @before_validate(map_args_to_pks) @registered_validate(error_handler=edit) @catch_errors(errors, error_handler=edit) diff --git a/tgext/crud/utils.py b/tgext/crud/utils.py index 9dec71f..3fc948f 100644 --- a/tgext/crud/utils.py +++ b/tgext/crud/utils.py @@ -162,4 +162,8 @@ def adapt_params_for_pagination(params, pagination_enabled=True): 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 + params['limit'] = paginator.paginate_items_per_page + +def allow_json_parameters(remainder, params): + if request.content_type == 'application/json': + params.update(request.json_body) \ No newline at end of file