Search code examples
pythondjangomany-to-many

How to repeat a many-to-many relationship in Django?


I have these models in my Django app:

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()


class Animal(models.Model):
    name = models.CharField(max_length=100, unique=True)

class AnimalList(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    list = models.ManyToManyField(Amimal)

I want to add the same pokemon to the same list twice:

>>> animal = Animal.objects.create(name='milla')
>>> user = User.objects.create(username='user')
>>> list = AnimalList.objects.create(user=user)
>>> list.list.add(animal)
>>> list.list.add(animal)
>>> animal.save()

The animal is added only once, though:

>>> list.list.all()
<QuerySet [<Animal: Animal object (3)>]>

I expected that, the documentation is explicit that

Adding a second time is OK, it will not duplicate the relation:

Yet, I do want to repeat the animals.

How could I do that? Is it possible by using ManyToManyField?


Solution

  • I could not repeat these relationships because Django applies a UniqueConstrant to many-to-many relationships. My solution was to add another table that referenced the animals:

    class AnimalListItem(models.Model):
        animal = models.ForeignKey(Animal, on_delete=models.CASCADE)
    
    class AnimalList(models.Model):
        user = models.ForeignKey(User, on_delete=models.CASCADE)
        list_items1 = models.ManyToManyField(AnimalListItem)
    

    After that, every time I wanted to add an animal to a list, I had to create a list item first, pointing to the animal, then add this list item to the list column.

    There are other possible solutions as well. For example, the through argument from ManyToManyField disables any default constraints that would be added to the relationship table. Or you could set db_constraint to False without through.

    However, those were not solutions to me because, as the documentation states:

    If the custom through table defined by the intermediate model does not enforce uniqueness on the (model1, model2) pair, allowing multiple values, the remove() call will remove all intermediate model instances

    I needed to remove only one instance at a time, so removing all of them would not be feasible.