Set up relationships in Django models: One to one, one to many and many to many

Problem

You want to setup relationships between Django models.

Solution

To define a one to many relationship between Django models use the ForeignKey data type. To define a many to many relationship between Django models use the ManyToManyField data type. To define a one to one relationship between Django models use the OneToOneField data type.

How it works

Django models operate by default on relational database systems (RDBMS) and thus they also support relationships amongst one another. In the simplest terms, database relationships are used to associate records on the basis of a key or id, resulting in improved data maintenance, query performance and less duplicate data, among other things.

Django models support the same three relationships supported by relational database systems: One to many, many to many and one to one. In the following sections I'll describe how to create these relationships in Django models, as well as a basic introduction on the Django model API related to querying these types of relationships.

One to many relationships in Django models.

A one to many relationship implies that one record can have many other records associated with itself. For example, a Menu record can have many Item records associated with it and yet an Item belongs to a single Menu record. To define a one to many relationship in Django models you use the ForeignKey data type on the model that has the many records (e.g. on the Item model). Listing 1 illustrates a sample of a one to many Django relationship.

Listing 1 - One to many Django model relationship

from django.db import models

class Menu(models.Model):
    name = models.CharField(max_length=30)


class Item(models.Model):
    menu = models.ForeignKey(Menu)
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=100)

The first Django model in listing 1 is Menu and has the name field (e.g. Menu instances can be Breakfast, Lunch, Drinks,etc). Next, in listing 1 is the Item Django model which has a menu field, that itself has the models.ForeignKey(Menu) definition. The models.ForeignKey() definition creates the one to many relationship, where the first argument Menu indicates the relationship model.

In addition to the database level benefits of creating a one to many relationship (e.g. improved data maintenance), Django models also provide an API to simplify the access of data related to this kind of relationship. Listing 2 illustrates various queries examples associated with one to many relationships in Django models.

Listing 2 - One to many Django model query syntax

from coffeehouse.items.models import Menu, Item

breakfast_menu = Menu.objects.get(name='Breakfast')

# Fetch all Item records for the Menu
breakfast_menu.item_set.all()

# Get the total Item count for the Menu 
breakfast_menu.item_set.count()

# Fetch Item records that match a filter for the Menu
breakfast_menu.item_set.filter(name__startswith='Whole')

# Create an Item directly on the Menu
# NOTE: Django also supports the get_or_create() and update_or_create() operations
breakfast_menu.item_set.create(name='Bacon, Egg & Cheese Biscuit',description='A fresh buttermilk biscuit...')

# Create an Item separatly and then add it to the Menu
new_menu_item = Item(name='Grilled Cheese',description='Flat bread or whole wheat ...')
new_menu_item.save()
breakfast_menu.item_set.add(new_menu_item)

# Clear all the menu references of the Item elements (i.e. reset the Item elements menu field to null)
# NOTE: This requires the ForeignKey definition to have null=True (e.g. models.ForeignKey(Menu, null=True)) so the key is allowed to be turned null
breakfast_menu.item_set.clear()

# Clear a certain menu reference from an Item element (i.e. reset the Item elements menu field to null)
# NOTE: This requires the ForeignKey definition to have null=True (e.g. models.ForeignKey(Menu, null=True)) so the key is allowed to be turned null
item_to_remove = Item.objects.get(name='Grilled Cheese')
breakfast_menu.item_set.remove(item_to_remove)

# Delete the Menu element along with its associated Item elements 
# NOTE: This requires the ForeignKey definition to have blank=True and on_delete=models.CASCADE (e.g. models.ForeignKey(Menu, blank=True, on_delete=models.CASCADE))
breakfast_menu.delete()

# Get the Menu of a given Item 
Item.objects.get(name='Whole-Grain Oatmeal').menu

# Get the Menu id of a given Item 
Item.objects.get(name='Whole-Grain Oatmeal').menu.id

# Get Item elements that belong to the Menu with name 'Drinks'
Item.objects.filter(menu__name='Drinks')


Listing 2 starts with a standard Django query for a Menu element which is then used to access its Item elements. For every one to many relationship, Django creates a reference among the models with the <one_model>.<many_model>_set syntax. In listing 2 you can see the various examples that include: fetching all associated records, counting associated records, filtering associated records by a given rule and creating/adding/deleting associated records. Toward the end of listing 2 you can see that it's also possible to access Menu element information from a standard Item element, for which you simply use the ForeignKey field instead of the special _set syntax. In this case appending menu to an Item elements is sufficient to gain access to it. Also note it's possible to use the __ syntax (a.k.a. "follow notation") to access related model fields (e.g. Item.objects.filter(menu__name='Drinks') tells Django to access the Item menu field and then jump to the Menu object's name field).

Many to many relationships in Django models.

A many to many relationship implies that many records can have many other records associated amongst one another. For example, Store records can have many Amenity records, just as Amenity records can belong to many Store records. To define a many to many relationship in Django models you use the ManyToManyField data type. Listing 3 illustrates a sample of a many to many Django relationship.

Listing 3 - Many to many Django model relationship

from django.db import models

class Amenity(models.Model):
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=100)


class Store(models.Model):
    name = models.CharField(max_length=30)    
    address = models.CharField(max_length=30)
    city = models.CharField(max_length=30)
    state = models.CharField(max_length=2)
    email = models.EmailField()
    amenities = models.ManyToManyField(Amenity,blank=True)

