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 - Tutor@python.org http://mail.python.org/mailman/listinfo/tutor