В предыдущем посте я упомянул о виджете для выбора даты в 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))