[Freeipa-devel] [PATCH] draft of group member management

Kevin McCarthy kmccarth at redhat.com
Fri Sep 14 22:20:00 UTC 2007


This is half finished, but suitable for a code review.  I'll also push
to demo so you can play with it.

This is basic group member management.  There are still a lot of issues
to deal with, but it is functional.

-Kevin

-------------- next part --------------
# HG changeset patch
# User Kevin McCarthy <kmccarth at redhat.com>
# Date 1189808409 25200
# Node ID 22f90eaa60da6f1ef6cd85335482f8b65166d9bc
# Parent  c35fdc2573b634dbdf2b4b219c88666dd1dd66b4
patch queue: groupmember.patch

diff -r c35fdc2573b6 -r 22f90eaa60da ipa-server/ipa-gui/ipagui/controllers.py
--- a/ipa-server/ipa-gui/ipagui/controllers.py	Thu Sep 13 10:55:56 2007 -0700
+++ b/ipa-server/ipa-gui/ipagui/controllers.py	Fri Sep 14 15:20:09 2007 -0700
@@ -120,17 +120,21 @@ class Root(controllers.RootController):
         if tg_errors:
             turbogears.flash("There was a problem with the form!")
 
-        client.set_principal(identity.current.user_name)
-        user = client.get_user_by_uid(uid, user_fields)
-        user_dict = user.toDict()
-        # Edit shouldn't fill in the password field.
-        if user_dict.has_key('userpassword'):
-            del(user_dict['userpassword'])
-
-        # store a copy of the original user for the update later
-        user_data = b64encode(dumps(user_dict))
-        user_dict['user_orig'] = user_data
-        return dict(form=user_edit_form, user=user_dict)
+        try:
+            client.set_principal(identity.current.user_name)
+            user = client.get_user_by_uid(uid, user_fields)
+            user_dict = user.toDict()
+            # Edit shouldn't fill in the password field.
+            if user_dict.has_key('userpassword'):
+                del(user_dict['userpassword'])
+
+            # store a copy of the original user for the update later
+            user_data = b64encode(dumps(user_dict))
+            user_dict['user_orig'] = user_data
+            return dict(form=user_edit_form, user=user_dict)
+        except ipaerror.IPAError, e:
+            turbogears.flash("User edit failed: " + str(e))
+            raise turbogears.redirect('/usershow', uid=kw.get('uid'))
 
     @expose()
     @identity.require(identity.not_anonymous())
@@ -204,6 +208,24 @@ class Root(controllers.RootController):
 
         return dict(users=users, uid=uid, fields=forms.user.UserFields())
 
+    @expose("ipagui.templates.userlistajax")
+    @identity.require(identity.not_anonymous())
+    def userlist_ajax(self, **kw):
+        """Searches for users and displays list of results in a table.
+           This method is used for ajax calls."""
+        client.set_principal(identity.current.user_name)
+        users = []
+        uid = kw.get('uid')
+        if uid != None and len(uid) > 0:
+            try:
+                users = client.find_users(uid.encode('utf-8'))
+                counter = users[0]
+                users = users[1:]
+            except ipaerror.IPAError, e:
+                turbogears.flash("User list failed: " + str(e))
+
+        return dict(users=users, uid=uid, fields=forms.user.UserFields())
+
 
     @expose("ipagui.templates.usershow")
     @identity.require(identity.not_anonymous())
@@ -371,8 +393,7 @@ class Root(controllers.RootController):
 
             rv = client.add_group(new_group)
             turbogears.flash("%s added!" % kw.get('cn'))
-            # raise turbogears.redirect('/groupedit', cn=kw['cn'])
-            raise turbogears.redirect('/')
+            raise turbogears.redirect('/groupshow', cn=kw.get('cn'))
         except ipaerror.exception_for(ipaerror.LDAP_DUPLICATE):
             turbogears.flash("Group with name '%s' already exists" %
                     kw.get('cn'))
