# 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]