I wanted to arrange a get-together among some of my busy friends, so I
wrote this little program to help.

If this were http://example.com/foo.cgi, you could send people links
such as http://example.com/foo.cgi?who=brian, and then they can check
the boxes to indicate what days they're available.

Depends on nevow.stan, which depends on ZopeInterface, and Python.

Here's a sample CGI script:

#!/usr/bin/python
import datepoll

text="""I finally received my minipov2 kits from Adafruit.  I'd like to
organize an electronics building night where we (Alice, Bob,
and I) can get together to work on the kits, and we can all
work on whatever other electronic gizmos we want to work on.  Doing
this together has several advantages: we can share knowledge, we can
share electronic equipment, and it will prevent us from putting it
off.  I'm not yet sure where we'll do this, but I'm thinking someplace
in San Francisco."""

datepoll.run_cgi(
    startdate=(2006, 2, 18), 
    enddate=(2006, 3, 4),
    title="When should we get together to solder?",
    text=text,
    logfile=file('/home/kragen/datepoll-log', 'a+'))


And here's the "datepoll.py" module it depends on:

#!/usr/bin/python
"""Date poll CGI script.

Frustrating that this takes 0.7 seconds of user time to import modules
on my 600MHz laptop.

Help a group of people decide what dates are best for them to get
together.  

I was going to clean this up before I sent it out to kragen-hacks, in
particular making it work if your date range doesn't span exactly two
months in the same year, but I never got around to it.  Like
everything else posted to kragen-hacks without an explicit notice to
the contrary, this is in the public domain.

This is related to my interests in calendaring, semistructured data
management, and systems with the Wiki nature, and to my idea of "rumor-oriented
programming".

TODO: (D means "done")
D display a calendar full of checkboxes
D display explanatory text
D only display checkboxes on relevant days
D save thing full of checkboxes to a logfile
D support username in URL, stored with thing
D preload checkbox state for editing
D display summary of people on a single calendar
D make day numbers etc. be <label>s
D add weekday labels
D redirect to URL constructed from real data
D make POST URL be correct
- support OpenID authentication

"""

import sys, calendar, nevow.tags, nevow.flat, cgi, os
T = nevow.tags

def script_url():
    port = os.environ['SERVER_PORT']
    if port == '80': cport = ''
    else: cport = ':%s' % port
    return '%(protocol)s://%(host)s%(:port)s%(localpart)s' % {
        'protocol': 'http',  # how do we tell if it's not?
        'host': os.environ.get('HTTP_HOST', os.environ['SERVER_NAME']),
        ':port': cport, # XXX untested
        'localpart': os.environ['SCRIPT_NAME'],
    }

class daterange:
    def __init__(self, start, end):
        self.start, self.end = start, end
    def __contains__(self, adate):
        return self.start <= adate < self.end

class monthcalendar:
    monthnames = ('Zero January February March April May June ' +
                  'July August September October November December').split()
    daynames = 'Sunday Monday Tuesday Wednesday Thursday Friday 
Saturday'.split()
    def __init__(self, year, month, daterange, current, who):
        self.year, self.month, self.daterange, self.current, self.who = (
            year, month, daterange, current, who)
    def monthlabel(self):
        return '%s %s' % (self.year, self.monthnames[self.month])
    def daycell(self, day):
        if not day: return ''
        date = self.year, self.month, day
        datename = '%04d-%02d-%02d' % date
        if date not in self.daterange:
            return day
        okers = self.current.okers(datename)
        ckid = 'ck' + datename
        checkbox = T.input(type='checkbox', name=datename, id=ckid)
        if self.who in okers: checkbox=checkbox(checked='checked')
        return (T.label(**{'for':ckid})[day, T.br, [(guy, T.br) for guy in 
okers]],
                checkbox)
    def monthrow(self, row):
        return T.tr[[T.td[self.daycell(day)] for day in row]]
    def stan(self):
        # I tried for a while to figure out how to register this
        # method as a Stan flattener for this object, but I gave up
        calendar.setfirstweekday(calendar.SUNDAY)
        rows = calendar.monthcalendar(self.year, self.month)
        return T.table(cellspacing=0, cellpadding=0)[
            T.tr[T.th(colspan=7)[self.monthlabel()]],
            T.tr[[T.td[d] for d in self.daynames]],
            [self.monthrow(row) for row in rows]]

class current_status:
    def __init__(self, logfile):
        logfile.seek(0)
        self.rv = {}
        for line in logfile:
            try: dork = eval(line)
            except: dork = {}
            if dork.has_key('who'):
                self.rv[dork['who'][0]] = dork
    def okers(self, datename):
        rv = []
        for guy in self.rv.keys():
            if self.rv[guy].has_key(datename):
                rv.append(guy)
        return rv
    def keys(self):
        return self.rv.keys()
    def __getitem__(self, item):
        return self.rv[item]

style='''
body { font-family: sans-serif }
th, h1 { background-color: #ddbbbb; font-weight: inherit }
h1 { padding: 0.5ex }
th, td { border-width: 0px; text-align: right; padding: 0.5ex; vertical-align: 
top }
'''

explanation='Check boxes to indicate days that would work for '
more_explanation=" (please change the name if that's not you)."

def handle_cgi_get(logfile, startdate, enddate, text, title):
    startyear, startmonth, startday = startdate
    endyear, endmonth, endday = enddate
    assert startyear == endyear, "Uhoh not implemented"
    assert startmonth + 1 == endmonth, "Uhoh not implemented"

    q = cgi.parse()
    who = q.get('who', ['anonymous'])[0]

    current_status_info = current_status(logfile)
    available_dates = daterange(startdate, enddate)

    formcontents = (T.p[explanation, T.input(name='who', value=who),
                        more_explanation],
                    T.table[T.tr[T.td[
                        monthcalendar(year=startyear, month=startmonth,
                                      daterange=available_dates,
                                      current=current_status_info,
                                      who=who).stan()
                    ], T.td[
                        monthcalendar(year=endyear, month=endmonth,
                                      daterange=available_dates,
                                      current=current_status_info,
                                      who=who).stan()
                    ]]],
                    T.input(type='submit', value='Update'))
    
    doc = T.html[T.head[T.title[title],
                        T.style(type='text/css')[style]],
                 T.body[T.h1[title],
                        T.p[text],
                        T.form(method='POST',
                               action=script_url())[formcontents]]]

    sys.stdout.write("Content-Type: text/html\n\n" + nevow.flat.flatten(doc))
    sys.stdout.flush()

def serialized_cgi_data(q):
    return repr(q) + '\n'

def handle_post(logfile, startdate, enddate, text, title):
    q = cgi.parse()
    logfile.write(serialized_cgi_data(q))
    logfile.flush()
    url = '%s?who=%s' % (script_url(), q.get('who', ['anonymous'])[0])
    sys.stdout.write("Status: 302 Moved\nLocation: %s\n\n" % url)

def run_cgi(**args):
    if os.environ.get('REQUEST_METHOD', "GET") == 'GET':
        return handle_cgi_get(**args)
    else:
        return handle_post(**args)

Reply via email to