Plone: custom edit form for dexterity content type with custom behavior on save - plone

I have:
a dexterity content type defined by schema (interface)
its default edit form
I'm trying to customize the edit form:
keeping current fields and validation as they are
adding new fields
The problem:
my values for the new fields I want not to be saved in my content type, but in other place.
So, how can I add new fields in an edit form of a dexterity content type without changing its schema and with custom behavior on save?
Simple example: Having a content type Car with fields name and year... I want on edit to edit not only the name and year, but also the Phone number of its producer (to be saved on save as value of Producer content type).
My WIP (solved to override the edit form, but no idea how to continue):
<browser:page
for=".IMyContentType"
name="edit"
class=".views.EditForm"
permission="cmf.ModifyPortalContent"
/>
from plone.dexterity.browser import edit
class EditForm(edit.DefaultEditForm):
pass

Here you can find the base DefaultEditForm code:
https://github.com/plone/plone.dexterity/blob/master/plone/dexterity/browser/edit.py#L20
To do additional actions you need to override the handleApply method (preserving the default action if you want to save field values in the actual content-type) and there provide your additional code.
If you want to add additional fields, you can override the fields attribute providing those additional fields or use the updateWidgets and updateFields methods to add those fields. Here you have some documentation:
https://docs.plone.org/external/plone.app.dexterity/docs/advanced/custom-add-and-edit-forms.html#edit-forms

Better use handleApply, but this is working, too:
<browser:page
for="my.package.IMyContentType"
name="edit"
class=".EditForm"
permission="zope2.View"
/>
<adapter
provides="plone.z3cform.fieldsets.interfaces.IFormExtender"
for="my.package.IMyContentType
zope.publisher.interfaces.browser.IDefaultBrowserLayer
.EditForm"
factory=".EditFormExtender"
/>
from my.package.config import MessageFactory as _
from plone.dexterity.browser import edit
from plone.dexterity.interfaces import IDexterityEditForm
from plone.z3cform import layout
from zope.interface import classImplements
from plone.z3cform.fieldsets.extensible import FormExtender
from z3c.form.field import Fields
from z3c.form import util
from zope import schema
class EditFormExtender(FormExtender):
def update(self):
if self.request.REQUEST_METHOD == 'GET':
# add fields
new_field1 = schema.TextLine(
__name__="New field 1",
title=_(u'label_new_field1', default=u'New field 1'),
description=_(u'help_new_field1',
default=u'Enter new field 1 value.'),
required=False,
)
new_field2 = schema.TextLine(
__name__="New field 2",
title=_(u'label_new_field2', default=u'New field 2'),
description=_(u'help_new_field2',
default=u'Enter new field 2 value.'),
required=False,
)
self.form.fields += Fields(new_field1, new_field2)
if self.request.REQUEST_METHOD == 'POST':
# save values
if 'form.buttons.save' in self.request.form:
# do your custom save here
prefix = 'form.widgets.'
field_names = ['new_field1', 'new_field2']
print "UPDATED:"
for field_name in field_names:
print self.request.form.get(prefix + field_name)
class EditForm(edit.DefaultEditForm):
""" Override MyContentType edit form
"""
EditView = layout.wrap_form(EditForm)
classImplements(EditView, IDexterityEditForm)

Related

Django CMS plugin nesting --- child doesn't show up in the "structure" interface

