Recursive loop using default factory for a dx based content schema field

Hi, im trying to generate a default value for a field provided by a dx content schema on instance creation, which takes part of the value from a parents folder field ('kennung').

I follow the following docs:
Default values for fields on add forms

This is my DefaultFactory implementation:

> @provider(IContextAwareDefaultFactory)
> def getDefaultKennung(context):
>     kennung = getattr(context, 'kennung')
>     if not kennung:
>         context = aq_parent(aq_inner(context))
>         kennung = context.kennung
>         if not kennung:
>             context = aq_parent(aq_inner(context))
>             kennung = context.kennung
>     if not kennung:
>         kennung = '<kennung>'
>     else:
>         kennung = kennung + '-<Nummer>'
>     return  unicode(kennung)

When i add an instance, it works:

image

But when it tries to display the created content later on after saving in its standard view, the following recursion error occurs:

Traceback (innermost last):
  Module ZPublisher.Publish, line 138, in publish
  Module ZPublisher.mapply, line 77, in mapply
  Module ZPublisher.Publish, line 48, in call_object
  Module plone.autoform.view, line 45, in __call__
  Module plone.autoform.view, line 55, in _update
  Module z3c.form.form, line 136, in updateWidgets
  Module z3c.form.field, line 277, in update
  Module z3c.form.browser.text, line 36, in update
  Module z3c.form.browser.widget, line 171, in update
  Module Products.CMFPlone.patches.z3c_form, line 46, in _wrapped
  Module z3c.form.widget, line 106, in update
  Module z3c.form.datamanager, line 76, in query
  Module z3c.form.datamanager, line 71, in get
  Module plone.dexterity.content, line 669, in __getattr__
  Module plone.dexterity.content, line 324, in __getattr__
  Module plone.dexterity.content, line 65, in _default_from_schema
...
  Module vhd.artikeltypen.base, line 11, in getDefaultKennung
  Module plone.dexterity.content, line 669, in __getattr__
  Module plone.dexterity.content, line 324, in __getattr__
  Module plone.dexterity.content, line 65, in _default_from_schema
  Module zope.schema._bootstrapfields, line 71, in __get__
  Module vhd.artikeltypen.base, line 11, in getDefaultKennung
  Module plone.dexterity.content, line 669, in __getattr__
  Module plone.dexterity.content, line 324, in __getattr__
  Module plone.dexterity.content, line 65, in _default_from_schema
  Module zope.schema._bootstrapfields, line 71, in __get__
  Module vhd.artikeltypen.base, line 11, in getDefaultKennung
  Module plone.dexterity.content, line 669, in __getattr__
  Module plone.dexterity.content, line 324, in __getattr__
  Module plone.dexterity.content, line 65, in _default_from_schema
  Module zope.schema._bootstrapfields, line 71, in __get__
  Module vhd.artikeltypen.base, line 11, in getDefaultKennung
  Module plone.dexterity.content, line 669, in __getattr__
  Module plone.dexterity.content, line 324, in __getattr__
  Module plone.dexterity.content, line 65, in _default_from_schema
  Module zope.schema._bootstrapfields, line 71, in __get__
  Module vhd.artikeltypen.base, line 11, in getDefaultKennung
  Module plone.dexterity.content, line 669, in __getattr__
  Module plone.dexterity.content, line 324, in __getattr__
RuntimeError: maximum recursion depth exceeded while calling a Python object

I had a look into the plone.dexterity.content file mentioned, only to see that is tries to load a default value when access to the attribute is triggered via the getattr call in my defaultFactory causing the infinite loop.

How do i solve this Problem ? Is there another approach for my task ? Thank you very much for any advice.

System is Plone 4.3.19 with dexterity content developed on the filesystem.

Apparently, the field you are looking for is not accessible via an attribute lookup.Consequently, the mechanism tries to determine a value for it via the "default" machinery. As your default factory uses getattr, you get the loop.

