Display Diary

John Hurst

Version 0.1.4

20050726:133108

Table of Contents

1 Introduction
2 Main Program
3 Read the Diary
4 Collect CGI Parameters
5 Generate Web Page
5.1 Procedures
5.2 Initialization
6 Confirm Appointment Date and Time
6.1 The doDiaryClick procedure
6.2 The confirmAppointment procedure
6.3 Support Operations for Appointment Confirmation
7 Grab User Details
8 EMail Confirmation Phase
8.1 Define the EMail Confirmation procedure
8.2 Define the sendEMail Procedure
9 Support Operations
10 Index
10.1 File Definitions
10.2 Chunk Definitions
10.3 Variable Definitions


1. Introduction

This program is responsible for handling John's Appointments Pages. There are two main parts to it: the display of the current diary, and the appointment making confirmation page.

The diary display is the main point of entry. It reads a diary file and generates a graphical web page which represents each day's activities in a colour coded line of the display. Each free time slot is displayed in green, and each engaged time slot is displayed in red. Each time slot is a clickable link to the script, which goes through a process of confirming a new appointment.

The second part is this appointment confirmation process: the requested date and time is displayed, and can be changed. The length of the appointment is also changeable. Once the user confirms the parameters of the appointment, it is checked against the diary, and an appropriate message displayed. Note that this program does NOT change the diary itself: that is done by ajh in confirming the date and time. For this purpose, the user's e-mail address is also collected. When a satisfactory time is found, the details are emailed to ajh for confirmation.

2. Main Program

"ajhdiary.py" 1 =
#!/usr/bin/python <import definitions 2> (year, month, day, hour, minute, second, weekday, yday, DST) = \ time.localtime(time.time()) today = datetime.date.today() <define diary page parameters 8> <define variables 3,29> <define procedures 9,10,20,21,28,30,31,32,33> <define generate web page 7> <define confirm appointment times 19> <define diary click handler 18> <define grab details 24> <define the email confirmation procedure 27> <read diary file and collect appointments 5> <collect cgi parameters and invoke appropriate part 6>

Define all import quotas and restrictions here

<import definitions 2> =
from appointments import Appointment,acmp{Note 2.1} import cgi import datetime import os import re import string import sys import time
Chunk referenced in 1
{Note 2.1}
appointments is a locally written module defining one class: Appointment. The function acmp compares two appointment instances and returns a cmp-type value for use in comparisons.
<define variables 3> =
year=2005 month=1 day=1 hour=0 minute=0 monthnames=["January","February","March","April","May","June",\ "July","August","September","October","November","December"] <define server addresses 4>
Chunk referenced in 1
Chunk defined in 3,29
<define server addresses 4> =
server="http://localhost/ajh" cgiserver="http://localhost/cgi-bin/ajh" tmpdirectory="/private/tmp" sendmail="/usr/sbin/sendmail" webhomedir="/home/ajh/www"
Chunk referenced in 3

3. Read the Diary

The diary is a text file, while each line representing a single appointment. While the appointments may appear in any order, in practice they are arranged chronologically, and each day can be prefaced with a day of the week line. It is generally assumed that appointments are wholly contained within a single day, but multiple day appoints are possible, and are represented by the end and start times running across midnight.

Format of diary file:

diary = line * .
line = dayoftheweek ! appointment .
dayoftheweek = date ' '{11} '=== ' weekday ' ===' .
appointment = date ' ' starttime '-' endtime ' ' activity ('  (' location ')')? .
date = YYYY MM DD .
YYYY = digit{4} .
MM = digit{2} .
DD = digit{2} .
starttime = time .
endtime = time .
time = hh mm .
hh = digit{2} .
mm = digit{2} .
weekday = 'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri' | 'Sat' | 'Sun' .
        

Example of a diary file:

20050121           === Fri ===
20050121 1000-1100 Judy Sheard  (75.G58)
20050123           === Sun ===
20050123 1000-1100 Church
20050124           === Mon ===
20050124 1000-1100 Kelsey  (75.G58)
20050125           === Tue ===
20050125 1030-1500 Swinburne 
20050126           === Wed ===
20050126 0000-0000 Australia Day 
20050126 0000-0000 Leave 
20050127           === Thu ===
20050127 0900-1600 Course Advice 
        
