Skip to content
Merged
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
1 change: 1 addition & 0 deletions mittab/apps/tab/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def motion_text_truncated(self, obj):
admin.site.register(models.TabSettings)
admin.site.register(models.Room)
admin.site.register(models.RoomTag)
admin.site.register(models.RankingGroup)
admin.site.register(models.Bye)
admin.site.register(models.NoShow)
admin.site.register(models.BreakingTeam)
Expand Down
52 changes: 47 additions & 5 deletions mittab/apps/tab/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,10 @@ class Media:


class TeamForm(forms.ModelForm):
debaters = forms.ModelMultipleChoiceField(queryset=Debater.objects.all(),
required=False)
debaters = forms.ModelMultipleChoiceField(
queryset=Debater.objects.all(),
required=False
)

def clean_debaters(self):
data = self.cleaned_data["debaters"]
Expand Down Expand Up @@ -233,9 +235,6 @@ class Meta:


class DebaterForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DebaterForm, self).__init__(*args, **kwargs)

class Meta:
model = Debater
exclude = ["tiebreaker"]
Expand Down Expand Up @@ -763,6 +762,49 @@ def __init__(self, *args, **kwargs):
self.fields.pop("judges")
self.fields.pop("rooms")


class RankingGroupForm(forms.ModelForm):
teams = forms.ModelMultipleChoiceField(
queryset=Team.objects.all(),
required=False,
)
debaters = forms.ModelMultipleChoiceField(
queryset=Debater.objects.all(),
required=False,
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
self.fields["teams"].initial = self.instance.teams.all()
self.fields["debaters"].initial = self.instance.debaters.all()

def save(self, commit=True):
ranking_group = super().save(commit=False)

def save_m2m():
ranking_group.teams.set(self.cleaned_data.get("teams", []))
ranking_group.debaters.set(self.cleaned_data.get("debaters", []))

if commit:
ranking_group.save()
save_m2m()
else:
self._save_m2m = save_m2m

return ranking_group

class Meta:
model = RankingGroup
fields = ("name", "teams", "debaters")


class MiniRankingGroupForm(RankingGroupForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields.pop("teams")
self.fields.pop("debaters")

class BackupForm(forms.Form):
backup_name = forms.CharField(
max_length=255,
Expand Down
22 changes: 22 additions & 0 deletions mittab/apps/tab/migrations/0032_rankinggroup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 3.2.25 on 2025-11-10 02:30

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('tab', '0031_remove_noshow_lenient_late'),
]

operations = [
migrations.CreateModel(
name='RankingGroup',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('debaters', models.ManyToManyField(blank=True, related_name='ranking_groups', to='tab.Debater')),
('teams', models.ManyToManyField(blank=True, related_name='ranking_groups', to='tab.Team')),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 3.2.25 on 2026-02-20 13:22

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('tab', '0032_rankinggroup'),
('tab', '0034_add_motion_model'),
]

operations = [
]
19 changes: 19 additions & 0 deletions mittab/apps/tab/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ def with_preloaded_relations_for_tab_card(cls):
"debaters__roundstats_set__round",
"debaters__team_set",
"debaters__team_set__no_shows",
"ranking_groups",
)

@classmethod
Expand Down Expand Up @@ -266,6 +267,7 @@ def with_preloaded_relations_for_tabbing(cls):
"debaters__roundstats_set__round",
"debaters__team_set",
"debaters__team_set__no_shows",
"ranking_groups",
)

def set_unique_team_code(self):
Expand Down Expand Up @@ -705,6 +707,23 @@ def __str__(self):
return self.tag


class RankingGroup(models.Model):
name = models.CharField(max_length=255, unique=True)
teams = models.ManyToManyField(
"Team",
blank=True,
related_name="ranking_groups",
)
debaters = models.ManyToManyField(
"Debater",
blank=True,
related_name="ranking_groups",
)

def __str__(self):
return self.name


class Motion(models.Model):
"""
Represents a debate motion (topic) for a specific round.
Expand Down
31 changes: 28 additions & 3 deletions mittab/apps/tab/views/debater_views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.shortcuts import render
from django.utils.text import slugify

from mittab.apps.tab.forms import DebaterForm
from mittab.apps.tab.helpers import redirect_and_flash_error, \
Expand All @@ -12,8 +13,16 @@

def view_debaters(request):
# Get a list of (id,debater_name) tuples
c_debaters = [(debater.pk, debater.display, 0, "")
for debater in Debater.objects.all()]
c_debaters = [
(
debater.pk,
debater.display,
0,
"",
list(debater.ranking_groups.order_by("name").values_list("name", flat=True)),
)
for debater in Debater.objects.prefetch_related("ranking_groups").all()
]
return render(
request, "common/list_data.html", {
"item_type": "debater",
Expand All @@ -25,7 +34,7 @@ def view_debaters(request):
def view_debater(request, debater_id):
debater_id = int(debater_id)
try:
debater = Debater.objects.get(pk=debater_id)
debater = Debater.objects.prefetch_related("ranking_groups").get(pk=debater_id)
except Debater.DoesNotExist:
return redirect_and_flash_error(request, "No such debater")
if request.method == "POST":
Expand Down Expand Up @@ -132,9 +141,25 @@ def rank_debaters(request):
request
)

ranking_group_tables = []
ranking_groups = RankingGroup.objects.prefetch_related("debaters").order_by("name")
for ranking_group in ranking_groups:
debater_ids = {debater.id for debater in ranking_group.debaters.all()}
grouped_debaters = [
debater_entry for debater_entry in debaters
if debater_entry[0].id in debater_ids
]
if grouped_debaters:
ranking_group_tables.append({
"title": f"{ranking_group.name} Rankings",
"anchor": f"debater-ranking-group-{slugify(ranking_group.name)}",
"debaters": grouped_debaters,
})

return render(
request, "tab/rank_debaters_component.html", {
"debaters": debaters,
"nov_debaters": nov_debaters,
"debater_ranking_groups": ranking_group_tables,
"title": "Speaker Rankings"
})
28 changes: 24 additions & 4 deletions mittab/apps/tab/views/team_views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.http import HttpResponseRedirect
from django.contrib.auth.decorators import permission_required
from django.shortcuts import render
from django.utils.text import slugify

from mittab.apps.tab.forms import TeamForm, TeamEntryForm, ScratchForm
from mittab.libs.cacheing import cache_logic
Expand All @@ -25,9 +26,16 @@ def flags(team):
result |= TabFlags.TEAM_NOT_CHECKED_IN
return result

c_teams = [(team.id, team.display_backend, flags(team),
TabFlags.flags_to_symbols(flags(team)))
for team in Team.objects.all()]
c_teams = [
(
team.id,
team.display_backend,
flags(team),
TabFlags.flags_to_symbols(flags(team)),
list(team.ranking_groups.order_by("name").values_list("name", flat=True)),
)
for team in Team.objects.prefetch_related("ranking_groups").all()
]
all_flags = [[TabFlags.TEAM_CHECKED_IN, TabFlags.TEAM_NOT_CHECKED_IN]]
filters, symbol_text = TabFlags.get_filters_and_symbols(all_flags)
return render(
Expand Down Expand Up @@ -384,9 +392,21 @@ def rank_teams(request):
public=False
)

ranking_group_tables = []
ranking_groups = RankingGroup.objects.prefetch_related("teams").order_by("name")
for ranking_group in ranking_groups:
team_ids = {team.id for team in ranking_group.teams.all()}
grouped_teams = [team for team in teams if team[0].id in team_ids]
if grouped_teams:
ranking_group_tables.append({
"title": f"{ranking_group.name} Rankings",
"anchor": f"team-ranking-group-{slugify(ranking_group.name)}",
"teams": grouped_teams,
})

return render(request, "tab/rank_teams_component.html", {
"varsity": teams,
"novice": nov_teams,
"team_ranking_groups": ranking_group_tables,
"title": "Team Rankings"
})

63 changes: 61 additions & 2 deletions mittab/apps/tab/views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,17 @@

from mittab.apps.tab.archive import ArchiveExporter
from mittab.apps.tab.views.debater_views import get_speaker_rankings
from mittab.apps.tab.forms import MiniRoomTagForm, RoomTagForm, SchoolForm, RoomForm, \
UploadDataForm, ScratchForm, SettingsForm
from mittab.apps.tab.forms import (
MiniRankingGroupForm,
MiniRoomTagForm,
RankingGroupForm,
RoomTagForm,
SchoolForm,
RoomForm,
UploadDataForm,
ScratchForm,
SettingsForm,
)
from mittab.apps.tab.helpers import redirect_and_flash_error, \
redirect_and_flash_success
from mittab.apps.tab.models import *
Expand Down Expand Up @@ -596,6 +605,56 @@ def manage_room_tags(request):
{"room_tags": room_tags,
"form": form})


def ranking_group(request, group_id=None):
group = None
if group_id is not None:
group = RankingGroup.objects.filter(pk=group_id).first()

if request.method == "POST":
if request.POST.get("_method") == "DELETE":
if group is not None:
group.delete()
return redirect_and_flash_success(
request, "Ranking group deleted successfully"
)
return redirect_and_flash_error(request, "Ranking group does not exist")

form = RankingGroupForm(request.POST, instance=group)
if not form.is_valid():
return redirect_and_flash_error(request, "Error saving ranking group.")

ranking_group_instance = form.save()
path = reverse("manage_ranking_groups")
message = (
f"Ranking group {ranking_group_instance.name} "
f"{'updated' if group else 'created'} successfully"
)
return redirect_and_flash_success(request, message, path=path)

form = RankingGroupForm(instance=group)
return render(
request,
"common/data_entry.html",
{
"form": form,
"links": [],
"title": f"Viewing Ranking Group: {group.name}" if group else "Create Ranking Group",
},
)


def manage_ranking_groups(request):
if request.method == "POST":
return ranking_group(request)
form = MiniRankingGroupForm(request.POST or None)
ranking_groups = RankingGroup.objects.all().order_by("name")
return render(
request,
"pairing/manage_ranking_groups.html",
{"ranking_groups": ranking_groups, "form": form},
)

def batch_checkin(request):
round_numbers = list([i + 1 for i in range(TabSettings.get("tot_rounds"))])
all_round_numbers = [0] + round_numbers
Expand Down
Loading