--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/cal.css Sat Jan 08 22:52:25 2011 +0200
@@ -0,0 +1,71 @@
+/* The calendar uses up all available horizontal width */
+table.calendar
+{
+ width: 100%;
+}
+
+/* Each column in the table is approximately the same width */
+table.calendar th
+{
+ width: 14%;
+}
+
+/* The prev-month link is on the left edge */
+table.calendar a.prev-month
+{
+ float: left;
+}
+
+/* The next-month link is on the right edge */
+table.calendar a.next-month
+{
+ float: right;
+}
+
+/* The weekdays-in-week header is fixed-height */
+table.calendar thead tr
+{
+ height: 1em;
+}
+
+/* A day's header is a fixed height cell */
+table.calendar tbody tr.week-header
+{
+ height: 1em;
+}
+
+/* The day number is visible inside the header */
+table.calendar tbody tr.week-header th
+{
+ padding-left: 0.5em;
+
+ text-align: left;
+}
+
+/* The numbers of days that are a part of the current month are clearly visible */
+table.calendar tbody th.in-month
+{
+ background-color: #eeeeee;
+}
+
+/* The numbers of days that are outside the current month are less noticable */
+table.calendar tbody th.out-month
+{
+ background-color: #ffffff;
+
+ color: #888888;
+}
+
+/* Each row of day-event-data for a week is fixed height */
+table.calendar tbody tr.week-data
+{
+ height: 1em;
+}
+
+/* The days are separated by borders */
+table.calendar tbody td
+{
+ border: 1px solid #d8d8d8;
+
+ border-style: none solid;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/svv/cal.py Sat Jan 08 22:52:25 2011 +0200
@@ -0,0 +1,222 @@
+"""
+ Calendar view of orders
+"""
+
+from svv.controllers import PageHandler
+from svv.html import tags
+from svv import database as db
+from svv import html
+
+
+import datetime
+import calendar
+import logging
+
+log = logging.getLogger('svv.cal')
+
+class CalendarView (PageHandler) :
+ """
+ Single-month calendar view with events for given month shown
+ """
+
+ # first date of week
+ FIRST_WEEKDAY = 6
+
+ # year/month format for URLs
+ URL_FORMAT = "%Y-%m"
+
+ # formatting styles
+ MONTH_TITLE_FORMAT = "%B %Y"
+
+ @classmethod
+ def dayofweek_title (cls, dow) :
+ """
+ Return day of week name for given dow number
+ """
+
+ return calendar.day_name[dow]
+
+ @classmethod
+ def _wrap_year (cls, year, month) :
+ """
+ Wraps month to between [1, 12], spilling overflow/underflow by to year.
+
+ Returns (year, month)
+ """
+
+ # underflow?
+ if month == 0 :
+ # wrap to previous year
+ return (year - 1, 12)
+
+ # overflow?
+ elif month == 13 :
+ # wrap to next year
+ return (year + 1, 1)
+
+ # sane value
+ elif 1 <= month <= 12 :
+ return (year, month)
+
+ # insane value
+ else :
+ assert False, "invalid year/month: %d/%d" % (year, month)
+
+ @classmethod
+ def prev_month (cls, month) :
+ """
+ Compute date for previous month.
+ """
+
+ # normalize
+ y, m = cls._wrap_year(month.year, month.month - 1)
+
+ return datetime.date(y, m, 1)
+
+ @classmethod
+ def next_month (cls, month) :
+ """
+ Compute date for following month.
+ """
+
+ # normalize
+ y, m = cls._wrap_year(month.year, month.month + 1)
+
+ return datetime.date(y, m, 1)
+
+
+ def process (self, **foo) :
+ """
+ Setup
+ """
+
+ # db session
+ self.session = self.app.session()
+
+ def render_day_header (self, month, date) :
+ """
+ Render <th> for day
+ """
+
+ today = datetime.date.today()
+
+ classes = []
+
+ if (date.year, date.month) == (month.year, month.month) :
+ # current month
+ classes.append('in-month')
+
+ else :
+ classes.append('out-month')
+
+ if date == today :
+ classes.append('today')
+
+ class_ = ' '.join(classes)
+
+ return tags.th(date.day, class_=class_)
+
+ def get_events_for_interval (self, start, end) :
+ """
+ Returns list of Order objects for given interval, ordered by start time.
+ """
+
+ # XXX: bad imports
+ from orders import Order
+
+ return self.session.query(Order).filter(
+ (Order.event_start.between(start, end))
+ | (Order.event_end.between(start, end))
+ ).order_by(Order.event_start).all()
+
+ def render_week (self, month, week) :
+ """
+ Render day rows for given week.
+ """
+
+ # XXX: nasty
+ from svv import urls
+
+ # load events for week
+ week_start = datetime.datetime.combine(min(week), datetime.time(0, 0, 0))
+ week_end = datetime.datetime.combine(max(week), datetime.time(23, 59, 59))
+
+ orders = self.get_events_for_interval(week_start, week_end)
+
+ log.debug("Render week %r -> %r: %d", week_start, week_end, len(orders))
+
+ # day headers
+ yield tags.tr(class_='week-header')(
+ self.render_day_header(month, date) for date in week
+ )
+
+ # each even on its own row for now
+ for order in orders :
+ yield tags.tr(class_='week-data')(
+ (
+ tags.td(
+ tags.a(href=self.url_for(urls.OrderView, id=order.id))(order.event_name)
+
+ ) if order.on_date(date) else (
+ tags.td("")
+
+ )
+ ) for date in week
+ )
+
+ def render_calendar (self, month) :
+ """
+ Render calendar for given date's month.
+ """
+
+ cal = calendar.Calendar(self.FIRST_WEEKDAY)
+
+ # next/prev month
+ prev = self.prev_month(month)
+ next = self.next_month(month)
+
+ return tags.table(class_='calendar')(
+ tags.caption(
+ tags.a(href=self.url_for(CalendarView, yearmonth=prev.strftime(self.URL_FORMAT)), class_='prev-month')(
+ html.raw("«")
+ ),
+ month.strftime(self.MONTH_TITLE_FORMAT),
+ tags.a(href=self.url_for(CalendarView, yearmonth=next.strftime(self.URL_FORMAT)), class_='next-month')(
+ html.raw("»")
+ ),
+ ),
+
+ # week-day headers
+ tags.thead(
+ tags.tr(
+ tags.th(
+ self.dayofweek_title(dow)
+ ) for dow in cal.iterweekdays()
+ )
+ ),
+
+ # month weeks
+ tags.tbody(
+ (
+ self.render_week(month, week)
+ ) for week in cal.monthdatescalendar(month.year, month.month)
+ ),
+ )
+
+ def render_content (self, yearmonth=None) :
+ """
+ Render calendar HTML for given year/month.
+ """
+
+ if yearmonth :
+ # requested month
+ month = datetime.datetime.strptime(yearmonth, self.URL_FORMAT).date()
+
+ else :
+ # this month
+ month = datetime.date.today()
+
+ return (
+ self.render_calendar(month),
+ )
+
--- a/svv/controllers.py Fri Jan 07 03:45:19 2011 +0200
+++ b/svv/controllers.py Sat Jan 08 22:52:25 2011 +0200
@@ -104,6 +104,7 @@
"/static/style.css",
"/static/forms.css",
"/static/tables.css",
+ "/static/cal.css",
"/static/treelist.css",
--- a/svv/orders.py Fri Jan 07 03:45:19 2011 +0200
+++ b/svv/orders.py Sat Jan 08 22:52:25 2011 +0200
@@ -94,6 +94,13 @@
return "%s %s" % (date, time)
+ def on_date (self, date) :
+ """
+ Does the event take place on this date?
+ """
+
+ return self.event_start.date() <= date <= self.event_end.date()
+
# bind against database schema
db.mapper(Customer, db.customers)
db.mapper(Contact, db.contacts)
--- a/svv/urls.py Fri Jan 07 03:45:19 2011 +0200
+++ b/svv/urls.py Sat Jan 08 22:52:25 2011 +0200
@@ -8,6 +8,7 @@
from svv.controllers import Index
from svv.customers import CustomersView, CustomerView
from svv.orders import OrdersView, OrderView, EditOrderView, NewOrderView, OrderContractDocument
+from svv.cal import CalendarView
# map URLs -> AppHandler
URLS = Map((
@@ -16,6 +17,8 @@
Rule('/orders/<int:id>', endpoint=OrderView),
Rule('/orders/<int:id>/edit', endpoint=EditOrderView),
Rule('/orders/<int:id>/Vuokrasopimus.pdf', endpoint=OrderContractDocument),
+ Rule('/calendar/', endpoint=CalendarView),
+ Rule('/calendar/<string:yearmonth>', endpoint=CalendarView),
Rule('/customers', endpoint=CustomersView),
Rule('/customers/<int:id>', endpoint=CustomerView),