# fields.py
from django import forms
from widgets import CheckedWidget
class CheckedField(forms.Field):
"""
Class which equips the given Field class with an additional checkbox.
>>> class TestForm1(forms.Form):
... url_field = CheckedField(forms.URLField)
...
>>> TestForm1().as_table()
u'
'
>>> form = TestForm1({})
>>> form.is_valid()
True
>>> form.cleaned_data['url_field'] == CheckedField.UNSELECTED_VALUE
True
>>> form = TestForm1({'url_field_0' : 'on'})
>>> form.is_valid()
False
>>> form.errors
{'url_field': [u'This field is required.']}
>>> form = TestForm1({'url_field_0' : 'on', 'url_field_1' : 'bogus_url'})
>>> form.is_valid()
False
>>> form.errors
{'url_field': [u'Enter a valid URL.']}
>>> form = TestForm1({'url_field_0' : 'on', 'url_field_1' : 'http://www.real.url.com/'})
>>> form.is_valid()
True
>>> form.cleaned_data['url_field']
u'http://www.real.url.com/'
>>>
"""
# the value to use if the checbox is not selected
UNSELECTED_VALUE = None
FORM_FIELD_ARGS = ('required', 'widget', 'label', 'initial', 'help_text', 'error_messages')
def __init__(self, target, *args, **kwargs):
"""
Target can either be a Field type or an instance of one.
In the former case, all the extra arguments are passed over to the target field's constructor
"""
if isinstance(target, type):
target = target(*args, **kwargs)
self.field = target
if 'widget' in kwargs:
widget = kwargs['widget']
else:
widget = self.field.widget
kwargs['widget'] = CheckedWidget(widget)
# some keyword arguments make sense only for the "target" Field subclass,
# not for forms.Field itself
field_kwargs = dict([(kw, kwargs[kw]) for kw in kwargs if kw in self.FORM_FIELD_ARGS])
super(CheckedField, self).__init__(*args, **field_kwargs)
def clean(self, value):
try:
cb_value, field_value = value
if cb_value:
# checkbox is selected, return the field's value
return self.field.clean(field_value)
else:
# checkbox is not selected, return the default value
return self.__class__.UNSELECTED_VALUE
except forms.util.ValidationError, ve:
# we don't want to swallow ValidationErrors from the "target" field
raise ve
except TypeError, ValueError:
raise forms.util.ValidationError, u'%s is not an iterable, or there is a length mismatch' % unicode(value)
# widgets.py
from django import forms
class CheckedWidget(forms.MultiWidget):
"""
A widget which pairs another widget with a CheckboxInput widget
The target widget is given as the first argument to the constructor,
and can be either an instance or a type
"""
def __init__(self, target, attrs=None):
if isinstance(target, type):
target = target(attrs)
widgets = (forms.CheckboxInput(attrs=attrs), target)
super(CheckedWidget, self).__init__(widgets, attrs)
def decompress(self, value):
"""
If the value is None, assume the CheckboxInput was not selected
"""
if value is None:
return [False, None]
return [True, value]