Skip to content

Upgrade Django to 6.1.3 - #4858

Merged
marcoacierno merged 3 commits into
mainfrom
upgrade-django-6.1
Nov 9, 2026
Merged

marcoacierno merged 3 commits into
mainfrom
upgrade-django-6.1

Conversation

@marcoacierno

@marcoacierno marcoacierno commented Nov 8, 2026 •

Copy link
Copy Markdown
Member

Summary

Upgrades Django from 5.2.18 to 6.1.3, together with the dependency bumps that Django 6.1 requires.

Companion upgrades

Package From To Why
wagtail 7.3.4 8.1 Wagtail 7.3 and 7.4 LTS support Django only up to 6.0. 8.x is the first line that supports 6.1.
wagtail-localize 1.13 1.14.5 Supports Wagtail 8 and Django 6.1
django-timezone-field 7.0 7.2.2 7.0 pins Django<6.0
django-autocomplete-light 3.9.4 4.0.3 3.9.4 imports django.utils.itercompat, which Django 6.0 removed. None of the v4 breaking changes affect us: we only use dal, dal_select2, Select2QuerySetView and dal_admin_filters.
django-ses 4.1.1 4.9.0 Django 6.1 support arrived in 4.8.0. Prod uses django_ses.SESBackend.

Wagtail 8.1 also brings in django-ninja and swapper, and bumps draftjs-exporter and modelsearch.

Code and config changes

  • Added django.contrib.postgres to INSTALLED_APPS. Without it, Django 6 raises postgres.E005 for wagtailsearch's SearchVectorField/GinIndex.
  • Fixed a crash in /admin/voting/vote/. dal-admin-filters (1.1.0, the latest release) calls ForwardManyToOneDescriptor.get_queryset() with no arguments, but Django 6 requires instance=. SubmissionFilter now resolves the queryset from the model field. Regression test: test_vote_changelist_filters_by_submission.
  • Fixed a crash in the mark_speakers_to_receive_vouchers admin action (FieldError). Django 6.1 changelists now apply select_related() with only the related fields from list_display, where 5.2 used a bare select_related(). The action called .only("conference_id") on that queryset, which defers submission while it is still being traversed. The existing test now runs the action on the changelist queryset, which is what the admin passes to actions.
  • CI Postgres service: 14.5 → 18.6 in backend-test.yml and claude.yml. Django 6.1 refuses to connect to PostgreSQL < 15. This matches docker-compose (18.6), and prod RDS already runs 18.3.

Deploy notes

  • New third-party migrations will run on deploy:
    • wagtailadmin.0006_formstate
    • wagtailcore.0098_apitoken
    • wagtailsearch.0010_add_text_fields, which adds two NOT NULL text columns to the search index table. Its raw SQL only runs on rollback.
    • All three migrations are reversible.
  • Wagtail 8.0 stops converting AVIF/WebP renditions to PNG by default. Restoring the old behaviour would need WAGTAILIMAGES_FORMAT_CONVERSIONS.
  • Wagtail 8.1 sitemaps include all locales when WAGTAIL_I18N_ENABLED. We don't use wagtail.contrib.sitemaps, so this doesn't affect us.
  • Django 6.1 changes cache keys for pages that vary on headers. Expect one cache miss per varied page after deploy.

I grepped the codebase for APIs that Django 6.0/6.1 and Wagtail 8.0/8.1 removed or changed and found no usages.

Follow-ups (not in this PR)

The upgrade adds new RemovedInDjango2028Warnings to the test run:

  • EMAIL_BACKEND → MAILERS setting migration
  • get_connection() / connection= in notifications/tasks.py

These need a separate change to the SES email path. There is also a warning from django-taggit internals (quote_name_unless_alias). We can't fix that one yet because Wagtail 8.1 caps django-taggit<7.

