Skip to content

Upgrade back-end code from Django 4.2 to 5.2 LTS#8052

Open
acwhite211 wants to merge 26 commits into
mainfrom
issue-8046
Open

Upgrade back-end code from Django 4.2 to 5.2 LTS#8052
acwhite211 wants to merge 26 commits into
mainfrom
issue-8046

Conversation

@acwhite211

@acwhite211 acwhite211 commented May 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes #8046

Upgrade Django from 4.2 to LTS version 5.2.

https://docs.djangoproject.com/en/6.0/releases/5.2/

Changes:

  • removed USE_L10N (A localization setting, that is now defaulted to true)
  • added new migration to fix many-to-many fields (django-generated)
  • Migrated GET request to logout to a POST request to be in HTTP (and django) spec
    • changed the logOut user tool (in Header/userToolDefinitions) to point to a new front-end endpoint: /specify/command/logout to match the cache clearing endpoint
    • this new endpoint (defined in Router/Routes.tsx) renders the Logout component
    • the Logout component makes the POST request to the backend and returns a loading screen

Checklist

  • Self-review the PR after opening it to make sure the changes look good and
    self-explanatory (or properly documented)
  • Add relevant issue to release milestone
  • Add pr to documentation list
  • Add automated tests

Testing instructions

  • Ensure strings are localized correctly
    • verify that labels are not raw schema labels, i.e. ensure schema config labels are respected.
    • Ensure that switching languages behaves correctly.
  • Log out from several locations
    • Ensure that no error dialogs flash on the screen
    • Logging back in takes you to the splash screen
  • General UI/UX testing
    • especially the workbench and attachments/attachment bulk upload

Summary by CodeRabbit

  • New Features

    • Added a dedicated logout flow with loading feedback and automatic redirection to the login page.
    • Logout actions from account, collection access, and missing-agent screens now use the updated logout route.
  • Bug Fixes

    • Improved logout request handling to ensure it uses the correct request method and preserves the intended return destination.
  • Refactor

    • Updated application routing and localization behavior for compatibility with the latest platform framework.

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@g1rly-c0d3r, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f657e5a9-67ba-481b-b1f3-0372db7e1b2e

📥 Commits

Reviewing files that changed from the base of the PR and between 6a6d62d and 6729ea7.

📒 Files selected for processing (1)
  • specifyweb/frontend/js_src/lib/components/Router/Routes.tsx
📝 Walkthrough

Walkthrough

Changes

Django 5.2 dependencies are introduced alongside path-based URL routing, field compatibility updates, a migration for many-to-many relationships, and pytest configuration. Frontend logout entry points now use a routed logout component that posts to the accounts endpoint before redirecting to login.

Django platform upgrade

Layer / File(s) Summary
Runtime and test foundation
requirements.txt, requirements-testing.txt, pytest.ini, specifyweb/settings/__init__.py, specifyweb/specify/utils/case_insensitive_bool.py
Django and authentication dependencies are upgraded, pytest is configured, USE_L10N is removed, and NullBooleanField now extends the local BooleanField.
URL routing modernization
specifyweb/**/urls.py
Regex-based routes are replaced with path() routes and typed converters while preserving existing view bindings.
Model field compatibility migration
specifyweb/specify/migrations/0045_alter_many_to_many_through_fields.py
Many-to-many through models and through_fields are updated for several relationships.

Frontend logout flow

Layer / File(s) Summary
Logout route and component
specifyweb/frontend/js_src/lib/components/Router/Routes.tsx, specifyweb/frontend/js_src/lib/components/Logout/index.tsx
A lazy-loaded command logout route posts to /accounts/logout/, handles loading and errors, and redirects to login.
Logout entry points
specifyweb/frontend/js_src/lib/components/ChooseCollection/index.tsx, specifyweb/frontend/js_src/lib/components/Core/Main.tsx, specifyweb/frontend/js_src/lib/components/Header/userToolDefinitions.ts
Existing logout entry points target /specify/command/logout/; the collection action explicitly uses POST.
Dialog icon formatting
specifyweb/frontend/js_src/lib/components/Atoms/Icons.tsx
The warning icon JSX is reformatted without changing its rendering.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Router
  participant Logout
  participant AccountsLogout
  participant LoginPage
  User->>Router: Open command/logout
  Router->>Logout: Load Logout component
  Logout->>AccountsLogout: POST /accounts/logout/
  AccountsLogout-->>Logout: Logout response
  Logout->>LoginPage: Redirect with next=/specify/
