# How to configure Ruff for Django


[Ruff](https://pydevtools.com/handbook/reference/ruff.md)'s default rule set knows nothing about Django. It will not flag `CharField(null=True)`, but it will flag every long line in an auto-generated migration. This guide fixes that: enable the `DJ` rule set, give Ruff the per-file ignores Django's generated code needs, and produce a configuration block that catches Django-specific bugs without burying them under noise.

This guide assumes a working Django project. Follow [Set up a Django project with uv](https://pydevtools.com/handbook/tutorial/set-up-a-django-project-with-uv.md) first if there isn't one yet.

## Add Ruff to the project

```bash
uv add --dev ruff
```

Confirm the install:

```console
$ uv run ruff --version
ruff 0.15.14
```

The exact version will vary. The configuration below was verified with `ruff 0.15.14`.

## Enable the flake8-django (DJ) rule set

Ruff ships seven Django-specific rules under the `DJ` prefix, all from the [flake8-django](https://docs.astral.sh/ruff/rules/#flake8-django-dj) plugin. None are on by default. Add `DJ` to `extend-select` in [`pyproject.toml`](https://pydevtools.com/handbook/reference/pyproject.toml.md):

```toml
[tool.ruff.lint]
extend-select = [
    "DJ",       # flake8-django: Django-specific bugs
    "E",        # pycodestyle errors
    "F",        # Pyflakes
    "W",        # pycodestyle warnings
    "I",        # isort
    "UP",       # pyupgrade
    "B",        # flake8-bugbear
]
```

Run Ruff over the project:

```bash
uv run ruff check .
```

Each `DJ` rule targets a Django pattern that ordinary linters miss:

| Code | Catches |
|------|---------|
| DJ001 | `null=True` on `CharField` / `TextField`. Django convention is an empty string default; nullable string fields force callers to handle both `None` and `""`. |
| DJ003 | `locals()` passed as the context dict to `render()`. Leaks every variable in scope to the template. |
| DJ006 | `Meta.exclude` on a `ModelForm`. Future model fields silently become form fields. Use `fields` instead. |
| DJ007 | `Meta.fields = "__all__"` on a `ModelForm`. Same risk as `exclude`, opt-in by name instead. |
| DJ008 | A `models.Model` subclass with no `__str__` method. Django Admin and the shell render it as `Question object (1)`. |
| DJ012 | Model body content out of Django's prescribed order (fields → managers → `Meta` → `__str__` → `save` → custom methods). |
| DJ013 | `@receiver` placed underneath another decorator. `@receiver` connects the inner function to a signal; outer decorators never run. |

For full descriptions and fix examples, see the [flake8-django reference](https://docs.astral.sh/ruff/rules/#flake8-django-dj).

## Skip auto-generated migrations

`makemigrations` writes Python files that ordinary style rules treat as broken by design: long lines and verbatim hardcoded literals. Linting them produces noise the developer cannot fix without editing generated code.

A fresh `0001_initial.py` for two trivial models trips three `E501 Line too long` errors out of the box. Tell Ruff to leave migrations alone:

```toml
[tool.ruff.lint.per-file-ignores]
"**/migrations/*.py" = ["E501", "RUF012"]
```

The pattern matches every `migrations/` directory under every Django app, regardless of nesting. `E501` covers the long-line problem; `RUF012` covers the mutable-default-value warning that fires on Django's `dependencies = []` and `operations = [...]` class attributes if `RUF` rules are enabled later.

To skip migrations entirely instead of selectively suppressing rules, use `extend-exclude`:

```toml
[tool.ruff]
extend-exclude = ["**/migrations/"]
```

Selective suppression is preferable: it still catches `F401` (unused imports) and other real bugs Django's generator can leave behind.

## Loosen rules in tests and settings

Two more files need narrower per-file rules.

`settings.py` defines a long list of password validator paths and a wildcard import is occasionally needed for environment-specific overrides. Add:

```toml
[tool.ruff.lint.per-file-ignores]
"**/settings.py" = ["E501", "F403", "F405"]
```

`F403` allows `from .base import *` in `settings/production.py`-style splits; `F405` allows the symbols introduced by that wildcard to be used without being explicitly imported. Drop both if the project does not use wildcard imports in settings.

Tests typically use `assert` statements, which the [security ruleset](https://pydevtools.com/handbook/how-to/how-to-enable-ruff-security-rules.md) flags as `S101`:

```toml
[tool.ruff.lint.per-file-ignores]
"**/tests.py" = ["S101"]
"**/test_*.py" = ["S101"]
```

Both patterns match Django's default `tests.py` and pytest-style `test_*.py` files. Drop the `S101` entry if the project does not enable the `S` rule set.

## Apply the complete configuration

Combine everything into a single `pyproject.toml` block:

```toml
[tool.ruff.lint]
extend-select = [
    "DJ",       # flake8-django
    "E",        # pycodestyle errors
    "F",        # Pyflakes
    "W",        # pycodestyle warnings
    "I",        # isort
    "UP",       # pyupgrade
    "B",        # flake8-bugbear
]

[tool.ruff.lint.per-file-ignores]
"**/migrations/*.py" = ["E501", "RUF012"]
"**/settings.py" = ["E501", "F403", "F405"]
"**/tests.py" = ["S101"]
"**/test_*.py" = ["S101"]
```

For a more aggressive baseline, layer the [recommended Ruff defaults](https://pydevtools.com/handbook/how-to/how-to-configure-recommended-ruff-defaults.md) on top by adding the rules from that guide to `extend-select`. The per-file ignores still apply.

## Run Ruff over the project

```bash
uv run ruff check .
```

Ruff prints each violation with file, line, and rule code. On a clean Django project, the output looks like this:

```console
$ uv run ruff check .
DJ008 Model does not define `__str__` method
 --> polls/models.py:4:7
  |
4 | class Question(models.Model):
  |       ^^^^^^^^
5 |     question_text = models.CharField(max_length=200, null=True)
6 |     pub_date = models.DateTimeField('date published')
  |

DJ001 Avoid using `null=True` on string-based fields such as `CharField`
 --> polls/models.py:5:21
...
```

Two ways to address what Ruff finds:

- `uv run ruff check --fix .` rewrites violations Ruff knows how to fix automatically, like sorting imports (`I001`) and removing unused imports (`F401`). The `DJ` rules do not auto-fix; they require a code change.
- `# noqa: DJ008` on a single line, or `# ruff: noqa: DJ008` at the top of a file, suppresses the rule narrowly when a violation is intentional.

For broader policy patterns (block-level disables, codebase-wide ignores), see the [Ruff configuration reference](https://docs.astral.sh/ruff/configuration/).

## Add Ruff to pre-commit

The same configuration runs from a [pre-commit](https://pydevtools.com/handbook/how-to/how-to-set-up-pre-commit-hooks-for-a-python-project.md) hook. Add the official Ruff hook to `.pre-commit-config.yaml`:

```yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.15.14
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format
```

The hook reads `pyproject.toml` automatically, so all the `DJ` rules and per-file ignores apply to commits and CI runs without further configuration. Bump the `rev:` whenever a new Ruff release ships.

## Learn More

- [Ruff reference](https://pydevtools.com/handbook/reference/ruff.md)
- [How to configure recommended Ruff defaults](https://pydevtools.com/handbook/how-to/how-to-configure-recommended-ruff-defaults.md)
- [How to enable Ruff security rules](https://pydevtools.com/handbook/how-to/how-to-enable-ruff-security-rules.md)
- [How to configure mypy and django-stubs in a uv project](https://pydevtools.com/handbook/how-to/how-to-configure-mypy-and-django-stubs-in-a-uv-project.md)
- [Set up a Django project with uv](https://pydevtools.com/handbook/tutorial/set-up-a-django-project-with-uv.md)
- [Ruff: a complete guide](https://pydevtools.com/handbook/explanation/ruff-complete-guide.md)
- [flake8-django rule reference](https://docs.astral.sh/ruff/rules/#flake8-django-dj)
- [Ruff per-file-ignores documentation](https://docs.astral.sh/ruff/settings/#lint_per-file-ignores)