<read diary file and collect appointments 5> =
linepat=re.compile(r'(....)(..)(..) (..)(..)-(..)(..) ([^(]*)(\((.*)\))?$') diaryfile=open('%s/diary' % webhomedir,'r') apps=[] for line in diaryfile.readlines(): line=string.rstrip(line){Note 5.1} res=linepat.match(line){Note 5.2} if res:{Note 5.3} start=datetime.datetime(year=int(res.group(1)),month=int(res.group(2)),\ day=int(res.group(3)),hour=int(res.group(4)),\ minute=int(res.group(5))) end =datetime.datetime(year=int(res.group(1)),month=int(res.group(2)),\ day=int(res.group(3)),hour=int(res.group(6)),\ minute=int(res.group(7))) activity=location='' if res.group(8): activity=res.group(8) if res.group(10): location=res.group(10) newap=Appointment(start=start,end=end,activity=activity,location=location){Note 5.4} apps.append(newap){Note 5.5} diaryfile.close() diaryfile=open('%s/diary.new' % webhomedir,'r'){Note 5.6} for line in diaryfile.readlines(): line=string.rstrip(line) res=linepat.match(line) if res: start=datetime.datetime(year=int(res.group(1)),month=int(res.group(2)),\ day=int(res.group(3)),hour=int(res.group(4)),\ minute=int(res.group(5))) end =datetime.datetime(year=int(res.group(1)),month=int(res.group(2)),\ day=int(res.group(3)),hour=int(res.group(6)),\ minute=int(res.group(7))) activity=location='' activity='unconfirmed' if res.group(10): location=res.group(10) newap=Appointment(start=start,end=end,activity=activity,location=location) apps.append(newap) diaryfile.close() apps.sort(acmp){Note 5.7}
Chunk referenced in 1
{Note 5.1}
remove the end of line character
{Note 5.2}
Match the line for the appointment non-terminal.
{Note 5.3}
we have an appointment, extract field values and make new appointment
{Note 5.4}
Make the new appointment
{Note 5.5}
and add it to the set of appointments so far
{Note 5.6}
repeat the above with the unconfirmed appointments in diary.new
{Note 5.7}
all done, sort the appointments into chronological order

4. Collect CGI Parameters

This is where the various phases of the appointments web page are split off. Each phase is flagged by a cgi parameter, except for the initial phase, that of the display of the diary, which normally has no parameters (but can be explicitly invoked with a diary parameter). Note also that the clicking of a time bar in the diary display has no explicit phase parameter, but relies upon the

<collect cgi parameters and invoke appropriate part 6> =
form = cgi.FieldStorage() formkeys=form.keys() if form.has_key('diary') or len(formkeys)==0: genWebPage(form) elif form.has_key('appoint'): confirmAppointment(form) elif form.has_key('details'): grabDetails(form) elif form.has_key('email'): doEMailConfirmation(form) else: doDiaryClick(form) sys.exit(0)
Chunk referenced in 1

5. Generate Web Page

The front end to this program suite. When first invoked, the ajhdiary program displays a graphical rendition of free and busy times. Each time is a clickable link to make an appointment. This routine is responsible for rendering this diary page.

<define generate web page 7> =
def genWebPage(form): <initialize web page variables 11> <initialize web page 12> <print web page heading 13> <print form 14>
Chunk referenced in 1
<define diary page parameters 8> =
daystart=8{Note 8.1} dayend=18{Note 8.2} quantum=5{Note 8.3} hourSlotsInDay=dayend-daystart{Note 8.4} quantaPerSlot=60/quantum{Note 8.5} quantaInDay=quantaPerSlot*hourSlotsInDay{Note 8.6}
Chunk referenced in 1
{Note 8.1}
hour of day when diary display starts
{Note 8.2}
hour of day when diary display ends
{Note 8.3}
granularity in minutes of appointment times
{Note 8.4}
length in hours of display
{Note 8.5}
number of appointment start times per hour
{Note 8.6}
number of appointment start times per day

