Skip to content

Commit

Permalink
Merge e545af7 into c6f8ea9
Browse files Browse the repository at this point in the history
  • Loading branch information
phgross committed Sep 20, 2019
2 parents c6f8ea9 + e545af7 commit ecd0c8a
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 30 deletions.
2 changes: 2 additions & 0 deletions news/802.bugfix
@@ -0,0 +1,2 @@
Fire ModifiedEvent when field is set to null in a PATCH request.
[phgross]
73 changes: 43 additions & 30 deletions src/plone/restapi/deserializer/dxcontent.py
Expand Up @@ -33,14 +33,46 @@ def __init__(self, context, request):

self.sm = getSecurityManager()
self.permission_cache = {}
self.modified = {}

def __call__(
self, validate_all=False, data=None, create=False
): # noqa: ignore=C901

if data is None:
data = json_body(self.request)

modified = {}
schema_data, errors = self.get_schema_data(data, validate_all)

# Validate schemata
for schema, field_data in schema_data.items():
validator = queryMultiAdapter(
(self.context, self.request, None, schema, None), IManagerValidator
)
for error in validator.validate(field_data):
errors.append({"error": error, "message": str(error)})

if errors:
raise BadRequest(errors)

# We'll set the layout after the validation and and even if there
# are no other changes.
if "layout" in data:
layout = data["layout"]
self.context.setLayout(layout)

# OrderingMixin
self.handle_ordering(data)

if self.modified and not create:
descriptions = []
for interface, names in self.modified.items():
descriptions.append(Attributes(interface, *names))
notify(ObjectModifiedEvent(self.context, *descriptions))

return self.context

def get_schema_data(self, data, validate_all):
schema_data = {}
errors = []

Expand All @@ -65,6 +97,8 @@ def __call__(
# set the field to missing_value if we receive null
if data[name] is None:
if not field.required:
if dm.get():
self.mark_field_as_changed(schema, name)
dm.set(field.missing_value)
else:
errors.append(
Expand Down Expand Up @@ -97,10 +131,7 @@ def __call__(
field_data[name] = value
if value != dm.get():
dm.set(value)
# Collect the names of the modified fields
# Use prefixed name because z3c.form does so
prefixed_name = schema.__name__ + "." + name
modified.setdefault(schema, []).append(prefixed_name)
self.mark_field_as_changed(schema, name)

elif validate_all:
# Never validate the changeNote of p.a.versioningbehavior
Expand All @@ -116,33 +147,15 @@ def __call__(
except ValidationError as e:
errors.append({"message": e.doc(), "field": name, "error": e})

# Validate schemata
for schema, field_data in schema_data.items():
validator = queryMultiAdapter(
(self.context, self.request, None, schema, None), IManagerValidator
)
for error in validator.validate(field_data):
errors.append({"error": error, "message": str(error)})
return schema_data, errors

if errors:
raise BadRequest(errors)
def mark_field_as_changed(self, schema, fieldname):
"""Collect the names of the modified fields use prefixed name because
z3c.form does so.
"""

# We'll set the layout after the validation and and even if there
# are no other changes.
if "layout" in data:
layout = data["layout"]
self.context.setLayout(layout)

# OrderingMixin
self.handle_ordering(data)

if modified and not create:
descriptions = []
for interface, names in modified.items():
descriptions.append(Attributes(interface, *names))
notify(ObjectModifiedEvent(self.context, *descriptions))

return self.context
prefixed_name = schema.__name__ + '.' + fieldname
self.modified.setdefault(schema, []).append(prefixed_name)

def check_permission(self, permission_name):
if permission_name is None:
Expand Down
12 changes: 12 additions & 0 deletions src/plone/restapi/tests/test_dxcontent_deserializer.py
Expand Up @@ -91,6 +91,18 @@ def handler(obj, event):
self.event.descriptions[0].attributes,
)

def test_deserializer_notifies_when_field_is_set_to_null(self):
def handler(obj, event):
obj._handler_called = True
self.event = event
provideHandler(handler, (IDexterityItem, IObjectModifiedEvent,))
self.deserialize(body='{"test_textline_field": null}')
self.assertTrue(getattr(self.portal.doc1, '_handler_called', False),
'IObjectModifiedEvent not notified')
self.assertEqual(
('IDXTestDocumentSchema.test_textline_field',),
self.event.descriptions[0].attributes)

def test_deserializer_does_not_update_field_without_write_permission(self):
self.portal.doc1.test_write_permission_field = u"Test Write Permission"
setRoles(self.portal, TEST_USER_ID, ["Member", "Contributor", "Editor"])
Expand Down

0 comments on commit ecd0c8a

Please sign in to comment.