Relationships in Django models

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.

One to many relationships in Django models.

A one to many relationship implies that one model record can have many other model records associated with itself. For example, a Menu model record can have many Item model 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 7-22 illustrates a sample of a one to many Django relationship.

Listing 7-22. 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 7-22 is Menu and has the name field (e.g. Menu instances can be Breakfast, Lunch, Drinks,etc). Next, in listing 7-22 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 which is explained in the next chapter on CRUD records across Django model relationships.

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 model 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 7-23 illustrates a sample of a many to many Django relationship.

Listing 7-23. 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 7-23 is Amenity and has the name and description fields. Next, in listing 7-23 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[5], 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, which is explained in the next chapter on CRUD records across Django model relationships.

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 model records can have a one to one relationship to Drink model 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 7-24 illustrates a sample of a one to one Django relationship.

Listing 7-24 One to one 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)
    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 7-24 is Item which is similar to the one presented in listing 7-22, except the version in listing 7-24 has the additional calories and price fields. Next, in listing 7-24 is the Drink 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, a technique that 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, which is explained in the next chapter on CRUD records across Django model relationships.

Options for relationship model data types

Previously you explored Django data types and their many options to customize how they handle data, such as : limiting values, allowing empty and null values, establishing predetermined values and enforcing DDL rules. In this section you'll learn about the options available for Django relationship model data types.

Note Options described in the general purpose model data type section (e.g. blank, unique) are applicable to relationship model data types unless noted.

Data integrity options: on_delete

All model relationships create dependencies between one another, so an important behavior to define is what happens to the other party when one party is removed. The on_delete option is designed for this purpose, to determine what to do with records on the other side of a relationship when one side is removed.

For example, if an Item model has a menu ForeignKey() field pointing to a Menu model (i.e. like listing 7-22, a one to many relationship: an Item always belong to one Menu, and a Menu has many Items), what happens to Item model records if their related Menu model instance is deleted ? Are the Item model records also deleted ?

The on_delete option is available for all three relationship model data types and supports the following values:

Reference options: self, literal strings and parent_link

Model relationships sometimes have recursive relationships. This is a common scenario in one to many relationship models with parent-child relationships. For example, a Category model can have a parent field which in itself is another Category model or a Person model can have a relatives field which in itself are other Person models. To define this type of relationship you must use the 'self' keyword to reference the same model, as shown in listing 7-25.

Listing 7-25 One to many Django model relationship with self-referencing model

from django.db import models

class Category(models.Model):
    menu = models.ForeignKey('self')

class Person(models.Model):
    relatives = models.ManyToManyField('self')

Although model relationship data types typically express their relationships through model object references (e.g. models.ForeignKey(Menu)), it's also valid to use literal strings to reference models (e.g. models.ForeignKey('Menu')). This technique is helpful when the model definition order does not allow you to reference model objects that are not yet in scope and is a technique often referred to as model 'lazy-loading'.

The parent_link=True option is an exclusive option for one to one relationships (i.e the models.OneToOneField data type) used when inheriting model classes, to help indicate the child class field should be used as a link to the parent class.

Reverse relationships: related_name, related_query_name and symmetrical

When you use relationship model data types, Django automatically establishes the reverse relationship between data types with the the _set reference. This mechanism is illustrated in listing 7-26.

Listing 7-26 One to many Django model relationship with reverse relationship references

from django.db import models

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

class Item(models.Model):
    menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=100)
    price = models.FloatField(blank=True,null=True)

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

# Direct access 
all_items_with_breakfast_menu = Item.objects.filter(menu=breakfast)

# Reverse access through instance
same_all_items_with_breakfast_menu = breakfast.item_set.all()

As you can see in listing 7-26, there are two routes between a Django relationship. The direct route involves using the model with the relationship definition, in this case, Item gets all the Item records with a Menu Breakfast instance. To do this, you use Item and filter on the menu ForeignKey reference (e.g. Item.objects.filter(menu=breakfast)).

But it's also possible to use a Menu instance (e.g. breakfast in listing 7-26) and get all Item records with a menu instance, this is called a reverse relationship or path. As you can see in the listing 7-26, the reverse relationship uses the <model_instance>.<related_model>_set syntax (e.g. breakfast.item_set.all() to get all Item records with a the breakfast instance).Now that you know what a reverse relationship is, let's explore the options associated with this term.