5.1 Procedures

The mergeflags procedure is used to reduce adjacent time slots that have different engagements to one engagement value. This is because the granularity of the appointments is 5 minutes (adjustable in the chunk <define diary page parameters >), and appointments may overlap within this range. The codes mean:

A
appointment at Caulfield
L
appointment at Clayton
*
appointment at unknown place
a
free at Caulfield
l
free at Clayton
u
unconfirmed appointment
<define procedures 9> =
def mergeflags(x,y): if x==y: return x else: return '-'
Chunk referenced in 1
Chunk defined in 9,10,20,21,28,30,31,32,33
<define procedures 10> =
def withinApp(r,d,a): s=a.start rdate=datetime.date(year=r.year,month=r.month,day=r.day) adate=datetime.date(year=s.year,month=s.month,day=s.day) if rdate != adate: return 0 e=a.end if s.hour==0 and s.minute==0: #print a.activity if re.match(r'.*(L|l)eave',a.activity): return 'leave' rtime=datetime.time(hour=r.hour,minute=r.minute) stime=datetime.time(hour=s.hour,minute=s.minute) etime=datetime.time(hour=e.hour,minute=e.minute) if rtime<stime: return 0 if rtime>=etime: return 0 return "busy"
Chunk referenced in 1
Chunk defined in 9,10,20,21,28,30,31,32,33

5.2 Initialization

We need to start the web diary display with today. Since there may be appointments recorded before today, we skip any of them, and then initialize the various variables required for the web display.

