diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 46d97ea1..7dc0aee4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,10 @@ This document describes changes between each past release. - Provide the userid when calling the hello page with an Authorization header. (#319) +**Bug fixes** + +- ``data`` is not mandatory in request body if the resource does not define + any schema or if no field is mandatory (fixes mozilla-services/kinto#63) 2.3.1 (2015-07-15) ------------------ diff --git a/cliquet/resource.py b/cliquet/resource.py index fbc36643..95aa87f9 100644 --- a/cliquet/resource.py +++ b/cliquet/resource.py @@ -97,8 +97,18 @@ def get_record_schema(self, resource, method): # Simply validate that posted body is a mapping. return colander.MappingSchema(unknown='preserve') + record_mapping = resource.mapping + + try: + record_mapping.deserialize({}) + is_empty_accepted = True + except colander.Invalid: + is_empty_accepted = False + + record_mapping.missing = {} if is_empty_accepted else colander.required + class RecordPayload(colander.MappingSchema): - data = resource.mapping + data = record_mapping def schema_type(self, **kw): return colander.Mapping(unknown='raise') @@ -402,7 +412,7 @@ def collection_post(self): id_field = self.collection.id_field new_record[id_field] = _id = self.request.json['data'][id_field] self._raise_400_if_invalid_id(_id) - except KeyError: + except (KeyError, ValueError): pass new_record = self.process_record(new_record) diff --git a/cliquet/tests/resource/test_views.py b/cliquet/tests/resource/test_views.py index 70429d98..34b37496 100644 --- a/cliquet/tests/resource/test_views.py +++ b/cliquet/tests/resource/test_views.py @@ -259,6 +259,40 @@ def test_record_put_on_unexisting_record_is_rejected_if_write_perm(self): headers=self.headers, status=403) +class EmptySchemaTest(BaseWebTest): + collection_url = '/moistures' + + def test_accept_empty_body(self): + resp = self.app.post(self.collection_url, + headers=self.headers) + self.assertIn('id', resp.json['data']) + resp = self.app.put(self.get_item_url(uuid.uuid4()), + headers=self.headers) + self.assertIn('id', resp.json['data']) + + def test_data_can_be_specified(self): + resp = self.app.post_json(self.collection_url, + {'data': {}}, + headers=self.headers) + self.assertIn('id', resp.json['data']) + + def test_data_fields_are_ignored(self): + resp = self.app.post_json(self.collection_url, + {'data': {'icq': '9427392'}}, + headers=self.headers) + self.assertNotIn('icq', resp.json['data']) + + +class OptionalSchemaTest(EmptySchemaTest): + collection_url = '/psilos' + + def test_known_fields_are_saved(self): + resp = self.app.post_json(self.collection_url, + {'data': {'edible': False}}, + headers=self.headers) + self.assertIn('edible', resp.json['data']) + + class InvalidRecordTest(BaseWebTest): def setUp(self): super(InvalidRecordTest, self).setUp() diff --git a/cliquet/tests/testapp/views.py b/cliquet/tests/testapp/views.py index d03c82c4..6fbbb45b 100644 --- a/cliquet/tests/testapp/views.py +++ b/cliquet/tests/testapp/views.py @@ -14,3 +14,19 @@ class Mushroom(resource.BaseResource): @resource.register() class Toadstool(resource.ProtectedResource): mapping = MushroomSchema() + + +@resource.register() +class Moisture(resource.ProtectedResource): + # Empty schema. + pass + + +class PsilocybinSchema(resource.ResourceSchema): + # Optional fields. + edible = colander.SchemaNode(colander.Boolean(), missing=True) + + +@resource.register() +class Psilo(resource.ProtectedResource): + mapping = PsilocybinSchema()