@@ -390,13 +411,43 @@ class Root(controllers.RootController):
             turbogears.flash("There was a problem with the form!")
 
         client.set_principal(identity.current.user_name)
-        group = client.get_group_by_cn(cn, group_fields)
-        group_dict = group.toDict()
-
-        # store a copy of the original group for the update later
-        group_data = b64encode(dumps(group_dict))
-        group_dict['group_orig'] = group_data
-        return dict(form=group_edit_form, group=group_dict)
+        try:
+            group = client.get_group_by_cn(cn, group_fields)
+
+            group_dict = group.toDict()
+
+            #
+            # convert members to users, for easier manipulation on the page
+            #
+            member_dns = []
+            if group_dict.has_key('uniquemember'):
+                member_dns = group_dict.get('uniquemember')
+                # remove from dict - it's not needed for update
+                # and we are storing the members in a different form
+                del group_dict['uniquemember']
+            if not(isinstance(member_dns,list) or isinstance(member_dns,tuple)):
+                member_dns = [member_dns]
+
+            # TODO: convert this into an efficient (single) function call
+            member_users = map(
+                    lambda dn: client.get_user_by_dn(dn, ['givenname', 'sn', 'uid']),
+                    member_dns)
+
+            # Map users into an array of dicts, which can be serialized
+            # (so we don't have to do this on each round trip)
+            member_dicts = map(lambda user: user.toDict(), member_users)
+
+            # store a copy of the original group for the update later
+            group_data = b64encode(dumps(group_dict))
+            member_data = b64encode(dumps(member_dicts))
+            group_dict['group_orig'] = group_data
+            group_dict['member_data'] = member_data
+
+            return dict(form=group_edit_form, group=group_dict, members=member_dicts)
+        except ipaerror.IPAError, e:
+            turbogears.flash("User show failed: " + str(e))
+            turbogears.flash("Group edit failed: " + str(e))
+            raise turbogears.redirect('/groupshow', uid=kw.get('cn'))
 
     @expose()
     @identity.require(identity.not_anonymous())
@@ -408,26 +459,83 @@ class Root(controllers.RootController):
             turbogears.flash("Edit group cancelled")
             raise turbogears.redirect('/groupshow', cn=kw.get('cn'))
 
