Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions mittab/apps/tab/migrations/0037_debater_ranking_public.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.25 on 2026-03-27 18:22

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('tab', '0036_alter_outround_room_nullable'),
]

operations = [
migrations.AddField(
model_name='debater',
name='ranking_public',
field=models.BooleanField(default=True),
),
]
1 change: 1 addition & 0 deletions mittab/apps/tab/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ class Debater(models.Model):
novice_status = models.IntegerField(choices=NOVICE_CHOICES)
tiebreaker = models.IntegerField(unique=True, null=True, blank=True)
apda_id = models.IntegerField(blank=True, null=True, default=-1)
ranking_public = models.BooleanField(default=True)

def save(self,
force_insert=False,
Expand Down
7 changes: 5 additions & 2 deletions mittab/apps/tab/views/public_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,12 @@ def public_speaker_rankings(request):
speaker_lists = {
"varsity": [
entry for entry in varsity_speakers
if entry[0].novice_status == Debater.VARSITY
if entry[0].ranking_public
],
"novice": [
entry for entry in novice_speakers
if entry[0].ranking_public
],
"novice": novice_speakers,
}
rows = {
slug: build_public_speaker_rows(
Expand Down
110 changes: 109 additions & 1 deletion mittab/libs/tests/views/test_public_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.contrib.auth import get_user_model
from nplusone.core import profiler

from mittab.apps.tab.models import (Judge, Room, TabSettings, Team,
from mittab.apps.tab.models import (Debater, Judge, Room, TabSettings, Team,
Round, Outround, RoundStats)
from mittab.apps.tab.public_rankings import (
get_ballot_round_settings,
Expand Down Expand Up @@ -718,6 +718,114 @@ def test_public_display_flags_follow_setting_changes(self):
self.assertTrue(flags["ballots"])


def test_debater_ranking_public_false_excluded_from_varsity_speakers(self):
"""A debater with ranking_public=False should not appear in varsity speaker rankings."""
client = Client()
Debater.objects.update(ranking_public=True)
set_ranking_settings("varsity", True, include_speaks=True, max_visible=1000)
caches["public"].clear()
cache_logic.clear_cache()

debater = Debater.objects.first()
self.assertIsNotNone(debater, "Expected at least one debater in the fixture")

# Confirm the debater appears when ranking_public=True
response = client.get(reverse("public_speaker_rankings"))
self.assertEqual(response.status_code, 200)
self.assertIn(debater.name, response.content.decode(),
"Debater should appear in rankings when ranking_public=True; "
"ensure this debater has round stats in the fixture")

# Now opt the debater out and verify they are hidden
debater.ranking_public = False
debater.save()
caches["public"].clear()
cache_logic.clear_cache()

response = client.get(reverse("public_speaker_rankings"))
self.assertEqual(response.status_code, 200)
self.assertNotIn(debater.name, response.content.decode())

def test_novice_debater_ranking_public_false_excluded_from_novice_speakers(self):
"""A novice debater with ranking_public=False should not appear in any section."""
client = Client()
Debater.objects.update(ranking_public=True)
set_ranking_settings("varsity", True, include_speaks=True, max_visible=1000)
set_ranking_settings("novice", True, include_speaks=True, max_visible=1000)
caches["public"].clear()
cache_logic.clear_cache()

debater = Debater.objects.filter(novice_status=Debater.NOVICE).first()
self.assertIsNotNone(debater, "Expected at least one novice debater in the fixture")

# Confirm the debater appears when ranking_public=True
response = client.get(reverse("public_speaker_rankings"))
self.assertEqual(response.status_code, 200)
self.assertIn(debater.name, response.content.decode(),
"Novice debater should appear in rankings when ranking_public=True; "
"ensure this debater has round stats in the fixture")

# Now opt the debater out and verify they are hidden from both sections
debater.ranking_public = False
debater.save()
caches["public"].clear()
cache_logic.clear_cache()

response = client.get(reverse("public_speaker_rankings"))
self.assertEqual(response.status_code, 200)
self.assertNotIn(debater.name, response.content.decode())

def test_novice_debater_ranking_public_true_appears_in_varsity_and_novice(self):
"""A novice debater with ranking_public=True should appear in both varsity and novice sections."""
client = Client()
Debater.objects.update(ranking_public=True)
set_ranking_settings("varsity", True, include_speaks=True, max_visible=1000)
set_ranking_settings("novice", True, include_speaks=True, max_visible=1000)
caches["public"].clear()
cache_logic.clear_cache()

debater = Debater.objects.filter(novice_status=Debater.NOVICE).first()
self.assertIsNotNone(debater, "Expected at least one novice debater in the fixture")

response = client.get(reverse("public_speaker_rankings"))
self.assertEqual(response.status_code, 200)
content = response.content.decode()
self.assertIn("Varsity Speakers", content)
self.assertIn("Novice Speakers", content)

# Verify the debater's name appears in both the varsity and novice sections
varsity_start = content.index("Varsity Speakers")
novice_start = content.index("Novice Speakers")
varsity_section = content[varsity_start:novice_start]
novice_section = content[novice_start:]
self.assertIn(debater.name, varsity_section,
"Novice debater should appear in the varsity (all-speakers) section")
self.assertIn(debater.name, novice_section,
"Novice debater should appear in the novice section")

def test_varsity_debater_does_not_appear_in_novice_speakers(self):
"""A varsity-only debater should not appear in the novice speaker rankings section."""
client = Client()
Debater.objects.update(ranking_public=True)
set_ranking_settings("varsity", True, include_speaks=True, max_visible=1000)
set_ranking_settings("novice", True, include_speaks=True, max_visible=1000)
caches["public"].clear()
cache_logic.clear_cache()

varsity_debater = Debater.objects.filter(novice_status=Debater.VARSITY).first()
self.assertIsNotNone(varsity_debater, "Expected at least one varsity debater in the fixture")

response = client.get(reverse("public_speaker_rankings"))
self.assertEqual(response.status_code, 200)
content = response.content.decode()

self.assertIn("Novice Speakers", content,
"Novice section should be present in the response")
novice_start = content.index("Novice Speakers")
novice_section = content[novice_start:]
self.assertNotIn(varsity_debater.name, novice_section,
"Varsity debater should not appear in the novice section")

def test_n_plus_one(self):
client = Client()

Expand Down
Loading