kansha/toolbox/calendar_widget.py
# -*- coding:utf-8 -*-
#--
# Copyright (c) 2012-2014 Net-ng.
# All rights reserved.
#
# This software is licensed under the BSD License, as described in
# the file LICENSE.txt, which you should have received as part of
# this distribution.
#--
import calendar
from datetime import date, datetime
from dateutil import relativedelta
from nagare import ajax, presentation, i18n, var
from nagare import security
from nagare.i18n import _
YEARS_AROUND = 5
def to_date(v, format='short'):
'''Format various inputs into a date object
In:
- v -- the value to transform
- format -- if ``v`` is a string, provide the date format used in it
'''
if isinstance(v, datetime):
return v.date()
elif isinstance(v, (str, unicode)):
try:
return i18n.parse_date(v, format)
except:
raise ValueError(_('Invalid date format !'))
return v
def in_(d, start, end):
'''is ``d`` value between ``start`` and ``end``
In:
- d -- a date (or datetime) object'''
return start <= d <= end
def before(d, start, strict=True):
'''is ``d`` before ``start``
In:
- strict -- ``d`` must be before if True else before or equal
'''
if strict:
return d < start
return d <= start
class Calendar(object):
def __init__(self, d=None, min_date=None, allow_none=False):
self.date = d
self.min_date = min_date
if self.date is not None:
val = self.date
elif self.min_date is not None:
val = to_date(self.min_date)
else:
val = date.today()
self.current = val.replace(day=1)
self.is_hidden = True
self.allow_none = allow_none
def toggle(self):
self.is_hidden = not self.is_hidden
def next_month(self):
self.current += relativedelta.relativedelta(months=+1)
def previous_month(self):
self.current += relativedelta.relativedelta(months=-1)
def choose_date(self, day, comp):
self.toggle()
self.date = self.current.replace(day=day)
comp.answer(self.date)
def remove_date(self, comp):
self.toggle()
self.date = None
comp.answer(self.date)
def is_authorized_date(self, d):
if self.min_date is not None and before(d, self.min_date):
return False
return True
def set_today(self):
self.current = date.today().replace(day=1)
def change_month(self, month):
self.current = self.current.replace(month=int(month))
def change_year(self, year):
self.current = self.current.replace(year=int(year))
def __call__(self, value=None):
if value is not None:
self.date = to_date(value)
return self.date
@presentation.render_for(Calendar)
def render_async(self, h, comp, *args):
with h.div(class_='calendar-input'):
input_id = h.generate_id('input')
calendar_id = h.generate_id('calendar')
if self.is_hidden:
style = 'display: none;'
else:
style = 'display: block;'
with h.div(class_='calendar', id_=calendar_id, style=style):
with h.div(class_='calendar-header'):
with h.a(title=_(u'Previous')).action(self.previous_month):
h << h.i(class_='icon-arrow-left', title=_(u'Previous'))
with h.span(class_='current'):
with h.select(onchange=ajax.Update(action=self.change_month)):
for n, month in i18n.get_month_names().iteritems():
month = month.capitalize()
h << h.option(month, value=n).selected(self.current.month)
h << u' '
with h.select(onchange=ajax.Update(action=self.change_year)):
for year in xrange(self.current.year - YEARS_AROUND, self.current.year + YEARS_AROUND):
h << h.option(year, value=year).selected(self.current.year)
with h.a(title=_(u'Next')).action(self.next_month):
h << h.i(class_='icon-arrow-right', title=_(u'Next'))
with h.div(class_='calendar-content'):
if isinstance(self.date, date):
if self.current.year == self.date.year and self.current.month == self.date.month:
active = self.date.day
else:
active = -1
else:
active = -1
today = date.today()
if today.month == self.current.month and today.year == self.current.year:
today = today.day
else:
today = -1
with h.table:
with h.thead:
with h.tr:
h << h.th(h.span(_('Wk'), title=_('Week number')), class_='week_number')
days = [day.capitalize() for day in i18n.get_day_names().itervalues()]
h << [h.th(h.span(d[:2], title=d)) for d in days]
with h.tbody:
for line in calendar.monthcalendar(self.current.year, self.current.month):
with h.tr:
week_number = date(self.current.year, self.current.month, max(1, line[0])).isocalendar()[1]
h << h.td(week_number, class_='week_number')
for day in line:
if day == 0:
h << h.td(class_='not-this-month')
else:
authorized = self.is_authorized_date(self.current.replace(day=day))
cls = []
if day == active:
cls.append(u'active')
if day == today:
cls.append(u'today')
if not authorized:
cls.append(u'excluded')
with h.td(class_=' '.join(cls)):
if authorized:
h << h.a(day).action(lambda day=day: self.choose_date(day, comp))
else:
h << h.span(day)
with h.div(class_='calendar-today'):
h << h.a(h.i(class_='icon-calendar'), _(u"Today"), class_='today-link btn').action(self.set_today)
if self.allow_none:
h << h.a(h.i(class_='icon-cross'), _(u'None'), class_='erase btn').action(lambda: self.remove_date(comp))
h << h.script(
"YAHOO.util.Event.onDOMReady(function() {"
"var region = YAHOO.util.Dom.getRegion(%(input_id)s);"
"YAHOO.util.Dom.setXY(%(calendar_id)s, [region.left, region.bottom + 3]);"
"});" % {
'input_id': ajax.py2js(input_id),
'calendar_id': ajax.py2js(calendar_id)
}
)
h << h.div(class_='clear')
return h.root