| --- |
| title: Django Support |
| description: Pyrefly support for Django ORM. |
| --- |
| |
| {/* |
| * Copyright (c) Meta Platforms, Inc. and affiliates. |
| * |
| * This source code is licensed under the MIT license found in the |
| * LICENSE file in the root directory of this source tree. |
| */} |
| |
| # Django Support |
| |
| Pyrefly includes **built-in support** for [Django](https://www.djangoproject.com/), a popular Python web framework. This feature provides static type checking for Django's Object-Relational Mapping (ORM) system, which is used to interact with databases. |
| |
| > **Note:** This support covers a subset of Django's ORM features. The implementation continues to evolve based on feedback and development. We currently support Django models, fields, and basic relationships. |
| |
| ### Feedback |
| |
| We welcome your feedback and suggestions. Please share your thoughts and ideas by opening an issue on [GitHub](https://github.com/facebook/pyrefly/issues). |
| |
| --- |
| |
| ## What is Django? |
| |
| Django is a high-level Python web framework. One of its core components is the Object-Relational Mapping (ORM) system, which allows developers to interact with databases using Python classes (models) instead of writing SQL queries directly. |
| |
| Django models define the structure of your database tables, and Django automatically handles the creation and management of the underlying database schema. |
| |
| --- |
| |
| ## Getting Started with Django and Pyrefly |
| |
| To use Pyrefly with Django, follow these steps: |
| |
| ### 1. Install django-stubs and set up a virtual environment |
| |
| ```bash |
| # Install django-stubs |
| pip install django-stubs |
| |
| # Create and activate a virtual environment |
| python3 -m venv .venv |
| source .venv/bin/activate |
| ``` |
| |
| ### 2. Install pyrefly (version 0.42.0 or later) |
| |
| ```bash |
| pip install pyrefly |
| ``` |
| |
| That's it! Pyrefly will automatically recognize Django constructs and provide appropriate type checking. |
| |
| --- |
| |
| ## How Pyrefly Supports Django |
| |
| Pyrefly provides type inference for Django's ORM without requiring any plugins or manual configuration. It: |
| |
| - Recognizes Django model classes that inherit from `models.Model` |
| - Understands Django field types (`CharField`, `IntegerField`, `ForeignKey`, etc.) |
| - Infers types for auto-generated fields (like `id` and `pk`) |
| - Handles relationships between models (`ForeignKey`, `ManyToManyField`) |
| - Provides special support for `choices` fields, including the `Choices`/`IntegerChoices`/`TextChoices` enum classes and Django's auto-generated `get_<fieldname>_display()` method |
| - Types Django's class-based generic views and the mixins that compose them — when a view is parameterized by a model, attributes inherited from the framework are typed against that model |
| - Provides immediate feedback when the code has type errors |
| - Does **not** require a plugin or manual config — support is built-in and automatic |
| |
| --- |
| |
| ## Some Supported Features with Examples |
| |
| The following examples showcase which Django features are currently supported by Pyrefly. This is a subset of Django's full feature set, but covers the most common use cases. |
| |
| ### Auto-Generated Fields |
| |
| Django automatically adds certain fields to every model, even if you don't define them explicitly. |
| |
| #### Primary Key: `id` Field |
| |
| By default, Django automatically adds an `id` field to serve as the primary key (unless you define a custom primary key): |
| |
| ```python |
| from django.db import models |
| |
| class Reporter(models.Model): |
| full_name = models.CharField(max_length=70) |
| # Django auto-adds: id = models.AutoField(primary_key=True) |
| |
| reporter = Reporter() |
| assert_type(reporter.id, int) |
| ``` |
| |
| #### Custom Primary Keys |
| |
| If you define a field with `primary_key=True`, Django will not add the `id` field. Pyrefly correctly infers the type of custom primary keys: |
| |
| ```python |
| from django.db import models |
| |
| class Reporter(models.Model): |
| uuid = models.UUIDField(primary_key=True) |
| full_name = models.CharField(max_length=70) |
| |
| reporter = Reporter() |
| assert_type(reporter.uuid, UUID) |
| assert_type(reporter.pk, UUID) # pk aliases the custom primary key |
| ``` |
| |
| #### ForeignKey `_id` Suffix Fields |
| |
| For every `ForeignKey` field named `X`, Django automatically creates a field named `X_id` that stores the ID of the related object: |
| |
| ```python |
| class Article(models.Model): |
| reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE) |
| # Django auto-adds: reporter_id: int |
| |
| article = Article() |
| assert_type(article.reporter_id, int) |
| ``` |
| |
| ### ForeignKey Relationships |
| |
| A `ForeignKey` creates a many-to-one relationship where each instance of one model relates to an instance of another model. |
| |
| ```python |
| from django.db import models |
| |
| class Reporter(models.Model): |
| full_name = models.CharField(max_length=70) |
| |
| class Article(models.Model): |
| reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE) |
| ``` |
| |
| #### Basic Forward Access |
| |
| Accessing a `ForeignKey` field returns an instance of the related model: |
| |
| ```python |
| article = Article() |
| assert_type(article.reporter, Reporter) |
| ``` |
| |
| #### Chained Access |
| |
| You can access fields on the related model: |
| |
| ```python |
| assert_type(article.reporter.full_name, str) |
| ``` |
| |
| #### Nullable ForeignKeys |
| |
| If a `ForeignKey` has `null=True`, Pyrefly reflects this in the inferred type: |
| |
| ```python |
| class Article(models.Model): |
| reporter = models.ForeignKey(Reporter, null=True, on_delete=models.CASCADE) |
| |
| article = Article() |
| assert_type(article.reporter, Reporter | None) |
| ``` |
| |
| ### ManyToManyField Relationships |
| |
| A `ManyToManyField` creates a many-to-many relationship where instances of one model can be related to multiple instances of another model. |
| |
| ```python |
| class Author(models.Model): |
| name = models.CharField(max_length=100) |
| |
| class Book(models.Model): |
| title = models.CharField(max_length=200) |
| authors = models.ManyToManyField(Author, related_name='books') |
| ``` |
| |
| #### Forward Managers |
| |
| Accessing a `ManyToManyField` returns a manager object that provides methods to interact with the related objects: |
| |
| ```python |
| book = Book() |
| assert_type(book.authors, ManyRelatedManager[Author, models.Model]) |
| assert_type(book.authors.all(), QuerySet[Author, Author]) |
| ``` |
| |
| The manager provides methods like `.add()`, `.remove()`, `.clear()`, and `.all()` to manage the relationship. |
| |
| --- |
| |
| ### Django Model Enums |
| |
| Pyrefly supports Django's model choices using `Choices`, `IntegerChoices`, and `TextChoices`. These provide type-safe enumerations for model fields. |
| |
| ```python |
| from django.db import models |
| |
| class Vehicle(models.IntegerChoices): |
| CAR = 1, "Car" |
| TRUCK = 2, "Truck" |
| MOTORCYCLE = 3, "Motorcycle" |
| |
| class Product(models.Model): |
| vehicle_type = models.IntegerField(choices=Vehicle.choices) |
| |
| # Pyrefly correctly infers enum types |
| assert_type(Vehicle.CAR.value, int) |
| assert_type(Vehicle.CAR.label, str) |
| assert_type(Vehicle.values, list[int]) |
| assert_type(Vehicle.choices, list[tuple[int, str]]) |
| ``` |
| |
| Pyrefly also supports `TextChoices` and the base `Choices` class with various value types including `enum.auto()` for automatic value generation. |
| |
| --- |
| |
| ## factory_boy Support |
| |
| Pyrefly recognizes [factory_boy](https://factoryboy.readthedocs.io/) factories that subclass `DjangoModelFactory`. For each factory, it figures out which Django model is produced and infers the correct return types for `create`, `build`, `create_batch`, and `build_batch`. No plugin or extra configuration is required. |
| |
| The examples below all build on this `User` model and `UserFactory`: |
| |
| ```python |
| # myapp/models.py |
| from django.db import models |
| |
| class User(models.Model): |
| username = models.CharField(max_length=150) |
| ``` |
| |
| ```python |
| # myapp/factories.py |
| from factory.django import DjangoModelFactory |
| |
| from myapp.models import User |
| |
| class UserFactory(DjangoModelFactory): |
| class Meta: |
| model = User |
| ``` |
| |
| ### `create()` and `build()` |
| |
| `UserFactory.create()` and `UserFactory.build()` each return a `User` instance: |
| |
| ```python |
| user = UserFactory.create() |
| assert_type(user, User) |
| |
| user = UserFactory.build() |
| assert_type(user, User) |
| ``` |
| |
| Field access on the returned instance is type-checked against the model: |
| |
| ```python |
| assert_type(user.username, str) |
| ``` |
| |
| ### `create_batch()` and `build_batch()` |
| |
| `UserFactory.create_batch(n)` and `UserFactory.build_batch(n)` return `list[User]`: |
| |
| ```python |
| users = UserFactory.create_batch(3) |
| assert_type(users, list[User]) |
| |
| users = UserFactory.build_batch(3) |
| assert_type(users, list[User]) |
| ``` |
| |
| --- |
| |
| ## Differences from Mypy |
| |
| Mypy uses a plugin (`mypy-django-plugin`) that provides very detailed type information by accessing runtime Django internals and performing multiple passes over the code. Pyrefly takes a different approach by following the type stubs directly without runtime introspection. |
| |
| ### Type Representation Differences |
| |
| In some cases, such as `ManyToManyField` relationships, Mypy and Pyrefly infer different types: |
| |
| **Example:** |
| |
| ```python |
| from django.db import models |
| |
| class Author(models.Model): |
| name = models.CharField(max_length=100) |
| |
| class Book(models.Model): |
| title = models.CharField(max_length=200) |
| authors = models.ManyToManyField(Author, related_name="books") |
| |
| class Article(models.Model): |
| headline = models.CharField(max_length=200) |
| authors = models.ManyToManyField(Author, related_name="articles") |
| |
| # What types do the managers have? |
| book = Book() |
| article = Article() |
| ``` |
| |
| Mypy (with django plugin): |
| - `book.authors` has type: `Author_ManyRelatedManager[Book_authors]` |
| - `article.authors` has type: `Author_ManyRelatedManager[Article_authors]` |
| - These are **different types** (different class name, different type parameter) |
| - Mypy will **reject** assigning one to the other |
| |
| Pyrefly (following stubs): |
| - `book.authors` has type: `ManyRelatedManager[Author, Model]` |
| - `article.authors` has type: `ManyRelatedManager[Author, Model]` |
| - These are the **same type** |
| - Pyrefly will **accept** assigning one to the other |
| |
| --- |
| |
| ## Features Not Yet Supported |
| |
| These are some of the Django features that are **not yet supported**: |
| |
| ### Reverse Relationships |
| |
| Django automatically creates reverse relationships for `ForeignKey` and `ManyToManyField`. For example: |
| |
| ```python |
| class Reporter(models.Model): |
| full_name = models.CharField(max_length=70) |
| |
| class Article(models.Model): |
| reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE) |
| |
| # Not yet supported: |
| reporter = Reporter() |
| reveal_type(reporter.article_set) # Expected: RelatedManager[Article] |
| ``` |
| |
| ### Advanced QuerySet Operations |
| |
| While basic `.all()` operations are supported, more complex QuerySet operations may not have complete type inference. |
| |
| Those are not the only unsupported features, so if there are specific features you would like to see, please request them by opening a github issue and adding the Django label to it. |