4 This module contains functions for registering new members, registering
5 members for terms, searching for members, and other member-related
8 Transactions are used in each method that modifies the database.
9 Future changes to the members database that need to be atomic
10 must also be moved into this module.
13 from csc.adm import terms
14 from csc.backends import db, ldapi
15 from csc.common import conf
16 from csc.common.excep import InvalidArgument
21 CONFIG_FILE = '/etc/csc/members.cf'
25 def load_configuration():
26 """Load Members Configuration"""
28 string_fields = [ 'studentid_regex', 'realname_regex', 'server',
29 'database', 'user', 'password', 'server_url', 'users_base',
30 'groups_base', 'admin_bind_dn', 'admin_bind_pw' ]
32 # read configuration file
33 cfg_tmp = conf.read(CONFIG_FILE)
35 # verify configuration
36 conf.check_string_fields(CONFIG_FILE, string_fields, cfg_tmp)
38 # update the current configuration with the loaded values
45 DBException = db.DBException
46 ConfigurationException = conf.ConfigurationException
48 class MemberException(Exception):
49 """Base exception class for member-related errors."""
51 class DuplicateStudentID(MemberException):
52 """Exception class for student ID conflicts."""
53 def __init__(self, studentid):
54 self.studentid = studentid
56 return "Student ID already exists in the database: %s" % self.studentid
58 class InvalidStudentID(MemberException):
59 """Exception class for malformed student IDs."""
60 def __init__(self, studentid):
61 self.studentid = studentid
63 return "Student ID is invalid: %s" % self.studentid
65 class InvalidTerm(MemberException):
66 """Exception class for malformed terms."""
67 def __init__(self, term):
70 return "Term is invalid: %s" % self.term
72 class InvalidRealName(MemberException):
73 """Exception class for invalid real names."""
74 def __init__(self, name):
77 return "Name is invalid: %s" % self.name
79 class NoSuchMember(MemberException):
80 """Exception class for nonexistent members."""
81 def __init__(self, memberid):
82 self.memberid = memberid
84 return "Member not found: %d" % self.memberid
88 ### Connection Management ###
90 # global database connection
91 db_connection = db.DBConnection()
93 # global directory connection
94 ldap_connection = ldapi.LDAPConnection()
97 """Connect to PostgreSQL."""
100 db_connection.connect(cfg['server'], cfg['database'])
101 ldap_connection.connect(cfg['server_url'], cfg['admin_bind_dn'], cfg['admin_bind_pw'], cfg['users_base'], cfg['groups_base'])
105 """Disconnect from PostgreSQL."""
107 db_connection.disconnect()
108 ldap_connection.disconnect()
112 """Determine whether the db_connection has been established."""
114 return db_connection.connected() and ldap_connection.connected()
120 def new(uid, realname, studentid=None, program=None, mtype='user'):
122 Registers a new CSC member. The member is added to the members table
123 and registered for the current term.
126 uid - the initial user id
127 realname - the full real name of the member
128 studentid - the student id number of the member
129 program - the program of study of the member
130 mtype - a string describing the type of member ('user', 'club')
132 Returns: the memberid of the new member
135 DuplicateStudentID - if the student id already exists in the database
136 InvalidStudentID - if the student id is malformed
137 InvalidRealName - if the real name is malformed
139 Example: new("Michael Spang", program="CS") -> 3349
142 # blank attributes should be NULL
143 if studentid == '': studentid = None
144 if program == '': program = None
145 if uid == '': uid = None
146 if mtype == '': mtype = None
148 # check the student id format
149 if studentid is not None and not re.match(cfg['studentid_regex'], str(studentid)):
150 raise InvalidStudentID(studentid)
152 # check real name format (UNIX account real names must not contain [,:=])
153 if not re.match(cfg['realname_regex'], realname):
154 raise InvalidRealName(realname)
156 # check for duplicate student id
157 member = db_connection.select_member_by_studentid(studentid) or \
158 ldap_connection.member_search_studentid(studentid)
160 raise DuplicateStudentID(studentid)
162 # check for duplicate userid
163 member = db_connection.select_member_by_userid(uid) or \
164 ldap_connection.user_lookup(uid)
166 raise InvalidArgument("uid", uid, "duplicate uid")
168 # add the member to the database
169 memberid = db_connection.insert_member(realname, studentid, program, userid=uid)
171 # add the member to the directory
172 ldap_connection.member_add(uid, realname, studentid, program)
174 # register them for this term in the database
175 db_connection.insert_term(memberid, terms.current())
177 # register them for this term in the directory
178 member = ldap_connection.member_lookup(uid)
179 member['term'] = [ terms.current() ]
180 ldap_connection.user_modify(uid, member)
182 # commit the database transaction
183 db_connection.commit()
190 Look up attributes of a member by memberid.
192 Returns: a dictionary of attributes
194 Example: get(3349) -> {
196 'name': 'Michael Spang',
197 'program': 'Computer Science',
202 return db_connection.select_member_by_id(memberid)
205 def get_userid(userid):
207 Look up attributes of a member by userid.
210 userid - the UNIX user id
212 Returns: a dictionary of attributes
214 Example: get('mspang') -> {
216 'name': 'Michael Spang',
217 'program': 'Computer Science',
222 return db_connection.select_member_by_userid(userid)
225 def get_studentid(studentid):
227 Look up attributes of a member by studnetid.
230 studentid - the student ID number
232 Returns: a dictionary of attributes
234 Example: get(...) -> {
236 'name': 'Michael Spang',
237 'program': 'Computer Science',
242 return db_connection.select_member_by_studentid(studentid)
247 Build a list of members in a term.
250 term - the term to match members against
252 Returns: a list of member dictionaries
254 Example: list_term('f2006'): -> [
255 { 'memberid': 3349, ... },
261 # retrieve a list of memberids in term
262 memberlist = db_connection.select_members_by_term(term)
264 return memberlist.values()
269 Build a list of members with matching names.
272 name - the name to match members against
274 Returns: a list of member dictionaries
276 Example: list_name('Spang'): -> [
277 { 'memberid': 3349, ... },
283 # retrieve a list of memberids matching name
284 memberlist = db_connection.select_members_by_name(name)
286 return memberlist.values()
291 Builds a list of all members.
293 Returns: a list of member dictionaries
296 # retrieve a list of members
297 memberlist = db_connection.select_all_members()
299 return memberlist.values()
302 def delete(memberid):
304 Erase all records of a member.
306 Note: real members are never removed from the database
308 Returns: attributes and terms of the member in a tuple
311 NoSuchMember - if the member id does not exist
313 Example: delete(0) -> ({ 'memberid': 0, name: 'Calum T. Dalek' ...}, ['s1993'])
317 member = db_connection.select_member_by_id(memberid)
321 raise NoSuchMember(memberid)
323 term_list = db_connection.select_terms(memberid)
325 # remove data from the db
326 db_connection.delete_term_all(memberid)
327 db_connection.delete_member(memberid)
328 db_connection.commit()
330 # remove data from the directory
331 if member and member['userid']:
332 uid = member['userid']
333 ldap_connection.user_delete(uid)
335 return (member, term_list)
340 Update CSC member attributes.
343 member - a dictionary with member attributes as returned by get,
344 possibly omitting some attributes. member['memberid']
345 must exist and be valid. None is NULL.
348 NoSuchMember - if the member id does not exist
349 InvalidStudentID - if the student id number is malformed
350 DuplicateStudentID - if the student id number exists
352 Example: update( {'memberid': 3349, userid: 'mspang'} )
355 if member.has_key('studentid') and member['studentid'] is not None:
357 studentid = member['studentid']
359 # check the student id format
360 if studentid is not None and not re.match(cfg['studentid_regex'], str(studentid)):
361 raise InvalidStudentID(studentid)
363 # check for duplicate student id
364 dupmember = db_connection.select_member_by_studentid(studentid)
366 raise DuplicateStudentID(studentid)
368 # not specifying memberid is a bug
369 if not member.has_key('memberid'):
370 raise Exception("no member specified in call to update")
371 memberid = member['memberid']
373 # see if member exists
374 if not get(memberid):
375 raise NoSuchMember(memberid)
378 db_connection.update_member(member)
380 # commit the transaction
381 db_connection.commit()
387 def register(memberid, term_list):
389 Registers a member for one or more terms.
392 memberid - the member id number
393 term_list - the term to register for, or a list of terms
396 InvalidTerm - if a term is malformed
398 Example: register(3349, "w2007")
400 Example: register(3349, ["w2007", "s2007"])
403 if type(term_list) in (str, unicode):
404 term_list = [ term_list ]
407 db_member = get(memberid)
408 if db_member['userid']:
409 uid = db_member['userid']
410 ldap_member = ldap_connection.member_lookup(uid)
411 if ldap_member and 'term' not in ldap_member:
412 ldap_member['term'] = []
414 for term in term_list:
417 if not re.match('^[wsf][0-9]{4}$', term):
418 raise InvalidTerm(term)
420 # add term to database
421 db_connection.insert_term(memberid, term)
423 # add the term to the directory
425 ldap_member['term'].append(term)
428 ldap_connection.user_modify(uid, ldap_member)
430 db_connection.commit()
433 def registered(memberid, term):
435 Determines whether a member is registered
439 memberid - the member id number
440 term - the term to check
442 Returns: whether the member is registered
444 Example: registered(3349, "f2006") -> True
447 return db_connection.select_term(memberid, term) is not None
450 def member_terms(memberid):
452 Retrieves a list of terms a member is
456 memberid - the member id number
458 Returns: list of term strings
460 Example: registered(0) -> 's1993'
463 terms_list = db_connection.select_terms(memberid)
464 terms_list.sort(terms.compare)
471 if __name__ == '__main__':
473 from csc.common.test import *
475 # t=test m=member s=student u=updated
476 tmname = 'Test Member'
478 tmprogram = 'Metaphysics'
480 tm2name = 'Test Member 2'
481 tm2uid = 'testmember2'
483 tm2uname = 'Test Member II'
485 tm2uprogram = 'Pseudoscience'
487 tmdict = {'name': tmname, 'userid': tmuid, 'program': tmprogram, 'type': 'user', 'studentid': tmsid }
488 tm2dict = {'name': tm2name, 'userid': tm2uid, 'program': None, 'type': 'user', 'studentid': tm2sid }
489 tm2udict = {'name': tm2uname, 'userid': tm2uid, 'program': tm2uprogram, 'type': 'user', 'studentid': tm2usid }
491 thisterm = terms.current()
492 nextterm = terms.next(thisterm)
499 assert_equal(True, connected())
502 dmid = get_studentid(tmsid)
503 if dmid: delete(dmid['memberid'])
504 dmid = get_studentid(tm2sid)
505 if dmid: delete(dmid['memberid'])
506 dmid = get_studentid(tm2usid)
507 if dmid: delete(dmid['memberid'])
510 tmid = new(tmuid, tmname, tmsid, tmprogram)
511 tm2id = new(tm2uid, tm2name, tm2sid)
514 tmdict['memberid'] = tmid
515 tm2dict['memberid'] = tm2id
516 tm2udict['memberid'] = tm2id
519 assert_equal(True, registered(tmid, thisterm))
520 assert_equal(True, registered(tm2id, thisterm))
521 assert_equal(False, registered(tmid, nextterm))
525 assert_equal(tmdict, get(tmid))
526 assert_equal(tm2dict, get(tm2id))
530 assert_equal(True, tmid in [ x['memberid'] for x in list_name(tmname) ])
531 assert_equal(True, tm2id in [ x['memberid'] for x in list_name(tm2name) ])
535 allmembers = list_all()
536 assert_equal(True, tmid in [ x['memberid'] for x in allmembers ])
537 assert_equal(True, tm2id in [ x['memberid'] for x in allmembers ])
541 register(tmid, nextterm)
542 assert_equal(True, registered(tmid, nextterm))
546 assert_equal([thisterm, nextterm], member_terms(tmid))
547 assert_equal([thisterm], member_terms(tm2id))
551 assert_equal(True, tmid in [ x['memberid'] for x in list_term(thisterm) ])
552 assert_equal(True, tmid in [ x['memberid'] for x in list_term(nextterm) ])
553 assert_equal(True, tm2id in [ x['memberid'] for x in list_term(thisterm) ])
554 assert_equal(False, tm2id in [ x['memberid'] for x in list_term(nextterm) ])
559 assert_equal(tm2udict, get(tm2id))
563 assert_equal(tm2udict, get_userid(tm2uid))
567 assert_equal(tm2udict, get_studentid(tm2usid))
568 assert_equal(tmdict, get_studentid(tmsid))
578 assert_equal(False, connected())