Search code examples
pythondjangodjango-modelsone-to-many

Create one object and bulk-create many related objects


Am new to Django (and Python) and have a problem initializing my model. For every school year created in the database, I want the corresponding days (not exactly 365/366, but > 300) to be created with a default value of 0 (a one-to-many relationship). I learned that I should not try to do this in a constructor, but after the constructor has run (and the object and its attributes have become accessible). So is this a case for using signals or should I override pre- or post-save(). And why? Or is there a third option which I have missed so far?

from django.db import models
import pandas as pd


class BaseModel(models.Model):
    objects = models.Manager()

    class Meta:
        abstract = True


class SchoolYear(BaseModel):
    start = models.DateField()
    end = models.DateField()

    def init_days(self):
        current_date = self.start
        delta = timedelta(days=1)
        while current_date <= self.end:
            self.days.add(schoolyear=self, date=current_date, value=0)
            current_date += delta


class Day(BaseModel):
    schoolyear = models.ForeignKey(SchoolYear, on_delete=models.CASCADE)
    date = models.DateField()
    value = models.IntegerField()

Solution

  • Signals tend to be considered an anti-pattern in all but a few cases. You can accomplish the above in a readable fashion by overriding the save function for your year model, and using bulk_create to create a large number of similar objects, giving you something like:

    class SchoolYear(BaseModel):
        start = models.DateField()
        end = models.DateField()
    
        def save(self):
            #see if this is a new year being created by checking for an ID
            being_created = self.pk is None
            #save it anyway (after this it does have an ID)
            super().save(*args, **kwargs)
            #test if this is a newly created year and create days if so
            if being_created:
                current_date = self.start
                delta = timedelta(days=1)
                days = []
                #create a list of days for creation
                while current_date <= self.end:
                    days.append(Day(schoolyear=self, date=current_date, value=0))
                    current_date += delta
                #use bulk_create to create the list of days efficiently.
                Day.objects.bulk_create(days)