# HG changeset patch # User Tero Marttila # Date 1234147164 -7200 # Node ID b65a95eb9f6bd87fbb5751d219ba956c7b8ead5b # Parent 8103d18907a07c0f9001a595f7a205a85e5cdd20 implement browse-by-date to show a nice calendar diff -r 8103d18907a0 -r b65a95eb9f6b handlers.py --- a/handlers.py Mon Feb 09 03:05:43 2009 +0200 +++ b/handlers.py Mon Feb 09 04:39:24 2009 +0200 @@ -2,7 +2,7 @@ Our URL action handlers """ -import pytz +import datetime, calendar, pytz from qmsk.web import http, template @@ -12,7 +12,7 @@ # load templates from here templates = template.TemplateLoader("templates", - h = helpers, + _helper_class = helpers.Helpers, urls = urls, channel_list = channels.channel_list, ) @@ -33,8 +33,8 @@ return http.Redirect(urls.channel_view.build(request, channel=channel.id)) -@preferences.handler(prefs.Formatter) -def channel_view (request, channel, count, formatter) : +@preferences.handler(prefs.Formatter, prefs.Timezone) +def channel_view (request, channel, count, formatter, timezone) : """ The main channel view page, display the most important info, and all requisite links """ @@ -47,6 +47,7 @@ return templates.render_to_response("channel_view", req = request, + timezone = timezone, channel = channel, count = count, formatter = formatter, @@ -66,12 +67,34 @@ else : raise http.ResponseError("Unknown filetype %r" % format) -def channel_calendar (request, channel) : +@preferences.handler(prefs.Timezone) +def channel_calendar (request, channel, year, month, timezone) : """ - Display a list of avilable logs for some days + Display a list of avilable logs for some month """ - pass + # current date as default + now = timezone.localize(datetime.datetime.now()) + + # target year/month + target = timezone.localize(datetime.datetime( + year = year if year else now.year, + month = month if month else now.month, + day = 1 + )) + + # get set of days available + days = channel.source.get_month_days(target) + + # display calendar + return templates.render_to_response("channel_calendar", + req = request, + timezone = timezone, + channel = channel, + calendar = calendar.Calendar(), + month = target.date(), + days = days, + ) @preferences.handler(prefs.Formatter, prefs.Timezone) def channel_date (request, channel, date, formatter, timezone) : @@ -90,6 +113,7 @@ return templates.render_to_response("channel_date", req = request, + timezone = timezone, channel = channel, formatter = formatter, date = date, diff -r 8103d18907a0 -r b65a95eb9f6b helpers.py --- a/helpers.py Mon Feb 09 03:05:43 2009 +0200 +++ b/helpers.py Mon Feb 09 04:39:24 2009 +0200 @@ -2,21 +2,56 @@ Some additional helpers """ -# "inherit" qmsk.web's helpers -from qmsk.web.helpers import * +import qmsk.web.helpers -def tz_name (tz) : +import datetime, calendar + +class Helpers (qmsk.web.helpers.Helpers) : """ - Returns a string describing the given timezone + Our set of helpers, inheriting from base helpers """ - return str(tz) + def tz_name (self, tz) : + """ + Returns a string describing the given timezone + """ -def fmt_date (date) : - """ - Formats a date - """ - - # XXX: hardcoded - return date.strftime('%Y-%m-%d') + return str(tz) + def fmt_date (self, date) : + """ + Formats a date + """ + + # XXX: hardcoded + return date.strftime('%Y-%m-%d') + + def fmt_month (self, date) : + """ + Formats a month + """ + + return date.strftime('%B %Y') + + def fmt_weekday (self, wday) : + """ + Formats an abbreviated weekday name + """ + + return calendar.day_abbr[wday] + + def build_date (self, month, mday) : + """ + Returns a datetime.date for the given (month.year, month.month, mday) + """ + + return datetime.date(month.year, month.month, mday) + + def is_today (self, date) : + """ + checks if the given date is today + """ + + # construct current date + return date == self.ctx['timezone'].localize(datetime.datetime.now()).date() + diff -r 8103d18907a0 -r b65a95eb9f6b log_source.py --- a/log_source.py Mon Feb 09 03:05:43 2009 +0200 +++ b/log_source.py Mon Feb 09 04:39:24 2009 +0200 @@ -2,7 +2,7 @@ A source of IRC log files """ -import datetime, itertools +import datetime, calendar, itertools import os, errno import pytz @@ -24,10 +24,19 @@ """ abstract + + def get_month_days (self, dt) : + """ + Get a set of dates, telling which days in the given month (as a datetime) have logs available + """ -class LogFile (LogSource) : + abstract + +class LogFile (object) : """ A file containing LogEvents + + XXX: modify to implement LogSource? """ def __init__ (self, path, parser, start_date=None, charset='utf-8', sep='\n') : @@ -168,7 +177,7 @@ for line in lines[:0:-1] : yield line.decode(self.charset) - def get_latest (self, count) : + def read_latest (self, count) : """ Returns up to count events, from the end of the file, or less, if the file doesn't contain that many lines. """ @@ -221,9 +230,12 @@ # convert to date and use that return self._get_logfile_date(dtz.date()) - def _get_logfile_date (self, d) : + def _get_logfile_date (self, d, load=True) : """ - Get the logfile corresponding to the given naive date in our timezone + Get the logfile corresponding to the given naive date in our timezone. If load is False, only test for the + presence of the logfile, do not actually open it. + + Returns None if the logfile does not exist. """ # format filename @@ -231,9 +243,24 @@ # build path path = os.path.join(self.path, filename) + + try : + if load : + # open+return the LogFile + return LogFile(path, self.parser, d, self.charset) + + else : + # test + return os.path.exists(path) - # return the LogFile - return LogFile(path, self.parser, d, self.charset) + # XXX: move to LogFile + except IOError, e : + # return None for missing files + if e.errno == errno.ENOENT : + return None + + else : + raise def _iter_date_reverse (self, dt=None) : """ @@ -280,18 +307,13 @@ while len(lines) < count : logfile = None - try : - # get next logfile - files += 1 - - # open - logfile = self._get_logfile_date(day_iter.next()) + # get next logfile + files += 1 - except IOError, e : - # skip nonexistant days if we haven't found any logs yet - if e.errno != errno.ENOENT : - raise - + # open + logfile = self._get_logfile_date(day_iter.next()) + + if not logfile : if files > MAX_FILES : raise Exception("No recent logfiles found") @@ -301,7 +323,7 @@ # read the events # XXX: use a queue - lines = list(logfile.get_latest(count)) + lines + lines = list(logfile.read_latest(count)) + lines # return the events return lines @@ -333,3 +355,27 @@ # chain together the two sources return itertools.chain(f_begin.read_from(dtz_begin), f_end.read_until(dtz_end)) + def get_month_days (self, month) : + """ + Returns a set of dates for which logfiles are available in the given datetime's month + """ + + # the set of days + days = set() + + # iterate over month's days using Calendar + for date in calendar.Calendar().itermonthdates(month.year, month.month) : + # convert date to target datetime + dtz = month.tzinfo.localize(datetime.datetime.combine(date, datetime.time(0))).astimezone(self.tz) + + # date in our target timezone + log_date = dtz.date() + + # test for it + if self._get_logfile_date(log_date, load=False) : + # add to set + days.add(date) + + # return set + return days + diff -r 8103d18907a0 -r b65a95eb9f6b static/irclogs.css --- a/static/irclogs.css Mon Feb 09 03:05:43 2009 +0200 +++ b/static/irclogs.css Mon Feb 09 04:39:24 2009 +0200 @@ -125,4 +125,12 @@ text-align: center; } +/* + * General + */ +/* Calendar */ +table.calendar td#today { + font-weight: bold; +} + diff -r 8103d18907a0 -r b65a95eb9f6b templates/channel.tmpl --- a/templates/channel.tmpl Mon Feb 09 03:05:43 2009 +0200 +++ b/templates/channel.tmpl Mon Feb 09 04:39:24 2009 +0200 @@ -48,5 +48,5 @@ ${next.body()} <%def name="footer_right()"> - All times are in ${h.tz_name(formatter.tz)} + All times are in ${h.tz_name(timezone)} diff -r 8103d18907a0 -r b65a95eb9f6b templates/channel_calendar.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/channel_calendar.tmpl Mon Feb 09 04:39:24 2009 +0200 @@ -0,0 +1,47 @@ +<%inherit file="channel.tmpl" /> + +<%def name="month_table(cal, month, dates)"> + +## table header - month name + + + +## month header - weekday names + + % for weekday in cal.iterweekdays() : + + % endfor + +## iterate over the weeks +% for week in cal.monthdays2calendar(month.year, month.month) : + + ## iterate over the week's days + % for day, weekday in week : + ## is it an empty cell? + % if not day : + + % else : + ## build date + <% date = h.build_date(month, day) %> + ## is it today? + % if day and h.is_today(date) : + + % endif + % endfor + +% endfor +
${h.fmt_month(month)}
${h.fmt_weekday(weekday)}
 \ + % else : + \ + % endif + ## link to logs for this day? + % if date in dates : + ${day}\ + % else : + ${day}\ + % endif +
