John wrote:
> Steve,
>
> If you're interested in just banging out a Python app, though, my
> experience
> was writing a calendaring tool for a group of my friends who get
> together
> [...]
> This sounds very cool, is it something you could post?
Okay. It's not the greatest implementation (as I said it was kind of a quick
hack) but here it is...
A few notes to help understand the app:
It's a single stand-alone CGI script in Python, and serves the three calendar
views (scheduled games, the form for players to vote on good dates, and a form
for the admin to decide on the game dates based on the votes) within this one
script.
It stores the data for the calendar in a MySQL database. The users need to
authenticate to the web server in order to get to the page, so we can look in
the script's environment to see who they are (so we know who voted for what
days, and who gets the admin access page).
The admin (and only the admin) can add a "?for=username" to the URL to access
the application as if they were one of the players (in case they can't get in
to the calendar but wish to change their vote data).
#!/usr/bin/python
#
# Display a calendar of our game days, allow "voting" for what days any person
# is available or not.
#
import cgitb; cgitb.enable()
import os
import sys
import calendar
import datetime
import cgi
import MySQLdb
monthnames = ('January', 'February', 'March',
'April', 'May', 'June',
'July', 'August', 'September',
'October', 'November', 'December')
daynames = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')
calendar.setfirstweekday(6) # start weeks on Sunday
currentdaybg = "#999933"
votecolors = ( "#ff0000", "#00ff00", "#ffff00" )
form = cgi.FieldStorage()
today = datetime.date.today()
print "Content-Type: text/html\n"
print '<html><body bgcolor=#000000 link=#ffffff vlink=#eeeeee text=#ffffff>'
if 'year' in form:
year = int(form.getfirst('year'))
if not 2000 < year < 2020:
print "Sorry, we're not supporting that year right now."
sys.exit(0)
else:
year = today.year
print """<style>
.popup
{
COLOR: #9999ff;
CURSOR: help;
TEXT-DECORATION: none;
}
</style>"""
if 'REMOTE_USER' not in os.environ:
print "Sorry, this page cannot be used without authentication."
sys.exit(0)
me = os.environ['REMOTE_USER']
admin = (me == 'login-name-of-admin-1' or me == 'login-name-of-admin-2')
#admin = False
mode = 'normal'
forWhom = None
if admin and 'for' in form:
me = form.getfirst('for')
forWhom=me
print "***",me,"***"
if 'mode' in form:
mode = form.getfirst('mode')
if mode == 'admin' and not admin:
print "Sorry, restricted access to this application is denied."
sys.exit(0)
if mode == 'admin' or mode == 'vote': print '<form action=calendar method=POST>'
if mode == 'vote': print '''
<H1>Voting For Game Dates</H1>
Generally, we play on Saturdays. If there's a special date we are considering
playing on, such as a holiday,
indicate your preference for that, but otherwise we're looking just for
Saturdays.
<P>
Indicate your preferences by selecting the appropriate response under each date:
<blockquote>
<b>---</b>: I am available, most likely, and willing to play that date.<br>
<b>Best</b>: This date is <b>particularly good</b> for me. Please play on that
date if possible.<br>
<b>NO</b>: This date is not good, I most likely will <b>not</b> be able to play.
</blockquote>
The default is "available", so if you're ok with playing any weekend in a
month, just
leave them all as "---". Don't mark them all as "best". The "best" setting is
provided
so that you can indicate unusually favorable dates, like when your SO is away
or something.
<P>
When finished, click the "Save" button at the bottom. You'll be brought back
to this form again
in case you want to make more changes. Click the "Return to Calendar View"
link (or navigate
away from this page) when you're done and have saved all the changes you want
to keep.
<P>
'''
#
# Connect to database
#
db = MySQLdb.connect(host='HHH', user='XXX', passwd='YYY', db='ZZZ')
#
# Accept updates from user
#
votelist = None
if mode == 'vote' or mode == 'admin':
for key in form:
if len(key) == 4:
try:
vmonth = int(key[0:2])
vdate = int(key[2:4])
vv = form.getfirst(key)
except:
# must not be the four-digit key we're looking
for
continue
if 1 <= vmonth <= 12 and 1 <= vdate <= 31:
if votelist is None: votelist = []
if mode == 'vote':
if vv == '-':
pass # just let the old vote
(if any) die
elif vv == 'Y':
votelist.append((datetime.date(year,vmonth,vdate), me, True))
elif vv == 'N':
votelist.append((datetime.date(year,vmonth,vdate), me, False))
else:
print "***WARNING*** Invalid
vote field encountered; vote for %d/%d NOT counted.<br>" % (vmonth,vdate)
elif mode == 'admin':
if vv == '-':
pass # just let the old vote
(if any) die
elif vv == 'P':
votelist.append((datetime.date(year,vmonth,vdate), 'GAME', True))
elif vv == 'A':
votelist.append((datetime.date(year,vmonth,vdate), 'GAME', False))
else:
print "***WARNING*** Invalid
schedule field encountered; setting for %d/%d NOT counted.<br>" % (vmonth,vdate)
if votelist is not None:
#
# Record new list of votes for this user
#
if mode == 'admin': duser = 'GAME'
else: duser = me
q = db.cursor()
q.execute('DELETE FROM votes WHERE vuser=%s AND vdate >= %s AND vdate
<= %s', (duser, '%04d-01-01'%year, '%04d-12-31'%year))
q.executemany('INSERT INTO votes (vdate, vuser, vote) values
(%s,%s,%s)', votelist)
logfile = open("/var/log/gamecal", "a")
print >>logfile, "%d %s %s %s" % (year, os.environ['REMOTE_USER'],
duser, votelist)
logfile.close()
myvotes = {}
allvotes = {}
gamedates = {}
if mode == 'vote':
#
# Get my votes from database to display
# --> myvotes
# maps 'mmdd':0/1 (0=no, 1=yes). Dates not in dict=no
preference
#
q = db.cursor()
q.execute('SELECT vdate, vote FROM votes WHERE vuser=%s AND vdate >= %s
AND vdate <= %s', (me, '%04d-01-01'%year, '%04d-12-31'%year))
for vote in q.fetchall():
myvotes['%02d%02d' % (vote[0].month, vote[0].day)] = vote[1]
if mode == 'admin':
#
# Get everyone's votes from database
# --> gamedates
# maps 'mmdd':'ALT'/'PRI' (alternate/primary play date)
# --> allvotes
# maps 'mmdd':(0/1/2, string) 0=no, 1=yes, 2=mixed; string
describes votes collected
#
q = db.cursor()
q.execute('SELECT vdate, vuser, vote FROM votes WHERE vdate >= %s AND
vdate <= %s', ('%04d-01-01'%year, '%04d-12-31'%year))
for vote in q.fetchall():
key = '%02d%02d' % (vote[0].month, vote[0].day)
if vote[1] == 'GAME':
gamedates[key] = ('ALT','PRI')[vote[2]]
elif key not in allvotes:
allvotes[key] = [vote[2], "%s: %s" % (vote[1],
('no','best')[vote[2]])]
else:
if allvotes[key][0] != vote[2]: allvotes[key][0] = 2
allvotes[key][1] += "; %s: %s" % (vote[1],
('no','best')[vote[2]])
else:
q = db.cursor()
q.execute("SELECT vdate, vote FROM votes WHERE vdate >= %s AND vdate <=
%s AND vuser='GAME'", ('%04d-01-01'%year, '%04d-12-31'%year))
for vote in q.fetchall():
key = '%02d%02d' % (vote[0].month, vote[0].day)
gamedates[key] = ('ALT','PRI')[vote[1]]
if mode == 'admin' or mode == 'vote':
print '<center><font size=5><b>Editing %d</b></font></center><P><P>' %
year
else:
print '''
<table border=0 width=100%%>
<tr>
<td align=left>
<font color=#666666 size=3><a href="calendar?year=%d">%d</a></font>
</td><td align=center>
<font color=#ffffff size=5>%d</font>
</td><td align=right>
<font color=#666666 size=3><a href="calendar?year=%d">%d</a></font>
</td>
</tr>
</table><P><P>
''' % (year-1,year-1, year, year+1,year+1)
print '''
<center>
<table border=0>
'''
for month in range(0, 12, 3):
print "<tr><td> </td></tr><tr>"
caldates = []
#
# Month Names
#
for m in range(0,3):
print "<th colspan=7><font
size=+2>%s</font></th><td> </td>" % monthnames[month+m]
caldates.append(calendar.monthcalendar(year,month+m+1))
print "</tr><tr>"
#
# Day Names
#
for m in range(0,3):
for d in range(0,7):
print "<th><small>%s</small></th>" % daynames[d]
print "<td></td>"
print "</tr>"
#
# Dates
#
for week in range(0,max([len(i) for i in caldates])):
print "<tr>"
for m in range(0,3):
if week >= len(caldates[m]):
print "<td colspan=7></td>"
else:
for d in range(0,7):
if caldates[m][week][d] == 0:
print "<td></td>"
else:
key = '%02d%02d' % (month+m+1,
caldates[m][week][d])
if mode == 'admin' and key in
allvotes:
print "<td align=right
bgcolor="+votecolors[allvotes[key][0]]+'><span title="%s"
class="popup">%d</span></td>' % (allvotes[key][1], caldates[m][week][d])
elif key in gamedates:
if gamedates[key] ==
'PRI':
print "<td
align=right bgcolor=#009900><b>[%d]</b></td>" % caldates[m][week][d]
else:
print "<td
align=right bgcolor=#000099>(%d)</td>" % caldates[m][week][d]
else:
if month+m+1 ==
today.month and caldates[m][week][d] == today.day and year == today.year:
print "<td
align=right bgcolor="+currentdaybg+">"
else: print "<td
align=right>"
print "%d</td>" %
caldates[m][week][d]
print "<td></td>"
print "</tr>"
if mode == 'vote' or mode == 'admin':
# make another row of voting buttons under the dates.
print "<tr>"
for m in range(0,3):
if week >= len(caldates[m]):
print "<td colspan=7></td>"
else:
for d in range(0,7):
if caldates[m][week][d] == 0:
print "<td></td>"
else:
key = '%02d%02d' %
(month+m+1, caldates[m][week][d])
print '<td><select
name=%s>' % key
print '<option
value="-">---'
if mode == 'admin':
print '<option
value="P"',
if
gamedates.get(key) == 'PRI': print 'SELECTED',
print '>Play'
print '<option
value="A"',
if
gamedates.get(key) == 'ALT': print 'SELECTED',
print '>Alt'
print
'</select></td>'
else:
print '<option
value="Y"',
if
myvotes.get(key) == 1: print 'SELECTED',
print '>Best'
print '<option
value="N"',
if
myvotes.get(key) == 0: print 'SELECTED',
print '>NO'
print
'</select></td>'
print "<td></td>"
print "</tr>"
print "</table></center>"
if mode == 'admin' or mode == 'vote':
if forWhom is not None:
print '<input type=hidden name="for" value="%s">' %
cgi.escape(forWhom)
print '<input type=hidden name="year" value="%d">' % year
print '<input type=hidden name="mode" value="%s">' % mode
print '<input type=submit value="Save Changes"></form>'
print '<P><a href="calendar?year=%d">Return to calendar view</a>
(abandons unsaved changes!)' % year
else:
print '<a href="calendar?mode=vote&year=%d">Indicate your good/bad
dates</a><br>' % year
if admin: print '<a href="calendar?mode=admin&year=%d">Set Game
Dates</a><br>' % year
print '<table border=0>'
if mode == 'admin':
print '''
<tr><td
bgcolor=%s> </td><td>Today</td></tr>
<tr><td bgcolor=%s> </td><td>Preferred
Date </td></tr>
<tr><td bgcolor=%s> </td><td>Problem
Date</td></tr>
<tr><td bgcolor=%s> </td><td>Mixed
Bag</td></tr>
''' % (currentdaybg, votecolors[1], votecolors[0], votecolors[2])
else:
print '''
<tr><td bgcolor=%s> </td><td>Today</td></tr>
<tr><td bgcolor=%s> </td><td>Play Date</td></tr>
<tr><td bgcolor=%s> </td><td>Alternate</td></tr>
''' % (currentdaybg, '#009900', '#000099')
print '</table>'
print "</body></html>"
------------------------------------------------------------------------------
After I put that on the website, I wrote another script which runs out of cron
and automatically sends out reminder messages to the players a few days in
advance of a game date:
#!/usr/bin/python
#
import sys
import datetime
import MySQLdb
import smtplib
#
# The gamecal.votes table looks like this:
#
#+-------+-------------+------+-----+------------+-------+
#| Field | Type | Null | Key | Default | Extra |
#+-------+-------------+------+-----+------------+-------+
#| vdate | date | NO | PRI | 0000-00-00 | |
#| vuser | varchar(20) | NO | PRI | NULL | |
#| vote | tinyint(1) | NO | | 0 | |
#+-------+-------------+------+-----+------------+-------+
#
# If the vuser is 'GAME', the "vote" indicates a scheduled
# game date and not a player's vote. In this case, 'vote'
# is 1 for a primary date and 0 for an alternate date.
#
db = MySQLdb.connect(host='HHH', user='XXX', passwd='YYY', db='ZZZ')
today = datetime.date.today()
query = db.cursor()
query.execute('''
SELECT vdate, vote
FROM votes
WHERE vdate >= %s AND vdate < %s AND vuser='GAME'
ORDER BY vdate
''', (today, today + datetime.timedelta(180)))
gamedates = query.fetchall()
query.close()
db.close()
msg = '''From: [EMAIL PROTECTED]
To: [EMAIL PROTECTED]
Subject: Reminder of Upcoming D&D Game
'''
#
# Find closest actual game date
#
nextGame = None
for game in gamedates:
if game[1] == 1:
nextGame = (game[0] - today).days
break
if nextGame is not None:
if nextGame == 2:
msg += "This is an automated reminder that the next D&D game is
nearly here!\n\n"
elif nextGame == 6:
msg += "This is an automated reminder of the upcoming D&D game
in less than a week.\n\n"
else:
sys.exit(0)
msg += "The next several game dates are:\n"
msg += "Date----------- Days Notes------\n"
for game in gamedates:
msg += "%-15s %4d" % (game[0].strftime('%a %b %d %Y'),
(game[0]-today).days)
if game[1] == 0:
msg += " (Alternate)"
msg += '\n'
msg += '''
Please remember that you can always check the schedule by visiting
http://url-to-calendar-application
You may also go to that webpage and indicate what days are particularly
good or bad for you in the future.
'''
mail = smtplib.SMTP('mailserver.XXX.com')
mail.sendmail('[EMAIL PROTECTED]','[EMAIL PROTECTED]', msg)
mail.quit()
_______________________________________________
Tutor maillist - [email protected]
http://mail.python.org/mailman/listinfo/tutor