Ticket #6231: ticket_6231.patch

File ticket_6231.patch, 15.1 KB (added by izi, 7 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.
Back to Top