Django Formsets manage complex repeating form fields in a view. Using the forms, you can find out how many forms were originally there, which ones have been changed, and which ones need to be deleted.
Like forms and form models, Django provides form model sets that simplify the task of creating a form set for a form that handles multiple model instances.
Django also provides built-in formsets that can be used to process a set of objects that belong to a shared foreign key.
In the model examples below, we can write an inline-formset to handle all children, for the parent, or for all child addresses.
# models.py class Parent(models.Model): name = models.CharField(max_length=255) class Child(models.Model): parent = models.ForeignKey(Parent) name = models.CharField(max_length=255) class Address(models.Model): child = models.ForeignKey(Child) country = models.CharField(max_length=255) state = models.CharField(max_length=255) address = models.CharField(max_length=255)
# forms.py from django.forms.models import inlineformset_factory ChildrenFormset = inlineformset_factory(models.Parent, models.Child, extra=1) AddressFormset = inlineformset_factory(models.Child, models.Address, extra=1)
Using the formsets described above, you can process all child elements for a parent on one page, and process all child element addresses on another page. If you want to allow users to add or edit all child elements along with addresses on the same page, then you must have a complete set of address forms for each child form in the child form set.
In this case, it makes sense to use nested forms. A nested formset is just a regular inline-formset.
To process nested forms, you need to do the following steps:
Step 1: Create a basic inline-formset
# forms.py from django.forms.models import BaseInlineFormSet class BaseChildrenFormset(BaseInlineFormSet): pass ChildrenFormset = inlineformset_factory(models.Parent, models.Child, formset=BaseChildrenFormset, extra=1)
Step 2. Attaching a nested form set for each form as shown below. The BaseInlineFormSet superclass defines the add_fields method, which is responsible for adding fields for each form in the formset. Also, here we can write the logic to link the nested formset.
# forms.py class BaseChildrenFormset(BaseInlineFormSet): def add_fields(self, form, index): super(BaseChildrenFormset, self).add_fields(form, index) # save the formset in the 'nested' property form.nested = AddressFormset( instance=form.instance, data=form.data if form.is_bound else None, files=form.files if form.is_bound else None, prefix='address-%s-%s' % ( form.prefix, AddressFormset.get_default_prefix()), extra=1)
Note: Here we have created a new property called "form.nested" which contains the nested formset (AddressFormset).
Step 3: Handling the formset and nested formsets in views
# views.py def manage_children(request, parent_id): """Edit children and their addresses for a single parent.""" parent = get_object_or_404(models.Parent, id=parent_id) if request.method == 'POST': formset = forms.ChildrenFormset(request.POST, instance=parent) if formset.is_valid(): formset.save() return redirect('parent_view', parent_id=parent.id) else: formset = forms.ChildrenFormset(instance=parent) return render(request, 'manage_children.html', { 'parent':parent, 'children_formset':formset})
Step 4: Rendering the nested formset in the template
# manage_children.html (Just formset display part) {{ children_formset.management_form }} {{ children_formset.non_form_errors }} {% for child_form in children_formset.forms %} {{ child_form }} {% if child_form.nested %} {{ child_form.nested.management_form }} {{ child_form.nested.non_form_errors }} {% for nested_form in child_form.nested.forms %} {{ nested_form }} {% endfor %} {% endif %} {% endfor %}
There are several points to pay attention to:
Verification. When validating a form in a formset, we also need to validate its subforms, which are in a nested formset.
Saving data. When you save a form, you must also save additions and changes to the forms in the nested form set.
When the page is submitted, we call formset.is_valid() to validate the forms. We override is_valid in our formset to add validation for nested formsets as well.
# forms.py class BaseChildrenFormset(BaseInlineFormSet): ... def is_valid(self): result = super(BaseChildrenFormset, self).is_valid() if self.is_bound: for form in self.forms: if hasattr(form, 'nested'): result = result and form.nested.is_valid() return result
This completes the validation of forms and nested forms. Now we need to process the saved. To do this, you must override the save method to save the parent formset and all nested formsets.
# forms.py class BaseChildrenFormset(BaseInlineFormSet): ... def save(self, commit=True): result = super(BaseChildrenFormset, self).save(commit=commit) for form in self.forms: if hasattr(form, 'nested'): if not self._should_delete_form(form): form.nested.save(commit=commit) return result
The save method is responsible for saving the forms in the formset, as well as all the forms in the nested formset for each form.
You can learn more about the Django CRM (Customer Relationship Management) open source package at
this link
.
Спасибо за полезную статью. Подскажите пожалуйста, что делать если нужно реализовать большее количество вложенных форм?
Например если на модель Address ссылается fk другой модели, на которую в свою очередь ссылается еще одна модель.
И нужно создавать все объекты с одной страницы.
В этом примере и так два уровня вложенности, всё остальное делается подобным способом.