LJ Archive

At the Forge

Model Relationships in Django

Reuven M. Lerner

Issue #255, July 2015

Django provides excellent support for relationships among database tables. Here's how you can define those relationships in your projects.

In my last few articles, I've looked at Django, a powerful Python-based Web framework. I discussed how Django projects are built out of individual, reusable applications. I also covered how Django's templates provide flexibility, while avoiding the use of explicit code within the template files themselves. Then I showed how you can use Django's ORM (object-relational manager) to define your models, which then were translated into table-creation queries in SQL. Finally, I explained how you can, through your models, access the database—creating, modifying and even deleting database records using Python objects.

So in this article, I'm going to finish this exploration of Django, looking at one final part of any application that uses a relational database: how you can define, and then use, associations among your Django models.

Relational databases have been around for several decades, and despite the hype surrounding NoSQL databases of various sorts, one of their great strengths is the ease with which you can combine information from different sources, mix and match them together, and then produce a result. Perhaps this isn't always true; in particular, relational databases often work poorly with hierarchical data. But the idea that you can read from several tables, combine them in a variety of ways, and get precisely the answer you were looking for, is not only powerful, but also addictive once you get used to it.

Thus, if you're using Django along with a relational database, you're likely going to want to use those relations with your Django models. However, it's not always obvious how you can do this. Here, I take a look at the three different types of relationships you can create among models (and thus SQL tables) and see how Django supports those relationships.

One-to-One Relationships

The first and easiest to understand relationship between models is the one-to-one relationship. This means each model has one and only one corresponding model in another class. (At the database level, it means every row has one and only one row in another table.)

For example, let's say I'm in charge of a bus company, in which each bus is assigned to a specific driver. For every driver, there is a bus. And, for every bus, there is a driver. The relationship in this case is one-to-one.

I created a small Django project (“buscompany”) to demonstrate this and an application (“schedule”) inside that project:

$ django-admin.py startproject buscompany
$ cd buscompany
$ python manage.py startapp schedule

In order for my Django project to recognize and work with my new application, I need to add it to the application's settings. This means going into the application's settings.py and adding the following line to the definition of INSTALLED_APPS:

'schedule'

Once an application is installed into a Django project, the project will recognize the application's model and other definitions.

Inside the “schedule” application, I then create two models: one for buses, and the other for drivers. Both of these are defined in schedule/models.py:

class Driver(models.Model):
first_name = models.TextField()
last_name = models.TextField()
birthdate = models.DateField()

def __str__(self):
    return "{} {}".format(self.first_name,
              self.last_name)


class Bus(models.Model):
registration_number = models.TextField()
license_plate = models.TextField()
purchased_on = models.DateField()
driver = models.OneToOneField(Driver)

def __str__(self):
    return "Vehicle license {}".format(self.license_plate)

As you can see, I have defined a Driver class, and a Bus class. In particular, note the “driver” attribute defined in Bus, and that it is defined to be an instance of models.OneToOneField. I indicate that this attribute should be linked to the Driver class by passing it as a parameter to models.OneToOneField.

Once I have created these basic model definitions, I can put them into practice by creating and then running the migrations:

$ python manage.py makemigrations
$ python manage.py migrate

Once the migrations have been run, the database will contain the models I want. But more important, an instance of Bus now can be linked to an instance of Driver. For example, next I create a driver using the Django management shell:

$ python manage.py shell
>>> from schedule.models import Bus, Driver

>>> d = Driver(birthdate='1980-01-01', first_name='Mr.',
>>> last_name='Driver')
>>> d.save()
>>> d.id       # Returns 1

Now I can use this new driver's ID when creating a new Bus object:

>>> b = Bus(driver_id=1, license_plate='abc123',
            purchased_on='2015-01-01', registration_number=123)
>>> b.save()

With the above in place, I can get the driver via the bus, and vice versa:

>>> str(b.driver)
'Mr. Driver'

>>> str(d.bus)
'Vehicle license abc123'

And of course, if I want to retrieve specific fields from an associated object, I can do that:

>>> d.bus.registration_number
u'123'

With this relationship in place, I now can produce a table of drivers and buses that the bus company's management can use to see the current allocation of drivers and buses.

I want to add two quick points to the above design. First and foremost, a one-to-one relationship is inherently double-sided. That is, both models effectively see a one-to-one relationship. You can, in Django, define the relationship on whichever object you wish. The results will be the same; the appropriate attributes will be created in both directions.

The second thing to notice, and remember, is that Django assumes, unless you explicitly say otherwise, that database columns are NOT NULL. This means if I try to create a bus without an associated driver:

b2 = Bus(license_plate='abc456',
     purchased_on='2015-02-02', registration_number=456)
b2.save()

Django will throw an IntegrityError exception, because I have not specified anything for the driver_id column. When creating your models, you will have to decide whether you want to have the added flexibility of NOT NULL columns—which, in this case, might indicate that a bus has not (yet) been allocated to a driver—or the added integrity of NOT NULL columns.

One-to-Many Relationships

The above works well when you have a simple, one-to-one relationship. However, life is a bit more complex than that. You often encounter situations where you need a one-to-many relationship. For example, let's assume my bus company wants to make the most of its buses. Thus, there isn't just one driver associated with a bus, but rather a number of possible drivers associated with a bus.

