Opened 5 hours ago
Last modified 102 minutes ago
#36128 new Bug
Lack of constraint validation in admin Inline with many-to-many intermediary model leads to IntegrityError
Reported by: | Guillaume LEBRETON | Owned by: | |
---|---|---|---|
Component: | Documentation | Version: | 5.1 |
Severity: | Normal | Keywords: | |
Cc: | Guillaume LEBRETON | Triage Stage: | Accepted |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Following this doc section https://docs.djangoproject.com/en/5.1/ref/contrib/admin/#working-with-many-to-many-intermediary-models
I set an admin tabular inline with an intermediary model.
The provided example is working but does not make that much sense; can a person have a membership to the same group several times ? Probably not, and if you try to do this with a simple many to many inline, without an intermediary model, you will get a validation error on the admin interface, "Please correct the duplicate data for group.".
The solution for the intermediary model is then adding a unique constraint, to avoid duplicated membership. But then, instead of having a validation error, there is a server error "IntegrityError".
To make the example work with the unique constraint, i had to add a custom formset:
models.py
class Person(models.Model): name = models.CharField(max_length=128) class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through="Membership") class Membership(models.Model): person = models.ForeignKey(Person, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE) date_joined = models.DateField() invite_reason = models.CharField(max_length=64) class Meta: constraints = [ models.UniqueConstraint("person", "group", name="unique_person_group"), ]
admins.py
class MembersFormset(forms.models.BaseInlineFormSet): def clean(self): groups = [] for form in self.forms: if form.cleaned_data: groups.append((form.cleaned_data['group'], form.cleaned_data['person'])) duplicated_groups = [x for x in groups if groups.count(x) > 1] if duplicated_groups: raise ValidationError( 'Duplicated values: %(duplicates)s', params={'duplicates': ", ".join(group.__str__() for group in set(duplicated_groups))} ) class MembershipInline(admin.TabularInline): model = Membership extra = 1 formset = MembersFormset
The doc https://docs.djangoproject.com/en/5.1/ref/contrib/admin/#working-with-many-to-many-intermediary-models about inline with intermediary model is quite detailed, but completely lacks hints about UniqueConstraint, while I think most of the time intermediary m2m are designed with a UniqueConstraint.
Therefore, documentation should be updated to hint about the recommended steps to validate unique constraints in Inlines with intermediary m2m.
Change History (2)
comment:1 by , 103 minutes ago
Triage Stage: | Unreviewed → Accepted |
---|---|
Type: | Uncategorized → Bug |
comment:2 by , 102 minutes ago
Summary: | IntegrityError in admin Inline with many-to-many intermediary model → Lack of constraint validation in admin Inline with many-to-many intermediary model leads to IntegrityError |
---|
Thank you for the report
I'm inclined to think that it should be easier than this and that the admin should handle the constraint errors nicely
But if not, I agree some docs could be beneficial