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
15 from csc.common import conf
20 CONFIG_FILE = '/etc/csc/members.cf'
24 def load_configuration():
25 """Load Members Configuration"""
27 string_fields = [ 'studentid_regex', 'realname_regex', 'server',
28 'database', 'user', 'password' ]
30 # read configuration file
31 cfg_tmp = conf.read(CONFIG_FILE)
33 # verify configuration
34 conf.check_string_fields(CONFIG_FILE, string_fields, cfg_tmp)
36 # update the current configuration with the loaded values
43 DBException = db.DBException
44 ConfigurationException = conf.ConfigurationException
46 class MemberException(Exception):
47 """Base exception class for member-related errors."""
49 class DuplicateStudentID(MemberException):
50 """Exception class for student ID conflicts."""
51 def __init__(self, studentid):
52 self.studentid = studentid
54 return "Student ID already exists in the database: %s" % self.studentid
56 class InvalidStudentID(MemberException):
57 """Exception class for malformed student IDs."""
58 def __init__(self, studentid):
59 self.studentid = studentid
61 return "Student ID is invalid: %s" % self.studentid
63 class InvalidTerm(MemberException):
64 """Exception class for malformed terms."""
65 def __init__(self, term):
68 return "Term is invalid: %s" % self.term
70 class InvalidRealName(MemberException):
71 """Exception class for invalid real names."""
72 def __init__(self, name):
75 return "Name is invalid: %s" % self.name
77 class NoSuchMember(MemberException):
78 """Exception class for nonexistent members."""
79 def __init__(self, memberid):
80 self.memberid = memberid
82 return "Member not found: %d" % self.memberid
86 ### Connection Management ###
88 # global database connection
89 connection = db.DBConnection()
92 """Connect to PostgreSQL."""
95 connection.connect(cfg['server'], cfg['database'])
99 """Disconnect from PostgreSQL."""
101 connection.disconnect()
105 """Determine whether the connection has been established."""
107 return connection.connected()
113 def new(realname, studentid=None, program=None, mtype='user', userid=None):
115 Registers a new CSC member. The member is added to the members table
116 and registered for the current term.
119 realname - the full real name of the member
120 studentid - the student id number of the member
121 program - the program of study of the member
122 mtype - a string describing the type of member ('user', 'club')
123 userid - the initial user id
125 Returns: the memberid of the new member
128 DuplicateStudentID - if the student id already exists in the database
129 InvalidStudentID - if the student id is malformed
130 InvalidRealName - if the real name is malformed
132 Example: new("Michael Spang", program="CS") -> 3349
135 # blank attributes should be NULL
136 if studentid == '': studentid = None
137 if program == '': program = None
138 if userid == '': userid = None
139 if mtype == '': mtype = None
141 # check the student id format
142 if studentid is not None and not re.match(cfg['studentid_regex'], str(studentid)):
143 raise InvalidStudentID(studentid)
145 # check real name format (UNIX account real names must not contain [,:=])
146 if not re.match(cfg['realname_regex'], realname):
147 raise InvalidRealName(realname)
149 # check for duplicate student id
150 member = connection.select_member_by_studentid(studentid)
152 raise DuplicateStudentID(studentid)
155 memberid = connection.insert_member(realname, studentid, program)
157 # register them for this term
158 connection.insert_term(memberid, terms.current())
160 # commit the transaction
168 Look up attributes of a member by memberid.
170 Returns: a dictionary of attributes
172 Example: get(3349) -> {
174 'name': 'Michael Spang',
175 'program': 'Computer Science',
180 return connection.select_member_by_id(memberid)
183 def get_userid(userid):
185 Look up attributes of a member by userid.
188 userid - the UNIX user id
190 Returns: a dictionary of attributes
192 Example: get('mspang') -> {
194 'name': 'Michael Spang',
195 'program': 'Computer Science',
200 return connection.select_member_by_userid(userid)
203 def get_studentid(studentid):
205 Look up attributes of a member by studnetid.
208 studentid - the student ID number
210 Returns: a dictionary of attributes
212 Example: get(...) -> {
214 'name': 'Michael Spang',
215 'program': 'Computer Science',
220 return connection.select_member_by_studentid(studentid)
225 Build a list of members in a term.
228 term - the term to match members against
230 Returns: a list of member dictionaries
232 Example: list_term('f2006'): -> [
233 { 'memberid': 3349, ... },
239 # retrieve a list of memberids in term
240 memberlist = connection.select_members_by_term(term)
242 # convert the list of memberids to a list of dictionaries
243 memberlist = map(connection.select_member_by_id, memberlist)
250 Build a list of members with matching names.
253 name - the name to match members against
255 Returns: a list of member dictionaries
257 Example: list_name('Spang'): -> [
258 { 'memberid': 3349, ... },
264 # retrieve a list of memberids matching name
265 memberlist = connection.select_members_by_name(name)
267 # convert the list of memberids to a list of dictionaries
268 memberlist = map(connection.select_member_by_id, memberlist)
273 def delete(memberid):
275 Erase all records of a member.
277 Note: real members are never removed from the database
279 Returns: attributes and terms of the member in a tuple
282 NoSuchMember - if the member id does not exist
284 Example: delete(0) -> ({ 'memberid': 0, name: 'Calum T. Dalek' ...}, ['s1993'])
288 member = connection.select_member_by_id(memberid)
292 raise NoSuchMember(memberid)
294 term_list = connection.select_terms(memberid)
296 # remove data from the db
297 connection.delete_term_all(memberid)
298 connection.delete_member(memberid)
301 return (member, term_list)
306 Update CSC member attributes.
309 member - a dictionary with member attributes as returned by get,
310 possibly omitting some attributes. member['memberid']
311 must exist and be valid. None is NULL.
314 NoSuchMember - if the member id does not exist
315 InvalidStudentID - if the student id number is malformed
316 DuplicateStudentID - if the student id number exists
318 Example: update( {'memberid': 3349, userid: 'mspang'} )
321 if member.has_key('studentid') and member['studentid'] is not None:
323 studentid = member['studentid']
325 # check the student id format
326 if studentid is not None and not re.match(cfg['studentid_regex'], str(studentid)):
327 raise InvalidStudentID(studentid)
329 # check for duplicate student id
330 dupmember = connection.select_member_by_studentid(studentid)
332 raise DuplicateStudentID(studentid)
334 # not specifying memberid is a bug
335 if not member.has_key('memberid'):
336 raise Exception("no member specified in call to update")
337 memberid = member['memberid']
339 # see if member exists
340 if not get(memberid):
341 raise NoSuchMember(memberid)
344 connection.update_member(member)
346 # commit the transaction
353 def register(memberid, term_list):
355 Registers a member for one or more terms.
358 memberid - the member id number
359 term_list - the term to register for, or a list of terms
362 InvalidTerm - if a term is malformed
364 Example: register(3349, "w2007")
366 Example: register(3349, ["w2007", "s2007"])
369 if type(term_list) in (str, unicode):
370 term_list = [ term_list ]
372 for term in term_list:
375 if not re.match('^[wsf][0-9]{4}$', term):
376 raise InvalidTerm(term)
378 # add term to database
379 connection.insert_term(memberid, term)
384 def registered(memberid, term):
386 Determines whether a member is registered
390 memberid - the member id number
391 term - the term to check
393 Returns: whether the member is registered
395 Example: registered(3349, "f2006") -> True
398 return connection.select_term(memberid, term) is not None
401 def member_terms(memberid):
403 Retrieves a list of terms a member is
407 memberid - the member id number
409 Returns: list of term strings
411 Example: registered(0) -> 's1993'
414 terms_list = connection.select_terms(memberid)
415 terms_list.sort(terms.compare)
422 if __name__ == '__main__':
424 from csc.common.test import *
426 # t=test m=member s=student u=updated
427 tmname = 'Test Member'
428 tmprogram = 'Metaphysics'
430 tm2name = 'Test Member 2'
432 tm2uname = 'Test Member II'
434 tm2uprogram = 'Pseudoscience'
435 tm2uuserid = 'testmember'
437 tmdict = {'name': tmname, 'userid': None, 'program': tmprogram, 'type': 'user', 'studentid': tmsid }
438 tm2dict = {'name': tm2name, 'userid': None, 'program': None, 'type': 'user', 'studentid': tm2sid }
439 tm2udict = {'name': tm2uname, 'userid': tm2uuserid, 'program': tm2uprogram, 'type': 'user', 'studentid': tm2usid }
441 thisterm = terms.current()
442 nextterm = terms.next(thisterm)
449 assert_equal(True, connected())
452 dmid = get_studentid(tmsid)
453 if dmid: delete(dmid['memberid'])
454 dmid = get_studentid(tm2sid)
455 if dmid: delete(dmid['memberid'])
456 dmid = get_studentid(tm2usid)
457 if dmid: delete(dmid['memberid'])
460 tmid = new(tmname, tmsid, tmprogram)
461 tm2id = new(tm2name, tm2sid)
464 tmdict['memberid'] = tmid
465 tm2dict['memberid'] = tm2id
466 tm2udict['memberid'] = tm2id
469 assert_equal(True, registered(tmid, thisterm))
470 assert_equal(True, registered(tm2id, thisterm))
471 assert_equal(False, registered(tmid, nextterm))
475 assert_equal(tmdict, get(tmid))
476 assert_equal(tm2dict, get(tm2id))
480 assert_equal(True, tmid in [ x['memberid'] for x in list_name(tmname) ])
481 assert_equal(True, tm2id in [ x['memberid'] for x in list_name(tm2name) ])
485 register(tmid, terms.next(terms.current()))
486 assert_equal(True, registered(tmid, nextterm))
490 assert_equal([thisterm, nextterm], member_terms(tmid))
491 assert_equal([thisterm], member_terms(tm2id))
495 assert_equal(True, tmid in [ x['memberid'] for x in list_term(thisterm) ])
496 assert_equal(True, tmid in [ x['memberid'] for x in list_term(nextterm) ])
497 assert_equal(True, tm2id in [ x['memberid'] for x in list_term(thisterm) ])
498 assert_equal(False, tm2id in [ x['memberid'] for x in list_term(nextterm) ])
503 assert_equal(tm2udict, get(tm2id))
507 assert_equal(tm2udict, get_userid(tm2uuserid))
511 assert_equal(tm2udict, get_studentid(tm2usid))
512 assert_equal(tmdict, get_studentid(tmsid))
522 assert_equal(False, connected())