В предыдущем посте я упомянул о виджете для выбора даты в 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))
8 комментариев:
Не хватает # coding: utf-8 в начае файла, без него python ругается на строку
choices.insert(0, (None, mark_safe('—')))
, где уникодный символ вставлен.
Не хватает # coding: utf-8 в начае файла, без него python ругается на строку
Это из-за Блоггера. В оригинале было —, а Блоггер интерпретировал это в HTML сущность.
Сейчас исправлю ;)
Спасибо большое за widget. Как раз начал ваять свой крвиой велосипед для такой же задачи и наткнулся на ваш :)
Было бы хорошо вместо пустых строк в каждый селект добавлять кастомный текст.
@krv
да, это нужная возможность, сейчас подумаю, как ее реализовать, чтобы можно было указывать кастомный текст и единый для всех селектов, и разный.
никому не мешает, что введённые данние не запоминаються?
Отличный виджет, легко вставляется в форму. Но никак не могу вставить его в модели, в админку вместо дефолтного. Никто не подскажет технологию?
playpauseandstop писал:
"
да, это нужная возможность, сейчас подумаю, как ее реализовать, чтобы можно было указывать кастомный текст и единый для всех селектов, и разный.
"
А как обстоят дела с данной фичей. Реализована она?
Отправить комментарий