I have a modelformset_factory of a Model that has many-many relations. The form is used in a wizard that is used to create a new object, and each subsequent form in the wizard handles related objects to that wizard. (E.g., Supplier, SupplierDisciplines, SupplierRisks, SupplierDocuments, etc.).
When the page renders, despite the factory having extra=0, three forms are rendered. Is the modelformset_factory somehow grabbing the existing entities and displaying them, despite them having no relation to the new object?
I have disabled javascript and commented out any template that includes JS, so that can probably be ruled out.
The model:
class SupplierDiscipline(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False,
)
supplier = models.ForeignKey(
Supplier,
on_delete=models.CASCADE,
related_name='offered_services',
)
discipline = models.ForeignKey(
Discipline,
on_delete=models.CASCADE,
)
since = models.IntegerField(
validators=[MinValueValidator(1900),
MaxValueValidator(2099)],
)
approved = models.BooleanField(default=False)
The formset:
SupplierDisciplineFormSet = modelformset_factory(
SupplierDiscipline,
fields=['discipline', 'since', 'approved',],
can_delete=False,
extra=0,
max_num=Discipline.objects.all().count(),
)
The formset is used in a SessionWizard (apologies for all the print statements):
class SupplierWizardCreateView(LoginRequiredMixin, ProcurementCreateUpdateMixin, SessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, "tmp"))
form_list = FORMS
templates = TEMPLATES
def get_template_names(self):
return self.templates[self.steps.current]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = self.get_form()
# Assume formsets have a `forms` attribute
context['is_formset'] = hasattr(form, 'forms')
return context
def done(self, form_list, **kwargs):
supplier = form_list[0].save() # Save supplier to DB
# Iterate through forms without supplier name;
# add supplier's id (PK) to that form, then save the form with the relation.
formset_objects_count = 1
for i in range(1, len(form_list)):
instance = form_list[i]
if hasattr(instance, 'total_form_count'): # Regular form
print("FORMSET FOUND")
print("FORMSET HAS: " + str(len(instance)) + " FORM OBJECTS")
for form in instance: # Formset objects
print("FORMSET OBJECT COUNT" + str(formset_objects_count))
formset_objects_count += 1
if form.is_valid():
print("FORM IS VALID! FORM IS:")
#print(form)
instance = form.save(commit=False)
instance.supplier = supplier
instance.save()
else:
print("FORM IS INVALID! FORM IS: ")
#print(form)
print(form.errors)
else:
instance = instance.save(commit=False)
instance.supplier = supplier
instance.save()
messages.success(self.request, f'Supplier "{supplier.name}" created successfully!')
return redirect('home')
def post(self, *args, **kwargs):
print("IN POST()")
form = self.get_form(data=self.request.POST, files=self.request.FILES)
if form.is_valid():
print("FORM IS VALID")
return super().post(*args, **kwargs)
else:
print(form.errors) # Log form errors to console
return self.render(form) # Render the form with errors
The html template:
<form action="" method="post" enctype="multipart/form-data" id="form-container">
{% csrf_token %}
{{ wizard.management_form }}
{% if wizard.form.forms %}
{# If we're here, we're working with formsets #}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{# Formset objects #}
<div class="form-group formset-form" id="formset-form">
{{ form|crispy }}
</div>
<button id="add-form" type="btn btn-success">Add Another</button>
<button id="delete-form" type="btn btn-danger">Delete</button>
{% endfor %}
{% else %}
{# Regular forms; not formset objects #}
{{ wizard.form|crispy }}
{% endif %}
<div class="form-group">
{% if not wizard.steps.next %}
<input class="btn btn-outline-info" type="submit" value="{% translate "Submit" %}"/>
{% else %}
<input class="btn btn-outline-info" type="submit" value="{% translate "Next" %}"/>
{% endif %}
{% if wizard.steps.prev %}
<button name="wizard_goto_step" class="btn btn-outline-info" type="submit"
value="{{ wizard.steps.first }}">{% translate "First" %}</button>
<button name="wizard_goto_step" class="btn btn-outline-info" type="submit"
value="{{ wizard.steps.prev }}">{% translate "Previous" %}</button>
{% endif %}
</div>
</form>
I am at a loss and honestly have no idea where to even start looking. I’ve set extra to -1, and that works, but the HTML that’s rendered makes it so that the hidden name=“…-TOTAL_FORMS” value is non-existent and causes other potential issues with POST-ing.
Any advice on where to start? Am I just not using a modelformset_factory correctly?