Search code examples
pythondjangotypespylint

Django: Resolve a related field to QuerySet type


Based on the official documentation:

# Declare the ForeignKey with related_name
class Tag(models.Model):
    article = models.ForeignKey(
        Article,
        on_delete=models.CASCADE,
        related_name="tags"
    )
    name = models.CharField(max_length=255)

# Return all tags
Article.tags.all()

My linter (django-pylint) is unable to type it porperly: Article.tags is Any, I expected a QuerySet[Tag].


Can I declare the Article.tags reference in the Article class? (preferred approach)

from django.db.models.query import QuerySet

class Article(models.Model):
    ...
    # Related field declaration
    tags: QuerySet[Tag]

Article.tags.all()

Or maybe I need to convert it every time I need it?

tags_qs: QuerySet[Tag] = Article.tags
tags_qs.all()

In both scenarios, it looks heavy to implement for each related field.


Of course, it's more a question for comfortable development experience than a critical issue. The goal is to allow my linter and other autocompletion/discovery tools to resolve related fields as QuerySet[T] type. Maybe I can't due to the design of the Python implementation, more than a Django issue.

Is there any other alternative to fix this problem?


Solution

  • Thanks to @Anentropic for pointing me in the right way, I opted for django-types. This library can be easily integrated with VS Code and the PyLance extension (which uses PyRight type checking, not MyPY).

    Setup with VS Code

    1. Clone django-types
    2. Rename django_stubs to django
    3. Move it into ./typings/django in your project folder

    Adaptation

    Following this instructions: https://github.com/sbdchd/django-types#usage

    from __future__ import annotations  # or just be in python 3.11
    
    from typing import TYPE_CHECKING
    
    if TYPE_CHECKING:
        from django.db.models import Manager
    
    class Article(models.Model):
        tags: Manager["Tag"]
    
    # Declare the ForeignKey with related_name
    class Tag(models.Model):
        article = models.ForeignKey(
            Article,
            on_delete=models.CASCADE,
            related_name="tags"
        )
        name = models.CharField(max_length=255)
    
    # Return all tags
    Article.tags.all()
    

    Now, PyLance working fine with autocompletion!