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:
on_delete=models.CASCADE
(Default).- Automatically deletes related records when the related instance is removed (e.g. if theMenu
Breakfast instance is deleted, allItem
records referencing theMenu
Breakfast instance are also deleted)on_delete=models.PROTECT
.- Prevents a related instance from being removed (e.g. if themenu
field onItem
usesForeignKey(Menu,on_delete=models.PROTECT)
, any attempt to removeMenu
instances referenced byItem
instances are blocked).on_delete=models.SET_NULL
.- Assigns NULL to related records when the related instance is removed, note this requires the field to also use thenull=True
option (e.g. if theMenu
Breakfast instance is deleted, allItem
records referencing theMenu
Breakfast instance are assigned NULL to theirmenu
field value).on_delete=models.SET_DEFAULT
.- Assigns a default value to related records when the related instance is removed, note this requires the field to also use adefault
option value (e.g. if theMenu
Breakfast instance is deleted, allItem
records referencing theMenu
Breakfast instance are assigned a defaultMenu
instance to theirmenu
field value).on_delete=models.SET
.- Assigns a value set through a callable to related records when the related instance is removed (e.g. if theMenu
Breakfast instance is deleted, allItem
records referencing theMenu
Breakfast instance are assigned an instance to theirmenu
field value set through a callable function).on_delete=models.DO_NOTHING
.- No action is taken when related records are removed. This is generally a bad relational database practice, so by default, databases will generate an error since you're leaving orphaned records with no value, null or otherwise. If you use this value, you must ensure the database table does not enforce referential integrity.
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.