I'm trying to create a Django CMS custom plugin that can assemble other plugins.
As far as I can tell, Django CMS can do this using Plugin nesting, and I've followed the examples to create a simple test case.
My expectation is that when you go into the "Structure" tab for a record in the model that has a PlaceholderField that includes the parent plugin, when you add a parent plugin, the pop-up for that model should ALSO have some way to edit/create/add an instance of the child plugin. But it doesn't --- all I see are the fields for the parent plugin and NOTHING about the children (see screenshot below).
Or am I missing the point of Plugin nesting entirely?
models.py:
from django.db import models
from cms.models import CMSPlugin
from cms.models.fields import PlaceholderField
from djangocms_text_ckeditor.models import AbstractText
class CustomPlugin(CMSPlugin):
title = models.CharField('Title', max_length=200, null=False)
placeholder_items = PlaceholderField ('custom-content')
renderer = models.CharField('Renderer', max_length=50, null=True, blank=True,
help_text='This is just to show that a custom renderer CAN be done here!')
class ChildTextPlugin(AbstractText):
pass
cms_plugins.py:
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from django.utils.translation import ugettext as _
from .models import CustomPlugin, ChildTextPlugin
class CMSCustomPlugin(CMSPluginBase):
model = CustomPlugin
name = _('Custom Plugin')
render_template = 'custom/custom_plugin.html'
allow_children = True
def render(self, context, instance, placeholder):
context = super(CMSCustomPlugin, self).render(context, instance, placeholder)
return context
class CMSChildTextPlugin(CMSPluginBase):
model = ChildTextPlugin
name = _('Child Text Plugin')
render_template = 'custom/child_text_plugin.html'
parent_classes = ['CMSCustomPlugin',]
def render(self, context, instance, placeholder):
context = super(ChildTextPlugin, self).render(context, instance, placeholder)
return context
plugin_pool.register_plugin(CMSCustomPlugin)
plugin_pool.register_plugin(CMSChildTextPlugin)
... and the answer is "it was working all the time" --- the interface comes AFTER the screen I posted above is submitted --- the Custom Plugin entry will have a "+" icon, and it's THERE that the children are found.

Plone: Add additional registration fields to users (members)