Loading

Possibly related PRs

Suggested reviewers: carolinedenis, g1rly-c0d3r, grantfitzsimmons, emenslin

🚥 Pre-merge checks | ✅ 3 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The warning icon formatting change in Atoms/Icons.tsx appears unrelated to the Django 5.2 upgrade objective. Remove unrelated UI-only formatting changes unless they are part of a documented follow-up objective.
Automatic Tests ⚠️ Warning The PR adds no new automated test files for the changed logout, URL, or migration behavior, and the checklist leaves “Add automated tests” unchecked. Add automated tests for the new logout flow, updated URL routing, and Django upgrade/migration paths before merging.
Testing Instructions ⚠️ Warning Testing steps cover logout/localization, but they omit explicit migration/backend-upgrade checks and remain too generic for the changed logout entry points. Add concrete verification for the Django 5.2 migration/startup path and the specific logout flows (user menu, no-access collection, missing-agent dialog).
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: upgrading Django from 4.2 to 5.2 LTS.
Linked Issues check ✅ Passed The PR updates Django to 5.2.13 and includes related compatibility changes needed for the Django 5.2 upgrade.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-8046

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@g1rly-c0d3r g1rly-c0d3r self-assigned this Jul 14, 2026
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

Warning

One or more dependencies are approaching or past End-of-Life.
Please plan upgrades accordingly.

STATUS=WARNING
NODE_VERSION=20
NODE_CYCLE=20
EOL_DATE=2026-04-30
DAYS_REMAINING=-83

--- Node.js ---
Version: 20
EOL: 2026-04-30
Status: WARNING

STATUS=OK
PYTHON_VERSION=3.12
PYTHON_CYCLE=3.12
EOL_DATE=2028-10-31
DAYS_REMAINING=832

--- Python ---
Version: 3.12
EOL: 2028-10-31
Status: OK

STATUS=OK
DJANGO_VERSION=5.2
DJANGO_CYCLE=5.2
EOL_DATE=2028-04-30
DAYS_REMAINING=648

--- Django ---
Version: 5.2
EOL: 2028-04-30
Status: OK


Triggered by ffecf67 on branch refs/heads/issue-8046
Comment thread specifyweb/backend/inheritance/urls.py Fixed
We decided that since we would be mocking all the calls, testing is a fruitless endevor for this component
@g1rly-c0d3r
g1rly-c0d3r marked this pull request as ready for review July 22, 2026 19:27
@g1rly-c0d3r
g1rly-c0d3r requested review from a team and CarolineDenis and removed request for a team July 22, 2026 19:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
specifyweb/frontend/js_src/lib/components/ChooseCollection/index.tsx (1)

175-177: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Avoid posting logout twice in this flow.

The ping call performs a POST, then the redirect mounts Logout, which POSTs to /accounts/logout/ again. Let one layer own the logout request; verify whether the pre-redirect POST is still required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/frontend/js_src/lib/components/ChooseCollection/index.tsx` around
lines 175 - 177, Update the logout flow around the ping call and redirect to
ensure /accounts/logout/ is POSTed only once. Determine whether Logout owns the
required logout request, then remove the redundant pre-redirect ping POST if so,
while preserving the redirect to formatUrl('/specify/command/logout/', { next:
nextUrl }).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@requirements-testing.txt`:
- Around line 2-3: Update the mypy dependency pin in requirements-testing.txt to
a version supported by django-stubs==5.2.9, such as mypy==1.13.0; retain the
django-stubs pin and do not keep the incompatible mypy==1.5.1 combination.

In `@requirements.txt`:
- Line 14: Update the PyJWT dependency version in requirements.txt from 2.12.1
to 2.13.0 or later, ensuring the pinned version includes the PyJWKClient
scheme-validation fix.

In `@specifyweb/backend/context/testurls.py`:
- Line 12: Update the URL pattern for views.viewsets to use a string-compatible
path converter instead of <int:level>, so the level argument remains a string
when passed to testsviews.viewsets().

