Django offers a powerful set of database modeling tools to help build enterprise-grade web applications. In some cases, application models need to reference each other dependently—this can lead to a circular import error. Fortunately, Django has an easy workaround.
Django is a robust Python-based web application framework with a powerful ORM model that supports Rapid Application Development (RAD). It does this, largely, through powerful abstractions of lower-level database programming. Yet another reason Python remains one of the most popular programming languages.
The Problem
In some cases, this abstraction makes logical errors tougher to diagnose—circular imports being one of them. Let’s say you have two models from different applications: Person
and Name
. Each Person
object gets a reference to a Name
object—which makes total sense given most people have names.
Each Name
object needs to easily access all Person
objects assigned that name. To make things easy, this is done via Django’s ManyToMany
field. To make this reference, you might import the Person
object from the People
app to define the association. Considering we’re doing a similar import with the People
model, that’s going to be an issue.
Below is the definition of the Person
class, defined in our app/people/models.py
file:
from django.db.models import Model, ForeignKey, CASCADE from names.models import Name class Person(Model): """ Our Person Model with a ForeignKey reference to the Name class. """ name = ForeignKey(Name, on_delete=CASCADE) ...
Below is the definition of the Name
class, defined in our app/names/models.py file:
from django.db.models import ManyToManyField, Model from people.models import Person class Name(Model): """ Object model for name, which references all Person Objects """ ... people = ManyToManyField(Person, related_name="person_name")
These classes, while a bit contrived for discussion’s sake, represent a co-dependency where each models.py
file requires the import of the others’. This is where the ImportError
is rooted. Without further consideration, we’ll get an error similar to the following:
ImportError: cannot import name 'Name' from partially initialized module 'names.models' (most likely due to a circular import) (C:\user\app\names\models.py)
Note: This is not a Django-specific error but rather a Python error (really a generic logical error) resulting from importing a file into a file that is importing the other. In other words; an infinite import loop.
The Solution
Fortunately, the great minds behind Django have provided a work-around for this common case. Through Django’s behind-the-scenes magic, one can avoid circular imports by using a string-based specification in model definitions. We just need to change the Name class to this:
from django.db.models import ManyToManyField, Model class Name(Model): """ Object model for name, which references all Person Objects """ ... people = ManyToManyField("people.Person", related_name="person_name")
Two things have happened:
- We removed the
from people.models import Person
statement (cause of the error); - We changed the
ManyToManyField
reference syntax to"people.Person"
instead ofPerson
.
This syntax is somewhat related to the forward reference update via PEP484. This update allows for functions and class definitions to reference non-declared functions and classes by using the quoted syntax.
Review
Django’s ORM provides developers with super-friendly APIs for dealing with basic-semi-complex database modeling. Throughout the years, Django’s developers have also made accommodations for complex database design as well as workarounds for common issues—the need for circular references being one. While these examples were a bit contrived, they illustrate how a little syntactic sugar goes a long way!