diff options
author | Rob Crittenden <rcritten@redhat.com> | 2007-11-08 22:12:42 -0500 |
---|---|---|
committer | Rob Crittenden <rcritten@redhat.com> | 2007-11-08 22:12:42 -0500 |
commit | e9dfbfa773149c57544e5c8e4d87a00fc9960bf1 (patch) | |
tree | 71eb26df5496b72e8a2f3eb4069f9a03a794905b | |
parent | 39dcd194ca6e2b677aa1f4726bf1a60016b20a67 (diff) | |
download | freeipa-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.py | 5 | ||||
-rw-r--r-- | ipa-server/ipa-gui/README.multivalue | 27 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/forms/group.py | 3 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/forms/user.py | 8 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/subcontrollers/group.py | 49 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/subcontrollers/user.py | 87 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/groupeditform.kid | 36 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/groupshow.kid | 20 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/usereditform.kid | 223 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/usernewform.kid | 2 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/usershow.kid | 96 |
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> + <!-- 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> |