The first Django model in listing 3 is Amenity and has the name and description fields. Next, in listing 3 is the Store Django model which has the amenities field, that itself has the models.ManyToManyField(Amenity,blank=True) definition. The models.ManyToManyField() definition creates the many to many relationship via a junction table, where the first argument Amenity indicates the relationship model and the optional blank=True argument allows a Store record to be created without the need of an amenities value.

In this case, the junction table created by Django is used to hold the relationships between the Amenity and Store records through their respective keys. Although you don't need to manipulate the junction table directly, for reference purposes Django uses the syntax <model_name>_<model_field_with_ManyToManyField> to name it (e.g. For Store model records stored in the stores_store table and Amenity model records stored in the stores_amenity table, the junction table is stores_store_amenities).

In addition to the database level benefits of creating a many to many relationship (e.g. improved data maintenance), Django models also provide an API to simplify the access of data related to this kind of relationship. Listing 4 illustrates various queries examples associated with many to many relationships in Django models.

Listing 4 - Many to many Django model query syntax

from coffeehouse.stores.models import Store, Amenity

wifi_amenity = Amenity.objects.get(name='WiFi')

# Fetch all Store records with Wifi Amenity
wifi_amenity.store_set.all()

# Get the total Store count for the Wifi Amenity
wifi_amenity.store_set.count()

# Fetch Store records that match a filter with the Wifi Amenity
wifi_amenity.store_set.filter(city__startswith='San Diego')

# Create a Store directly with the Wifi Amenity
# NOTE: Django also supports the get_or_create() and update_or_create() operations
wifi_amenity.store_set.create(name='Uptown',address='1240 University Ave'...)

# Create a Store separatly and then add the Wifi Amenity to it
new_store = Store(name='Midtown',address='784 W Washington St'...)
new_store.save()
wifi_amenity.store_set.add(new_store)

# Clear all the Wifi amenity records in the junction table for all Store elements
wifi_amenity.store_set.clear()

# Clear the Wifi amenity record from the junction table for a certain Store element
store_to_remove_amenity = Store.objects.get(name='Midtown')
wifi_amenity.store_set.remove(store_to_remove_amenity)

# Delete the Wifi amenity element along with its associated junction table records for Store elements
wifi_amenity.delete()

# Get the Amenity elements of a given Store
Store.objects.get(name='Downtown').amenities.all()

# Get all Store elements that have amenity id=3
Store.objects.filter(amenities__id=3)

# Same operations supported through "amenity.store_set." are supported the other way around through "store.amenities"

# Fetch store
midtown_store = Store.objects.get(name='Midtown')

# Create and add Amenity element to Store
midtown_store.amenities.create(name='Laptop Lock',description='Ask our baristas...')

# count, add, remove, etc, also supported 

In listing 4 you can see the various examples on many to many Django model query operations. Notice that similar to the one to many relationship examples in listing 2, with many to many Django models it's also possible to do queries parting from either model.

One to one relationships in Django models.

A one to one relationship implies that one record is associated with another record. If you're familiar with object-orientated programming, a one to one relationship in RDBMS is similar to object-oriented inheritance that uses the is a rule (e.g. a Car object is a Vehicle object). For example, generic Item records can have a one to one relationship to Drink records, where the latter records hold information specific to drinks (e.g. caffeine content) and the former records hold generic information about items (e.g. price). To define a one to one relationship in Django models you use the OneToOneField data type. Listing 5 illustrates a sample of a one to one Django relationship.

Listing 5 - One to one Django model relationship

from django.db import models

class Item(models.Model):
    menu = models.ForeignKey(Menu)
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=100)
    calories = models.IntegerField()
    price = models.FloatField()

class Drink(models.Model):
    item = models.OneToOneField(Item,on_delete=models.CASCADE,primary_key=True)
    caffeine = models.IntegerField()


The first Django model in listing 5 is Item which is similar to the one presented in listing 1, except the version in listing 5 has the additional calories and price fields. Next, in listing 5 is the Drink Django model which has the item field, that itself has the models.OneToOneField(Amenity,on_delete=models.CASCADE,primary_key=True) definition.

The models.OneToOneField() definition creates the one to one relationship, where the first argument Item indicates the relationship model. The second argument on_delete=models.CASCADE tells Django that in case the relationship record is deleted (i.e. the Item) its other record (i.e. the Drink) also be deleted, this last argument prevents orphaned data. Finally, the primary_key=True tells Django to use the relationship id (i.e. Drink.id) as the primary key instead of using a separate and default column id, this last argument makes it easier to track relationships.

In addition to the database level benefits of creating a one to one relationship (e.g. improved data maintenance), Django models also provide an API to simplify the access of data related to this kind of relationship. Listing 6 illustrates various queries examples associated with one to one relationships in Django models.

Listing 6 - One to one Django model query syntax

from coffeehouse.items.models import Item, Drink

mocha_item = Item.objects.get(name='Mocha')

# Access the Drink element through its base Item element 
mocha_item.drink

# Get Drink objects through Item with caffeine field less than 200
Item.objects.filter(drink__caffeine__lt=200)

# Delete the Item element and its associated Drink record
# NOTE: This deletes the associated Drink record due to the on_delete=models.CASCADE in the OneToOneField definition
mocha_item.delete()

# Query a Drink through an Item property 
Drink.objects.get(item__name='Latte')

As you can see in listing 6, the operations for one to one Django model relationships are much simpler than the previous example due mainly to the simpler relationship nature. Notice that similar to the previous relationship examples in listings 2 and 4, with one to one Django models it's also possible to do queries parting from either model.