+ + +${month_table(calendar, month, days)} + diff -r 8103d18907a0 -r b65a95eb9f6b urls.py --- a/urls.py Mon Feb 09 03:05:43 2009 +0200 +++ b/urls.py Mon Feb 09 04:39:24 2009 +0200 @@ -27,13 +27,13 @@ ) # urls -index = url('/', handlers.index ) -channel_select = url('/channel_select/?channel:cid', handlers.channel_select ) -channel_view = url('/channels/{channel:cid}/?count:int=10', handlers.channel_view ) -channel_last = url('/channels/{channel:cid}/last/{count:int=100}/{format=html}', handlers.channel_last ) -channel_date = url('/channels/{channel:cid}/calendar', handlers.channel_calendar ) -channel_date = url('/channels/{channel:cid}/date/{date:date}', handlers.channel_date ) -channel_search = url('/channels/{channel:cid}/search/?q', handlers.channel_search ) +index = url('/', handlers.index ) +channel_select = url('/channel_select/?channel:cid', handlers.channel_select ) +channel_view = url('/channels/{channel:cid}/?count:int=10', handlers.channel_view ) +channel_last = url('/channels/{channel:cid}/last/{count:int=100}/{format=html}', handlers.channel_last ) +channel_calendar = url('/channels/{channel:cid}/calendar/{year:int=0}/{month:int=0}', handlers.channel_calendar ) +channel_date = url('/channels/{channel:cid}/date/{date:date}', handlers.channel_date ) +channel_search = url('/channels/{channel:cid}/search/?q', handlers.channel_search ) # mapper mapper = urltree.URLTree(urls)