Complete group and position management
This commit is contained in:
parent
97ac7fb7bd
commit
95019e376a
|
@ -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 ) )
|
||||
|
|
|
@ -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 ###
|
||||
|
||||
|
|
|
@ -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)))
|
|
@ -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():
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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']))
|
||||
|
|
|
@ -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()
|
|
@ -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 ###
|
||||
|
||||
|
|
Loading…
Reference in New Issue