<initialize web page variables 11> =
curapp=0 if len(apps)>0:{Note 11.1} while (curapp < len(apps)) and (apps[curapp].start < today): curapp=curapp+1 startdate=today{Note 11.2} lengthInDays=56{Note 11.3} finishdate=today+datetime.timedelta(days=lengthInDays){Note 11.4} date=startdate slots=[' ' for i in range(quantaInDay)]{Note 11.5} hourmark="<IMG SRC=\"%s/graphics/hourmarker.gif\">" % (server)
Chunk referenced in 7
{Note 11.1}
If there are any appointments, skip any that occur before the first day of display (today).
{Note 11.2}
define the start date displayed in the web page
{Note 11.3}
The number of days displayed in the web page
{Note 11.4}
define the finish date displayed in the web page. This will be lengthInDays long.
{Note 11.5}
initialize all slots to free
<initialize web page 12> = print "Content-type: text/html\n\n"
Chunk referenced in 7
<print web page heading 13> =
print """ <HTML BGCOLOR="white"> <HEAD> <TITLE>AJH Diary Page</TITLE> </HEAD> <BODY> <H1>Welcome to John Hurst's Diary Page!</H1> <P>The following table lists all of John Hurst's appointments for the next eight weeks.</P> <p>The first column is the date (in ISO format), followed by the day of the week. Each appointment is indicated by a red bar (<IMG SRC="http://localhost/ajh/graphics/bookedhour.gif"/>), free times are shown in green (<IMG SRC="http://localhost/ajh/graphics/freehour.gif"/>), and unconfirmed times are shown as yellow (<IMG SRC="http://localhost/ajh/graphics/tentativehour.gif"/>). Times when I am on leave are shown as light blue (<IMG SRC="http://localhost/ajh/graphics/Leave.png"/>). Each hour interval is marked by a vertical black line.</p> <P>Where details of my location are known, the campus is overprinted on the red strip.</P> <P>If you wish to contact me, or make an appointment, or schedule a meeting, then position the cursor over the desired date and time and click. You will be then asked to confirm the date, time and duration of the appointment, and enter your personal details. Note that red, yellow or blue times will be rejected, so you should select only green areas. You can further refine the date and time once you enter the confirm page.</P> <P>Note that Mondays are normally "research days", when I am based at Caulfield. </P> """
Chunk referenced in 7
<print form 14> =
print "<FORM action=\"%s/ajhdiary.py?confirm\" method=\"post\">" % (cgiserver) print "<TABLE BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"0\">" while date < finishdate: <skip saturdays and sundays 15> <handle all appointments for this date 17> dayslots=[0 for i in range(hourSlotsInDay)] for h in range(0,hourSlotsInDay): hour=slots[quantaPerSlot*h:quantaPerSlot*(h+1)] v=reduce(mergeflags,hour) dayslots[h]=v #print string.join(dayslots,'') <print a diary day 16> date=date+datetime.timedelta(days=1) print "</TABLE>" print "</FORM>"
Chunk referenced in 7
<skip saturdays and sundays 15> =
weekday=date.weekday() if weekday>=5: # sat or sun if weekday>=6: # sun print "<TR><TD>&nbsp;</TD></TR>" print "<TR>" print "<TD></TD><TD></TD>" print "<TD></TD><TD></TD>" for h in range(hourSlotsInDay): hs=h+daystart; he=hs+1 imagename="%02d%02d.gif" % (hs,he) print "<TD>"+hourmark+"</TD>" print "<TD><IMG SRC=\"%s/graphics/%s\"/></TD>" % (server,imagename) print "<TD>"+hourmark+"</TD>" print "</TR>" #print "<TR><TD>&nbsp;</TD></TR>" while apps[curapp].start == date: curapp=curapp+1 date=date+datetime.timedelta(days=1) continue
Chunk referenced in 14
<print a diary day 16> =
print "<TR>" print "<TD>"+date.strftime("%Y%m%d")+"</TD>" print "<TD WIDTH=\"10\">"+" "+"</TD>" print "<TD>"+date.strftime("%a")+"</TD>" print "<TD WIDTH=\"10\">"+" "+"</TD>" for h in range(0,hourSlotsInDay): print "<TD>"+hourmark+"</TD>" hs=h+daystart; he=hs+1 hourrange="%02d00-%02d00" % (hs,he) if dayslots[h]==' ': image="freehour.gif"; alt="free" elif dayslots[h]=='h': image="Leave.png"; alt="on leave" elif dayslots[h]=='a': image="Caulfield-free.png"; alt="Caulfield free" elif dayslots[h]=='*': image="bookedhour.gif"; alt="busy" elif dayslots[h]=='L': image="Clayton-appt.png"; alt="Clayton" elif dayslots[h]=='A': image="Caulfield-appt.png"; alt="Caulfield" elif dayslots[h]=='u': image="tentativehour.gif"; alt="unconfirmed" else: pass if dayslots[h]!='-': line="%s/graphics/%s\" ALT=\"%s %s\"" % (server,image,hourrange,alt) button="INPUT type=\"image\" name=\"%s:%s %s\"" % (date,hourrange,alt) line="<"+button+" SRC=\""+line+"></INPUT>" else: ms=0;me=5; line='' for k in range(h*quantaPerSlot,(h+1)*quantaPerSlot): hourrange="%02d%02d-%02d%02d" % (hs,ms,hs,me) if slots[k]==' 'or slots[k]=='a': image="free.gif"; alt="free" elif slots[k]=='u': image="tentative.gif"; alt="unconfirmed" else: image="booked.gif"; alt="busy" link="%s/graphics/%s\" ALT=\"%s %s\"" % (server,image,hourrange,alt) button="INPUT type=\"image\" name=\"%s:%s %s\"" % (date,hourrange,alt) line=line+"<"+button+" SRC=\""+link+"></INPUT>" ms=ms+5;me=me+5 print "<TD>"+line+"</TD>" print "<TD>"+hourmark+"</TD>"+"</TR>" slots=[' ' for i in range(quantaInDay)]
Chunk referenced in 14
<handle all appointments for this date 17> =
while apps[curapp].start == date: cur=apps[curapp] #print cur flag='*' res0=re.match(r'Leave',cur.activity) res1=re.match('Caulfield',cur.location) res2=re.match('Caulfield Day',cur.activity) res3=re.match(r'(Clayton)|(75\.)',cur.location) res5=re.match(r'(CSE4213)',cur.activity) res6=re.match(r'(unconfirmed)',cur.activity) if res0: {Note 17.1} for i in range(quantaInDay): slots[i]='h' elif res3 or res5: flag='L' elif res1: flag='A' elif res2: for i in range(quantaInDay): if slots[i]==' ': slots[i]='a' elif res6: flag='u' if cur.start.hour > 0: st=12*(cur.start.hour-daystart)+cur.start.minute/quantum en=12*(cur.end.hour-daystart)+cur.end.minute/quantum #print "st=%d,en=%d " % (st,en) if st >= 0 and en < 12*(dayend-daystart): pass else: if st < 0: st = 0 if en > 12*(dayend-daystart): en = 12*(dayend-daystart) #print "st=%d,en=%d " % (st,en) for t in range(st,en): if slots[t] != 'h': {Note 17.2} slots[t]=flag #print slots curapp=curapp+1
Chunk referenced in 14
{Note 17.1}
'Leave' overrides all other entries
{Note 17.2}
Don't override holidays!

