#36128: IntegrityError in admin Inline with many-to-many intermediary model
-------------------------------------+-------------------------------------
     Reporter:  Guillaume LEBRETON   |                     Type:
                                     |  Uncategorized
       Status:  new                  |                Component:
                                     |  Documentation
      Version:  5.1                  |                 Severity:  Normal
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
 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.
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36128>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/010701949307df8e-2b441fa4-0ef6-43ff-9668-c6b44ce0a701-000000%40eu-central-1.amazonses.com.

Reply via email to