New release (version 0.2).
Updates in this version: * Tests added to most Python modules. * Split configuration files. * Added maintainer scripts to manage permissions during install and purge. * Added functions for use by tools planned for next release (chfn, etc). ceo: * Added support for account "repair", which will recreate LDAP entries and Kerberos principals if necessary. * The recreate account menu option is now active. Miscellaneous: * Replaced instances of "== None" and "!= None" with "is None" and "is not None", respectively (thanks to: Nick Guenther). * Renamed terms.valid() to terms.validate() (thanks to: Nick Guenther).
This commit is contained in:
parent
cb59e85c2e
commit
58bf72726a
16
bin/ceo
16
bin/ceo
|
@ -1,22 +1,20 @@
|
|||
#!/usr/bin/python2.4 --
|
||||
|
||||
"""CEO SUID Python Wrapper Script"""
|
||||
import os, sys
|
||||
|
||||
safe_environment = ['LOGNAME', 'USERNAME', 'USER', 'HOME',
|
||||
'TERM', 'LANG', 'LC_ALL', 'LC_COLLATE',
|
||||
'LC_CTYPE', 'LC_MESSAGE', 'LC_MONETARY',
|
||||
'LC_NUMERIC', 'LC_TIME', 'UID', 'GID',
|
||||
'SSH_CONNECTION', 'SSH_AUTH_SOCK',
|
||||
'SSH_CLIENT']
|
||||
safe_environment = ['LOGNAME', 'USERNAME', 'USER', 'HOME', 'TERM', 'LANG'
|
||||
'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGE', 'LC_MONETARY',
|
||||
'LC_NUMERIC', 'LC_TIME', 'UID', 'GID', 'SSH_CONNECTION', 'SSH_AUTH_SOCK',
|
||||
'SSH_CLIENT']
|
||||
|
||||
for key in os.environ.keys():
|
||||
if not key in safe_environment:
|
||||
if key not in safe_environment:
|
||||
del os.environ[key]
|
||||
|
||||
os.environ['PATH'] = '/bin:/usr/bin'
|
||||
|
||||
for dir in sys.path[:]:
|
||||
if not dir.find('/usr') == 0 or dir.find('/usr/local') == 0:
|
||||
if not dir.find('/usr') == 0:
|
||||
while dir in sys.path:
|
||||
sys.path.remove(dir)
|
||||
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
csc (0.2) unstable; urgency=low
|
||||
|
||||
* Tests added to most Python modules.
|
||||
* Split configuration files.
|
||||
* Added maintainer scripts to manage permissions during install and purge.
|
||||
* Added functions for use by tools planned for next release (chfn, etc).
|
||||
* Added support for account "repair", which will recreate LDAP entries
|
||||
and principals if necessary.
|
||||
* The recreate account menu option in CEO is now active.
|
||||
* Replaced instances of "== None" and "!= None" with "is None" and
|
||||
"is not None", respectively (thanks to: Nick Guenther).
|
||||
* Renamed terms.valid() to terms.validate() (thanks to: Nick Guenther).
|
||||
|
||||
-- Michael Spang <mspang@uwaterloo.ca> Fri, 26 Jan 2007 20:10:14 -0500
|
||||
|
||||
csc (0.1) unstable; urgency=low
|
||||
|
||||
* Initial Release.
|
||||
|
|
|
@ -3,11 +3,11 @@ Section: admin
|
|||
Priority: optional
|
||||
Maintainer: Michael Spang <mspang@uwaterloo.ca>
|
||||
Build-Depends: debhelper (>= 4.0.0)
|
||||
Standards-Version: 3.6.1
|
||||
Standards-Version: 3.7.2
|
||||
|
||||
Package: csc
|
||||
Architecture: any
|
||||
Depends: python, python2.4, python2.4-ldap, python2.4-pygresql, krb5-user, less
|
||||
Depends: python, python2.4, python2.4-ldap, python2.4-pygresql, krb5-user, less, ${shlibs:Depends}
|
||||
Description: Computer Science Club Administrative Utilities
|
||||
This package contains the CSC Electronic Office
|
||||
and other Computer Science Club administrative
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
This package was debianized by mspang <mspang@uwaterloo.ca> on
|
||||
This package was debianized by Michael Spang <mspang@uwaterloo.ca> on
|
||||
Thu, 28 Dec 2006 04:07:03 -0500.
|
||||
|
||||
Copyright (c) 2006, 2007 Michael Spang
|
||||
Copyright (c) 2006-2007, Michael Spang
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
case "$1" in
|
||||
configure|upgrade)
|
||||
|
||||
if getent passwd ceo > /dev/null; then
|
||||
CEO=ceo
|
||||
SUID=4750
|
||||
else
|
||||
CEO=root
|
||||
SUID=755
|
||||
fi
|
||||
|
||||
if getent group office > /dev/null; then
|
||||
OFFICE=office
|
||||
else
|
||||
OFFICE=root
|
||||
fi
|
||||
|
||||
if ! dpkg-statoverride --list /usr/bin/ceo > /dev/null; then
|
||||
dpkg-statoverride --add --update $CEO $OFFICE $SUID /usr/bin/ceo
|
||||
fi
|
||||
|
||||
if [ -f /etc/csc/ldap.cf ] && ! dpkg-statoverride --list /etc/csc/ldap.cf > /dev/null; then
|
||||
dpkg-statoverride --add --update $CEO staff 640 /etc/csc/ldap.cf
|
||||
fi
|
||||
|
||||
if [ ! -e /etc/csc/ceo.keytab ] && [ -x /usr/sbin/kadmin.local ]; then
|
||||
if dpkg-statoverride --list /etc/csc/ceo.keytab > /dev/null; then
|
||||
dpkg-statoverride --remove /etc/csc/ceo.keytab || true
|
||||
fi
|
||||
echo 'warning: re-creating ceo.keytab'
|
||||
echo 'ktadd -k /etc/csc/ceo.keytab ceo/admin' | /usr/sbin/kadmin.local || true
|
||||
if [ -e /etc/csc/ceo.keytab ]; then
|
||||
echo -e "\nSuccess!"
|
||||
else
|
||||
echo -e "\nFailed!"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f /etc/csc/ceo.keytab ] && ! dpkg-statoverride --list /etc/csc/ceo.keytab > /dev/null; then
|
||||
dpkg-statoverride --add --update $CEO staff 640 /etc/csc/ceo.keytab
|
||||
fi
|
||||
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \"$1\"" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
case "$1" in
|
||||
purge)
|
||||
|
||||
if dpkg-statoverride --list /usr/bin/ceo > /dev/null; then
|
||||
dpkg-statoverride --remove /usr/bin/ceo || true
|
||||
fi
|
||||
|
||||
if dpkg-statoverride --list /etc/csc/ldap.cf > /dev/null; then
|
||||
dpkg-statoverride --remove /etc/csc/ldap.cf || true
|
||||
fi
|
||||
|
||||
if dpkg-statoverride --list /etc/csc/ceo.keytab > /dev/null; then
|
||||
dpkg-statoverride --remove /etc/csc/ceo.keytab || true
|
||||
fi
|
||||
|
||||
rmdir --ignore-fail-on-non-empty /etc/csc
|
||||
|
||||
;;
|
||||
|
||||
remove|failed-upgrade|upgrade)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postrm called with invalid argument \"$1\"" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
|
@ -2,13 +2,11 @@
|
|||
|
||||
PYTHON := python2.4
|
||||
|
||||
configure:
|
||||
|
||||
build: build-stamp
|
||||
|
||||
build-stamp:
|
||||
mkdir build
|
||||
$(CC) -DFULL_PATH=\"/usr/lib/csc/ceo\" -o build/ceo misc/setuid-prog.c
|
||||
$(CC) -DFULL_PATH='"/usr/lib/csc/ceo"' -o build/ceo misc/setuid-prog.c
|
||||
touch build-stamp
|
||||
|
||||
clean:
|
||||
|
@ -17,27 +15,22 @@ clean:
|
|||
dh_clean
|
||||
rm -f build-stamp
|
||||
rm -rf build/
|
||||
find pylib/ -name '*.pyc' -print0 | xargs -0 rm -f
|
||||
find pylib/ -name "*.pyc" -print0 | xargs -0 rm -f
|
||||
|
||||
install: build
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_clean -k
|
||||
|
||||
# configuration files will contain sensitive information
|
||||
chmod 600 etc/*
|
||||
|
||||
dh_installdirs etc/csc usr/lib/$(PYTHON)/site-packages usr/share/csc \
|
||||
usr/lib/csc usr/bin
|
||||
dh_install -X.svn -X.pyc pylib/csc usr/lib/$(PYTHON)/site-packages/
|
||||
dh_install -X.svn -X.pyc etc/* etc/csc/
|
||||
dh_install -X.svn -X.pyc sql/* usr/share/csc/
|
||||
dh_install pylib/* usr/lib/$(PYTHON)/site-packages/
|
||||
dh_install etc/* etc/csc/
|
||||
dh_install sql/* usr/share/csc/
|
||||
|
||||
dh_install -X.svn -X.pyc bin/ceo usr/lib/csc/
|
||||
dh_install -X.svn -X.pyc build/ceo usr/bin/
|
||||
dh_install bin/ceo usr/lib/csc/
|
||||
dh_install build/ceo usr/bin/
|
||||
|
||||
|
||||
binary-indep: build install
|
||||
binary-arch: build install
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_installchangelogs
|
||||
|
@ -60,7 +53,8 @@ binary-indep: build install
|
|||
dh_md5sums
|
||||
dh_builddeb
|
||||
|
||||
binary: binary-indep binary-arch
|
||||
.PHONY: build clean binary-indep binary-arch binary install configure
|
||||
binary-indep:
|
||||
|
||||
binary-arch: build install
|
||||
binary: binary-indep binary-arch
|
||||
|
||||
.PHONY: build clean binary-indep binary-arch binary install
|
||||
|
|
|
@ -3,6 +3,6 @@ Bugs and Caveats
|
|||
================
|
||||
|
||||
CEO:
|
||||
- curses does not draw borders/lines correctly in a screen session
|
||||
- windows don't always clear properly
|
||||
- the menu is not redrawn between windows and therefore a gap may grow there
|
||||
- curses does not draw borders/lines correctly in a screen session. screen apparently ignores
|
||||
some font-changing characters. workaround should be possible (other progs work).
|
||||
- the menu is not redrawn between windows and therefore a gap tends to grow there
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
TODO:
|
||||
|
||||
* Python bindings for libkadm5
|
||||
* Python bindings for quota?
|
||||
* New UI: urwid-based?
|
||||
* Logging via syslog
|
||||
* Try to recover and roll-back on error during account creation
|
||||
* Write manpages
|
|
@ -1,35 +1,44 @@
|
|||
# $Id: accounts.cf 45 2007-01-02 01:39:10Z mspang $
|
||||
# CSC Accounts Configuration
|
||||
# /etc/csc/accounts.cf: CSC Accounts Configuration
|
||||
|
||||
### Account Options ###
|
||||
include /etc/csc/ldap.cf
|
||||
include /etc/csc/kerberos.cf
|
||||
|
||||
minimum_id = 20000
|
||||
maximum_id = 40000
|
||||
### Member Account Options ###
|
||||
|
||||
shell = "/bin/bash"
|
||||
home = "/users"
|
||||
gid = 100
|
||||
member_min_id = 20000
|
||||
member_max_id = 39999
|
||||
member_shell = "/bin/bash"
|
||||
member_home = "/users"
|
||||
member_desc = "CSC Member Account"
|
||||
member_group = "users"
|
||||
|
||||
### Club Account Options ###
|
||||
|
||||
### LDAP Configuration ###
|
||||
club_min_id = 15000
|
||||
club_max_id = 19999
|
||||
club_shell = "/bin/bash"
|
||||
club_home = "/users"
|
||||
club_desc = "CSC Club Account"
|
||||
club_group = "users"
|
||||
|
||||
server_url = "ldap:///"
|
||||
### Administrative Account Options
|
||||
|
||||
users_base = "ou=People,dc=csclub,dc=uwaterloo,dc=ca"
|
||||
groups_base = "ou=Group,dc=csclub,dc=uwaterloo,dc=ca"
|
||||
admin_min_id = 10000
|
||||
admin_max_id = 14999
|
||||
admin_shell = "/bin/bash"
|
||||
admin_home = "/users"
|
||||
admin_desc = "CSC Administrative Account"
|
||||
admin_group = "users"
|
||||
|
||||
bind_dn = "cn=ceo,dc=csclub,dc=uwaterloo,dc=ca"
|
||||
bind_password = "secret"
|
||||
|
||||
|
||||
### Kerberos Configuration ###
|
||||
|
||||
realm = "CSCLUB.UWATERLOO.CA"
|
||||
principal = "ceo/admin@CSCLUB.UWATERLOO.CA"
|
||||
keytab = "/etc/csc/ceo.keytab"
|
||||
### Account Group Options ###
|
||||
|
||||
group_min_id = 10000
|
||||
group_max_id = 14999
|
||||
group_desc = "CSC Group"
|
||||
|
||||
### Validation Tuning ###
|
||||
|
||||
username_regex = "^[a-z][-a-z0-9]*$"
|
||||
realname_regex = "^[^,:=]*$"
|
||||
groupname_regex = "^[a-z][-a-z0-9]*$"
|
||||
min_password_length = 4
|
||||
shells_file = "/etc/shells"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# /etc/csc/kerberos.cf: CSC Kerberos Administration Configuration
|
||||
|
||||
realm = "CSCLUB.UWATERLOO.CA"
|
||||
admin_principal = "ceo/admin@CSCLUB.UWATERLOO.CA"
|
||||
admin_keytab = "/etc/csc/ceo.keytab"
|
|
@ -0,0 +1,9 @@
|
|||
# /etc/csc/ldap.cf: CSC LDAP Configuration
|
||||
|
||||
server_url = "ldaps:///"
|
||||
|
||||
users_base = "ou=People,dc=csclub,dc=uwaterloo,dc=ca"
|
||||
groups_base = "ou=Group,dc=csclub,dc=uwaterloo,dc=ca"
|
||||
|
||||
admin_bind_dn = "cn=ceo,dc=csclub,dc=uwaterloo,dc=ca"
|
||||
admin_bind_pw = "secret"
|
|
@ -1,13 +1,6 @@
|
|||
# $Id: members.cf 45 2007-01-02 01:39:10Z mspang $
|
||||
# CSC Members Configuration
|
||||
# /etc/csc/members.cf: CSC Members Configuration
|
||||
|
||||
### Database Configuration ###
|
||||
|
||||
server = "localhost"
|
||||
database = "ceo"
|
||||
|
||||
user = "ceo"
|
||||
password = "secret"
|
||||
include /etc/csc/pgsql.cf
|
||||
|
||||
### Validation Tuning ###
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# /etc/csc/pgsql.cf: PostgreSQL database configuration
|
||||
|
||||
### Database Configuration ###
|
||||
|
||||
# server = "localhost"
|
||||
server = ""
|
||||
database = "ceo"
|
||||
|
||||
# not used
|
||||
user = "ceo"
|
||||
password = "secret"
|
|
@ -1,19 +1,5 @@
|
|||
# $Id: __init__.py 24 2006-12-18 20:23:12Z mspang $
|
||||
"""
|
||||
PyCSC - CSC Administrative Utilities
|
||||
|
||||
Member Management:
|
||||
|
||||
ceo - legacy ceo interface
|
||||
|
||||
Account Management:
|
||||
|
||||
ceo - legacy ceo interface
|
||||
|
||||
Modules:
|
||||
|
||||
admin - administrative code (member and account management)
|
||||
backend - backend interface code
|
||||
ui - user interface code
|
||||
Computer Science Club Python Modules
|
||||
|
||||
The csc module is a container for all CSC-specific Python modules.
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
"""
|
||||
CSC Administrative Modules
|
||||
|
||||
This module provides member and account management modules.
|
||||
|
||||
members - member registration management functions
|
||||
accounts - account administration functions
|
||||
terms - helper routines for manipulating terms
|
||||
"""
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,3 @@
|
|||
# $Id: members.py 44 2006-12-31 07:09:27Z mspang $
|
||||
"""
|
||||
CSC Member Management
|
||||
|
||||
|
@ -10,44 +9,29 @@ 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
|
||||
from csc.common import conf
|
||||
|
||||
|
||||
|
||||
|
||||
### Configuration
|
||||
### Configuration ###
|
||||
|
||||
CONFIG_FILE = '/etc/csc/members.cf'
|
||||
|
||||
cfg = {}
|
||||
|
||||
|
||||
def load_configuration():
|
||||
"""Load Members Configuration"""
|
||||
|
||||
# configuration already loaded?
|
||||
if len(cfg) > 0:
|
||||
return
|
||||
string_fields = [ 'studentid_regex', 'realname_regex', 'server',
|
||||
'database', 'user', 'password' ]
|
||||
|
||||
# read in the file
|
||||
cfg_tmp = read_config(CONFIG_FILE)
|
||||
# read configuration file
|
||||
cfg_tmp = conf.read(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)
|
||||
# verify configuration
|
||||
conf.check_string_fields(CONFIG_FILE, string_fields, cfg_tmp)
|
||||
|
||||
# update the current configuration with the loaded values
|
||||
cfg.update(cfg_tmp)
|
||||
|
@ -56,24 +40,46 @@ def load_configuration():
|
|||
|
||||
### Exceptions ###
|
||||
|
||||
DBException = db.DBException
|
||||
ConfigurationException = conf.ConfigurationException
|
||||
|
||||
class MemberException(Exception):
|
||||
"""Exception class for member-related errors."""
|
||||
"""Base exception class for member-related errors."""
|
||||
|
||||
class DuplicateStudentID(MemberException):
|
||||
"""Exception class for student ID conflicts."""
|
||||
pass
|
||||
def __init__(self, studentid):
|
||||
self.studentid = studentid
|
||||
def __str__(self):
|
||||
return "Student ID already exists in the database: %s" % self.studentid
|
||||
|
||||
class InvalidStudentID(MemberException):
|
||||
"""Exception class for malformed student IDs."""
|
||||
pass
|
||||
def __init__(self, studentid):
|
||||
self.studentid = studentid
|
||||
def __str__(self):
|
||||
return "Student ID is invalid: %s" % self.studentid
|
||||
|
||||
class InvalidTerm(MemberException):
|
||||
"""Exception class for malformed terms."""
|
||||
pass
|
||||
def __init__(self, term):
|
||||
self.term = term
|
||||
def __str__(self):
|
||||
return "Term is invalid: %s" % self.term
|
||||
|
||||
class InvalidRealName(MemberException):
|
||||
"""Exception class for invalid real names."""
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
def __str__(self):
|
||||
return "Name is invalid: %s" % self.name
|
||||
|
||||
class NoSuchMember(MemberException):
|
||||
"""Exception class for nonexistent members."""
|
||||
pass
|
||||
def __init__(self, memberid):
|
||||
self.memberid = memberid
|
||||
def __str__(self):
|
||||
return "Member not found: %d" % self.memberid
|
||||
|
||||
|
||||
|
||||
|
@ -82,12 +88,10 @@ class NoSuchMember(MemberException):
|
|||
# global database connection
|
||||
connection = db.DBConnection()
|
||||
|
||||
|
||||
def connect():
|
||||
"""Connect to PostgreSQL."""
|
||||
|
||||
load_configuration()
|
||||
|
||||
connection.connect(cfg['server'], cfg['database'])
|
||||
|
||||
|
||||
|
@ -103,24 +107,27 @@ def connected():
|
|||
return connection.connected()
|
||||
|
||||
|
||||
|
||||
### Member Table ###
|
||||
|
||||
def new(realname, studentid=None, program=None):
|
||||
def new(realname, studentid=None, program=None, mtype='user', userid=None):
|
||||
"""
|
||||
Registers a new CSC member. The member is added
|
||||
to the members table and registered for the current
|
||||
term.
|
||||
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
|
||||
mtype - a string describing the type of member ('user', 'club')
|
||||
userid - the initial user id
|
||||
|
||||
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
|
||||
InvalidRealName - if the real name is malformed
|
||||
|
||||
Example: new("Michael Spang", program="CS") -> 3349
|
||||
"""
|
||||
|
@ -128,16 +135,21 @@ def new(realname, studentid=None, program=None):
|
|||
# blank attributes should be NULL
|
||||
if studentid == '': studentid = None
|
||||
if program == '': program = None
|
||||
if userid == '': userid = None
|
||||
if mtype == '': mtype = 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)
|
||||
if studentid is not None and not re.match(cfg['studentid_regex'], str(studentid)):
|
||||
raise InvalidStudentID(studentid)
|
||||
|
||||
# check real name format (UNIX account real names must not contain [,:=])
|
||||
if not re.match(cfg['realname_regex'], realname):
|
||||
raise InvalidRealName(realname)
|
||||
|
||||
# check for duplicate student id
|
||||
member = connection.select_member_by_studentid(studentid)
|
||||
if member:
|
||||
raise DuplicateStudentID("student id exists in database: %s" % studentid)
|
||||
raise DuplicateStudentID(studentid)
|
||||
|
||||
# add the member
|
||||
memberid = connection.insert_member(realname, studentid, program)
|
||||
|
@ -155,9 +167,6 @@ 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) -> {
|
||||
|
@ -188,7 +197,7 @@ def get_userid(userid):
|
|||
}
|
||||
"""
|
||||
|
||||
return connection.select_member_by_account(userid)
|
||||
return connection.select_member_by_userid(userid)
|
||||
|
||||
|
||||
def get_studentid(studentid):
|
||||
|
@ -265,20 +274,23 @@ def delete(memberid):
|
|||
"""
|
||||
Erase all records of a member.
|
||||
|
||||
Note: real members are never removed
|
||||
from the database
|
||||
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
|
||||
|
||||
Returns: attributes and terms of the
|
||||
member in a tuple
|
||||
Exceptions:
|
||||
NoSuchMember - if the member id does not exist
|
||||
|
||||
Example: delete(0) -> ({ 'memberid': 0, name: 'Calum T. Dalek' ...}, ['s1993'])
|
||||
"""
|
||||
|
||||
# save member data
|
||||
member = connection.select_member_by_id(memberid)
|
||||
|
||||
# bail if not found
|
||||
if not member:
|
||||
raise NoSuchMember(memberid)
|
||||
|
||||
term_list = connection.select_terms(memberid)
|
||||
|
||||
# remove data from the db
|
||||
|
@ -291,13 +303,12 @@ def delete(memberid):
|
|||
|
||||
def update(member):
|
||||
"""
|
||||
Update CSC member attributes. None is NULL.
|
||||
Update CSC member attributes.
|
||||
|
||||
Parameters:
|
||||
member - a dictionary with member attributes as
|
||||
returned by get, possibly omitting some
|
||||
attributes. member['memberid'] must exist
|
||||
and be valid.
|
||||
member - a dictionary with member attributes as returned by get,
|
||||
possibly omitting some attributes. member['memberid']
|
||||
must exist and be valid. None is NULL.
|
||||
|
||||
Exceptions:
|
||||
NoSuchMember - if the member id does not exist
|
||||
|
@ -307,20 +318,18 @@ def update(member):
|
|||
Example: update( {'memberid': 3349, userid: 'mspang'} )
|
||||
"""
|
||||
|
||||
if member.has_key('studentid') and member['studentid'] != None:
|
||||
if member.has_key('studentid') and member['studentid'] is not 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)
|
||||
if studentid is not None and not re.match(cfg['studentid_regex'], str(studentid)):
|
||||
raise InvalidStudentID(studentid)
|
||||
|
||||
# check for duplicate student id
|
||||
member = connection.select_member_by_studentid(studentid)
|
||||
if member:
|
||||
raise DuplicateStudentID("student id exists in database: %s" %
|
||||
studentid)
|
||||
dupmember = connection.select_member_by_studentid(studentid)
|
||||
if dupmember:
|
||||
raise DuplicateStudentID(studentid)
|
||||
|
||||
# not specifying memberid is a bug
|
||||
if not member.has_key('memberid'):
|
||||
|
@ -328,10 +337,8 @@ def update(member):
|
|||
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)
|
||||
if not get(memberid):
|
||||
raise NoSuchMember(memberid)
|
||||
|
||||
# do the update
|
||||
connection.update_member(member)
|
||||
|
@ -359,14 +366,14 @@ def register(memberid, term_list):
|
|||
Example: register(3349, ["w2007", "s2007"])
|
||||
"""
|
||||
|
||||
if not type(term_list) in (list, tuple):
|
||||
if type(term_list) in (str, unicode):
|
||||
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)
|
||||
raise InvalidTerm(term)
|
||||
|
||||
# add term to database
|
||||
connection.insert_term(memberid, term)
|
||||
|
@ -388,10 +395,10 @@ def registered(memberid, term):
|
|||
Example: registered(3349, "f2006") -> True
|
||||
"""
|
||||
|
||||
return connection.select_term(memberid, term) != None
|
||||
return connection.select_term(memberid, term) is not None
|
||||
|
||||
|
||||
def terms_list(memberid):
|
||||
def member_terms(memberid):
|
||||
"""
|
||||
Retrieves a list of terms a member is
|
||||
registered for.
|
||||
|
@ -404,7 +411,9 @@ def terms_list(memberid):
|
|||
Example: registered(0) -> 's1993'
|
||||
"""
|
||||
|
||||
return connection.select_terms(memberid)
|
||||
terms_list = connection.select_terms(memberid)
|
||||
terms_list.sort(terms.compare)
|
||||
return terms_list
|
||||
|
||||
|
||||
|
||||
|
@ -412,15 +421,104 @@ def terms_list(memberid):
|
|||
|
||||
if __name__ == '__main__':
|
||||
|
||||
connect()
|
||||
|
||||
|
||||
sid = new("Test User", "99999999", "CS")
|
||||
from csc.common.test import *
|
||||
|
||||
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)
|
||||
# t=test m=member s=student u=updated
|
||||
tmname = 'Test Member'
|
||||
tmprogram = 'Metaphysics'
|
||||
tmsid = '00000000'
|
||||
tm2name = 'Test Member 2'
|
||||
tm2sid = '00000001'
|
||||
tm2uname = 'Test Member II'
|
||||
tm2usid = '00000002'
|
||||
tm2uprogram = 'Pseudoscience'
|
||||
tm2uuserid = 'testmember'
|
||||
|
||||
tmdict = {'name': tmname, 'userid': None, 'program': tmprogram, 'type': 'user', 'studentid': tmsid }
|
||||
tm2dict = {'name': tm2name, 'userid': None, 'program': None, 'type': 'user', 'studentid': tm2sid }
|
||||
tm2udict = {'name': tm2uname, 'userid': tm2uuserid, 'program': tm2uprogram, 'type': 'user', 'studentid': tm2usid }
|
||||
|
||||
thisterm = terms.current()
|
||||
nextterm = terms.next(thisterm)
|
||||
|
||||
test(connect)
|
||||
connect()
|
||||
success()
|
||||
|
||||
test(connected)
|
||||
assert_equal(True, connected())
|
||||
success()
|
||||
|
||||
dmid = get_studentid(tmsid)
|
||||
if dmid: delete(dmid['memberid'])
|
||||
dmid = get_studentid(tm2sid)
|
||||
if dmid: delete(dmid['memberid'])
|
||||
dmid = get_studentid(tm2usid)
|
||||
if dmid: delete(dmid['memberid'])
|
||||
|
||||
test(new)
|
||||
tmid = new(tmname, tmsid, tmprogram)
|
||||
tm2id = new(tm2name, tm2sid)
|
||||
success()
|
||||
|
||||
tmdict['memberid'] = tmid
|
||||
tm2dict['memberid'] = tm2id
|
||||
tm2udict['memberid'] = tm2id
|
||||
|
||||
test(registered)
|
||||
assert_equal(True, registered(tmid, thisterm))
|
||||
assert_equal(True, registered(tm2id, thisterm))
|
||||
assert_equal(False, registered(tmid, nextterm))
|
||||
success()
|
||||
|
||||
test(get)
|
||||
assert_equal(tmdict, get(tmid))
|
||||
assert_equal(tm2dict, get(tm2id))
|
||||
success()
|
||||
|
||||
test(list_name)
|
||||
assert_equal(True, tmid in [ x['memberid'] for x in list_name(tmname) ])
|
||||
assert_equal(True, tm2id in [ x['memberid'] for x in list_name(tm2name) ])
|
||||
success()
|
||||
|
||||
test(register)
|
||||
register(tmid, terms.next(terms.current()))
|
||||
assert_equal(True, registered(tmid, nextterm))
|
||||
success()
|
||||
|
||||
test(member_terms)
|
||||
assert_equal([thisterm, nextterm], member_terms(tmid))
|
||||
assert_equal([thisterm], member_terms(tm2id))
|
||||
success()
|
||||
|
||||
test(list_term)
|
||||
assert_equal(True, tmid in [ x['memberid'] for x in list_term(thisterm) ])
|
||||
assert_equal(True, tmid in [ x['memberid'] for x in list_term(nextterm) ])
|
||||
assert_equal(True, tm2id in [ x['memberid'] for x in list_term(thisterm) ])
|
||||
assert_equal(False, tm2id in [ x['memberid'] for x in list_term(nextterm) ])
|
||||
success()
|
||||
|
||||
test(update)
|
||||
update(tm2udict)
|
||||
assert_equal(tm2udict, get(tm2id))
|
||||
success()
|
||||
|
||||
test(get_userid)
|
||||
assert_equal(tm2udict, get_userid(tm2uuserid))
|
||||
success()
|
||||
|
||||
test(get_studentid)
|
||||
assert_equal(tm2udict, get_studentid(tm2usid))
|
||||
assert_equal(tmdict, get_studentid(tmsid))
|
||||
success()
|
||||
|
||||
test(delete)
|
||||
delete(tmid)
|
||||
delete(tm2id)
|
||||
success()
|
||||
|
||||
test(disconnect)
|
||||
disconnect()
|
||||
assert_equal(False, connected())
|
||||
disconnect()
|
||||
success()
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
# $Id: terms.py 44 2006-12-31 07:09:27Z mspang $
|
||||
"""
|
||||
Terms Routines
|
||||
|
||||
This module contains functions for manipulating
|
||||
terms, such as determining the current term,
|
||||
finding the next or previous term, converting
|
||||
dates to terms, and more.
|
||||
This module contains functions for manipulating terms, such as determining
|
||||
the current term, finding the next or previous term, converting dates to
|
||||
terms, and more.
|
||||
"""
|
||||
import time, datetime, re
|
||||
|
||||
|
@ -16,27 +14,27 @@ EPOCH = 1970
|
|||
SEASONS = [ 'w', 's', 'f' ]
|
||||
|
||||
|
||||
def valid(term):
|
||||
def validate(term):
|
||||
"""
|
||||
Determines whether a term is well-formed:
|
||||
Determines whether a term is well-formed.
|
||||
|
||||
Parameters:
|
||||
term - the term string
|
||||
|
||||
Returns: whether the term is valid (boolean)
|
||||
|
||||
Example: valid("f2006") -> True
|
||||
Example: validate("f2006") -> True
|
||||
"""
|
||||
|
||||
regex = '^[wsf][0-9]{4}$'
|
||||
return re.match(regex, term) != None
|
||||
return re.match(regex, term) is not None
|
||||
|
||||
|
||||
def parse(term):
|
||||
"""Helper function to convert a term string to the number of terms
|
||||
since the epoch. Such numbers are intended for internal use only."""
|
||||
|
||||
if not valid(term):
|
||||
if not validate(term):
|
||||
raise Exception("malformed term: %s" % term)
|
||||
|
||||
year = int( term[1:] )
|
||||
|
@ -176,8 +174,8 @@ def from_timestamp(timestamp):
|
|||
|
||||
This function notes that:
|
||||
WINTER = JANUARY to APRIL
|
||||
SPRING = MAY TO AUGUST
|
||||
FALL = SEPTEMBER TO DECEMBER
|
||||
SPRING = MAY to AUGUST
|
||||
FALL = SEPTEMBER to DECEMBER
|
||||
|
||||
Parameters:
|
||||
timestamp - number of seconds since the epoch
|
||||
|
@ -235,18 +233,22 @@ def next_unregistered(registered):
|
|||
|
||||
if __name__ == '__main__':
|
||||
|
||||
assert parse('f2006') == 110
|
||||
assert generate(110) == 'f2006'
|
||||
assert next('f2006') == 'w2007'
|
||||
assert previous('f2006') == 's2006'
|
||||
assert delta('f2006', 'w2007') == 1
|
||||
assert add('f2006', delta('f2006', 'w2010')) == 'w2010'
|
||||
assert interval('f2006', 3) == ['f2006', 'w2007', 's2007']
|
||||
assert from_timestamp(1166135779) == 'f2006'
|
||||
assert parse( current() ) >= 110
|
||||
assert next_unregistered( [current()] ) == next( current() )
|
||||
assert next_unregistered( [] ) == current()
|
||||
assert next_unregistered( [previous(current())] ) == current()
|
||||
assert next_unregistered( [add(current(), -2)] ) == current()
|
||||
from csc.common.test import *
|
||||
|
||||
print "All tests passed." "\n"
|
||||
test(parse); assert_equal(110, parse('f2006')); success()
|
||||
test(generate); assert_equal('f2006', generate(110)); success()
|
||||
test(next); assert_equal('w2007', next('f2006')); success()
|
||||
test(previous); assert_equal('s2006', previous('f2006')); success()
|
||||
test(delta); assert_equal(1, delta('f2006', 'w2007')); success()
|
||||
test(compare); assert_equal(-1, compare('f2006', 'w2007')); success()
|
||||
test(add); assert_equal('w2010', add('f2006', delta('f2006', 'w2010'))); success()
|
||||
test(interval); assert_equal(['f2006', 'w2007', 's2007'], interval('f2006', 3)); success()
|
||||
test(from_timestamp); assert_equal('f2006', from_timestamp(1166135779)); success()
|
||||
test(current); assert_equal(True, parse( current() ) >= 110 ); success()
|
||||
|
||||
test(next_unregistered)
|
||||
assert_equal( next(current()), next_unregistered([ current() ]))
|
||||
assert_equal( current(), next_unregistered([]))
|
||||
assert_equal( current(), next_unregistered([ previous(current()) ]))
|
||||
assert_equal( current(), next_unregistered([ add(current(), -2) ]))
|
||||
success()
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
# $Id: __init__.py 23 2006-12-18 20:14:51Z mspang $
|
||||
"""
|
||||
User Interfaces
|
||||
Application-style User Interfaces
|
||||
|
||||
This module contains frontends and related modules.
|
||||
CEO's primary frontends are:
|
||||
This module contains large frontends with many functions
|
||||
and fancy graphical user interfaces.
|
||||
|
||||
legacy - aims to reproduce the curses UI of the previous CEO
|
||||
"""
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
# $Id: __init__.py 23 2006-12-18 20:14:51Z mspang $
|
||||
"""
|
||||
Legacy User Interface
|
||||
|
||||
This module contains the legacy CEO user interface and related modules.
|
||||
Important modules are:
|
||||
|
||||
main.py - all of the main UI logic
|
||||
helpers.py - user interface library routines
|
||||
main - all of the main UI logic
|
||||
helpers - user interface library routines
|
||||
"""
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# $Id: helpers.py 35 2006-12-28 05:14:05Z mspang $
|
||||
"""
|
||||
Helpers for legacy User Interface
|
||||
|
||||
|
@ -7,7 +6,7 @@ the look and behavior of the previous CEO. Included is code for various
|
|||
curses-based UI widgets that were provided by Perl 5's Curses and
|
||||
Curses::Widgets libraries.
|
||||
|
||||
Though attempts have been made to keep the UI bug-compatible with
|
||||
Though attempts have been made to keep the UI [bug-]compatible with
|
||||
the previous system, some compromises have been made. For example,
|
||||
the input and textboxes draw 'OK' and 'Cancel' buttons where the old
|
||||
CEO had them, but they are fake. That is, the buttons in the old
|
||||
|
@ -52,14 +51,14 @@ def read_input(wnd, offy, offx, width, maxlen, echo=True):
|
|||
# turn on cursor
|
||||
try:
|
||||
curses.curs_set(1)
|
||||
except:
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
# set keypad mode to allow UP, DOWN, etc
|
||||
wnd.keypad(1)
|
||||
|
||||
# the input string
|
||||
input = ""
|
||||
inputbuf = ""
|
||||
|
||||
# offset of cursor in input
|
||||
# i.e. the next operation is applied at input[inputoff]
|
||||
|
@ -78,7 +77,7 @@ def read_input(wnd, offy, offx, width, maxlen, echo=True):
|
|||
if echo:
|
||||
# discard characters before displayoff,
|
||||
# as the window may be scrolled to the right
|
||||
substring = input[displayoff:]
|
||||
substring = inputbuf[displayoff:]
|
||||
|
||||
# pad the string with zeroes to overwrite stale characters
|
||||
substring = substring + " " * (width - len(substring))
|
||||
|
@ -96,7 +95,7 @@ def read_input(wnd, offy, offx, width, maxlen, echo=True):
|
|||
|
||||
# enter returns input
|
||||
if key == KEY_RETURN:
|
||||
return input
|
||||
return inputbuf
|
||||
|
||||
# escape aborts input
|
||||
elif key == KEY_ESCAPE:
|
||||
|
@ -104,7 +103,7 @@ def read_input(wnd, offy, offx, width, maxlen, echo=True):
|
|||
|
||||
# EOT (C-d) aborts if there is no input
|
||||
elif key == KEY_EOT:
|
||||
if len(input) == 0:
|
||||
if len(inputbuf) == 0:
|
||||
return None
|
||||
|
||||
# backspace removes the previous character
|
||||
|
@ -112,7 +111,7 @@ def read_input(wnd, offy, offx, width, maxlen, echo=True):
|
|||
if inputoff > 0:
|
||||
|
||||
# remove the character immediately before the input offset
|
||||
input = input[0:inputoff-1] + input[inputoff:]
|
||||
inputbuf = inputbuf[0:inputoff-1] + inputbuf[inputoff:]
|
||||
inputoff -= 1
|
||||
|
||||
# move either the cursor or entire line of text left
|
||||
|
@ -124,7 +123,7 @@ def read_input(wnd, offy, offx, width, maxlen, echo=True):
|
|||
if inputoff < len(input):
|
||||
|
||||
# remove the character at the input offset
|
||||
input = input[0:inputoff] + input[inputoff+1:]
|
||||
inputbuf = inputbuf[0:inputoff] + inputbuf[inputoff+1:]
|
||||
|
||||
# left moves the cursor one character left
|
||||
elif key == curses.KEY_LEFT:
|
||||
|
@ -139,7 +138,7 @@ def read_input(wnd, offy, offx, width, maxlen, echo=True):
|
|||
|
||||
# right moves the cursor one character right
|
||||
elif key == curses.KEY_RIGHT:
|
||||
if inputoff < len(input):
|
||||
if inputoff < len(inputbuf):
|
||||
|
||||
# move the cursor to the right
|
||||
inputoff += 1
|
||||
|
@ -155,8 +154,8 @@ def read_input(wnd, offy, offx, width, maxlen, echo=True):
|
|||
|
||||
# end moves the cursor past the last character
|
||||
elif key == curses.KEY_END:
|
||||
inputoff = len(input)
|
||||
displayoff = len(input) - width + 1
|
||||
inputoff = len(inputbuf)
|
||||
displayoff = len(inputbuf) - width + 1
|
||||
|
||||
# insert toggles insert/overwrite mode
|
||||
elif key == curses.KEY_IC:
|
||||
|
@ -164,15 +163,15 @@ def read_input(wnd, offy, offx, width, maxlen, echo=True):
|
|||
|
||||
# other (printable) characters are added to the input string
|
||||
elif curses.ascii.isprint(key):
|
||||
if len(input) < maxlen or maxlen == 0:
|
||||
if len(inputbuf) < maxlen or maxlen == 0:
|
||||
|
||||
# insert mode: insert before current offset
|
||||
if insert:
|
||||
input = input[0:inputoff] + chr(key) + input[inputoff:]
|
||||
inputbuf = inputbuf[0:inputoff] + chr(key) + inputbuf[inputoff:]
|
||||
|
||||
# overwrite mode: replace current offset
|
||||
else:
|
||||
input = input[0:inputoff] + chr(key) + input[inputoff+1:]
|
||||
inputbuf = inputbuf[0:inputoff] + chr(key) + inputbuf[inputoff+1:]
|
||||
|
||||
# increment the input offset
|
||||
inputoff += 1
|
||||
|
@ -218,13 +217,13 @@ def inputbox(wnd, prompt, field_width, echo=True):
|
|||
|
||||
# read an input string within the field region of text_wnd
|
||||
inputy, inputx, inputwidth = 1, 1, textwidth - 2
|
||||
input = read_input(text_wnd, inputy, inputx, inputwidth, 0, echo)
|
||||
inputbuf = read_input(text_wnd, inputy, inputx, inputwidth, 0, echo)
|
||||
|
||||
# erase the window
|
||||
child_wnd.erase()
|
||||
child_wnd.refresh()
|
||||
|
||||
return input
|
||||
return inputbuf
|
||||
|
||||
|
||||
def line_wrap(line, width):
|
||||
|
@ -323,7 +322,7 @@ def msgbox(wnd, msg, title="Message"):
|
|||
curses.curs_set(0)
|
||||
outer_wnd.keypad(1)
|
||||
while True:
|
||||
key = outer_wnd.getch(0,0)
|
||||
key = outer_wnd.getch(0, 0)
|
||||
if key == KEY_RETURN or key == KEY_ESCAPE:
|
||||
break
|
||||
|
||||
|
@ -379,18 +378,18 @@ def menu(wnd, offy, offx, width, options, _acquire_wnd=None):
|
|||
wnd.refresh()
|
||||
|
||||
# read one keypress
|
||||
input = wnd.getch()
|
||||
keypress = wnd.getch()
|
||||
|
||||
# UP moves to the previous option
|
||||
if input == curses.KEY_UP and selected > 0:
|
||||
if keypress == curses.KEY_UP and selected > 0:
|
||||
selected = (selected - 1)
|
||||
|
||||
# DOWN moves to the next option
|
||||
elif input == curses.KEY_DOWN and selected < len(options) - 1:
|
||||
elif keypress == curses.KEY_DOWN and selected < len(options) - 1:
|
||||
selected = (selected + 1)
|
||||
|
||||
# RETURN runs the callback for the selected option
|
||||
elif input == KEY_RETURN:
|
||||
elif keypress == KEY_RETURN:
|
||||
text, callback = options[selected]
|
||||
|
||||
# highlight the selected option
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# $Id: main.py 44 2006-12-31 07:09:27Z mspang $
|
||||
"""
|
||||
CEO-like Frontend
|
||||
|
||||
|
@ -21,7 +20,7 @@ BORDER_COLOR = curses.COLOR_RED
|
|||
def action_new_member(wnd):
|
||||
"""Interactively add a new member."""
|
||||
|
||||
username, studentid, program = '', None, ''
|
||||
studentid, program = None, ''
|
||||
|
||||
# read the name
|
||||
prompt = " Name: "
|
||||
|
@ -33,7 +32,7 @@ def action_new_member(wnd):
|
|||
|
||||
# read the student id
|
||||
prompt = "Student id:"
|
||||
while studentid == None or (re.search("[^0-9]", studentid) and not studentid.lower() == 'exit'):
|
||||
while studentid is None or (re.search("[^0-9]", studentid) and not studentid.lower() == 'exit'):
|
||||
studentid = inputbox(wnd, prompt, 18)
|
||||
|
||||
# abort if exit is entered
|
||||
|
@ -48,7 +47,7 @@ def action_new_member(wnd):
|
|||
program = inputbox(wnd, prompt, 18)
|
||||
|
||||
# abort if exit is entered
|
||||
if program == None or program.lower() == 'exit':
|
||||
if program is None or program.lower() == 'exit':
|
||||
return False
|
||||
|
||||
# connect the members module to its backend if necessary
|
||||
|
@ -59,14 +58,17 @@ def action_new_member(wnd):
|
|||
memberid = members.new(realname, studentid, program)
|
||||
|
||||
msgbox(wnd, "Success! Your memberid is %s. You are now registered\n"
|
||||
% memberid + "for the " + terms.current() + " term.");
|
||||
% memberid + "for the " + terms.current() + " term.")
|
||||
|
||||
except members.InvalidStudentID:
|
||||
msgbox(wnd, "Invalid student ID.")
|
||||
msgbox(wnd, "Invalid student ID: %s" % studentid)
|
||||
return False
|
||||
except members.DuplicateStudentID:
|
||||
msgbox(wnd, "A member with this student ID exists.")
|
||||
return False
|
||||
except members.InvalidRealName:
|
||||
msgbox(wnd, 'Invalid real name: "%s"' % realname)
|
||||
return False
|
||||
|
||||
|
||||
def action_term_register(wnd):
|
||||
|
@ -85,7 +87,7 @@ def action_term_register(wnd):
|
|||
if not member: return False
|
||||
|
||||
memberid = member['memberid']
|
||||
term_list = members.terms_list(memberid)
|
||||
term_list = members.member_terms(memberid)
|
||||
|
||||
# display user
|
||||
display_member_details(wnd, member, term_list)
|
||||
|
@ -134,7 +136,7 @@ def action_term_register_multiple(wnd):
|
|||
if not member: return False
|
||||
|
||||
memberid = member['memberid']
|
||||
term_list = members.terms_list(memberid)
|
||||
term_list = members.member_terms(memberid)
|
||||
|
||||
# display user
|
||||
display_member_details(wnd, member, term_list)
|
||||
|
@ -177,11 +179,57 @@ def action_term_register_multiple(wnd):
|
|||
msgbox(wnd, "Your are now registered for terms: " + ", ".join(term_list))
|
||||
|
||||
except members.InvalidTerm:
|
||||
msgbox(wnd, "Term is not valid: %s" % term)
|
||||
msgbox(wnd, "Invalid term entered.")
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def repair_account(wnd, memberid, userid):
|
||||
"""Attemps to repair an account."""
|
||||
|
||||
if not accounts.connected(): accounts.connect()
|
||||
|
||||
member = members.get(memberid)
|
||||
exists, haspw = accounts.status(userid)
|
||||
|
||||
if not exists:
|
||||
password = input_password(wnd)
|
||||
accounts.create_member(userid, password, member['name'], memberid)
|
||||
msgbox(wnd, "Account created (where the hell did it go, anyway?)\n"
|
||||
"If you're homedir still exists, it will not be inaccessible to you,\n"
|
||||
"please contact systems-committee@csclub.uwaterloo.ca to get this resolved.\n")
|
||||
|
||||
elif not haspw:
|
||||
password = input_password(wnd)
|
||||
accounts.add_password(userid, password)
|
||||
msgbox(wnd, "Password added to account.")
|
||||
|
||||
else:
|
||||
msgbox(wnd, "No problems to repair.")
|
||||
|
||||
|
||||
def input_password(wnd):
|
||||
|
||||
# password input loop
|
||||
password = "password"
|
||||
check = "check"
|
||||
while password != check:
|
||||
|
||||
# read password
|
||||
prompt = "User password:"
|
||||
password = None
|
||||
while not password:
|
||||
password = inputbox(wnd, prompt, 18, False)
|
||||
|
||||
# read another password
|
||||
prompt = "Enter the password again:"
|
||||
check = None
|
||||
while not check:
|
||||
check = inputbox(wnd, prompt, 27, False)
|
||||
|
||||
return password
|
||||
|
||||
|
||||
def action_create_account(wnd):
|
||||
"""Interactively create an account for a member."""
|
||||
|
||||
|
@ -198,7 +246,7 @@ def action_create_account(wnd):
|
|||
if not member: return False
|
||||
|
||||
memberid = member['memberid']
|
||||
term_list = members.terms_list(memberid)
|
||||
term_list = members.member_terms(memberid)
|
||||
|
||||
# display the member
|
||||
display_member_details(wnd, member, term_list)
|
||||
|
@ -218,67 +266,59 @@ def action_create_account(wnd):
|
|||
msgbox(wnd, "I suggest searching for the member by userid or name from the main menu.")
|
||||
return False
|
||||
|
||||
# member already has an account?
|
||||
if member['userid']:
|
||||
|
||||
userid = member['userid']
|
||||
msgbox(wnd, "Member " + str(memberid) + " already has an account: " + member['userid'] + "\n"
|
||||
"Attempting to repair it. Contact the sysadmin if there are still problems." )
|
||||
|
||||
repair_account(wnd, memberid, userid)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# read user id
|
||||
prompt = "Userid:"
|
||||
while userid == '':
|
||||
userid = inputbox(wnd, prompt, 18)
|
||||
|
||||
# user abort
|
||||
if userid == None or userid.lower() == 'exit':
|
||||
if userid is None or userid.lower() == 'exit':
|
||||
return False
|
||||
|
||||
# member already has an account?
|
||||
#if member['userid'] != None:
|
||||
# msgbox(wnd, "Member " + str(memberid) + " already has an account: " + member['userid'] + "\n"
|
||||
# "Contact the sysadmin if there are still problems." )
|
||||
# return False
|
||||
|
||||
# password input loop
|
||||
password = "password"
|
||||
check = "check"
|
||||
while password != check:
|
||||
|
||||
# read password
|
||||
prompt = "User password:"
|
||||
password = None
|
||||
while not password:
|
||||
password = inputbox(wnd, prompt, 18, False)
|
||||
|
||||
# read another password
|
||||
prompt = "Enter the password again:"
|
||||
check = None
|
||||
while not check:
|
||||
check = inputbox(wnd, prompt, 27, False)
|
||||
|
||||
# read password
|
||||
password = input_password(wnd)
|
||||
|
||||
# create the UNIX account
|
||||
result = accounts.create_account(userid, password, member['name'], memberid)
|
||||
|
||||
if result == accounts.LDAP_EXISTS:
|
||||
msgbox(wnd, "Error: Could not do stuff , Already exists.")
|
||||
try:
|
||||
if not accounts.connected(): accounts.connect()
|
||||
accounts.create_member(userid, password, member['name'], memberid)
|
||||
except accounts.AccountExists, e:
|
||||
msgbox |