6. Confirm Appointment Date and Time

There are two parts to the confirmation of appointment time, because one entry point is from the diary page, and a click on a time bar, while the other entry point is when the user changes the time within the confirm page, and wishes to recheck the availablity. The first is handled by doDiryClick, while the second is handled by confirmAppointment.

6.1 The doDiaryClick procedure

doDiaryClick is called when the user clicks on a time bar in the diary display. The handling of this requires a little skullduggery, since the parameters passed to the cgi call are x,y coordinates within the image bar, which are basically of no use. However, the name of the image is part of the parameter name, and hence we can use that to find which image was clicked, and hence what date and time for which the user wishes to make an appointment.

We do this by examining the first form parameter (which happens to be the x coordinate, but that is of no consequence here). It is of the form:

2005-02-01:1600-1700 free

which encodes the year, month, day, time range and activity. By pattern matching against this, we determine the desired appointment time, and pass that to the appointment time confirmation process.

<define diary click handler 18> =
def doDiaryClick(form): value=formkeys[0] res=re.match(r'(.*)\.x$',value) if res: value=res.group(1) else: print "Cannot extract parameter from %s" % (value) exit res=re.match(r'(\d{4})-(\d{2})-(\d{2}):(\d{2})(\d{2})-(\d{2})(\d{2})(.*)$',value) year=int(res.group(1)) month=int(res.group(2)) day=int(res.group(3)) sh=int(res.group(4)) sm=int(res.group(5)) eh=int(res.group(6)) em=int(res.group(7)) activity=res.group(8) dur=60*(eh-sh)+(em-sm) handleConfirm(year,month,day,sh,sm,eh,em,dur)
Chunk referenced in 1

6.2 The confirmAppointment procedure

confirmAppointment is called when the users changes the appointment date or time, and wishes to recheck the availability. In this situation, all the date and time parameters are passed directly, and we do not need to do the same decoding trick used in doDiaryClick. However, we still end up in the handleConfirm procedure as before.

<define confirm appointment times 19> =
def confirmAppointment(form): year=int(form['year'].value) monthname=form['month'].value month=monthnames.index(monthname)+1 day=int(form['day'].value) sh=int(form['hour'].value) sm=int(form['minute'].value) eh=sh; em=sm duration=form['duration'].value dur=convertDurationToInteger(duration) name='appoint' value=form['appointtime'].value #print "<P>Got appoint parms %04d,%02d,%02d</P>" % (year,month,day) handleConfirm(year,month,day,sh,sm,eh,em,dur)
Chunk referenced in 1

6.3 Support Operations for Appointment Confirmation

