Adding Memberdata Properties
This How-to applies to:
Any version.
This How-to is intended for:
Integrators, Customizers
The memberdata storage policy is the responsibility of the portal_memberdata tool/component. This tool is responsible for listing what attributes you are storing on the Member's (using a PropertyManager interface) and how they are stored (on the portal_memberdata as a BTree Object). So lets say for instance we want to store a boolean called, 'subscribe' and a 'favorite_food' string. This is the same way you would do it in CMF as well--except your HTML would be a tad simpler and you have to do the validation manually.
open up the /manage (Zope Management Interface, aka ZMI) of the Plone site in your browser.
select the portal_memberdata tool in the ZMI and click the Properties tab
- Name: subscribe Value: 1 Type: boolean
- Name: favorite_food Value: Type: string
now we must customize the personalize_form. This is not being automatically generated (yet) so you will end up having to type lots of boilerplate code.
go to plone_prefs/personalize_form and click Customize
now below visible_ids DIV you will want to paste the following code:
<div class="row"> <div class="label"> <span i18n:translate="label_display_names">Subscribe</span> <div id="subscribe_help" i18n:translate="help_subscribe" class="help" style="visibility:hidden"> Allows you to opt-in or opt-out of the newsletter subscription service. </div> </div> <div class="field" tal:define="subscribe python:request.get('subscribe', member.getProperty('subscribe')); tabindex tabindex/next;"> <input type="radio" class="noborder" name="subscribe" value="on" id="cb_subscribe" checked="checked" tabindex="" onfocus="formtooltip('subscribe_help',1)" onblur="formtooltip('subscribe_help',0)" tal:attributes="tabindex tabindex;" tal:condition="subscribe" /> <input type="radio" class="noborder" name="subscribe" value="on" id="cb_subscribe" tabindex="" onfocus="formtooltip('subscribe_help',1)" onblur="formtooltip('subscribe_help',0)" tal:attributes="tabindex tabindex;" tal:condition="not: subscribe" /> <label for="cb_subscribe" i18n:translate="yes">Yes</label> <br /> <input type="radio" class="noborder" name="subscribe" value="" id="cb_unsubscribe" tabindex="" onfocus="formtooltip('subscribe_help',1)" onblur="formtooltip('subscribe_help',0)" tal:attributes="tabindex tabindex;" tal:condition="subscribe" /> <input type="radio" class="noborder" name="subscribe" value="" id="cb_unsubscribe" checked="checked" tabindex="" onfocus="formtooltip('subscribe_help',1)" onblur="formtooltip('uubscribe_help',0)" tal:attributes="tabindex tabindex;" tal:condition="not: subscribe" /> <label for="cb_unsubscribe" i18n:translate="no">No</label> </div> </div> <div class="row" tal:define="error_favorite_food errors/favorite_food | nothing; favorite_food python:request.get('favorite_food', member.getProperty('favorite_food'));"> <div class="label"> <span i18n:translate="label_favorite_food">Favorite Food</span> <div id="favorite_food_help" i18n:translate="help_favorite_food" class="help" style="visibility:hidden"> You can change your favorite food here. </div> </div> <div class="field" tal:attributes="class python:test(error_favorite_food, 'field error', 'field')"> <div tal:condition="error_favorite_food" tal:replace="structure string:$error_favorite_food <br />" /> <input type="text" name="favorite_food" size="25" tabindex="" value="member.favorite_food html_quote" onfocus="formtooltip('full_name_help',1)" onblur="formtooltip('full_name_help',0)" tal:attributes="value favorite_food; tabindex tabindex/next;" /> </div> </div>
if you wanted to make favorite_food a required field you would edit the validate_personalize script in plone_scripts/form_scripts/validate_personalize and you could add validation rules to it. You will notice that the validation is wired up to the form for this field.
Note: This can also be done with CMFMember, which adds additional capabilities, such as workflow, to members, but at the cost of higher complexity.
Select values
The solution described above is just rolling our own form changes for new fields; if you want to have a list of groups coming from somewhere for a drop-down menu, you can just query the groups_tool for that (see the API for the groups tool; you can ask it for the list of groups).
Keep in mind that the overall goal here was to show you how to add new _memberdata_properties_; if you want to assign a user to group(s), you could gather that info on the membership preference form, but you wouldn't add it to the portal_memberdata tool; instead, you'd want to use the API (also findable on the groups_tool) for add-a-member-to-a-group.
Location of validate_personalize script
plone_scripts/form_scripts/validate_personalize
is
portal_skins/plone_form_scripts/validate_personalize
in Plone 2.1.3 and possibly others
This recipe was written in Plone 1.0 days
Also, the preferred CSS class names for forms changed since this was written; the HTML should be updated to use the nicer-style, newer stuff. Just take a look at hthe existing personalize_form and you can see that the div blocks for each field on an HTML form are slightly different.
how does custom fields relate to LDAP users?
but my goal goes further: I want to bind the custom field I created to an LDAP attribute. I am using LDAPUserFolder to add Plone users from an LDAP, and this LDAP has an "e-mail" attribute for all users. I want to read the value from the LDAP and put it in the "e-mail" field of Plone, and vice-versa: I want to overwrite the LDAP attribute whenever the user changes his/her Plone e-mail!
is it possible to do such a thing?
It is possible; it's not directly related
However, adding _new_ memberdata fields doesn't change this; if you're using LDAP for your memberdata storage, your new fields will be stored as well. This recipe just demonstrates how to add new fields to memberdata and put them on the form/validation/script.
Plone 2.5 corrections
<div class="field"
tal:define="subscribe python:request.get('subscribe', member.getProperty('subscribe'));">
<label for="subscribe" i18n:translate="label_subscribe">Subscribe</label>
<br/>
<input type="radio"
class="noborder"
name="subscribe"
value="on"
id="cb_subscribe"
checked="checked"
tabindex=""
onfocus="formtooltip('subscribe_help',1)"
onblur="formtooltip('subscribe_help',0)"
tal:attributes="tabindex tabindex;"
tal:condition="subscribe"
/>
<input type="radio"
class="noborder"
name="subscribe"
value="on"
id="cb_subscribe"
tabindex=""
onfocus="formtooltip('subscribe_help',1)"
onblur="formtooltip('subscribe_help',0)"
tal:attributes="tabindex tabindex;"
tal:condition="not: subscribe"
/>
<label for="cb_subscribe" i18n:translate="yes">Yes</label>
<input type="radio"
class="noborder"
name="subscribe"
value=""
id="cb_unsubscribe"
tabindex=""
onfocus="formtooltip('subscribe_help',1)"
onblur="formtooltip('subscribe_help',0)"
tal:attributes="tabindex tabindex;"
tal:condition="subscribe"
/>
<input type="radio"
class="noborder"
name="subscribe"
value=""
id="cb_unsubscribe"
checked="checked"
tabindex=""
onfocus="formtooltip('subscribe_help',1)"
onblur="formtooltip('uubscribe_help',0)"
tal:attributes="tabindex tabindex;"
tal:condition="not: subscribe"
/>
<label for="cb_unsubscribe" i18n:translate="no">No</label>
</div>
<div class="field"
tal:define="error_favorite_food errors/favorite_food | nothing;
favorite_food python:request.get('favorite_food', member.getProperty('favorite_food'));">
<label for="fullname" i18n:translate="label_favorite_food">Favorite Food</label>
<div class="formHelp" i18n:translate="help_favorite_food">
You can change your favorite food here.
</div>
<input type="text"
name="favorite_food"
size="25"
tabindex=""
value="member.favorite_food html_quote"
tal:attributes="value favorite_food;" />
<div class="field" tal:attributes="class python:test(error_favorite_food, 'field error', 'field')">
<div tal:condition="error_favorite_food"
tal:replace="structure string:$error_favorite_food <br />" />
</div>
</div>
Plone 2.5 corrections
Thanks for the code and instructions, next step....
Displaying the new properties
You'll see the code that displays the user's "card". Just add lines to it to look up the member from the userid, then access the properties you've added (in this example, phone_day)::
<div class="card" tal:define="userid result/getMemberId;
home python:container.portal_membership.getHomeUrl(userid, verifyPermission=1);
portrait python: here.portal_membership.getPersonalPortrait(userid);
member python:mtool.getMemberById(userid);
tFullname python:member.getProperty('fullname');
tLocation python:member.getProperty('location');
tPhone python:member.getProperty('phone_day');">
Then, just display the properties as part of the card::
<a href="#" tal:attributes="href home">
<img src="defaultUser.gif"
alt=""
border="0"
width="75"
height="100"
tal:attributes="src portrait/absolute_url" />
<br />
<span tal:content="tFullname">John Doe</span>
</a>
<br />
<span tal:content="tLocation">Boston, MA</span><br />
<span tal:content="tPhone">978/555-1212</span>
</div>
Setting properties when a user joins
to add a boolean property
I had to customize portal_skins/plone_form_scripts/personalize to make the personalize_form work with a custom boolean member property. Assuming the property name is 'has_apple'
* Add to the Parameter List: has_apple=None
* Insert the following code:
if has_apple is None and REQUEST is not None:
has_apple=0
else:
has_apple=1
REQUEST.set('has_apple', has_apple)
Without this, personalize_form did not update the member data and the checkbox appeared always on (= the default property value).
one more line..
member.setProperties(listed=listed, ext_editor=ext_editor, visible_ids=visible_ids, has_apple=has_apple)
Select/Multi-select value
I notice you don't cover the select/multi-select type here, but I wonder if you would know - what I would like to do is to have a field that retrieves a list of groups for the user to select from. Is this possible using this mechanism of adding portal_memberdata properties or do I need to get more complicated (membrane, remember, CMFMember subclassing or whatever?)