django-modelsdatabase-design

How to link 3 tables where 2 of them are many-to-many?


We have a problem in our schema design for our database that I would like to figure out the cleanest way to solve. The three models are defined in these simplified Django model classes:

class Animal(Model):
    name = CharField()
    infusion_rate = FloatField()
    infusate = ForeignKey(Infusate)  # but with specific tracer concentrations?

class Infusate(Model):
    name = CharField()
    tracers = ManyToManyForeignKey(Tracer)

class Tracer(Model):
    name = CharField()

Currently, we have a through model for the Infusate:Tracer M:M relationship that adds a concentration float field:

class InfusateTracer(Model):
    infusate = ForeignKey(Infusate)
    tracer = ForeignKey(Tracer)
    concentration = FloatField()

but we need to link the Animal model to a single infusate, but with specific concentrations for each of its multiple tracers. In other words, how do I relate an animal to a single infusate, but multiple tracers with specific concentrations belonging to that one infusate?

I was thinking that I could add a field to InfusateTracer and have Animal link to that instead. Let's call it animal, so:

class InfusateTracer(Model):
    infusate = ForeignKey(Infusate)
    tracer = ForeignKey(Tracer)
    concentration = FloatField()
    animal = IntegerField()

but how do I enforce that the relationship between infusate and animal fields in the InfusateTracer model is M:1 (i.e. InfusateTracer.animal never co-occurs with multiple different InfusateTracer.infusate values (but multiple InfusateTracer.tracer values))? Is there some sort of unique constraint I can use to enforce that?

From a different angle, I could create an AnimalTracer linking/through model, but then I would have the same difficulty of retrieving a single unique infusate name associated with the animal.

Trying to figure out the most elegant solution that maintains data integrity is melting my brain.


Solution

  • I was thinking about this again this morning and I had an alternate idea.

    To think of the problem another way, I want to be able to link an animal to an infusate (which defines a set of tracers without concentrations). That is one link. When I have an animal, I want to essentially add an annotation to the InfusateTracer table (but I cannot put it directly in that table). It is as if I want to add a concentration field to the InfusateTracer table, so to do it without changing that table, I just have to link to it... So that's what I should do instead of independently linking the animal table to the tracer table to define the concentrations, I should link the animal table to the InfusateTracer table using a linking table (because it is a many to many relationship). In other words, I am annotating the links between infusate and tracer. The relationships would look like this:

    class Animal(Model):
        name = CharField()
        infusion_rate = FloatField()
        infusate = ForeignKey(Infusate)
    
    class Infusate(Model):
        name = CharField()
        tracers = ManyToManyForeignKey(Tracer)
    
    class Tracer(Model):
        name = CharField()
    
    class InfusateTracer(Model):
        infusate = ForeignKey(Infusate)
        tracer = ForeignKey(Tracer)
    
    class AnimalInfusateTracer(Model):
        animal = ForeignKey(Animal)
        infusate_tracer = ForeignKey(InfusateTracer)
        concentration = FloatField()
    

    I think this is the answer because I don't have to worry about synchronizing two sets of multiple independent links from infusate to tracer and from animal to tracer (so that they each link to the same set).

    When I am loading data, all I have to worry about is looking for infusatetracer records That have the correct infusate and correct tracer!