From 8c5b7fbd066fc22ddbf3590b1fc76959be4d967e Mon Sep 17 00:00:00 2001 From: sriramveeraghanta Date: Fri, 26 Jun 2026 21:27:54 +0530 Subject: [PATCH] =?UTF-8?q?chore(api):=20upgrade=20Django=204.2=20?= =?UTF-8?q?=E2=86=92=205.2=20and=20bump=20Django=20ecosystem=20deps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upgrade Django 4.2.30 LTS → 5.2.15 LTS and bump all Django-coupled dependencies to versions that officially support 5.2 (DRF 3.17.1, channels 4.3.2, django-cors-headers 4.9.0, django-filter 25.2, django-storages 1.14.6, django-redis 7.0.0, celery 5.5.3, django-celery-beat 2.9.0, django-celery-results 2.6.0, drf-spectacular 0.29.0, scout-apm 3.5.3, psycopg 3.3.4, whitenoise 6.12.0, django-debug-toolbar 6.0.0, pytest-django 4.12.0). OpenTelemetry set, django-crum and pytz held (already 5.2-compatible). Code changes the upgrade required: - urls.py: gate the debug-toolbar URL include on apps.is_installed(), since django-debug-toolbar 6.0 ships a model that errors when the app isn't in INSTALLED_APPS (test settings run DEBUG=True but don't install it). - migration 0122: state-only AlterField for three M2M fields using through_fields (Django 5.1 deconstruction normalization); sqlmigrate is a no-op, zero DB impact. - test_authentication.py: module-level autouse cache.clear() fixture to fix 8 pre-existing throttle test-isolation failures (identical on the 4.2 baseline) so the suite is green. Verified on python:3.12-alpine + Postgres 15.7: check clean, makemigrations --check clean, full migrate applies, pytest 393 passed. Adds the migration plan/audit write-up under apps/api/docs/. --- apps/api/docs/django-5.2-migration-plan.md | 218 ++++++++++++++++++ ...ssignees_alter_issue_assignees_and_more.py | 29 +++ .../tests/contract/app/test_authentication.py | 16 ++ apps/api/plane/urls.py | 3 +- apps/api/requirements/base.txt | 34 +-- apps/api/requirements/local.txt | 2 +- apps/api/requirements/test.txt | 2 +- 7 files changed, 284 insertions(+), 20 deletions(-) create mode 100644 apps/api/docs/django-5.2-migration-plan.md create mode 100644 apps/api/plane/db/migrations/0122_alter_draftissue_assignees_alter_issue_assignees_and_more.py diff --git a/apps/api/docs/django-5.2-migration-plan.md b/apps/api/docs/django-5.2-migration-plan.md new file mode 100644 index 00000000000..0b2527d0efb --- /dev/null +++ b/apps/api/docs/django-5.2-migration-plan.md @@ -0,0 +1,218 @@ +# Django 4.2 → 5.2 Migration Plan (`apps/api`) + +> **Status:** ✅ Executed & verified on branch `chore/django-5.2-upgrade` · **Target:** Django **5.2.15 LTS** from **4.2.30 LTS** +> **Scope:** `apps/api` (the only Django service in the monorepo) · **Strategy:** single coordinated PR, direct 4.2 → 5.2 jump · **Dependency policy:** opportunistic (latest stable, with evidence-based safe overrides) + +--- + +## 1. Executive summary + +The Plane API was audited end-to-end for Django 5.0/5.1/5.2 breaking changes (12 targeted code scans, a full settings audit, a 129-file migration scan, and per-dependency compatibility research). **The application code is already Django 5.2-clean.** Every breaking-change scan came back unaffected. + +The migration is therefore **almost entirely a coordinated third-party dependency bump plus verification** — not a code rewrite. Risk is **low–medium** and concentrated in the dependency layer, not in Plane's own code. + +**No runtime or infrastructure blockers:** + +| Prerequisite | Django 5.2 requires | Plane has | Status | +| ------------ | ------------------- | ------------------------------- | ------ | +| Python | ≥ 3.10 | 3.12.x (Docker + CI) | ✅ | +| PostgreSQL | ≥ 14 (drops PG 13) | 15.7-alpine (all compose files) | ✅ | +| DB adapter | psycopg 3 preferred | psycopg 3.3.0 | ✅ | + +**The only mandatory app-level change** is bumping the dependency pins. Everything else (settings, models, migrations, middleware, templates) already conforms to 5.2. + +--- + +## Execution status — verified ✅ + +Executed on branch `chore/django-5.2-upgrade` and verified in the containerized test harness (`docker-compose-test.yml`: `python:3.12.5-alpine`, Postgres 15.7, Valkey, RabbitMQ, MinIO). + +**Verification results (Django 5.2.15):** + +| Gate | Result | +| ------------------------------------------------- | ------------------------------------------------------------------------------ | +| `pip install -r requirements/test.txt` | ✅ resolves, no conflicts | +| `manage.py check` | ✅ "System check identified no issues" | +| `manage.py makemigrations --check --dry-run` | ✅ no changes (after the state-only `0122` migration) | +| `manage.py migrate` (full history on Postgres 15) | ✅ db `0001`→`0122`, `django_celery_beat`, `license`, `sessions` all applied | +| `pytest` (full suite) | ✅ **393 passed, 0 failed** | +| `drf-spectacular` schema gen (0.29) | ✅ generates (15.8k lines); only pre-existing `operationId` collision warnings | + +**Changes made beyond the dependency pins (each surfaced by verification):** + +1. **`plane/urls.py`** — gate the debug-toolbar URL include on `apps.is_installed("debug_toolbar")`. django-debug-toolbar **6.0** ships a real model (`HistoryEntry`); under test settings (`DEBUG=True`, app _not_ installed) the unconditional `include(debug_toolbar.urls)` raised `RuntimeError` during `manage.py check`. The guard makes the include correct in all settings (local: included; test/prod: skipped). +2. **`plane/db/migrations/0122_alter_draftissue_assignees_alter_issue_assignees_and_more.py`** — a **state-only** `AlterField` for three `ManyToManyField`s (`issue.assignees`, `draftissue.assignees`, `module.members`) that use `through=`/`through_fields=`. Django 5.1 normalized M2M `through_fields` deconstruction. `sqlmigrate` confirms **all three operations are `(no-op)`** — zero DDL, zero database impact. Required only so `makemigrations --check` stays green in CI. +3. **`plane/tests/contract/app/test_authentication.py`** — added a module-level autouse `cache.clear()` fixture (mirrors the existing per-class `_clear_state` fixtures). This fixes **8 pre-existing test failures** that are unrelated to the upgrade — verified by running the auth test file on the `preview` baseline (Django 4.2.30), which produced the **identical** "8 failed, 18 passed". Root cause: the per-IP `AuthenticationThrottle` (`10/minute`) stores request history in the shared cache, and `TestMagicSignIn`/`TestMagicSignUp` didn't reset it between tests. + +**Not applied (revised from the original plan):** `FORMS_URLFIELD_ASSUME_HTTPS` — in Django 5.2 this transitional setting emits its **own** `RemovedInDjango60Warning`, and the codebase has no `forms.URLField` to silence, so adding it only adds noise. Reverted. + +--- + +## 2. Current state + +- **Service:** `apps/api` — Django + DRF + Celery (worker/beat) + Channels (ASGI http-only). No other Python/Django service exists in the repo. +- **Django:** `4.2.30` (final 4.2 LTS line; 4.2 reaches EOL ~April 2026 — this upgrade is time-sensitive). +- **Dependency files:** `requirements/base.txt`, `requirements/local.txt`, `requirements/production.txt`, `requirements/test.txt` (plain pip pins; **no lockfile** — edits are direct). +- **Run surfaces:** `bin/docker-entrypoint-{api,worker,beat,migrator}.sh`. All four must be smoke-tested. +- **Tests:** `pytest` + `pytest-django`, settings module `plane.settings.test`, run with `--reuse-db --nomigrations --strict-markers`. +- **CI:** `pull-request-build-lint-api.yml` runs **ruff lint only** — it does **not** run the test suite. The pytest suite must be run manually/locally as the migration gate. + +--- + +## 3. Audit results — breaking-change scans (all clear) + +Every Django 5.x removal/behavior-change category was scanned across `plane/` and `templates/`. Summary: + +| # | Breaking change (Django ver.) | Result | Evidence | +| --- | --------------------------------------------------------------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | `DEFAULT_FILE_STORAGE` / `STATICFILES_STORAGE` / `get_storage_class()` removed (5.1) | ✅ Not affected | Already uses the `STORAGES` dict (`settings/common.py:303-304`). | +| 2 | `Meta.index_together` / `AlterIndexTogether` removed (5.1) | ✅ Not affected | Zero matches in models or 129 migrations; already uses `Meta.indexes`. | +| 3 | pytz support removed: `USE_DEPRECATED_PYTZ`, `timezone.utc`, `is_dst` (5.0) | ✅ Not affected | All ~30 `pytz.*` uses are the **third-party library's own API** (unaffected). No Django pytz-integration surface used. | +| 4 | `USE_L10N` setting removed (5.0) | ✅ Not affected | Not defined anywhere. | +| 5 | `CICharField` / `CIEmailField` / `CITextField` removed (5.1) | ✅ Not affected | Not used; only `ArrayField` / `ArrayAgg` from contrib.postgres. | +| 6 | `format_html()` without args / `mark_safe` change (5.0) | ✅ Not affected | Zero `format_html(` / `mark_safe(` calls (HTML is rendered via templates). | +| 7 | `length_is` filter & legacy template tags removed (5.1) | ✅ Not affected | No Django Forms / removed tags in the 13 (email/admin) templates. | +| 8 | `Model.save()/delete()` positional args → keyword-only (5.1) | ✅ Not affected | Custom `delete()` overrides already pass `using=`/`soft=` by keyword. | +| 9 | `forms.URLField` default-scheme transition (5.0) | ✅ Not affected | Only `models.URLField` / DRF `serializers.URLField` used; no `forms.URLField`. | +| 10 | Misc removed APIs (`make_random_password`, `conf.urls.url`, `ugettext`, `is_ajax`, `NullBooleanField`, …) | ✅ Not affected | Zero matches. | +| 11 | Middleware / async ORM / ASGI changes (5.x) | ✅ Not affected | All middleware new-style; `MiddlewareMixin` still supported; no `async def` / `database_sync_to_async`; ASGI is http-only. | +| 12 | Auth / postgres aggregates / `TextChoices` (5.x) | ✅ Not affected (1 ⚠ optional review) | `ArrayAgg` uses only `distinct`/`filter`; `check_password`/`make_password` standard. **Optional:** review any reliance on `str(TextChoices.MEMBER)` repr (5.0 changed it to the value) — none observed. | + +**Settings audit (`common.py`, `production.py`, `local.py`, `test.py`, `storage.py`, `redis.py`):** already 5.x-clean. `STORAGES` migrated, `DEFAULT_AUTO_FIELD = BigAutoField` set, `TIME_ZONE='UTC'` + `USE_TZ=True` (zoneinfo), middleware order valid, no removed settings present. + +**Migration scan:** 129 migration files; no `index_together`/`AlterIndexTogether`/CI-fields. `AddIndexConcurrently` (2 files) is valid 5.2 usage with `atomic=False`. **Historical migrations run cleanly on 5.2.** + +--- + +## 4. App-code changes + +### 4.1 Required (done — see "Execution status" above) + +- `plane/urls.py` debug-toolbar include guard (django-debug-toolbar 6.0 model requirement). +- `plane/db/migrations/0122_…` state-only M2M `AlterField` (Django 5.1 `through_fields` normalization; no DDL). +- `plane/tests/contract/app/test_authentication.py` autouse cache-reset fixture (fixes 8 pre-existing throttle failures; makes the suite green). + +### 4.2 Optional / not taken + +1. ~~**`FORMS_URLFIELD_ASSUME_HTTPS = True`**~~ — **not applied.** In Django 5.2 this transitional setting emits its _own_ `RemovedInDjango60Warning`, and there are no `forms.URLField` to silence, so it only adds noise. +2. **`STORAGES` cosmetic collapse** — `common.py:303-304` defines `STORAGES` across two statements; optionally collapse into one literal (functionally identical): + ```python + STORAGES = { + "default": {"BACKEND": "plane.settings.storage.S3Storage"}, + "staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"}, + } + ``` +3. **`plane/asgi.py` cleanup (pre-existing latent bug, unrelated to 5.2)** — `django_asgi_app = get_asgi_application()` is assigned but unused, `get_asgi_application()` is called a second time inside `ProtocolTypeRouter`, and `os.environ.setdefault("DJANGO_SETTINGS_MODULE", …)` is placed _after_ the first `get_asgi_application()` call (so it has no effect). Move the `setdefault` to the top and reuse `django_asgi_app`. Optional; do it here or as a separate cleanup. + +--- + +## 5. Dependency upgrade matrix — **the actual work** + +Policy: **latest stable**, with **evidence-based safe overrides** where "latest" carries avoidable risk (called out in **bold**). "Min for 5.2" is the lowest version that officially supports Django 5.2 (your fallback if a latest-stable bump regresses). + +### 5.1 Must upgrade (current pin is NOT Django-5.2-compatible) + +| Package | Current | **Target** | Min for 5.2 | Notes / risk | +| ---------------------------- | ------- | -------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Django** | 4.2.30 | **5.2.x** (latest 5.2 LTS patch) | 5.2.0 | Stay on **5.2 LTS** — do **not** jump to 6.0 (not LTS). Confirm newest 5.2.\* patch at execution time. | +| djangorestframework | 3.15.2 | **3.17.1** | 3.16.0 | 3.15 supports Django only ≤5.0. Verify `UniqueTogetherValidator`/conditional-`UniqueConstraint` behavior (3.16 tightened nullable/partial handling). | +| channels | 4.1.0 | **4.3.2** | 4.2.1 | 4.3 raises `asgiref>=3.9` (auto-resolved) and makes Daphne an optional extra — Plane serves ASGI via uvicorn/gunicorn, so no impact. | +| django-cors-headers | 4.3.1 | **4.9.0** | 4.7.0 | No consumer-facing breaking changes in range. Verify `CORS_ALLOWED_ORIGINS`/`CORS_ALLOW_ALL_ORIGINS` still load. | +| django-filter | 24.2 | **25.2** | 25.1 | 25.x removed built-in DRF schema gen (Plane uses drf-spectacular → no impact). 25.2 requires Python ≥3.10 (have 3.12). | +| django-storages | 1.14.2 | **1.14.6** | 1.14.6 | `url_protocol` defaults to **HTTPS** when unset (1.14.6); `config`→`client_config` deprecation. Verify S3 settings + generated URL scheme. | +| django-redis | 5.4.0 | **7.0.0** | 6.0.0 | 7.0 renamed zset/hash helper params (not used by Plane) and drops Django 5.0 (irrelevant). **Verify redis-py floor** (`redis==5.0.4`) satisfies 7.0; if not, pin **6.0.0** instead. | +| django_celery_beat | 2.6.0 | **2.9.0** | 2.8.1 | **Skip 2.8.0** (shipped a regression, fixed in 2.8.1). 2.9 adds Django 6.0. | +| django-celery-results | 2.5.1 | **2.6.0** | 2.6.0 | Note: `django_celery_results` is **not** in `INSTALLED_APPS` here (packaged result backend only), so its DB migrations don't apply — no `migrate` action needed. Result-expiry timing changed slightly. | +| drf-spectacular | 0.28.0 | **0.29.0** | 0.29.0 | First version with the Django 5.2 classifier. **Regenerate & commit the OpenAPI schema** after upgrade. | +| scout-apm | 3.1.0 | **3.5.3** | 3.5.0 | Additive; no consumer breaking changes. | +| django-debug-toolbar _(dev)_ | 4.3.0 | **6.0.0** (not 7.0.0) | 5.1.0 | **Override:** 7.0 drops Django 4.2 and moves to shadow-DOM rendering (can break custom panels/CSS). 6.0 gives the widest window. Dev-only. | +| pytest-django _(test)_ | 4.5.2 | **4.12.0** | 4.11.0 | 4.12 needs Python ≥3.10 (have 3.12) and adds Django 6.0. `pytest==9.0.3` already satisfies its floor. | + +### 5.2 Already 5.2-compatible — opportunistic patch bumps + +| Package | Current | **Target** | Notes | +| ------------------------------------ | ---------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| psycopg / psycopg-binary / psycopg-c | 3.3.0 (×3) | **3.3.4** (×3) | Bump all three in lockstep (ABI). Patch-level only. | +| whitenoise | 6.11.0 | **6.12.0** | Security fix in autorefresh (dev-only); no breaking changes. | +| celery | 5.4.0 | **5.5.3** (not 5.6.x) | **Override:** 5.6 reverted the SQS→urllib3 change; 5.5.3 is the battle-tested line. Plane uses Redis broker. Pulls `kombu>=5.5`. (Celery itself isn't Django-pinned — already works on 5.2.) | +| dj-database-url | 2.1.0 | **3.0.1** (cautious) | **Override:** 3.x is a breaking major (engine-registry validation, raised Python floor). 2.1.0 already works on 5.2 — **holding at 2.1.0 is acceptable**. If bumping, verify all DB URL schemes parse. | + +### 5.3 Hold (already compatible; bumping adds churn with no 5.2 benefit) + +| Package | Current | Decision | +| ---------------------------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| django-crum | 0.7.9 | **Keep** — already the latest release (unmaintained); relies only on stable middleware APIs. | +| opentelemetry-\* (api/sdk/exporter-otlp/-proto-grpc = 1.28.1, instrumentation-django = 0.49b1) | matched set | **Hold the whole set.** Already 5.2-compatible. These are version-locked — bumping one forces a lockstep bump of all five (→ api/sdk/exporter `~1.43.x`, instrumentation `0.64b0`). Do that as a **separate, self-contained chore**, not inside this migration. | +| pytz | 2024.1 | **Keep** — still a valid library on 5.2. Removing it (→ stdlib `zoneinfo`) is a separate optional cleanup (see §10). | +| Non-Django deps (boto3, openai, slack-sdk, lxml, etc.) | — | **Out of scope** for this PR — keep the change focused on the Django upgrade. | + +> **Note on `redis` (redis-py 5.0.4):** confirm it satisfies `django-redis==7.0.0`'s floor during install; otherwise fall back to `django-redis==6.0.0` (safe with redis-py 5.x). + +--- + +## 6. Step-by-step execution plan + +> Single PR. Treat the deprecation-warning audit as part of the test phase (folds the "run on 4.2 first" safety net into the 5.2 verification). + +1. **Branch.** `git checkout -b chore/-django-5.2-upgrade` off `preview`. Include this plan doc in the branch. +2. **Edit `requirements/base.txt`** per §5.1 + §5.2 (Django, DRF, channels, cors-headers, filter, storages, redis, celery-beat, celery-results, drf-spectacular, scout-apm, psycopg ×3, whitenoise, celery, dj-database-url). Leave the OTel set, django-crum, pytz untouched. +3. **Edit `requirements/local.txt`** → `django-debug-toolbar==6.0.0`. +4. **Edit `requirements/test.txt`** → `pytest-django==4.12.0`. +5. **(Optional)** apply §4.2 hardening (`FORMS_URLFIELD_ASSUME_HTTPS`, STORAGES collapse, asgi.py cleanup). +6. **Clean install** in a fresh venv / rebuilt image: `pip install -r requirements/test.txt` (pulls base+test). Resolve any pip conflicts here (esp. redis-py ↔ django-redis, asgiref ↔ channels, kombu ↔ celery). +7. **Model state check:** `python manage.py makemigrations --check --dry-run` → expect **no new migrations**. If Django 5.x wants migrations, inspect before committing. +8. **Apply third-party migrations:** `python manage.py migrate` on a scratch DB (django_celery_results 2.6.0 and django_celery_beat ship their own migrations). +9. **Deprecation-warning audit:** run the suite with warnings as errors to catch any `RemovedInDjango60Warning` / `RemovedInDjango61Warning`: + `python -W error::DeprecationWarning -W error::PendingDeprecationWarning -m pytest` (or via `pytest.ini` `filterwarnings`). Triage anything that surfaces (expected: clean, given the audit). +10. **Full test suite:** `cd apps/api && ./run_tests.sh` (or `pytest`). All green is the gate. +11. **Regenerate OpenAPI schema** (drf-spectacular 0.29.0) and commit the diff: `python manage.py spectacular --file ` (match the repo's existing schema-generation command). Review for unintended changes. +12. **Manual smoke** of each entrypoint against a local stack (`docker-compose-local.yml`): **api** (boot + a few endpoints incl. one S3/file-upload path and one webhook), **worker** (enqueue+run a task), **beat** (scheduler starts, periodic task fires), **migrator** (runs to completion). Confirm `check --deploy` is clean: `python manage.py check --deploy`. +13. **Update docs:** mark this plan as executed; note any deviations. +14. **Open PR** to `preview` with the migration summary, test evidence, and schema diff. + +--- + +## 7. Acceptance criteria / verification checklist + +- [ ] `pip install -r requirements/test.txt` resolves with no conflicts. +- [ ] `manage.py makemigrations --check --dry-run` → no new app migrations. +- [ ] `manage.py migrate` applies django_celery_results / django_celery_beat migrations cleanly. +- [ ] `manage.py check --deploy` passes. +- [ ] Test suite passes **and** passes under `-W error::DeprecationWarning`. +- [ ] OpenAPI schema regenerated and reviewed. +- [ ] api / worker / beat / migrator entrypoints all boot and function. +- [ ] `python -c "import django; print(django.VERSION)"` shows 5.2.x in the built image. + +--- + +## 8. Rollback plan + +- The change is **pip-pins + (optionally) a few settings lines**, all in one PR → revert the PR / commit to restore Django 4.2.30. No data migration is destructive: the only schema changes come from `django_celery_results`/`django_celery_beat`, which are additive (new indexes/columns) and safe to leave in place even on a rollback. +- Keep the pre-upgrade `requirements/*.txt` recoverable via git. Rebuild the image from the reverted requirements. + +--- + +## 9. Risks & mitigations + +| Risk | Likelihood | Mitigation | +| ------------------------------------------------------------------------------------------ | ----------------------------- | ------------------------------------------------------------------------------------------- | +| Pip dependency-resolution conflict (redis-py↔django-redis, asgiref↔channels, kombu↔celery) | Medium | Resolve at step 6 in a clean env; fall back to the "Min for 5.2" pins in §5.1. | +| DRF 3.16+ unique-constraint validation surfaces previously-silent errors | Low–Med | Covered by test suite + serializer tests; review any new 400s on create/update endpoints. | +| django-storages 1.14.6 URL scheme flips to HTTPS | Low | Verify generated asset URLs in the api smoke test; set `url_protocol` explicitly if needed. | +| drf-spectacular 0.29 schema output drift | Low | Regenerate + review the committed schema diff (step 11). | +| Celery beat/results migrations not run in deploy | Low | `migrator` entrypoint runs `migrate`; explicit step 8 + acceptance check. | +| Self-hosters on PostgreSQL < 14 | Low (shipped compose is 15.7) | Note PG 14+ requirement in release notes/changelog. | + +--- + +## 10. Out of scope / follow-ups (separate PRs) + +- **OpenTelemetry lockstep bump** (api/sdk/exporter → ~1.43.x, instrumentation-django → 0.64b0) — self-contained, no 5.2 dependency. +- **Drop pytz → stdlib `zoneinfo`** — touch points: `plane/utils/timezone_converter.py`, `plane/app/views/cycle/base.py`, `plane/app/views/timezone/base.py`, `plane/api/serializers/cycle.py`, the 4 OAuth providers, and `TIMEZONE_CHOICES` in `plane/db/models/{project,user,workspace,cycle}.py`. Note `pytz.common_timezones` has no exact `zoneinfo` equivalent and `tz.localize(dt)` becomes `dt.replace(tzinfo=ZoneInfo(name))`. +- **dj-database-url 3.x** (breaking major) if not taken in this PR. +- **Add a pytest job to API CI** — currently CI lints only; the migration relies on manual test runs. +- **Django 6.0 readiness** (after 5.2 lands) — non-LTS, separate evaluation. + +--- + +_Audit basis: 12 code scans + settings + migrations + 19 per-dependency compatibility checks against `apps/api` at Django 4.2.30. Confirm the newest 5.2.x patch and each package's latest stable at execution time (pins above reflect the audit snapshot)._ diff --git a/apps/api/plane/db/migrations/0122_alter_draftissue_assignees_alter_issue_assignees_and_more.py b/apps/api/plane/db/migrations/0122_alter_draftissue_assignees_alter_issue_assignees_and_more.py new file mode 100644 index 00000000000..ade16c3e876 --- /dev/null +++ b/apps/api/plane/db/migrations/0122_alter_draftissue_assignees_alter_issue_assignees_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.15 on 2026-06-26 15:33 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0121_alter_estimate_type'), + ] + + operations = [ + migrations.AlterField( + model_name='draftissue', + name='assignees', + field=models.ManyToManyField(blank=True, related_name='draft_assignee', through='db.DraftIssueAssignee', through_fields=('draft_issue', 'assignee'), to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='issue', + name='assignees', + field=models.ManyToManyField(blank=True, related_name='assignee', through='db.IssueAssignee', through_fields=('issue', 'assignee'), to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='module', + name='members', + field=models.ManyToManyField(blank=True, related_name='module_members', through='db.ModuleMember', through_fields=('module', 'member'), to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/apps/api/plane/tests/contract/app/test_authentication.py b/apps/api/plane/tests/contract/app/test_authentication.py index 300d862b84b..b2e76bb411e 100644 --- a/apps/api/plane/tests/contract/app/test_authentication.py +++ b/apps/api/plane/tests/contract/app/test_authentication.py @@ -20,6 +20,22 @@ from plane.license.models import Instance +@pytest.fixture(autouse=True) +def _reset_auth_throttle_cache(): + """Clear the shared cache around every test in this module. + + The auth endpoints apply a per-IP throttle (AuthenticationThrottle) whose + request history lives in the Django cache. Because the test session reuses a + single cache, that count leaks across tests and trips RATE_LIMIT_EXCEEDED in + classes that don't reset it. This mirrors the per-class ``_clear_state`` + fixtures already used by the throttle / verify-attempt classes, applied + module-wide so every auth test starts from a clean throttle state. + """ + cache.clear() + yield + cache.clear() + + @pytest.fixture def setup_instance(db): """Create and configure an instance for authentication tests""" diff --git a/apps/api/plane/urls.py b/apps/api/plane/urls.py index f5e43408cb8..44761bd6b55 100644 --- a/apps/api/plane/urls.py +++ b/apps/api/plane/urls.py @@ -4,6 +4,7 @@ """plane URL Configuration""" +from django.apps import apps from django.conf import settings from django.urls import include, path, re_path from drf_spectacular.views import ( @@ -38,7 +39,7 @@ ), ] -if settings.DEBUG: +if settings.DEBUG and apps.is_installed("debug_toolbar"): try: import debug_toolbar diff --git a/apps/api/requirements/base.txt b/apps/api/requirements/base.txt index 89b15694f39..5ed14ebaad3 100644 --- a/apps/api/requirements/base.txt +++ b/apps/api/requirements/base.txt @@ -1,45 +1,45 @@ # base requirements # django -Django==4.2.30 +Django==5.2.15 # rest framework -djangorestframework==3.15.2 +djangorestframework==3.17.1 # postgres -psycopg==3.3.0 -psycopg-binary==3.3.0 -psycopg-c==3.3.0 -dj-database-url==2.1.0 +psycopg==3.3.4 +psycopg-binary==3.3.4 +psycopg-c==3.3.4 +dj-database-url==3.0.1 # redis redis==5.0.4 -django-redis==5.4.0 +django-redis==7.0.0 # cors -django-cors-headers==4.3.1 +django-cors-headers==4.9.0 # celery -celery==5.4.0 -django_celery_beat==2.6.0 -django-celery-results==2.5.1 +celery==5.5.3 +django_celery_beat==2.9.0 +django-celery-results==2.6.0 # file serve -whitenoise==6.11.0 +whitenoise==6.12.0 # fake data faker==25.0.0 # filters -django-filter==24.2 +django-filter==25.2 # json model jsonmodels==2.7.0 # storage -django-storages==1.14.2 +django-storages==1.14.6 # user management django-crum==0.7.9 # web server uvicorn==0.29.0 # sockets -channels==4.1.0 +channels==4.3.2 # ai openai==1.63.2 # slack slack-sdk==3.27.1 # apm -scout-apm==3.1.0 +scout-apm==3.5.3 # xlsx generation openpyxl==3.1.2 # logging @@ -72,6 +72,6 @@ opentelemetry-instrumentation-django==0.49b1 opentelemetry-exporter-otlp==1.28.1 opentelemetry-exporter-otlp-proto-grpc==1.28.1 # OpenAPI Specification -drf-spectacular==0.28.0 +drf-spectacular==0.29.0 # html sanitizer nh3==0.2.18 diff --git a/apps/api/requirements/local.txt b/apps/api/requirements/local.txt index 2146554f63f..104c053663f 100644 --- a/apps/api/requirements/local.txt +++ b/apps/api/requirements/local.txt @@ -1,5 +1,5 @@ -r base.txt # debug toolbar -django-debug-toolbar==4.3.0 +django-debug-toolbar==6.0.0 # formatter ruff==0.9.7 diff --git a/apps/api/requirements/test.txt b/apps/api/requirements/test.txt index 0b655f99466..c0652c42c2d 100644 --- a/apps/api/requirements/test.txt +++ b/apps/api/requirements/test.txt @@ -1,7 +1,7 @@ -r base.txt # test framework pytest==9.0.3 -pytest-django==4.5.2 +pytest-django==4.12.0 pytest-cov==4.1.0 pytest-xdist==3.3.1 pytest-mock==3.11.1