В предыдущем посте я упомянул о виджете для выбора даты в Django. И все вроде бы хорошо, но этот виджет становится совершенно бесполезным, когда надо:
- выбрать месяц день и год в другом порядке (например, в привычном день-месяц-год);
- использовать трехбуквенные сокращения месяца, а не полное название
- не выбирать день (например, май 2008)
- не выбирать ни день, ни месяц (например, 2008 год)
- добавить первым пустой <option>
И потому для этих случаев я смастерил очередной велосипед свой виджет, который устраняет все эти недостатки. Посмотреть как он работает можно здесь (поля Дата рождения, Год поступления, Год окончания, Период работы с, по).
Уже интересно?
Тогда получайте сам виджет:
import datetime, re
from time import strptime
from django.newforms.widgets import Widget, Select
from django.utils.dates import MONTHS, MONTHS_3
from django.utils.safestring import mark_safe
PATTERNS = (
('%b', 'month'),
('%B', 'month'),
('%d', 'day'),
('%m', 'month'),
('%y', 'year'),
('%Y', 'year'),
)
class SelectDateWidget(Widget):
"""
Extended version of django.newforms.extras.SelectDateWidget
The main advantages are:
- Widget can splits date input into custom select boxes.
- Custom select boxes can have first empty option.
"""
day_field = '%s_day'
month_field = '%s_month'
year_field = '%s_year'
def __init__(self, *args, **kwargs):
"""
Optional arguments:
format_separator - separator in input_format. By default: -
input_format - valid date input format. By default: %B-%d-%Y
null - adds first empty option to all selects. By
default: False
years - list/tuple of years to use in the "year" select
box. By default: this year and next 9 printed.
"""
self.attrs = kwargs.get('attrs', {})
self.format_separator = kwargs.get('format_separator', '-')
self.input_format = kwargs.get('input_format', '%B-%d-%Y')
self.null = kwargs.get('null', False)
if 'years' in kwargs:
self.years = kwargs['years']
else:
year = datetime.date.today().year
self.years = range(year, year+10)
fields = []
parts = self.input_format.split(self.format_separator)
for part in parts:
for k, v in PATTERNS:
if part == k:
fields.append((k, v))
if not fields:
raise TypeError('Date input format "%s" is broken.' % self.input_format)
self.fields = fields
self.input_format = self.input_format.replace('%b', '%m').replace('%B', '%m')
def id_for_label(self, id_):
return id_
id_for_label = classmethod(id_for_label)
def render(self, name, value, attrs=None):
try:
year, month, day = value.year, value.month, value.day
except AttributeError:
year = month = day = None
if isinstance(value, basestring):
try:
t = strptime(value, self.input_format)
year, month, day = t[0], t[1], t[2]
except:
pass
def _choices(pattern):
if pattern == '%b':
choices = MONTHS_3.items()
choices.sort()
elif pattern == '%B':
choices = MONTHS.items()
choices.sort()
elif pattern == '%d':
choices = [(i, i) for i in range(1, 32)]
elif pattern == '%m':
choices = [(i, i) for i in range(1, 13)]
elif pattern == '%y':
choices = [(i, str(i)[-2:]) for i in self.years]
elif pattern == '%Y':
choices = [(i, i) for i in self.years]
if self.null:
choices.insert(0, (None, mark_safe('—')))
return tuple(choices)
id_ = self.attrs.get('id', 'id_%s' % name)
output = []
for i, field in enumerate(self.fields):
pattern, field_name = field
field = getattr(self, '%s_field' % field_name)
sel_name = field % name
sel_value = locals().get(field_name, None)
if i == 0:
local_attrs = self.build_attrs(id=id_)
else:
local_attrs['id'] = field % id_
sel = Select(choices=_choices(pattern)).render(sel_name, sel_value, local_attrs)
output.append(sel)
return mark_safe('\n'.join(output))
def value_from_datadict(self, data, files, name):
value = []
for pattern, field_name in self.fields:
field = getattr(self, '%s_field' % field_name)
field_value = data.get(field % name, None)
if field_value and field_value != 'None':
value.append(str(field_value))
if value:
return '-'.join(value)
return data.get(name, None)
p.s. Примеры использования виджета в упомянутой форме:
class CvForm(forms.Form):
""" Some fields missed """
g_birth_date = forms.DateField(label=_('Birth date'), initial=datetime.date.today,
input_formats=('%d-%m-%Y',),
widget=SelectDateWidget(input_format='%d-%B-%Y', years=range(year, year-101, -1)))
e_from = forms.DateField(label=_('Entry year'), required=False, input_formats=('%Y',),
widget=SelectDateWidget(input_format='%Y', years=range(year, year-51, -1), null=True))
e_to = forms.DateField(label=_('Graduate year'), required=False, input_formats=('%Y'),
widget=SelectDateWidget(input_format='%Y', years=range(year, year-51, -1), null=True))
w_from = forms.DateField(label=_('Work from'), required=False, input_formats=('%m-%Y',),
widget=SelectDateWidget(input_format='%B-%Y', years=range(year, year-51, -1), null=True))
w_to = forms.DateField(label=_('Work to'), required=False, input_formats=('%m-%Y',),
widget=SelectDateWidget(input_format='%B-%Y', years=range(year, year-51, -1), null=True))