As things currently stand, that's not possible; a given bus can have only a single driver at a time. In order to change this, and to allow multiple drivers to drive the same bus (although not simultaneously, one would hope), I need to change the association. Instead of a one-to-one relationship between buses and drivers, there now will be a one-to-many relationship.

In SQL, you would accomplish this by having two separate tables: one for drivers and one for buses. Because one bus could have multiple drivers, I would put a “bus” foreign key in the drivers table. In this way, each driver would point to a single bus, but a bus could be affiliated with multiple drivers.

When one table references another in the database world, it's called using a “foreign key”. And so it shouldn't come as a surprise that in Django, I need to modify my models, removing the OneToOneField from Bus and adding a ForeignKey field in Driver. The models then will look like this:

from django.db import models

class Bus(models.Model):
registration_number = models.TextField()
license_plate = models.TextField()
purchased_on = models.DateField()

def __str__(self):
    return "Vehicle license {}".format(self.license_plate)

class Driver(models.Model):
first_name = models.TextField()
last_name = models.TextField()
birthdate = models.DateField()
bus = models.ForeignKey(Bus)

def __str__(self):
    return "{} {}".format(self.first_name,
              self.last_name)

Notice that I flipped the order of the models definitions, so that Bus would be recognized as a known class name to Python when I define the Driver class.

As always in Django, I then tell the system to create a migration:

$ python manage.py makemigrations

But in this particular case, because I already have added some records to my database, Django refuses to continue. It warns me that I'm asking to create a new field (“bus”) on the Driver model, but that because there already are records, and because the field is defined as “not null” (the default in Django), the migration cannot work. Django thus gives me the choice between providing a default for existing records or for quitting from the migrations and trying again later. I decided to choose a default and then entered 1, the ID of the bus in my system.

Once that was done, Django finished making the migrations. I ran them with:


$ python manage.py migrate

$ python manage.py shell

>>> from schedule.models import Bus, Driver
>>> d = Driver.objects.first()
>>> d.bus
<Bus: Vehicle license abc123>

So far, so good—my driver has a bus. Let's add another driver for the same bus:


>>> d2 = Driver(birthdate='1980-02-02', first_name='Ms.',
                last_name='AlsoDriver', bus_id=1)
>>> d2.save()
>>> d2.bus
<Bus: Vehicle license abc123>

Now I have two drivers who work with the same bus. Does the bus know this? Let's find out:


>>> b = Bus.objects.first()
>>> b.driver_set.all()
[<Driver: Mr. Driver>, <Driver: Ms. AlsoDriver>]

Sure enough, I find that when I retrieve the driver_set from the bus and then ask Django to provide me with the objects themselves, I get the two drivers.

Many-to-Many Relationships

The above is fine if each driver uses only a single bus. But it's more likely that each driver will use a number of different buses, and that each bus will have a number of different drivers. In such a case, this existing model will not allow me to describe the data; instead of a one-to-many relationship, I need a many-to-many relationship. This is a common thing in the data-modeling world; consider the relationship between authors and books, or ingredients and recipes, or classes in school, and you'll see that a many-to-many relationship is often the best (and only) way to model things correctly.

In a relational database, there's no way for two tables to have a many-to-many relationship directly. Rather, you have to create a “join table”, a third table in which the relations are listed. Django understands this and allows you to define a many-to-many relationship by creating a ManyToManyField on one of the models. Django then creates the join table for you. Let's modify my Driver model, so that they'll have this relationship:

class Driver(models.Model):
first_name = models.TextField()
last_name = models.TextField()
birthdate = models.DateField()
bus = models.ManyToManyField(Bus)

def __str__(self):
    return "{} {}".format(self.first_name,
              self.last_name)

Notice that I put the ManyToManyField only on one of the classes and not both of them. I then create and run my migrations:

$ python manage.py makemigrations
$ python manage.py migrate

Next, I re-enter the Django shell and re-configure the associations:

>>> d = Driver.objects.get(id=2)
>>> d.bus.add(1)
>>> d.save()
>>> d.bus.add(2)
>>> d.save()

>>> d.bus.all()
[<Bus: Vehicle license abc123>, 
 ↪<Bus: Vehicle license xyz987>]

After executing the above, I similarly added a second driver to each of the buses. Do I see the results of this on the other side? Let's find out:


>>> b = Bus.objects.get(id=1)
>>> b.driver_set.all()
[<Driver: Mr. Driver>, <Driver: Ms. AlsoDriver>]

Indeed, you can see that associating a bus and driver can be done from either side. The fact that a join table exists is transparent; Django creates and maintains that table for you, part of its general approach of helping you concentrate on the important aspects of your projects, rather than the nuts and bolts that happen behind the scenes.

This concludes my tour of many of Django's major features, which started a few months ago. If you're a Python developer, and if you are interested in creating complex Web applications, there's no doubt that Django is the way to go. If you're looking to do something simpler, I'd suggest Flask—but only you know how complex your system will be. There's no doubt that if you have an ambitious Web application that will use a database, Django is up to the task and is worth serious consideration.

Reuven M. Lerner is a Web developer, consultant and trainer. He recently completed his PhD in Learning Sciences from Northwestern University. You can read his blog, Twitter feed and newsletter at lerner.co.il. Reuven lives with his wife and three children in Modi'in, Israel.

LJ Archive