source: main/waeup.kofa/trunk/src/waeup/kofa/widgets/datewidget.py @ 9048

Last change on this file since 9048 was 8193, checked in by uli, 12 years ago

Display local datetime when using FormattedDateDisplayWidget? (or
derived versions like FriendlyDateWidget?) with datetimes.

  • Property svn:keywords set to Id
File size: 8.8 KB
Line 
1## $Id: datewidget.py 8193 2012-04-17 11:35:01Z uli $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
19A datewidget with customizable date format.
20"""
21import pytz
22from datetime import datetime
23from zope.component import queryUtility
24from zope.formlib.interfaces import ConversionError, IDisplayWidget
25from zope.formlib.textwidgets import DateWidget, DateDisplayWidget, escape
26from zope.formlib.widget import renderElement, CustomWidgetFactory
27from zope.interface import implements
28from waeup.kofa.interfaces import IKofaUtils
29from waeup.kofa.utils.helpers import to_timezone
30
31
32#: A dictionary of supported date formats.
33#:
34#: The following formats are supported:
35#:
36#: ``iso``
37#:    ISO format ``YYYY-MM-DD``
38#: ``le``
39#:    little endian with slashes: ``DD/MM/YYYY``
40#: ``de``
41#:    german date format: ``DD.MM.YYYY``
42#: ``us``
43#:    middle endian format common in the U.S.: ``MM/DD/YYYY``
44#:
45#: Furthermore we support for input widgets an additional year
46#: marker. Input date widgets with this marker provide also a year
47#: selector, handy for dates of birth etc.
48#:
49#: The year-supporting formats are similar to the basic versions above:
50#:
51#: ``iso-year``
52#:    ISO format ``YYYY-MM-DD``
53#: ``le-year``
54#:    little endian with slashes: ``DD/MM/YYYY``
55#: ``de-year``
56#:    german date format: ``DD.MM.YYYY``
57#: ``us-year``
58#:    middle endian format common in the U.S.: ``MM/DD/YYYY``
59#:
60#: For date display widgets there is naturally no difference between a
61#: year and non-year setting (you can for instance use 'le' or 'le-year'
62#: with the same output).
63DATE_FORMATS = {
64    'iso': ('datepicker', '%Y-%m-%d'),
65    'le':  ('datepicker-le', '%d/%m/%Y'),
66    'de':  ('datepicker-de', '%d.%m.%Y'),
67    'us':  ('datepicker-us', '%m/%d/%Y'),
68    'iso-year': ('datepicker-year', '%Y-%m-%d'),
69    'le-year':  ('datepicker-le-year', '%d/%m/%Y'),
70    'de-year':  ('datepicker-de-year', '%d.%m.%Y'),
71    'us-year':  ('datepicker-us-year', '%m/%d/%Y'),
72    }
73
74#: a dict containing tuples (<FORMAT>, <SHOW_YEAR>) as keys and
75#: suitable CSS tags as values.
76FORMATS_BY_VALUE = dict(
77    [((val[1], 'year' in key), val[0]) for key, val in DATE_FORMATS.items()])
78
79class FormattedDateWidget(DateWidget):
80    """A date widget that supports different (and _explicit_) date formats.
81
82    If the widget is bound to a schema field with respective
83    attributes, it reads its `show_year` and `date_format` attributes
84    (see waeup.kofa.schema.FormattedDate for an example) and sets a
85    CSS tag according to these values.
86
87    The widget also accepts ISO format as a fallback, even if a
88    different format was set. This should help with imports.
89
90    This is an input widget.
91    """
92    date_format = '%Y-%m-%d'
93    show_year = False
94
95    def __init__(self, context, request, *args, **kw):
96        # try to grab date_format and show_year from bound schema field.
97        date_format = getattr(context, 'date_format', self.date_format)
98        if date_format is not None:
99            self.date_format = date_format
100        self.show_year = getattr(context, 'show_year', self.show_year)
101        # add css class determined by date_format and show_year
102        css_cls = FORMATS_BY_VALUE.get((self.date_format, self.show_year), '')
103        self.cssClass = ' '.join([self.cssClass, css_cls]).strip()
104        return super(FormattedDateWidget, self).__init__(
105            context, request, *args, **kw)
106
107    def _toFieldValue(self, input):
108        # In import files we can use the hash symbol at the end of a
109        # date string to avoid annoying automatic date transformation
110        # by Excel or Calc
111        input = input.strip('#')
112        if input == self._missing:
113            return self.context.missing_value
114        else:
115            try:
116                value = datetime.strptime(input, self.date_format)
117            except (ValueError, IndexError), v:
118                try:
119                    # Try ISO format as fallback.
120                    # This is needed for instance during imports.
121                    value = datetime.strptime(
122                        input, FormattedDateWidget.date_format)
123                except (ValueError, IndexError), v:
124                    raise ConversionError("Invalid datetime data", v)
125        return value.date()
126
127    def _toFormValue(self, value):
128        if value:
129            value = value.strftime(self.date_format)
130        return value
131
132
133class FormattedDateDisplayWidget(DateDisplayWidget):
134    """A date widget that supports different (and _explicit_) date formats.
135
136    This is a display widget.
137
138    It can also be used for displaying datetimes. If used to display a
139    datetime (not a date), the widget returns local datetime with
140    timezone set according to KofaUtils.
141    """
142    date_format = '%Y-%m-%d'
143    show_year = False
144
145    implements(IDisplayWidget)
146
147    def __init__(self, context, request, *args, **kw):
148        # try to grab date_format and show_year from bound schema field.
149        date_format = getattr(context, 'date_format', self.date_format)
150        if date_format is not None:
151            self.date_format = date_format
152        self.show_year = getattr(context, 'show_year', self.show_year)
153        return super(FormattedDateDisplayWidget, self).__init__(
154            context, request, *args, **kw)
155
156    def __call__(self):
157        if self._renderedValueSet():
158            content = self._data
159        else:
160            content = self.context.default
161        if content == self.context.missing_value:
162            return ""
163        if isinstance(content, datetime):
164            # shift value to local timezone
165            tz = pytz.utc
166            utils = queryUtility(IKofaUtils)
167            if utils is not None:
168                tz = utils.tzinfo
169            content = to_timezone(content, tz)
170        content = content.strftime(self.date_format)
171        return renderElement("span", contents=escape(content),
172                             cssClass=self.cssClass)
173
174class DateLEWidget(FormattedDateWidget):
175    date_format = '%d/%m/%Y'
176
177class DateDEWidget(FormattedDateWidget):
178    date_format = '%d.%m.%Y'
179
180class DateUSWidget(FormattedDateWidget):
181    date_format = '%m/%d/%Y'
182
183class DateLEDisplayWidget(FormattedDateDisplayWidget):
184    date_format = '%d/%m/%Y'
185
186class DateDEDisplayWidget(FormattedDateDisplayWidget):
187    date_format = '%d.%m.%Y'
188
189class DateUSDisplayWidget(FormattedDateDisplayWidget):
190    date_format = '%m/%d/%Y'
191
192def FriendlyDateWidget(format):
193    """Get a friendly date input widget for `format`.
194
195    This widget is suitable for edit and add forms.
196
197    Valid `format` values are the keys of `DATE_FORMATS`
198    dict. Default is ``le`` (little endian; DD/MM/YYYY).
199
200    Friendly date widgets are rendered with a specialized CSS tag for
201    enabling JavaScript datepickers.
202    """
203    css_class, date_format = DATE_FORMATS.get(format, DATE_FORMATS['le'])
204    return CustomWidgetFactory(
205        FormattedDateWidget,
206        cssClass=css_class,
207        date_format=date_format)
208
209def FriendlyDateDisplayWidget(format):
210    """Get a friendly date display widget for `format`.
211
212    This widget is suitable for display forms.
213
214    Valid `format` values are the keys of `DATE_FORMATS`
215    dict. Default is ``le`` (little endian; DD/MM/YYYY).
216
217    This widget is not rendered with a specialized CSS tag for
218    enabling JavaScript datepickers. `css_class` is ignored which means
219    there is nor difference between e.g. ``le`` and ``le-year``.`
220    """
221    css_class, date_format = DATE_FORMATS.get(format, DATE_FORMATS['le'])
222    return CustomWidgetFactory(
223        FormattedDateDisplayWidget,
224        date_format=date_format)
225
226def FriendlyDatetimeDisplayWidget(format):
227    """Get a friendly datetime display widget for `format`.
228
229    This widget is suitable for display forms.
230
231    Valid `format` values are the keys of `DATE_FORMATS`
232    dict. Default is ``le`` (little endian; DD/MM/YYYY %H:%M:%S).
233
234    This widget is not rendered with a specialized CSS tag for
235    enabling JavaScript datepickers. `css_class` is ignored which means
236    there is no difference between e.g. ``le`` and ``le-year``.`
237    """
238    css_class, date_format = DATE_FORMATS.get(format, DATE_FORMATS['le'])
239    datetime_format = date_format + ' %H:%M:%S'
240    return CustomWidgetFactory(
241        FormattedDateDisplayWidget,
242        date_format=datetime_format)
Note: See TracBrowser for help on using the repository browser.