Page Source
from utils.display import Display, FileCopy, Database, Arguments, Modal
from utils.controls import *
from utils.output import *
from utils.web_exc import WebError
from utils.static_strings import StaticString
from utils import shapes, perms, actions, misc
import queries
from config import docroot
import string, os
import time,MySQLdb
# the page has three arguments the subject (CS/CSPP), course_num (105/523), retire_date
# (the date course was retired) the third is optional and if not specified defaults to
# latest version of the course. A special argument 'all' to the retire_date will list
# all versions of the courses returned by the other two criterion
# so .../subject/CSPP/retire_date/all will give all CSPP courses ever offerred.
# Note the other format cannot be used to specify this kind of a query
# Special Modes which is still DISPLAY shape but does some extra stuff
SPECIAL_DISP_MODES = ['verify','retire','unretire','renumber']
# When making the page a call to xxx_mode after the usual DISPLAY stuff
# is displayed. xxx is a SPECIAL_DISP_MODES member
# The xxx_mode is expected to add a form of some sort and the action button will
# tag a '&mode=xxx' after the '?action=nn'
# In perform_non_edit if the mode is one of the special modes,
# then a call is done to perform_xxx
# All the arguments are in self.args (parse_arguments already called) and
# form variables are in self.req.form_data
# perform_xxx should do the action, notify the overseer if necessary and return the
# URL to bounce to
ACTIVE_FLAG_LONG='9999-12-31 00:00:00.00'
ACTIVE_FLAG='9999-12-31'
ACTIVE_FLAG_SQL="""'9999-12-31'"""
class VerifyControl(Control):
def __init__(self, permission, date_field,
fmt="<address>Last Verified by %(fname)s %(lname)s on %e %B, %Y.</address>"):
"Checks if login is a member of ver_class & gives appropriate output"
self.perm = permission
self.date_field = date_field
self.fmt = fmt
def output_disp(self,display,container):
output = time.strftime(self.fmt, time.localtime(display.fields[self.date_field]))
container.append(output % display.fields);
class ActiveCourseControl(Control):
def output_form(self,display,container):
pass
def output_disp(self,display,container):
# type and str version of flag value changed to DateTime...
if not str(display.fields['retire_date']) == ACTIVE_FLAG and not str(display.fields['retire_date']) == ACTIVE_FLAG_LONG:
container.append("Course was retired on <i> %s </i> <br>" %
display.fields['retire_date'])
class DescriptionDisplay(Display, Database, Arguments, Modal, StaticString):
single_controls = [
"<b>", TextAreaControl(field="title",title="Title",rows=2),"</b> ",
IfExistsControl("aka",[ "<b> (" , TextFieldControl("aka","Also Known As"), ")</b> "]),
"<p><b>Prerequisites:</b> ", TextAreaControl(field="prerequisites",title="Prerequisites",rows=3),"\n",
"<p><b>Catalog Description:</b> ",TextAreaControl(field="catalog_desc",
title="Catalog Description"),
"</p>",
IfExistsControl("description",[
"<p><b>Long Description:</b> ", TextAreaControl(field="description", title="Description"),
"</p>" ]),
"Instructors:<i> ",TextAreaControl(field="instructors",
title="Instructors",rows=2),"</i> <br>",
"Quarter offered: <i> ",TextFieldControl(field="quarter",
title="Quarter"),"</i> <br>",
IfFieldThenElseControl("ver_login",
VerifyControl("faculty","ver_tstamp"),
time.strftime(
"<address>Unverified as of %e %B, %Y.</address>",
time.localtime(time.time()))),
ActiveCourseControl()
];
multiple_container_control = SimpleContainerControl(TABLE)
multiple_row_container_control = SimpleContainerControl(TR)
multiple_controls = [
FieldControl("course_info",nbsppad=1),
LinkControl("course_info","%(script)s/%(!subject)s/%(!course_num)s/%(!retire_date)s",
FieldControl("title",nbsppad=1))
]
error_messages = {
"no_course" : ("No Course",
"""Course does not exist.
Check the name of the course and the retire date and try again."""),
"retd_crse" : ("Retired Course",
"Course is already retired."),
"act_crse" : ("Active Course",
"Course is already active."),
"act_ver_exists" : ("Active Version Exists",
"There is a currently active version of the course already."),
"no_int" : ("Non Integer",
"Course number should be a positive integer."),
"already_retired": ("Already Retired",
"The same course cannot be retired twice in a day. Try retiring it tomorrow"),
"new_crse_exists": ("New Course Exists",
"""The course to which you want to renumber has an active version.
Retire that first and try again"""),
}
def handle_no_perms(self,page,message):
self.generate_error_page(page,"Permission Denied",
"You dont have %s permissions on this page" % message)
def handle_no_course(self,page,message):
self.generate_error_page(page,"Course not found",
"There is no record of a %(subject)s-%(course_num)s course." % message)
def process_arguments(self):
self.args = self.parse_arguments(['subject',
'course_num',
('retire_date',ACTIVE_FLAG)]) # get the URL arguments
# Add 00 if we got a 3 digit course number
cnum = self.args['course_num']
if cnum and len(cnum) == 3:
self.args['course_num'] = cnum+"00"
def handle_non_edit(self):
act = self.req.url.internal['mode'][0] # it is a list with one element
if act in SPECIAL_DISP_MODES and self.permit_mode(act):
method = getattr(self,'perform_'+act) # get the function to call
return method() # call it
else:
raise WebError ("no_perms()",act) # This results in a function call to handle_no_perms
def handle_edit(self):
if self.permit_mode('edit'):
self.perform_database_update()
actions.notify(region='courses',
user=self.req.login,
data=self.args,
type_op="edit")
return "" # Same page Same mode
else:
raise WebError("no_perms()",act)
def perform_action(self):
self.process_arguments()
if self.req.url.internal.has_key('mode'):
return self.handle_non_edit()
else:
return self.handle_edit()
def SQL_Error(self,stage,e=None):
# stage is extra string giving more information
actions.notify(region='courses',
user=self.req.login,
type_op = "edit",
data = self.args,
message = "SQL Error at stage " + stage + ":" + str(e))
raise WebError("SQL_Error",
"""Contact webmaster@cs.uchicago.edu, with details as to how
to reproduce the error""")
def permit_mode(self, mode):
return ((mode == 'display') or # display mode
(mode in ['edit'] + SPECIAL_DISP_MODES and # valid mode
perms.may(self.req.login,mode,'courses')))
def make_page(self,page):
self.process_arguments()
page.set_type("courses")
if self.is_multiple():
title = self.get_multiple_title()
page.set_title(title)
page.add_navigation("%(sect:courses)s/add" % self.req.urls(),"Add Course")
page.add_navigation("%(sect:courses)s/desc_verify" % self.req.urls(), "by Verification")
page.append("<h1>%s</h1>" % title)
page.append(self.multiple_database())
else:
title = self.get_single_title()
page.set_title(title)
self.add_modes_to_page(page)
page.add_navigation("%(sect:courses)s/description" % self.req.urls(), "Descriptions")
page.append("<h1>%s</h1>" % title)
if self.mode == 'edit':
self.shape = shapes.FORM
page.append(self.single_database())
elif self.mode == 'display':
self.shape = shapes.DISP
page.append(self.single_database())
elif self.mode in SPECIAL_DISP_MODES: # any of the action oriented display modes
page.append(self.single_database()) # add the usual stuff
method = getattr(self,self.mode+'_mode') # get the method to call
method(page) # call it!
def is_multiple(self): # single page iff all three specified
return not (self.args['subject'] and self.args['course_num'] and
(self.args['retire_date'] != 'all'))
def get_single_title(self):
return "%s %s" % (string.upper(self.args['subject']),
self.args['course_num'])
def get_multiple_title(self):
if not self.args['subject']:
return "Course Descriptions"
else:
return "%s Course Descriptions" % string.upper(self.args['subject']);
def db_rows(self):
return self.query.count()
def db_query(self):
if self.is_multiple():
self.db_multiple_query()
else:
self.db_single_query()
def db_multiple_query(self):
columns = { 'course_info' : "concat(subject,course_num)",
'subject' : 'subject',
'course_num' : 'course_num',
'title' : 'title',
'retire_date' : 'retire_date'}
tables = ["courses"]
order=["subject","course_num"]
where = ""
if self.args['subject']:
where = where + "AND subject=%(subject)s "
if self.args['course_num']:
where = where + "AND course_num=%(course_num)s "
if self.args['retire_date'] != 'all':
where = where + "AND retire_date=%(retire_date)s "
where = where[3:] # remove the first AND from the where clause
SQLargs = {}
for k,v in self.args.items():
SQLargs[k] = queries.represent_value(v)
where = where % SQLargs
q = queries.Select(tables=tables, columns=columns,where= where, order = order)
q.execute()
self.query = q
def db_single_query(self):
wc = "subject=%(subject)s AND course_num=%(course_num)s AND retire_date=%(retire_date)s"
SQLArgs = {}
for k,v in self.args.items():
SQLArgs[k]=queries.represent_value(v)
wc = wc % SQLArgs
q = queries.Select(tables=["courses left join people on ver_login=login"],
columns={ 'course_num':'course_num',
'title' : 'courses.title',
'description' : 'description',
'catalog_desc':'catalog_desc',
'prerequisites' : 'prerequisites',
'aka' : 'aka',
'instructors':'instructors',
'quarter' : 'quarter',
'subject':'subject',
'ver_tstamp' : 'unix_timestamp(ver_date)',
'login' : 'login',
'ver_login' : 'ver_login',
'fname':'fname',
'mname' : 'mname',
'lname' : 'lname',
'retire_date' : 'retire_date'
},
where= wc
)
q.execute()
if q.count() == 0:
raise WebError ("no_course()",self.args)
self.query = q
def db_fetch(self):
self.fields = self.query.fetch()
return self.fields
def course_exists(self,subject=None,course_num=None,retire_date=None):
"""returns (n,wc) where N= number of records present satisfying given condition.
If any not supplied then taken from self.args, WC returns the where clause constructed"""
SQLargs={}
if subject:
SQLargs['subject'] = queries.represent_value(subject)
else:
SQLargs['subject'] = queries.represent_value(self.args['subject'])
if course_num:
SQLargs['course_num'] = queries.represent_value(course_num)
else:
SQLargs['course_num'] = queries.represent_value(self.args['course_num'])
if retire_date:
SQLargs['retire_date'] = queries.represent_value(retire_date)
else:
SQLargs['retire_date'] = queries.represent_value(self.args['retire_date'])
wc = "subject=%(subject)s AND course_num=%(course_num)s AND retire_date=%(retire_date)s"
wc = wc % SQLargs
q = queries.Select(tables=["courses"],
columns={'c' : 'count(*)'},
where = wc)
q.execute()
num = q.fetch()['c'] # the number of records
# This should be either a 0 or a 1
if (num < 0) or (num > 1):
self.SQL_Error("DB Integrity Error. %s records with same primary fields!" % num)
return (num,wc) # The field c returned by SQL
def db_update(self):
# Check if record for course exists
try: # Start the atomic operation
lock = queries.Lock(tables=["courses"])
num,whereclause = self.course_exists()
if num == 0:
raise WebError ("no_course{}")
# Course exists. Go ahead and update it
upd = queries.Update(table = "courses", where = whereclause, sets=self.form_fields)
try:
upd.execute()
except MySQLdb.Error,e:
self.SQL_Error("update",e)
finally:
lock.unlock()
return
def verify_mode(self,page):
"""Add button to verify"""
submit = SUBMIT_INPUT(attrs={'value':'Verify Course Description'})
form = FORM(sub=submit,action="%(action:)s&mode=verify"%self.req.urls())
page.append("\n\n")
page.append(render_output(form))
def perform_verify(self):
"Actually verify it"
try: # Start atomic operation
lock = queries.Lock(tables=["courses"])
num,whereclause = self.course_exists()
if num == 0:
raise WebError("no_course{}")
# Course exists. Verify it
upd = queries.Update(table="courses",
where = whereclause,
sets= {'ver_login':self.req.login,
'ver_date':queries.SQL('current_date()')})
try:
upd.execute()
except MySQLdb.Error,e:
self.SQL_Error("verify",e)
finally:
lock.unlock()
# notify overseer and return new URL to bounce to
actions.notify(region='courses',
user=self.req.login,
data=self.args,
type_op = "verify")
return "%(mode:display)s" % self.req.urls()
def retire_mode(self,page):
page.append(self.GetString("retire_course")) # what retire exactly does
submit = SUBMIT_INPUT(attrs={'value':'Retire Course'})
form = FORM(sub=submit,action="%(action:)s&mode=retire"%self.req.urls())
page.append("\n\n")
page.append(render_output(form))
def perform_retire(self):
""" Retires the course whose details are in self.args"""
# Are we trying to retire a retired course?
if self.args['retire_date'] != ACTIVE_FLAG: # already retired
raise WebError("retd_crse{}")
# The action takes place here
self.retire_course(subject=self.args['subject'],
course_num=self.args['course_num'])
# notify the overseer and return new URL
actions.notify(region='courses',
user=self.req.login,
data = self.args,
type_op="retire")
return "%(mode:display)s" % self.req.urls()
def unretire_mode(self,page):
page.append(self.GetString("unretire_course")) # what does it exactly do
submit = SUBMIT_INPUT(attrs={'value':'Unretire Course'})
form = FORM(sub=submit,action="%(action:)s&mode=unretire"%self.req.urls())
page.append("\n\n")
page.append(render_output(form))
def perform_unretire(self):
# Are we trying to unretire an active course?
if self.args['retire_date'] == ACTIVE_FLAG: # already active
raise WebError("act_crse{}")
n,wc = self.course_exists()
if n == 0: raise WebError ("no_course{}")
n,wc = self.course_exists(retire_date=ACTIVE_FLAG)
if n == 1: raise WebError ("act_ver_exists{}")
# First check that there is no active version of this course.
# If none exists create an active version by copying this record and setting it
# as active.
self.create_course_from_template(new_subject=self.args['subject'],
new_course_num=self.args['course_num'])
actions.notify(region='courses',
data=self.args,
type_op="unretire",
user=self.req.login)
return "%(mode:display)s" % self.req.urls()
def renumber_mode(self,page):
page.append(self.GetString("renumber_course")) # what exactly does it do
submit = SUBMIT_INPUT(attrs={'value':'Renumber Course'})
subject = SELECT(name="new_subject",options=misc.subject_pairs)
course_num = TEXT_INPUT(attrs={'name':'new_course_num','size':8})
form = FORM(sub=["<b>New Number:</b> ",subject,course_num,submit],
action="%(action:)s&mode=renumber"%self.req.urls())
page.append("\n\n")
page.append(render_output(form))
def perform_renumber(self):
"""Retire the current course if not already retired, and create a new course with details
taken from req.form_data and the current course as the template. This amounts to a call to
retire_course and one to create_course_from_template, in this order. If done in the wrong
order, then renumbering a course to itself will not work!"""
# get the data from req.form_data
subject = self.req.form_data['new_subject']
course_num = int(self.req.form_data['new_course_num'])
# validate input data
if course_num <= 0: raise WebError ("no_int{}")
n,wc = self.course_exists() # Does current course exist?
if n==0: raise WebError("no_course{}")
# Does the new course have an active version already
n,wc = self.course_exists(subject=subject,
course_num=course_num,
retire_date=ACTIVE_FLAG)
if n==1: raise WebError ("new_crse_exists{}")
# if necessary retire the current course
if self.args['retire_date'] == ACTIVE_FLAG:
date = self.retire_course(self.args['subject'],self.args['course_num'])
else:
date = self.args['retire_date']
# either case now date is the date on which this course is/was retired
self.create_course_from_template(old_subject=self.args['subject'],
old_course_num=self.args['course_num'],
old_retire_date = date,
new_subject=subject,
new_course_num=course_num)
# Again order matters else wont work if (old/new)subject and (old/new)course_num
# are the same.
actions.notify(region="courses",
type_op="renumber",
data=self.args,
message = "New Subject = %s, New Course Number = %s" % (subject,course_number),
user=self.req.login)
return "%(mode:display)s" % self.req.urls() # no overseer notification needed here
def retire_course(self,subject,course_num):
"Reires the course specified and returns the date (TODAY) on which it was retired"
try: # Start atomic operation
lock = queries.Lock(tables=["courses","schedules"])
# is the course present and active?
num,whereclause = self.course_exists(subject=subject,
course_num=course_num,
retire_date=ACTIVE_FLAG)
if num == 0: raise WebError("no_course{}")
# Get todays date
q = queries.Select(columns={'date':queries.SQL('current_date()')})
q.execute()
date = q.fetch()['date'] # todays date
# Has a version of this course been retired today already?
num,arbit=self.course_exists(subject=subject,
course_num=course_num,
retire_date = date)
if num == 1: raise WebError("already_retired{}")
# Need to execute two update queries both need to have the same date_stamp
# Theoretically the two queries can be executed before and after 12 midnight
# That is why we got the date stamp from SQL first
# Retire the course
upd1 = queries.Update(table="courses",
where = whereclause,
sets= {'retire_date':date})
try:
upd1.execute()
except MySQLdb.Error,e:
self.SQL_Error("retire - courses stage",e)
# All schedules pointing to this course should also be modified
upd2 = queries.Update(table="schedules",
where=whereclause,
sets={'retire_date':date})
try:
upd2.execute()
except MySQLdb.Error,e:
self.SQL_Error("retire - schedules stage",e)
finally:
lock.unlock()
return date
def create_course_from_template(self,
new_subject,
new_course_num,
old_subject=None,
old_course_num=None,
old_retire_date = None):
"""Creates a new active course with given details, remaining data taken from the course
pointed to by self.args"""
if not old_subject: old_subject = self.args['subject']
if not old_course_num: old_course_num = self.args['course_num']
if not old_retire_date:old_retire_date = self.args['retire_date']
try: # Start atomic operation
lock = queries.Lock(tables=["courses"])
num,whereclause = self.course_exists(subject=old_subject,
course_num=old_course_num,
retire_date=old_retire_date) # is the course present
if num == 0: raise WebError("no_course{}")
# Course exists
# Now we need to check if there is an active version of this new course
n,arbit = self.course_exists(subject=new_subject,
course_num=new_course_num,
retire_date=ACTIVE_FLAG)
if n == 1: raise WebError ("act_ver_exists{}")
# There is no active version. So we can go ahead and create an active version
#First select the current record fully. While checking if it exists we set up the
# where clause. Just reuse it.
q = queries.Select(tables=["courses"],where=whereclause)
# empty columns={} means get all the columns
q.execute()
rec = q.fetch() # Get the record
try:
rec['subject'] = new_subject
rec['course_num'] = new_course_num
rec['retire_date'] = ACTIVE_FLAG # Change the record so that it is active
except KeyError: # Something flawed with implementation
self.SQL_Error("Design Flaw: Primary Key fields not present!")
ins = queries.Insert(table="courses",rows=[rec])
try:
ins.execute()
except MySQLdb.Error,e:
# Most probably this means we are violating integrity constraints
# Again should not happen, but who knows the ways of computers!
self.SQL_Error("unretire - insert stage",e)
finally:
lock.unlock()
###
# This class is instantiated from inline_descr.py
class InlineDescriptionDisplay(DescriptionDisplay):
multiple_container_control = NullContainerControl()
multiple_row_container_control = NullContainerControl()
multiple_controls = [
"<h2>", FieldControl("course_info",nbsppad=1), "</h2>",
LinkControl("course_info",
"%(sect:courses)s/description/%(!subject)s/%(!course_num)s/%(!retire_date)s",
FieldControl("title",nbsppad=1)) ,
IfExistsControl("aka",[ "<b> (" , FieldControl("aka","Also Known As"), ")</b> "]),
"<i> ", TextFieldControl("prerequisites","PQ"),"</i>\n",
TextAreaControl("description","Description"),
"<i> ",TextAreaControl("instructors","Instructors"),"</i>",
"<i> ",TextFieldControl("quarter","Quarter"),"</i> ",
"<p>\n",
IfFieldThenElseControl("ver_login",
VerifyControl("faculty","ver_tstamp"),
time.strftime(
"<address>Unverified as of %e %B, %Y.</address>",
time.localtime(time.time()))),
"</p>",
ActiveCourseControl()
];
def permit_mode(self, mode):
# No editing
return (mode == 'display')
def is_multiple(self):
return 1 # always multiple
def db_query(self):
columns = { 'course_info' : "concat(subject,course_num)",
'course_num':'course_num',
'title' : 'courses.title',
'description' : 'description',
'prerequisites' : 'prerequisites',
'aka' : 'aka',
'instructors':'instructors',
'quarter' : 'quarter',
'subject':'subject',
'ver_tstamp' : 'unix_timestamp(ver_date)',
'login' : 'login',
'ver_login' : 'ver_login',
'fname':'fname',
'mname' : 'mname',
'lname' : 'lname',
'retire_date' : 'retire_date'
}
tables=["courses left join people on ver_login=login"]
order=["subject","course_num"]
where = ""
if self.args['subject']:
where = where + "AND subject=%(subject)s "
if self.args['course_num']:
where = where + "AND course_num=%(course_num)s "
if self.args['retire_date'] != 'all':
where = where + "AND retire_date=%(retire_date)s "
where = where[3:] # remove the first AND from the where clause
SQLargs = {}
for k,v in self.args.items():
SQLargs[k] = queries.represent_value(v)
where = where % SQLargs
q = queries.Select(tables=tables,
columns=columns,
where= where,
order = order
)
q.execute()
self.query = q
class VerifyDescDisplay(Display, Database, Arguments, Modal):
multiple_container_control = SimpleContainerControl(TABLE)
multiple_row_container_control = SimpleContainerControl(TR)
multiple_controls = [
FieldControl("course_info",nbsppad=1),
CoalesceControl(subcontrols=["( Verified ",
FieldControl("ver_date",nbsppad=1),
")"]),
LinkControl("course_info","%(sect:courses)s/description/%(!subject)s/%(!course_num)s/%(!retire_date)s",
FieldControl("title",nbsppad=1))
]
def process_arguments(self):
self.args = self.parse_arguments(['subject']) # get the URL arguments
def make_page(self,page):
self.process_arguments()
page.set_type("courses")
title = self.get_multiple_title()
page.set_title(title)
page.add_navigation("%(sect:courses)s/add" % self.req.urls(),"Add Course")
page.add_navigation("%(sect:courses)s/description" % self.req.urls(), "by Course Number")
page.append("<h1>%s</h1>" % title)
page.append(self.multiple_database())
def is_multiple(self): # Always multiple
1
def get_multiple_title(self):
if not self.args['subject']:
return "Course Descriptions by Verification Date"
else:
return "%s Course Descriptions by Verification Date" % string.upper(self.args['subject']);
def db_rows(self):
return self.query.count()
def db_query(self):
self.db_multiple_query()
def db_multiple_query(self):
columns = { 'course_info' : "concat(subject,course_num)",
'subject' : 'subject',
'course_num' : 'course_num',
'title' : 'title',
'retire_date' : 'retire_date',
'ver_date' : 'ver_date'}
tables = ["courses"]
order=["ver_date","subject","course_num"]
if self.args['subject']:
where = "subject=%(subject)s"
else:
where = ""
SQLargs = {}
for k,v in self.args.items():
SQLargs[k] = queries.represent_value(v)
where = where % SQLargs
q = queries.Select(tables=tables, columns=columns,where= where, order = order)
q.execute()
self.query = q
def db_fetch(self):
self.fields = self.query.fetch()
return self.fields
def new(req):
return DescriptionDisplay(req)
def new_inline(req):
return InlineDescriptionDisplay(req)
def new_verify(req):
return VerifyDescDisplay(req)