<define procedures 20> =
def printReturn(msg): print "<P>%s</P>" % (msg)
Chunk referenced in 1
Chunk defined in 9,10,20,21,28,30,31,32,33
<define procedures 21> =
def handleConfirm(year,month,day,sh,sm,eh,em,dur): printHtmlHeaders("Confirm Appointment Data and Time") monthname=monthnames[month-1] print "<P>You have asked for an appointment on %d %s %4d, " % (day,monthname,year) print "starting at %02d:%02d " % (sh,sm) print "for %3d minutes" % (dur) #print "Activity: %s</P>" % (activity) req=datetime.datetime(year=year,month=month,day=day,hour=sh,minute=sm) print """<P>If you wish, you can change the date, time or duration before confirming. If you do change the date or time, make sure to click the changed time button.</P>""" <check time in appointments list 22> <print check response 23> print "<FORM action=\"%s/ajhdiary.py\" method=\"post\">" % (cgiserver) print "<P>Date: " popupMenu("year",[2005,2006,2007],year) popupMenu("month",monthnames,monthname) popupMenu("day",range(1,32),day) print "</P><P>Time (24hr): " popupMenu("hour",range(daystart,dayend),sh) popupMenu("minute",range(0,60,5),sm) print "</P><P>Duration: " duration=convertDurationToString(dur) durtimes=['0:05 mins','0:10 mins','0:15 mins','0:20 mins','0:30 mins','0:40 mins','0:45 mins','1:00 hour','1:15 hours','1:30 hours','2:00 hours','> 2 hours'] popupMenu("duration",durtimes,duration) print "</P>" datentime="%04d-%02d-%02d:%02d%02d-%02d%02d" % (year,month,day,sh,sm,eh,em) print "<INPUT type=\"hidden\" name=\"appointtime\" value=\"%s\"/>" % (datentime) msg="I've changed the time, please check if it is available" mode='appoint' print "<INPUT type=\"submit\" name=\"%s\" value=\"%s\"></INPUT>\n" % (mode,msg) if not res: msg="I'm happy with this time. Click to Confirm" mode='details' print "<INPUT type=\"submit\" name=\"%s\" value=\"%s\"></INPUT>\n" % (mode,msg) print "</FORM>"
Chunk referenced in 1
Chunk defined in 9,10,20,21,28,30,31,32,33
<check time in appointments list 22> =
res=0 for ap in apps: res = withinApp(req,dur,ap) if res: break
Chunk referenced in 21
<print check response 23> =
#print "<P>%s</P>" % (res) if res=='leave': print "<H2>" printReturn("Sorry, John is on leave then. Please select another time.") print "</H2>" if res=='busy': print "<H2>" printReturn("Sorry, John is not available then. Please select another time.") print "</H2>"
Chunk referenced in 21 24

7. Grab User Details

<define grab details 24> =
<define textBox 25> <define hiddenData 26> def grabDetails(val): printHtmlHeaders("Enter Your Details") #print form name='' email='' why='' year=int(form['year'].value) monthname=form['month'].value month=monthnames.index(monthname)+1 day=int(form['day'].value) sh=int(form['hour'].value) sm=int(form['minute'].value) eh=sh; em=sm duration=form['duration'].value dur=convertDurationToInteger(duration) if form.has_key('Name'): name=form['Name'].value if form.has_key('EMail'): email=form['EMail'].value if form.has_key('Purpose'): why=form['Purpose'].value loc='' if form.has_key('Location'): loc=form['Location'].value req=datetime.datetime(year=year,month=month,day=day,hour=sh,minute=sm) res=0 for ap in apps: res = withinApp(req,dur,ap) if res: break if res: <print check response 23> print "<P>Please use your browser back button to return and correct the time</P>\n" sys.exit(0) #print "<P>Got appoint parms %04d,%02d,%02d</P>" % (year,month,day) print "<P>You have asked to make an appointment for %d %s %4d, " % (day,monthname,year) print "starting at %02d:%02d " % (sh,sm) print "for %d minutes." % (dur) #print "Activity: %s</P>" % (activity) print "<P>Please enter the following details about yourself: "+\ "(Please bear in mind that the appointment is not confirmed "+\ "until these details are supplied)</P>" req=datetime.datetime(year=year,month=month,day=day,hour=sh,minute=sm) print "<FORM action=\"%s/ajhdiary.py?details\" method=\"post\">\n" % (cgiserver) hiddenData('year',year) hiddenData('month',monthname) hiddenData('day',day) hiddenData('hour',sh) hiddenData('minute',sm) hiddenData('duration',duration) textBox("Please enter your name",'Name',name,1,40) textBox("Please enter your email address"+\ " (this will be used to confirm your appointment,"+\ " so please type it carefully)", 'EMail',email,2,80) textBox("Please enter the reason for this appointment",'Purpose',why,2,80) popupMenu("Location",['Clayton','Caulfield'],loc) print "<INPUT type=\"submit\" name=\"email\" value=\"%s\"></INPUT>\n" \ % "Details are correct, make appointment" print "</FORM>\n" pass
Chunk referenced in 1
<define textBox 25> =
def textBox(msg,name,val,rows,cols): print "<P>%s<BR/>" % (msg) print "<TEXTAREA name=\"%s\" rows=\"%d\" cols=\"%d\">" % \ (name,rows,cols) print "%s</TEXTAREA></P>" % (val) return
Chunk referenced in 24
<define hiddenData 26> =
def hiddenData(name,value): print "<INPUT type=\"hidden\" name=\"%s\" value=\"%s\"/>" % (name,value) return
Chunk referenced in 24

