How can I handle a variable amount of variations for a single product?

I’m making an ecommerce app that primarily sells audiobooks. These will always be available to stream (but not download) from the website. The catch here is there also need to be a dynamic amount of variations for each product.

For example, I have this Product model:

class Product(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="product_creator")
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
    sku = models.CharField(max_length=255, null=True, blank=True)
    title = models.CharField(max_length=255)
    author = models.CharField(max_length=255)
    description = models.TextField(blank=True)
    slug = models.SlugField(max_length=150)
    price = models.DecimalField(max_digits=4, decimal_places=2)
    audio_file = models.FileField(upload_to="audiobooks/")
    cover_image = models.ImageField(upload_to="covers/", blank=True, null=True)
    published_date = models.DateTimeField(auto_now_add=True)
    updated_date = models.DateTimeField(auto_now=True)
    is_active = models.BooleanField(default=True)

    def __str__(self):
        return self.title

And since I need to have the ability to create a new variation for the product, I can also have a Variation model like this:

class Variation(models.Model):
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
    name = models.CharField(max_length=255)
    slug = models.SlugField(max_length=255, unique=True)
    description = models.TextField(blank=True)
    price_increase = models.DecimalField(max_digits=4, decimal_places=2)
    is_downloadable = models.BooleanField(default=False)
    downloadable_file = models.FileField(upload_to="audiobooks/downloadable/", blank=True, null=True)
    variation_image = models.ImageField(upload_to="audiobooks/images/", blank=True, null=True)

    def __str__(self) -> str:
        return self.name

Let’s say I create some audiobook products with Product. I want all the products to have variations that include a downloadable .mp3 file, and a downloadable .m4b file. Some of them will have physical CDs, some of them may have accompanying comic books, and some may have unique collector items like an edition of the comic book with a gold cover, or a book with black pages and white text. I can create all these variations with Variation model, but I can’t figure out how to connect any number of these variations with a product efficiently. I was thinking a new table that holds a foreign key to the product and then somehow a variable number of FKs to the variations, but is that even possible, or practical at all?

The easiest way to handle this is to add a ForeignKey field to Variation that is referring to Product.

I did consider this, but the problem here is that eventually I want this all to be wrapped under a form that is more user-friendly to someone who may not be familiar with databases or the admin page. To that extent, I like the idea of being able to save these individual variations in memory so that they can be reused in the future. I made the Variation table with something a little more template-y in mind because I can imagine that most products are probably going to have a downloadable version of the audiobook, but I want that to be more optional than something built into the Product itself.

Maybe my design philosophy here is flawed, but I’m curious if there’s a way to implement this sort of variation template somehow.

Don’t plan on “saving” anything in memory only. You save data in the database. It’s more reusable there than anywhere else.

The idea behind having the foreign key in Variation is that it allows you to have any number of different / unique instances of Variation associated with any one Product.

Beyond that, I’m not sure I understand what you’re trying to achieve with this or why having a Variation model wouldn’t be appropriate here.

In the long run I want to have a GUI that is easier to use than the admin page by some staff member with 0 experience working with databases. Saving a variation template is more a feature I would like to implement in the future, but I see your point that the Variation model may be more appropriate here the more I think about it.

In this case, how would the DB query look? Would there have to be a second query for any variations referring to the selected product?

That’s always the objective. The Django admin is not intended to be your application UI.

In the simplest, most direct case, you would create an inline formset for all the instances of Variation assigned to a specific Product.