Code

Ticket #6231: ticket_6231.patch

File ticket_6231.patch, 15.1 KB (added by izi, 6 years ago)

patch for ticket #6231

  • django/newforms/extras/widgets.py

     
    66import re 
    77 
    88from django.newforms.widgets import Widget, Select 
    9 from django.utils.dates import MONTHS 
     9from django.utils.dates import MONTHS, MONTHS_AP, MONTHS_3 
    1010from django.utils.safestring import mark_safe 
     11from django.conf import settings 
    1112 
    12 __all__ = ('SelectDateWidget',) 
     13__all__ = ('SelectDateWidget', 'SelectTimeWidget',) 
    1314 
    14 RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$') 
    1515 
    16 class SelectDateWidget(Widget): 
     16class SelectDateWidgetBase(Widget): 
    1717    """ 
    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) 
    1927 
    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 
     65class SelectDateWidget(SelectDateWidgetBase): 
    2266    """ 
    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): 
    2870        # years is an optional list/tuple of years to use in the "year" select box. 
    29         self.attrs = attrs or {} 
    3071        if years: 
    3172            self.years = years 
    3273        else: 
    3374            this_year = datetime.date.today().year 
    3475            self.years = range(this_year, this_year+10) 
     76        super(SelectDateWidget, self).__init__(attrs, format) 
    3577 
    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) 
    4586 
    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 
    5093        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 
    52104 
    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 
    58120 
    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 [] 
    63147 
    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 [] 
    68159 
    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 [] 
    70171 
    71     def id_for_label(self, id_): 
    72         return '%s_month' % id_ 
    73     id_for_label = classmethod(id_for_label) 
    74172 
     173class 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 
    75212    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]) 
    79222        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

     
    2020# SelectDateWidget ############################################################ 
    2121 
    2222>>> from django.newforms.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') 
    2424>>> print w.render('mydate', '') 
    2525<select name="mydate_month" id="id_mydate_month"> 
    2626<option value="1">January</option> 
     
    2342342008-04-01 
    235235 
    236236 
     237# SelectTimeWidget ############################################################ 
     238 
     239>>> from django.newforms.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 
     331Accepts a datetime or a string: 
     332 
     333>>> w.render('mydate', datetime.time(15, 45, 15)) == w.render('mydate', '15:45:15') 
     334True 
     335 
     336Using 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() 
     342True 
     343>>> print a.cleaned_data['mytime'] 
     34415:45:15 
     345 
     346As with any widget that implements get_value_from_datadict, 
     347we must be prepared to accept the input from the "as_hidden" 
     348rendering 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() 
     354True 
     355>>> print b.cleaned_data['mytime'] 
     35615:45:15 
     357 
     358 
    237359# MultiWidget and MultiValueField ############################################# 
    238360# MultiWidgets are widgets composed of other widgets. They are usually 
    239361# combined with MultiValueFields - a field that is composed of other fields.