Source code for uswds_forms.date

from datetime import date
from collections import namedtuple

from django.core.exceptions import ValidationError
from django.forms import MultiWidget, NumberInput
from django.forms.fields import MultiValueField, IntegerField


__all__ = (
    'UswdsDateField',
    'UswdsDateWidget',
)


# This is just a convenience that allows us to reference the
# fields without having to remember what index they are in the
# tuple ordering.
DateTuple = namedtuple('DateTuple', ['year', 'month', 'day'])


FIELD_ORDERING = DateTuple._fields
YEAR_ID = FIELD_ORDERING.index('year')
MONTH_ID = FIELD_ORDERING.index('month')
DAY_ID = FIELD_ORDERING.index('day')


[docs]class UswdsDateWidget(MultiWidget): ''' A :class:`django.forms.MultiWidget` for a USWDS-style date, with separate number fields for date, month, and year. This widget is used automatically by :class:`uswds_forms.UswdsDateField`, so you probably won't need to use it directly. However, it can be subclassed in case you need to customize it. ''' #: This is the default template used by the widget, which can #: be overridden if needed. template_name = 'uswds_forms/date.html' year_attrs = { 'pattern': r'[0-9]{4}', 'min': '1900', 'max': '9999', } month_attrs = { 'pattern': r'0?[1-9]|1[012]', 'min': '1', 'max': '12', } day_attrs = { 'pattern': r'0?[1-9]|1[0-9]|2[0-9]|3[01]', 'min': '1', 'max': '31', } def __init__(self, attrs=None): widgets = DateTuple( year=NumberInput(attrs=self.year_attrs), month=NumberInput(attrs=self.month_attrs), day=NumberInput(attrs=self.day_attrs), ) super().__init__(widgets, attrs=attrs) def decompress(self, value): if value: return list(DateTuple( year=value.year, month=value.month, day=value.day )) return [None, None, None] @staticmethod def get_field_names(name): # Note that this is actually dependent on the way our superclass # names our subwidgets. The naming scheme should be pretty stable, # though. return DateTuple( year=name + '_{}'.format(YEAR_ID), month=name + '_{}'.format(MONTH_ID), day=name + '_{}'.format(DAY_ID) )
[docs] def get_context(self, name, value, attrs): ''' Returns the context for the widget's template. This returns a superset of what's provided by its superclass' :meth:`~django.forms.MultiWidget.get_context` implementation, adding the following keys to ``widget``: * ``'hint_id'``: The unique id of some hint text showing users an example of what they can enter. * ``'subwidgets'``: This has the same iterable value described in the superclass documentation, but it has been enhanced such that its ``year``, ``month``, and ``day`` properties are aliases to its entries. Using these aliases can potentially make templates more readable. ''' ctx = super().get_context(name, value, attrs) widget = ctx['widget'] hint_id = '%s_%s' % (widget['attrs']['id'], 'hint') for subwidget in widget['subwidgets']: subwidget['attrs'].update({ 'class': 'usa-input-inline', 'aria-describedby': hint_id, }) widget['subwidgets'] = DateTuple(*widget['subwidgets']) widget.update({'hint_id': hint_id}) return ctx
[docs]class UswdsDateField(MultiValueField): ''' A :class:`django.forms.MultiValueField` for a USWDS-style date. Its value normalizes to a Python :class:`datetime.date` object. For an example of how this looks in practice, see the `USWDS date input example <https://standards.usa.gov/components/form-controls/#date-input>`_. ''' widget = UswdsDateWidget def __init__(self, *args, **kwargs): fields = DateTuple( year=IntegerField(), month=IntegerField(), day=IntegerField(), ) super().__init__(fields, *args, **kwargs) def compress(self, data_list): if data_list: fields = DateTuple(*data_list) try: return date( year=fields.year, month=fields.month, day=fields.day ) except ValueError as e: raise ValidationError('Invalid date: %s.' % str(e)) return None