djangomany-to-manyrelationshipunique-constraintself-join

ManyToManyField self symetrical unique together relation


I have some trouble trying to fetch some manytomanyrelated data in by Django Application

Here is my model :

SoftwareVersion(models.Model):
    id = models.AutoField(
        primary_key=True,
        db_index=True
    )

    ... Some other fields ...

    incompatibilities= models.ManyToManyField(
        "self",
        symmetrical=True,
        blank=True,
        default=None,
        through="Incompatibilities"
    )

Incompatibilities(models.Model):
    id = models.AutoField(
        primary_key=True,
        db_index=True
    )

    softwareversion_a = models.ForeignKey(
        "SoftwareVersion",
        models.CASCADE,
        db_index=True,
        db_column='softwareversion_a ',
        related_name='softwareversion_a',
        verbose_name="software version a",
    )

    softwareversion_b = models.ForeignKey(
        "SoftwareVersion",
        models.CASCADE,
        db_index=True,
        db_column='softwareversion_b',
        related_name='softwareversion_b',
        verbose_name="softwareversion_b",
    )

    status = models.BooleanField(
        verbose_name='Status',
        default=False
    )

    class Meta:
        unique_together = (('softwareversion_a', 'softwareversion_b'),)

To this I add in the SoftwareVersion's save method a logic to create related Incompatibilities for each new software version. I have tried several way to do it (with a loop or with a bulk_create) here is the bulk create function i use :

# Inside SoftwareVersion Model class
def save(self, force_insert=False, force_update=False, using=None, update_fields=None) -> None:
    save = super().save(force_insert, force_update, using, update_fields)
    
    Incompatibilities.objects.bulk_create(
       (Incompatibilities(
       softwareversion_a=self,
       softwareversion_b=software_version,
       status=False
            ) for software_version in SoftwareVersion.objects.exclude(self)),
            ignore_conflicts=True,
            batch_size=1000
        )

    return save

First problem I have is this method ignore the unique_together constraint, it creates duplicates. Before i was using a loop on each SoftwareVersion to create an object but it was too long so i wanted to use bulk_create but it seems to not work as i intended. Is there another optimized way to do it or a parameter to pass so that the unique together constraints is respected.

Secondly, what is the most optimized way to query "Give all the incompatibilities for this software version". Currently i am doing this :

Incompatibilities.objects.filter(softwareversion_a=?)

But i actually miss the incompatibilities which reference the software version in the field "softwareversion_b". I have tried to use the logic :

VersionLogiciel.objects.get(pk=?).incompatibilities.all()

But django output the same SQL request and so, the same result.

Incompatibilities.objects.filter(softwareversion_a=?)

Is there a parameter to put somewhere so that two field (here 'softwareversion_a', 'softwareversion_b') can be interchangeable. Or maybe i have to query in a particular way to access to the incompatibilities objects. I have tried querying :

Incompatibilities.objects.filter(Q(softwareversion_a=?) or Q(softwareversion_b=?))

But since i have to order result on softwareversion_a related fields and softwareversion_b related field i can't get it working like i want.

PS : Sorry for my bad english and notice that i translated the model to english so i may i have done some mistakes but on my side the model compilation and migrations is working as intended.

Thanks in advance for any help.


Solution

  • I end up opting for the second solution here : https://charlesleifer.com/blog/self-referencing-many-many-through/

    Instead of adding methods to my django model i deside to create postgres Trigger with django-pgtrigger. My trigger permit to replicate update, insert and delete. To avoid recursion i add a "if exist" verification to avoid updating already up to date data (same for insert and delete).