In `@specifyweb/frontend/js_src/lib/components/Logout/index.tsx`:
- Line 17: Update the logout redirect callback in the Logout component to read
and validate the route’s next value, preserving it when valid and falling back
to “/specify/” otherwise. Use that resolved value in the formatUrl call instead
of always passing the fixed “/specify/” target.
- Around line 16-22: Update the logout failure handler in the promise chain
around softError so a failed POST transitions away from the indefinitely
displayed LoadingScreen. Render an error/retry state or navigate to a terminal
fallback on rejection, while preserving the existing successful redirect
behavior.
- Around line 11-20: Move the logout request and its loading/redirect error
handling from the component render path into a mount-only effect, using the
component’s existing effect and loading utilities. Ensure the POST to
/accounts/logout/ starts once after mount, while preserving the current success
redirect and softError behavior.

---

Nitpick comments:
In `@specifyweb/frontend/js_src/lib/components/ChooseCollection/index.tsx`:
- Around line 175-177: Update the logout flow around the ping call and redirect
to ensure /accounts/logout/ is POSTed only once. Determine whether Logout owns
the required logout request, then remove the redundant pre-redirect ping POST if
so, while preserving the redirect to formatUrl('/specify/command/logout/', {
next: nextUrl }).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3501db84-1148-48c2-861a-22071020f1f5

📥 Commits

Reviewing files that changed from the base of the PR and between 7c0e603 and 6a6d62d.

📒 Files selected for processing (20)
  • pytest.ini
  • requirements-testing.txt
  • requirements.txt
  • specifyweb/backend/batch_identify/urls.py
  • specifyweb/backend/context/testurls.py
  • specifyweb/backend/context/urls.py
  • specifyweb/backend/inheritance/urls.py
  • specifyweb/backend/setup_tool/urls.py
  • specifyweb/backend/trees/urls.py
  • specifyweb/frontend/js_src/lib/components/Atoms/Icons.tsx
  • specifyweb/frontend/js_src/lib/components/ChooseCollection/index.tsx
  • specifyweb/frontend/js_src/lib/components/Core/Main.tsx
  • specifyweb/frontend/js_src/lib/components/Header/userToolDefinitions.ts
  • specifyweb/frontend/js_src/lib/components/Logout/index.tsx
  • specifyweb/frontend/js_src/lib/components/Router/Routes.tsx
  • specifyweb/settings/__init__.py
  • specifyweb/specify/migrations/0045_alter_many_to_many_through_fields.py
  • specifyweb/specify/urls.py
  • specifyweb/specify/utils/case_insensitive_bool.py
  • specifyweb/urls.py
💤 Files with no reviewable changes (1)
  • specifyweb/settings/init.py

Comment thread requirements-testing.txt
Comment on lines +2 to 3
django-stubs==5.2.9
mypy==1.5.1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== requirements-testing.txt =="
cat -n requirements-testing.txt

echo
echo "== nearby django-stubs/mypy pins =="
rg -n 'django-stubs|mypy' requirements*.txt pyproject.toml setup.cfg tox.ini 2>/dev/null || true

echo
echo "== lock/config references =="
fd -a '.*requirements.*|.*constraints.*|pyproject.toml|setup.cfg|tox.ini' . | sed 's#^\./##' | head -100

Repository: specify/specify7

Length of output: 792


🌐 Web query:

django-stubs 5.2.9 mypy compatibility supported versions

💡 Result:

For django-stubs version 5.2.9, the supported environment configurations are as follows [1][2]: Mypy versions: 1.13 through 1.19 [1][2] Django versions: 5.2 (full support), with partial support for Django 5.1 and 5.0 [1][2] Python versions: 3.10 through 3.13 [1][2] Version 5.2.9 was the final release of the 5.2.x series for django-stubs, designed to target Django 5.2 [3][4]. Subsequent releases transitioned to supporting Django 6.0 [3][4]. When using "partial support" for older Django versions, the stubs may contain newer type definitions (e.g., if a return type changed between versions) or include symbols not present in your specific, older Django installation [5]. It is generally recommended to use the version of django-stubs that corresponds to your installed Django version to ensure full compatibility [5].

Citations:


Align mypy with django-stubs 5.2.9.

