Django: How to navigate multi-level reverse relationships?

156 Views Asked by At

I have several models that are related hierarchically:

  • Projects have one or more Experiments
  • Experiments have one or more Scans
  • Scans have one or more Scan Decisions

Simplified Models:

class Project(models.Model):
  id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
  name = models.CharField(max_length=255)

class Experiment(models.Model):
  id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
  name = models.CharField(max_length=255, blank=False)
  project = models.ForeignKey('Project', related_name='experiments', on_delete=models.CASCADE)

class Scan(models.Model):
  id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
  name = models.CharField(max_length=127, blank=False)
  experiment = models.ForeignKey('Experiment', related_name='scans', on_delete=models.CASCADE)

class ScanDecision(models.Model):
  id = models.UUIDField(primary_key=true, default=uuid4, editable=False)
  scan = models.ForeignKey('Scan', related_name='decisions', on_delete=models.CASCADE)

If I have a specific Scan Decision and I want to get the Project associated with that decision, how can I do so?

The Django documentation speaks of backward relationships but the examples seem limited to a single level of relationship.

For example, to get the ScanDecision associated with a Scan I could do something like:

sd = ScanDecision.objects.get(id=1)
sd.scan_set.all() # Returns all scan objects related to the specific ScanDecision

But what if I want to get the Project that is associated with the ScanDecision indirectly through Scan and Experiment?

e.g., something like this but without all the steps?

sd = ScanDecision.objects.get(id=1) # The desired ScanDecision object
s = sd.scan_set.all() # Gets the related Scan object
e = s.experiment_set.all() # Gets the Experiment related to the selected Scan object
p = e.project_set.all() # Gets the Project related to the selected Experiment object

Ideally I'd like something like:

sd = ScanDecision.objects.get(id=1)
p = sd.project_set.all()

Note: Each object only knows about it's immediately antecedent object, e.g. there is an ForeignKey relationship setup only between the child and parent objects, not any other levels. Thus ScanDecision has an FK to Scan but not to Experiment or Project. Similarly, Scan has an FK to Experiment but not to Project.

1

There are 1 best solutions below

0
preator On

Based on your update you can use forward relationship sd.scan.experiment.project. To answer the question though to access backward relationship you could do decisions = project.experiments.scans.decisions.all()