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)
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.