Django.fun

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?

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!

Tutorials

Современный Python: начинаем проект с pyenv и poetry

Настройка проекта Python — виртуальные среды и управление пакетами

Использование requests в Python — тайм-ауты, повторы, хуки

Понимание декораторов в Python

ProcessPoolExecutor в Python: полное руководство

map() против submit() с ProcessPoolExecutor в Python

Понимание атрибутов, словарей и слотов в Python

Полное руководство по slice в Python

Выпуск Django 4.0

Безопасное развертывание приложения Django с помощью Gunicorn, Nginx и HTTPS

Автоматический повтор невыполненных задач Celery

Django REST Framework и Elasticsearch

Докеризация Django с помощью Postgres, Gunicorn и Nginx

Асинхронные задачи с Django и Celery

Релизы безопасности Django: 3.2.4, 3.1.12 и 2.2.24

Выпуски исправлений ошибок Django: 3.2.3, 3.1.11 и 2.2.23

Эффективное использование сериализаторов Django REST Framework

Выпуски безопасности Django: 3.2.2, 3.1.10 и 2.2.22

Выпущенные релизы безопасности Django: 3.2.1, 3.1.9 и 2.2.21

Обработка периодических задач в Django с помощью Celery и Docker

View all tutorials →