Enable multi-value field support for some attributes on the edit pages

Better error reporting in the GUI
Include a document describing how multi-valued fields work
This commit is contained in:
Rob Crittenden
2007-11-08 22:12:42 -05:00
parent 39dcd194ca
commit e9dfbfa773
11 changed files with 473 additions and 75 deletions

View File

@@ -28,6 +28,11 @@ class IPAError(exceptions.Exception):
error."""
self.code = code
self.message = message
# Fill this in as an empty LDAP error message so we don't have a lot
# of "if e.detail ..." everywhere
if detail is None:
detail = []
detail.append({'desc':'','info':''})
self.detail = detail
def __str__(self):

View File

@@ -0,0 +1,27 @@
The way multi-valued fields work is this:
- A new widget is added to the form. I name it as the attribute + s.
For example, I use cns for the cn attribute.
- If you need a new validator use a ForEach() so that each value is
checked.
- This attribute is populated from the incoming attribute from the
user or group record. The widget can support multiple fields at once
but I'm using it for just one field. In fact, I don't know if it
will work with more the way I'm using it.
- In the GUI an operator can add/remove values to each multi-valued field.
- Naming is very important in the widget. TurboGears automatically
re-assembles the data into a list of dict entries if you name things
properly. For example, the cns (multiple CN entries) looks like:
cns-0.cn=Rob+Crittenden&cns-1.cn=Robert+Crittenden&cns-2.cn=rcrit
- This gets converted to:
[{'cn': u'Rob Crittenden'}, {'cn': u'Robert Crittenden'}, {'cn': u'rcrit'}]
- I take this list of dicts and pull out each value and append it to a new
list that represents the original multi-valued field
- Then the list/dict version is removed (in this case, kw['cns']).
When adding a new field you have to update:
1. The form to add the new ExpandingForm() field and perhaps a validator
2. The edit template to add the boilerplate to display the field
3. The show template to be able to display all the fields separately
4. The new template if you want to be able to enter these on new entries
5. The subcontroller so you can do the input and output conversions

View File

@@ -1,8 +1,10 @@
import turbogears
from turbogears import validators, widgets
from tg_expanding_form_widget.tg_expanding_form_widget import ExpandingForm
class GroupFields():
cn = widgets.TextField(name="cn", label="Name")
cns = ExpandingForm(name="cns", label="Common Names", fields=[cn])
gidnumber = widgets.TextField(name="gidnumber", label="GID")
description = widgets.TextField(name="description", label="Description")
@@ -37,6 +39,7 @@ class GroupNewForm(widgets.Form):
class GroupEditValidator(validators.Schema):
cn = validators.ForEach(validators.String(not_empty=True))
gidnumber = validators.Int(not_empty=False)
description = validators.String(not_empty=False)

View File

@@ -1,10 +1,12 @@
import turbogears
from turbogears import validators, widgets
from tg_expanding_form_widget.tg_expanding_form_widget import ExpandingForm
class UserFields():
givenname = widgets.TextField(name="givenname", label="Given Name")
sn = widgets.TextField(name="sn", label="Family Name")
cn = widgets.TextField(name="cn", label="Common Names")
cns = ExpandingForm(name="cns", label="Common Names", fields=[cn])
title = widgets.TextField(name="title", label="Title")
displayname = widgets.TextField(name="displayname", label="Display Name")
initials = widgets.TextField(name="initials", label="Initials")
@@ -21,11 +23,16 @@ class UserFields():
mail = widgets.TextField(name="mail", label="E-mail Address")
telephonenumber = widgets.TextField(name="telephonenumber", label="Work Number")
telephonenumbers = ExpandingForm(name="telephonenumbers", label="Work Numbers", fields=[telephonenumber])
facsimiletelephonenumber = widgets.TextField(name="facsimiletelephonenumber",
label="Fax Number")
facsimiletelephonenumbers = ExpandingForm(name="facsimiletelephonenumbers", label="Fax Numbers", fields=[facsimiletelephonenumber])
mobile = widgets.TextField(name="mobile", label="Cell Number")
mobiles = ExpandingForm(name="mobiles", label="Cell Numbers", fields=[mobile])
pager = widgets.TextField(name="pager", label="Pager Number")
pagers = ExpandingForm(name="pagers", label="Pager Numbers", fields=[pager])
homephone = widgets.TextField(name="homephone", label="Home Number")
homephones = ExpandingForm(name="homephones", label="Home Numbers", fields=[homephone])
street = widgets.TextField(name="street", label="Street Address")
l = widgets.TextField(name="l", label="City")
@@ -102,6 +109,7 @@ class UserEditValidator(validators.Schema):
userpassword_confirm = validators.String(not_empty=False)
givenname = validators.String(not_empty=True)
sn = validators.String(not_empty=True)
cn = validators.ForEach(validators.String(not_empty=True))
mail = validators.Email(not_empty=True)
uidnumber = validators.Int(not_empty=False)
gidnumber = validators.Int(not_empty=False)

View File

@@ -90,7 +90,7 @@ class GroupController(IPAController):
# on any error, we redirect to the _edit_ group page.
# this code does data setup, similar to groupedit()
#
group = client.get_entry_by_cn(kw['cn'], group_fields)
group = client.get_entry_by_cn(kw['cn'][0], group_fields)
group_dict = group.toDict()
member_dicts = []
@@ -180,6 +180,14 @@ class GroupController(IPAController):
group_dict = group.toDict()
# Load potential multi-valued fields
if isinstance(group_dict['cn'], str):
group_dict['cn'] = [group_dict['cn']]
cns = []
for cn in group_dict['cn']:
cns.append(dict(cn=cn))
group_dict['cns'] = cns
#
# convert members to users, for easier manipulation on the page
#
@@ -210,14 +218,19 @@ class GroupController(IPAController):
self.restrict_post()
client = self.get_ipaclient()
# Fix incoming multi-valued form fields
kw['cn'] = []
for i in range(len(kw['cns'])):
kw['cn'].append(kw['cns'][i]['cn'])
del(kw['cns'])
if kw.get('submit') == 'Cancel Edit':
turbogears.flash("Edit group cancelled")
raise turbogears.redirect('/group/show', cn=kw.get('cn'))
raise turbogears.redirect('/group/show', cn=kw.get('cn')[0])
# Decode the member data, in case we need to round trip
member_dicts = loads(b64decode(kw.get('member_data')))
tg_errors, kw = self.groupupdatevalidate(**kw)
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
@@ -233,6 +246,9 @@ class GroupController(IPAController):
try:
orig_group_dict = loads(b64decode(kw.get('group_orig')))
# remove multi-valued form fields
del(orig_group_dict['cns'])
new_group = ipa.group.Group(orig_group_dict)
if new_group.description != kw.get('description'):
group_modified = True
@@ -243,6 +259,14 @@ class GroupController(IPAController):
group_modified = True
new_group.setValue('gidnumber', new_gid)
# Did any cn entries change?
oldcn = new_group.getValues('cn')
if isinstance(oldcn, str):
oldcn = [oldcn]
if oldcn != kw['cn']:
group_modified = True
new_group.setValue('cn', kw['cn'])
if group_modified:
rv = client.update_group(new_group)
#
@@ -252,7 +276,7 @@ class GroupController(IPAController):
#
kw['group_orig'] = b64encode(dumps(new_group.toDict()))
except ipaerror.IPAError, e:
turbogears.flash("Group update failed: " + str(e))
turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')
@@ -268,8 +292,9 @@ class GroupController(IPAController):
failed_adds = client.add_members_to_group(
utf8_encode_values(dnadds), new_group.dn)
kw['dnadd'] = failed_adds
group_modified = True
except ipaerror.IPAError, e:
turbogears.flash("Group update failed: " + str(e))
turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')
@@ -285,8 +310,9 @@ class GroupController(IPAController):
failed_dels = client.remove_members_from_group(
utf8_encode_values(dndels), new_group.dn)
kw['dndel'] = failed_dels
group_modified = True
except ipaerror.IPAError, e:
turbogears.flash("Group update failed: " + str(e))
turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')
@@ -308,8 +334,11 @@ class GroupController(IPAController):
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')
turbogears.flash("%s updated!" % kw['cn'])
raise turbogears.redirect('/group/show', cn=kw['cn'])
if group_modified == True:
turbogears.flash("%s updated!" % kw['cn'][0])
else:
turbogears.flash("No modifications requested.")
raise turbogears.redirect('/group/show', cn=kw['cn'][0])
@expose("ipagui.templates.grouplist")
@@ -330,7 +359,7 @@ class GroupController(IPAController):
turbogears.flash("These results are truncated.<br />" +
"Please refine your search and try again.")
except ipaerror.IPAError, e:
turbogears.flash("Find groups failed: " + str(e))
turbogears.flash("Find groups failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect("/group/list")
return dict(groups=groups, criteria=criteria,
@@ -358,7 +387,7 @@ class GroupController(IPAController):
return dict(group=group_dict, fields=ipagui.forms.group.GroupFields(),
members = member_dicts)
except ipaerror.IPAError, e:
turbogears.flash("Group show failed: " + str(e))
turbogears.flash("Group show failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect("/")
@expose()

View File

@@ -61,6 +61,35 @@ class UserController(IPAController):
user_new_form.validator.add_field(s['field'], validator)
user_edit_form.validator.add_field(s['field'], validator)
def setup_mv_fields(self, field, fieldname):
"""Given a field (must be a list) and field name, convert that
field into a list of dictionaries of the form:
[ { fieldname : v1}, { fieldname : v2 }, .. ]
This is how we pre-fill values for multi-valued fields.
"""
mvlist = []
if field is not None:
for v in field:
mvlist.append({ fieldname : v } )
else:
# We need to return an empty value so something can be
# displayed on the edit page. Otherwise only an Add link
# will show, not an empty field.
mvlist.append({ fieldname : '' } )
return mvlist
def fix_incoming_fields(self, fields, fieldname, multifieldname):
"""This is called by the update() function. It takes the incoming
list of dictionaries and converts it into back into the original
field, then removes the multiple field.
"""
fields[fieldname] = []
for i in range(len(fields[multifieldname])):
fields[fieldname].append(fields[multifieldname][i][fieldname])
del(fields[multifieldname])
return fields
@expose()
def index(self):
@@ -150,7 +179,7 @@ class UserController(IPAController):
return dict(form=user_new_form, user=kw,
tg_template='ipagui.templates.usernew')
except ipaerror.IPAError, e:
turbogears.flash("User add failed: " + str(e))
turbogears.flash("User add failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=user_new_form, user=kw,
tg_template='ipagui.templates.usernew')
@@ -259,6 +288,32 @@ class UserController(IPAController):
turbogears.flash("User edit failed: No uid or principal provided")
raise turbogears.redirect('/')
user_dict = user.toDict()
# Load potential multi-valued fields
if isinstance(user_dict['cn'], str):
user_dict['cn'] = [user_dict['cn']]
user_dict['cns'] = self.setup_mv_fields(user_dict['cn'], 'cn')
if isinstance(user_dict.get('telephonenumber',''), str):
user_dict['telephonenumber'] = [user_dict.get('telephonenumber'),'']
user_dict['telephonenumbers'] = self.setup_mv_fields(user_dict.get('telephonenumber'), 'telephonenumber')
if isinstance(user_dict.get('facsimiletelephonenumber',''), str):
user_dict['facsimiletelephonenumber'] = [user_dict.get('facsimiletelephonenumber'),'']
user_dict['facsimiletelephonenumbers'] = self.setup_mv_fields(user_dict.get('facsimiletelephonenumber'), 'facsimiletelephonenumber')
if isinstance(user_dict.get('mobile',''), str):
user_dict['mobile'] = [user_dict.get('mobile'),'']
user_dict['mobiles'] = self.setup_mv_fields(user_dict.get('mobile'), 'mobile')
if isinstance(user_dict.get('pager',''), str):
user_dict['pager'] = [user_dict.get('pager'),'']
user_dict['pagers'] = self.setup_mv_fields(user_dict.get('pager'), 'pager')
if isinstance(user_dict.get('homephone',''), str):
user_dict['homephone'] = [user_dict.get('homephone'),'']
user_dict['homephones'] = self.setup_mv_fields(user_dict.get('homephone'), 'homephone')
# Edit shouldn't fill in the password field.
if user_dict.has_key('userpassword'):
del(user_dict['userpassword'])
@@ -300,7 +355,7 @@ class UserController(IPAController):
except ipaerror.IPAError, e:
if uid is None:
uid = principal
turbogears.flash("User edit failed: " + str(e))
turbogears.flash("User edit failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect('/user/show', uid=uid)
@expose()
@@ -314,6 +369,14 @@ class UserController(IPAController):
turbogears.flash("Edit user cancelled")
raise turbogears.redirect('/user/show', uid=kw.get('uid'))
# Fix incoming multi-valued fields we created for the form
kw = self.fix_incoming_fields(kw, 'cn', 'cns')
kw = self.fix_incoming_fields(kw, 'telephonenumber', 'telephonenumbers')
kw = self.fix_incoming_fields(kw, 'facsimiletelephonenumber', 'facsimiletelephonenumbers')
kw = self.fix_incoming_fields(kw, 'mobile', 'mobiles')
kw = self.fix_incoming_fields(kw, 'pager', 'pagers')
kw = self.fix_incoming_fields(kw, 'homephone', 'homephones')
# Decode the group data, in case we need to round trip
user_groups_dicts = loads(b64decode(kw.get('user_groups_data')))
@@ -334,6 +397,14 @@ class UserController(IPAController):
try:
orig_user_dict = loads(b64decode(kw.get('user_orig')))
# remove multi-valued fields we created for the form
del(orig_user_dict['cns'])
del(orig_user_dict['telephonenumbers'])
del(orig_user_dict['facsimiletelephonenumbers'])
del(orig_user_dict['mobiles'])
del(orig_user_dict['pagers'])
del(orig_user_dict['homephones'])
new_user = ipa.user.User(orig_user_dict)
new_user.setValue('title', kw.get('title'))
new_user.setValue('givenname', kw.get('givenname'))
@@ -400,7 +471,7 @@ class UserController(IPAController):
# too much work to figure out unless someone really screams
pass
except ipaerror.IPAError, e:
turbogears.flash("User update failed: " + str(e))
turbogears.flash("User update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=user_edit_form, user=kw,
user_groups=user_groups_dicts,
tg_template='ipagui.templates.useredit')
@@ -412,7 +483,7 @@ class UserController(IPAController):
if password_change:
rv = client.modifyPassword(kw['krbprincipalname'], "", kw.get('userpassword'))
except ipaerror.IPAError, e:
turbogears.flash("User password change failed: " + str(e))
turbogears.flash("User password change failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=user_edit_form, user=kw,
user_groups=user_groups_dicts,
tg_template='ipagui.templates.useredit')
@@ -481,7 +552,7 @@ class UserController(IPAController):
turbogears.flash("These results are truncated.<br />" +
"Please refine your search and try again.")
except ipaerror.IPAError, e:
turbogears.flash("User list failed: " + str(e))
turbogears.flash("User list failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect("/user/list")
return dict(users=users, uid=uid, fields=ipagui.forms.user.UserFields())
@@ -523,7 +594,7 @@ class UserController(IPAController):
user_groups=user_groups, user_reports=user_reports,
user_manager=user_manager, user_secretary=user_secretary)
except ipaerror.IPAError, e:
turbogears.flash("User show failed: " + str(e))
turbogears.flash("User show failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect("/")
@expose()
@@ -539,7 +610,7 @@ class UserController(IPAController):
turbogears.flash("user deleted")
raise turbogears.redirect('/user/list')
except (SyntaxError, ipaerror.IPAError), e:
turbogears.flash("User deletion failed: " + str(e))
turbogears.flash("User deletion failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
raise turbogears.redirect('/user/list')
@validate(form=user_new_form)
@@ -661,7 +732,7 @@ class UserController(IPAController):
users_counter = users[0]
users = users[1:]
except ipaerror.IPAError, e:
turbogears.flash("search failed: " + str(e))
turbogears.flash("search failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(users=users, criteria=criteria,
which_select=kw.get('which_select'),

View File

@@ -25,6 +25,8 @@ from ipagui.helpers import ipahelper
<script type="text/javascript" charset="utf-8"
src="${tg.url('/static/javascript/dynamicedit.js')}"></script>
<script type="text/javascript" charset="utf-8"
src="${tg.url('/tg_widgets/tg_expanding_form_widget/javascript/expanding_form.js')}"></script>
<?python searchurl = tg.url('/group/edit_search') ?>
@@ -66,15 +68,35 @@ from ipagui.helpers import ipahelper
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
<th>
<label class="fieldlabel" for="${group_fields.cn.field_id}"
<label class="fieldlabel" for="${group_fields.cns.field_id}"
py:content="group_fields.cn.label" />:
</th>
<td>
<!-- <span py:replace="group_fields.cn.display(value_for(group_fields.cn))" />
<span py:if="tg.errors.get('cn')" class="fielderror"
py:content="tg.errors.get('cn')" /> -->
${value_for(group_fields.cn)}
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${group_fields.cns.field_id}">
<tbody>
<?python repetition = 0
cn_index = 0
cn_error = tg.errors.get('cn')
?>
<tr py:for="cn in value_for(group_fields.cn)"
id="${group_fields.cns.field_id}_${repetition}"
class="${group_fields.cns.field_class}">
<td py:for="field in group_fields.cns.fields">
<span><input class="textfield" type="text" id="${group_fields.cns.field_id}_${repetition}_cn" name="cns-${repetition}.cn" value="${cn}"/></span>
<span py:if="cn_error and cn_error[cn_index]" class="fielderror"
py:content="tg.errors.get('cn')" />
</td>
<?python cn_index = cn_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${group_fields.cns.field_id}_${repetition}')">Remove (-)</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${group_fields.cns.field_id}_doclink" href="javascript:ExpandingForm.addItem('${group_fields.cns.field_id}');">Add ( + )</a>
</td>
</tr>

View File

@@ -7,7 +7,7 @@
</head>
<body>
<?python
edit_url = tg.url('/group/edit', cn=group.get('cn'))
edit_url = tg.url('/group/edit', cn=group.get('cn')[0])
?>
<div id="details">
<h1>View Group</h1>
@@ -22,7 +22,21 @@ edit_url = tg.url('/group/edit', cn=group.get('cn'))
<th>
<label class="fieldlabel" py:content="fields.cn.label" />:
</th>
<td>${group.get("cn")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = group.get("cn")
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
@@ -51,7 +65,7 @@ edit_url = tg.url('/group/edit', cn=group.get('cn'))
member_type = "user"
view_url = tg.url('/user/show', uid=member_uid)
else:
member_cn = "%s" % member.get('cn')
member_cn = "%s" % member.get('cn')[0]
member_desc = "[group]"
member_type = "group"
view_url = tg.url('/group/show', cn=member_cn)

View File

@@ -26,6 +26,8 @@ from ipagui.helpers import ipahelper
src="${tg.url('/static/javascript/dynamicedit.js')}"></script>
<script type="text/javascript" charset="utf-8"
src="${tg.url('/static/javascript/dynamicselect.js')}"></script>
<script type="text/javascript" charset="utf-8"
src="${tg.url('/tg_widgets/tg_expanding_form_widget/javascript/expanding_form.js')}"></script>
<?python
searchurl = tg.url('/user/edit_search')
@@ -141,14 +143,35 @@ from ipagui.helpers import ipahelper
<tr>
<th>
<label class="fieldlabel" for="${user_fields.cn.field_id}"
py:content="user_fields.cn.label" />:
<label class="fieldlabel" for="${user_fields.cns.field_id}"
py:content="user_fields.cns.label" />:
</th>
<td>
<span py:replace="user_fields.cn.display(value_for(user_fields.cn))" />
<span py:if="tg.errors.get('cn')" class="fielderror"
py:content="tg.errors.get('cn')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.cns.field_id}">
<tbody>
<?python repetition = 0
cn_index = 0
cn_error = tg.errors.get('cn')
?>
<tr py:for="cn in value_for(user_fields.cn)"
id="${user_fields.cns.field_id}_${repetition}"
class="${user_fields.cns.field_class}">
<td py:for="field in user_fields.cns.fields">
<span><input class="textfield" type="text" id="${user_fields.cns.field_id}_${repetition}_cn" name="cns-${repetition}.cn" value="${cn}"/></span>
<span py:if="cn_error and cn_error[cn_index]" class="fielderror"
py:content="tg.errors.get('cn')" />
</td>
<?python cn_index = cn_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.cns.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.cns.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.cns.field_id}');">Add Common Name</a>
</td>
</tr>
@@ -364,61 +387,170 @@ from ipagui.helpers import ipahelper
<tr>
<th>
<label class="fieldlabel" for="${user_fields.telephonenumber.field_id}"
py:content="user_fields.telephonenumber.label" />:
<label class="fieldlabel" for="${user_fields.telephonenumbers.field_id}"
py:content="user_fields.telephonenumbers.label" />:
</th>
<td>
<span py:replace="user_fields.telephonenumber.display(value_for(user_fields.telephonenumber))" />
<span py:if="tg.errors.get('telephonenumber')" class="fielderror"
py:content="tg.errors.get('telephonenumber')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.telephonenumbers.field_id}">
<tbody>
<?python repetition = 0
tele_index = 0
tele_error = tg.errors.get('telephonenumber')
?>
<tr py:for="tele in value_for(user_fields.telephonenumber)"
id="${user_fields.telephonenumbers.field_id}_${repetition}"
class="${user_fields.telephonenumbers.field_class}">
<td py:for="field in user_fields.telephonenumbers.fields">
<span><input class="textfield" type="text" id="${user_fields.telephonenumbers.field_id}_${repetition}_telephonenumber" name="telephonenumbers-${repetition}.telephonenumber" value="${tele}"/></span>
<span py:if="tele_error and tele_error[tele_index]" class="fielderror"
py:content="tg.errors.get('telephonenumber')" />
</td>
<?python tele_index = tele_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.telephonenumbers.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.telephonenumbers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.telephonenumbers.field_id}');">Add Work Number</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.facsimiletelephonenumbers.field_id}"
py:content="user_fields.facsimiletelephonenumbers.label" />:
</th>
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.facsimiletelephonenumbers.field_id}">
<tbody>
<?python repetition = 0
fax_index = 0
fax_error = tg.errors.get('facsimiletelephonenumber')
?>
<tr py:for="fax in value_for(user_fields.facsimiletelephonenumber)"
id="${user_fields.facsimiletelephonenumbers.field_id}_${repetition}"
class="${user_fields.facsimiletelephonenumbers.field_class}">
<td py:for="field in user_fields.facsimiletelephonenumbers.fields">
<span><input class="textfield" type="text" id="${user_fields.facsimiletelephonenumbers.field_id}_${repetition}_facsimiletelephonenumber" name="facsimiletelephonenumbers-${repetition}.facsimiletelephonenumber" value="${fax}"/></span>
<span py:if="fax_error and fax_error[fax_index]" class="fielderror"
py:content="tg.errors.get('facsimiletelephonenumber')" />
</td>
<?python fax_index = fax_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.facsimiletelephonenumbers.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.facsimiletelephonenumbers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.facsimiletelephonenumbers.field_id}');">Add Fax Number</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.facsimiletelephonenumber.field_id}"
py:content="user_fields.facsimiletelephonenumber.label" />:
<label class="fieldlabel" for="${user_fields.mobiles.field_id}"
py:content="user_fields.mobiles.label" />:
</th>
<td>
<span py:replace="user_fields.facsimiletelephonenumber.display(value_for(user_fields.facsimiletelephonenumber))" />
<span py:if="tg.errors.get('facsimiletelephonenumber')" class="fielderror"
py:content="tg.errors.get('facsimiletelephonenumber')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.mobiles.field_id}">
<tbody>
<?python repetition = 0
mobile_index = 0
mobile_error = tg.errors.get('mobile')
?>
<tr py:for="mobile in value_for(user_fields.mobile)"
id="${user_fields.mobiles.field_id}_${repetition}"
class="${user_fields.mobiles.field_class}">
<td py:for="field in user_fields.mobiles.fields">
<span><input class="textfield" type="text" id="${user_fields.mobiles.field_id}_${repetition}_mobile" name="mobiles-${repetition}.mobile" value="${mobile}"/></span>
<span py:if="mobile_error and mobile_error[mobile_index]" class="fielderror"
py:content="tg.errors.get('mobile')" />
</td>
<?python mobile_index = mobile_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.mobiles.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.mobiles.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.mobiles.field_id}');">Add Cell Number</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.mobile.field_id}"
py:content="user_fields.mobile.label" />:
<label class="fieldlabel" for="${user_fields.pagers.field_id}"
py:content="user_fields.pagers.label" />:
</th>
<td>
<span py:replace="user_fields.mobile.display(value_for(user_fields.mobile))" />
<span py:if="tg.errors.get('mobile')" class="fielderror"
py:content="tg.errors.get('mobile')" />
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.pagers.field_id}">
<tbody>
<?python repetition = 0
pager_index = 0
pager_error = tg.errors.get('pager')
?>
<tr py:for="pager in value_for(user_fields.pager)"
id="${user_fields.pagers.field_id}_${repetition}"
class="${user_fields.pagers.field_class}">
<td py:for="field in user_fields.pagers.fields">
<span><input class="textfield" type="text" id="${user_fields.pagers.field_id}_${repetition}_pager" name="pagers-${repetition}.pager" value="${pager}"/></span>
<span py:if="pager_error and pager_error[pager_index]" class="fielderror"
py:content="tg.errors.get('pager')" />
</td>
<?python pager_index = pager_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.pagers.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.pagers.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.pagers.field_id}');">Add Pager Number</a>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${user_fields.pager.field_id}"
py:content="user_fields.pager.label" />:
<label class="fieldlabel" for="${user_fields.homephones.field_id}"
py:content="user_fields.homephones.label" />:
</th>
<td>
<span py:replace="user_fields.pager.display(value_for(user_fields.pager))" />
<span py:if="tg.errors.get('pager')" class="fielderror"
py:content="tg.errors.get('pager')" />
</td>
</tr>
<td colspan="3">
<table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${user_fields.homephones.field_id}">
<tbody>
<?python repetition = 0
homephone_index = 0
homephone_error = tg.errors.get('homephone')
?>
<tr py:for="homephone in value_for(user_fields.homephone)"
id="${user_fields.homephones.field_id}_${repetition}"
class="${user_fields.homephones.field_class}">
<tr>
<th>
<label class="fieldlabel" for="${user_fields.homephone.field_id}"
py:content="user_fields.homephone.label" />:
</th>
<td>
<span py:replace="user_fields.homephone.display(value_for(user_fields.homephone))" />
<span py:if="tg.errors.get('homephone')" class="fielderror"
py:content="tg.errors.get('homephone')" />
<td py:for="field in user_fields.homephones.fields">
<span><input class="textfield" type="text" id="${user_fields.homephones.field_id}_${repetition}_homephone" name="homephones-${repetition}.homephone" value="${homephone}"/></span>
<span py:if="homephone_error and homephone_error[homephone_index]" class="fielderror"
py:content="tg.errors.get('homephone')" />
</td>
<?python homephone_index = homephone_index + 1 ?>
<td>
<a
href="javascript:ExpandingForm.removeItem('${user_fields.homephones.field_id}_${repetition}')">Remove</a>
</td>
<?python repetition = repetition + 1?>
</tr>
</tbody>
</table>
<a id="${user_fields.homephones.field_id}_doclink" href="javascript:ExpandingForm.addItem('${user_fields.homephones.field_id}');">Add Home Phone</a>
</td>
</tr>
</table>
@@ -655,7 +787,7 @@ from ipagui.helpers import ipahelper
group_dn = group.get('dn')
group_dn_esc = ipahelper.javascript_string_escape(group_dn)
group_name = group.get('cn')
group_name = group.get('cn')[0]
group_descr = "[group]"
group_type = "group"
@@ -685,6 +817,7 @@ from ipagui.helpers import ipahelper
div_counter = div_counter + 1
?>
</div>
&nbsp; <!-- a space here to prevent an empty div -->
</div>
</div>

View File

@@ -13,6 +13,8 @@ from ipagui.helpers import ipahelper
src="${tg.url('/static/javascript/dynamicedit.js')}"></script>
<script type="text/javascript" charset="utf-8"
src="${tg.url('/static/javascript/dynamicselect.js')}"></script>
<script type="text/javascript" charset="utf-8"
src="${tg.url('/tg_widgets/tg_expanding_form_widget/javascript/expanding_form.js')}"></script>
<?python
searchurl = tg.url('/user/edit_search')

View File

@@ -57,7 +57,21 @@ else:
<th>
<label class="fieldlabel" py:content="fields.cn.label" />:
</th>
<td>${user.get("cn")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = user.get("cn")
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<th>
@@ -132,31 +146,101 @@ else:
<th>
<label class="fieldlabel" py:content="fields.telephonenumber.label" />:
</th>
<td>${user.get("telephonenumber")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = user.get("telephonenumber", '')
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.facsimiletelephonenumber.label" />:
</th>
<td>${user.get("facsimiletelephonenumber")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = user.get("facsimiletelephonenumber", '')
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.mobile.label" />:
</th>
<td>${user.get("mobile")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = user.get("mobile", '')
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.pager.label" />:
</th>
<td>${user.get("pager")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = user.get("pager", '')
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.homephone.label" />:
</th>
<td>${user.get("homephone")}</td>
<td>
<table cellpadding="2" cellspacing="0" border="0">
<tbody>
<?python
index = 0
values = user.get("homephone", '')
if isinstance(values, str):
values = [values]
?>
<tr py:for="index in range(len(values))">
<td>${values[index]}</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>