From da138c848a9e8a2a85138f112b9525503c56c30e Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 06:23:34 +0000 Subject: [PATCH] fix: scope main-feed pending queue to site_main posts The main-feed pending queue (/questions/?status=pending) previously showed pending posts from any project with visibility=NORMAL, which includes tournaments and question series. This meant users with elevated permission on those projects saw foreign-tournament pending posts in the main feed pending queue. Restrict the pending status branch in get_posts_feed() to posts that have site_main as their default project or as an M2M project when for_main_feed=True. Other curation statuses and non-main-feed callers (e.g. tournament pages) are unaffected. Fixes #3216 Co-authored-by: aseckin <3686968+aseckin@users.noreply.github.com> --- posts/services/feed.py | 13 ++++- .../test_posts/test_services/test_feed.py | 54 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/posts/services/feed.py b/posts/services/feed.py index 7f20902e29..57b15b41a4 100644 --- a/posts/services/feed.py +++ b/posts/services/feed.py @@ -13,6 +13,7 @@ posts_full_text_search, ) from projects.models import Project +from projects.services.common import get_site_main_project from questions.models import Question from users.models import User from utils.cache import cache_get_or_set @@ -140,7 +141,17 @@ def get_posts_feed( # noqa: C901 for status in statuses: if status in Post.CurationStatus: - post_status_q |= Q(curation_status=status) + status_q = Q(curation_status=status) + # Main-feed pending queue should only include posts attached to site_main, + # not pending posts from tournaments/question series with visibility=NORMAL. + if status == Post.CurationStatus.PENDING and for_main_feed: + site_main = get_site_main_project() + status_q &= Q(default_project=site_main) | Q( + pk__in=Post.projects.through.objects.filter( + project=site_main + ).values("post_id") + ) + post_status_q |= status_q if status == "open": post_status_q |= Q( Q(published_at__lte=now) diff --git a/tests/unit/test_posts/test_services/test_feed.py b/tests/unit/test_posts/test_services/test_feed.py index cad368dbcf..5c858b6d0c 100644 --- a/tests/unit/test_posts/test_services/test_feed.py +++ b/tests/unit/test_posts/test_services/test_feed.py @@ -4,9 +4,12 @@ from posts.models import PostUserSnapshot, Post from posts.services.feed import get_posts_feed +from projects.models import Project +from projects.services.common import get_site_main_project from questions.models import Question from tests.unit.test_comments.factories import factory_comment from tests.unit.test_posts.factories import factory_post +from tests.unit.test_projects.factories import factory_project from tests.unit.test_questions.factories import create_question, factory_forecast from tests.unit.utils import datetime_aware @@ -118,3 +121,54 @@ def test_get_posts_feed__exclude_unpublished(user1): posts = get_posts_feed(statuses=[Post.CurationStatus.PENDING]) assert len(posts) == 1 assert posts[0].id == post_pending.id + + +def test_get_posts_feed__pending_main_feed_excludes_tournament_posts(user1): + """ + The main-feed pending queue should only include posts attached to site_main, + not pending posts submitted into tournaments with visibility=NORMAL. + """ + + site_main = get_site_main_project() + tournament = factory_project( + type=Project.ProjectTypes.TOURNAMENT, + visibility=Project.Visibility.NORMAL, + ) + + post_pending_main = factory_post( + author=user1, + default_project=site_main, + question=create_question(question_type=Question.QuestionType.BINARY), + curation_status=Post.CurationStatus.PENDING, + ) + post_pending_main_via_m2m = factory_post( + author=user1, + default_project=tournament, + projects=[site_main], + question=create_question(question_type=Question.QuestionType.BINARY), + curation_status=Post.CurationStatus.PENDING, + ) + post_pending_tournament_only = factory_post( + author=user1, + default_project=tournament, + question=create_question(question_type=Question.QuestionType.BINARY), + curation_status=Post.CurationStatus.PENDING, + ) + + # Main-feed pending queue: only the site_main-attached posts show up + posts = get_posts_feed( + user=user1, + for_main_feed=True, + statuses=[Post.CurationStatus.PENDING], + ) + post_ids = {p.id for p in posts} + assert post_ids == {post_pending_main.id, post_pending_main_via_m2m.id} + assert post_pending_tournament_only.id not in post_ids + + # Without for_main_feed, the tournament-only pending post is included + posts = get_posts_feed( + user=user1, + statuses=[Post.CurationStatus.PENDING], + ) + post_ids = {p.id for p in posts} + assert post_pending_tournament_only.id in post_ids