svv/cal.py
changeset 37 eabea2857143
child 39 4f331cfc76a4
--- /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("&laquo;")
+                ),
+                month.strftime(self.MONTH_TITLE_FORMAT),
+                tags.a(href=self.url_for(CalendarView, yearmonth=next.strftime(self.URL_FORMAT)), class_='next-month')(
+                    html.raw("&raquo;")
+                ),
+            ),
+            
+            # 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),
+        )
+