Code

Ticket #16575: date_format-select_date_widget.diff

File date_format-select_date_widget.diff, 8.2 KB (added by calebs, 2 years ago)
Line 
1diff --git a/django/forms/extras/widgets.py b/django/forms/extras/widgets.py
2index 4fb4869..efac85f 100644
3--- a/django/forms/extras/widgets.py
4+++ b/django/forms/extras/widgets.py
5@@ -16,25 +16,21 @@ __all__ = ('SelectDateWidget',)
6 
7 RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')
8 
9-def _parse_date_fmt():
10-    fmt = get_format('DATE_FORMAT')
11-    escaped = False
12-    output = []
13-    for char in fmt:
14-        if escaped:
15-            escaped = False
16-        elif char == '\\':
17-            escaped = True
18-        elif char in 'Yy':
19-            output.append('year')
20-            #if not self.first_select: self.first_select = 'year'
21-        elif char in 'bEFMmNn':
22-            output.append('month')
23-            #if not self.first_select: self.first_select = 'month'
24-        elif char in 'dj':
25-            output.append('day')
26-            #if not self.first_select: self.first_select = 'day'
27-    return output
28+
29+def _parse_format(fmt=None):
30+    """
31+    Sort the order of date selectors according to the date_format attribute.
32+    If date_format is not given, default to get_format('DATE_FORMAT')[0]
33+    """
34+    if not fmt:
35+        return _parse_format(re.subn(r'\\.', '',
36+                             get_format('DATE_INPUT_FORMATS')[0])[0])
37+    def pos(s):
38+        match = re.search('[' + s + ']', fmt)
39+        return match.start() + 1 if match else 0
40+    dic = {'day': pos('dj'), 'month': pos('bEFMmNn'), 'year': pos('yY')}
41+    return sorted(filter(None, dic.keys()), key=dic.get)
42+
43 
44 class SelectDateWidget(Widget):
45     """
46@@ -43,99 +39,102 @@ class SelectDateWidget(Widget):
47     This also serves as an example of a Widget that has more than one HTML
48     element and hence implements value_from_datadict.
49     """
50-    none_value = (0, '---')
51-    month_field = '%s_month'
52-    day_field = '%s_day'
53-    year_field = '%s_year'
54+    names = ('day', 'month', 'year',)
55+    none_values_dafault = '---'
56+    field_values = ['%s_' + n for n in names]
57+    fields = dict(zip(names, field_values))
58+    day_field, month_field, year_field = field_values
59 
60-    def __init__(self, attrs=None, years=None, required=True):
61-        # years is an optional list/tuple of years to use in the "year" select box.
62+    def __init__(self, attrs=None, years=None, required=True,
63+            date_format=None, none_values=None):
64+        # years is an optional list/tuple of years
65+        # to use in the "year" select box.
66         self.attrs = attrs or {}
67         self.required = required
68-        if years:
69-            self.years = years
70-        else:
71+        if not years:
72             this_year = datetime.date.today().year
73-            self.years = range(this_year, this_year+10)
74+            years = range(this_year, this_year + 10)
75+        self.years = years
76+        self.date_format = _parse_format(date_format)
77+        self.none_values = none_values \
78+            or dict([(i, self.none_values_dafault) for i in 'dmy'])
79 
80     def render(self, name, value, attrs=None):
81+        get_names_dict = lambda l: dict(zip(self.names, l))
82+
83         try:
84-            year_val, month_val, day_val = value.year, value.month, value.day
85+            values_list = [getattr(value, n) for n in self.names]
86         except AttributeError:
87-            year_val = month_val = day_val = None
88+            values_list = []
89             if isinstance(value, basestring):
90                 if settings.USE_L10N:
91                     try:
92                         input_format = get_format('DATE_INPUT_FORMATS')[0]
93                         v = datetime.datetime.strptime(value, input_format)
94-                        year_val, month_val, day_val = v.year, v.month, v.day
95+                        values_list = [getattr(v, n) for n in self.names]
96                     except ValueError:
97                         pass
98                 else:
99                     match = RE_DATE.match(value)
100                     if match:
101-                        year_val, month_val, day_val = [int(v) for v in match.groups()]
102-        choices = [(i, i) for i in self.years]
103-        year_html = self.create_select(name, self.year_field, value, year_val, choices)
104-        choices = MONTHS.items()
105-        month_html = self.create_select(name, self.month_field, value, month_val, choices)
106-        choices = [(i, i) for i in range(1, 32)]
107-        day_html = self.create_select(name, self.day_field, value, day_val,  choices)
108-
109-        output = []
110-        for field in _parse_date_fmt():
111-            if field == 'year':
112-                output.append(year_html)
113-            elif field == 'month':
114-                output.append(month_html)
115-            elif field == 'day':
116-                output.append(day_html)
117+                        values_list = (int(v) for v in match.groups())
118+        values = get_names_dict(values_list)
119+
120+        choices = get_names_dict([
121+            [(i, i) for i in range(1, 32)],
122+            MONTHS.items(),
123+            [(i, i) for i in self.years],
124+        ])
125+        create_select_short = lambda n: self.create_select(
126+            name, self.fields[n], value, values.get(n), choices[n], n
127+        )
128+
129+        htmls = get_names_dict([create_select_short(n) for n in self.names])
130+        date_format = self.date_format or self._parse_format()
131+        output = [htmls[field] for field in date_format]
132         return mark_safe(u'\n'.join(output))
133 
134-    def id_for_label(self, id_):
135-        first_select = None
136-        field_list = _parse_date_fmt()
137+    def id_for_label(cls, id_):
138+        field_list = _parse_format()
139         if field_list:
140             first_select = field_list[0]
141-        if first_select is not None:
142-            return '%s_%s' % (id_, first_select)
143         else:
144-            return '%s_month' % id_
145+            first_select = 'month'
146+        return '%s_%s' % (id_, first_select)
147     id_for_label = classmethod(id_for_label)
148 
149     def value_from_datadict(self, data, files, name):
150-        y = data.get(self.year_field % name)
151-        m = data.get(self.month_field % name)
152-        d = data.get(self.day_field % name)
153+        d, m, y = (data.get(self.fields[n] % name) for n in self.names)
154         if y == m == d == "0":
155             return None
156-        if y and m and d:
157-            if settings.USE_L10N:
158-                input_format = get_format('DATE_INPUT_FORMATS')[0]
159-                try:
160-                    date_value = datetime.date(int(y), int(m), int(d))
161-                except ValueError:
162-                    return '%s-%s-%s' % (y, m, d)
163-                else:
164-                    date_value = datetime_safe.new_date(date_value)
165-                    return date_value.strftime(input_format)
166-            else:
167-                return '%s-%s-%s' % (y, m, d)
168-        return data.get(name, None)
169+        if not (y and m and d):
170+            return data.get(name)
171+        if not settings.USE_L10N:
172+            return '%s-%s-%s' % (y, m, d)
173+        input_format = get_format('DATE_INPUT_FORMATS')[0]
174+        try:
175+            date_value = datetime.date(int(y), int(m), int(d))
176+            date_value = datetime_safe.new_date(date_value)
177+            return date_value.strftime(input_format)
178+        except ValueError:
179+            return '%s-%s-%s' % (y, m, d)
180 
181-    def create_select(self, name, field, value, val, choices):
182+    def create_select(self, name, field, value, val, choices, date_part='d'):
183         if 'id' in self.attrs:
184             id_ = self.attrs['id']
185         else:
186             id_ = 'id_%s' % name
187         if not (self.required and val):
188-            choices.insert(0, self.none_value)
189+            choices.insert(0, (0, self.none_values[date_part[0]]))
190         local_attrs = self.build_attrs(id=field % id_)
191         s = Select(choices=choices)
192         select_html = s.render(field % name, val, local_attrs)
193         return select_html
194 
195     def _has_changed(self, initial, data):
196-        input_format = get_format('DATE_INPUT_FORMATS')[0]
197-        data = datetime_safe.datetime.strptime(data, input_format).date()
198-        return super(SelectDateWidget, self)._has_changed(initial, data)
199\ No newline at end of file
200+        try:
201+            input_format = get_format('DATE_INPUT_FORMATS')[0]
202+            data = datetime_safe.datetime.strptime(data, input_format).date()
203+        except (TypeError, ValueError):
204+            pass
205+        return super(SelectDateWidget, self)._has_changed(initial, data)