delete in formset by clearing fields instead of checkbox

354 views
Skip to first unread message

akaihola

unread,
Mar 12, 2009, 6:34:25 PM3/12/09
to Django users
I have an inline formset with three CharFields in each form of a child
object. For usability reasons, I'd like deleting to happen when all
fields of a form are blank (or whitespace-only) instead of using a
delete checkbox.

I'm looking at save_existing_objects() in django/forms/models.py:
399-421 and feeling a bit desperate. I don't see any trivial way to
override the delete checkbox mechanism.

Is the most reasonable approach to subclass BaseInlineFormSet,
override save_existing_objects() and use that for the formset=
argument of inlineformset_factory()?

Alex Gaynor

unread,
Mar 12, 2009, 6:36:57 PM3/12/09
to django...@googlegroups.com
The method you propose of overiding save_existing_objects is how I'd do it.

Alex

--
"I disapprove of what you say, but I will defend to the death your right to say it." --Voltaire
"The people's good is the highest law."--Cicero

akaihola

unread,
Mar 13, 2009, 10:16:55 AM3/13/09
to Django users
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)

akaihola

unread,
Mar 17, 2009, 9:42:36 AM3/17/09
to Django users
Just in case anyone decides to use the code I posted earlier in this
thread, there's one bug: the str() call needs to be replaced with a
unicode() call to prevent failure with non-ASCII input.
Reply all
Reply to author
Forward
0 new messages