source: main/waeup.ikoba/trunk/src/waeup/ikoba/widgets/datewidget.py @ 17930

Last change on this file since 17930 was 11949, checked in by Henrik Bettermann, 10 years ago

Change of name.

  • Property svn:keywords set to Id
File size: 8.9 KB
Line 
1## $Id: datewidget.py 11949 2014-11-13 14:40:27Z henrik $
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.ikoba.interfaces import IIkobaUtils
29from waeup.ikoba.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.ikoba.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            try:
130                value = value.strftime(self.date_format)
131            except ValueError:
132                return value
133        return value
134
135
136class FormattedDateDisplayWidget(DateDisplayWidget):
137    """A date widget that supports different (and _explicit_) date formats.
138
139    This is a display widget.
140
141    It can also be used for displaying datetimes. If used to display a
142    datetime (not a date), the widget returns local datetime with
143    timezone set according to IkobaUtils.
144    """
145    date_format = '%Y-%m-%d'
146    show_year = False
147
148    implements(IDisplayWidget)
149
150    def __init__(self, context, request, *args, **kw):
151        # try to grab date_format and show_year from bound schema field.
152        date_format = getattr(context, 'date_format', self.date_format)
153        if date_format is not None:
154            self.date_format = date_format
155        self.show_year = getattr(context, 'show_year', self.show_year)
156        return super(FormattedDateDisplayWidget, self).__init__(
157            context, request, *args, **kw)
158
159    def __call__(self):
160        if self._renderedValueSet():
161            content = self._data
162        else:
163            content = self.context.default
164        if content == self.context.missing_value:
165            return ""
166        if isinstance(content, datetime):
167            # shift value to local timezone
168            tz = pytz.utc
169            utils = queryUtility(IIkobaUtils)
170            if utils is not None:
171                tz = utils.tzinfo
172            content = to_timezone(content, tz)
173        try:
174            content = content.strftime(self.date_format)
175        except ValueError:
176            return None
177        return renderElement("span", contents=escape(content),
178                             cssClass=self.cssClass)
179
180class DateLEWidget(FormattedDateWidget):
181    date_format = '%d/%m/%Y'
182
183class DateDEWidget(FormattedDateWidget):
184    date_format = '%d.%m.%Y'
185
186class DateUSWidget(FormattedDateWidget):
187    date_format = '%m/%d/%Y'
188
189class DateLEDisplayWidget(FormattedDateDisplayWidget):
190    date_format = '%d/%m/%Y'
191
192class DateDEDisplayWidget(FormattedDateDisplayWidget):
193    date_format = '%d.%m.%Y'
194
195class DateUSDisplayWidget(FormattedDateDisplayWidget):
196    date_format = '%m/%d/%Y'
197
198def FriendlyDateWidget(format):
199    """Get a friendly date input widget for `format`.
200
201    This widget is suitable for edit and add forms.
202
203    Valid `format` values are the keys of `DATE_FORMATS`
204    dict. Default is ``le`` (little endian; DD/MM/YYYY).
205
206    Friendly date widgets are rendered with a specialized CSS tag for
207    enabling JavaScript datepickers.
208    """
209    css_class, date_format = DATE_FORMATS.get(format, DATE_FORMATS['le'])
210    return CustomWidgetFactory(
211        FormattedDateWidget,
212        cssClass=css_class,
213        date_format=date_format)
214
215def FriendlyDateDisplayWidget(format):
216    """Get a friendly date display widget for `format`.
217
218    This widget is suitable for display forms.
219
220    Valid `format` values are the keys of `DATE_FORMATS`
221    dict. Default is ``le`` (little endian; DD/MM/YYYY).
222
223    This widget is not rendered with a specialized CSS tag for
224    enabling JavaScript datepickers. `css_class` is ignored which means
225    there is nor difference between e.g. ``le`` and ``le-year``.`
226    """
227    css_class, date_format = DATE_FORMATS.get(format, DATE_FORMATS['le'])
228    return CustomWidgetFactory(
229        FormattedDateDisplayWidget,
230        date_format=date_format)
231
232def FriendlyDatetimeDisplayWidget(format):
233    """Get a friendly datetime display widget for `format`.
234
235    This widget is suitable for display forms.
236
237    Valid `format` values are the keys of `DATE_FORMATS`
238    dict. Default is ``le`` (little endian; DD/MM/YYYY %H:%M:%S).
239
240    This widget is not rendered with a specialized CSS tag for
241    enabling JavaScript datepickers. `css_class` is ignored which means
242    there is no difference between e.g. ``le`` and ``le-year``.`
243    """
244    css_class, date_format = DATE_FORMATS.get(format, DATE_FORMATS['le'])
245    datetime_format = date_format + ' %H:%M:%S'
246    return CustomWidgetFactory(
247        FormattedDateDisplayWidget,
248        date_format=datetime_format)
Note: See TracBrowser for help on using the repository browser.