forked from benadida/helios-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwidgets.py
197 lines (162 loc) · 7.9 KB
/
widgets.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
"""
Widget for datetime split, with calendar for date, and drop-downs for times.
"""
from django import forms
from django.db import models
from django.template.loader import render_to_string
from django.forms.widgets import Select, MultiWidget, DateInput, TextInput, Widget
from django.forms.extras.widgets import SelectDateWidget
from time import strftime
import re
from django.utils.safestring import mark_safe
__all__ = ('SelectTimeWidget', 'SplitSelectDateTimeWidget')
# Attempt to match many time formats:
# Example: "12:34:56 P.M." matches:
# ('12', '34', ':56', '56', 'P.M.', 'P', '.', 'M', '.')
# ('12', '34', ':56', '56', 'P.M.')
# Note that the colon ":" before seconds is optional, but only if seconds are omitted
time_pattern = r'(\d\d?):(\d\d)(:(\d\d))? *([aApP]\.?[mM]\.?)?$'
RE_TIME = re.compile(time_pattern)
# The following are just more readable ways to access re.matched groups:
HOURS = 0
MINUTES = 1
SECONDS = 3
MERIDIEM = 4
class SelectTimeWidget(Widget):
"""
A Widget that splits time input into <select> elements.
Allows form to show as 24hr: <hour>:<minute>:<second>, (default)
or as 12hr: <hour>:<minute>:<second> <am|pm>
Also allows user-defined increments for minutes/seconds
"""
hour_field = '%s_hour'
minute_field = '%s_minute'
meridiem_field = '%s_meridiem'
twelve_hr = False # Default to 24hr.
def __init__(self, attrs=None, hour_step=None, minute_step=None, twelve_hr=False):
"""
hour_step, minute_step, second_step are optional step values for
for the range of values for the associated select element
twelve_hr: If True, forces the output to be in 12-hr format (rather than 24-hr)
"""
self.attrs = attrs or {}
if twelve_hr:
self.twelve_hr = True # Do 12hr (rather than 24hr)
self.meridiem_val = 'a.m.' # Default to Morning (A.M.)
if hour_step and twelve_hr:
self.hours = range(1,13,hour_step)
elif hour_step: # 24hr, with stepping.
self.hours = range(0,24,hour_step)
elif twelve_hr: # 12hr, no stepping
self.hours = range(1,13)
else: # 24hr, no stepping
self.hours = range(0,24)
if minute_step:
self.minutes = range(0,60,minute_step)
else:
self.minutes = range(0,60)
def render(self, name, value, attrs=None):
try: # try to get time values from a datetime.time object (value)
hour_val, minute_val = value.hour, value.minute
if self.twelve_hr:
if hour_val >= 12:
self.meridiem_val = 'p.m.'
else:
self.meridiem_val = 'a.m.'
except AttributeError:
hour_val = minute_val = 0
if isinstance(value, basestring):
match = RE_TIME.match(value)
if match:
time_groups = match.groups();
hour_val = int(time_groups[HOURS]) % 24 # force to range(0-24)
minute_val = int(time_groups[MINUTES])
# check to see if meridiem was passed in
if time_groups[MERIDIEM] is not None:
self.meridiem_val = time_groups[MERIDIEM]
else: # otherwise, set the meridiem based on the time
if self.twelve_hr:
if hour_val >= 12:
self.meridiem_val = 'p.m.'
else:
self.meridiem_val = 'a.m.'
else:
self.meridiem_val = None
# If we're doing a 12-hr clock, there will be a meridiem value, so make sure the
# hours get printed correctly
if self.twelve_hr and self.meridiem_val:
if self.meridiem_val.lower().startswith('p') and hour_val > 12 and hour_val < 24:
hour_val = hour_val % 12
elif hour_val == 0:
hour_val = 12
output = []
if 'id' in self.attrs:
id_ = self.attrs['id']
else:
id_ = 'id_%s' % name
# For times to get displayed correctly, the values MUST be converted to unicode
# When Select builds a list of options, it checks against Unicode values
hour_val = u"%.2d" % hour_val
minute_val = u"%.2d" % minute_val
hour_choices = [("%.2d"%i, "%.2d"%i) for i in self.hours]
local_attrs = self.build_attrs(id=self.hour_field % id_)
select_html = Select(choices=hour_choices).render(self.hour_field % name, hour_val, local_attrs)
output.append(select_html)
minute_choices = [("%.2d"%i, "%.2d"%i) for i in self.minutes]
local_attrs['id'] = self.minute_field % id_
select_html = Select(choices=minute_choices).render(self.minute_field % name, minute_val, local_attrs)
output.append(select_html)
if self.twelve_hr:
# If we were given an initial value, make sure the correct meridiem gets selected.
if self.meridiem_val is not None and self.meridiem_val.startswith('p'):
meridiem_choices = [('p.m.','p.m.'), ('a.m.','a.m.')]
else:
meridiem_choices = [('a.m.','a.m.'), ('p.m.','p.m.')]
local_attrs['id'] = local_attrs['id'] = self.meridiem_field % id_
select_html = Select(choices=meridiem_choices).render(self.meridiem_field % name, self.meridiem_val, local_attrs)
output.append(select_html)
return mark_safe(u'\n'.join(output))
def id_for_label(self, id_):
return '%s_hour' % id_
id_for_label = classmethod(id_for_label)
def value_from_datadict(self, data, files, name):
# if there's not h:m:s data, assume zero:
h = data.get(self.hour_field % name, 0) # hour
m = data.get(self.minute_field % name, 0) # minute
meridiem = data.get(self.meridiem_field % name, None)
#NOTE: if meridiem is None, assume 24-hr
if meridiem is not None:
if meridiem.lower().startswith('p') and int(h) != 12:
h = (int(h)+12)%24
elif meridiem.lower().startswith('a') and int(h) == 12:
h = 0
if (int(h) == 0 or h) and m:
return '%s:%s' % (h, m)
return data.get(name, None)
class SplitSelectDateTimeWidget(MultiWidget):
"""
MultiWidget = A widget that is composed of multiple widgets.
This class combines SelectTimeWidget and SelectDateWidget so we have something
like SpliteDateTimeWidget (in django.forms.widgets), but with Select elements.
"""
def __init__(self, attrs=None, hour_step=None, minute_step=None, twelve_hr=None, years=None):
""" pass all these parameters to their respective widget constructors..."""
widgets = (SelectDateWidget(attrs=attrs, years=years), SelectTimeWidget(attrs=attrs, hour_step=hour_step, minute_step=minute_step, twelve_hr=twelve_hr))
super(SplitSelectDateTimeWidget, self).__init__(widgets, attrs)
# See https://stackoverflow.com/questions/4324676/django-multiwidget-subclass-not-calling-decompress
def value_from_datadict(self, data, files, name):
if data.get(name, None) is None:
return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
return self.decompress(data.get(name, None))
def decompress(self, value):
if value:
return [value.date(), value.time().replace(microsecond=0)]
return [None, None]
def format_output(self, rendered_widgets):
"""
Given a list of rendered widgets (as strings), it inserts an HTML
linebreak between them.
Returns a Unicode string representing the HTML for the whole lot.
"""
rendered_widgets.insert(-1, '<br/>')
return u''.join(rendered_widgets)