Page Source
from utils.display import Display, FileCopy, Database, Arguments, Modal
from utils.controls import *
from utils.web_exc import WebError
from utils import bounce, misc, perms, actions, shapes
import queries, debugging
from config import docroot
import string, os
import time, MySQLdb
import re # regular expression
from utils.output import *
ACTIVE_FLAG='9999-12-31 00:00:00.00'
ACTIVE_FLAG_SQL="""'9999-12-31'"""
class PowerEditControl(IfThenElseControl):
"""Draw NoEditControl if user does not have edit permissions,
else draw TextFieldControl. So only instructors fall into the
else category"""
def __init__(self):
elsecontrols = [NoEditControl(field="course",title="Course")]
thencontrols = [SelectQueryControl(field="course2",title="Course",
query=queries.Select(tables="courses",
columns={"sys_value" : "concat(subject,course_num)",
"user_value": "concat(subject,course_num,': ',left(title,35))"},
order=["sys_value"],
where="retire_date="+ACTIVE_FLAG_SQL)), # currently active
TextFieldControl(field="course_sec",title="Sec Num")]
IfThenElseControl.__init__(self, thencontrols,elsecontrols)
def output_form(self,display,container):
self.thencontrols[0].default = display.fields['course2'] # Set a default
IfThenElseControl.output_form(self,display,container)
def condition_form(self, display):
return display.req.login and perms.may(display.req.login, 'edit', 'schedules')
def condition_disp(self, display):
return 0 # show all the information
# Always called with full set of arguments <page>/year/qtr/course
# course is like CS105-02
# If only one argument given assumed to be course and year and qtr becomes
# current qtr.
# This displays the info page for the sub-num-sec.
# Always instantiated in the single mode, never in Multiple mode.
class InstrControl(SelectQueryControl):
"""Output a comma seperated links to each instructors homepage"""
def __init__(self):
self.field = "instructors"
self.title = "Instructors"
self.query = None
self.comment=""
self.multi=1
self.nbsppad=1
self.size = None
self.attrs = None
def output_disp(self,display,container):
"""All the instructors are in display.instructors, as a list of dictionaries. Each dictionary has
fullname, login, pseudo"""
ans = []
for dict in display.instructors:
if dict['pseudo']:
op = dict['fullname']
else:
op = '<A HREF="%(person:'+ dict['login']+ ')s">'
op = op % display.urls()
op = (op+ '%(fullname)s</A>') % dict
ans = ans + [op]
container.append(string.join(ans,", "))
def output_form(self,display,container):
self.default = []
for dict in display.instructors:
self.default.append(dict['login'])
self.query = queries.Select(tables=["instructors as i","people as p"],
columns={'sys_value' :'i.login',
'user_value' : "concat(fname,' ',mname,' ',lname)"},
where="i.login=p.login",
order=["lname"]
)
SelectQueryControl.output_form(self,display,container)
class IsOldCourse(ConditionalControl):
def __init__(self,datefield,text):
self.datefield = datefield
self.text = text
def condition_disp(self,display):
date = display.fields[self.datefield]
# return None means show "Currently Active"
if str(date) == ACTIVE_FLAG: return None
# check if date < today (date in SQLformat)
# I'm not sure if "9999-12-31 00:00:00.00" and "2005-9-28" are so comparable
return date < misc.SqlToday()
def met_output_disp(self,display,container):
container.append(self.text % display.fields)
def not_met_output_disp(self,display,container):
container.append("<Currently Active>")
def output_form(self,display,container):
pass
def input_form(self,display,form_fields):
pass
class InfoDisplay(Display, Database, FileCopy, Arguments, Modal):
single_controls = [
"<table>",
"<tr>",
'<td align="right">', "<b>Course :</b>", "</td>\n",
'<td align="left">', PowerEditControl(),"</td>\n",
"</tr>\n",
"<tr>",
'<td align="right">', "<b>Title :</b>", "</td>\n",
'<td align="left">', NoEditControl(field="title",title="Title"), "</td>\n",
"</tr>\n",
"<tr>",
'<td align="right">', "<b>Description :</b>", "</td>\n",
'<td align="left">', SingleLinkControl(
url='%(sect:courses)s/description/%(!subject)s/%(!course_num)s/%(!retire_date)s',
linktext='Click here for the course description',
nbsppad=1
),"</td>\n",
"</tr>\n",
"<tr>",
'<td align="right">', "<b>Instructor(s) :</b>","</td>\n",
'<td align="left">', InstrControl(), "</td>\n",
"</tr>\n",
"<tr>",
'<td align="right">',"<b>Class Schedule :</b>", "</td>\n",
'<td align="left">', TextFieldControl(field="timeday",title="Time of day"), " at ",
TextFieldControl(field="room",title="Room"), "</td>\n",
"</tr>\n",
"<tr>",
'<td align="right">', "<b>Course Homepage :</b>","</td>\n",
'<td align="left">', IfExistsControl(field="homepage",
thencontrols= IfShapeControl(
dispcontrols=[URLControl(field="homepage",
linktext="Course Home Page")],
formcontrols=[TextFieldControl(field="homepage",
title="Home page",
comment="Absolute URLs please")]),
elsecontrols=["<No Course home page given>"]),
"</td>\n",
"</tr>\n",
"<tr>",
'<td align="right" valign="top">',"<b>Textbook(s) :</b>","</td>\n",
'<td align="left">', IfExistsControl(field="text_book",
elsecontrols="<No textbook info given>",
thencontrols=TextAreaControl(field="text_book",
title="Textbook(s)",
comment="")),
"<td>\n",
"</tr>\n",
"<tr>",
'<td align="right">',"<b> Course Retired :</b>", "</td>\n",
'<td align="left">',IsOldCourse("retire_date","Retired on %(retire_date)s"), "</td>\n",
"</tr>\n",
"<tr>",
'<td align="right">',"<b> Remarks :</b>", "</td>\n",
'<td align="left">', IfExistsControl(field="remarks",
thencontrols= [
"<i>",
TextAreaControl(field="remarks",title="Remarks",
comment="Remarks about the course"),
"</i>" ],
elsecontrols="<None>"
), "</td>\n",
"</tr>\n",
"<tr>",
'<td align="right">',"<b> Announcement :</b>", "</td>\n",
'<td align="left">', IfExistsControl(field="extra_info",
thencontrols= [
"<i>",
TextAreaControl(field="extra_info",title="Extra Info",
comment="Important course specific info"),
"</i>" ],
elsecontrols="<None>"
), "</td>\n",
"</tr>\n",
"</table>"
]
error_messages = {
'bad_args' : ("Insufficient data",
"Wrong number of arguments"),
'bad_fmt' : ("Insufficient data",
"Course Info insufficient or wrong format"),
'edit_perm': ("Permission Denied",
"You don't have permission to edit this page"),
'no_data' : ("Data not found",
"""Check course number or other arguments and try again.
If it still persists contact webmaster@cs.uchicago.edu"""),
'no_course': ("Course does not exist",
"Reasons: No instructor / No course / course not active"),
'bad_iden' : ("No such active course",
"""You tried to change the course details of this schedule.
The changed value does not have a corresponding active course.
Contact webmaster@cs.uchicago.edu, and get the edit done manually"""),
'sql_err' : ("SQL_Error",
"""Contact webmaster@cs.uchicago.edu, with details as
to how to reproduce the error""")}
def unpack_course(self,course):
# course is a string like "CS102-1" or "CMSC174-1". Return a 3-tuple CS,102,1
regexp = '([a-zA-Z]+)(\d+)-(\d+)' # any # letters + any # digit + "-" + any # of digits
MatchObj = re.match(regexp,course)
if not MatchObj: # if not in the correct format
return None
else: return MatchObj.groups()
def SQLify(self,dict):
# takes a dictionary and returns another one with same keys and values = sql_representvalue(old value)
SQLargs = {}
for k,v in dict.items():
SQLargs[k] = queries.represent_value(v)
return SQLargs
def process_arguments(self,form_data=1):
"""parse the arguments got either thru cmd line and POST DATA
unless form_data is set to None here"""
self.args = {}
qtr,year = misc.current_qtr()
arg_names = [('year',year),('quarter',qtr),'course']
if len(self.req.url.arguments) == 1: # only one argument, then it is course
self.args['course'] = self.req.url.arguments[0]
self.args['quarter'] = qtr
self.args['year'] = year
else:
self.args = self.parse_arguments(arg_names,form_data=form_data)
# All args should be present now except possibly for course
if not self.args['course']:
# if argument not given
raise WebError("bad_args{}")
# Now that all the data is there add three more args for 'subject','number','section'
# unpacked from 'course'
unpacked = self.unpack_course(self.args['course'])
if unpacked:
self.args['subject'], self.args['number'], self.args['section'] = self.unpack_course(self.args['course'])
# finally check if the number is three digit in which case need to add two zeroes.
cnum = self.args['number']
if cnum and len(cnum) == 3:
self.args['number'] =cnum+"00"
else:
raise WebError ("bad_fmt{}")
def perform_action(self):
# This time ignore the form data else if the course information
# has been modified we will get an error
self.process_arguments(form_data=None)
self.prep_database()
if self.permit_mode('edit'):
self.perform_database_update()
actions.notify(region='schedules', data=self.args, user=self.req.login, type_op = "edit")
return "%(sect:courses)s/%(!year)s/%(!quarter)s" % self.req.urls(self.args) # Same page
else:
raise WebError ("edit_perm{}")
def permit_mode(self, mode):
if ((mode == 'display') or # display mode
( mode == 'edit' and # permitted to edit
perms.may(self.req.login,'edit','schedules'))):
return 1
# only other possibility is if mode=edit and login is one of the instructors
if mode != 'edit': return 0
for ins in self.instructors:
if ins['login'] == self.req.login:
return 1
return None
def add_nav_links(self,page):
if perms.may(self.req.login,'edit','instructors'):
page.add_navigation("%(sect:courses)s/instructors" % self.req.urls(),"Instructors List")
def make_page(self,page):
self.process_arguments()
page.set_type("courses")
page.set_title(self.get_single_title())
self.prep_database()
self.add_modes_to_page(page)
self.add_nav_links(page)
# deletion takes one-argument, then sched_id
if self.mode == 'edit':
self.shape = shapes.FORM
page.append(self.single_database())
# If user can delete schedules, and currently in edit mode then present the link
if perms.may(self.req.login,'delete','schedules') and self.mode== 'edit':
url = "%(sect:courses)s/del_schedule/%(!sched_id)s" % self.req.urls(self.fields)
page.append('<A HREF="'+url+'"> Delete this schedule</A>')
def get_single_title(self):
return "Course Information for %(course)s" % self.args
def is_multiple(self): # only single mode
return None
def db_rows(self):
return self.query.count() # should be one
def db_query(self):
tables=["schedules as s", "people as p", "courses as c", "instructorship as i"]
columns={"course" : "concat(s.subject,s.course_num,'-',s.course_sec)",
"course2" :"concat(s.subject,s.course_num)",
"subject" : "s.subject",
"course_sec" : "concat(s.course_sec)",
"course_num" :"s.course_num",
"homepage":"s.homepage",
"pseudo":"pseudo",
"title" :"c.title",
"login":"login",
"fullname" : "concat(fname,' ',mname,' ',lname)",
"lname":"lname",
"timeday":"s.timeday",
"room":"s.room",
"text_book" :"text_book",
"retired":"retired",
"remarks" :"remarks",
"extra_info" :"extra_info",
"retire_date" : "s.retire_date",
"sched_id" : "s.sched_id",
"num" : "count(*)" # number of instructors
}
# most of the time, only one instructor in which case stored in schedules.
wc = " i.instructor=login AND s.course_num=c.course_num AND s.subject=c.subject "
wc = wc + " AND s.year=%(year)s AND s.quarter=%(quarter)s AND s.subject=%(subject)s "
wc = wc + " AND s.course_num=%(number)s AND s.course_sec=%(section)s "
wc = wc + " AND s.retire_date=c.retire_date "
# Convert into SQL form, i.e. quote the strings...
wc = wc % self.SQLify(self.args)
groupby = "s.sched_id"
q = queries.Select(tables=tables,columns = columns,where=wc,groupby=groupby)
q.execute()
if q.count() != 1:
raise WebError("no_data{}")
self.query = q
def db_fetch(self):
self.fields = self.query.fetch()
if self.fields['num'] > 1: # More than one instructor then get their info
tables=["instructorship","people"]
columns={ 'fullname' : "concat(fname,' ',mname,' ',lname)",
'login' : 'login',
'pseudo': 'pseudo or retired'}
wc = "login = instructor and sched_id="
wc = wc + queries.represent_value(self.fields['sched_id'])
q = queries.Select(tables=tables,columns=columns,where=wc)
q.execute()
self.instructors = q.fetchall() # get all the instructors
else:
self.instructors = [{'fullname' : self.fields['fullname'],
'login': self.fields['login'],
'pseudo':self.fields['pseudo'] or display.fields['retired']}]
return self.fields
def wrong_input(self):
# check if we are editing an existing record
# i.e. course should exist -- need not be active though
# if wrong returns 0 else returns the sched_id for the record to edit.
# Get the subject and other stuff from the URL arguments
if not self.form_fields['instructors']: # no instructor selected
return None
(sub,num,sec) = self.unpack_course(self.args['course'])
ff = {}
ff['subject'] = sub
ff['course_num'] = num
ff['course_sec'] = sec
ff['year'] = int(self.args['year'])
ff['quarter'] = self.args['quarter']
where = "subject = %(subject)s and course_num = %(course_num)s and course_sec=%(course_sec)s"
where = where + " and year=%(year)s and quarter=%(quarter)s "
# search for a corresponding schedule
columns = {"sched_id":"sched_id"}
query = queries.Select(tables=["schedules"],columns=columns,
where= where % self.SQLify(ff))
query.execute()
# Does the record we are trying to change exist?
if query.count() != 1:
return None
row = query.fetch() # All the old data of this schedule
# if the course field is not there construct it from the other two.
if not self.form_fields.has_key('course'):
if not self.form_fields.has_key('course2'):
self.form_fields['course'] = self.args['course']
else:
self.form_fields['course'] = self.form_fields['course2'] + "-" + self.form_fields['course_sec']
del self.form_fields['course2']
del self.form_fields['course_sec']
# are we trying to change the course identity?
if self.form_fields['course'] == self.args['course']:
# No then all is well
# Remove the 'course' entry
del self.form_fields['course']
return row['sched_id']
# We are trying to change the identity of the course
# That's OK. But check if the new identity points to an
# existing course which is active.
# Active Version because we dont have access to any
# retire_date info and we dont want the user to enter
# such data.
# Try to parse the new identity
ff = self.unpack_course(self.form_fields['course'])
if not ff: # Did not parse?
raise WebError("bad_fmt{}")
(sub,num,sec) = ff # Extract the parts
# and save it as if this was entered separately by the user
newdata = {}
newdata['subject'] = sub
newdata['course_num'] = num
newdata['course_sec'] = sec
# Now see if there is an active course with this data
where = "subject = %(subject)s and course_num = %(course_num)s and "
where = where + ("retire_date="+ACTIVE_FLAG_SQL)
columns = {'subject' : 'subject'} # Something here
query = queries.Select(tables=["courses"],columns=columns,
where = where % self.SQLify(newdata))
query.execute()
if query.count() != 1: # Does such a course exist?
# No then tell user about the error
raise WebError ("bad_iden{}")
else:
# Copy the new unpacked data into form_fields
# and get rid of the packed data
self.form_fields['subject'] = ff[0] # First entry
self.form_fields['course_num'] = ff[1] # Second entry
self.form_fields['course_sec'] = ff[2]
del self.form_fields['course']
return row['sched_id'] # the sched_id
def massage_form_fields(self):
# massage form_fields into the right format
if not self.form_fields.has_key('instructors'):
self.form_fields['instructors'] = None
if type(self.form_fields['instructors']) == types.StringType: # only one instructor
self.form_fields['instructors'] = [self.form_fields['instructors']]
def input_error(self):
raise WebError ("no_course{}")
def SQL_Error(self,stage,e):
# stage is "update","delete" or "insert". E is exception object
actions.notify(region='courses',
user=self.req.login,
type_op="edit",
data=self.args,
message="Stage = '%s' Error = '%s'" % (stage,str(e)))
raise WebError ("sql_err{}")
def db_update(self):
self.massage_form_fields()
try:
lock = queries.Lock(tables=["schedules","instructorship"])
id = self.wrong_input() # None if wrong input or the sched_id if correct
if not id:
self.input_error() # Gave wrong data
sqlid = queries.represent_value(id) # SQLify id
self.instructors = self.form_fields['instructors']
del self.form_fields['instructors'] # remove this from form_fields
# if only one instructor save it also in schedules for optimization
if len(self.instructors) == 1: #only one instructor
self.form_fields['instructor'] = self.instructors[0]
else: self.form_fields['instructor'] = ""
# Update the schedules table first, all values in self.form_fields
upd = queries.Update(table="schedules",where="sched_id = %s" % sqlid, sets=self.form_fields)
try:
upd.execute()
except MySQLdb.Error,e:
self.SQL_Error("update",e)
# Delete all the instructors for this course in instructorship table
delq = queries.Delete(table="instructorship",where="sched_id = %s" % sqlid)
try:
delq.execute()
except MySQLdb.Error,e:
self.SQL_Error("delete",e)
# Add the new set of instructors
rows = []
for inst in self.instructors:
rows.append({'instructor' : inst, 'sched_id' : id}) # SQLification done automagically
ins = queries.Insert(table="instructorship",rows=rows)
try:
ins.execute()
except MySQLdb.Error,e:
self.SQL_Error("insert",e)
finally: # unlock the table, whatever happens, exception or no exception
lock.unlock()
def new(req):
return InfoDisplay(req)