Skip to content

fix: cast avatar_asset to CharField to resolve mixed type errors in URL concatenation - #9512

Merged
dheeru0198 merged 1 commit into
previewfrom
fix/web-8477-module-detail-500
Jul 30, 2026
Merged

fix: cast avatar_asset to CharField to resolve mixed type errors in URL concatenation#9512
dheeru0198 merged 1 commit into
previewfrom
fix/web-8477-module-detail-500

Conversation

@Program2113

@Program2113 Program2113 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Description

Module detail pages return a 500 and the UI falls back to its "no modules exist" empty state. Creating a work item with a module attached shows "work item creation failed" — even though the work item is created and is linked to the module.

Both are the same bug: a Django 4.2 → 5.2 regression in every queryset that builds a user's avatar URL in SQL.

Concat(Value("/api/assets/v2/static/"), "assignees__avatar_asset", Value("/"))

On Django 5.x, ConcatPair.as_postgresql resolves the output field of each argument to decide whether it needs a text cast. avatar_asset is a UUID FK column, so pairing it with a CharField raises:

django.core.exceptions.FieldError: Expression contains mixed types: UUIDField, CharField. You must set output_field.

The affected querysets are lazy, so this fires during response rendering — after DRF's exception handler is out of scope — which is why it surfaces as a bare 500 with no useful error body.

Why the module list works but module detail doesn't: ModuleViewSet.list uses .values() and never touches the assignee distribution. retrieve builds it, and dies.

Why work-item creation reports failure: addIssueToModule in apps/web/core/components/issues/issue-modal/base.tsx awaits

Promise.all([changeModulesInIssue(...), ...moduleIds.map(fetchModuleDetails)])

fetchModuleDetails GETs the module detail endpoint. That 500s, Promise.all rejects, and the catch in handleCreateIssue raises the error toast — after both writes have already committed. So the reported failure is cosmetic, but it leaves users re-creating work items that already exist.

Fix: cast the UUID column to text at all 24 call sites (11 files), matching each site's existing output_field= style:

Cast("assignees__avatar_asset", models.CharField())

No behaviour change — avatar_url renders identically (/api/assets/v2/static/<uuid>/), asserted in the new tests.

Setting output_field on the outer Concat does not work here: Concat.__init__ wraps its arguments in nested ConcatPairs that each resolve their own output field, so the inner pair still raises.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Screenshots and Media (if applicable)

N/A — server-side 500 with no response body. Reproduced and verified at the API layer instead; see below.

Test Scenarios

Added apps/api/plane/tests/contract/app/test_avatar_url_annotation.py — 9 contract tests covering the affected endpoints, with a user whose avatar comes from a FileAsset so the previously-broken Case branch is actually exercised. All 9 fail on the unpatched tree and pass with the fix.

Endpoints confirmed returning 500 / FieldError before this change, and 200 after:

Endpoint Path
Module detail GET .../modules/<id>/
Archived module detail GET .../archived-modules/<id>/
Archived cycle detail GET .../archived-cycles/<id>/
Cycle analytics GET .../cycles/<id>/analytics/?type=issues
Workspace analytics GET .../analytics/?x_axis=assignees__id&y_axis=issue_count
Default analytics GET .../default-analytics/
Project advance analytics stats GET .../advance-analytics-stats/?type=work-items (also with &cycle_id=)
Entity search (user mentions) GET .../entity-search/?query_type=user_mention
Cycle transfer work items POST .../cycles/<id>/transfer-issues/
Public space work items GET /api/public/anchor/<anchor>/issues/

Also patched, same pattern, not covered by the new tests:

  • plane/bgtasks/analytic_plot_export.py — the analytics CSV export. The endpoint returns 200 because the work is deferred, so the FieldError lands in the Celery worker rather than the response.
  • plane/space/utils/grouper.py — vote/reaction actor avatars in the public space grouper.
  • plane/app/views/search/base.py, plane/app/views/module/archive.py, plane/app/views/cycle/base.py, plane/app/views/cycle/archive.py — remaining sites of the identical expression, latent under other query parameters.

