diff --git a/README.md b/README.md index 1fcbeb0..9c3e799 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,8 @@ register((Artist, Album, Playlist)) ``` And if you wanted to add custom logic for an endpoint? Or change the endpoint -name? Or add validation? All supported. Here's a "fancy" class definition: +name? Or change your top level json object name? Or add validation? All supported. +Here's a "fancy" class definition: ```python class Style(Model): @@ -172,6 +173,7 @@ class Style(Model): __tablename__ = 'Genre' __endpoint__ = 'styles' __methods__ = ('GET', 'DELETE') + __top_level_json_name__ = 'Genres' @staticmethod def validate_GET(resource=None): diff --git a/sandman/model/models.py b/sandman/model/models.py index 7c8cc89..1773dcd 100644 --- a/sandman/model/models.py +++ b/sandman/model/models.py @@ -29,6 +29,12 @@ class Model(object): Default: None """ + __top_level_json_name__ = None + """The top level json text to output for this class + + Default: 'resources' + """ + __methods__ = ('GET', 'POST', 'PATCH', 'DELETE', 'PUT') """override :attr:`__methods__` if you wish to change the HTTP methods this :class:`sandman.model.Model` supports. diff --git a/sandman/sandman.py b/sandman/sandman.py index e2f5598..ed66552 100644 --- a/sandman/sandman.py +++ b/sandman/sandman.py @@ -117,20 +117,31 @@ def _single_resource_html_response(resource): return make_response(render_template('resource.html', resource=resource, tablename=tablename)) -def _collection_json_response(resources, start, stop, depth=0): +def _collection_json_response(cls, resources, start, stop, depth=0): """Return the JSON representation of the collection *resources*. :param list resources: list of :class:`sandman.model.Model`s to render :rtype: :class:`flask.Response` """ + + top_level_json_name = None + if cls.__top_level_json_name__ is not None: + top_level_json_name = cls.__top_level_json_name__ + else: + top_level_json_name = 'resources' + result_list = [] for resource in resources: result_list.append(resource.as_dict(depth)) + + payload = {} if start is not None: - return jsonify(resources=result_list[start:stop]) + payload[top_level_json_name] = result_list[start:stop] else: - return jsonify(resources=result_list) + payload[top_level_json_name] = result_list + + return jsonify(payload) def _collection_html_response(resources, start=0, stop=20): """Return the HTML representation of the collection *resources*. @@ -262,7 +273,7 @@ def resource_created_response(resource): resource.resource_uri()) return response -def collection_response(resources, start=None, stop=None): +def collection_response(cls, resources, start=None, stop=None): """Return a response for the *resources* of the appropriate content type. :param resources: resources to be returned in request @@ -271,7 +282,7 @@ def collection_response(resources, start=None, stop=None): """ if _get_acceptable_response_type() == JSON: - return _collection_json_response(resources, start, stop) + return _collection_json_response(cls, resources, start, stop) else: return _collection_html_response(resources, start, stop) @@ -487,7 +498,7 @@ def get_collection(collection): page = int(request.args['page']) results_per_page = app.config.get('RESULTS_PER_PAGE', 20) start, stop = page * results_per_page, (page +1) * results_per_page - return collection_response(resources, start, stop) + return collection_response(cls, resources, start, stop) @app.route('/', methods=['GET']) @etag diff --git a/tests/models.py b/tests/models.py index f85ed0e..4914087 100644 --- a/tests/models.py +++ b/tests/models.py @@ -51,6 +51,7 @@ class Album(Model): __tablename__ = 'Album' __methods__ = ('POST', 'PATCH', 'DELETE', 'PUT', 'GET') + __top_level_json_name__ = 'Albums' def __str__(self): """Return string representation of *self*.""" diff --git a/tests/test_sandman.py b/tests/test_sandman.py index c7d5a8e..014849e 100644 --- a/tests/test_sandman.py +++ b/tests/test_sandman.py @@ -289,6 +289,12 @@ def test_put_fail_validation(self): 'UnitPrice': 0.99,})) assert response.status_code == 403 + def test_responds_with_top_level_json_name_if_present(self): + """Test top level json element is the one defined on the Model + rather than the string 'resources'""" + response = self.get_response('/albums', 200) + assert len(json.loads(response.get_data(as_text=True))[u'Albums']) == 347 + class TestSandmanValidation(TestSandmanBase): """Sandman tests related to request validation"""