I have two additional fields for my group objects (like described here).
Now I need (other) additional fields for my member objects as well (short strings). I have created them in portal_memberdata/manage_propertiesForm, but I still can't select them for registration form usage (##member-registration).
I need the two new fields for registration, at least one of them mandatory. How can I achieve this? Thank you!
Update:
I found plone.app.users.userdataschema and added my fields to the interface IUserDataSchema; furthermore, I monkeypatched plone.app.users.browser.personalpreferences.UserDataPanelAdapter. There still seems to be missing something (no change visible in ##member-registration).
My customization code looks like this:
from plone.app.users.userdataschema import IUserDataSchema
from zope import schema
from Products.CMFPlone import PloneMessageFactory as _
IUserDataSchema.custom1 = schema.ASCIILine(
title=_(u'label_custom1',
default=u'Custom1 member id'),
description=_(u'help_custom1_creation',
default=u'Custom1 membership is required; '
u'please enter your member id'),
required=True)
from plone.app.users.browser.personalpreferences import UserDataPanelAdapter
def set_custom1(self, value):
if value is None:
value = ''
return self.context.setMemberProperties({'custom1': value})
def get_custom1(self):
return self._getProperty('custom1')
UserDataPanelAdapter.custom1 = property(get_custom1, set_custom1)
It didn't work when I used the monkeypatched original interface class;
but it does work to monkeypatch the UserDataSchemaProvider to return a subclass:
from plone.app.users.userdataschema import IUserDataSchema
from plone.app.users.userdataschema import UserDataSchemaProvider
from zope import schema
from Products.CMFPlone import PloneMessageFactory as _
class IUserDataSchemaExtended(IUserDataSchema):
"""
Extends the userdata schema
by a mandatory field
"""
customField1 = schema.ASCIILine(
title=_(u'label_customField1',
default=u'CustomField1 member id'),
description=_(u'help_customField1_creation',
default=u'CustomField1 membership is required; '
u'please enter your member id'),
required=True)
def getExtendedSchema(self):
return IUserDataSchemaExtended
UserDataSchemaProvider.getSchema = getExtendedSchema
from plone.app.users.browser.personalpreferences import UserDataPanelAdapter
def set_customField1(self, value):
if value is None:
value = ''
return self.context.setMemberProperties({'customField1': value})
def get_customField1(self):
return self._getProperty('customField1')
UserDataPanelAdapter.customField1 = property(get_customField1, set_customField1)
Remarks:
It might be better to simply use customField1 for the translatable title instead of label_customField as the name the field is used when the registration page is quickedited
with Plone 5, it apparently is possible to configure additional userdata fields via XML

How to customize z3c.form widget for fields inside groups (fieldsets)

I have a Plone custom control panel registry and I'm trying to use a well know method to customize some of the widgets properties for zope.schema.Text and zope.schema.TextField.
I commonly customize the updateWidgets in that way:
def updateWidgets(self):
super(MyEditForm, self).updateWidgets()
self.widgets['my_text_area'].style = 'width: 100%'
self.widgets['my_text_area'].rows = 7
But now I'm working on a form where fields are splitted in two fieldsets:
class MySettingsEditForm(controlpanel.RegistryEditForm):
schema = IMySettingsSchema
groups = (Form1, Form2)
# fields = nothing
If I try to access self.widgets['my_text_area'] I get KeyError. It seems that as I did't defined the fields attribute I can't access directly widgets.
I found that I have groups so I can call something like self.groups[0].fields['my_text_area'] but still I find no way to access widgets for fields inside groups.
How can I customize widgets attributes when using groups?
I think what you need is playing with widget subform, see this code:
def fix_table_widget(self, name, widgets):
sub_widgets = widgets[name].widgets
for widget in sub_widgets:
new_label = widget.subform.widgets['weekday'].value
widget.subform.widgets['selected'].items[0]['label'] = new_label
widget.subform.widgets['weekday'].mode = 'hidden'
def schoolrequest_customizations(self):
''' Customizations for the schoolrequest base views
'''
for group in self.groups:
widgets = group.widgets
if 'table_bus_to_school' in widgets:
self.block_widget_table('table_bus_to_school', widgets)
self.fix_table_widget('table_bus_to_school', widgets)
if 'table_bus_to_home' in widgets:
self.block_widget_table('table_bus_to_home', widgets)
self.fix_table_widget('table_bus_to_home', widgets)
def update(self):
super(MyForm, self).update()
self.schoolrequest_customizations()

How to customize Schema.Datetime widget for just displaying year,month,day format in Dexterity enviroment?

I will like to customize Schema.Datetime field widget for local time format just displaying year-month-date.
Thanks!
You can use the Date field for this and it will automatically render a date-only widget:
from my.package import _
from plone.supermodel import model
from zope import schema
class IMyContentType(model.Schema):
...
start_date = schema.Date(
title=_(u'Start date'),
description=_(u'This widget will show only the date.'),
required=False,
)

How could I render just the form from an autoform?

I'm in the context of a BrowserView. I want to render a form inside the page.
class CommandeView(BrowserView):
...
def render_myform(self):
livraison_form = LivraisonForm(self.context, self.request)
livraison_form.update()
return livraison_form.render()
class ILivraisonForm(interface.Interface):
livraison = schema.Choice(title=_(u"Mode de livraison"),
vocabulary=livraison_vocabulary)
class LivraisonForm(AutoExtensibleForm, form.EditForm):
schema = ILivraisonForm
class LivraisonFormAdapter(object):
component.adapts(ICommande)
interface.implements(ILivraisonForm)
def __init__(self, context):
self.context = context
self.livraison = self.context.livraison
I would like render_myform to render only the form but it return a HTML Plone page.
Any tips are welcomed.
Haven't checked this in-depth, but the form is probably wrapped by the plone FormWrapper class.
Look for a .form attribute on the LivraisonForm instance:
def render_myform(self):
livraison_form = LivraisonForm(self.context, self.request).form
livraison_form.update()
return livraison_form.render()
It may be you need to add an additional interface to the request for this to work (the wrapper normally does this for you. I'm a little low on time right now, so you get this untested:
from plone.z3cform import z2
class CommandeView(BrowserView):
def render_myform(self):
z2.switch_on(self) # Apply layer interface, set up locale, add `getURL` method
livraison_form = LivraisonForm(self.context, self.request).form
livraison_form.update()
return livraison_form.render()
The Plone Knowledgebase uses this same setup to show a form in a viewlet.

Resources