Complete group and position management

This commit is contained in:
David Bartley 2007-11-15 05:28:58 -05:00
parent 97ac7fb7bd
commit 95019e376a
8 changed files with 296 additions and 15 deletions

View File

@ -12,11 +12,20 @@ attributetype ( 1.3.6.1.4.1.27934.1.1.3 NAME 'studentid'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{8} SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.27934.1.1.4 NAME 'position'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} )
objectclass ( 1.3.6.1.4.1.27934.1.2.1 NAME 'member'
SUP top AUXILIARY
MUST ( cn $ uid )
MAY ( studentid $ program $ term $ description ) )
MAY ( studentid $ program $ term $ description $ position ) )
objectclass ( 1.3.6.1.4.1.27934.1.2.2 NAME 'club'
SUP top AUXILIARY
MUST ( cn $ uid ) )
objectclass ( 1.3.6.1.4.1.27934.1.2.3 NAME 'group'
SUP top STRUCTURAL
MUST ( cn )
MAY ( uniqueMember ) )

View File

@ -9,7 +9,7 @@ Transactions are used in each method that modifies the database.
Future changes to the members database that need to be atomic
must also be moved into this module.
"""
import re
import re, ldap
from csc.adm import terms
from csc.backends import ldapi
from csc.common import conf
@ -220,6 +220,72 @@ def list_group(group):
return {}
def list_positions():
"""
Build a list of positions
Returns: a list of positions and who holds them
Example: list_positions(): -> {
'president': { 'mspang': { 'cn': 'Michael Spang', ... } } ],
...
]
"""
ceo_ldap = ldap_connection.ldap
user_base = ldap_connection.user_base
escape = ldap_connection.escape
if not ldap_connection.connected(): ldap_connection.connect()
members = ceo_ldap.search_s(user_base, ldap.SCOPE_SUBTREE, '(position=*)')
positions = {}
for (_, member) in members:
for position in member['position']:
if not position in positions:
positions[position] = {}
positions[position][member['uid'][0]] = member
return positions
def set_position(position, members):
"""
Sets a position
Parameters:
position - the position to set
members - an array of members that hold the position
Example: set_position('president', ['dtbartle'])
"""
ceo_ldap = ldap_connection.ldap
user_base = ldap_connection.user_base
escape = ldap_connection.escape
res = ceo_ldap.search_s(user_base, ldap.SCOPE_SUBTREE,
'(&(objectClass=member)(position=%s))' % escape(position))
old = set([ member['uid'][0] for (_, member) in res ])
new = set(members)
mods = {
'del': set(old) - set(new),
'add': set(new) - set(old),
}
if len(mods['del']) == 0 and len(mods['add']) == 0:
return
for type in ['del', 'add']:
for userid in mods[type]:
dn = 'uid=%s,%s' % (escape(userid), user_base)
entry1 = {'position' : [position]}
entry2 = {} #{'position' : []}
entry = ()
if type == 'del':
entry = (entry1, entry2)
elif type == 'add':
entry = (entry2, entry1)
mlist = ldap_connection.make_modlist(entry[0], entry[1])
ceo_ldap.modify_s(dn, mlist)
def delete(userid):
"""
Erase all records of a member.
@ -248,6 +314,27 @@ def delete(userid):
return member
def change_group_member(action, group, userid):
ceo_ldap = ldap_connection.ldap
user_base = ldap_connection.user_base
group_base = ldap_connection.group_base
escape = ldap_connection.escape
user_dn = 'uid=%s,%s' % (escape(userid), user_base)
group_dn = 'cn=%s,%s' % (escape(group), group_base)
entry1 = {'uniqueMember' : []}
entry2 = {'uniqueMember' : [user_dn]}
entry = []
if action == 'add' or action == 'insert':
entry = (entry1, entry2)
elif action == 'remove' or action == 'delete':
entry = (entry2, entry1)
else:
raise InvalidArgument("action", action, "invalid action")
mlist = ldap_connection.make_modlist(entry[0], entry[1])
ceo_ldap.modify_s(group_dn, mlist)
### Term Table ###

View File

