What a package declares

Link to this section

Two things a maintainer publishes about a package decide an upgrade:

  • Framework :: Django :: 5.2 A classifier. It names a Django version the package supports.
  • Django>=4.2,<6.1 The Django requirement. It decides which versions the package installs with. An upper bound like this one excludes 6.1.

Together they give every Django version one of three states: declared, excluded or not declared. Only an exclusion blocks. Not declared means nobody has said anything. Usually a classifier is missing, sometimes nobody maintains the package any more.

What the 200 most downloaded Django packages declare

Link to this section

To see how that looks in practice, I checked the newest release of the 200 most downloaded Django packages against Django 5.2, 6.0 and 6.1 on 28 September 2026.

Newest release of the 200 most downloaded Django packages, as of 28 September 2026
DjangoDeclaredNot declaredExcluded
5.2 LTS125 750
6.085 1132
6.129 1665

Downloads are those of the 30 days before 1 September 2026. The rules are those of django-upgrade-report 0.2.2, described in its README. Downloads are not installations, and the packages were selected by name, so a few Django packages with an unusual name are missing. The raw data is listed under sources.

Django 5.2 LTS: nothing excludes it

Not one of the 200 packages excludes 5.2. The 62 % that declare it account for 81 % of the downloads. If you are still on Django 4.2, the packages are not what stops you. They are a reason not to wait, though: for 30 of the 200, the newest release no longer installs on 4.2, among them Django REST framework, django-filter and django-debug-toolbar. Every month on 4.2 holds more packages back on old releases.

Django 6.1: mostly classifiers that lag behind

Django 6.1 came out on 5 August 2026. So far 29 packages declare it and 5 exclude it. 53 declare 6.0 but not yet 6.1, pytest-django, django-cors-headers and whitenoise among them. Of the 48 packages with a release since 5 August, half declare 6.1. That looks like lag, not like incompatibility.

The real blockers are few, but widely used

These five packages exclude 6.1 in their newest release. If you use one of them, you wait for a release, run a fork or replace it:

  • django-celery-beatDjango<6.1, 7.6 million downloads a month
  • django-prometheusDjango<6.1, 4.2 million downloads a month
  • drf-extensionsDjango<6.0, 2.4 million downloads a month
  • django-scim2Django<6.1, 2.2 million downloads a month
  • django-postgres-extraDjango<6.0, 0.4 million downloads a month

The quiet risk: packages nobody maintains

37 of the 200 packages have had no release in more than two years. Every one of them is among the 75 that do not declare 5.2, django-model-utils with 4.9 million downloads a month, djangorestframework-csv and django-ratelimit among them. Such a package may keep working. If it breaks on the next version, though, nobody fixes it. Then the upgrade needs a replacement, and a replacement needs time.

The order matters as much as the answer

Link to this section

A package that supports the target version has one of two places. If the release that declares the target still runs on your current Django, you update it first, on its own, and it goes live before Django changes. If that release has dropped your current Django, it has to come in the same change as Django.

And the newest release is often the wrong one. The newest django-debug-toolbar, 8.0, requires Django 5.2. On 4.2 the right step is 5.1.0, the oldest release that declares 5.2. Small steps like this keep every change easy to review.

This is what it looks like for an example project on Django 4.2.7 with 18 Django packages, planned for 5.2:

Excerpt from the plan for an example project, Django 4.2.7 to 5.2
PackageTodayStepNote
django-crispy-forms2.02.4first, before crispy-bootstrap5
crispy-bootstrap50.72025.4requires django-crispy-forms 2.3 or newer
django-allauth0.57.065.7.0requires Django 4.2.16 or newer: update Django 4.2 first
django-debug-toolbar4.2.05.1.0requires Django 4.2.9 or newer
django-storages1.14.2check by handnewest release declares Django up to 5.1
django-model-utils4.3.1check by handnewest release declares Django up to 5.1, no release in two years

In total: 15 packages to update before Django, 3 to check by hand, none blocked. Two of the updates need a newer patch release of Django 4.2, so the first step is an update within 4.2.

Check your own project

Link to this section

I turned this check into a free, open-source tool: django-upgrade-report. It reads your lockfile or requirements, asks PyPI what every Django package declares and prints the plan: blocked, update first, update together with Django, check by hand, ready. In the project directory, one command is enough:

uvx django-upgrade-report

It needs Python 3.10 or newer, but neither Django nor your virtualenv. Only package names and versions go to PyPI, never your code. Packages from git or a private index are not looked up at all. It also runs in CI, with Markdown, JSON or an HTML report.

django-upgrade-report on GitHub

It shows what maintainers declare, not whether your tests pass. Plan with it, then let django-upgrade rewrite the code and let the tests decide.