Ticket #12238 (closed PLIP: wontfix)

Opened 5 years ago

Last modified 4 years ago

Add multi-sources personalize form extension

Reported by: encolpe Owned by: encolpe
Priority: minor Milestone: 4.3
Component: General Version: 4.1
Keywords: plone.app.users Cc: jstegle

Description

Proposer: Encolpe DEGOUTE
Seconder: Julien STEGLE

Motivation

The plone.app.users.userdataschema.IUserDataSchemaProvider doesn't allow extesions. As that utility is unnamed, you can override it only once in all packages. If several packages try to override that interface only the last one is activated. We had to add extra fields on our personal-information form from various packages, so this package was created with the idea of managing multiple sources overrides of the personal-information form and properties.

Assumptions

We had to add extra fields on our personal-information form from various packages by using adapters. By adding extra fields, we can specify your own getters & setters if we don't want your field to be managed as a user property. we'll also be able to add compatible widgets for the personal preferences' form fields.

That implementation will only extend the form and won't manage the storage as the current implementation.

Proposal & Implementation

Proposal

There are three interfaces you need to know:

collective.customizablePersonalizeForm.adapters.interfaces.IExtendedUserDataSchema(Interface):

    def getSchema(self,):

collective.customizablePersonalizeForm.adapters.interfaces.IExtendedUserDataPanel(Interface):
    
    def getProperties(self,):

collective.customizablePersonalizeForm.adapters.interfaces.IExtendedUserDataWidgets(Interface):

    def getWidgets(self,):

An adapter providing the IExtendedUserDataSchema allows to add additional fields by returning an Interface through its getSchema method.

Carefull !

If you ever add a field on your form, you'll also need to declare a property with the same name as your field, allowing you to get and set your property. That's why we use IExtendedUserDataPanel. An adapter providing this interface will return a list of strings through the getProperties method.

Those resultant strings are the names of the fields provided by your custom interface, like 'portrait' or 'fullname'. By default the getters and setters of your fields will use Plone's member.getProperty and member.setProperty methods, using the memberdata to store the values. If you ever need to use your own getters and setters, you can return a dict instead of the name of your field, using this format:

 {'name' : 'your_field_name', 'getter': your_own_getter_method, 'setter': your_own_setter_method}

Simple Extension Example

A simple interface providing our field:

class ITestAdditionalDataSchema(Interface):

    test_field = schema.Bool(
        title=_(u'label_test_field_title', default=u'Test field'),
        description=_(u'label_test_field_description', default=u''),
        required=False,
        )

A simple adapter that adapts the context and request, returning our interface

class TestSchemaAdapter(object):
    def __init__(self, context, request):
        self.context = context
        self.request = request

    def getSchema(self):
        return ITestAdditionalDataSchema

Now we create an adapter that will declare a 'test_field' property, we didn't specify a getter and setter so this property will act as a memberdata property (be sure to declare it via memberdata_properties.xml):

class TestPropertiesAdapter(object):
    def __init__(self, context, request):
        self.context = context
        self.request = request

    def getProperties(self):
        return ['test_field']

Let's subscribe to the new interfaces to provide the personal-information form with our field:

    <adapter for="* zope.publisher.interfaces.browser.IBrowserRequest"
             provides="plone.app.users.adapters.interfaces.IExtendedUserDataSchema"
             factory=".adapters.TestSchemaAdapter"
             name="test.ExtraField"/>

    <adapter for="* zope.publisher.interfaces.browser.IBrowserRequest"
             provides="plone.app.users.adapters.interfaces.IExtendedUserDataPanel"
             factory=".adapters.TestPropertiesAdapter"
             name="test.ExtraProperties"/>

Custom Widgets

You may want to use a widget for your field. It can be done through the IExtendedUserDataWidgets interface. All you have to do is mapping a field id with a valid custom_widget factory like the following example.

class TestWidgetAdapter(object):
    def __init__(self, context, request):
        self.context = context
        self.request = request

    def getWidgets(self):
        return [{'field': 'test_field', 'factory' : CheckBoxWidget},
               ]
    <adapter for="* zope.publisher.interfaces.browser.IBrowserRequest"
             provides="plone.app.users.adapters.interfaces.IExtendedUserDataWidgets"
             factory=".adapters.TestWidgetAdapter"
             name="test.ExtraWidgets"/>

Your test_field will now use the widget factory you declared in the getWidgets method. You can also override the default Plone fields widgets using this method if you ever need to.

Deliverables

This proposal is yet implemented as a standalone product ( collective.customizablePersonalizeForm) compatible with Plone 4.0 and 4.1. It is full tested and documented.

The implementation can be merged in plone.app.users or be renamed plone.app.personalizeinfos.

Risks

The implementation doesn't allow to disable existing fields. If it's pushed into plone.app.users this fit this usecase.

Participants

  • Encolpe DEGOUTE (encolpe)
  • Julien STEGLE (jstegle)

Progress

All the code is available in the Collective Subversion repository:

 http://svn.plone.org/svn/collective/collective.customizablePersonalizeForm/trunk

Change History

comment:1 Changed 5 years ago by eleddy

Discussed on the FWT call today. There is a rewrite of p.a.users in z3c.form and this could make a the mess messier at the moment. We would like to see this stay as an add on for the time being. Thanks!

comment:2 Changed 5 years ago by jonstahl

Sounds like this concept could be a very sensible fit for (re)implementation/updating when the p.a.users z3c.form rewrite is finished. :-) Does the z3c.form rewrite have a PLIP yet? (I searched but couldn't spot it.) Should it?

comment:3 Changed 5 years ago by encolpe

z3c.form rewrite haven't a PLIP. We will stay tune to make our product compatible with the new plone.app.users.

comment:4 Changed 4 years ago by davisagli

Have you taken a look at how plone.app.discussion makes it possible for add-ons to add fields to the comment submission form? It is based on the extensible form infrastructure from plone.z3cform, which I think we should standardize on as our way of allowing forms to be customized (it is also how Dexterity behaviors work) rather than inventing more approaches. Of course, that requires that the user forms be rewritten to be based on z3c.form.

comment:5 Changed 4 years ago by jstegle

From what i saw in the rewritten  plone.app.users z3c.form branche, a  UserDataPanelAdapter is still used to manage getters and setters and a  PersonalPreferencesPanelAdapter to manage the form.

Based on the plone.app.discussion example, which uses plone.z3cform.fieldset.extensible.FormExtender to extends fields, I don't get how the getters and setters will be managed if we add fields this way. The problem is if that no getters and setters are set for additional fields you'll get an 'has no property your_property' error when you'll access the personal preferences form.

Furthermore this way doesn't solve the widgets management for the form fields. Though I agree that the usage of z3c.form should be used as a standard, the current way of working of the plone.app.users won't fit all the cases the proposal mananges.

comment:6 Changed 4 years ago by eleddy

  • Version set to 4.1

Sorry for dropping the followup on this plip - it got in the wrong column in our tracking. Long term, this isn't the ideal solution for this problem, and we couldn't come to a consensus on what actually is the right solution. While we acknowledge that this is a good intermediary solution, long term we think the core version needs "something else" so let's leave this as an add on for the time being. We know its a hard problem so thanks for tackling it.

comment:7 Changed 4 years ago by eleddy

  • Status changed from new to closed
  • Resolution set to wontfix

comment:8 Changed 4 years ago by eleddy

  • Status changed from closed to confirmed

for future reference, this was the p.a.users rewrite #12253

comment:9 Changed 4 years ago by eleddy

  • Status changed from confirmed to closed

comment:10 Changed 4 years ago by davisagli

  • Component changed from Infrastructure to General
Note: See TracTickets for help on using tickets.