Test plan

  • manage.py check: no errors. The remaining warnings (fields.E010 on I18n fields, staticfiles.W004) were already there.
  • manage.py makemigrations --check: no changes
  • manage.py graphql_schema: schema.graphql unchanged
  • Full backend suite in Docker: 1370 passed, 15 skipped, 3 xfailed
  • Local smoke test in Docker with headless Chromium:
    • Server boots on Django 6.1.3 and the Wagtail migrations apply.
    • Every Django admin changelist (69), plus the first change form and add form for each: 158 pages, all 200.
    • Wagtail 8.1 dashboard, page explorer (new /cms-admin/api/explorer/ endpoint), page editor + preview panel, images, documents, snippets, site settings, locales, redirects, forms, workflows, reports: all render.
    • GraphQL responds.
  • Local write flows through the Wagtail UI. All test content was deleted afterwards through the UI as well.
    • Pages: create a page, save a draft, publish, edit, republish. Autosave and editing-session pings work. Draftail rich text is converted server-side by draftjs-exporter 7.3. The new page is indexed in the search index, which uses the new title_text column.
    • Republished the existing homepage, so existing StreamField content still validates.
    • wagtail-localize: added an it locale synced from English, submitted the page for translation, machine-translated it (OpenAI), and published it in Italian. cmsPage returns both the English and the Italian content over GraphQL.
    • Image upload works, and so do fill/WebP/AVIF renditions. Document upload and serving work.
    • Deleted the pages, the locale, the image and the document.
  • Local Django admin tests: created data through the admin UI and deleted it all afterwards, also through the admin.
    • Conferences: created a conference with deadlines, a filter_horizontal field and split datetimes. Edited the deadlines: changed one, deleted one, and added one with "Add another". Both deadline validation errors render. The schedule builder, grants summary and video-mapping pages load. The clone action ran through the Resonate workflow on a worker running Django 6.1.
    • Every admin model with data, 25 in total: opened the change form and saved it unchanged. Nothing failed, and no field values changed. The only differences were microseconds dropped by the split datetime widget and \r\n newlines from textareas, both of which also happen on 5.2.
    • Changelists: search, sorting, filters (141 filter links) and facets. History pages and delete confirmation pages. Admin and DAL autocomplete. All custom admin URLs: email template and sent email views, review session pages, email speakers, export attendees, merge tags, user password change, visa documents. Ordered-model move up/down. CSV export of votes and ranking. delete_selected, including the "protected objects" page.
    • Admin actions on test data, through the admin endpoint. All email actions went out through the Django 6.1 mail path (console backend). Also ran the status/pending-status actions, voucher creation, mark_as_failed and the tag form. I skipped the actions that call Pretix or YouTube.
  • Sanity-check the Wagtail admin on staging after deploy

Existing bugs found while testing (they also happen on main with Django 5.2 and are not changed by this PR)

All of these are fixed in #4859.

  • Deleting a conference that has grants raises RecursionError. Grant.__init__ reads status, pending_status and country_type. When the deletion collector loads grants with .only(), each read reloads the row and starts the same thing again. Reproduced with Grant.objects.only("id").first() on both 5.2.18 and 6.1.3.
  • Grant CSV export returns a 500. GrantResource.export_field(self, field, obj) does not accept the **kwargs that django-import-export 4.x passes (export_fields).
  • Deadline inline returns a 500 on an invalid or empty start/end date. validate_deadlines_form reads cleaned_data["start"], which raises KeyError when that field failed validation.
  • Changelist search returns a 500 on files_upload.File and VideosImportRequest. Their search_fields list plain foreign keys (uploaded_by, conference), and Django can't do an icontains lookup on those.
  • Several admin pages call Pretix and return a 500 when Pretix returns an error. This covers the grants and submissions changelists, the schedule invitations list and export, and create_grant_vouchers. They call Pretix row by row without handling the error, so a conference without a Pretix event breaks them.
  • The Resonate worker stops receiving work after 5 idle minutes. Its SSE connection gets deregistered and it never reconnects, so workflows stay pending until the worker restarts. resonate-sdk is not changed by this PR.

Known issue, not caused by this PR: on the voting changelist, the DAL "By Submission" filter renders as a plain <select> and Select2 never starts. select2.full.js loads after admin/js/jquery.init.js has removed the global jQuery. The script order, Django's vendored Select2, and DAL's JS are the same on Django 5.2 / DAL 3.9.4, so the same thing happens on main.

