pythondjangodjango-formsformset

Django Formset - each form with different initial value from M2M-through relationship


I have to models which are connected by a M2M-Field realized by another Class ComponentInModule, so that I can add there the extra information, how often a component is in the module.

class Module(models.Model):
   ...
   component = models.ManyToManyField(Component, through="ComponentInModule")

class Component(models.Model):
   ...

class ComponentInModule(models.Model):
    module = models.ForeignKey(InfrastructureModule, on_delete=models.CASCADE)
    component = models.ForeignKey(InfrastructureComponent, on_delete=models.CASCADE)
    amount = models.IntegerField(default=1)

Now I am trying to load a Module as a form with its corresponding Components as a formset.

class ComponentForm(ModelForm):
    amount = IntegerField()
module = InfrastructureModule.objects.get(id=x)
ComponentFormSet = modelformset_factory(Component, form=ComponentForm, extra=0)
component_formset = ComponentFormSet(queryset=module.get_components())

As you can see my ComponentForm has the extra field for the amount. The question now is, how can I pass the value of amount to the Formset on creation, so that all forms are initialized with the right value? With a single Form it's no problem, because I can just pass the value to the __init__ function of the form and put it into the amount field self.fields["amount"].initial = amount. I tried passing a list of values to the formset with form_kwargs, but then I got the problem, that in the __init__function I dont know which of the values in the list is the right one right now.

Is there any way to do this using formsets? Or is there some other option I am missing how you can include the extra fields from a M2M-relation in a ModelForm?


Solution

  • So I worked it out. I made a custom BaseModelFormSet class:

    class BaseCompFormset(BaseModelFormSet):
        def get_form_kwargs(self, index):
            kwargs = super().get_form_kwargs(index)
            amount = kwargs["amount"][index]
            return {"amount": amount}
    

    Adjusted the __init__ function of the form:

     def __init__(self, *args, **kwargs):
            amount = kwargs.pop("amount")
            super(ComponentForm, self).__init__(*args, **kwargs)
            if self.instance:
                self.fields["amount"].initial = amount
    
    

    And used those to create my modelformset_factory:

        amounts = [x.amount for x in module.get_components_in_module()]
        ComponentFormSet = modelformset_factory(Component, formset=BaseCompFormset, form=ComponentForm, extra=0)
        component_formset = ComponentFormSet(queryset=module.get_components(), form_kwargs={'amount':amounts})
    

    And now succesfully got the forms of the formset with the right initial value for amount!