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.
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 object gets a reference to a
Name object—which makes total sense given most people have names.
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
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.
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 Personstatement (cause of the error);
- We changed the
ManyToManyFieldreference syntax to
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.
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!