В предыдущем посте я упомянул о виджете для выбора даты в 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 писал:
"
да, это нужная возможность, сейчас подумаю, как ее реализовать, чтобы можно было указывать кастомный текст и единый для всех селектов, и разный.
"
А как обстоят дела с данной фичей. Реализована она?
Отправить комментарий