Introduction
Types have become an important part of Python development. Django uses a fair bit of magic to achieve its ergonomics. That magic can sometimes make type checking difficult. Installing django-stubs gets you quite far, but there are still situations when some type checkers still need some help.
One of these situations can arise when you use related object managers.
Cannot access member X for type Y
Let’s create some fictional models to illustrate the problem.
class Author(models.Model):
name = models.TextField(max_length=20)
class Category(models.Model):
name = models.TextField(max_length=20)
class Article(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
authors = models.ManyToManyField(Author, related_name="articles")
A common way of accessing related objects in Django is through related managers. In the above example that could look something like this:
category = Category.objects.get(pk=1)
print(category.article_set.all())
author = Author.objects.get(pk=1)
print(author.articles.all())
The problem? Both article_set
and articles
are dynamically added to the instance and the type checker doesn’t know anything about them.
Helping the type checker
We can help the type checker by adding the attributes to the respective models when the type checker is running:
from typing import TYPE_CHECKING
from django.db import models
if TYPE_CHECKING:
from django.db.models.manager import RelatedManager
class Author(models.Model):
name = models.TextField(max_length=20)
if TYPE_CHECKING:
articles: RelatedManager["Article"]
class Category(models.Model):
name = models.TextField(max_length=20)
if TYPE_CHECKING:
article_set: RelatedManager["Article"]
class Article(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
authors = models.ManyToManyField(Author, related_name="articles")
Now proceed and use related object managers without any type errors!