django circular imports

Simple Workaround to Avoid Django Circular Import Errors

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 Djang0’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.

Discussion

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!