Django: typehinting backward / related_name / ForeignKey relationships
Asked Answered
F

2

7

Let's say we have the following models:

class Site(models.Model):
    # This is djangos build-in Site Model
    pass

class Organization(models.Model):
    site = models.OneToOneField(Site)

And if I use this somewhere in some other class:

organization = self.site.organization

Then mypy complains:

Site has no attribute "organization"

How can I make mypy happy here?

Fecteau answered 24/8, 2022 at 7:9 Comment(0)
T
14

Django adds backwards relations at runtime which aren't caught by mypy which only does static analysis.

To make mypy happy (and to make it work with your editor's autocomplete) you need to add an explicit type hint to Site:

class Site(models.Model):
    organization: "Organization"

class Organization(models.Model):
    site = models.OneToOneField(Site)

Using quotes around the type is needed since we are doing a forward reference to Organization before it has been defined.

For foreign keys and many-to-many relationships, you can do the same thing, but using a QuerySet type hint instead:

class Organization(models.Model):
    site = models.OneToOneField(Site)
    employees: models.QuerySet["Employee"]

class Employee(models.Model):
    organization = models.ForeignKey(
        Organization,
        on_delete=models.CASCADE,
        related_name="employees",
    )

EDIT: There is a django-stubs package which is meant to integrate with mypy, however I haven't used it personally. It may provide a solution for this without having to explicitly add type hints to models.

Tharpe answered 25/8, 2022 at 18:40 Comment(3)
It's a wrong type. isinstance(o.employees, models.QuerySet) is False. type(o.employees) shows django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager. Also, if Employee defines custom objects manager (models.Manager) named EmployeeManager, then isinstance(o.employees, EmployeeManager) is True. and without this custom objects manager, isinstance(o.employees, models.Manager) is TrueKatharyn
I think employees: models.Manager["Employee"] should do the magic.Katharyn
About django-stubs, check RelatedManager: ``` if typing.TYPE_CHECKING: from django_stubs_ext.db.models.manager import RelatedManager ```Katharyn
P
-6

I just know a litle of english, and i don't know this is true. But you can add

site = models.OneToOneField(Site, on_delete=models.CASCADE)

Purehearted answered 24/8, 2022 at 8:6 Comment(1)
and you can add related_name in relationship of siteStrontianite

© 2022 - 2024 — McMap. All rights reserved.