I would ask why the field is not accessible via attribute lookup: has it not been saved?

To reduce the risk of infinite loops, you likely should avoid attribute access in your default factory. In many case, you can replace attribute access via a lookup in the instance's __dict__.

It's happening in the default process of SAVING a content type object instance after filling out the provided add form, where the calculated default value is displayed correctly, and hitting 'save'. It's the (first) display attempt of the object that causes the loop, i think. Every object of this content type triggers the loop when displayed and my defaultFactory is active.

I even disabled my defaultFactory, edited every content object to make sure it has a saved value for the field in question, and tried again - infinite loop.

But i will try to have a look into the dict attribute, without triggering a 'getattr' it should work. Thank you very much !

Further investigation discovered the following:

When the content edit form is filled in with the calculated value from the defaultFactory, for example because the field is missing on the content type because the field was recently added on the model schema so it is reasonable that content objects without this attribute exist in the ZODB, and then the user hits 'save' to 'store' this calculated default value as an attribute on the object, the creation and storing of the object attribute does apparently not occur.

Screenshots:

Edit of the content object without the 'kennung' attribute stored on the object, but value filled in by defaultFactory:
image

At the same time missing attribute on object, as expected. Field was added recently by schema extension:
image

Next hitting 'save' without changing any values on the edit form to store the auto generated default value on the missing field.

Reloading the object, field with saved default value still missing.
image

My guess is that the 'machinery' 'thinks', there were no changes and so no actual saving occured, although there was a prefilled field with an automatically calculated value present which does not exist on the object yet.

Changing the value before saving does solve the problem:
image

image

A very special use case, but for the records ...

Vincent via Plone Community wrote at 2024-2-12 08:55 +0000:

i created Example class from
34. Dexterity: Reference – Mastering Plone 5 development — Plone Training 2024 documentation
I edited, saved the object few time,
then suddendly i got error
"RuntimeError: maximum recursion depth exceeded while calling a Python object" too.

I can not delete the created Example class! from /test7777/folder_contents, or from ZMI

Have you removed the code for Example?
In this case, Example instances in the ZODB would be loaded
as broken objects which behave differently from "real" Example instances.
Should you have removed the class code, reinstalling it and then
deleting its instances from the ZODB is likely the easiest solution.

...
manually deleting:

folder.manage_delObjects(["primary-field"])
Traceback (most recent call last):
File ".../eggs/Products.BTreeFolder2-4.3-py3.8.egg/Products/BTreeFolder2/BTreeFolder2.py", line 219, in _getOb
return self._tree[id].of(self)
AttributeError: 'Example' object has no attribute 'of'

You can try even lower level API: folder._delOb(id).
Should this still result in an excepton, del folder._tree[id].

even "Clear and Rebuild" from portal_catalog did not help.

The use of low level API makes your catalog inconsistent;
the "Clear and Rebuild" should make it consistent again.

1 Like

Thx @dieter !!!
it worked by your advice!

removed class code and uninstall and reinstall addon,
no 'Example' type info imported
INFO [GenericSetup.types:100][waitress-0] 'Folder' type info imported.

then deleting from: /test7777/folder_contents:
Traceback (innermost last):
Module ZPublisher.WSGIPublisher, line 162, in transaction_pubevents
Module ZPublisher.WSGIPublisher, line 371, in publish_module
Module ZPublisher.WSGIPublisher, line 266, in publish
Module ZPublisher.mapply, line 85, in mapply
Module ZPublisher.WSGIPublisher, line 63, in call_object
Module plone.app.content.browser.contents.delete, line 62, in call
Module plone.app.linkintegrity.browser.info, line 40, in call
Module plone.app.linkintegrity.browser.info, line 57, in get_breaches
AttributeError: 'Example' object has no attribute 'getPhysicalPath'

but then this worked:
folder._delOb('primary-field')
import transaction; transaction.commit()

now doing your step "Clear and Rebuild"

Thank you very much!