Search code examples
pythondjangodjango-formsdjango-admin

How to store data that has been initialized by code in BaseInlineFormSet


I need help. I have a specific form which has the option to create a month and a specific value for that month. Here is the model:

class MonthAllocat(BaseModel):
    variable = models.ForeignKey(Variable, related_name="month_allocat", on_delete=models.CASCADE)
    month = models.PositiveSmallIntegerField(choices=MONTH_CHOICES)
    weight = models.DecimalField(max_digits=5, decimal_places=2, validators=PERCENTAGE_VALIDATOR)

    class Meta:
        db_table = "month_allocation"
        unique_together = ["month", "forecast"]
        ordering = ("month",)

variable - this is the main form on which the work takes place

At the time of creating a new form, I set certain values for each month: Here is what I have in admin.py ->

class MonthAllocationInline(admin.TabularInline):
    model = MonthAllocation
    formset = VariableMonthAllocationInlineFormSet
    fields = ("month", "weight")
    classes = ("collapse",)
    ordering = ("month",)
    max_num = len(MONTH_CHOICES)
    form = MonthAllocationForm

    class Media:
        css = {"all": ("css/hide_admin_object_name.css",)}

    def get_extra(self, request, obj=None, **kwargs):  # pragma: no cover
        if obj:
            return 0
        else:
            return len(MONTH_CHOICES)

->

class MonthAllocationForm(ModelForm):
    def __init__(self, *args, **kwargs):  # pragma: no cover
        super(MonthAllocationForm, self).__init__(*args, **kwargs)
        if kwargs.get("prefix").split("-")[1].isdigit() and not self.initial:  # type: ignore
            self.fields["month"].initial = MONTH_CHOICES[int(kwargs.get("prefix").split("-")[1])][  # type: ignore
                0
            ]
            self.fields["weight"].initial = round(Decimal(100 / len(MONTH_CHOICES)), 2)

    class Meta:
        model = MonthAllocation
        fields = ("month", "weight")

Previously, I checked that the sum is equal to 100

class VariableMonthAllocationInlineFormSet(FieldTargetSumFormSetMixin):
    SUM_BY_FIELD = "weight"
    TARGET_SUM = 100
class FieldTargetSumFormSetMixin(BaseInlineFormSet):
    SUM_BY_FIELD = "weight"
    TARGET_SUM = 100

    def clean(self):  # pragma: no cover
        super().clean()
        valid_forms = [form for form in self.forms if form not in self.deleted_forms and form.cleaned_data]
        if not valid_forms:
            return

        total_allocation = 0
        for form in valid_forms:
            total_allocation += form.cleaned_data.get(self.SUM_BY_FIELD, 0)
        if total_allocation != self.TARGET_SUM:
            raise ValidationError("Total allocation should be 100%")

As you can see here, I checked if the user entered the data, cleaned_data is not empty, which means we are doing the validation.

And it all worked great, it pre-set values, in order to make it easier for the user to set their own values. But it didn’t save the preset ones, since I didn’t need it at that moment.

Now I need to make it so that after the user enters in one month, for example, 30, it distributes 70 to the rest of the months, proportionally.

And here is where I have a problem. How to save preset values? Given that they may change. This is how the completed months visually look on the form when the main form is created: example

I tried to do like this:

class VariableMonthAllocationInlineFormSet(FieldTargetSumFormSetMixin):
    SUM_BY_FIELD = "weight"
    TARGET_SUM = 100

    def save(self, commit=False):
        instances = super(VariableMonthAllocationInlineFormSet, self).save(commit=False)

But here I only get the months that the user has entered. When trying to find forms where cleaned_data is empty: valid_forms = [form for form in self.forms if form not in self.deleted_forms and not form.cleaned_data] And for example, by looping through these forms like this:

        for forms1 in valid_forms:
            tt = forms1.save(commit=False)
            tt.weight = data
            tt.save_m2m()

I am not getting the id of the main form in tt Is it possible to somehow get the data of the built-in form at this moment:

    def save(self, commit=False):
        instances = super(VariableMonthAllocationInlineFormSet, self).save(commit=False)

Solution

  • Understood. In clean, I write the required values to instance, for example, those that have been initialized. Here is an example:

    forms_additional[idx_form].instance.*main_form* = self.instance(main form is stored here)
    forms_additional[idx_form].instance.month = value
    

    And in save, in order to write down the id of the main form, I do this:

    for forms1 in forms_additional:
        new_object_day = forms1.save(commit=False)
        if new_object_day.*main_form*_id is None:
            new_object_day.*main_form*_id = new_object_day.*main_form*.id
        new_object_day.save()
    

    I'm not completely sure that this is correct, but it works.