ManyToManyField.add() doesn't respect a unique constraint in intermediate table
I added the following Company and Member models, and CompanyMember as their intermediate table with a unique constraint including role field in addition to through_fields.
from django.db import models
class Company(models.Model):
pass
class Member(models.Model):
companies = models.ManyToManyField(Company, through='CompanyMember', through_fields=('company', 'member'), related_name='members')
class CompanyMember:
company = models.ForeignKey(Company, on_delete=models.CASCADE)
member = models.ForeignKey(Member, on_delete=models.CASCADE)
role = models.SmallIntegerField()
class Meta:
constraints = [
models.UniqueConstraint(fields=['company', 'member', 'role'], name='company_member_role'),
]
In this situation, company.members.add() silently fails to add existing member with different role specified via through_defaults.
company = Company.objects.create()
member = Member.objects.create()
company.members.add(member, through_defaults={'role': 1})
assert company.members.through.objects.all().count() == 1
company.members.add(member, through_defaults={'role': 2})
assert company.members.through.objects.all().count() == 2 # fails
We need to workaround by adding the relation to the intermediate table directly.
company.members.through.objects.create(company=company, member=member, role=2)
Change History
(7)
| Description: |
modified (diff)
|
| Description: |
modified (diff)
|
| Description: |
modified (diff)
|
| Description: |
modified (diff)
|
| Description: |
modified (diff)
|
| Resolution: |
→ invalid
|
| Status: |
new → closed
|
I'll let others chime in but I think this is invalid.
ManyToManyFieldwas designed to allow only a single instance of the relationship it defines and not allow extra dimensions to be considered. In your case that means a single instance of theMember <-> Companymany-to-many relationship can be tracked at a time and theroledimension is not taken into account at all.If you want to keep using
ManyToManyFieldfor this purpose you'll likely need to tweak your data model a bite.g.