Josh
Josh

Reputation: 311

Django - ManyToMany where I could select the same foreign model multiple times

Apologies if this has been answered, I'm uncertain how to word it so couldn't find an existing solution.

Let's say I have a basic ManyToMany relationship as shown here:

class Topping(models.Model):
    name = models.Charfield()

class Pizza(models.Model):
    toppings = models.ManyToMany(Topping)

That's simple and great, however in the project I'm working on, I want to be able to select the same Topping more than once. For example, ["pepperoni", "mushroom", "onion"] would be a perfectly good result, but I need to allow something like ["pepperoni", "pepperoni", "pepperoni"]

I've tried using a intermediate class, where Pizza has a ManyToMany to Topping, which is just a foreignkey to ToppingType, which is the Charfield -

class ToppingType(models.Model):
    name = models.Charfield()

class Topping(models.Model):
    type = models.ForeignKey(ToppingType)

class Pizza(models.Model):
    toppings = models.ManyToMany(Topping)

and that works, however it means if one day I create something with five "pepperoni" selections, I then permanently have five copies of pepperoni as a Topping in my database.

As I mentioned at the top, I'm sure there's a fairly clean solution, but I've had trouble figuring out how to phrase my searches.

Upvotes: 1

Views: 144

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476594

You don't need a ManyToManyField to the Topping, you can let the Topping act as a junction table [wiki] and thus specify this as through=… model [Django-doc]:

class ToppingType(models.Model):
    name = models.Charfield(max_length=255, unique=True)

class Pizza(models.Model):
    toppings = models.ManyToMany(
        ToppingType,
        through='Topping'
    )

class Topping(models.Model):
    type = models.ForeignKey(ToppingType, on_delete=models.CASCADE)
    pizza = models.ForeignKey(Pizza, on_delete=models.CASCADE)

Here you thus create a Topping object per pizza per topping. But you do not duplicate the ToppingTypes. In fact if you do not specify a through=…, then Django creates a (hidden) model that acts as the junction model.

You thus can create for example as ToppingTypes pepperoni and onion:

pepperoni = ToppingType.objects.create(name='pepperoni')
onion = ToppingType.objects.create(name='onion')

and then for a pizza add pepperoni, onion, and pepperoni:

my_pizza = Pizza.objects.create()
Topping.objects.create(pizza=my_pizza, type=pepperoni)
Topping.objects.create(pizza=my_pizza, type=onion)
Topping.objects.create(pizza=my_pizza, type=pepperoni)

Upvotes: 1

Related Questions