Django modelformset_factory removes model models marked for deletion

When using modelformset_factory, how do you delete objects from the database that are marked for deletion in the form?

I create a modelform_factory model as follows:

ItemFormset = modelformset_factory(Item, ItemModelForm, extra=1, can_delete=True) qset = Item.objects.filter(pr=pr) formset = ItemFormset(queryset=qset) 

When the form set returns to POST, I get this data:

 if request.method == "POST": formset = ItemFormset(request.POST,queryset=qset) if formset.is_valid(): marked_for_delete = formset.deleted_forms instances = formset.save(commit=False) for item in instances: item.pr = pr item.save() 

When the formset.deleted_forms returns, I can get all the objects marked for deletion using formset.deleted_forms , but I cannot figure out how to delete them. I tried to scroll through each of them and delete each separately, but I get an error: Item object can't be deleted because its id attribute is set to None.

In the template, I include {{form.id}} , so each object has an identifier that is passed back to POST.

After calling instances = formset.save(commit=False) I can call formset.deleted_objects , but this is just an empty list: []

Can someone see what I'm doing wrong so that objects are not deleted from the database?

+4
source share
2 answers

By including all the fields from the Item model in ItemModelForm, I was able to call formset.save() , and all models marked for deletion in the form are deleted, and any modified or added models are updated or saved. I include the 'pr' field (foreign key) as HiddenInput and initialize it by extending ItemModelForm as follows:

 class EnhancedItemForm(ItemModelForm): def __init__(self, *args, **kwargs): super(EnhancedItemForm, self).__init__(*args, **kwargs) self.fields['pr'].widget = forms.HiddenInput() self.fields['pr'].initial = pr ItemFormset = modelformset_factory(Item, EnhancedItemForm, extra=extra_forms, can_delete=True) formset = ItemFormset(queryset=qset) 

Then I was able to process the message as follows:

 if request.method=="POST": formset = ItemFormset(request.POST) if formset.is_valid(): # Save, delete, update ..everything you need in one command: instances = formset.save() for instance in instances: # Make sure the assigned pr hasn't changed if instance.pr != pr: instance.pr = pr instance.save() 

Since modelformset_factory accepts the ModelForm class and not an instance of modelform, I had to extend ItemModelForm in a view where I know what I want pr to be initialized to.

+2
source

What confuses you is that formset.save(commit=False) does not do what you think.

Although with the setting commit=False edited objects are not save() d, the deleted objects are confused.

Therefore, when you go through marked_for_delete after calling save(commit=False) , you get already deleted objects, so None for their identifiers.

Your own answer is better, more idiomatic Django, as it happens; in general, you just need to call formset.save() and use commit=True by default. The fact that the case commit=False relatively rare and not used is probably why no one fixed the (IMO, buggy) behavior of deleting objects.

(As an aside, I only observed this behavior in a transactional environment without transactions / AutoCommit, perhaps with commit=False and transactions enabled, you will get a more stable deletion behavior.)

PS - This behavior has been changed in Django 1.7 :

"If you call formet.save (commit = False), the objects will not be deleted automatically. You need to call delete () for each of the formset.deleted_objects to delete them."

+4
source

Source: https://habr.com/ru/post/1399865/


All Articles