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):
|