I’m using a date selection widget for a registration form in Django. For some reason if you submit the page with validation errors the first option element in the select is duplicated. This happens for every refresh, so if you’ve submitted the page 5 times, you’ll have the first element repeated 6 times. Even stranger is that the widget consists of 3 selects: day, month, year and this only happens with the month select.
Any input would be greatly appreciated.
Edit:
Link to the page: http://gfc.beta-site.co.za:8000/accounts/register/
Code:
import time
import datetime
import re
from django.forms.widgets import Widget, Select
from django.utils import datetime_safe
from django.utils.dates import MONTHS_AP as MONTHS
from django.utils.safestring import mark_safe
from django.utils.formats import get_format
from django.conf import settings
__all__ = ('SelectDateWidget',)
RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')
class SelectDateWidget(Widget):
"""
A Widget that splits date input into three <select> boxes.
This also serves as an example of a Widget that has more than one HTML
element and hence implements value_from_datadict.
"""
none_value = (0, '---')
month_field = '%s_month'
day_field = '%s_day'
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 - 10
self.years = list(range(1942, this_year))
self.years.reverse()
#self.years = range(this_year, this_year+10)
def render(self, name, value, attrs=None):
try:
year_val, month_val, day_val = value.year, value.month, value.day
except AttributeError:
year_val = month_val = day_val = None
if isinstance(value, basestring):
if settings.USE_L10N:
try:
input_format = get_format('DATE_INPUT_FORMATS')[0]
# Python 2.4 compatibility:
# v = datetime.datetime.strptime(value, input_format)
# would be clearer, but datetime.strptime was added in
# Python 2.5
v = datetime.datetime(*(time.strptime(value, input_format)[0:6]))
year_val, month_val, day_val = v.year, v.month, v.day
except ValueError:
pass
else:
match = RE_DATE.match(value)
if match:
year_val, month_val, day_val = [int(v) for v in match.groups()]
choices = [(i, i) for i in self.years]
year_html = self.create_select(name, self.year_field, value, year_val, choices, (0,'Year'), 'dob_year')
choices = MONTHS.items()
month_html = self.create_select(name, self.month_field, value, month_val, choices, (0,'Month'), 'dob_month')
choices = [(i, i) for i in range(1, 32)]
day_html = self.create_select(name, self.day_field, value, day_val, choices, (0,'Day'), 'dob_day')
format = get_format('DATE_FORMAT')
escaped = False
output = []
for char in format:
if escaped:
escaped = False
elif char == '\\':
escaped = True
elif char in 'Yy':
output.append(year_html)
elif char in 'bFMmNn':
output.append(month_html)
elif char in 'dj':
output.append(day_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)
d = data.get(self.day_field % name)
if y == m == d == "0":
return None
if y and m and d:
if settings.USE_L10N:
input_format = get_format('DATE_INPUT_FORMATS')[0]
try:
date_value = datetime.date(int(y), int(m), int(d))
except ValueError:
pass
else:
date_value = datetime_safe.new_date(date_value)
return date_value.strftime(input_format)
else:
return '%s-%s-%s' % (y, m, d)
return data.get(name, None)
def create_select(self, name, field, value, val, choices, none_value, class_name = None):
if 'id' in self.attrs:
id_ = self.attrs['id']
else:
id_ = 'id_%s' % name
if not (self.required and val):
choices.insert(0, none_value)
local_attrs = self.build_attrs(id=field % id_)
s = Select(choices=choices)
select_html = '<div class="'+ class_name +'">' + s.render(field % name, val, local_attrs) + "</div>"
return select_html
The problem was that the MONTHS_AP being imported is a dictionary that is mutable and thus passed by reference. The
choices.insert()in thecreate_select()function was thus adding a none_value to the MONTHS_AP dictionary every time it was called.I’ve solved the problem by creating a tuple that contains the month information and using that instead as tuples are immutable.
Thanks Issac for putting me on the right track and Matt for getting the question in a readable format. I’d also like to thank my first grade teacher… 😉