Here is what I came up with. I had to override ModelForm as well to
get rid of validation errors in emptied forms. This solution works in
my use case where I only have CharFields and IntegerFields. It's not
as simple as I had wished. Can it be?
class DeleteIfEmptyModelForm(ModelForm):
"""
A ModelForm which marks itself valid and empty if all visible
fields are
blank or all-whitespace.
"""
def full_clean(self):
if not self.is_bound:
return
if not self.is_empty():
return super(DeleteIfEmptyModelForm, self).full_clean()
def is_empty(self):
self.cleaned_data = {}
if not hasattr(self, '_empty'):
self._empty = True
for boundfield in self:
field = self.fields[
boundfield.name]
value = field.widget.value_from_datadict(
self.data, self.files, self.add_prefix
(
boundfield.name))
if not boundfield.is_hidden and
boundfield.name !=
DELETION_FIELD_NAME and value is not None and str(value).strip():
self._empty = False
break
try:
clean_value = field.clean(value)
except ValidationError:
clean_value = ''
self.cleaned_data[
boundfield.name] = clean_value
return self._empty
class DeleteIfEmptyInlineFormSet(BaseInlineFormSet):
"""
Modified version of django.forms.models.BaseInlineFormSet for
allowing
deleting objects by emptying their visible fields instead of
checking the
delete checkbox.
Not overriding _get_deleted_forms() since it doesn't seem to be
used in my
use case.
"""
def save_existing_objects(self, commit=True):
"""Only one small change here, see comment below"""
self.changed_objects = []
self.deleted_objects = []
if not self.get_queryset():
return []
existing_objects = {}
for obj in self.get_queryset():
existing_objects[
obj.pk] = obj
saved_instances = []
for form in self.initial_forms:
obj = existing_objects[form.cleaned_data
[self._
pk_field.name]]
# the change is on this line:
if self.can_delete and (form.cleaned_data
[DELETION_FIELD_NAME] or form.is_empty()):
self.deleted_objects.append(obj)
obj.delete()
else:
if form.changed_data:
self.changed_objects.append((obj,
form.changed_data))
saved_instances.append(self.save_existing(form,
obj, commit=commit))
if not commit:
self.saved_forms.append(form)
return saved_instances
def add_fields(self, form, index):
"""Override delete field and make it hidden."""
super(DeleteIfEmptyInlineFormSet, self).add_fields(form,
index)
form.fields[DELETION_FIELD_NAME].widget = forms.HiddenInput()
# usage:
formset_class = forms.models.inlineformset_factory(
parent_model, model,
form=DeleteIfEmptyModelForm, formset=DeleteIfEmptyInlineFormSet)