Simple Workaround to Avoid Django Circular Import Errors

django circular imports

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:

  1. We removed the from people.models import Person statement (cause of the error);
  2. We changed the ManyToManyField reference syntax to "people.Person" instead of Person.

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!

alpharithms discord banner 1
Zαck West
Entrepreneur, programmer, designer, and lifelong learner. Can be found taking notes from Mother Nature when not hammering away at the keyboard.