"""mems.ui.form.date_widget
Provides a date selection widget.
"""
from types import IntType, NoneType
from quixote.form import Widget
from quixote.form import FormValueError
from mx import DateTime
from mems.lib.typeutils import typecheck
__revision__ = "$Id: date_widget.ptl,v 1.11 2002/07/09 20:15:39 gward Exp $"
__doc__ = """\
DateSelectWidget
Quixote date selection widget. 'value' argument is either a DateTime
instance or None. The default behavior if 'value' is None, is that the
first (and selected option) will be blank (i.e. 'not-selected') and
the value returned by the date widget when each of the HTML
select components is left in the un-selected state is also None.
Otherwise, the select components are populated with the year, month
and day corresponding to the DateTime instance specified in value
and any changes to the date will be returned as the new value.
The constructor for DateSelectWidget takes a 'allow_unselected' keyword
argument which is a boolean that is on by default. When turned off,
the 'not-selected' option is no longer presented in the HTML. A
None 'value' will be turned into a new DateTime instance corresponding
to 'now' which then becomes the selected option for the Widget.
The constructor for DateSelectWidget also takes a 'use_JS' keyword
argument which is a boolean that is on by default. This will
construct a short JavaScript function that will be inserted into the
HTML (as a comment) that will 'correct' invalid dates on-the-fly,
without needing the containing form to be submitted (i.e. 2000-Feb-31 is
changed to 2000-Feb-29). Once the form is submitted if the
'auto_correct' boolean keyword argument is true, the same
correction is performed regardless of if 'use_JS' was on or not.
If 'auto_correct' is false, a FormValueError is raised if the
form data contains illegal dates. The exception contains the
information that would be used for the correction.
Quirks:
This widget will currently only work as long as Quixote continues
to use the 'one-form-per-page' architecture. If Quixote ever moves
to multiple forms/page, then forms will need to be named in the HTML
and that name will need to be passed to the constructor of the widget.
The 20 line JavaScript function is inserted once, with slight
naming variation, for EVERY DateSelectWidget instance :-o So, at the
moment, this widget is not recomended for long lists of records
on a single page that require date manipulation. Why ? I chose
this path because of the initial goal not to change anything in
Quixote to support a baseline JavaScript validation. Down the road,
I think the *right* way to implement this would be to have some
sort of page level (or maybe form level) registration mechanism
for adding or inquiring about JS functions that belong to the
page, implying a forms rework for Quixote... maybe a future?
"""
MONTH_NAMES = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
template render_date_select(date, name, year_name, month_name,
day_name, allow_unselected, use_JS):
# render_date_select(date : DateTime | None, name : string,
# year_name : string, month_name : string,
# day_name : string, allow_unselected : boolean,
# use_JS : boolean)
#
# Render the HTML for the date_Select widget. If 'allow_unselected'
# is true, allow the widget to have a 'blank' option, otherwise
# display the date represented by 'date' in three select dialogs.
# If 'date' is None, create a new DateTime instance for 'now'.
# The HTML is formatted so that it looks decent in 'view source'
if date is None:
date = DateTime.now()
selected = ' selected="selected"'
else:
selected = ""
if allow_unselected:
not_selected_option = '' % selected
not_selected_size = 1
else:
not_selected_option = ''
not_selected_size = 0
if use_JS:
script_ref = 'onchange="%s_set_day()"' % name
datetime_validation_JS(name, year_name, month_name,
day_name, not_selected_size)
else:
script_ref = ''
# year select
"""
"""
# month select
"""
"""
# day select
"""
"""
template datetime_validation_JS(name, year_name, month_name,
day_name, not_selected_size):
# datetime_validation_JS(name : string, year_name : string,
# month_name : string, day_name : string,
# not_selected_size : int)
#
# Return the javascript that mimicks the 'datetime_validation'
# method from the date_widget in JavaScript. The JS is written
# such that it works with the 'render_dateselect' template HTML.
# If quixote moves to multiple forms/page, the target form will
# probably need a name. Currently quixote forms are not named.
# Don't forget to escape the JavaScript mod operator
"""
""" % (name, year_name, month_name, day_name, 28 + not_selected_size,
27 + not_selected_size, 29 + not_selected_size)
class DateSelectWidget (Widget):
data_type = "DateTime"
widget_type = "date_select"
def __init__ (self, name, value=None, allow_unselected=1, use_JS=1,
auto_correct=0):
typecheck(value, DateTime.DateTimeType, allow_none=1)
year_name = name + "_year" # name for the year HTML select
month_name = name + "_month" # name for the month HTML select
day_name = name + "_day" # name for the day HTML select
name = "_".join(name.split('.')) # deal with subwidgets...
self.year_name = "_".join(year_name.split('.'))
self.month_name = "_".join(month_name.split('.'))
self.day_name = "_".join(day_name.split('.'))
self.allow_unselected = allow_unselected # add a 'not-selected' option
self.auto_correct = auto_correct # correct the date like 'use_JS' does
# otherwise raise FormValueError
self.use_JS = use_JS # insert Javascript for additional
# on-the-fly client-side validation
Widget.__init__(self, name, value)
def render (self, request):
return render_date_select(self.value,
self.name,
self.year_name,
self.month_name,
self.day_name,
self.allow_unselected,
self.use_JS)
def parse (self, request):
year = request.form.get(self.year_name)
mon = request.form.get(self.month_name)
day = request.form.get(self.day_name)
# look for the 'not-selected' option
if not year and not mon and not day:
return None
# user has tried to select a date
try:
int_year = int(year)
int_mon = int(mon)
int_day = int(day)
except ValueError:
raise FormValueError, "year, month, and day must all be set"
return self.datetime_validation(int_year, int_mon, int_day)
def datetime_validation(self, year, mon, day):
"""datetime_validation(year : int, month : int, day : int) -> DateTime
Create and return a DateTime instance based on the
year, month, day arguments. Adjust the day to be
within the legal range of days for that particular
month/year combination. Account for a leap-year.
"""
typecheck(year, IntType)
typecheck(mon, IntType)
typecheck(day, IntType)
adjusted_day = day
if mon == 2: # feb
# check for a leap-year
if ((year % 4) == 0 and (year % 100) > 0) or (year % 400) == 0:
if day > 29:
adjusted_day = 29;
elif day > 28:
adjusted_day = 28;
elif (mon == 4 or mon == 6 or mon == 9 or mon == 11) and day > 30:
adjusted_day = 30;
# Report an error if not auto_correcting and correction is needed
if not self.auto_correct and adjusted_day <> day:
raise FormValueError, ("The last day in %s, %d is %d"
% (MONTH_NAMES[mon - 1],
year, adjusted_day))
self.value = DateTime.DateTime(year, mon, adjusted_day)
return self.value
# Register this widget class
from quixote.form import register_widget_class
register_widget_class(DateSelectWidget)