pyceo-broken/pylib/csc/adm/members.py

427 lines
10 KiB
Python

# $Id: members.py 44 2006-12-31 07:09:27Z mspang $
"""
CSC Member Management
This module contains functions for registering new members, registering
members for terms, searching for members, and other member-related
functions.
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
from csc.adm import terms
from csc.backends import db
from csc.common.conf import read_config
### Configuration
CONFIG_FILE = '/etc/csc/members.cf'
cfg = {}
def load_configuration():
"""Load Members Configuration"""
# configuration already loaded?
if len(cfg) > 0:
return
# read in the file
cfg_tmp = read_config(CONFIG_FILE)
if not cfg_tmp:
raise MemberException("unable to read configuration file: %s"
% CONFIG_FILE)
# check that essential fields are completed
mandatory_fields = [ 'server', 'database', 'user', 'password' ]
for field in mandatory_fields:
if not field in cfg_tmp:
raise MemberException("missing configuratino option: %s" % field)
if not cfg_tmp[field]:
raise MemberException("null configuration option: %s" %field)
# update the current configuration with the loaded values
cfg.update(cfg_tmp)
### Exceptions ###
class MemberException(Exception):
"""Exception class for member-related errors."""
class DuplicateStudentID(MemberException):
"""Exception class for student ID conflicts."""
pass
class InvalidStudentID(MemberException):
"""Exception class for malformed student IDs."""
pass
class InvalidTerm(MemberException):
"""Exception class for malformed terms."""
pass
class NoSuchMember(MemberException):
"""Exception class for nonexistent members."""
pass
### Connection Management ###
# global database connection
connection = db.DBConnection()
def connect():
"""Connect to PostgreSQL."""
load_configuration()
connection.connect(cfg['server'], cfg['database'])
def disconnect():
"""Disconnect from PostgreSQL."""
connection.disconnect()
def connected():
"""Determine whether the connection has been established."""
return connection.connected()
### Member Table ###
def new(realname, studentid=None, program=None):
"""
Registers a new CSC member. The member is added
to the members table and registered for the current
term.
Parameters:
realname - the full real name of the member
studentid - the student id number of the member
program - the program of study of the member
Returns: the memberid of the new member
Exceptions:
DuplicateStudentID - if the student id already exists in the database
InvalidStudentID - if the student id is malformed
Example: new("Michael Spang", program="CS") -> 3349
"""
# blank attributes should be NULL
if studentid == '': studentid = None
if program == '': program = None
# check the student id format
regex = '^[0-9]{8}$'
if studentid != None and not re.match(regex, str(studentid)):
raise InvalidStudentID("student id is invalid: %s" % studentid)
# check for duplicate student id
member = connection.select_member_by_studentid(studentid)
if member:
raise DuplicateStudentID("student id exists in database: %s" % studentid)
# add the member
memberid = connection.insert_member(realname, studentid, program)
# register them for this term
connection.insert_term(memberid, terms.current())
# commit the transaction
connection.commit()
return memberid
def get(memberid):
"""
Look up attributes of a member by memberid.
Parameters:
memberid - the member id number
Returns: a dictionary of attributes
Example: get(3349) -> {
'memberid': 3349,
'name': 'Michael Spang',
'program': 'Computer Science',
...
}
"""
return connection.select_member_by_id(memberid)
def get_userid(userid):
"""
Look up attributes of a member by userid.
Parameters:
userid - the UNIX user id
Returns: a dictionary of attributes
Example: get('mspang') -> {
'memberid': 3349,
'name': 'Michael Spang',
'program': 'Computer Science',
...
}
"""
return connection.select_member_by_account(userid)
def get_studentid(studentid):
"""
Look up attributes of a member by studnetid.
Parameters:
studentid - the student ID number
Returns: a dictionary of attributes
Example: get(...) -> {
'memberid': 3349,
'name': 'Michael Spang',
'program': 'Computer Science',
...
}
"""
return connection.select_member_by_studentid(studentid)
def list_term(term):
"""
Build a list of members in a term.
Parameters:
term - the term to match members against
Returns: a list of member dictionaries
Example: list_term('f2006'): -> [
{ 'memberid': 3349, ... },
{ 'memberid': ... }.
...
]
"""
# retrieve a list of memberids in term
memberlist = connection.select_members_by_term(term)
# convert the list of memberids to a list of dictionaries
memberlist = map(connection.select_member_by_id, memberlist)
return memberlist
def list_name(name):
"""
Build a list of members with matching names.
Parameters:
name - the name to match members against
Returns: a list of member dictionaries
Example: list_name('Spang'): -> [
{ 'memberid': 3349, ... },
{ 'memberid': ... },
...
]
"""
# retrieve a list of memberids matching name
memberlist = connection.select_members_by_name(name)
# convert the list of memberids to a list of dictionaries
memberlist = map(connection.select_member_by_id, memberlist)
return memberlist
def delete(memberid):
"""
Erase all records of a member.
Note: real members are never removed
from the database
Parameters:
memberid - the member id number
Returns: attributes and terms of the
member in a tuple
Example: delete(0) -> ({ 'memberid': 0, name: 'Calum T. Dalek' ...}, ['s1993'])
"""
# save member data
member = connection.select_member_by_id(memberid)
term_list = connection.select_terms(memberid)
# remove data from the db
connection.delete_term_all(memberid)
connection.delete_member(memberid)
connection.commit()
return (member, term_list)
def update(member):
"""
Update CSC member attributes. None is NULL.
Parameters:
member - a dictionary with member attributes as
returned by get, possibly omitting some
attributes. member['memberid'] must exist
and be valid.
Exceptions:
NoSuchMember - if the member id does not exist
InvalidStudentID - if the student id number is malformed
DuplicateStudentID - if the student id number exists
Example: update( {'memberid': 3349, userid: 'mspang'} )
"""
if member.has_key('studentid') and member['studentid'] != None:
studentid = member['studentid']
# check the student id format
regex = '^[0-9]{8}$'
if studentid != None and not re.match(regex, str(studentid)):
raise InvalidStudentID("student id is invalid: %s" % studentid)
# check for duplicate student id
member = connection.select_member_by_studentid(studentid)
if member:
raise DuplicateStudentID("student id exists in database: %s" %
studentid)
# not specifying memberid is a bug
if not member.has_key('memberid'):
raise Exception("no member specified in call to update")
memberid = member['memberid']
# see if member exists
old_member = connection.select_member_by_id(memberid)
if not old_member:
raise NoSuchMember("memberid does not exist in database: %d" %
memberid)
# do the update
connection.update_member(member)
# commit the transaction
connection.commit()
### Term Table ###
def register(memberid, term_list):
"""
Registers a member for one or more terms.
Parameters:
memberid - the member id number
term_list - the term to register for, or a list of terms
Exceptions:
InvalidTerm - if a term is malformed
Example: register(3349, "w2007")
Example: register(3349, ["w2007", "s2007"])
"""
if not type(term_list) in (list, tuple):
term_list = [ term_list ]
for term in term_list:
# check term syntax
if not re.match('^[wsf][0-9]{4}$', term):
raise InvalidTerm("term is invalid: %s" % term)
# add term to database
connection.insert_term(memberid, term)
connection.commit()
def registered(memberid, term):
"""
Determines whether a member is registered
for a term.
Parameters:
memberid - the member id number
term - the term to check
Returns: whether the member is registered
Example: registered(3349, "f2006") -> True
"""
return connection.select_term(memberid, term) != None
def terms_list(memberid):
"""
Retrieves a list of terms a member is
registered for.
Parameters:
memberid - the member id number
Returns: list of term strings
Example: registered(0) -> 's1993'
"""
return connection.select_terms(memberid)
### Tests ###
if __name__ == '__main__':
connect()
sid = new("Test User", "99999999", "CS")
assert registered(id, terms.current())
print get(sid)
register(sid, terms.next(terms.current()))
assert registered(sid, terms.next(terms.current()))
print terms_list(sid)
print get(sid)
print delete(sid)