Saving the same data to two models from a single formset

I’m having two models:

class BinsHistory(models.Model):
    ...
    pass

class Bins(models.Model):
    ...
    pass

I have a formset based on the Bins model. This formset is passed to the view. Now, I’m trying to save the data that is getting stored to bins, to the BinsHistory model. I know how to do this using model instances directly, but how should I do this when using BinFormset() data? Should I manually iterate over all formset instances creating a bunch of BinsHistory intances setting their values using BinFormset.cleaned_data()? I feel like I’m missing something obvious.

Your options depend, in part, to how closely those two models resemble each other and how close those formsets are to those models.

If the formsets are directly and exclusively related to those models, you could create two model formsets, one for each model, and bind the post data to each - effectively allowing you to process them in parallel.

Or, you could create an iterable from the first model and use the bulk_create method to insert them in the other.

However, it’s not possible to provide a precise answer in the absence of knowing the complete objects and any possible relationships or constraints that could affect their creation.

The BinsHistory is just abbreviated from Bins, since some fields aren’t useful/redundant. I’m partial to using a dummy formset for the BinsHistory model, but would like to know if there’s something I should be aware of considering the foreignkey relationships.

class BinsHistory(models.Model):
    document_type = models.IntegerField(
        choices=DocumentTypes.choices)
    document_status = models.IntegerField(
        choices=DocumentStatus.choices,
        default=DocumentStatus.EMPTY)
    serial_range = IntegerRangeField()
    series = models.PositiveIntegerField()
    created_at = models.DateTimeField(auto_now_add=True)
    bin_size = models.PositiveIntegerField()
    parent_transaction = models.ForeignKey(
        Transactions,
        on_delete=models.PROTECT)

class Bins(models.Model):
    document_type = models.IntegerField(
        choices=DocumentTypes.choices)
    document_status = models.IntegerField(
        choices=DocumentStatus.choices,
        default=DocumentStatus.EMPTY)
    created_at = models.DateTimeField(auto_now_add=True)
    series = models.PositiveIntegerField()
    serial_start = models.PositiveIntegerField()
    serial_end = models.PositiveIntegerField()
    bin_size = models.PositiveIntegerField()
    serial_range = IntegerRangeField()
    located_at = models.ForeignKey(
        Warehouse,
        on_delete=models.PROTECT)
    parent_transaction = models.ForeignKey(
        Transactions,
        on_delete=models.PROTECT)

With BinsHistory being a proper subset of Bins, I’d be leaning in that direction as well - provided the formset provides all required fields.

There isn’t anything that I can think of. My initial reaction is that this should be quite “clean”.

I’m testing the solution, but I’m worried that my mock data isn’t behaving as I expect. If I use forms.BinsFormset({'bins_set-TOTAL_FORMS': '1','bins_set-INITIAL_FORMS': '0','bins_set-0-document_type': 3, 'bins_set-0-series': 2000, 'bins_set-0-serial_range_0': 2000, 'bins_set-0-serial_range_1': 4000}, instance=tr.save(commit=False)), is_valid() returns true, but if I do the same with BinsHistoryFormset is_valid returns false. Is there a change of behavior that I’m not accounting for in my mock?

class BinsForm(forms.ModelForm):
    class Meta:
        model = Bins
        fields = ['document_type', 'series',
                  'serial_range']
BinsFormset = inlineformset_factory(
    Transactions, Bins, form=BinsForm,
    extra=6, can_delete=False, max_num=12)

class BinHistoryForm(forms.ModelForm):
    class Meta:
        model = BinsHistory
        fields = ['document_type', 'series',
                  'serial_range']

BinsHistoryFormset = inlineformset_factory(
    Transactions, BinsHistory, form=BinHistoryForm,
    extra=60, can_delete=False, max_num=60)

I’d have to check this out to be sure, but I think you need to use the same Form with both Formsets. My immediate guess is that binding the data isn’t working because the ID fields are different. While the form might be constructed from a different model, I would think that the specification of BinsHistory in the factory constructor would still specify the right model to use to save the data.

This is all guesswork and conjecture right now. Unfortunately, I’m not in a position at the moment to check this out for sure - but that’s the direction I’d be looking.

If I change my mocked data from bins_set to binshistory_set, they are correctly bound, but that would mean that I have to mangle the request.POST object data, something that I would like to avoid. That last one could be solved by changing the prefix of the formset, to force them both to have the same prefix for the data, which works in my mocked instances.

1 Like