Login

Month / Year dropdown widget

Author:
gregb
Posted:
August 16, 2009
Language:
Python
Version:
1.1
Tags:
date year credit-card month
Score:
1 (after 1 ratings)

This is an adaption of

django.forms.extras.widgets.SelectDateWidget

which has no day dropdown - it still produces a date but with the day set to 1.

Example use

class myForm(forms.Form):
    # ...
    date = forms.DateField(
        required=False,
        widget=MonthYearWidget(years=xrange(2004,2010))
    )
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import datetime
import re

from django.forms.widgets import Widget, Select
from django.utils.dates import MONTHS
from django.utils.safestring import mark_safe

__all__ = ('MonthYearWidget',)

RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')

class MonthYearWidget(Widget):
    """
    A Widget that splits date input into two <select> boxes for month and year,
    with 'day' defaulting to the first of the month.
    
    Based on SelectDateWidget, in 
    
    django/trunk/django/forms/extras/widgets.py
    
    
    """
    none_value = (0, '---')
    month_field = '%s_month'
    year_field = '%s_year'

    def __init__(self, attrs=None, years=None, required=True):
        # years is an optional list/tuple of years to use in the "year" select box.
        self.attrs = attrs or {}
        self.required = required
        if years:
            self.years = years
        else:
            this_year = datetime.date.today().year
            self.years = range(this_year, this_year+10)

    def render(self, name, value, attrs=None):
        try:
            year_val, month_val = value.year, value.month
        except AttributeError:
            year_val = month_val = None
            if isinstance(value, basestring):
                match = RE_DATE.match(value)
                if match:
                    year_val, month_val, day_val = [int(v) for v in match.groups()]

        output = []

        if 'id' in self.attrs:
            id_ = self.attrs['id']
        else:
            id_ = 'id_%s' % name

        month_choices = MONTHS.items()
        if not (self.required and value):
            month_choices.append(self.none_value)
        month_choices.sort()
        local_attrs = self.build_attrs(id=self.month_field % id_)
        s = Select(choices=month_choices)
        select_html = s.render(self.month_field % name, month_val, local_attrs)
        output.append(select_html)

        year_choices = [(i, i) for i in self.years]
        if not (self.required and value):
            year_choices.insert(0, self.none_value)
        local_attrs['id'] = self.year_field % id_
        s = Select(choices=year_choices)
        select_html = s.render(self.year_field % name, year_val, local_attrs)
        output.append(select_html)

        return mark_safe(u'\n'.join(output))

    def id_for_label(self, id_):
        return '%s_month' % id_
    id_for_label = classmethod(id_for_label)

    def value_from_datadict(self, data, files, name):
        y = data.get(self.year_field % name)
        m = data.get(self.month_field % name)
        if y == m == "0":
            return None
        if y and m:
            return '%s-%s-%s' % (y, m, 1)
        return data.get(name, None)

More like this

  1. SelectDateWidget with format: day, month, year by zenx 5 years, 3 months ago
  2. SplitSelectDateTimeWidget by bradmontgomery 6 years, 4 months ago
  3. Fuzzy Date Diff Template Filter by zain 6 years, 1 month ago
  4. Calendar table by fauxparse 6 years, 11 months ago
  5. Bootstrap button dropdown widget (replaces forms.Select) by benjaoming 2 years, 8 months ago

Comments

leosh (on February 16, 2011):

There's a bug in this snippet. Line 56 should read:

    if not (self.required and month_val):

And line 65 should read:

    if not (self.required and year_val):

Otherwise you get strange behavior when only one of them is filled in.

#

danny_adair (on February 19, 2013):

The second last line

    return '%s-%s-%s' % (y, m, 1)

will only work if you didn't customize your DATE_FORMAT/DATE_INPUT_FORMATS (the former is usually the first item of the latter, i.e. the "preferred" input format). In order for this to always work, add

    from django.conf import settings

at the top, and replace that second last line with:

    return datetime.date(int(y), int(m), 1).strftime(
        settings.DATE_INPUT_FORMATS[0]
    )

P.S.: django.utils.formats.localize() is for rendering output, not input, and expects a setting in the format of DATE_FORMAT, i.e. without percentage signs... That's why I'm using time.strptime()

P.P.S.: Theoretically, the int() conversion could be try'd but the widget doesn't allow free text input so I find it ok to leave it out

#

danny_adair (on February 19, 2013):

"That's why I'm using time.strptime()" - uhm, make that "date.strftime()" :-)

#

Please login first before commenting.