Skip to content

fix: scope Personal feed filter to personal projects only#4969

Open
aseckin wants to merge 5 commits into
mainfrom
claude/issue-1520-personal-filter-shouldnt-have-private
Open

fix: scope Personal feed filter to personal projects only#4969
aseckin wants to merge 5 commits into
mainfrom
claude/issue-1520-personal-filter-shouldnt-have-private

Conversation

@aseckin

@aseckin aseckin commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Closes #1520

Summary

The "Personal" toggle on the "My questions and posts" feed previously sent access=private, which on the backend matches every post whose default project has no default_permission — i.e. private tournaments and other private projects as well as personal projects. Users saw non-personal private questions under the "Personal" chip.

This introduces a dedicated personal access value that filters strictly by default_project.type == PERSONAL_PROJECT, and wires the frontend "Personal" filter to it. The existing access=private behavior is unchanged.

Changes

  • posts/serializers.py — add PERSONAL to PostFilterSerializer.Access.
  • posts/models.py — add Post.objects.filter_personal().
  • posts/services/feed.py — route access=personal to the new filter.
  • front_end/.../my_questions_and_posts.tsx — send access=personal.

Test plan

  • Backend tests pass (pytest).
  • Frontend typecheck/lint pass.
  • Manually verify: on the "My questions and posts" feed, toggling "Personal" shows only questions whose default project is a personal project, and not questions in private tournaments.

Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a new Personal access filter to question and post feeds, allowing users to browse posts from their personal space.
  • Bug Fixes
    • Fixed filter chip selection so the Personal option is correctly recognized and marked as active.
  • Other
    • Expanded feed filtering to handle the Personal access mode consistently alongside existing access levels.

The "Personal" toggle in the "My questions and posts" feed previously
sent `access=private`, which matched every post whose default project
had no default_permission — including private tournaments and other
private projects, not just personal projects. Introduce a dedicated
`personal` access value backed by a `Post.filter_personal()` manager
method that filters by `default_project.type == PERSONAL_PROJECT`, and
switch the frontend filter to use it.

Closes #1520

Co-authored-by: Sylvain <SylvainChevalier@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0a014dc4-3e05-4f75-b11a-72e5466a69ea

📥 Commits

Reviewing files that changed from the base of the PR and between f7f00fb and a6187c5.

📒 Files selected for processing (1)
  • posts/services/feed.py

📝 Walkthrough

Walkthrough

Adds a "personal" access filter distinct from "private". Backend gains a PostQuerySet.filter_personal method and a PERSONAL access enum value, applied in the feed service. Frontend filter chip now targets "personal" instead of "private".

Changes

Personal Access Filter

Layer / File(s) Summary
Backend filter contract and queryset
posts/serializers.py, posts/models.py
Adds PERSONAL = "personal" to PostFilterSerializer.Access and a new filter_personal method on PostQuerySet that filters posts by default_project__type == PERSONAL_PROJECT.
Feed service access filtering
posts/services/feed.py
Extends the get_posts_feed access branching to handle PERSONAL by calling qs.filter_personal().
Frontend filter chip update
front_end/src/app/(main)/questions/components/feed_filters/my_questions_and_posts.tsx
Updates the POST_ACCESS_FILTER option's value and active check from "private" to "personal".

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

Poem