Django 6.1 forces a few companion upgrades:

- wagtail 7.3.4 -> 8.1: Wagtail 7.3 and 7.4 LTS only support up to
  Django 6.0; 8.x is the first line supporting 6.1.
- wagtail-localize 1.13 -> 1.14.5 for Wagtail 8 / Django 6.1 support.
- django-timezone-field 7.0 -> 7.2.2: 7.0 pins Django<6.0.
- django-autocomplete-light 3.9.4 -> 4.0.3: 3.9.4 imports
  django.utils.itercompat, removed in Django 6.0.
- django-ses 4.1.1 -> 4.9.0: Django 6.1 support landed in 4.8.0.

Add django.contrib.postgres to INSTALLED_APPS: Django 6 raises
postgres.E005 for wagtailsearch's SearchVectorField/GinIndex otherwise.

Bump the CI postgres service from 14.5 to 18.6 (matching docker-compose;
prod RDS runs 18.3): Django 6.1 refuses to connect to PostgreSQL < 15.
@vercel

vercel Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
pycon Error Error Nov 9, 2026 2:01am UTC

@claude

claude Bot commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

No issues found.

@codecov

codecov Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.44%. Comparing base (2d2173c) to head (e3e4252).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4858      +/-   ##
==========================================
+ Coverage   92.43%   92.44%   +0.01%     
==========================================
  Files         360      360              
  Lines       10938    10938              
  Branches      949      949              
==========================================
+ Hits        10110    10112       +2     
+ Misses        706      703       -3     
- Partials      122      123       +1     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

dal_admin_filters calls the FK descriptor's get_queryset() with no
arguments, but Django 6 requires the `instance` keyword argument, so
/admin/voting/vote/ raised a TypeError. dal-admin-filters 1.1.0 is the
latest release, so resolve the queryset from the model field instead.
@claude

claude Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Upgrades Django 5.2→6.1, Wagtail 7.3→8.1, django-autocomplete-light 3→4, django-ses, django-timezone-field and wagtail-localize. Also moves CI Postgres to 18.6, adds django.contrib.postgres to INSTALLED_APPS, and works around dal_admin_filters in voting/admin.py.

  • Production Postgres version: CI and docker-compose now use 18.6, but nothing in this PR covers the production database. Confirm prod is on a compatible version, or upgrade it first. A Postgres 14→18 major upgrade needs pg_upgrade or a dump/restore.
  • SubmissionFilter.get_queryset_for_field: the override reaches into _meta.get_field(name).related_model._base_manager. It sidesteps a dal_admin_filters incompatibility with Django 6, and dal-admin-filters==1.1.0 is still pinned. Check whether a newer release fixes this, so the override can be removed instead of kept.
  • django.contrib.postgres in INSTALLED_APPS: the PR doesn't say why it's needed. reviews/adapters.py imports from it without the app being installed. If it was added to satisfy a Wagtail 8 or modelsearch requirement, say so. Otherwise drop it.
  • Test coverage: the only new test covers the admin filter. The major bumps (dal 4's autocomplete views in submissions/views.py, Wagtail 8 and wagtail-localize 1.14 for the CMS, and Django 6's removals/deprecations) have no targeted tests. Please confirm the full suite passes, and that makemigrations --check reports no pending migrations from the Wagtail or Django upgrades.

Django 6.1's changelist applies select_related() with the related fields
from list_display instead of an unrestricted select_related(). Calling
.only("conference_id") on that queryset defers `submission` while it is
still being traversed, so the action raised FieldError from the admin.

The test now runs the action on the changelist queryset, which is what the
admin passes to it.
@marcoacierno
marcoacierno merged commit c479d5a into main Nov 9, 2026
8 of 9 checks passed
@marcoacierno
marcoacierno deleted the upgrade-django-6.1 branch November 9, 2026 02:22

This branch had an error being deployed

1 failed deployment
Preview — e3e42520 Deployed Nov 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant