Kaydet (Commit) 728b6fd9 authored tarafından Loic Bistuer's avatar Loic Bistuer

Fixed #24219 -- Moved SelectDateWidget together with the other widgets

and deprecated django.forms.extras.

Thanks Berker Peksag and Tim Graham for the reviews.
üst 3a4c9e1b
from django.forms.extras.widgets import SelectDateWidget
import warnings
from django.forms.widgets import SelectDateWidget
from django.utils.deprecation import RemovedInDjango21Warning
__all__ = ['SelectDateWidget']
warnings.warn(
"django.forms.extras is deprecated. You can find "
"SelectDateWidget in django.forms.widgets instead.",
RemovedInDjango21Warning, stacklevel=2)
"""
Extra HTML Widget classes
"""
from __future__ import unicode_literals
import datetime
import re
from django.forms.widgets import Widget, Select
from django.utils import datetime_safe
from django.utils.dates import MONTHS
from django.utils.encoding import force_str
from django.utils.safestring import mark_safe
from django.utils.formats import get_format
from django.utils import six
from django.conf import settings
__all__ = ('SelectDateWidget',)
RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')
def _parse_date_fmt():
fmt = get_format('DATE_FORMAT')
escaped = False
for char in fmt:
if escaped:
escaped = False
elif char == '\\':
escaped = True
elif char in 'Yy':
yield 'year'
elif char in 'bEFMmNn':
yield 'month'
elif char in 'dj':
yield 'day'
class SelectDateWidget(Widget):
"""
A Widget that splits date input into three <select> boxes.
This also serves as an example of a Widget that has more than one HTML
element and hence implements value_from_datadict.
"""
none_value = (0, '---')
month_field = '%s_month'
day_field = '%s_day'
year_field = '%s_year'
def __init__(self, attrs=None, years=None, months=None, empty_label=None):
self.attrs = attrs or {}
# Optional list or tuple of years to use in the "year" select box.
if years:
self.years = years
else:
this_year = datetime.date.today().year
self.years = range(this_year, this_year + 10)
# Optional dict of months to use in the "month" select box.
if months:
self.months = months
else:
self.months = MONTHS
# Optional string, list, or tuple to use as empty_label.
if isinstance(empty_label, (list, tuple)):
if not len(empty_label) == 3:
raise ValueError('empty_label list/tuple must have 3 elements.')
self.year_none_value = (0, empty_label[0])
self.month_none_value = (0, empty_label[1])
self.day_none_value = (0, empty_label[2])
else:
if empty_label is not None:
self.none_value = (0, empty_label)
self.year_none_value = self.none_value
self.month_none_value = self.none_value
self.day_none_value = self.none_value
def render(self, name, value, attrs=None):
try:
year_val, month_val, day_val = value.year, value.month, value.day
except AttributeError:
year_val = month_val = day_val = None
if isinstance(value, six.string_types):
if settings.USE_L10N:
try:
input_format = get_format('DATE_INPUT_FORMATS')[0]
v = datetime.datetime.strptime(force_str(value), input_format)
year_val, month_val, day_val = v.year, v.month, v.day
except ValueError:
pass
else:
match = RE_DATE.match(value)
if match:
year_val, month_val, day_val = [int(v) for v in match.groups()]
html = {}
choices = [(i, i) for i in self.years]
html['year'] = self.create_select(name, self.year_field, value, year_val, choices, self.year_none_value)
choices = list(six.iteritems(self.months))
html['month'] = self.create_select(name, self.month_field, value, month_val, choices, self.month_none_value)
choices = [(i, i) for i in range(1, 32)]
html['day'] = self.create_select(name, self.day_field, value, day_val, choices, self.day_none_value)
output = []
for field in _parse_date_fmt():
output.append(html[field])
return mark_safe('\n'.join(output))
def id_for_label(self, id_):
for first_select in _parse_date_fmt():
return '%s_%s' % (id_, first_select)
else:
return '%s_month' % id_
def value_from_datadict(self, data, files, name):
y = data.get(self.year_field % name)
m = data.get(self.month_field % name)
d = data.get(self.day_field % name)
if y == m == d == "0":
return None
if y and m and d:
if settings.USE_L10N:
input_format = get_format('DATE_INPUT_FORMATS')[0]
try:
date_value = datetime.date(int(y), int(m), int(d))
except ValueError:
return '%s-%s-%s' % (y, m, d)
else:
date_value = datetime_safe.new_date(date_value)
return date_value.strftime(input_format)
else:
return '%s-%s-%s' % (y, m, d)
return data.get(name, None)
def create_select(self, name, field, value, val, choices, none_value):
if 'id' in self.attrs:
id_ = self.attrs['id']
else:
id_ = 'id_%s' % name
if not self.is_required:
choices.insert(0, none_value)
local_attrs = self.build_attrs(id=field % id_)
s = Select(choices=choices)
select_html = s.render(field % name, val, local_attrs)
return select_html
from django.forms.widgets import SelectDateWidget # NOQA
......@@ -5,26 +5,31 @@ HTML Widget classes
from __future__ import unicode_literals
import copy
import datetime
from itertools import chain
import re
from django.conf import settings
from django.forms.utils import flatatt, to_current_timezone
from django.utils import datetime_safe, formats, six
from django.utils.datastructures import MultiValueDict
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.dates import MONTHS
from django.utils.encoding import force_str, force_text, python_2_unicode_compatible
from django.utils.formats import get_format
from django.utils.html import conditional_escape, format_html
from django.utils.translation import ugettext_lazy
from django.utils.safestring import mark_safe
from django.utils import formats, six
from django.utils.six.moves import range
from django.utils.six.moves.urllib.parse import urljoin
from django.utils.translation import ugettext_lazy
__all__ = (
'Media', 'MediaDefiningClass', 'Widget', 'TextInput',
'EmailInput', 'URLInput', 'NumberInput', 'PasswordInput',
'HiddenInput', 'MultipleHiddenInput', 'ClearableFileInput',
'FileInput', 'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
'CheckboxSelectMultiple', 'MultiWidget',
'SplitDateTimeWidget', 'SplitHiddenDateTimeWidget',
'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'NumberInput',
'EmailInput', 'URLInput', 'PasswordInput', 'HiddenInput',
'MultipleHiddenInput', 'FileInput', 'ClearableFileInput', 'Textarea',
'DateInput', 'DateTimeInput', 'TimeInput', 'CheckboxInput', 'Select',
'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
'SplitHiddenDateTimeWidget', 'SelectDateWidget',
)
MEDIA_TYPES = ('css', 'js')
......@@ -893,3 +898,134 @@ class SplitHiddenDateTimeWidget(SplitDateTimeWidget):
super(SplitHiddenDateTimeWidget, self).__init__(attrs, date_format, time_format)
for widget in self.widgets:
widget.input_type = 'hidden'
class SelectDateWidget(Widget):
"""
A Widget that splits date input into three <select> boxes.
This also serves as an example of a Widget that has more than one HTML
element and hence implements value_from_datadict.
"""
none_value = (0, '---')
month_field = '%s_month'
day_field = '%s_day'
year_field = '%s_year'
date_re = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')
def __init__(self, attrs=None, years=None, months=None, empty_label=None):
self.attrs = attrs or {}
# Optional list or tuple of years to use in the "year" select box.
if years:
self.years = years
else:
this_year = datetime.date.today().year
self.years = range(this_year, this_year + 10)
# Optional dict of months to use in the "month" select box.
if months:
self.months = months
else:
self.months = MONTHS
# Optional string, list, or tuple to use as empty_label.
if isinstance(empty_label, (list, tuple)):
if not len(empty_label) == 3:
raise ValueError('empty_label list/tuple must have 3 elements.')
self.year_none_value = (0, empty_label[0])
self.month_none_value = (0, empty_label[1])
self.day_none_value = (0, empty_label[2])
else:
if empty_label is not None:
self.none_value = (0, empty_label)
self.year_none_value = self.none_value
self.month_none_value = self.none_value
self.day_none_value = self.none_value
@staticmethod
def _parse_date_fmt():
fmt = get_format('DATE_FORMAT')
escaped = False
for char in fmt:
if escaped:
escaped = False
elif char == '\\':
escaped = True
elif char in 'Yy':
yield 'year'
elif char in 'bEFMmNn':
yield 'month'
elif char in 'dj':
yield 'day'
def render(self, name, value, attrs=None):
try:
year_val, month_val, day_val = value.year, value.month, value.day
except AttributeError:
year_val = month_val = day_val = None
if isinstance(value, six.string_types):
if settings.USE_L10N:
try:
input_format = get_format('DATE_INPUT_FORMATS')[0]
v = datetime.datetime.strptime(force_str(value), input_format)
year_val, month_val, day_val = v.year, v.month, v.day
except ValueError:
pass
else:
match = self.date_re.match(value)
if match:
year_val, month_val, day_val = [int(v) for v in match.groups()]
html = {}
choices = [(i, i) for i in self.years]
html['year'] = self.create_select(name, self.year_field, value, year_val, choices, self.year_none_value)
choices = list(six.iteritems(self.months))
html['month'] = self.create_select(name, self.month_field, value, month_val, choices, self.month_none_value)
choices = [(i, i) for i in range(1, 32)]
html['day'] = self.create_select(name, self.day_field, value, day_val, choices, self.day_none_value)
output = []
for field in self._parse_date_fmt():
output.append(html[field])
return mark_safe('\n'.join(output))
def id_for_label(self, id_):
for first_select in self._parse_date_fmt():
return '%s_%s' % (id_, first_select)
else:
return '%s_month' % id_
def value_from_datadict(self, data, files, name):
y = data.get(self.year_field % name)
m = data.get(self.month_field % name)
d = data.get(self.day_field % name)
if y == m == d == "0":
return None
if y and m and d:
if settings.USE_L10N:
input_format = get_format('DATE_INPUT_FORMATS')[0]
try:
date_value = datetime.date(int(y), int(m), int(d))
except ValueError:
return '%s-%s-%s' % (y, m, d)
else:
date_value = datetime_safe.new_date(date_value)
return date_value.strftime(input_format)
else:
return '%s-%s-%s' % (y, m, d)
return data.get(name, None)
def create_select(self, name, field, value, val, choices, none_value):
if 'id' in self.attrs:
id_ = self.attrs['id']
else:
id_ = 'id_%s' % name
if not self.is_required:
choices.insert(0, none_value)
local_attrs = self.build_attrs(id=field % id_)
s = Select(choices=choices)
select_html = s.render(field % name, val, local_attrs)
return select_html
......@@ -18,6 +18,8 @@ details on these changes.
* The ``weak`` argument to ``django.dispatch.signals.Signal.disconnect()`` will
be removed.
* The ``django.forms.extras`` package will be removed.
.. _deprecation-removed-in-2.0:
2.0
......
......@@ -49,11 +49,10 @@ Setting arguments for widgets
Many widgets have optional extra arguments; they can be set when defining the
widget on the field. In the following example, the
:attr:`~django.forms.extras.widgets.SelectDateWidget.years` attribute is set
for a :class:`~django.forms.extras.widgets.SelectDateWidget`::
:attr:`~django.forms.SelectDateWidget.years` attribute is set for a
:class:`~django.forms.SelectDateWidget`::
from django import forms
from django.forms.extras.widgets import SelectDateWidget
BIRTH_YEAR_CHOICES = ('1980', '1981', '1982')
FAVORITE_COLORS_CHOICES = (('blue', 'Blue'),
......@@ -61,7 +60,7 @@ for a :class:`~django.forms.extras.widgets.SelectDateWidget`::
('black', 'Black'))
class SimpleForm(forms.Form):
birth_year = forms.DateField(widget=SelectDateWidget(years=BIRTH_YEAR_CHOICES))
birth_year = forms.DateField(widget=forms.SelectDateWidget(years=BIRTH_YEAR_CHOICES))
favorite_colors = forms.MultipleChoiceField(required=False,
widget=forms.CheckboxSelectMultiple, choices=FAVORITE_COLORS_CHOICES)
......@@ -752,8 +751,6 @@ Composite widgets
Similar to :class:`SplitDateTimeWidget`, but uses :class:`HiddenInput` for
both date and time.
.. currentmodule:: django.forms.extras.widgets
``SelectDateWidget``
~~~~~~~~~~~~~~~~~~~~
......@@ -807,3 +804,9 @@ Composite widgets
# A custom empty label with tuple
field1 = forms.DateField(widget=SelectDateWidget(
empty_label=("Choose Year", "Choose Month", "Choose Day"))
.. versionchanged:: 1.9
This widget used to be located in the ``django.forms.extras.widgets``
package. It is now defined in ``django.forms.widgets`` and like the
other widgets it can be imported directly from ``django.forms``.
......@@ -637,7 +637,7 @@ Forms
value.
* :attr:`SelectDateWidget.months
<django.forms.extras.widgets.SelectDateWidget.months>` can be used to
<django.forms.SelectDateWidget.months>` can be used to
customize the wording of the months displayed in the select widget.
* The ``min_num`` and ``validate_min`` parameters were added to
......@@ -1378,7 +1378,7 @@ Miscellaneous
the current class *and* on a parent ``Form``.
* The ``required`` argument of
:class:`~django.forms.extras.widgets.SelectDateWidget` has been removed.
:class:`~django.forms.SelectDateWidget` has been removed.
This widget now respects the form field's ``is_required`` attribute like
other widgets.
......
......@@ -336,9 +336,10 @@ Forms
a form's :attr:`~django.forms.Form.label_suffix` while using shortcuts such
as ``{{ form.as_p }}`` in templates.
* :class:`~django.forms.extras.widgets.SelectDateWidget` now accepts an
:attr:`~django.forms.extras.widgets.SelectDateWidget.empty_label` argument, which will
override the top list choice label when :class:`~django.forms.DateField` is not required.
* :class:`~django.forms.SelectDateWidget` now accepts an
:attr:`~django.forms.SelectDateWidget.empty_label` argument, which will
override the top list choice label when :class:`~django.forms.DateField`
is not required.
* After an :class:`~django.forms.ImageField` has been cleaned and validated, the
``UploadedFile`` object will have an additional ``image`` attribute containing
......
......@@ -187,6 +187,10 @@ Miscellaneous
will be removed in Django 2.1. The more general ``check_expression_support()``
should be used instead.
``django.forms.extras`` is deprecated. You can find
:class:`~django.forms.SelectDateWidget` in ``django.forms.widgets``
(or simply ``django.forms``) instead.
.. removed-features-1.9:
Features removed in 1.9
......
......@@ -12,10 +12,9 @@ from django.forms import (
ChoiceField, ClearableFileInput, DateField, DateInput, DateTimeInput,
FileInput, Form, HiddenInput, MultipleChoiceField, MultipleHiddenInput,
MultiValueField, MultiWidget, NullBooleanSelect, PasswordInput,
RadioSelect, Select, SelectMultiple, SplitDateTimeField,
RadioSelect, Select, SelectDateWidget, SelectMultiple, SplitDateTimeField,
SplitDateTimeWidget, Textarea, TextInput, TimeInput, ValidationError,
)
from django.forms.extras import SelectDateWidget
from django.forms.widgets import RadioFieldRenderer
from django.utils.safestring import mark_safe, SafeData
from django.utils import six, translation
......
from django import forms
from django.forms.extras import SelectDateWidget
from .models import Company
......@@ -14,7 +13,7 @@ class I18nForm(forms.Form):
class SelectDateForm(forms.Form):
date_field = forms.DateField(widget=SelectDateWidget)
date_field = forms.DateField(widget=forms.SelectDateWidget)
class CompanyForm(forms.ModelForm):
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment