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)