django-stubs 5.2.9 documents supported mypy versions as 1.13–1.19, but this file pins mypy==1.5.1. Use a supported mypy pin here, such as mypy==1.13.0, or keep the older django-stubs version if mypy 1.5.1 is required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@requirements-testing.txt` around lines 2 - 3, Update the mypy dependency pin
in requirements-testing.txt to a version supported by django-stubs==5.2.9, such
as mypy==1.13.0; retain the django-stubs pin and do not keep the incompatible
mypy==1.5.1 combination.

Comment thread requirements.txt
sqlalchemy2-stubs
requests==2.32.4
pycryptodome==3.21.0
PyJWT==2.12.1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== requirements.txt =="
sed -n '1,60p' requirements.txt 2>/dev/null || true

echo
echo "== PyJWT references in repo =="
rg -n --fixed-strings "PyJWKClient|PyJWT|requests_jwt|jwt\." -S . --glob '!*.lock' --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' | head -200 || true

echo
echo "== lockfiles mentioning PyJWT =="
for f in $(git ls-files | rg '(^|/)(requirements.*\.txt|poetry\.lock|Pipfile\.lock|uv\.lock|package-lock\.json|yarn\.lock|pnpm-lock\.yaml)$'); do
  echo "--- $f"
  rg -n --fixed-strings "PyJWT|pyjwt|jwk|jwk-set" "$f" || true
done

Repository: specify/specify7

Length of output: 673


🌐 Web query:

OSV GHSA-993g-76c3-p5m4 PyJWT PyJWKClient scheme-validation fixed version

💡 Result:

The vulnerability GHSA-993g-76c3-p5m4 (also tracked as CVE-2026-48522) involves a missing scheme allowlist in PyJWT's PyJWKClient, which allowed the library to fetch URIs using schemes beyond the expected http and https (such as file://, ftp://, and data:) [1][2][3]. This flaw enabled potential Server-Side Request Forgery (SSRF) and, in specific configurations, token forgery [2][4]. The vulnerability is fixed in PyJWT version 2.13.0 [3][5]. In this release, PyJWKClient was updated to strictly reject any URI scheme that is not explicitly http or https [5][6]. Additionally, a new allowed_schemes initialization parameter was added to allow for future configuration of allowed schemes [1][2].

Citations:


🌐 Web query:

GitHub PyJWKClient GHSA-993g-76c3-p5m4 fixed commit PyJWT 2.12.1 PyJWT 2.13.0 allow_schemes

💡 Result:

The vulnerability GHSA-993g-76c3-p5m4, also identified as CVE-2026-48522, addressed a security flaw in PyJWT's PyJWKClient where it lacked a scheme allowlist [1][2]. This allowed the client to fetch JWKS data using unintended schemes such as file://, ftp://, and data:, potentially leading to SSRF or token forgery if an application accepted attacker-influenced URIs [1][3]. This issue was fixed in PyJWT version 2.13.0 [4][5]. The fix introduced an allowed_schemes parameter to the PyJWKClient constructor [6][7]. Key details of the fix: - PyJWKClient now defaults to allowed_schemes=("http", "https") [7]. - The library performs pre-validation of the URI scheme before invoking urllib.request.urlopen [1][7]. - If a scheme is not in the allowed list, PyJWKClient raises a PyJWKClientError [1][7]. - This change allows developers to maintain backward compatibility for standard HTTP/HTTPS use cases while providing the flexibility to enable other schemes explicitly if required [6][7]. It is important to note that this update specifically addresses the URI scheme restriction [1][5]. It is distinct from another security fix, GHSA-jq35-7prp-9v3f, which was also released in PyJWT 2.13.0 and addressed an algorithm allow-list bypass when using PyJWK keys [8][9].

Citations:


Upgrade PyJWT to avoid known PyJWKClient vulnerabilities.

PyJWT==2.12.1 is affected by PyJWKClient scheme-validation issues; update to 2.13.0 or later so the default JWKS fetches are limited to allowed HTTP(S) schemes.

🧰 Tools
🪛 OSV Scanner (2.4.0)

[HIGH] 14-14: pyjwt 2.12.1: undefined

(PYSEC-2026-175)


[HIGH] 14-14: pyjwt 2.12.1: undefined

(PYSEC-2026-177)


[HIGH] 14-14: pyjwt 2.12.1: undefined

(PYSEC-2026-178)


[HIGH] 14-14: pyjwt 2.12.1: undefined

(PYSEC-2026-179)


[HIGH] 14-14: pyjwt 2.12.1: PyJWKClient: missing scheme allowlist enables CVE-2024-21643-class SSRF + token forgery via file://, ftp://, data: schemes

(GHSA-993g-76c3-p5m4)


[HIGH] 14-14: pyjwt 2.12.1: PyJWKClient unbounded JWKS endpoint requests via attacker-controlled kid values (DoS)

(GHSA-fhv5-28vv-h8m8)


[HIGH] 14-14: pyjwt 2.12.1: PyJWT: Algorithm allow-list bypass when decoding with PyJWK / PyJWKClient keys

(GHSA-jq35-7prp-9v3f)


[HIGH] 14-14: pyjwt 2.12.1: PyJWT: Unauthenticated DoS via unbounded Base64URL decoding of unused payload segment in b64=false detached JWS

(GHSA-w7vc-732c-9m39)


[HIGH] 14-14: pyjwt 2.12.1: PyJWT: Public-key JWK accepted as HMAC secret enables forged HS256 tokens when mixed families are allowed

(GHSA-xgmm-8j9v-c9wx)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@requirements.txt` at line 14, Update the PyJWT dependency version in
requirements.txt from 2.12.1 to 2.13.0 or later, ensuring the pinned version
includes the PyJWKClient scheme-validation fix.

Source: Linters/SAST tools

re_path(r'^app.resource$', views.app_resource),
re_path(r'^available_related_searches.json$', views.available_related_searches),
path('domain.json', views.domain),
path('viewsets/<int:level>.xml', views.viewsets),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching testurls.py:\n'
fd 'testurls\.py$' . || true

printf '\nGit-tracked context testurls.py:\n'
git ls-files | rg '(^|/)testurls\.py$' | sed -n '1,50p'

printf '\nRelevant snippets in context testurls.py:\n'
while IFS= read -r f; do
  echo "--- $f"
  nl -ba "$f" | sed -n '1,40p'
done < <(git ls-files | rg 'context/testurls\.py$' || true)

printf '\nSearch for viewsets and level=':
rg -n "def viewsets|viewsets|'viewset_'\+level|viewset_|level\.xml" --glob '*.py' .

Repository: specify/specify7

Length of output: 392


🏁 Script executed:

#!/bin/bash
set -u

echo "### testurls.py"
nl -ba specifyweb/backend/context/testurls.py | sed -n '1,80p'

echo
echo "### viewsets usages / level handling"
rg -n "def viewsets|viewsets|'viewset_'|\"viewset_|level\.xml|viewset_" --glob '*.py' .

echo
echo "### Django int converter behavior probe"
python3 - <<'PY'
try:
    from django.urls.converters import IntConverter
    ic = IntConverter()
    converted = ic.to_python('05')
    print(type(converted).__name__, repr(converted))
    print(type(converted).__name__, type('').__name__, isinstance('', str))
    print(converted + '/abc.xml')
except Exception as e:
    print(type(e).__name__, str(e))
PY

Repository: specify/specify7

Length of output: 3369


🏁 Script executed:

#!/bin/bash
set -u

echo "### specifyweb/backend/context/testurls.py"
awk '{printf "%6d\t%s\n", NR, $0}' specifyweb/backend/context/testurls.py | sed -n '1,80p'

echo
echo "### specifyweb/backend/context/testsviews.py"
awk '{printf "%6d\t%s\n", NR, $0}' specifyweb/backend/context/testsviews.py | sed -n '1,60p'

echo
echo "### viewsets path/url/import references"
rg -n "testurls|testsviews|viewsets/<int:level>|def viewsets\(request, level\)" --glob '*.py' .

Repository: specify/specify7

Length of output: 2308


🌐 Web query:

Django URL converters int converter documentation

💡 Result:

In Django, the int URL converter is used within path patterns to match a sequence of digits and convert the captured value into a Python integer [1][2]. Key technical details: - Matching behavior: The int converter matches zero or any positive integer [1][2]. - Type conversion: It automatically converts the captured string value into a Python int before passing it as a keyword argument to the view function [1][3]. - Implementation: Internally, it uses the regular expression [0-9]+ to identify matching segments [4]. - Usage: You use it in a URL pattern by placing it inside angle brackets, such as path('articles/int:year/', views.year_archive) [1][2]. If you need different matching logic (for example, to support negative numbers or specific constraints), you can register a custom path converter using the register_converter function [1][2][3].

Citations:


Keep level as a string.

Django’s <int:level> converter passes an int, while testsviews.viewsets() concatenates it with strings, raising a TypeError for this URL when called.

