Ticket #6231: ticket_6231_v2.patch
File ticket_6231_v2.patch, 15.1 KB (added by , 16 years ago) |
---|
-
django/forms/extras/widgets.py
6 6 import re 7 7 8 8 from django.forms.widgets import Widget, Select 9 from django.utils.dates import MONTHS 9 from django.utils.dates import MONTHS, MONTHS_AP, MONTHS_3 10 10 from django.utils.safestring import mark_safe 11 from django.conf import settings 11 12 12 __all__ = ('SelectDateWidget', )13 __all__ = ('SelectDateWidget', 'SelectTimeWidget',) 13 14 14 RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')15 15 16 class SelectDateWidget (Widget):16 class SelectDateWidgetBase(Widget): 17 17 """ 18 A Widget that splits date input into three <select> boxes. 18 Base class for SelectDateWidget, SelectDateTimeWidget and 19 SelectTimeWidget. 20 """ 21 def __init__(self, attrs=None, format=None): 22 if attrs is None: 23 attrs = {} 24 self.attrs = attrs 25 self.values = {} 26 self.format = self.parse_format(format) 19 27 20 This also serves as an example of a Widget that has more than one HTML 21 element and hence implements value_from_datadict. 28 def render(self, name, value, attrs=None): 29 """ 30 Return the html code of the widget. 31 """ 32 if 'id' in self.attrs: 33 id_ = self.attrs['id'] 34 else: 35 id_ = 'id_%s' % name 36 local_attrs = self.build_attrs() 37 self.values = self.parse_value(value) 38 output = [] 39 for (n, fmt) in self.format: 40 select_name = '%s_%s' % (name, n) 41 local_attrs['id'] = '%s_%s' % (id_, n) 42 if hasattr(self, '%s_choices' % n): 43 select = Select(choices=getattr(self, '%s_choices' % n)(fmt)) 44 html = select.render(select_name, self.values[n], local_attrs) 45 output.append(html) 46 return mark_safe(u'\n'.join(output)) 47 48 def id_for_label(self, id_): 49 return '%s_%s' % (self.format[0][1], id_) 50 id_for_label = classmethod(id_for_label) 51 52 def value_from_datadict(self, data, files, name): 53 raise NotImplementedError('SelectDateWidgetBase::value_from_datadict()\ 54 is abstract and must be implemented in child classes') 55 56 def parse_format(self, fmt): 57 raise NotImplementedError('SelectDateWidgetBase::parse_format() is \ 58 abstract and must be implemented in child classes') 59 60 def parse_value(self, fmt): 61 raise NotImplementedError('SelectDateWidgetBase::parse_value() is \ 62 abstract and must be implemented in child classes') 63 64 65 class SelectDateWidget(SelectDateWidgetBase): 22 66 """ 23 month_field = '%s_month' 24 day_field = '%s_day' 25 year_field = '%s_year' 26 27 def __init__(self, attrs=None, years=None): 67 A Widget that splits date input into three <select> boxes. 68 """ 69 def __init__(self, attrs=None, years=None, format=None): 28 70 # years is an optional list/tuple of years to use in the "year" select box. 29 self.attrs = attrs or {}30 71 if years: 31 72 self.years = years 32 73 else: 33 74 this_year = datetime.date.today().year 34 75 self.years = range(this_year, this_year+10) 76 super(SelectDateWidget, self).__init__(attrs, format) 35 77 36 def render(self, name, value, attrs=None): 37 try: 38 year_val, month_val, day_val = value.year, value.month, value.day 39 except AttributeError: 40 year_val = month_val = day_val = None 41 if isinstance(value, basestring): 42 match = RE_DATE.match(value) 43 if match: 44 year_val, month_val, day_val = [int(v) for v in match.groups()] 78 def value_from_datadict(self, data, files, name): 79 vals = [] 80 y = data.get('%s_year' % name) 81 m = data.get('%s_month' % name) 82 d = data.get('%s_day' % name) 83 if y and m and d: 84 return u'-'.join([y, m, d]) 85 return data.get(name, None) 45 86 46 output = [] 47 48 if 'id' in self.attrs: 49 id_ = self.attrs['id'] 87 def parse_value(self, val): 88 ret = {} 89 if isinstance(val, datetime.date): 90 ret['month'] = val.month 91 ret['day'] = val.day 92 ret['year'] = val.year 50 93 else: 51 id_ = 'id_%s' % name 94 try: 95 l = map(int, val.split('-')) 96 except (ValueError, AttributeError): 97 l = (None, None, None) 98 for i, k in [(0, 'year'), (1, 'month'), (2, 'day')]: 99 try: 100 ret[k] = l[i] 101 except IndexError: 102 ret[k] = None 103 return ret 52 104 53 month_choices = MONTHS.items() 54 month_choices.sort() 55 local_attrs = self.build_attrs(id=self.month_field % id_) 56 select_html = Select(choices=month_choices).render(self.month_field % name, month_val, local_attrs) 57 output.append(select_html) 105 def parse_format(self, fmt): 106 """ 107 Parse the given format `fmt` and set the format property. 108 """ 109 if fmt is None: 110 fmt = settings.DATE_FORMAT 111 ret = [] 112 for item in fmt: 113 if item in ['d', 'D', 'j', 'L']: 114 ret.append(('day', item,)) 115 elif item in ['n', 'm', 'F', 'b', 'M', 'N']: 116 ret.append(('month', item,)) 117 elif item in ['y', 'Y']: 118 ret.append(('year', item,)) 119 return ret 58 120 59 day_choices = [(i, i) for i in range(1, 32)] 60 local_attrs['id'] = self.day_field % id_ 61 select_html = Select(choices=day_choices).render(self.day_field % name, day_val, local_attrs) 62 output.append(select_html) 121 def month_choices(self, fmt): 122 """ 123 Return list of choices (tuple (key, value)) for monthes select. 124 """ 125 if fmt == 'n': 126 # month numbers without leading 0 (1 .. 12) 127 return [(i, i) for i in range(1, 13)] 128 elif fmt == 'm': 129 # month numbers with leading 0 (01 .. 12) 130 return [(i, '%02d' % i) for i in range(1, 13)] 131 elif fmt in ['F', 'b', 'M', 'N']: 132 if fmt == 'F': 133 # full month names 134 month_choices = MONTHS.items() 135 elif fmt == 'b': 136 # 3 first letters of month lowercase 137 month_choices = [(k, v.lower()) for (k, v) in MONTHS_3.items()] 138 elif fmt == 'M': 139 # 3 first letters of month 140 month_choices = MONTHS_3.items() 141 elif fmt == 'N': 142 # abbrev of month names 143 month_choices = MONTHS_AP.items() 144 month_choices.sort() 145 return month_choices 146 return [] 63 147 64 year_choices = [(i, i) for i in self.years] 65 local_attrs['id'] = self.year_field % id_ 66 select_html = Select(choices=year_choices).render(self.year_field % name, year_val, local_attrs) 67 output.append(select_html) 148 def day_choices(self, fmt): 149 """ 150 Return list of choices (tuple (key, value)) for days select. 151 """ 152 if fmt == 'j': 153 # day of month number without leading 0 154 return [(i, i) for i in range(1, 32)] 155 elif fmt == 'd': 156 # day of month number with leading 0 157 return [(i, '%02d' % i) for i in range(1, 32)] 158 return [] 68 159 69 return mark_safe(u'\n'.join(output)) 160 def year_choices(self, fmt): 161 """ 162 Return list of choices (tuple (key, value)) for years select. 163 """ 164 if fmt == 'Y': 165 # years with 4 numbers 166 return [(i, i) for i in self.years] 167 elif fmt == 'y': 168 # years with only the last 2 numbers 169 return [(i, str(i)[-2:]) for i in self.years] 170 return [] 70 171 71 def id_for_label(self, id_):72 return '%s_month' % id_73 id_for_label = classmethod(id_for_label)74 172 173 class SelectTimeWidget(SelectDateWidgetBase): 174 """ 175 A Widget that splits time input into two or three <select> boxes. 176 XXX: at the moment it is limited to theses formats: 'Hi' and 'His'. 177 """ 178 def __init__(self, attrs=None, format=None): 179 super(SelectTimeWidget, self).__init__(attrs, format) 180 181 def parse_format(self, fmt): 182 if fmt not in ['Hi', 'His']: 183 fmt = 'Hi' 184 ret = [] 185 for item in fmt: 186 if item == 'H': 187 ret.append(('hour', item,)) 188 elif item == 'i': 189 ret.append(('minute', item,)) 190 elif item == 's': 191 ret.append(('second', item,)) 192 return ret 193 194 def parse_value(self, val): 195 ret = {} 196 if isinstance(val, datetime.time): 197 ret['hour'] = val.hour 198 ret['minute'] = val.minute 199 ret['second'] = val.second 200 else: 201 try: 202 l = map(int, val.split(':')) 203 except (ValueError, AttributeError): 204 l = (None, None, None) 205 for i, k in [(0, 'hour'), (1, 'minute'), (2, 'second')]: 206 try: 207 ret[k] = l[i] 208 except IndexError: 209 ret[k] = None 210 return ret 211 75 212 def value_from_datadict(self, data, files, name): 76 y, m, d = data.get(self.year_field % name), data.get(self.month_field % name), data.get(self.day_field % name) 77 if y and m and d: 78 return '%s-%s-%s' % (y, m, d) 213 vals = [] 214 h = data.get('%s_hour' % name) 215 m = data.get('%s_minute' % name) 216 s = data.get('%s_second' % name) 217 if h and m: 218 if s: 219 return u':'.join([h, m, s]) 220 else: 221 return u':'.join([h, m]) 79 222 return data.get(name, None) 223 224 def hour_choices(self, fmt): 225 """ 226 Return list of choices (tuple (key, value)) for hours select. 227 """ 228 # hour 24H format with leading 0 229 return [(i, '%02d' % i) for i in range(0, 24)] 230 231 def minute_choices(self, fmt): 232 """ 233 Return list of choices (tuple (key, value)) for minutes select. 234 """ 235 # minutes with leading 0 236 return [(i, '%02d' % i) for i in range(0, 60)] 237 238 def second_choices(self, fmt): 239 """ 240 Return list of choices (tuple (key, value)) for seconds select. 241 """ 242 # seconds with leading 0 243 return [(i, '%02d' % i) for i in range(0, 60)] 244 -
tests/regressiontests/forms/extra.py
20 20 # SelectDateWidget ############################################################ 21 21 22 22 >>> from django.forms.extras import SelectDateWidget 23 >>> w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016') )23 >>> w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016'), format='FjY') 24 24 >>> print w.render('mydate', '') 25 25 <select name="mydate_month" id="id_mydate_month"> 26 26 <option value="1">January</option> … … 234 234 2008-04-01 235 235 236 236 237 # SelectTimeWidget ############################################################ 238 239 >>> from django.forms.extras import SelectTimeWidget 240 >>> w = SelectTimeWidget() 241 >>> print w.render('mytime', '') 242 <select name="mytime_hour" id="id_mytime_hour"> 243 <option value="0">00</option> 244 <option value="1">01</option> 245 <option value="2">02</option> 246 <option value="3">03</option> 247 <option value="4">04</option> 248 <option value="5">05</option> 249 <option value="6">06</option> 250 <option value="7">07</option> 251 <option value="8">08</option> 252 <option value="9">09</option> 253 <option value="10">10</option> 254 <option value="11">11</option> 255 <option value="12">12</option> 256 <option value="13">13</option> 257 <option value="14">14</option> 258 <option value="15">15</option> 259 <option value="16">16</option> 260 <option value="17">17</option> 261 <option value="18">18</option> 262 <option value="19">19</option> 263 <option value="20">20</option> 264 <option value="21">21</option> 265 <option value="22">22</option> 266 <option value="23">23</option> 267 </select> 268 <select name="mytime_minute" id="id_mytime_minute"> 269 <option value="0">00</option> 270 <option value="1">01</option> 271 <option value="2">02</option> 272 <option value="3">03</option> 273 <option value="4">04</option> 274 <option value="5">05</option> 275 <option value="6">06</option> 276 <option value="7">07</option> 277 <option value="8">08</option> 278 <option value="9">09</option> 279 <option value="10">10</option> 280 <option value="11">11</option> 281 <option value="12">12</option> 282 <option value="13">13</option> 283 <option value="14">14</option> 284 <option value="15">15</option> 285 <option value="16">16</option> 286 <option value="17">17</option> 287 <option value="18">18</option> 288 <option value="19">19</option> 289 <option value="20">20</option> 290 <option value="21">21</option> 291 <option value="22">22</option> 292 <option value="23">23</option> 293 <option value="24">24</option> 294 <option value="25">25</option> 295 <option value="26">26</option> 296 <option value="27">27</option> 297 <option value="28">28</option> 298 <option value="29">29</option> 299 <option value="30">30</option> 300 <option value="31">31</option> 301 <option value="32">32</option> 302 <option value="33">33</option> 303 <option value="34">34</option> 304 <option value="35">35</option> 305 <option value="36">36</option> 306 <option value="37">37</option> 307 <option value="38">38</option> 308 <option value="39">39</option> 309 <option value="40">40</option> 310 <option value="41">41</option> 311 <option value="42">42</option> 312 <option value="43">43</option> 313 <option value="44">44</option> 314 <option value="45">45</option> 315 <option value="46">46</option> 316 <option value="47">47</option> 317 <option value="48">48</option> 318 <option value="49">49</option> 319 <option value="50">50</option> 320 <option value="51">51</option> 321 <option value="52">52</option> 322 <option value="53">53</option> 323 <option value="54">54</option> 324 <option value="55">55</option> 325 <option value="56">56</option> 326 <option value="57">57</option> 327 <option value="58">58</option> 328 <option value="59">59</option> 329 </select> 330 331 Accepts a datetime or a string: 332 333 >>> w.render('mydate', datetime.time(15, 45, 15)) == w.render('mydate', '15:45:15') 334 True 335 336 Using a SelectDateWidget in a form: 337 338 >>> class GetTime(Form): 339 ... mytime = TimeField(widget=SelectTimeWidget) 340 >>> a = GetTime({'mytime_hour':'15', 'mytime_minute':'45', 'mytime_second':'15'}) 341 >>> print a.is_valid() 342 True 343 >>> print a.cleaned_data['mytime'] 344 15:45:15 345 346 As with any widget that implements get_value_from_datadict, 347 we must be prepared to accept the input from the "as_hidden" 348 rendering as well. 349 350 >>> print a['mytime'].as_hidden() 351 <input type="hidden" name="mytime" value="15:45:15" id="id_mytime" /> 352 >>> b=GetTime({'mytime':'15:45:15'}) 353 >>> print b.is_valid() 354 True 355 >>> print b.cleaned_data['mytime'] 356 15:45:15 357 358 237 359 # MultiWidget and MultiValueField ############################################# 238 360 # MultiWidgets are widgets composed of other widgets. They are usually 239 361 # combined with MultiValueFields - a field that is composed of other fields.