8. EMail Confirmation Phase

8.1 Define the EMail Confirmation procedure

The email confirmation has very little to do, basically collecting up the appointment data, and emailing it to ajh for confirmation. It does a check to ensure that all data is present, and asks the user to step back and reenter if all data is not present. This is really a short cut to avoid having another phase to check the data.

The new appointment data is appended to the tentative appointments file, diary.new if all is OK. Note that this file must be universally writeable, since the program operates as user 'nobody'.

<define the email confirmation procedure 27> =
def doEMailConfirmation(form): if form.has_key('EMail'): adr=form['EMail'].value else: adr='' if form.has_key('Name'): sub=form['Name'].value else: sub='' if form.has_key('Purpose'): why=form['Purpose'].value else: why='' year=int(form['year'].value) month=monthnames.index(form['month'].value)+1 day=int(form['day'].value) hrs=int(form['hour'].value) mins=int(form['minute'].value) duration=form['duration'].value dur=convertDurationToInteger(duration) eh=hrs;em=mins+dur while em>=60: eh=eh+1; em=em-60 msg=sub + \ " has requested an appointment at " + \ form['Location'].value + \ " on " + \ form['year'].value + " " + \ form['month'].value + " " + \ form['day' ].value + " at " + \ ("%02d:%02d" % (hrs,mins)) + \ (" for %s. " % (duration)) + \ "\n\nPurpose: " + why if adr and sub and why: printHtmlHeaders("Appointment Awaiting Confirmation") sendEMail(adr,sub,msg) tentf=open("%s/diary.new" % (webhomedir),'a') tentf.write("%04d%02d%02d %02d%02d-%02d%02d %s\n" % \ (year,month,day,hrs,mins,eh,em,why)) tentf.close() else: printHtmlHeaders("You have not entered all fields!") print """<P>Please use your browser back button to return and enter a value in each field.</P>""" pass
Chunk referenced in 1

8.2 Define the sendEMail Procedure

The sendEMail procedure is responsible for emailing ajh the details of the appointment, so that it can be confirmed. The actual details of confirmation are not part of this program, but require the information in the diary.new file to be transferred to the diary file. This effectively converts yellow coloured tentative appointments in the diary page to red coloured times.

The details of the actual command to send the email may be system specific.

<define procedures 28> =
def sendEMail(adr,sub,msg): msgf=open("%s/msg-Appointment" % (tmpdirectory),'w') msgf.write("To: ajh@csse.monash.edu.au\n") msgf.write("Cc: %s\n" % (adr)) msgf.write("From: Appointments@csse.monash.edu.au\n") msgf.write("Reply-to: %s\n" % (adr)) msgf.write("Subject: Making Appointment for %s\n\n" % (sub)) msgf.write(msg) msgf.close() cmd="%s -t -v -oi <%s/msg-Appointment >/dev/null" % (sendmail,tmpdirectory) os.system(cmd) print "<P>Details of your appointment request have been emailed to John Hurst, who will confirm your appointment by email.</P>" msg="Click here to return to Diary Page" mode="diary" print "<FORM action=\"%s/ajhdiary.py\" method=\"post\">" % (cgiserver) print "<INPUT type=\"submit\" name=\"%s\" value=\"%s\"></INPUT>\n" % (mode,msg) print "</FORM>" return
Chunk referenced in 1
Chunk defined in 9,10,20,21,28,30,31,32,33