+        # 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:
-            return dict(form=group_edit_form, group=kw,
+            return dict(form=group_edit_form, group=kw, members=member_dicts,
                         tg_template='ipagui.templates.groupedit')
 
+        group_modified = False
+
+        #
+        # Update group itself
+        #
         try:
             orig_group_dict = loads(b64decode(kw.get('group_orig')))
 
             new_group = ipa.group.Group(orig_group_dict)
-            new_group.setValue('description', kw.get('description'))
+            if new_group.description != kw.get('description'):
+                group_modified = True
+                new_group.setValue('description', kw.get('description'))
             if kw.get('gidnumber'):
+                group_modified = True
                 new_group.setValue('gidnumber', str(kw.get('gidnumber')))
 
-            rv = client.update_group(new_group)
-            turbogears.flash("%s updated!" % kw['cn'])
-            raise turbogears.redirect('/groupshow', cn=kw['cn'])
+            if group_modified:
+                rv = client.update_group(new_group)
+            #
+            # TODO - if the group update succeeds, but below operations fail,
+            # we needs to make sure a subsequent submit doesn't try to update
+            # the group again.  Probably by overwriting the group_orig hidden
+            # field blob.
+            #
         except ipaerror.IPAError, e:
             turbogears.flash("User update failed: " + str(e))
-            return dict(form=group_edit_form, group=kw,
+            return dict(form=group_edit_form, group=kw, members=member_dicts,
                         tg_template='ipagui.templates.groupedit')
+
+        #
+        # Add members
+        #
+        try:
+            uidadds = kw.get('uidadd')
+            if uidadds != None:
+                if not(isinstance(uidadds,list) or isinstance(uidadds,tuple)):
+                    uidadds = [uidadds]
+                failed = client.add_users_to_group(uidadds, kw.get('cn'))
+                #
+                # TODO - deal with failed adds
+                #
+        except ipaerror.IPAError, e:
+            turbogears.flash("User update failed: " + str(e))
+            return dict(form=group_edit_form, group=kw, members=member_dicts,
+                        tg_template='ipagui.templates.groupedit')
+
+        #
+        # Remove members
+        #
+        try:
+            uiddels = kw.get('uiddel')
+            if uiddels != None:
+                if not(isinstance(uiddels,list) or isinstance(uiddels,tuple)):
+                    uiddels = [uiddels]
+                failed = client.remove_users_from_group(uiddels, kw.get('cn'))
+                #
+                # TODO - deal with failed removals
+                #
+        except ipaerror.IPAError, e:
+            turbogears.flash("User update failed: " + str(e))
+            return dict(form=group_edit_form, group=kw, members=member_dicts,
+                        tg_template='ipagui.templates.groupedit')
+
+        # TODO if not group_modified
+
+        turbogears.flash("%s updated!" % kw['cn'])
+        raise turbogears.redirect('/groupshow', cn=kw['cn'])
+
 
     @expose("ipagui.templates.grouplist")
     @identity.require(identity.not_anonymous())
@@ -458,7 +566,25 @@ class Root(controllers.RootController):
         client.set_principal(identity.current.user_name)
         try:
             group = client.get_group_by_cn(cn, group_fields)
-            return dict(group=group.toDict(), fields=forms.group.GroupFields())
+            group_dict = group.toDict()
+
+            #
+            # convert members to users, for display on the page
+            #
+            member_dns = []
+            if group_dict.has_key('uniquemember'):
+                member_dns = group_dict.get('uniquemember')
+            if not(isinstance(member_dns,list) or isinstance(member_dns,tuple)):
+                member_dns = [member_dns]
+
+            # TODO: convert this into an efficient (single) function call
+            member_users = map(
+                    lambda dn: client.get_user_by_dn(dn, ['givenname', 'sn', 'uid']),
+                    member_dns)
+            member_dicts = map(lambda user: user.toDict(), member_users)
+
+            return dict(group=group_dict, fields=forms.group.GroupFields(),
+                    members = member_dicts)
         except ipaerror.IPAError, e:
             turbogears.flash("Group show failed: " + str(e))
             raise turbogears.redirect("/")
diff -r c35fdc2573b6 -r 22f90eaa60da ipa-server/ipa-gui/ipagui/forms/group.py
--- a/ipa-server/ipa-gui/ipagui/forms/group.py	Thu Sep 13 10:55:56 2007 -0700
+++ b/ipa-server/ipa-gui/ipagui/forms/group.py	Fri Sep 14 15:20:09 2007 -0700
@@ -9,6 +9,7 @@ class GroupFields():
     cn_hidden = widgets.HiddenField(name="cn")
 
     group_orig = widgets.HiddenField(name="group_orig")
+    member_data = widgets.HiddenField(name="member_data")
 
 class GroupNewValidator(validators.Schema):
     cn = validators.PlainText(not_empty=True)
@@ -36,11 +37,11 @@ class GroupEditValidator(validators.Sche
     description = validators.String(not_empty=False)
 
 class GroupEditForm(widgets.Form):
-    params = ['group']
+    params = ['members', 'group']
 
     fields = [GroupFields.gidnumber, GroupFields.description,
               GroupFields.cn_hidden,
-              GroupFields.group_orig]
+              GroupFields.group_orig, GroupFields.member_data]
 
     validator = GroupEditValidator()
 
diff -r c35fdc2573b6 -r 22f90eaa60da ipa-server/ipa-gui/ipagui/templates/groupedit.kid
--- a/ipa-server/ipa-gui/ipagui/templates/groupedit.kid	Thu Sep 13 10:55:56 2007 -0700
+++ b/ipa-server/ipa-gui/ipagui/templates/groupedit.kid	Fri Sep 14 15:20:09 2007 -0700
@@ -16,6 +16,6 @@
     <h2>Edit Group</h2>
   </div>
 
-  ${form.display(action="groupupdate", value=group)}
+  ${form.display(action="groupupdate", value=group, members=members)}
 </body>
 </html>
diff -r c35fdc2573b6 -r 22f90eaa60da ipa-server/ipa-gui/ipagui/templates/groupeditform.kid
--- a/ipa-server/ipa-gui/ipagui/templates/groupeditform.kid	Thu Sep 13 10:55:56 2007 -0700
+++ b/ipa-server/ipa-gui/ipagui/templates/groupeditform.kid	Fri Sep 14 15:20:09 2007 -0700
@@ -2,14 +2,112 @@
   class="simpleroster">
   <form action="${action}" name="${name}" method="${method}" class="tableform">
 
+
+  <?python searchurl = tg.url('/userlist_ajax') ?>
+
   <script type="text/javascript">
     function toggleProtectedFields(checkbox) {
-      gidnumberField = document.getElementById('form_gidnumber');
+      gidnumberField = $('form_gidnumber');
       if (checkbox.checked) {
         gidnumberField.disabled = false;
       } else {
         gidnumberField.disabled = true;
       }
+    }
+
+    /*
+     * Callback used for afterFinish in scriptaculous effect
+     */
+    function removeElement(effect) {
+      Element.remove(effect.element);
+    }
+
+    function adduser(uid, cn) {
+      newdiv = document.createElement('div');
+      newdiv.appendChild(document.createTextNode(
+        cn.escapeHTML() + " (" + uid.escapeHTML() + ") "));
+
+      undolink = document.createElement('a');
+      undolink.setAttribute('href', '');
+      undolink.setAttribute('onclick',
+        'new Effect.Fade(Element.up(this), {afterFinish: removeElement});' +
+        'return false;');
+      undolink.appendChild(document.createTextNode("undo"));
+      newdiv.appendChild(undolink);
+
+      uidInfo = document.createElement('input');
+      uidInfo.setAttribute('type', 'hidden');
+      uidInfo.setAttribute('name', 'uidadd');
+      uidInfo.setAttribute('value', uid);
+      newdiv.appendChild(uidInfo);
+
+      newdiv.style.display = 'none';
+      $('newmembers').appendChild(newdiv);
+
+      return newdiv
+    }
+
+    function adduserHandler(element, uid, cn) {
+      newdiv = adduser(uid, cn)
+      new Effect.Fade(Element.up(element));
+      new Effect.Appear(newdiv);
+      /* Element.up(element).remove(); */
+    }
+
+    function removeuser(uid, cn) {
+      newdiv = document.createElement('div');
+      newdiv.appendChild(document.createTextNode(
+        cn.escapeHTML() + " (" + uid.escapeHTML() + ") "));
+
+      undolink = document.createElement('a');
+      undolink.setAttribute('href', '');
+      undolink.setAttribute('onclick',
+        'new Effect.Fade(Element.up(this), {afterFinish: removeElement});' +
+        "new Effect.Appear($('member-" + uid + "'));" +
+        'return false;');
+      undolink.appendChild(document.createTextNode("undo"));
+      newdiv.appendChild(undolink);
+
+      uidInfo = document.createElement('input');
+      uidInfo.setAttribute('type', 'hidden');
+      uidInfo.setAttribute('name', 'uiddel');
+      uidInfo.setAttribute('value', uid);
+      newdiv.appendChild(uidInfo);
+
+      newdiv.style.display = 'none';
+      $('delmembers').appendChild(newdiv);
+
+      return newdiv
+    }
+
+    function removeuserHandler(element, uid, cn) {
+      newdiv = removeuser(uid, cn);
+      new Effect.Fade(Element.up(element));
+      new Effect.Appear(newdiv);
+      /* Element.up(element).remove(); */
+    }
+
+    function enterDoSearch(e) {
+      var keyPressed;
+      if (window.event) {
+        keyPressed = window.event.keyCode;
+      } else {
+        keyPressed = e.which; 
+      }
+
+      if (keyPressed == 13) {
+        return doSearch();
+      } else {
+        return true;
+      }
+    }
+
+    function doSearch() {
+      new Ajax.Updater('searchresults',
+          '${searchurl}',
+          {  asynchronous:true,
+             parameters: { uid: $('uid').value } });
+      return false;
     }
   </script>
 
@@ -63,6 +161,56 @@
       </tr>
     </table>
 
+    <div>
+      <div class="formsection">Group Members</div>
+
+      <div style="float:right; width:50%">
+        <div>To Remove:</div>
+        <div id="delmembers">
+        </div>
+      </div>
+
+      <div>
+        <div py:for="member in members" id="member-${member.get('uid')}">
+          <?python
+          member_uid = member.get('uid')
+          member_name = "%s %s" % (member.get('givenname', ''),
+                                   member.get('sn', ''))
+          ?>
+          ${member_name}
+          <a href="" 
+            onclick="removeuserHandler(this, '${member_uid}', '${member_name}');
+                     return false;"
+          >remove</a>
+        </div>
+      </div>
+
+    </div>
+
+    <div style="clear:both">
+      <div class="formsection">Add Persons</div>
+
+      <div style="float:right; width:50%">
+        <div>To Add:</div>
+        <div id="newmembers">
+        </div>
+      </div>
+
+      <div>
+        <div id="search">
+          <input id="uid" type="text" name="uid"
+            onkeypress="return enterDoSearch(event);" />
+          <input type="button" value="Find Users"
+            onclick="return doSearch();"
+          />
+        </div>
+        <div id="searchresults">
+        </div>
+      </div>
+    </div>
+
+
+
     <table class="formtable" cellpadding="2" cellspacing="0" border="0">
       <tr>
         <th>
diff -r c35fdc2573b6 -r 22f90eaa60da ipa-server/ipa-gui/ipagui/templates/groupshow.kid
--- a/ipa-server/ipa-gui/ipagui/templates/groupshow.kid	Thu Sep 13 10:55:56 2007 -0700
+++ b/ipa-server/ipa-gui/ipagui/templates/groupshow.kid	Fri Sep 14 15:20:09 2007 -0700
@@ -32,6 +32,18 @@
         </tr>
     </table>
 
+    <div class="formsection">Group Members</div>
+    <div py:for="member in members">
+      <?python
+      member_name = "%s %s" % (member.get('givenname', ''),
+                               member.get('sn', ''))
+      ?>
+      ${member_name} (${member.get('uid')})
+    </div>
+
+    <br/>
+    <br/>
+
     <a href="${tg.url('/groupedit', cn=group.get('cn'))}">edit</a>
 
 </body>
diff -r c35fdc2573b6 -r 22f90eaa60da ipa-server/ipa-gui/ipagui/templates/userlistajax.kid
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ipa-server/ipa-gui/ipagui/templates/userlistajax.kid	Fri Sep 14 15:20:09 2007 -0700
@@ -0,0 +1,14 @@
+<div xmlns:py="http://purl.org/kid/ns#">
+    <div py:if='(users != None) and (len(users) > 0)'>
+        <div>${len(users)} results returned:</div>
+        <div py:for="user in users">
+          ${user.givenName} ${user.sn} (${user.uid})
+          <a href="" 
+            onclick="adduserHandler(this, '${user.uid}', '${user.cn}'); return false;"
+          >add</a>
+        </div>
+    </div>
+    <div py:if='(users != None) and (len(users) == 0)'>
+        No results found for "${uid}"
+    </div>
+</div>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/x-pkcs7-signature
Size: 2228 bytes
Desc: not available
URL: <http://listman.redhat.com/archives/freeipa-devel/attachments/20070914/0c7ee1fc/attachment.bin>


More information about the Freeipa-devel mailing list