A private hutch, a personal den,
Now the feed knows where to wend 🐇
One filter hops, the chip shines bright,
Personal posts are sorted right.
A tiny tweak, but clear and neat—
The rabbit’s trail is clean and sweet. 🥕

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: scoping the Personal filter to personal projects only.
Linked Issues check ✅ Passed [#1520] The Personal feed filter now targets personal projects and excludes private-project questions as requested.
Out of Scope Changes check ✅ Passed All code changes align with the Personal filter fix, and no unrelated scope additions are evident.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-1520-personal-filter-shouldnt-have-private

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.

Collapse filter_personal()'s return to a single line and replace the
three sequential access-filter if-branches with a dict dispatch so
get_posts_feed stays under the C901 complexity limit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@aseckin aseckin requested a review from hlbmtc July 1, 2026 10:07
@aseckin

aseckin commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

QA passed, pending code review @hlbmtc

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🚀 Preview Environment

Your preview environment is ready!

Resource Details
🌐 Preview URL https://metaculus-pr-4969-claude-issue-1520-personal-fil-preview.mtcl.cc
📦 Docker Image ghcr.io/metaculus/metaculus:claude-issue-1520-personal-filter-shouldnt-have-private-a6187c5
🗄️ PostgreSQL NeonDB branch preview/pr-4969-claude-issue-1520-personal-fil
Redis Fly Redis mtc-redis-pr-4969-claude-issue-1520-personal-fil

Details

  • Commit: bbb16d00ed268819d924e1f834019f459b794f05
  • Branch: claude/issue-1520-personal-filter-shouldnt-have-private
  • Fly App: metaculus-pr-4969-claude-issue-1520-personal-fil

ℹ️ Preview Environment Info

Isolation:

  • PostgreSQL and Redis are fully isolated from production
  • Each PR gets its own database branch and Redis instance
  • Changes pushed to this PR will trigger a new deployment

Limitations:

  • Background workers and cron jobs are not deployed in preview environments
  • If you need to test background jobs, use Heroku staging environments

Cleanup:

  • This preview will be automatically destroyed when the PR is closed

@aseckin aseckin marked this pull request as ready for review July 2, 2026 12:22
Comment thread posts/services/feed.py Outdated
Comment on lines +225 to +231
access_filters = {
PostFilterSerializer.Access.PRIVATE: "filter_private",
PostFilterSerializer.Access.PUBLIC: "filter_public",
PostFilterSerializer.Access.PERSONAL: "filter_personal",
}
if access in access_filters:
qs = getattr(qs, access_filters[access])()

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.

@claude this makes function call implicit and distorts its signature

@claude claude Bot Jul 6, 2026

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.

Claude finished @hlbmtc's task in 1m 52s —— View job


Fair point — reverted the string-dispatch

You're right: getattr(qs, access_filters[access])() hid the calls to filter_private / filter_public / filter_personal from grep, IDE go-to-definition, and static type checking, and dropped their signatures behind a string lookup.

I only did that to keep get_posts_feed under the C901 complexity limit. Better fix: pull the explicit if/elif chain into a tiny helper so the call sites stay obvious and get_posts_feed's complexity only grows by one function call.

Pushed: f7f00fb

def _apply_access_filter(
    qs: Post.objects, access: PostFilterSerializer.Access | None
) -> Post.objects:
    if access == PostFilterSerializer.Access.PRIVATE:
        return qs.filter_private()
    if access == PostFilterSerializer.Access.PUBLIC:
        return qs.filter_public()
    if access == PostFilterSerializer.Access.PERSONAL:
        return qs.filter_personal()
    return qs

And in get_posts_feed:

# Filter by access
qs = _apply_access_filter(qs, access)

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.

@claude Nah, just make it inline -- no need to have a separate function

@claude claude Bot Jul 6, 2026

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.

Inlining the access filter

  • Read posts/services/feed.py
  • Remove _apply_access_filter helper and inline the branches
  • Verify ruff C901 complexity still passes
  • Commit and push

View job run • Branch: claude/issue-1520-personal-filter-shouldnt-have-private

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.

@claude Try again

@claude claude Bot Jul 6, 2026

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.

Claude finished @hlbmtc's task in 4m 16s —— View job


Inlined the access filter

  • Read posts/services/feed.py
  • Remove _apply_access_filter helper and inline the branches
  • Commit and push

Pushed: a6187c5

# Filter by access
if access == PostFilterSerializer.Access.PRIVATE:
    qs = qs.filter_private()
if access == PostFilterSerializer.Access.PUBLIC:
    qs = qs.filter_public()
if access == PostFilterSerializer.Access.PERSONAL:
    qs = qs.filter_personal()

Note: the earlier commit 1dd6acf had extracted this to a dict dispatch because get_posts_feed's cyclomatic complexity was right at the C901 limit of 56, and 3 sequential ifs pushed it to ~58. If CI now flags C901 on get_posts_feed, the fix should be raising [tool.ruff.lint.mccabe] max-complexity in pyproject.toml (or # noqa: C901 on the function def) — let me know which you prefer and I'll apply it.
• Branch: claude/issue-1520-personal-filter-shouldnt-have-private

Reverts the getattr-by-string dispatch that hid filter_private /
filter_public / filter_personal from grep and static analysis. Moves the
if/elif chain into _apply_access_filter so get_posts_feed stays under
the C901 complexity limit without losing explicit call sites.

Co-authored-by: Hlib <hlbmtc@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@claude claude Bot temporarily deployed to testing_env July 6, 2026 17:53 Inactive
@claude claude Bot temporarily deployed to testing_env July 6, 2026 17:53 Inactive
Remove the _apply_access_filter helper and put the three
if branches directly in get_posts_feed.

Co-authored-by: Hlib <hlbmtc@users.noreply.github.com>
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.

Personal filter shouldn't have private questions

2 participants