Plone: Add additional registration fields to users (members) - plone

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

Related

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

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)

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.

Extend Plone-Controlpanel Form

Is it possible to extend the Controlpanel-View with each addon?
For Example
ca.db.core -> Makes basic fieldset/tab for DB Connection Settings
ca.db.person -> If installed, Adds to the "core" settings a new fieldset/tab for Person specific fields/settings
ca.db.schema -> If installed, also adds an new fieldset/tab for schema.org Fields
Yes it's possible, I once discussed this problem with a guy, who wrote the bda.plone.shop addon.
They faced the same problem, and solved it by using a ContextProxy object, which puts the different schema definitions together in one proxy object.
Using a proxy is IMHO a hack, but I don't know a better solution.
The proxy, tries to get/set a attribute from a list of schemas.
Be aware, you need to handle conflicting names, means if you have the same field name in more than one schema.
class ContextProxy(object):
def __init__(self, interfaces):
self.__interfaces = interfaces
alsoProvides(self, *interfaces)
def __setattr__(self, name, value):
if name.startswith('__') or name.startswith('_ContextProxy__'):
return object.__setattr__(self, name, value)
registry = getUtility(IRegistry)
for interface in self.__interfaces:
proxy = registry.forInterface(interface)
try:
getattr(proxy, name)
except AttributeError:
pass
else:
return setattr(proxy, name, value)
raise AttributeError(name)
def __getattr__(self, name):
if name.startswith('__') or name.startswith('_ContextProxy__'):
return object.__getattr__(self, name)
registry = getUtility(IRegistry)
for interface in self.__interfaces:
proxy = registry.forInterface(interface)
try:
return getattr(proxy, name)
except AttributeError:
pass
raise AttributeError(name)
Now you need to use the proxy in your ControlPanel form.
I assume you're using the RegistryEditForm from plone.registry:
class SettingsEditForm(controlpanel.RegistryEditForm):
schema = ISettings
label = _(u"Settings")
description = _(u"")
# IMPORTANT Note 1 - This is where you hook in your proxy
def getContent(self):
interfaces = [self.schema] # Base schema from ca.db.core
interfaces.extend(self.additionalSchemata) # List of additional schemas
return ContextProxy(interfaces)
# IMPORTANT Note 2 - You may get the additional schemas dynamically to extend the Settings Form. For example by name (startswith...)
# In this case they have a separate interface, which marks the relevant interfaces.
#property
def additionalSchemata(self):
registry = getUtility(IRegistry)
interface_names = set(record.interfaceName for record
in registry.records.values())
for name in interface_names:
if not name:
continue
interface = None
try:
interface = resolve(name)
except ImportError:
# In case of leftover registry entries of uninstalled Products
continue
if ISettingsProvider.providedBy(interface):
yield interface
...
You can find the full code here

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.

relating models to one another using generic views

I'm new to Django and programming in general. I'm trying to make a simple site that allows players of a sport sign up for leagues that have been created by the admin. In my models.py, I created two models:
from django.db import models
from django.forms import ModelForm
class League(models.Model):
league_name = models.CharField(max_length=100)
pub_date = models.DateTimeField('date published')
class Info(models.Model):
league = models.ManyToManyField(League)
name = models.CharField(max_length=50)
phone = models.IntegerField()
email = models.EmailField()
def __unicode__(self):
return self.info
class InfoForm (ModelForm):
class Meta:
model = Info
exclude = ('league')
From what I've read, I can probably use the Create/Update/Delete generic views to display a form for the user to sign up for the league. So with my app, I want the user to come to a simple homepage that lists the leagues, be able to click on the league and enter their info to sign up. Here's what my urlconf looks like:
from django.conf.urls.defaults import *
from mysite.player_info.models import League, Info, InfoForm
info_dict = {
'queryset': League.objects.all(),
}
InfoForm = {'form_class' : InfoForm}
urlpatterns = patterns('',
(r'^$', 'django.views.generic.list_detail.object_list', info_dict),
(r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
url(r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='player_info/results.html'), 'league_results'),
(r'^(?P<object_id>\d+)/info/create/$', 'django.views.generic.create_update.create_object', InfoForm),
)
Here's my problem: When I click on a league to sign up for on the homepage with my current setup, I get this error: TypeError at /league/1/info/create.... create_object() got an unexpected keyword argument 'object_id'. What am I doing wrong?
The issue isn't with your models, but rather with the function your "create" URL calls -- the line that calls django.views.generic.create_update.create_object() in urls.py. create_object() doesn't take an object_id argument, but you specified one in your url (r'^(?P<object_id>\d+)/info/create/$'). This makes sense -- you're creating an object, so you don't know its ID yet. create_object() only takes a form_class or model argument, as noted in the docs.
I'm guessing you're trying to create an Info object that is attached to a League object, and in that URL, <object_id> is the ID number of the League object; in which case, you shouldn't name that ID number, and instead should just use r"^\d+/info/create/$" as the URL. I'm not sure how you'll grab the league ID number using Django's create_object() function, though. You might have to write your own view handler. You may be able to use a custom ModelForm and pass it in with the form_class parameter, but I'm not sure.

Resources