{"slug": "django-django-upgrade-1-32-0-out-now-with-44-ai-assisted-bug-fixes", "title": "Django: django-upgrade 1.32.0 out now, with 44 AI-assisted bug fixes", "summary": "Adam Johnson released django-upgrade 1.32.0, which fixes 44 bugs found and fixed by Claude Fable across two rounds of self-directed bug discovery using the prompt \"Find and fix bugs.\" The fixes include correcting the django_urls fixer's handling of literal angle brackets and unescaped dots in url()-to-path() conversions, and a long-standing error where the unescape_entities fixer rewrote calls to html.escape() instead of html.unescape(). Johnson said the bottlenecks were his review capacity and GitHub Actions CI runs per commit, and concluded the bar for software quality should be raised given such LLM power.", "body_md": "# Django: django-upgrade 1.32.0 out now, with 44 AI-assisted bug fixes\n\n[django-upgrade](https://github.com/adamchainz/django-upgrade) is my tool for automatically upgrading your project code for new Django versions. It rewrites your Python files to fix deprecations and adopt some new features, taking a chunk of the monotony out of upgrading between Django versions.\n\nYesterday, I released [version 1.32.0](https://django-upgrade.readthedocs.io/en/latest/changelog.html), which fixes 44 bugs. Some are big, some are small, and all of them were found by Claude Fable, with this simple prompt:\n\nFind and fix bugs\n\nYup, that’s it. Across two rounds of self-directed bug discovery, Claude found and fixed these bugs, matching the coding style and changelog entries. It needed one more prompt to split the fixes into individual commits.\n\nI am pretty astounded at how well this little project worked. Claude worked “in the cloud”, while I was doing other stuff, so the bottlenecks to progress were my review capacity and CI runs on GitHub Actions for each commit.\n\nMy big takeaway is that given such LLM power, the bar for software quality should be raised.\n\nLet’s review some of the bugs that it fixed, within the individual code fixers in django-upgrade.\n\n## Bad `url()` to `path()` conversions\n\nThe `django_urls` fixer converts old `url()` calls, with their regular expression patterns, into [`path()`](https://docs.djangoproject.com/en/stable/ref/urls/#path) calls with the newer route syntax, where possible.\n\nClaude found two cases where the “where possible” condition was too optimistic.\n\nFirst, literal angle brackets. In a regular expression, `<` and `>` are literal characters, but in `path()` route syntax they declare parameters. The fixer copied them through unchanged:\n\n```\n-    url(r\"^go/<page>/$\", views.redirect_angle),\n+    path(\"go/<page>/\", views.redirect_angle),\n```\n\nThe old pattern matched only the exact URL `/go/<page>/`, angle brackets included. The new route matches `/go/anything/` and passes `page` as a keyword argument to the view, which likely isn’t expecting it. And if the bracketed text isn’t a valid Python identifier, like `<not-a-name>`, Django instead raises `ImproperlyConfigured` at startup. [PR #715](https://github.com/adamchainz/django-upgrade/pull/715) made the fixer skip such patterns.\n\nSecond, unescaped dots. In a regular expression, a bare `.` matches any character, whilst `\\.` matches only a literal dot. The fixer treated both the same, converting:\n\n```\n-    url(r\"^report.pdf$\", views.report),\n+    path(\"report.pdf\", views.report),\n```\n\nThe old pattern also matched URLs like `/reportxpdf`, so the conversion silently narrowed which URLs the pattern matches. Most such patterns contain a “latent bug” where the author meant `\\.`, but it’s not django-upgrade’s place to change behaviour. [PR #710](https://github.com/adamchainz/django-upgrade/pull/710) made the fixer leave patterns with unescaped dots alone.\n\nThese two bug fixes have hopefully closed a loop for me on a client project. Last year, I tried applying django-upgrade to a large client project, and it failed some tests. In my investigation, I cut down the fixers being applied to a short list, including `django_urls`, and still some tests failed. I reached the suspicion that some URLs were being converted incorrectly, but I ran out of time to properly investigate. Now a bot has found and fixed these bugs without me even trying, I’ll be trying that upgrade again!\n\n## Escaping backwards\n\nDjango 3.0 deprecated `django.utils.text.unescape_entities()` in favour of Python’s [`html.unescape()`](https://docs.python.org/3/library/html.html#html.unescape).\n\nBack in version 1.2.0 (2021), I added the `unescape_entities` fixer to django-upgrade to rewrite calls to the new function. But I accidentally made it rewrite calls to the inverse function, `html.escape()` instead of `html.unescape()`:\n\n``` python\n-from django.utils.text import unescape_entities\n+import html\n\n-text = unescape_entities(raw)\n+text = html.escape(raw)\n```\n\nWoops! You can see how the results would vary:\n\n```\n>>> html.unescape(\"Tom &amp; Jerry\")\n'Tom & Jerry'\n>>> html.escape(\"Tom &amp; Jerry\")\n'Tom &amp;amp; Jerry'\n```\n\n😬\n\n[PR #684](https://github.com/adamchainz/django-upgrade/pull/684) was the bug fix to correctly rewrite code to use `html.unescape()`.\n\nThis fixer survived nearly five years of use, perhaps through a combination of few projects activating it, no one noticing when it broke their code, and users potentially working around the issue by disabling the fixer. I’m glad Claude could spot the obvious error.\n\n## `TestCase.multi_db = False` fixer blocking all database queries\n\nDjango 2.2 replaced the test case attributes `allow_database_queries` and `multi_db` with [`databases`](https://docs.djangoproject.com/en/stable/topics/testing/tools/#django.test.SimpleTestCase.databases).\n\ndjango-upgrade rewrites those old attributes to the new one, but it mapped `multi_db = False` to an empty list:\n\n```\n class OrderTests(TestCase):\n-    multi_db = False\n+    databases = []\n```\n\nThat looks sensible at first glance, but it’s wrong. Under Django’s deprecation shim, `multi_db = False` still allowed queries against the default database. The rewritten `databases = []` blocks queries against *all* databases, breaking previously-working tests with `DatabaseOperationForbidden` errors.\n\n[PR #727](https://github.com/adamchainz/django-upgrade/pull/727) corrected the mapping:\n\n```\n class OrderTests(TestCase):\n-    multi_db = False\n+    databases = [\"default\"]\n```\n\nThis is another bug I introduced in version 1.2.0. I guess no one hit this code path, since it would trigger an obvious test case breakage. But with open source, it is hard to know how many folks will actually make a bug report.\n\n## Bar height++\n\nThese bug fixes are a subset of the 44 in the release—see the [changelog](https://django-upgrade.readthedocs.io/en/latest/changelog.html) for the full list. There are definitely some more bugs lurking, but for now I’m out of time and energy for django-upgrade. I even left some harder-to-review bug fixes in draft PRs for my next pass at the project.\n\nBut yeah, since bugs like these are now fairly cheap to find, I hope that the software quality bar goes up. Applying fixes still requires some vigilance in review and checks from deterministic tools like linters, but good projects generally already apply such tools. And while it can be hard to trust LLMs to build features, where they often generate large piles of code, this genre of small, targeted bug fixes are an easy win.\n\nTry my prompt on your own project. Here it is again:\n\nFind and fix bugs\n\nRead my book [Boost Your Django DX](https://adamchainz.gumroad.com/l/byddx), freshly updated in November 2024.\n\nOne summary email a week, no spam, I pinky promise.\n\n**Related posts:**\n\n- [Introducing django-upgrade, a tool for upgrading your Django projects](https://adamj.eu/tech/2021/09/16/introducing-django-upgrade/)\n- [django-upgrade Mega Release 1.11.0](https://adamj.eu/tech/2022/10/26/django-upgrade-mega-release/)\n- [django-upgrade release with Django 4.2 fixers](https://adamj.eu/tech/2023/02/17/django-upgrade-release-django-4.2/)\n\n**Tags:** [django](https://adamj.eu/tech/tag/django/)", "url": "https://wpnews.pro/news/django-django-upgrade-1-32-0-out-now-with-44-ai-assisted-bug-fixes", "canonical_source": "https://adamj.eu/tech/2026/08/18/django-upgrade-1.32.0-44-bug-fixes/", "published_at": "2026-08-17 23:00:00+00:00", "updated_at": "2026-09-16 16:44:37.679218+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-agents"], "entities": ["django-upgrade", "Adam Johnson", "Claude Fable", "Django", "GitHub Actions", "html.unescape()", "html.escape()", "django.utils.text.unescape_entities()"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/django-django-upgrade-1-32-0-out-now-with-44-ai-assisted-bug-fixes", "markdown": "https://wpnews.pro/news/django-django-upgrade-1-32-0-out-now-with-44-ai-assisted-bug-fixes.md", "text": "https://wpnews.pro/news/django-django-upgrade-1-32-0-out-now-with-44-ai-assisted-bug-fixes.txt", "jsonld": "https://wpnews.pro/news/django-django-upgrade-1-32-0-out-now-with-44-ai-assisted-bug-fixes.jsonld"}}