summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2007-11-08 22:12:42 -0500
committerRob Crittenden <rcritten@redhat.com>2007-11-08 22:12:42 -0500
commite9dfbfa773149c57544e5c8e4d87a00fc9960bf1 (patch)
tree71eb26df5496b72e8a2f3eb4069f9a03a794905b
parent39dcd194ca6e2b677aa1f4726bf1a60016b20a67 (diff)
downloadfreeipa-e9dfbfa773149c57544e5c8e4d87a00fc9960bf1.tar.gz
freeipa-e9dfbfa773149c57544e5c8e4d87a00fc9960bf1.tar.xz
freeipa-e9dfbfa773149c57544e5c8e4d87a00fc9960bf1.zip
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
-rw-r--r--ipa-python/ipaerror.py5
-rw-r--r--ipa-server/ipa-gui/README.multivalue27
-rw-r--r--ipa-server/ipa-gui/ipagui/forms/group.py3
-rw-r--r--ipa-server/ipa-gui/ipagui/forms/user.py8
-rw-r--r--ipa-server/ipa-gui/ipagui/subcontrollers/group.py49
-rw-r--r--ipa-server/ipa-gui/ipagui/subcontrollers/user.py87
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/groupeditform.kid36
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/groupshow.kid20
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/usereditform.kid223
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/usernewform.kid2
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/usershow.kid96
11 files changed, 477 insertions, 79 deletions
diff --git a/ipa-python/ipaerror.py b/ipa-python/ipaerror.py
index 0106132ca..b10a9a8fc 100644
--- a/ipa-python/ipaerror.py
+++ b/ipa-python/ipaerror.py
@@ -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):
diff --git a/ipa-server/ipa-gui/README.multivalue b/ipa-server/ipa-gui/README.multivalue
new file mode 100644
index 000000000..ba315181d
--- /dev/null
+++ b/ipa-server/ipa-gui/README.multivalue
@@ -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
diff --git a/ipa-server/ipa-gui/ipagui/forms/group.py b/ipa-server/ipa-gui/ipagui/forms/group.py
index 380c904a4..f9ae5e5ea 100644
--- a/ipa-server/ipa-gui/ipagui/forms/group.py
+++ b/ipa-server/ipa-gui/ipagui/forms/group.py
@@ -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)
diff --git a/ipa-server/ipa-gui/ipagui/forms/user.py b/ipa-server/ipa-gui/ipagui/forms/user.py
index 1a35b4e07..b426f8e91 100644
--- a/ipa-server/ipa-gui/ipagui/forms/user.py
+++ b/ipa-server/ipa-gui/ipagui/forms/user.py
@@ -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)
diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/group.py b/ipa-server/ipa-gui/ipagui/subcontrollers/group.py
index f0574a21c..8ea87641e 100644
--- a/ipa-server/ipa-gui/ipagui/subcontrollers/group.py
+++ b/ipa-server/ipa-gui/ipagui/subcontrollers/group.py
@@ -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()
diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/user.py b/ipa-server/ipa-gui/ipagui/subcontrollers/user.py
index d328052b1..a33307ae6 100644
--- a/ipa-server/ipa-gui/ipagui/subcontrollers/user.py
+++ b/ipa-server/ipa-gui/ipagui/subcontrollers/user.py
@@ -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'),
diff --git a/ipa-server/ipa-gui/ipagui/templates/groupeditform.kid b/ipa-server/ipa-gui/ipagui/templates/groupeditform.kid
index cab585fcc..865cdfcc3 100644
--- a/ipa-server/ipa-gui/ipagui/templates/groupeditform.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/groupeditform.kid
@@ -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>
diff --git a/ipa-server/ipa-gui/ipagui/templates/groupshow.kid b/ipa-server/ipa-gui/ipagui/templates/groupshow.kid
index 7a66acdbe..f0d1ddfbb 100644
--- a/ipa-server/ipa-gui/ipagui/templates/groupshow.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/groupshow.kid
@@ -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)
diff --git a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid
index f6da48870..5afb0d055 100644
--- a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid
@@ -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" />:
- </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')" />
-
+ <label class="fieldlabel" for="${user_fields.cns.field_id}"
+ py:content="user_fields.cns.label" />:
+ </th>
+ <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" />:
- </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')" />
+ <label class="fieldlabel" for="${user_fields.telephonenumbers.field_id}"
+ py:content="user_fields.telephonenumbers.label" />:
+ </th>
+ <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.facsimiletelephonenumber.field_id}"
- py:content="user_fields.facsimiletelephonenumber.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')" />
+ <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.mobile.field_id}"
- py:content="user_fields.mobile.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')" />
+ <label class="fieldlabel" for="${user_fields.mobiles.field_id}"
+ py:content="user_fields.mobiles.label" />:
+ </th>
+ <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.pager.field_id}"
- py:content="user_fields.pager.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')" />
+ <label class="fieldlabel" for="${user_fields.pagers.field_id}"
+ py:content="user_fields.pagers.label" />:
+ </th>
+ <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.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')" />
+ <label class="fieldlabel" for="${user_fields.homephones.field_id}"
+ py:content="user_fields.homephones.label" />:
+ </th>
+ <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}">
+
+ <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>
diff --git a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid
index eeaa87fa4..82a90982d 100644
--- a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid
@@ -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')
diff --git a/ipa-server/ipa-gui/ipagui/templates/usershow.kid b/ipa-server/ipa-gui/ipagui/templates/usershow.kid
index cc56340d9..a3b564c11 100644
--- a/ipa-server/ipa-gui/ipagui/templates/usershow.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/usershow.kid
@@ -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>