9. Support Operations

needHeaders is a global variable that identifies whether the Content/type http protocol header has to be issued before generating any other output.

<define variables 29> = needHeaders = 1
Chunk referenced in 1
Chunk defined in 3,29
<define procedures 30> =
def printHtmlHeaders(title): global needHeaders if not needHeaders: return print "Content-Type: text/html\n\n" print """<HTML> <head> <TITLE>%s</TITLE> </head> <BODY BGCOLOR=\"#f0fff0\" LINK=\"#000080" VLINK=\"#c08000\" ALINK=\"#0000ff\"> """ % (title) print "<h1>%s</h1>" % (title) needHeaders = 0
Chunk referenced in 1
Chunk defined in 9,10,20,21,28,30,31,32,33
<define procedures 31> =
def popupMenu(name,options,default): print "<SELECT name=\"%s\">" % (name) for opt in options: isdef='' if opt==default: isdef=" selected" print "<OPTION%s>%s</OPTION>" % (isdef,opt) print "</SELECT>"
Chunk referenced in 1
Chunk defined in 9,10,20,21,28,30,31,32,33
<define procedures 32> =
def convertDurationToString(d): if d<60: return "0:%02d mins" % (d) if d==60: return "1:00 hour" if d>120: return ">2 hours" h=0 while d>60: h=h+1 d=d-60 return "%d:%02d hours" % (h,d)
Chunk referenced in 1
Chunk defined in 9,10,20,21,28,30,31,32,33
<define procedures 33> =
def convertDurationToInteger(d): res=re.match(r'0:(\d{2}) mins',d) if res: return int(res.group(1)) else: res=re.match(r'(\d):(\d{2}) hour',d) if res: return 60*int(res.group(1))+int(res.group(2)) else: return 121{Note 33.1} pass
Chunk referenced in 1
Chunk defined in 9,10,20,21,28,30,31,32,33
{Note 33.1}
greater than two hours

10. Index

10.1 File Definitions

File Name Defined in
ajhdiary.py 1

10.2 Chunk Definitions

Chunk Name Defined in Used in
check time in appointments list 22 21
collect cgi parameters and invoke appropriate part 6 1
current date 35
current version 34
define confirm appointment times 19 1
define diary click handler 18 1
define diary page parameters 8 1
define generate web page 7 1
define grab details 24 1
define hiddenData 26 24
define procedures 9, 10, 20, 21, 28, 30, 31, 32, 33 1
define procedures 9, 10, 20, 21, 28, 30, 31, 32, 33 1
define procedures 9, 10, 20, 21, 28, 30, 31, 32, 33 1
define procedures 9, 10, 20, 21, 28, 30, 31, 32, 33 1
define server addresses 4 3
define textBox 25 24
define the email confirmation procedure 27 1
define variables 3, 29 1
define variables 3, 29 1
handle all appointments for this date 17 14
import definitions 2 1
initialize web page 12 7
initialize web page variables 11 7
print a diary day 16 14
print check response 23 21, 24
print form 14 7
print web page heading 13 7
read diary file and collect appointments 5 1
skip saturdays and sundays 15 14

10.3 Variable Definitions

Identifier Defined in Used in

Document History

<current version 34> = 0.1.4
<current date 35> = 20050726:133108
20050120:144523 ajh 0.0.0 first version
20050121:143848 ajh 0.0.1 restructured to make more literate
20050126:175733 ajh 0.0.2 merged with make-appt, refined confirm appointment, and added (empty) grab details section
20050128:103354 ajh 0.1.0 extensive changes to bring it up to first production version
20050128:134946 ajh 0.1.1 added part hour displays in diary page
20050129:180457 ajh 0.1.2 cleaned up literacy and added variable markup
20050425:202459 ajh 0.1.3 fixed bug that stopped 8am appointments being displayed
20050726:133108 ajh 0.1.4 minor tidy up