From ea2e95194779b0a24cbff2eb0465f69da778d86c Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:29:58 +0000 Subject: [PATCH 1/2] fix: recompute global leaderboard tags on post approval Global leaderboard tag computation depends on each question's open_time and scheduled_close_time. For newly created posts (in particular duplicated ones, whose timing fields the frontend clears) these fields are null at create time, so the tag pass in create_post produces no tags. Approval finalizes the times but did not re-run the tag pass, leaving duplicated posts without a global leaderboard tag until another lifecycle transition (close/resolve/unresolve) triggered a recompute. Co-authored-by: aseckin --- posts/services/common.py | 6 +++ tests/unit/test_posts/test_views.py | 57 +++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/posts/services/common.py b/posts/services/common.py index a4d7c158fb..c284bf6eac 100644 --- a/posts/services/common.py +++ b/posts/services/common.py @@ -455,6 +455,12 @@ def approve_post( ], ) + # Now that the question timing fields are finalized, recompute global + # leaderboard tags — at create time these were skipped because the + # required open_time / scheduled_close_time were not yet set (relevant + # for duplicated posts in particular, whose timing fields are cleared). + update_global_leaderboard_tags(post) + # Automatically update secondary and default project forecasting end date for project in post.get_related_projects(): if project.type in [ diff --git a/tests/unit/test_posts/test_views.py b/tests/unit/test_posts/test_views.py index 26ca9c32da..0bf9bbfc68 100644 --- a/tests/unit/test_posts/test_views.py +++ b/tests/unit/test_posts/test_views.py @@ -11,6 +11,8 @@ from projects.permissions import ObjectPermission from projects.services.common import get_site_main_project from questions.models import Question +from scoring.constants import LeaderboardScoreTypes +from scoring.models import Leaderboard 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 @@ -555,6 +557,61 @@ def test_approve_post(user1, user1_client, question_binary): assert response.status_code == status.HTTP_400_BAD_REQUEST +@freeze_time("2024-11-17T12:00Z") +def test_approve_post__updates_global_leaderboard_tags(user1, user1_client): + # Set up a matching global leaderboard on the site-main project so that + # approval-time question dates fall inside its window. + site_main = get_site_main_project() + Leaderboard.objects.create( + project=site_main, + score_type=LeaderboardScoreTypes.PEER_GLOBAL, + start_time=make_aware(datetime(2024, 1, 1)), + end_time=make_aware(datetime(2025, 1, 1)), + ) + + tournament = factory_project( + type=Project.ProjectTypes.TOURNAMENT, + visibility=Project.Visibility.NORMAL, + override_permissions={user1.pk: ObjectPermission.ADMIN}, + ) + # Simulate a duplicated post: question has no open/close/resolve times set + # yet (frontend clears them for duplicates), so create-time tag update + # cannot compute a leaderboard window. + question = create_question(question_type=Question.QuestionType.BINARY) + question.open_time = None + question.scheduled_close_time = None + question.scheduled_resolve_time = None + question.save() + post = factory_post( + author=user1, + curation_status=Post.CurationStatus.PENDING, + default_project=tournament, + question=question, + ) + # Pre-condition: create-time tag pass produced no leaderboard tag because + # the timing fields were null. + post.projects.set( + post.projects.exclude(type=Project.ProjectTypes.LEADERBOARD_TAG) + ) + + url = reverse("post-approve", kwargs={"pk": post.pk}) + response = user1_client.post( + url, + { + "published_at": "2024-11-17T11:00Z", + "open_time": "2024-11-17T11:00Z", + "cp_reveal_time": "2024-11-18T11:00Z", + "scheduled_close_time": "2024-11-19T11:00Z", + "scheduled_resolve_time": "2024-11-19T11:00Z", + }, + ) + assert response.status_code == status.HTTP_204_NO_CONTENT + + post.refresh_from_db() + leaderboard_tags = post.projects.filter(type=Project.ProjectTypes.LEADERBOARD_TAG) + assert leaderboard_tags.count() == 1 + + def test_repost(user1, user1_client, user2, user2_client, question_binary): default_project = factory_project( type=Project.ProjectTypes.TOURNAMENT, From 9f6bc25ee1f80b8ee34d360b30b126888c6a0f17 Mon Sep 17 00:00:00 2001 From: Atakan Seckin Date: Wed, 1 Jul 2026 13:02:27 +0200 Subject: [PATCH 2/2] style: apply ruff formatting to test_views.py Co-Authored-By: Claude Opus 4.8 --- tests/unit/test_posts/test_views.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/unit/test_posts/test_views.py b/tests/unit/test_posts/test_views.py index 0bf9bbfc68..fe83f0edd9 100644 --- a/tests/unit/test_posts/test_views.py +++ b/tests/unit/test_posts/test_views.py @@ -590,9 +590,7 @@ def test_approve_post__updates_global_leaderboard_tags(user1, user1_client): ) # Pre-condition: create-time tag pass produced no leaderboard tag because # the timing fields were null. - post.projects.set( - post.projects.exclude(type=Project.ProjectTypes.LEADERBOARD_TAG) - ) + post.projects.set(post.projects.exclude(type=Project.ProjectTypes.LEADERBOARD_TAG)) url = reverse("post-approve", kwargs={"pk": post.pk}) response = user1_client.post(