Verified there are no remaining unpatched instances: all 24 Concat( call sites under apps/api/plane are this avatar pattern, and all now cast.

Full suite: 492 passed, 12 failed. All 12 failures are pre-existing test_authentication.py magic-link tests that need instance configuration — confirmed failing identically on a clean checkout of the parent commit.

ruff check --fix apps/api (what CI runs) exits 0. The 3 F401s it auto-fixes are pre-existing in plane/app/views/issue/sub_issue.py and plane/app/views/project/invite.py, both untouched here.

References

WEB-8477 — Bug 1 ("Unable to create work item after selecting module").

Introduced by #9325 (chore: upgrade Django 4.2 → 5.2).

This also repairs the public-space work items endpoint and several analytics
endpoints that fail the same way, so WEB-8477 Bug 2 (empty Cycles chart) and
Bug 3 (published project URL) are worth re-testing against this branch — both
hit affected endpoints, but neither was confirmed as this root cause.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed avatar URLs so users with uploaded avatar assets display correctly across analytics, search, issue, cycle, and module views.
    • Improved avatar rendering in archived views, issue transfers, public issue activity, and exported analytics.
  • Tests

    • Added coverage verifying avatar URLs across key analytics, search, archive, and issue-transfer workflows.

@CLAassistant

CLAassistant commented Jul 30, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@Program2113 Program2113 changed the title fix: cast avatar_asset to CharField to resolve mixed type errors in U… fix: cast avatar_asset to CharField to resolve mixed type errors in URL concatenation Jul 30, 2026
@makeplane

makeplane Bot commented Jul 30, 2026

Copy link
Copy Markdown

Linked to Plane Work Item(s)

References

This comment was auto-generated by Plane

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Avatar asset identifiers are explicitly cast to character fields before URL concatenation across analytics, distribution, search, issue actor, export, and cycle-transfer queries. New contract tests cover affected endpoints and verify static asset URL output.

Changes

Avatar URL casting

Layer / File(s) Summary
Analytics and distribution annotations
apps/api/plane/app/views/analytic/*, apps/api/plane/app/views/cycle/*, apps/api/plane/app/views/module/*
Avatar asset fields are cast to CharField before constructing static asset URLs in analytics, module, and cycle distribution queries.
Search and issue actor annotations
apps/api/plane/app/views/search/base.py, apps/api/plane/space/utils/grouper.py, apps/api/plane/space/views/issue.py
User mention, vote, reaction, and grouped issue avatar URL expressions now cast asset identifiers before concatenation.
Background paths and regression coverage
apps/api/plane/bgtasks/analytic_plot_export.py, apps/api/plane/utils/cycle_transfer_issues.py, apps/api/plane/tests/contract/app/test_avatar_url_annotation.py
Background query paths apply the cast, and contract tests exercise related endpoints and expected asset URL formatting.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: dheeru0198

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main fix to avatar_asset URL concatenation.
Description check ✅ Passed The description covers all required sections with clear context, type of change, tests, media notes, and references.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/web-8477-module-detail-500

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.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/plane/space/views/issue.py (1)

713-722: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the reaction actor fields for reaction URLs.

Line 716 still casts votes__actor__avatar_asset, so reaction payloads can return the wrong actor avatar (or null when there is no vote). Use issue_reactions__actor__* throughout this Case.

Proposed fix
-                                            votes__actor__avatar_asset__isnull=False,
+                                            issue_reactions__actor__avatar_asset__isnull=False,
                                             then=Concat(
                                                 Value("/api/assets/v2/static/"),
-                                                Cast("votes__actor__avatar_asset", CharField()),
+                                                Cast("issue_reactions__actor__avatar_asset", CharField()),
                                                 Value("/"),
                                             ),
                                         ),
                                         When(
-                                            votes__actor__avatar_asset__isnull=True,
-                                            then=F("votes__actor__avatar"),
+                                            issue_reactions__actor__avatar_asset__isnull=True,
+                                            then=F("issue_reactions__actor__avatar"),
                                         ),
🤖 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 `@apps/api/plane/space/views/issue.py` around lines 713 - 722, Update the
reaction URL Case expression to use issue_reactions__actor__* fields
consistently instead of votes__actor__* fields, including the avatar asset null
check, Cast, and fallback avatar reference. Preserve the existing URL
construction and fallback behavior while applying these changes throughout the
Case.
🤖 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.

Outside diff comments:
In `@apps/api/plane/space/views/issue.py`:
- Around line 713-722: Update the reaction URL Case expression to use
issue_reactions__actor__* fields consistently instead of votes__actor__* fields,
including the avatar asset null check, Cast, and fallback avatar reference.
Preserve the existing URL construction and fallback behavior while applying
these changes throughout the Case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6c5d1e1d-7bc9-4c7c-b643-a3f1df07f31e

📥 Commits

Reviewing files that changed from the base of the PR and between 7564480 and b01f9ac.

📒 Files selected for processing (12)
  • apps/api/plane/app/views/analytic/base.py
  • apps/api/plane/app/views/analytic/project_analytics.py
  • apps/api/plane/app/views/cycle/archive.py
  • apps/api/plane/app/views/cycle/base.py
  • apps/api/plane/app/views/module/archive.py
  • apps/api/plane/app/views/module/base.py
  • apps/api/plane/app/views/search/base.py
  • apps/api/plane/bgtasks/analytic_plot_export.py
  • apps/api/plane/space/utils/grouper.py
  • apps/api/plane/space/views/issue.py
  • apps/api/plane/tests/contract/app/test_avatar_url_annotation.py
  • apps/api/plane/utils/cycle_transfer_issues.py

@dheeru0198
dheeru0198 merged commit 027a5a0 into preview Jul 30, 2026
17 of 18 checks passed
@dheeru0198
dheeru0198 deleted the fix/web-8477-module-detail-500 branch July 30, 2026 14:59
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.

3 participants