Kalender
Løsningen er enkel fordi den betrakter notater for en dag som en tekst. Det er altså ikke noen inndeling i timer for hver dag. Det er heller ikke noen kategorisering av begivenheter eller brukere. Adgangskontroll kan gjøres på to nivåer:
- Du kan sette adgangskontroll til kalenderen i sin helhet. Enten ved å lage en egen passordbeskyttet inngangsside eller ved å bruke .htaccess, slik som forklart i modulen: Autentisering eller ved å bruke et passord i et skript. Det første er sikrere enn det andre.
- Du kan sette passord for å oppdatere kalenderen ved å bruke passord slik som beskrevet i skriptet som er sitert nedenfor.
Du kan teste en vidåpen versjon her, ingen adgangskontroll på noe nivå:
Denne tømmes med ujevne mellomrom for data, og det er lagt begrensninger på hvor langt fram og tilbake du kan gå i tid.
Løsningen er basert på tre filer:
- kalender.py. Her ligger hele funksjonaliteten: presentasjon av års- og månedsoversikter, registrering av endringer etc.
- month_template.html. Template for å lage hovedsiden for kalenderen
- printmonth_template.html. Template for å lage en versjon av kalenderen som er egnet for utskrift
De to siste kunne vært lagt inn som tekstkonstanter i skriptet, men de er valgt holdt utenfor fordi jeg finner det enklere å vedlikeholde og modifisere løsningen på denne måten. Ut fra en ren effektivitetsvurdering burde de vært inkludert ( en fil mindre å åpne).
Datalagring
Data lagres som enkle XML-filer, en fil pr måned. Skriptet lager slike filer etter behov. Et utsnitt fra en månedsfil kan se slik ut:
<?xml version="1.0" encoding="utf-8"?>
<notes>
<day dayno="7">
kl 0915 Tannlegen#kl 1515 Forelesning#
</day>
<day dayno="10">
kl 0915 Forelesning#Få ferdig evalueringsrapport
</day>
...
</notes>
Linjeskift er lagret som #. Konverteringen skjer bak kulissene og brukeren trenger ikke tenke på dette. Det er nødvendig for å kunne vise teksten fram på en enhetlig måte i de aktuelle nettleserne som handterer linjeskift litt forskjellig.
Pythonskriptet
#! /usr/bin/python
# -*- coding: cp1252 -*-
import calendar,xml.dom.minidom,os,time,cgi,sys
import cgitb; cgitb.enable()
#--------------------------------------------------
# Manage a webbased calendar
# Module responds to 5 main commands:
#
# - prepareMonth(year,month,day)
# Prepares and returns a calender for selected month
#
# - updateDay(year,month,day)
# Updates data for the day and returns the monthpage
#
# - start()
# Prepares and returns a calender for this month
#
# - preparePrintMonth(year,month,day)
# Prepares and returns a calender for the month, suitable for print
#
# - prepareYear(year)
# Prepares and returns a simple calender page for selected year
#
# Data is organized in one XML-file for each month.
# These files are generated as needed. Must be within a
# cataog with writepermission for everyone
# Data is simple:
# <notes>
# <day dayno="1">
# Velkommen til en ny måned
# </day>
# .....
# </notes>
# NOTE: linebreaks are stored as #
# The skeleton files:
# - MONTHFILE
# - PRINTMONTHFILE
# must be available with paths as stated relative to
# this script, see below. All datafiles are stored
# in catalog DATAPATH
#--------------------------------------------------
#--------------------------------------------------
# Adjust these values according to your installation
# and language
#--------------------------------------------------
# Absolute URL to this script
SCRIPTPATH='http://www.ia.hiof.no/~borres/cgi-bin/demokalender/demokal.py'
# Path to where XML-files will be stored, relative to this script
# Make sure that we can make files and write to them in this catalog:
DATAPATH='data/'
# Template for web page with monthly calendar, relative to script:
MONTHFILE='month_template.html'
# Template for web page with monthly calendar for print, relative to script:
PRINTMONTHFILE='printmonth_template.html'
# if you want to use a Password for update of the calendar
USEPASSWORD=False
# Password for update, effective when USEPASSWORD=True
PASSWD='pass'
#--------------------------------------------------
# language. These are strings generated by the script
# You may also change the templatefiles:
# MONTHFILE and PRINTMONTHFILE
#--------------------------------------------------
# month-names, dummy to handle 0 automatically
monthlist=['dummy','januar','februar','mars','april','mai',
'juni','juli','august','september','oktober',
'november','desember']
# day-names
daylist=['mandag','tirsdag','onsdag','torsdag','fredag',
'lørdag','søndag']
# Password announcment: Passwd to update:
NEEDPWD='Passord for å oppdatere: '
# message : Bad Password
WRONGPWD='Galt passord'
# announcing an empty day, no notes
NONOTES='ingen notater'
#--------------------------------------------------
# helpers to read and write text-files
#--------------------------------------------------
def loadFile(fname):
try:
file=open(fname,'r')
res=file.read()
file.close()
return res
except:
reportErrorAndExit('Cant read: '+fname)
def storeFile(fname,content):
try:
outf=open(fname,'w')
outf.write(content)
outf.close()
except:
reportErrorAndExit('Error writing: '+ fname)
#-------------------------------------------------------
# report error and quit
#-------------------------------------------------------
def reportErrorAndExit(msg):
S="""<html><head><title>error</title></head>
<body><p>%s</p>
<p><a href="javascript:history.back()"><<<</a></p>
</body></html>
"""
print S%msg
sys.exit()
#--------------------------------------------------
# utility to calculate correct filename
#--------------------------------------------------
def getMonthsXMLFilename(year,month):
return DATAPATH+'data_'+str(year)+'_'+str(month)+'.xml'
#---------------------------------------------
# Prepare the content of a cell for one day in
# main calendar page
#---------------------------------------------
def getCellContent(day,notes):
# three possible class attributes
# nullday:days out of month
# empty: no notes
# content: with a note
if day==0:
return '\n<td class="nullday"> </td>'
classatt='content'
if notes.has_key(day):
if len(notes[day].strip())< 1:
classatt='empty'
else:
classatt='empty'
T="""\n<td id="t%s" class="%s" style="cursor:pointer"
onclick="javascript:showday(%s)"> %s </td>"""
return T%(str(day),classatt,str(day),str(day))
#--------------------------------------------------
# Prepare a list of refs to (this and)neighbouring months
#--------------------------------------------------
def makeMonthRefList(year, month):
S='[<a href="%s?command=show&month=12&year=%s">'\
%(SCRIPTPATH,str(year-1))
S+=str(year-1)+' <-</a>] \n'
for i in range(1,13):
S+='[<a href="%s?command=show&month=%s&year=%s">'\
%(SCRIPTPATH,str(i),str(year))
S+=monthlist[i][0:3]+'</a>] \n'
S+='[<a href="%s?command=show&month=1&year=%s">'\
%(SCRIPTPATH,str(year+1))
S+=' -> '+str(year+1)+'</a>] \n'
return S
#--------------------------------------------------
# Prepare a table for one month in main calendar page
#--------------------------------------------------
def makeTable(year,month,notes):
weeklist=calendar.monthcalendar( year, month)
# heading
T='<tr>'
for d in range(0,7):
T+='<th width="14%">'+daylist[d][:3]+'</th>'
T+='</tr>'
# content
for week in weeklist:
T+='\n<tr>\n'
for day in week:
# prepare the content of this cell, including td-tags
T+=getCellContent(day,notes)
T+='\n</tr>\n'
return T
#------------------------------------------------
# Prepare a dictionary with all entries for this month
# Produce a new file if necessary
#------------------------------------------------
def prepareDictionary(year,month):
# first we must check if the appropriate XML-file exists
filename=getMonthsXMLFilename(year,month)
if not(os.path.exists(filename)):
# we must make it
xmlstr="""<?xml version="1.0" encoding="ISO-8859-1"?>
<notes>
<day dayno="1">
Velkommen til %s
</day>
</notes>"""
storeFile(filename,xmlstr%monthlist[month])
# we have what we want
noteDict={}
s=loadFile(filename)
# dont bother about things that may look like entities
s=s.replace('&','&')
dom=xml.dom.minidom.parseString(s)
daylist=dom.getElementsByTagName('day')
for day in daylist:
dayno=int(day.getAttribute('dayno').encode('ISO-8859-1'))
note=day.firstChild.data.encode('ISO-8859-1')
if len(note.strip()) > 0:
noteDict[dayno]=note
return noteDict
#-------------------------------------------------
# prepare all stuff to hidden fields:
# daily notes and the monthname
#-------------------------------------------------
def makeDayContent(year,month,notes):
S=''
for i in range(0,32):
T=NONOTES
if notes.has_key(i):
T=notes[i]
S+='\n<div id="%s" class="hiddenday">%s</div>'%(str(i),T)
S+='\n<div id="monthname" class="hiddenmonth">%s</div>'%monthlist[month]
return S
#--------------------------------------------------
# Prepare main calendar web page
#--------------------------------------------------
def prepareMonth(year,month,day):
if day <= 0:
day=1
noteDictionary=prepareDictionary(year,month)
S=loadFile(MONTHFILE)
# shortcut to this day
daytext=NONOTES
if noteDictionary.has_key(day):
daytext=noteDictionary[day].strip()
daytext=daytext.replace('#','\n')
daytext=daytext.replace('\n\n','\n')
S=S.replace('#daytext#',daytext)
S=S.replace('#day#',str(day))
T=makeTable(year,month,noteDictionary)
S=S.replace('#table#',T)
T=makeMonthRefList(year, month)
S=S.replace('#reflist#',T)
S=S.replace('#month#',str(month))
S=S.replace('#monthname#',monthlist[month])
S=S.replace('#year#',str(year))
S=S.replace('#next_year#',str(year+1))
S=S.replace('#previous_year#',str(year-1))
T=makeDayContent(year,month,noteDictionary)
S=S.replace('#content#',T)
S=S.replace('#scriptpath#',SCRIPTPATH)
if USEPASSWORD:
S=S.replace('#password',NEEDPWD+'<input type="password" name="password"/>')
else:
S=S.replace('#password','')
print S
#--------------------------------------------------
# Update notes for one day
#--------------------------------------------------
def updateDay(year,month,day,content):
# fix content such that lf or cr-lf is replaced by #
content=content.replace('\r\n','\n')
content=content.replace('\n\n','\n')
content=content.replace('\n','#')
notes=prepareDictionary(year,month)
notes[day]=content
# dictionary to XML-string
T='<?xml version="1.0" encoding="ISO-8859-1"?>\n<notes>%s</notes>'
S=''
for i in range(0,32):
if notes.has_key(i):
S+='\n<day dayno="%s">\n%s\n</day>'%(str(i),notes[i].strip())
T=T%S
# save updated XML-file
storeFile(getMonthsXMLFilename(year,month),T)
#--------------------------------------------------
# Start, prepares for this month
#--------------------------------------------------
def start():
# makes a calendar for the month we have just now
t=time.localtime()
prepareMonth(t[0],t[1],t[2])
#-----------------------------------------------------
# Prepare the printable HTML-page
#-----------------------------------------------------
def preparePrintMonth(year,month,theDay):
# get the skeleton file
htmlstr=loadFile(PRINTMONTHFILE)
# get the data
notes=prepareDictionary(year,month)
# make the table, outer loop is week
# inner is day
weeklist=calendar.monthcalendar( year, month)
# heading
T='<tr>'
for d in range(0,7):
T+='<th>'+daylist[d]+'</th>'
T+='</tr>'
# content
for week in weeklist:
T+='\n<tr>\n'
for day in week:
if day==0:
T+='<td></td>'
continue
# prepare the content of this cell
T+='\n<td>'
T+='<div class="verysmall">'+str(day)+'.</div>'
S=''
if notes.has_key(day):
S=notes[day].strip()
S=S.replace('#','<br/>')
T+='\n<div>%s</div>'%S
T+='\n</tr>\n'
htmlstr=htmlstr.replace('#table#',T)
htmlstr=htmlstr.replace('#monthname#',monthlist[month])
htmlstr=htmlstr.replace('#month#',str(month))
htmlstr=htmlstr.replace('#year#',str(year))
htmlstr=htmlstr.replace('#day#',str(theDay))
htmlstr=htmlstr.replace('#scriptpath#',SCRIPTPATH)
print htmlstr
#-------------------------------------------------------
# print the calendar for this year
#-------------------------------------------------------
def prepareYear(y=2005):
calendar.setfirstweekday(calendar.MONDAY)
import cStringIO
saveout = sys.stdout
tmpF=cStringIO.StringIO()
sys.stdout = tmpF
calendar.prcal(y)
sys.stdout = saveout
T=tmpF.getvalue()
org_monthlist=['dummy','January','February','March','April','May','June',
'July','August','September','October','November','December']
org_daylist=['Mo','Tu','We','Th','Fr','Sa','Su']
for i in range(1,len(monthlist)):
T=T.replace(org_monthlist[i],'<strong>'+monthlist[i]+'</strong>')
for i in range(0,len(daylist)):
T=T.replace(org_daylist[i],'<span style="color:blue">'+daylist[i][0:2]+'</span>')
T=T.replace(str(y),'<strong>'+str(y)+'</strong>')
S='<html><head><title>aar</title></head><body><pre>'
S+=T
S+='</pre>'
S+='<div><a href="javascript:history.back()"><<<</a></div>'
S+='</body></html>'
print S
#-----------------------------------------------------
# The scriptpart finding out what to do
#
form=cgi.FieldStorage()
print 'Content-type: text/html; charset=iso-8859-1\n'
#-----------------------------------------
# Preparing some defaults
# Tell calender that monday is first day of week
calendar.setfirstweekday(calendar.MONDAY)
# Do we have a Password, only used when update
pwd='no-pass-wrd'
if form.has_key('password'):
pwd=form['password'].value
# default Date is today: y,m,d
t=time.localtime()
this_y,this_m,this_d=t[0],t[1],t[2]
y,m,d=t[0],t[1],t[2]
if form.has_key('year'):
# we allow only 4 years ahead and back
y=min(max(int(form['year'].value),this_y-4),this_y+4)
if form.has_key('month'):
m=min(max(int(form['month'].value),1),12)
if form.has_key('day'):
d=min(max(int(form['day'].value),1),calendar.monthrange(y,m)[1])
#notes for the day (updating)
c=''
if form.has_key('daytext'):
c=form['daytext'].value
#-------------------------------------------------
# what is the job ?
#-------------------------------------------------
if form.has_key('command'):
cmd=form['command'].value
if cmd=='show':
# show the actual month
prepareMonth(y,m,1)
elif cmd=='update':
# Password control ? and !cookie set
if USEPASSWORD and pwd!=PASSWD:
reportErrorAndExit(WRONGPWD)
# update a certain day and then show the actual month
# set cookie
updateDay(y,m,d,c)
prepareMonth(y,m,d)
elif cmd=='print':
# show printversion of the actual month
preparePrintMonth(y,m,d)
elif cmd=='printyear':
# show printversion of the actual year
prepareYear(y)
else:
# show this month
start()
else:
start()
Merk at skriptet gjør en rekke replace-kall for å befolke templatefilene med riktige verdier. Templatefilene inneholder en rekke stringer av typen #something# som skal erstattes av skriptet.
Javascript
Template-fila, month_template.html, som viser fram en månedsoversikt bruker et javascript for å la brukeren navigere på dagene i måneden. Dette baserer seg på at alle dagsnotatene er plasserte i HTML-file i skjulte div-elementer (display:none). Når brukeren trykker på en dag, hentes innholdet i det skjulte feltet fram og plasseres i en textboks, textarea, hvor det kan inspiseres og endres. Javascriptet som gjør dette ser slik ut:
function showday(dayno)
{
//---------------------------
// set header to daily note
headerNode=document.getElementById("todayheader");
monthNode=document.getElementById("monthname");
while(headerNode.hasChildNodes())
headerNode.removeChild(headerNode.lastChild);
daystr="";
if (dayno != 0)
daystr=""+dayno+". "+monthNode.innerHTML;
headerNode.appendChild(document.createTextNode(daystr));
//---------------------------
//mark hidden input field day input
dayInputNode=document.getElementById("inputday");
dayInputNode.setAttribute("value",dayno);
//-----------------------------
// display content for this day in textarea
fromNode=document.getElementById(dayno);
s=fromNode.firstChild.data;
while (s.indexOf('\n')!=-1)
s=s.replace('\n','')
while (s.indexOf('#')!=-1)
s=s.replace('#','\n')
while (s.indexOf('\n\n')!=-1)
s=s.replace('\n\n','\n')
window.document.form1.daytext.value = s;
}