Repository navigation
Releases: derblub/django-upgrade-report
Releases · derblub/django-upgrade-report
Release list
1.1.0
Added
- The footer of the Markdown and HTML reports and the README link the weekly page on how ready the Django ecosystem is.
- The HTML report and the ecosystem page carry a quiet Pushing Pixels signature: the pixel mark next to the name in the footer, in the text colour, and the Pushing Pixels tile as the page icon. Both stay single files.
Fixed
- The README has a table of contents, and its links to its own sections now also work on PyPI.
Full changelog: v1.0.0...v1.1.0
1.0.0
The GitHub Action moves to @v1; @v0 stays on 0.4.0.
Added
- A weekly page on how ready the Django ecosystem is: the newest release of the 300 most downloaded Django-related packages, judged against every Django version from 4.2 on with the report's rules, the share ready, to check and blocked per version, and how fast packages caught up after each release. Built by
ecosystem/build.pyoutside the package and published to GitHub Pages by a workflow. - Several projects in one report: pass more than one
PROJECT, or--recursiveto find them under a directory (a monorepo, a folder of services). An overview shows each project's step and counts, the packages that block more than one project, and the upgrades several projects share; each project's report follows, folded in Markdown. Projects share one package index client, a project that cannot be checked does not stop the others (exit 2), and--fail-onlooks at all of them. The HTML report links each project from the overview, and the JSON report has"kind": "multi". The GitHub Action takes several paths, one per line, andrecursive; its outputs, comment and tracking issue cover every project. --framework wagtailand--framework django-cmsplan the upgrade of Wagtail or django CMS the way the report plans Django's: their own targets and LTS releases, the packages that depend on them, the same sections, commands and bot configurations. Wagtail's major-only classifiers (Framework :: Wagtail :: 6) count for a minor release when the package came out after it. The report says whether the target runs on your Django and, when not, which Django to upgrade to first; plugins nameddjangocms-…count for django CMS. The questions at a terminal and the tracking issue name the framework too. The JSON report hasframework,current_frameworkandframework_requires_python, and the GitHub Action takesframework.- The report lists what Django removed on the way to the target, from its release notes, and where your code still uses it ("The model's Meta.index_together option is removed shop/models.py:11 · django-upgrade fixes this"); the others as a link to the release notes, all of them with
-v. Markdown shows them as a table, the HTML report as its own section, and the JSON report has them underremovals. - A direct dependency your code never names gets the note "not imported or configured in your code: remove it instead?". The project's code is read locally, never imported or sent: imports, dotted paths in settings strings, app lists,
{% load %}in templates andmanage.pycommands in scripts. Anything unsure counts as used. The end of every report lists all direct dependencies the code never names, Django-related or not, under "Possibly unused" (JSON:unused), and--explainsays where the code uses a package. With--python, module names come from the environment.--no-scan-codeturns it off,--scan-code DIRreads another directory. -iopens the report in the terminal: sections and packages on the left, the chosen package on the right with its notes, signs of support, command and links, and keys to search (/), show one status (f), tick packages off (space, kept in.django-upgrade-report/state.jsonin the project), copy the command (e), open the changelog (o), check another target (t) and write the report to a file (w). It needs the newtuiextra (pip install 'django-upgrade-report[tui]', oruvx --with textual django-upgrade-report -i); the tool itself still depends onpackagingonly.- Packages to check show signs of support that their metadata does not give: when the README on PyPI of the release the report names, or of the newest one, names the target ("README of 2.1 mentions Django 5.2"). Signs are notes linked to their source and never change a status; packages to check without any sign come first. With
--evidence(Action:evidence: true), also when the default branch of its GitHub repository tests the target: tox, nox and GitHub workflow matrices are read ("main branch tests Django 6.0 (tox.ini)"), and when a changelog section after your version adds or tests it ("changelog of 1.14.5 mentions Django 5.1 support"). That sends the names of those public repositories to GitHub, never of a package from git, a path or a private index. For blocked packages and ones to check without a sign,--evidencealso lists up to two issues or pull requests in the repository whose title names the target ("open PR: Add Django 5.2 support (#912)", "merged PR: …"), searched through GitHub's API (10 searches a run, 30 withGITHUB_TOKEN). The JSON report has the signs underevidenceand the issues underupstream. --via ltsplans an upgrade across several LTS releases in steps: one report per LTS on the way to the target (--via each: per feature version), each starting where the one before ends, with its upgrades done and Django on the newest patch. A package blocked in one step says so in the later ones,--fail-onlooks at every step, and the JSON report of a path has"kind": "path"with one report per step. Markdown folds each step, the HTML report shows them on one page under an overview with one checklist, and the GitHub Action takesvia(the counts are summed over the steps; the tracking issue lists the tasks per step). The warning about a skipped LTS mentions it.--emit renovateand--emit dependabotprint a configuration that makes the bots follow the plan: Django held on its series until everything that goes first is upgraded, Django in one pull request with the packages that need it, and upgrades that need each other grouped. Each rule says when to remove it.--emit uv(orpoetry,pdm,pipenv,pip,auto) prints the commands that carry out the plan instead of the report: what to upgrade first, one command each in the report's order, upgrades that need each other in one command, then Django with what needs it; dependencies that only come with others are upgraded in the lock. For pip it names the requirement lines to change, with file and line. When the target Django needs a newer Python, the Python upgrades come first. With--format jsonthe commands are in the newcommandsfield, and the HTML report shows each package's command, with a copy button.- The JSON report says on which requirement file line a package is pinned or named:
origin, such as"requirements/base.txt:12". - The report knows which dependencies your project names itself and which only come in through others: from
pyproject.tomlor thePipfilebeside a lockfile, the project entries ofuv.lock(every member of a workspace), and the requirement files themselves. The JSON report hasdirectper package (null when the source does not say), and the HTML report a switch "only direct dependencies". - The HTML report can be searched and filtered: a search field (
/) over names, reasons and notes, a chip per status, "only with notes", and tiles that filter by their status. The filters are kept in the address (#status=blocked&q=allauth), so a filtered view can be passed on;Escresets them, and printing shows every row. A click on a column heading sorts by name, by the major versions a step crosses or by the last release, and every row opens to its links (PyPI, changelog, repository), its newest and last release, and the line to pin with a copy button;j/kgo from row to row andEnteropens one. The page carries the JSON report as data. Without JavaScript it shows everything, and--staticleaves the scripts out. - The GitHub Action can keep one open issue per target with the plan as a task list (
issue: true): each run keeps the ticks people set, ticks off and dates what needs nothing any more, and says once when everything is ready.examples/weekly.ymluses it. - The HTML report is a checklist: every row with something to do has a box to tick, a tile counts what is done, and the ticks are remembered in the browser for that plan (a new plan starts unticked). It prints cleanly.
- The GitHub Action can put the report on the pull request as one comment that it updates on every push (
comment: true), or only when a status or step changed (comment: on-change). Several projects in one repository get one comment each. A comment that cannot be written, as from a fork, never fails the job. --baseline REPORT.jsoncompares the report with an earlier JSON report and starts with what changed: statuses, smaller or bigger steps, packages added or removed, warnings.--only-changesshows nothing else, and nothing when nothing changed, for a weekly job that only speaks up when there is news;--fail-on-change anyorworsefails it. The JSON report has it underchanges, and the Markdown and HTML reports show it first. The GitHub Action takesbaselineandfail-on-changeand reportschanges;examples/weekly.ymlruns it every week against last week's report.- When the target Django needs a newer Python than your project uses, the report starts with what your dependencies need on it, all of them: the ones whose release excludes it or has no wheel for it, with the first newer release that runs on it, the ones no release fixes, how many run on it already, and whether your Django patch release declares that Python ("Django 4.2.7 does not declare Python 3.12, 4.2.8 does").
--python-targetnames another Python or turns it off,--fail-on-pythonfails the run on it, and the JSON report has it underpython. The GitHub Action has the inputspython-targetandfail-on-pythonand the outputpython-blocked. - At a terminal, the report asks for what the project leaves out and that changes it: the Django you run when it is not pinned, a smaller first step when
autowould skip an LTS, and your Python when no file names it. It then says which option gives the same report next time. It never asks in CI, with-o,--explainor a format other than text, or with...
0.4.0
Added
- Packages whose job Django took over say what Django has instead, for example "built into Django 1.7: its own migrations, remove South" or "built into Django 3.1: models.JSONField" for jsonfield and django-jsonfield. They are shown even when their metadata does not mention Django. The list is short and every entry has a source (the maintainers or Django's release notes): South, django-discover-runner, django-secure, django-uuidfield, django-durationfield, django-transaction-hooks, jsonfield, django-jsonfield, django-jsonfield-backport and django-template-partials. The JSON report has it under
built_into_django.
Changed
- A tidier text report. An upgrade row no longer repeats "2.0 declares Django 5.2" next to "1.0 → 2.0", a manual check no longer ends every line with ", not 5.2", the project's Python moves into the line under the headline, and git URLs are shortened to
git github.com/org/fork. - A fork or local package says where it comes from as
from git github.com/org/forkin every format, instead of a longer note; the advice for a blocked fork moved into the section's hint.
0.3.0
Added
- Forks and local packages are judged by what they declare themselves, read locally and never sent anywhere: the installed metadata with
--python, a local directory'spyproject.toml, or the constraints inpoetry.lockandpdm.lock. A fork pinned years ago withDjango<4.1now shows up as blocked instead of only "not checked", and its requirements on other packages count against their upgrades. The JSON report says where such a package comes from in the newsourcefield. When only such packages are blocked, the section says that your copy excludes the target, not that no release supports it.
Changed
- An upper bound whose upload date is unknown reads "may predate 5.2" instead of "released before 5.2".
0.2.4
Changed
- A health check (you already run the target Django) says so in the headline, "Django 6.1 · health check" instead of "Django 6.1 → 6.1", and its "Check manually" hint says that these packages already run on your Django and only their metadata lags behind.
- Why it is a health check is no longer a warning: the JSON report lists it under the new
notices, and the text report shows it as a quiet line under the headline. - Text report: notes go on their own line, aligned under the reason, instead of making the row longer. A note that every package in a section shares is said once for the section. The counts in the summary line are colored by status, and zeros are dimmed.
0.2.3
Changed
- Much less load on PyPI: for a pinned dependency, the small metadata of the installed release is fetched first, and the whole release history only for Django-related packages. On Saleor's lockfile that is 18 instead of 224 full fetches, 6.5 instead of 122 MB, with the same results.
- At most 8 requests in flight at once instead of 16, and four attempts instead of three, with growing pauses.
Fixed
- One package the index could not answer for, such as an HTTP 503 from PyPI, stopped the whole run. It is now reported as "Could not check" and the rest of the report is shown;
--fail-onexits with 2 because the report is incomplete. The JSON report lists them undernot_checked.
0.2.2
0.2.1
Changed
- "Upgrade first" lists packages in the order their notes require: an upgrade that needs another one first comes after it.
- The search for the first release that declares the target bisects instead of scanning, so long release histories cost far fewer requests.
Fixed
- Upgrades that each need the other first told you to upgrade either one first. They are now marked as going together, in the same change.
- An upgrade that another installed package forbids stayed under "Upgrade first". It is now "Check manually", with the conflict in the notes.
- Planning the upgrades re-read every requirement of every installed package for each upgrade, which made large projects slow.
- Index metadata with values that are not text crashed the run.
- A package index that knows no Django as new as your project's (a mirror that stopped syncing) gave contradictory warnings. It is now an error that says so.
- A download link that does not name its package was listed under a guessed name. It is now listed under its URL.
0.2.0
First release on PyPI. Find out which of your dependencies block a Django upgrade, and in which order to upgrade them.
uvx django-upgrade-reportAdded
--target auto, the new default: the newest LTS above your Django, or the newest release when no LTS is above it.--fromsets the Django version you run when your requirements only give a range.- The project's Python is read from
.python-version,uv.lock,pyproject.tomlorPipfile.lock. The report warns when the target Django needs a newer Python. - Packages from git, local paths, URLs or a private index are listed as "Not from PyPI, not checked" and their names are never sent to PyPI.
--check-private-on-pypilooks them up anyway, for an index that mirrors PyPI. A project-wide index counts too:--index-urlor--no-indexin requirement files,[tool.uv]and PDM index settings, and the pip and uv index environment variables. - Warnings when the target skips an LTS, when your Django requirement excludes the target, and when Django is not pinned.
- Notes when a release needs a newer patch of your current Django, or a newer version of another package you pin. Upgrades that depend on one going with Django go with Django, too.
- Packages marked
Development Status :: 7 - Inactiveare flagged. Packages built only on Wagtail or django CMS are included, with a note. schema_versionand documented fields in the JSON report.- Action inputs
fromandcheck-private-on-pypi, and outputsblocked,upgrade,checkandready. The action caches PyPI responses and runs on Windows runners. - A release workflow that publishes to PyPI with trusted publishing and moves the
v0tag, souvx django-upgrade-reportanduses: derblub/django-upgrade-report@v0work. - Exit status 2 for every error, so it cannot be mistaken for
--fail-on's status 1.
Changed
- The default target was
lts, which gave a downgrade report for projects on Django 6.0 or 6.1.ltsnow shows a health check of your version when your Django is newer. - An upper bound such as
Django<6.0only counts as support when the release came out after the target. For a target that is not released yet, only classifiers count. - Unknown targets (
5.3,52) and targets below your Django are an error instead of an empty report. - Unpinned requirements are judged by the newest release they allow, not the newest release overall.
- Environment markers are evaluated for your project's Python on Linux, not for the machine running the tool, and all Django requirement lines that apply are combined.
Fixed
- Exact pins such as
Django==5.2.17were read as excluding the version they pin. - A package was "Blocked" when only its newest release excluded the target, even though yours or an older one allowed it. It is now "Check manually".
- Only the newest 40 releases were searched, so the suggested release was often not the smallest one.
- Major-only classifiers such as
Framework :: Django :: 5were ignored. - uv's forked resolutions, and duplicate entries in
poetry.lockandpdm.lock, used the last entry instead of the one for your Python. - Pins from
-cconstraint files and-rincludes were overwritten by later unpinned lines, and constraint-only packages were reported as dependencies. Laterpyproject.tomltables overwrote earlier pins, and Poetry dependencies with several constraints were dropped. - Requirement files with a UTF-8 BOM lost their first line, and UTF-16 files crashed. Lockfiles and TOML are read as UTF-8 whatever the locale.
- An empty lockfile gave an all-clear report instead of an error.
--pythonreported a shadowed copy of a package installed twice, and failed on Python 3.7 and older environments.- Tracebacks for unreadable files,
--pythonfailures, a bad--index-url(which printed its credentials) or-ointo a missing directory. Missing directories are now created. - Reports are written as UTF-8, so
-oand redirected output no longer crash on Windows. - Timeouts, connection resets, HTTP 429 and truncated answers from the index are retried.
- Ready packages hid their notes, such as "version not pinned", in text and Markdown output.
- An installed version missing from the index was shown as ready, without a note.
- The Markdown report left out packages not on the index, and
*in version specifiers turned into emphasis. - The progress counter never reached its total.
- The action failed on Windows runners, and a second use in one job overwrote the first JSON report.
- The action and CI used Node 20 actions, which GitHub runners no longer run.
--fail-onpassed when every Django-related package came from another index, as soon as one unrelated package came from PyPI.