Fixing Circular Model References

Let’s paint the scene. We are creating models for some kind of building with meeting rooms. The details don’t really matter. This building could be a Library, a Corporate Building, a Classroom Building, Hotel… Doesn’t matter.

Let’s say you have models that look like the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from django.db import models

class Seat(models.Model):
    type = models.CharField(max_length=50)
    wheels = models.BooleanField()

class Room(models.Model):
    number = models.IntegerField()
    floor = models.OneToOneField(Floor, on_delete=models.CASCADE)
    seats = models.ManyToManyField(Seat, on_delete=models.CASCADE)

class Floor(models.Model):
    amenities = models.CharField(max_length=200)
    number = models.IntegerField()

There can be 1 or more Rooms. Each Room can have multiple Seats. But, each Room can only be on 1 Floor. And you set it up like the above in your models.py file.

You try to do:

1
$ python manage.py makemigrations

But, you get the following error instead:

1
NameError: name 'Floor' is not defined

The reason for this, is Python is an interpreted language. It interprets each line of the file as it moves through each line of the file.

So, this error says that the Python interpreter saw this line:

1
floor = models.OneToOneField(Floor, on_delete=models.CASCADE)

But, Python doesn’t know what class Floor is because the interpreter hasn’t seen that class yet. Python has no idea what to do.

So the natural thing to do, is to move the Floor class up above the Room class. Like this,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from django.db import models

class Seat(models.Model):
    type = models.CharField(max_length=50)
    wheels = models.BooleanField()

class Floor(models.Model):
    amenities = models.CharField(max_length=200)
    number = models.IntegerField()

class Room(models.Model):
    number = models.IntegerField()
    floor = models.OneToOneField(Floor, on_delete=models.CASCADE)
    seats = models.ManyToManyField(Seat, on_delete=models.CASCADE)

Now, it works!

Wait! You know your models.py file will get more and more chaotic as you go on.

What if your models now look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from django.db import models

class Seat(models.Model):
    type = models.CharField(max_length=50)
    wheels = models.BooleanField()
    belongs_to = models.OneToOneField(Room, on_delete=models.CASCADE)

class Floor(models.Model):
    amenities = models.CharField(max_length=200)
    number = models.IntegerField()

class Room(models.Model):
    number = models.IntegerField()
    floor = models.OneToOneField(Floor, on_delete=models.CASCADE)
    seats = models.ManyToManyField(Seat, on_delete=models.CASCADE)

Say, hypothetically, that you need to keep track which Seat goes in each room. You can sort of imagine that perhaps, if you have too many people in a room, and somebody takes a Seat out of another room so people can sit in the Room they are occupying you might want to keep track of the Room that each Seat belongs to…

This is a really weird example, I know. But, it’s just to show you how you can fix this if it every happens to you.

In this specific case, you can’t move Room model above the Seat model because then the Seat would be below Room and you need to reference Seat and Floor so you can reference them in the Room model.

How do you fix this?

Use "s (quotes) on the model that’s not being referenced.

So, it would look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from django.db import models

class Seat(models.Model):
    type = models.CharField(max_length=50)
    wheels = models.BooleanField()
    belongs_to = models.OneToOneField("Room", on_delete=models.CASCADE)

class Floor(models.Model):
    amenities = models.CharField(max_length=200)
    number = models.IntegerField()

class Room(models.Model):
    number = models.IntegerField()
    floor = models.OneToOneField(Floor, on_delete=models.CASCADE)
    seats = models.ManyToManyField(Seat, on_delete=models.CASCADE)

Now do another makemigrations

1
2
python manage.py makemigrations
python manage.py migrate

No more NameError!

You just learned how to fix circular references in models! Take some time to comment below what you just learned and how you might use it in your own code! :)

comments powered by Disqus