Repository: plone/plone.restapi (language: python)

Issue:
Content creation does not set values for fields if new value is equal to default value of the field
Steps to reproduce:
* Create a new Event
* Set the value of start field to be the same value returned by the default_factory of the field
* Request the same content after some time, the value of the start field will be provided by the default_factory.



Discussion / hints:

Fix patch:
diff --git a/news/1207.bugfix b/news/1207.bugfix
new file mode 100644
index 0000000000..6e17c465bb
--- /dev/null
+++ b/news/1207.bugfix
@@ -0,0 +1,2 @@
+Fixes values not being stored during content creation if value is equal to the one returned by defaultFactory.
+[ericof]
\ No newline at end of file
diff --git a/src/plone/restapi/deserializer/dxcontent.py b/src/plone/restapi/deserializer/dxcontent.py
index affe7b7708..70fe9de181 100644
--- a/src/plone/restapi/deserializer/dxcontent.py
+++ b/src/plone/restapi/deserializer/dxcontent.py
@@ -41,7 +41,7 @@ def __call__(
         if data is None:
             data = json_body(self.request)
 
-        schema_data, errors = self.get_schema_data(data, validate_all)
+        schema_data, errors = self.get_schema_data(data, validate_all, create)
 
         # Validate schemata
         for schema, field_data in schema_data.items():
@@ -75,7 +75,7 @@ def __call__(
 
         return self.context
 
-    def get_schema_data(self, data, validate_all):
+    def get_schema_data(self, data, validate_all, create=False):
         schema_data = {}
         errors = []
 
@@ -133,7 +133,20 @@ def get_schema_data(self, data, validate_all):
                         errors.append({"message": e.doc(), "field": name, "error": e})
                     else:
                         field_data[name] = value
-                        if value != dm.get():
+                        current_value = dm.get()
+                        if value != current_value:
+                            should_change = True
+                        elif create and dm.field.defaultFactory:
+                            # During content creation we should set the value even if
+                            # it is the same from the dm if the current_value was
+                            # returned from a default_factory method
+                            should_change = (
+                                dm.field.defaultFactory(self.context) == current_value
+                            )
+                        else:
+                            should_change = False
+
+                        if should_change:
                             dm.set(value)
                             self.mark_field_as_changed(schema, name)
 


Test patch:
diff --git a/src/plone/restapi/tests/test_dxcontent_deserializer.py b/src/plone/restapi/tests/test_dxcontent_deserializer.py
index d349f2e864..e9cc6fdf14 100644
--- a/src/plone/restapi/tests/test_dxcontent_deserializer.py
+++ b/src/plone/restapi/tests/test_dxcontent_deserializer.py
@@ -41,11 +41,11 @@ def setUp(self):
                 "Document", id="doc" + str(x), title="Test doc " + str(x)
             )
 
-    def deserialize(self, body="{}", validate_all=False, context=None):
+    def deserialize(self, body="{}", validate_all=False, context=None, create=False):
         context = context or self.portal.doc1
         self.request["BODY"] = body
         deserializer = getMultiAdapter((context, self.request), IDeserializeFromJson)
-        return deserializer(validate_all=validate_all)
+        return deserializer(validate_all=validate_all, create=create)
 
     def test_deserializer_raises_with_invalid_body(self):
         with self.assertRaises(DeserializationError) as cm:
@@ -211,6 +211,24 @@ def test_set_layout(self):
         self.deserialize(body='{"layout": "my_new_layout"}')
         self.assertEqual("my_new_layout", self.portal.doc1.getLayout())
 
+    def test_deserializer_should_store_default_value_on_content_creation(self):
+        # Store default_factory values during content creation. This is specially
+        # important for content types like Event, where the default_factory returns
+        # a distinct value each time.
+        self.portal.invokeFactory(
+            "DXTestDocument",
+            id="doc_default_value",
+        )
+        context = self.portal.doc_default_value
+        default_value = "DefaultFactory"
+        body = {"test_default_factory_field": default_value}
+        self.deserialize(body=json.dumps(body), context=context, create=True)
+        self.assertIn(
+            "test_default_factory_field",
+            dir(context),
+            "Default value still available.",
+        )
+
 
 class TestDXContentSerializerDeserializer(unittest.TestCase):