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>&nbsp;</td></tr><tr>"
        caldates = []
        #
        # Month Names
        #
        for m in range(0,3): 
                print "<th colspan=7><font 
size=+2>%s</font></th><td>&nbsp;&nbsp;&nbsp;</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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td>Today</td></tr>
        <tr><td bgcolor=%s>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td>Preferred 
Date </td></tr>
        <tr><td bgcolor=%s>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td>Problem 
Date</td></tr>
        <tr><td bgcolor=%s>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td>Mixed 
Bag</td></tr>
''' % (currentdaybg, votecolors[1], votecolors[0], votecolors[2])
else:
        print '''
<tr><td bgcolor=%s>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td>Today</td></tr>
<tr><td bgcolor=%s>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td>Play Date</td></tr>
<tr><td bgcolor=%s>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</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

Reply via email to