The related_name option allows you to customize the name or disable a reverse model relationship. Renaming a reverse relationship provides more intuitive syntax over the _set syntax from listing 7-26, where as disabling a reverse relationship is helpful when a related model is used in other contexts and blocking access to a reverse relationship is required for accessibility reasons.

For example, in listing 7-26 the reverse relationship uses the breakfast.item_set.all() syntax, but if you change the field to models.ForeignKey(...related_name='menus'), you can use the reverse relationship breakfast.menus.all() syntax. To disable a reverse relationship you can use the + (plus sign) on the related_name value (e.g. models.ForeignKey(...related_name='+')).

Reverse relationships are also available as part of queries, as illustrated in listing 7-27.

Listing 7-27 One to many Django model relationship with reverse relationship queries

# Based on models from listing 7-26

# Direct access, Item records with price higher than 1
Items.objects.filter(price__gt=1)

# Reverse access query, Menu records with Item price higher than 1
Menu.objects.filter(item__price__gt=1)

Notice how the Menu query in listing 7-27 uses the item reference to filter all Menu records via its Item relationship. By default, reverse relationship queries use the name of the model, so in this case, the related Menu model is Item, therefore the query field is item. However, if you define the related_name option on a field this value takes precedence. For example, with models.ForeignKey(...related_name='menus') the reverse query in listing 7-27 becomes Menu.objects.filter(menus__price__gt=1), all of which takes us to the related_query_name option.

The related_query_name option is used to override the related_name option value for cases where you want the reverse query to have a different field value. For example, with models.ForeignKey(...related_name='menus',related_query_name='onlyitemswith') the reverse relationship reference for menus is listing 7-26 would still work, but the reverse relationship query from listing 7-27 would change to Menu.objects.filter(onlyitemswith__price__gt=1).

Covering an edge-case for many to many relationships is the symmetrical option. If you create a many to many relationship that references itself -- as illustrated in listing 7-25 with the 'self' syntax -- Django assumes the relationship is symmetrical (e.g. all Person instances are relatives and therefore requires no reverse relationships since it would be redundant) thus self referencing many to many relationships forgoe adding a _set reverse relationship to the field. You can use symmetrical=False to force Django to maintain the reverse relationship.

Tip The next chapter covers Django model relationship queries in greater detail.

Database options: to_field, db_constraint, swappable, through, through_fields and db_table

By default, Django model relationships are established on the primary key of a model which in itself defaults to a model's id field. For example, the field menu = models.ForeignKey(Menu) stores the id from a Menu instance as the relationship reference. You can override this default behavior with the to_field option and specify a different field on which to establish the relationship reference. Note that if you assign a to_field value, this field must be set with unique=True.

By default, Django follows relational database conventions and constrains relationships at the database level. The db_constraint option -- which defaults to True -- allows you to bypass this constraint by assigning it a False value. Setting db_constraint=False should only by used when you know beforehand the data relationships in a database is broken and doesn't require constraint checking at the database level.

The swappable option is intended to influence migrations for models that contain relationships and are swappable with other models. Unless you implement a very sophisticated model hierarchy with model swapping features, this option is primarily intended for Django's built-in User model which uses a relationship and is often swapped out for custom user models. The chapter on user management contains more details on this swappable model option .

Specific to many to many model relationships (i.e. the models.ManyToManyField data type) the through, through_fields & db_table options, influence the junction table used in these type of relationships. If you wan't to change the default name for a many to many junction table, you can use the db_table option to specify a custom junction table name.

By default, a junction table for a many to many relationship stores a minimum amount of information: an id for the relationship and the id's for each of the model relationships. It's possible to specify a separate model to operate as a junction table and store additional information about the many to many relationship (e.g. through=MyCustomModel uses the MyCustomTable model as the many to many junction table). If you define a through option, then it's also necessary to use the through_fields to tell Django which fields in the new model are used to store references for the model relationships.

Form values: limit_choices_to

When Django models with relationships are used in the context of forms, it can be useful and even necessary to delimit the amount of displayed relationships. For example, if you use an Item model with a relationship to a Menu model, displaying the entire set of Item records as forms (e.g. in the Django admin) can be impractical if you have hundreds of Item records.

The limit_choices_to can be used on a relationship model type to filter the amount of displayed records in forms. The limit_choices_to can declare an in-line reference field filter (e.g. limit_choices_to={'in_stock':True}) or a callable that performs more complex logic (e.g. limit_choices_to=my_complex_method_limit_picker).

Tip The next chapter covers Django model forms in greater detail.
  1. https://en.wikipedia.org/wiki/Associative_entity