@ -0,0 +1,64 @@
import urwid
from csc.apps.urwid.widgets import *
from csc.apps.urwid.window import *
from csc.adm import accounts, members
from csc.common.excep import InvalidArgument
class ChangeMember(WizardPanel):
def __init__(self, state, data):
state['data'] = data
WizardPanel.__init__(self, state)
def init_widgets(self):
self.userid = WordEdit("Username: ")
data = self.state['data']
self.widgets = [
urwid.Text( "%s %s Member" % (data['type'], data['name']) ),
urwid.Divider(),
self.userid,
]
def check(self):
self.state['userid'] = self.userid.get_edit_text()
if self.state['userid']:
if not members.connected(): members.connect()
self.state['member'] = members.get(self.userid.get_edit_text())
if not self.state['member']:
set_status("Member not found")
self.focus_widget(self.userid)
return True
clear_status()
class EndPage(WizardPanel):
def init_widgets(self):
self.headtext = urwid.Text("")
self.midtext = urwid.Text("")
self.widgets = [
self.headtext,
urwid.Divider(),
self.midtext,
]
def focusable(self):
return False
def check(self):
pop_window()
def activate(self):
data = self.state['data']
type = data['type'].lower()
failed = []
for group in data['groups']:
try:
members.change_group_member(type, group, self.state['userid'])
except:
failed.append(group)
if len(failed) == 0:
self.headtext.set_text("%s succeeded" % data['type'])
self.midtext.set_text("Congratulations, the group modification "
"has succeeded.")
else:
self.headtext.set_text("%s partially succeeded" % data['type'])
self.midtext.set_text("Failed to %s member to %s for the "
"following groups: %s. This may indicate an attempt to add a "
"duplicate group member or to delete a non-present group "
"member." % (data['type'].lower(), data['name'],
', '.join(failed)))

View File

@ -21,7 +21,8 @@ class LdapFilter:
search = self.escape(self.widget.get_edit_text(self))
filter = '(%s=%s)' % (attr, search)
try:
matches = self.ldap.search_s(self.base, ldap.SCOPE_SUBTREE, filter)
matches = self.ldap.search_s(self.base,
ldap.SCOPE_SUBTREE, filter)
if len(matches) > 0:
(_, attrs) = matches[0]
for (k, v) in self.map.items():

View File

