Dexterity contextual dynamic schema behavior

What I am trying to achieve is a dynamic Dexterity Add/Edit form based on context but I''m not sure that this is possible, yet. Thus:

Let's say that I have the following folder site structure:

  • Vehicles
    • Cars
    • Trucks
  • Pets
    • Cats
  • Fashion
    • Kids
      • Shoes
      • .......

Now, I will define a MagicItem Dexterity c-type with this IMagicBehavior enabled.

This IMagicBehavior should return a set of fields when I add the MagicItem within Cars, another one when I add it within Cats and yet another one when adding within Kids > Shoes

The info for this custom schema is somehow available on each section.

The site structure grows exponentially, thus adding custom content-types for each site section is not an option.

collective.taxonomy does some dynamic magic stuff in this direction, but it's quite hard to follow.

Thus, I would appreciate if someone has a better example, or can help somehow with this.

2 Likes

You can add custom edit/add forms for IMagicContentType using https://docs.plone.org/external/plone.app.dexterity/docs/advanced/custom-add-and-edit-forms.html. This will allow you to implement a schema attribute somewhere that can check the context's parent if IMagicBehavior is enabled.

Alternatively you could use field permissions, but that sounds like a pita.

1 Like

@jaroel Thank you!

Following the documentation link you provided I managed to override the EditForm and AddForm like:

""" Custom Add/Edit forms
"""
from plone.dexterity.browser import edit
from plone.dexterity.browser import add
from plone.dexterity.utils import getAdditionalSchemata


class EditForm(edit.DefaultEditForm):
    """ Custom edit form """

    @property
    def additionalSchemata(self):
        for schema in super(EditForm, self).additionalSchemata:
            yield schema

        #
        # Custom code here
        #

class AddForm(add.DefaultAddForm):
    portal_type = 'magic'

    @property
    def additionalSchemata(self):
        for schema in super(AddForm, self).additionalSchemata:
            yield schema

        #
        # Custom code here
        #


class AddView(add.DefaultAddView):
    form = AddForm

Still, found something interesting digging into the code: The getAdditionalSchemata method called by additionalSchemata has support also for context.

See:

Are there any pitfalls using this approach?
Any chance that fields might disappear in copied / moved content ? If you move content from "Fashion" folder to "Sales" and edit it there ?

Well, I think you'll not be able to edit the missing fields, But the old attributes should persist on the new instance.

I'll let you know when I have a working code.

Meanwhile, after adding the following to my forms classes additionalSchemata:

    schemaclass = SchemaClass(
        'custom', (Schema, ),
            __module__='my.package.views.ad_form',
            attrs={
                'options': schema.Choice(
                    title=u"Options",
                    description=u"Options",
                    vocabulary=u"my.packages.vocabularies.Options",
                    required=True,
                )
            }
        )

    schemaclass.setTaggedValue(FIELDSETS_KEY,
        [Fieldset('features', label=_(u'Features'), fields=['options'])])

    yield schemaclass

I have a nice Add/Edit form, but when I click on Save:

TypeError: ('Could not adapt', <Acquisition.ImplicitAcquisitionWrapper object at 0x7f594a615b40>, <SchemaClass...

I guess you'll have to do something with IFormFieldProvider as well?
A full trace would help.

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.z3cform.layout, line 63, in __call__
* Module plone.z3cform.layout, line 47, in update
* Module plone.dexterity.browser.add, line 134, in update
* Module plone.z3cform.fieldsets.extensible, line 65, in update
* Module plone.z3cform.patch, line 30, in GroupForm_update
* Module z3c.form.group, line 145, in update
* Module plone.app.z3cform.csrf, line 22, in execute
* Module z3c.form.action, line 98, in execute
* Module z3c.form.button, line 315, in __call__
* Module z3c.form.button, line 170, in __call__
* Module plone.dexterity.browser.add, line 109, in handleAdd
* Module z3c.form.form, line 263, in createAndAdd
* Module plone.dexterity.browser.add, line 76, in create
* Module plone.dexterity.browser.add, line 193, in _applyChanges
* Module z3c.form.datamanager, line 91, in set
* Module z3c.form.datamanager, line 66, in adapted_context

TypeError: ('Could not adapt', <Acquisition.ImplicitAcquisitionWrapper object at 0x7f36bdaa75f0>, <SchemaClass...

What about implementing your own plone.behavior.interfaces.IBehaviorAssignable for the context of concern returning the behavior(s) needed in context?

Yea, makes more sense!

Well, the context is passed only in the edit mode. Thus, I will still have to override the AddForm.

I'll see what I can do.

A behaviorassignable is as flexible as you design and implement it.

1 Like

Please share if you get this to work, I have a feeling this will be useful one day.

PS: I had a (weird?) user case where I used the add-form to add the 'important fields' and the 'redirect' to the edit form on save (for all the 'not required/optional fields'). Could work as a workaround if everything else fails (?)

I managed to fix:

by providing a generic mutator:

class Mutator(object):
    """ Generic mutator
    """
    def __init__(self, context):
        self.__dict__['context'] = context

    def __setattr__(self, name, value):
        setattr(self.context, name, value)

    def __getattr__(self, name):
        return getattr(self.context, name, None)

and register it on the fly:

if not queryAdapter(context, schemaclass):
    provideAdapter(Mutator, (IMyContent,), schemaclass)

yield schemaclass

But providing an adapter on each Add / Edit Form call doesn't seem right to me.

Optimization suggestions are welcomed :wink:

See full gist: dynamic_schema.py · GitHub

Defently not 'optimising' , but if it was possible to have a 'folder full edit view' where you edited all the items in a folder at the same time… that would be magic :):alien:

Have you seen collective.ambidexterity? Being that it provides TTW (dynamic?) editing abilities for validators, defaults and vocabularies, maybe you could find some inspiration or use the package as a stepping stone for what you want to achieve.

See docs/tutorial: https://collectiveambidexterity.readthedocs.io/en/latest/tutorial.html

1 Like

Probably a bad idea, but imagine if something like this was possible (by something I am thinking about the UI).

  • Content 'Cats' has IMagicBehavior
  • Folder 'Pets' has a relation field to a (Collective) Easyform
  • In the easy form content type (related to from Pets) you add and edit fields
  • 'Cats' gets all the fields defined in the easy form.

I can see a dynamic schema being very useful in situations where you have to work with a vendor defined API. Just yesterday, I was looking at this mess https://developers.plentymarkets.com/rest-doc

Here you would have several dozen "types" of endpoints, which are individually described. The description could be processed by Plone, and on-the-fly create the required dexterity add and edit forms needed to create, read, update, delete content within the third party platform...

1 Like

Wasn't out there an add-on that saves collective.easyform input as Dexterity items?

PloneFormGen has one, but I dont think easy form has. (But FormGen is Archetype)

2 Likes