Self-relating many-to-many field with explicit through link

Hi -
I have begun a task to map webs of “techniques.” The idea here is to map decomposition and reuse of techniques, and to do it with an explicit table for the link relation so as to add detail specific to that link.

class Technique(models.Model):
name = models.CharField(max_length=50, default="")
combines = models.ManyToManyField('Technique', through='CombineLink', through_fields=["combines","necessary_for"], related_name='necessary_for', blank=True)

class CombineLink(models.Model):
necessary_for = models.ForeignKey(Technique, related_name='is_reused_in', on_delete=models.CASCADE, null=True)
combines = models.ForeignKey(Technique, related_name='uses', on_delete=models.CASCADE, null=True)

My initial problem was that I didn’t have ‘through_fields’ set, but although I didn’t get an error, I was striking a problem where the reverse_accessor was returning a queryset of CombineLinks, not a queryset of Techniques. Adding the through_fields list seems to have resolved this.

But, the reverse accessor on CombineLink fields can be invoked from a technique, but returns a queryset of CombineLink objects. This is not exactly what I expected, but is useful.

waterwheel = Technique.objects.create(name="waterwheel")
flourmilling = Technique.objects.create(name="flourmilling")
waterwheel.necessary_for.all() # field name
<QuerySet [<Technique: flourmilling (c7a74122-12b8-47c5-beb2-740c4e571429)>]>

waterwheel.is_reused_in.all() # reverse_accessor
<QuerySet [<CombineLink: (810ecd64)-(waterwheel,flourmilling)>]>

I guess my question is ‘should I update my expectation? Or have I misconfigured this, e.g. by ommitting ‘symmetrical=False’ or something else?’

is_reused_in is the reverse accessor to CombineLink.

With a Technique, you can follow it forward through combines, and backwards through necessary_for.

So it’s instance_name.combines.all() and instance_name.necessary_for.all().

See the examples on the Many-to-many relationships page in the docs.

1 Like