Hi,
I've come across some interesting behaviour if you intentionally supply bad
data to a model formset whose forms use CheckboxInputs. Basically, it's
possible to have custom validation code on the form class which ensures the
boolean field's input is False, have this validation pass, but then the
instance silently fails to save to the database when you call formset.save()
when an instance's existing value is True.
The way to do this is to provide a '0' as input on the form submit, rather than
omitting the field entirely, as a proper checkbox widget would do.
This is more easily explained with code:
class M(models.Model):
flag = models.BooleanField()
class DefaultModelForm(forms.ModelForm):
# This form will get an auto-generated BooleanField, which uses
# the CheckboxInput widget.
class Meta:
model = M
def clean(self):
# Now, because BooleanField.to_python knows about '0',
# cleaned_data actually contains False. '0' and '1' can
# be posted by RadioSelect widgets.
cleaned_data = super(DefaultModelForm, self).clean()
if cleaned_data.get('flag', None) != False:
raise ValidationError, u'Flag must be false!'
return cleaned_data
class SimpleTest(TestCase):
def setUp(self):
# Let's set up a signal handler so we can tell whether the model was
saved.
self.save_count = collections.defaultdict(int)
def saved(sender, **kwargs):
self.save_count['count'] += 1
signals.post_save.connect(saved, sender=M, dispatch_uid='M')
self.m = M.objects.create(flag=True)
# Prove our plumbing works
self.assertEqual(1, self.save_count['count'])
def test_checkbox(self):
formset_class = forms.models.modelformset_factory(
M,
max_num=1,
form=DefaultModelForm
)
# Note that 'form-0-flag' being 0 is what a radio button
# would normally provide.
formset = formset_class({
'form-INITIAL_FORMS': '1',
'form-TOTAL_FORMS': '1',
'form-0-id': str(self.m.pk),
'form-0-flag': '0',
}, queryset=M.objects.all())
# Uh-oh - this now validates as True, because the BooleanField
successfully
# parsed '0' as False, but the CheckboxWidget doesn't know what to do
with
# '0'.
formset.is_valid()
# Since all the data looks OK, we go ahead and try to save. To compound
# the problem, the CheckboxWidget sees that the initial value from the
# model was True, and interprets '0' as bool('0') == True, so doesn't
# think that the data has changed. The model therefore never actually
# gets saved...
formset.save()
# If it saved properly, our save count should have gone up by one.
Sadly,
# it didn't, so this test fails.
self.assertEqual(2, self.save_count['count'])
# So - despite is_valid() assuring us that all our data was OK, saving
the
# model silently failed.
I've tried this with Django 1.2.3 and trunk r14735.
I'm not quite sure what the bug is here, if there is one; but it seem wrong
that you can write validation code that explicitly checks cleaned_data for a
boolean to be False, have that validation pass, and then for the underlying
model to not be saved when the formset save() is called.
(For the curious - we discovered this while writing tests for a Piston API that
uses formsets to validate data, and accidentally passed a '0' instead of
omitting the field in the test.)
Should I raise a bug for this one?
Cheers,
Dan
--
Dan Fairs | [email protected] | www.fezconsulting.com
--
You received this message because you are subscribed to the Google Groups
"Django developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/django-developers?hl=en.