diff --git a/backend/tournaments/management/commands/load_live_tournament_data.py b/backend/tournaments/management/commands/load_live_tournament_data.py new file mode 100644 index 0000000..a724d90 --- /dev/null +++ b/backend/tournaments/management/commands/load_live_tournament_data.py @@ -0,0 +1,145 @@ +from django.core.management.base import BaseCommand +from django.contrib.auth import get_user_model +from datetime import date +from tournaments.models import Tournament, Team, Player, Room, Round + +User = get_user_model() + + +class Command(BaseCommand): + help = 'Load sample data for a live tournament with 16 teams, players, rooms, and rounds' + + def handle(self, *args, **kwargs): + # Team names - creative science-themed names + team_names = [ + ("Quantum Quizzers", "MIT"), + ("Atomic Aces", "Stanford"), + ("Neural Networks", "UC Berkeley"), + ("Periodic Panthers", "Caltech"), + ("Genomic Giants", "Harvard"), + ("Photon Phasers", "Princeton"), + ("Molecular Mavericks", "Yale"), + ("Cosmic Crusaders", "Cornell"), + ("Electron Eagles", "Columbia"), + ("Particle Prodigies", "Carnegie Mellon"), + ("Galactic Geniuses", "Duke"), + ("Chemical Champions", "Northwestern"), + ("Velocity Victors", "UChicago"), + ("Thermal Titans", "Georgia Tech"), + ("Nucleus Knights", "Rice"), + ("Plasma Pioneers", "Johns Hopkins"), + ] + + # Get or create tournament director + director, _ = User.objects.get_or_create( + username='tournament_director', + defaults={ + 'first_name': 'Sarah', + 'last_name': 'Chen', + 'email': 'schen@stanford.edu', + 'bio': 'Tournament director for Stanford Science Bowl.', + 'school': 'Stanford University', + } + ) + + # Get the Stanford 2026 High School Tournament and set it to live + try: + tournament = Tournament.objects.get(name='Stanford 2026 High School Tournament') + # Update tournament to be live + tournament.status = 'IN_PROGRESS' + tournament.format = 'ROUND_ROBIN' + tournament.max_teams = 16 + tournament.current_teams = 16 + tournament.save() + + self.stdout.write(self.style.SUCCESS(f'Found tournament: {tournament.name}')) + self.stdout.write(self.style.SUCCESS(f'Set status to: IN_PROGRESS')) + + # Clear existing data for this tournament + tournament.teams.all().delete() + tournament.rooms.all().delete() + tournament.rounds.all().delete() + self.stdout.write(self.style.WARNING('Cleared existing tournament data')) + except Tournament.DoesNotExist: + self.stdout.write(self.style.ERROR('Stanford 2026 High School Tournament not found!')) + self.stdout.write(self.style.ERROR('Please run: python manage.py load_sample_tournaments first')) + return + + # Create 16 teams with 5 players each, assigned to 4 pools (A, B, C, D) + teams = [] + pools = ['A', 'B', 'C', 'D'] + for idx, (team_name, school) in enumerate(team_names, 1): + # Assign teams to pools: seeds 1,5,9,13 to Pool A, 2,6,10,14 to Pool B, etc. + pool = pools[(idx - 1) % 4] + team = Team.objects.create( + tournament=tournament, + name=team_name, + school=school, + seed=idx, + pool=pool, + ) + teams.append(team) + + # Create 5 players for each team + positions = ['Captain', 'Vice Captain', 'Member', 'Member', 'Alternate'] + for player_idx in range(5): + Player.objects.create( + team=team, + name=f"{team_name.split()[0]} Player {player_idx + 1}", + grade_level=str(11 if player_idx < 3 else 10), + total_points=0, + tossups_heard=0, + correct_buzzes=0, + incorrect_buzzes=0, + ) + + self.stdout.write(self.style.SUCCESS(f'Created team: {team_name} with 5 players')) + + # Create 8 rooms + room_names = [ + "Physics Lab A", + "Chemistry Lab B", + "Biology Lab C", + "Math Room 201", + "Earth Science 305", + "Energy Lab D", + "Auditorium East", + "Auditorium West", + ] + + rooms = [] + for room_name in room_names: + room = Room.objects.create( + tournament=tournament, + name=room_name, + status='IN_PROGRESS', + current_round=2, # Currently in round 2 + ) + rooms.append(room) + self.stdout.write(self.style.SUCCESS(f'Created room: {room_name}')) + + # Create 5 rounds with packet assignments + rounds_data = [ + (1, "Round 1 - Pool Play", "NSB 2024 Regionals Set 1"), + (2, "Round 2 - Pool Play", "NSB 2024 Regionals Set 2"), + (3, "Round 3 - Pool Play", "NSB 2024 Nationals Prelim"), + (4, "Round 4 - Semifinals", "NSB 2025 Regionals Set 1"), + (5, "Round 5 - Finals", "NSB 2025 Nationals Finals"), + ] + + for round_num, round_name, packet_name in rounds_data: + Round.objects.create( + tournament=tournament, + round_number=round_num, + name=round_name, + packet_name=packet_name, + ) + self.stdout.write(self.style.SUCCESS(f'Created round: {round_name}')) + + self.stdout.write(self.style.SUCCESS( + f'\n✅ Live tournament data loaded successfully!' + f'\n - 16 teams created' + f'\n - 80 players created (5 per team)' + f'\n - 8 rooms created' + f'\n - 5 rounds created' + )) diff --git a/backend/tournaments/migrations/0003_team_pool.py b/backend/tournaments/migrations/0003_team_pool.py new file mode 100644 index 0000000..9d9df06 --- /dev/null +++ b/backend/tournaments/migrations/0003_team_pool.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2025-12-20 23:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0002_tournament_registration_url_tournament_website_url'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='pool', + field=models.CharField(blank=True, help_text="Pool/Group assignment (e.g., 'A', 'B', 'C', 'D')", max_length=10), + ), + ] diff --git a/backend/tournaments/models.py b/backend/tournaments/models.py index 6c7bbd4..a99bf2a 100644 --- a/backend/tournaments/models.py +++ b/backend/tournaments/models.py @@ -91,6 +91,7 @@ class Team(models.Model): name = models.CharField(max_length=255) school = models.CharField(max_length=255) seed = models.IntegerField(null=True, blank=True) + pool = models.CharField(max_length=10, blank=True, help_text="Pool/Group assignment (e.g., 'A', 'B', 'C', 'D')") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/backend/tournaments/serializers.py b/backend/tournaments/serializers.py index ed008fc..ce92f2d 100644 --- a/backend/tournaments/serializers.py +++ b/backend/tournaments/serializers.py @@ -55,11 +55,11 @@ def get_director(self, obj): class TeamSerializer(serializers.ModelSerializer): """Serializer for teams.""" players_count = serializers.SerializerMethodField() - + class Meta: model = Team - fields = ['id', 'name', 'school', 'seed', 'players_count'] - + fields = ['id', 'name', 'school', 'seed', 'pool', 'players_count'] + def get_players_count(self, obj): return obj.players.count() diff --git a/docker-compose.yml b/docker-compose.yml index e8d7bac..74c4e5f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,21 +35,19 @@ services: db: condition: service_healthy -frontend: - image: node:20 - working_dir: /app - command: sh -c "npm install && npm run dev -- --host --force" - volumes: - - ./frontend:/app - - /app/node_modules - ports: - - "5173:5173" - environment: - - VITE_API_URL=http://localhost:8000 - - CHOKIDAR_USEPOLLING=true - - CHOKIDAR_INTERVAL=100 - stdin_open: true - tty: true + frontend: + image: node:20 + working_dir: /app + command: sh -c "npm install && npm run dev -- --host" + volumes: + - ./frontend:/app + - /app/node_modules + ports: + - "5173:5173" + environment: + - VITE_API_URL=http://localhost:8000 + stdin_open: true + tty: true volumes: postgres_data: diff --git a/frontend/src/features/tournaments/pages/TournamentDetailPage.tsx b/frontend/src/features/tournaments/pages/TournamentDetailPage.tsx index 7ddcc90..439065d 100644 --- a/frontend/src/features/tournaments/pages/TournamentDetailPage.tsx +++ b/frontend/src/features/tournaments/pages/TournamentDetailPage.tsx @@ -10,7 +10,7 @@ export function TournamentDetailPage() { const [teams, setTeams] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [activeTab, setActiveTab] = useState<'overview' | 'teams' | 'contact'>('overview'); + const [activeTab, setActiveTab] = useState<'overview' | 'teams' | 'pools' | 'contact'>('overview'); useEffect(() => { if (id) { @@ -170,6 +170,18 @@ export function TournamentDetailPage() { > Teams ({teams.length}) + {tournament.format === 'ROUND_ROBIN' && tournament.status === 'IN_PROGRESS' && ( + + )}