Proposed fix
-    path('viewsets/<int:level>.xml', views.viewsets),
+    path('viewsets/<str:level>.xml', views.viewsets),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
path('viewsets/<int:level>.xml', views.viewsets),
path('viewsets/<str:level>.xml', views.viewsets),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/backend/context/testurls.py` at line 12, Update the URL pattern
for views.viewsets to use a string-compatible path converter instead of
<int:level>, so the level argument remains a string when passed to
testsviews.viewsets().

Comment on lines +11 to +20
loading(
ajax<string>('/accounts/logout/', {
method: 'POST',
headers: { },
})
.then(
() => globalThis.location.assign(formatUrl("/accounts/login/", {next: "/specify/"})),
(error) => softError(error)
)
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'index\.tsx$' . | sed 's#^\./##' | grep 'frontend/js_src/lib/components/Logout/index.tsx' || true

echo "== file contents =="
cat -n specifyweb/frontend/js_src/lib/components/Logout/index.tsx

echo "== definitions/usages =="
rg -n "function Loading|const Loading|export .*Loading|function ajax|const ajax|function softError|const softError|loading\\(" specifyweb/frontend/js_src/lib/components specifyweb/frontend/js_src/lib 2>/dev/null | head -200

Repository: specify/specify7

Length of output: 22738


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files for LoadingContext/loading/softError/Route components =="
rg -n "LoadingContext|LoadingScreen|loading\\(|softError|formatUrl\\(" specifyweb/frontend/js_src/lib -S

echo "== structural scan for Loading component definition =="
ast-grep scan --lang tsx --rule 'kind: Component; matcher: {kind: FunctionDeclaration, has: {kind: BlockStatement}}' specifyweb/frontend/js_src/lib/components 2>/dev/null || true

Repository: specify/specify7

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== LoadingContext implementation =="
sed -n '1,230p' specifyweb/frontend/js_src/lib/components/Core/Contexts.tsx

echo "== ajax implementation relevant section =="
sed -n '1,155p' specifyweb/frontend/js_src/lib/utils/ajax/index.ts

echo "== static checker for render-time loading calls =="
python3 - <<'PY'
from pathlib import Path
p = Path('specifyweb/frontend/js_src/lib/components/Logout/index.tsx')
text = p.read_text()
print('contains ajax call:', 'ajax<string>' in text)
print('inside function body:')
start = text.index('export function Logout')
end = text.index('return <LoadingScreen />') + len('return <LoadingScreen />')
body = text[start:end]
print(body)
print('has useEffect:', 'useEffect' in body)
PY

Repository: specify/specify7

Length of output: 14011


Move the logout request into an effect.

ajax<...>('/accounts/logout/', ...) creates a new fetch promise during every render, so rerenders can submit logout multiple times; start this one-time POST from a stable effect.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/frontend/js_src/lib/components/Logout/index.tsx` around lines 11 -
20, Move the logout request and its loading/redirect error handling from the
component render path into a mount-only effect, using the component’s existing
effect and loading utilities. Ensure the POST to /accounts/logout/ starts once
after mount, while preserving the current success redirect and softError
behavior.

Comment on lines +16 to +22
.then(
() => globalThis.location.assign(formatUrl("/accounts/login/", {next: "/specify/"})),
(error) => softError(error)
)
)

return <LoadingScreen />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle failures without leaving an endless loading screen.

softError only logs in production, while LoadingScreen resolves its pending promise only on unmount. A failed POST therefore leaves the user stuck on the loading screen. Render an error/retry state or navigate to a terminal fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/frontend/js_src/lib/components/Logout/index.tsx` around lines 16 -
22, Update the logout failure handler in the promise chain around softError so a
failed POST transitions away from the indefinitely displayed LoadingScreen.
Render an error/retry state or navigate to a terminal fallback on rejection,
while preserving the existing successful redirect behavior.

headers: { },
})
.then(
() => globalThis.location.assign(formatUrl("/accounts/login/", {next: "/specify/"})),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the collection return target.

ChooseCollection passes nextUrl to /specify/command/logout/, but this component always redirects with next: '/specify/'. Read and validate the route’s next value, using /specify/ only as the fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/frontend/js_src/lib/components/Logout/index.tsx` at line 17,
Update the logout redirect callback in the Logout component to read and validate
the route’s next value, preserving it when valid and falling back to “/specify/”
otherwise. Use that resolved value in the formatUrl call instead of always
passing the fixed “/specify/” target.

@github-project-automation github-project-automation Bot moved this from 📋Back Log to Dev Attention Needed in General Tester Board Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Dev Attention Needed

Development

Successfully merging this pull request may close these issues.

Upgrade Django to LTS version 5.2

4 participants