# How Pyrefly Handles Django ORM Models, QuerySets, and View Decorators

> Discover how Pyrefly handles Django ORM models, QuerySets, and view decorators by analyzing type stubs and applying a generic type solver for precise type propagation without hardcoding.

- Repository: [Meta/pyrefly](https://github.com/facebook/pyrefly)
- Tags: deep-dive
- Published: 2026-05-21

---

**Pyrefly handles Django ORM models, QuerySets, and view decorators by loading official type stubs (django-stubs) and applying its generic type solver to propagate precise types through the AST, without any framework-specific hardcoding.**

Pyrefly is a performant Python type checker developed by Meta (facebook/pyrefly) that analyzes code by consuming type stub files rather than embedding framework-specific logic. When working with **Pyrefly Django ORM** patterns, the tool leverages the `django-stubs` package to understand generic QuerySet types and decorator signatures, treating them as standard parametric types within its solver.

## How Pyrefly Types Django ORM QuerySets

Pyrefly does not contain special-purpose Django code for the ORM. Instead, it relies on the `django-stubs` package where `Model` is defined as a generic base class and `QuerySet` is declared as `class QuerySet[ModelType, Any]: …`.

### Generic Model and QuerySet Definitions

When a Python file imports `django.db.models`, Pyrefly loads the corresponding `.pyi` stub files from `django-stubs`. The stubs define `Model` as a generic base class, allowing Pyrefly to record concrete model definitions as specific instantiations of that generic.

When you subclass a model:

```python
class MyModel(models.Model):
    ...

```

Pyrefly records that `MyModel` is a concrete `Model` type. The generic `QuerySet` is then instantiated as `models.QuerySet[MyModel, MyModel]` when accessed through that model class.

### Type Propagation Through Inheritance

The core type solver (`pyrefly_types` crate) treats generic classes like `QuerySet` as **parametric types** and propagates them through attribute accesses and method calls. This respects the variance rules defined in the type system, ensuring that `MyModel.objects.all()` returns the correctly parameterized QuerySet type rather than a generic `Any`.

### Test Validation with django_testcase!

The inference logic is exercised in the test suite via the `django_testcase!` macro defined in [`pyrefly/lib/test/django/util.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/test/django/util.rs). This macro creates a temporary Django environment, runs a Python script, and checks inferred types against expectations using `typing_extensions.assert_type`.

In [`pyrefly/lib/test/django/view.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/test/django/view.rs), the test suite verifies that for a `ListView[MyModel]` instance:

```python
from django.db import models
from django.views.generic.list import ListView
from typing_extensions import assert_type

class MyModel(models.Model):
    ...

class MyListView(ListView[MyModel]):
    ...

list_view = MyListView()

# Pyrefly infers:

assert_type(list_view.queryset,
            models.QuerySet[MyModel, MyModel] | None)   # ✅

assert_type(list_view.get_context_object_name(models.QuerySet[MyModel]()),
            str)                                          # ✅

```

The test confirms that `detail_view.queryset` and `list_view.queryset` both infer as `models.QuerySet[MyModel, MyModel] | None`.

## Handling Django View Decorators

Pyrefly handles Django view decorators by resolving their types from stubs and folding them into the wrapped function's type signature.

### Callable Signature Preservation

Decorators such as `@user_passes_test` are defined in the Django stubs as callables that accept a function and return a new function with the *same* signature (plus optional extra parameters). Pyrefly parses the decorator expression, resolves the decorator's type from the stub, and preserves the original function's signature through the decoration process.

### Decorator Resolution in the Type Solver

When a decorator's stub is typed as `Callable[[Callable[..., R]], Callable[..., R]]`, the solver substitutes the inner callable's signature into the outer type. This yields a function type identical to the original, ensuring that decorated views maintain their type safety without requiring explicit annotations.

The test in [`pyrefly/lib/test/django/view.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/test/django/view.rs) verifies this behavior:

```python
from django.contrib.auth.decorators import user_passes_test
from django.http import HttpRequest, HttpResponse

@user_passes_test(lambda u: u.is_active)
def my_view(request: HttpRequest) -> HttpResponse:
    ...

# Pyrefly knows that `my_view` still has type

# (HttpRequest) -> HttpResponse

```

## The Architecture Behind Django Support

The underlying mechanism supporting Django is the same for any third-party library with type stubs.

### Environment Setup and Stub Loading

The `django_testcase!` macro creates a `TestEnv` that adds the path to a local copy of the Django source and the `django-stubs` package to the Python import search path. The `pyrefly_python` crate (located in `crates/pyrefly_python/src/*`) reads the `.pyi` files, builds a symbol table, and resolves generic parameters.

### Core Type Solver Mechanics

The `pyrefly_types` crate (in `crates/pyrefly_types/src/*`) handles the heavy lifting. It treats generic classes like `QuerySet` as parametric types and propagates them through the AST. Because this logic lives in the generic type solver, Pyrefly automatically supports any library that provides proper type stubs, including Django's ORM and its class-based view decorators.

Additional QuerySet usage examples, such as `author.authors.all()` patterns, are tested in [`pyrefly/lib/test/django/many_to_many.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/test/django/many_to_many.rs).

## Summary

- Pyrefly relies on `django-stubs` rather than hardcoded Django logic to understand the framework's API.
- **ORM Models** are handled as generic base classes, with concrete models instantiating specific `QuerySet[ModelType, ModelType]` types.
- **QuerySet inference** is validated through the `django_testcase!` macro in [`pyrefly/lib/test/django/util.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/test/django/util.rs) and test files like [`pyrefly/lib/test/django/view.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/test/django/view.rs).
- **View decorators** preserve function signatures by resolving stub definitions as `Callable` transformations in the type solver.
- The architecture uses the `pyrefly_python` crate for stub loading and `pyrefly_types` for generic type propagation, enabling automatic support for any properly stubbed library.

## Frequently Asked Questions

### Does Pyrefly require special configuration for Django projects?

No special configuration is required. Pyrefly automatically discovers and loads `django-stubs` from your Python environment. As long as the stubs are installed in the Python path, Pyrefly's frontend (`pyrefly_python` crate) will read the `.pyi` files and build the appropriate symbol tables for type checking.

### How does Pyrefly handle generic QuerySet methods like filter()?

Pyrefly handles `filter()` and similar methods by resolving their definitions in the `django-stubs` `.pyi` files. Since `QuerySet` is defined as `class QuerySet[ModelType, Any]`, methods that return `Self` or `QuerySet[ModelType, ...]` are automatically instantiated with the concrete model type. The core type solver propagates these parametric types through method chains, ensuring that `MyModel.objects.filter(...)` returns `QuerySet[MyModel, MyModel]`.

### Can Pyrefly infer types for custom Django decorators?

Yes. Pyrefly can infer types for custom decorators as long as they are properly typed in stubs or inline annotations. If a decorator is typed as `Callable[[Callable[..., R]], Callable[..., R]]` (or similar), the type solver will substitute the wrapped function's signature into the decorator's return type, preserving the original callable signature through the decoration process.

### What version of django-stubs does Pyrefly use?

Pyrefly does not bundle a specific version of `django-stubs`. It uses whichever version is available in the Python environment configured for your project. The test suite in `pyrefly/lib/test/django/` uses a local copy of the stubs to ensure consistent test results, but for production code, Pyrefly loads the stubs from your configured Python path.