@ -7,6 +7,8 @@ import csc.apps.urwid.newmember as newmember
import csc.apps.urwid.renew as renew
import csc.apps.urwid.info as info
import csc.apps.urwid.search as search
import csc.apps.urwid.positions as positions
import csc.apps.urwid.groups as groups
from csc.adm import accounts, members, terms
from csc.common.excep import InvalidArgument
@ -49,12 +51,14 @@ def program_name():
office_data = {
"name" : "Office Staff",
"group" : "office"
"group" : "office",
"groups" : [ "office", "cdrom", "audio", "video", "www" ],
}
syscom_data = {
"name" : "Systems Committee",
"group" : "syscom"
"group" : "syscom",
"groups" : [ "office", "staff", "adm", "src" ],
}
def menu_items(items):
@ -67,6 +71,7 @@ def main_menu():
("Create Club Account", new_club, None),
("Display Member", display_member, None),
("Search", search_members, None),
("Manage Positions", manage_positions, None),
("Manage Office Staff", group_members, office_data),
("Manage Systems Committee", group_members, syscom_data),
("Exit", raise_abort, None),
@ -100,7 +105,7 @@ def new_club(*args, **kwargs):
newmember.ClubIntroPage,
newmember.ClubInfoPage,
(newmember.EndPage, "club"),
], (60,15))
], (60, 15))
def renew_member(*args, **kwargs):
push_wizard("Renew Membership", [
@ -137,10 +142,23 @@ def search_term(data):
def search_group(data):
push_wizard("By Group", [ search.GroupPage ])
def manage_positions(data):
push_wizard("Manage Positions", [
positions.IntroPage,
positions.InfoPage,
positions.EndPage,
], (50, 15))
def group_members(data):
add_data = data.copy()
add_data['type'] = 'Add'
remove_data = data.copy()
remove_data['type'] = 'Remove'
menu = [
("Add %s member" % data["name"].lower(), add_group_member, data),
("Remove %s member" % data["name"].lower(), remove_group_member, data),
("Add %s member" % data["name"].lower(),
change_group_member, add_data),
("Remove %s member" % data["name"].lower(),
change_group_member, remove_data),
("List %s members" % data["name"].lower(), list_group_members, data),
("Back", raise_back, None),
]
@ -148,11 +166,11 @@ def group_members(data):
listbox = urwid.ListBox( menu_items( menu ) )
push_window(listbox, "Manage %s" % data["name"])
def add_group_member(data):
pass
def remove_group_member(data):
pass
def change_group_member(data):
push_wizard("%s %s Member" % (data["type"], data["name"]), [
(groups.ChangeMember, data),
groups.EndPage,
])
def list_group_members(data):
if not members.connected(): members.connect()

View File

@ -58,7 +58,7 @@ class InfoPage(WizardPanel):
self.state['name'] = self.name.get_edit_text()
self.state['program'] = self.program.get_edit_text()
if len( self.state['userid'] ) < 4:
if len( self.state['userid'] ) < 3:
self.focus_widget( self.userid )
set_status("Username is too short")
return True
@ -188,5 +188,7 @@ class EndPage(WizardPanel):
else:
self.headtext.set_text("User Added")
self.midtext.set_text("Congratulations, %s has been added "
"successfully. Please run 'addhomedir %s'."
"successfully. Please run 'addhomedir %s'. "
"You should also rebuild the website in order to update the "
"memberlist."
% (self.state['userid'], self.state['userid']))

View File

@ -0,0 +1,83 @@
import urwid
from csc.apps.urwid.widgets import *
from csc.apps.urwid.window import *
from csc.adm import accounts, members
from csc.common.excep import InvalidArgument
position_data = [
('president', 'President'),
('vice-president', 'Vice-president'),
('treasurer', 'Treasurer'),
('secretary', 'Secretary'),
('sysadmin', 'System Administrator'),
('librarian', 'Librarian'),
('imapd', 'Imapd'),
('webmaster', 'Web Master'),
]
class IntroPage(WizardPanel):
def init_widgets(self):
self.widgets = [
urwid.Text( "Managing Positions" ),
urwid.Divider(),
urwid.Text( "Enter a username for each position. If a position is "
"held by multiple people, enter a comma-separated "
"list of usernames. If a position is held by nobody "
"leave the username blank." ),
]
def focusable(self):
return False
class InfoPage(WizardPanel):
def init_widgets(self):
if not members.connected(): members.connect()
self.widgets = [
urwid.Text( "Positions" ),
urwid.Divider(),
]
positions = members.list_positions()
self.position_widgets = {}
for (position, text) in position_data:
widget = WordEdit("%s: " % text)
if position in positions:
widget.set_edit_text(','.join(positions[position].keys()))
else:
widget.set_edit_text('')
self.position_widgets[position] = widget
self.widgets.append(widget)
def parse(self, entry):
if len(entry) == 0:
return []
return entry.split(',')
def check(self):
self.state['positions'] = {}
for (position, widget) in self.position_widgets.iteritems():
self.state['positions'][position] = \
self.parse(widget.get_edit_text())
clear_status()
class EndPage(WizardPanel):
def init_widgets(self):
old = members.list_positions()
self.headtext = urwid.Text("")
self.midtext = urwid.Text("")
self.widgets = [
self.headtext,
urwid.Divider(),
self.midtext,
]
def focusable(self):
return False
def activate(self):
for (position, info) in self.state['positions'].iteritems():
members.set_position(position, info)
self.headtext.set_text("Positions Updated")
self.midtext.set_text("Congratulations, positions have been updated. "
"You should rebuild the website in order to update the Positions "
"page.")
def check(self):
pop_window()

View File

@ -641,6 +641,23 @@ class LDAPConnection(object):
return gids
def make_modlist(self, old, new):
keys = set(old.keys()).union(set(new))
mlist = []
for key in keys:
if key in old and not key in new:
mlist.append((ldap.MOD_DELETE, key, list(set(old[key]))))
elif key in new and not key in old:
mlist.append((ldap.MOD_ADD, key, list(set(new[key]))))
else:
to_add = list(set(new[key]) - set(old[key]))
if len(to_add) > 0:
mlist.append((ldap.MOD_ADD, key, to_add))
to_del = list(set(old[key]) - set(new[key]))
if len(to_del) > 0:
mlist.append((ldap.MOD_DELETE, key, to_del))
return mlist
### Tests ###