Open-source project
adamchainz/django-upgrade avatar
adamchainz/django-upgrade

django-upgrade is a code rewriter you can hang off a pre-commit hook

Automatically upgrade your Django projects.

1,252 stars82 forksPythonMIT

At a glance

What is it?
django-upgrade automates the source changes a Django project needs when it moves to a newer Django or Python version, and the repository shows a tool built for automation rather than for interactive use: a single runtime dependency on a token-level rewriting library, a published pre-commit hook, a uv build backend with a lockfile, and a coverage badge that claims one hundred percent.
Who is it for?
Adopt django-upgrade when you have a long-lived Django project with a deprecation backlog, since the work it removes is mechanical and the pre-commit hook lets you keep the project on the current idioms one commit at a time. Skip it if your codebase is small enough that a search and replace covers it.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The runtime dependency list is one line

The shortest honest description of this project is in its packaging metadata.

toml
[project]
name = "django-upgrade"
version = "1.33.0"
requires-python = ">=3.11"
dependencies = [
  "tokenize-rt>=4.1",
]
scripts.django-upgrade = "django_upgrade.main:main"

One runtime dependency, and it is a token-level rewriting library rather than an AST or framework package. That single choice carries most of the design.

A tool that rewrites source code has three ways to do it badly. It can use regular expressions, which match inside string literals and comments and produce code that parses and behaves differently. It can use an abstract syntax tree in a way that loses formatting and comments on output. It can work on the token stream, where each string, name, operator and comment is a distinct item with its original spelling and position.

Choosing `tokenize-rt` puts this project in the third category. Formatting survives, comments survive, and a pattern that happens to match inside a docstring does not trigger a rewrite. For a tool whose entire job is editing source files you did not write, that is the difference between a refactor and an incident.

It also means the tool does not need to import Django to know anything about Django. There is no dependency on Django itself in the runtime requirements, which is why the package can be installed and run on a project whose Django version you are trying to move away from.

The command line entry point is a single console script named `django-upgrade`, wired to `django_upgrade.main:main`.

Django 2.2 to 6.0 classifiers against a Python 3.11 floor

The version metadata is the most informative part of the file after the dependency list, because the two ranges tell different stories.

The Python requirement is `>=3.11`, and the classifiers list 3.11 through 3.15. The Django classifiers, however, run from 2.2 all the way to 6.0: 2.2, 3.0, 3.1, 3.2, 4.0, 4.1, 4.2, 5.0, 5.1, 5.2 and 6.0.

Those are not contradictory. They describe a tool whose output targets old Django releases and whose own execution requires a modern Python. That is exactly the upgrade path this kind of project exists for: the code being rewritten is Django 2.2 era, and the machine running the rewrite is on a current Python.

The classifiers also list Intended Audience as Developers, Operating System as Independent, Programming Language as Python 3 Only, and Typing as Typed. That last one is worth noting, because a source rewriter that ships type information is built by someone who intends it to be used in a typed codebase, where an imprecise rewrite would be caught by the type checker rather than in production.

The development status classifier is 5 - Production/Stable, and the project is at version 1.33.0. There are no GitHub releases, so the version history is on PyPI and in the repository rather than in release tags.

It ships as a pre-commit hook, not just a command

Two files at the repository root decide how the tool is meant to be used.

There is a `.pre-commit-config.yaml` and a `.pre-commit-hooks.yaml`. The second is the manifest that tells pre-commit what the hook is called and how to invoke it, which means this project is designed to be consumed by pre-commit out of the box rather than by wiring a script into a Makefile yourself.

That choice changes the shape of an upgrade. Running django-upgrade once across a codebase produces a large commit that nobody can review. Running it as a pre-commit hook means the rewrite happens on files as they are touched, so the diff for each change is small, lands with the change that prompted it, and can be reverted with it.

For a project that has been sitting on an old Django version for years, that is the difference between an upgrade that happens and an upgrade that is postponed indefinitely. There is a badge in the README confirming the pre-commit integration is enabled.

The other pre-commit-adjacent file at the root is `.editorconfig`, alongside `.typos.toml`, which configures a spell checker. Both are small signals that the repository treats its own source as something a contributor should not have to think about formatting on.

The build and documentation tooling is worth one more note. The build backend is `uv_build` with a version constraint on `uv-build`, the tests and docs are declared as dependency groups, and `uv.lock` sits at the root, so the whole project is built, tested and documented through uv with a locked dependency set.

Coverage, tests and two history files

The badges in the README and the dependency groups in pyproject describe the same project from two sides.

There is a coverage badge claiming one hundred percent, with a link to the continuous integration run. A codemod that rewrites source code can claim that honestly only because the logic is small and branch-complete: there is one behaviour per rewrite rule, and a test asserts the before and after text of each. There is no I/O to mock, no database and no framework to configure.

The test group lists `coverage[toml]`, `pytest>=9` and `pytest-randomly`. That last one is a deliberate choice rather than a default: randomising test order catches tests that depend on each other's state, which in a project whose inputs are source strings is a real failure mode if any global caches exist.

Two history files sit at the root, `CHANGELOG.rst` and `HISTORY.rst`. Having both is unusual and suggests they serve different readers: a changelog for what changed in each release, and a longer history that reaches further back for people looking for when a specific rewrite was introduced or removed.

The documentation group lists `furo`, `sphinx` and `sphinx-copybutton`, so the documentation site is a Sphinx project published to Read The Docs, with the Furo theme and a copy button for code samples.

The last push was on 2026-10-05, and the licence is MIT, authored by Adam Johnson.

Editorial conclusion

Adopt django-upgrade when you have a long-lived Django project with a deprecation backlog, since the work it removes is mechanical and the pre-commit hook lets you keep the project on the current idioms one commit at a time. Skip it if your codebase is small enough that a search and replace covers it. Verify first that your Python version meets the 3.11 floor of the current release, since running the tool is how you discover which rewrites it applies to your code.

Frequently asked questions

How do I install django-upgrade?

The package is on PyPI as `django-upgrade` at version 1.33.0 and installs a single console script named `django-upgrade`. Full usage instructions are on the Read the Docs site rather than in the README.

Does django-upgrade need Django installed?

No. The runtime dependency list contains only `tokenize-rt>=4.1`, so the tool rewrites source without importing Django, which is what lets it work on a project you are trying to upgrade away from an old version.

Can django-upgrade run as a pre-commit hook?

Yes. The repository ships `.pre-commit-hooks.yaml` as well as `.pre-commit-config.yaml`, so the hook is described for pre-commit and can be applied to files as they change rather than in one large commit.

Official sources

  1. adamchainz/django-upgrade on GitHub
  2. Issues
  3. License: MIT
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/adamchainz-django-upgrade.svg)](https://hysenlabs.com/projects/adamchainz-django-upgrade)

Was this useful?