diff --git a/backend/reviews/adapters.py b/backend/reviews/adapters.py index 06b1b64787..0b55b430fd 100644 --- a/backend/reviews/adapters.py +++ b/backend/reviews/adapters.py @@ -177,10 +177,21 @@ def get_recap_context( ) } + # Count submissions per speaker for the current conference + speaker_submission_counts = { + str(speaker_id): count + for speaker_id, count in Submission.objects.for_conference(conference.id) + .non_cancelled() + .values("speaker_id") + .annotate(count=Count("id")) + .values_list("speaker_id", "count") + } + return dict( admin_site.each_context(request), items=items, grants=grants, + speaker_submission_counts=speaker_submission_counts, review_session_id=review_session.id, audience_levels=conference.audience_levels.all(), submission_types=conference.submission_types.all(), diff --git a/backend/reviews/templates/proposals-recap.html b/backend/reviews/templates/proposals-recap.html index c988689ec0..d9331cba4f 100644 --- a/backend/reviews/templates/proposals-recap.html +++ b/backend/reviews/templates/proposals-recap.html @@ -285,6 +285,18 @@ tr:nth-of-type(odd) { background-color: var(--body-bg); } + + .speaker-multi-submission-badge { + display: inline-block; + background-color: #f0ad4e; + color: #fff; + font-size: 10px; + font-weight: bold; + padding: 2px 6px; + border-radius: 10px; + margin-left: 6px; + vertical-align: middle; + } + {% endwith %} {{forloop.counter}} @@ -676,6 +691,11 @@

Show proposals of type:

  • Speaker {{ item.speaker.fullname }} + {% with submission_count=speaker_submission_counts|get_item:speaker_id %} + {% if submission_count and submission_count > 1 %} + {{ submission_count }} talks + {% endif %} + {% endwith %}
  • diff --git a/backend/reviews/tests/test_admin.py b/backend/reviews/tests/test_admin.py index ae4c2e54ed..013815a5e9 100644 --- a/backend/reviews/tests/test_admin.py +++ b/backend/reviews/tests/test_admin.py @@ -833,8 +833,48 @@ def test_proposals_review_get_recap_context(rf): assert "review_session_id" in context assert "audience_levels" in context assert "all_statuses" in context + assert "speaker_submission_counts" in context assert context["review_session_id"] == review_session.id assert str(submission.speaker_id) in context["grants"] + # Verify speaker submission count is tracked + assert str(submission.speaker_id) in context["speaker_submission_counts"] + assert context["speaker_submission_counts"][str(submission.speaker_id)] == 1 + + +def test_proposals_review_get_recap_context_with_multiple_submissions_per_speaker(rf): + user = UserFactory(is_staff=True, is_superuser=True) + conference = ConferenceFactory() + + review_session = ReviewSessionFactory( + conference=conference, + session_type=ReviewSession.SessionType.PROPOSALS, + status=ReviewSession.Status.COMPLETED, + ) + AvailableScoreOptionFactory(review_session=review_session, numeric_value=0) + AvailableScoreOptionFactory(review_session=review_session, numeric_value=1) + + # Create a speaker with multiple submissions + speaker = UserFactory() + submission_1 = SubmissionFactory(conference=conference, speaker=speaker) + submission_2 = SubmissionFactory(conference=conference, speaker=speaker) + submission_3 = SubmissionFactory(conference=conference, speaker=speaker) + + # Create another speaker with only one submission + single_speaker = UserFactory() + single_submission = SubmissionFactory(conference=conference, speaker=single_speaker) + + request = rf.get("/") + request.user = user + + adapter = get_review_adapter(review_session) + items = adapter.get_recap_items_queryset(review_session).all() + context = adapter.get_recap_context(request, review_session, items, AdminSite()) + + assert "speaker_submission_counts" in context + # Speaker with 3 submissions should have count of 3 + assert context["speaker_submission_counts"][str(speaker.id)] == 3 + # Speaker with 1 submission should have count of 1 + assert context["speaker_submission_counts"][str(single_speaker.id)] == 1 def test_proposals_review_process_recap_post(rf):