From 2fa8c08da301a6e352eb09674d4113e918da41b5 Mon Sep 17 00:00:00 2001 From: Erik Vroon Date: Wed, 12 Feb 2025 12:27:14 +0100 Subject: [PATCH] Add teams with players (#1118) fixes https://github.com/evroon/bracket/issues/978 --- backend/bracket/routes/teams.py | 41 +++++++++++++------ .../tests/integration_tests/api/teams_test.py | 5 ++- frontend/public/locales/en/common.json | 6 ++- .../forms/player_create_csv_input.tsx | 24 +++++++---- 4 files changed, 52 insertions(+), 24 deletions(-) diff --git a/backend/bracket/routes/teams.py b/backend/bracket/routes/teams.py index b3b6f5a3..41077aac 100644 --- a/backend/bracket/routes/teams.py +++ b/backend/bracket/routes/teams.py @@ -1,3 +1,4 @@ +import csv import os from uuid import uuid4 @@ -9,6 +10,7 @@ from heliclockter import datetime_utc from bracket.database import database from bracket.logic.subscriptions import check_requirement from bracket.logic.teams import get_team_logo_path +from bracket.models.db.player import PlayerBody from bracket.models.db.team import ( FullTeamWithPlayers, Team, @@ -34,6 +36,7 @@ from bracket.routes.util import ( team_with_players_dependency, ) from bracket.schema import players_x_teams, teams +from bracket.sql.players import get_all_players_in_tournament, insert_player from bracket.sql.teams import ( get_team_by_id, get_team_count, @@ -209,19 +212,31 @@ async def create_multiple_teams( user: UserPublic = Depends(user_authenticated_for_tournament), _: Tournament = Depends(disallow_archived_tournament), ) -> SuccessResponse: - team_names = [team.strip() for team in team_body.names.split("\n") if len(team) > 0] - existing_teams = await get_teams_with_members(tournament_id) - check_requirement(existing_teams, user, "max_teams", additions=len(team_names)) + reader = list(csv.reader(team_body.names.split("\n"), delimiter=",")) + teams_and_players = [ + (row[0], row[1:] if len(row) > 1 else []) for row in reader if len(row) > 0 + ] + players = [player for row in teams_and_players for player in row[1]] - for team_name in team_names: - await database.execute( - query=teams.insert(), - values=TeamInsertable( - name=team_name, - active=team_body.active, - created=datetime_utc.now(), - tournament_id=tournament_id, - ).model_dump(), - ) + existing_teams = await get_teams_with_members(tournament_id) + existing_players = await get_all_players_in_tournament(tournament_id) + + check_requirement(existing_teams, user, "max_teams", additions=len(reader)) + check_requirement(existing_players, user, "max_players", additions=len(players)) + + async with database.transaction(): + for team_name, players in teams_and_players: + await database.execute( + query=teams.insert(), + values=TeamInsertable( + name=team_name, + active=team_body.active, + created=datetime_utc.now(), + tournament_id=tournament_id, + ).model_dump(), + ) + for player in players: + player_body = PlayerBody(name=player, active=team_body.active) + await insert_player(player_body, tournament_id) return SuccessResponse() diff --git a/backend/tests/integration_tests/api/teams_test.py b/backend/tests/integration_tests/api/teams_test.py index 0b28b391..52407f5e 100644 --- a/backend/tests/integration_tests/api/teams_test.py +++ b/backend/tests/integration_tests/api/teams_test.py @@ -4,7 +4,7 @@ import pytest from bracket.database import database from bracket.models.db.team import Team -from bracket.schema import teams +from bracket.schema import players, teams from bracket.utils.db import fetch_one_parsed_certain from bracket.utils.dummy_records import DUMMY_MOCK_TIME, DUMMY_TEAM1 from bracket.utils.http import HTTPMethod @@ -57,12 +57,13 @@ async def test_create_team( async def test_create_teams( startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext ) -> None: - body = {"names": "Team -1\nTeam -2", "active": True} + body = {"names": "Team -1,Player 42,Player 43\nTeam -2", "active": True} response = await send_tournament_request( HTTPMethod.POST, "teams_multi", auth_context, None, body ) assert response["success"] is True await assert_row_count_and_clear(teams, 2) + await assert_row_count_and_clear(players, 3) @pytest.mark.asyncio(loop_scope="session") diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index caa6ca60..e299679b 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -31,8 +31,8 @@ "all_matches_scheduled_description": "Matches have been scheduled on all courts in this round. Add a new round or add a new court for more matches.", "api_docs_title": "API docs", "archive_tournament_button": "Archive Tournament", - "archived_label": "Archived", "archived_header_label": "This tournament is archived. It is now read-only.", + "archived_label": "Archived", "at_least_one_player_validation": "Enter at least one player", "at_least_one_team_validation": "Enter at least one team", "at_least_two_team_validation": "Need at least two teams", @@ -116,6 +116,7 @@ "empty_name_validation": "Name cannot be empty", "empty_password_validation": "Password cannot be empty", "empty_slot": "Empty slot", + "example_label": "Example:", "filter_stage_item_label": "Filter on stage item", "filter_stage_item_placeholder": "No filter", "forgot_password_button": "Forgot password?", @@ -152,7 +153,7 @@ "multiple_players_input_placeholder": "Player 1", "multiple_players_title": "Multiple Players", "multiple_teams": "Multiple Teams", - "multiple_teams_input_label": "Add multiple teams. Put every team on a separate line", + "multiple_teams_input_label": "Add multiple teams. Put every team on a separate line. You can also add players per team, separated by `,`.", "multiple_teams_input_placeholder": "Team 1", "name_field_text": "name", "name_input_label": "Name", @@ -236,6 +237,7 @@ "team_count_input_round_robin_label": "Number of teams advancing from the previous stage", "team_count_select_elimination_label": "Number of teams advancing from the previous stage", "team_count_select_elimination_placeholder": "2, 4, 8 etc.", + "team_member_select_placeholder": "Pick team members for this team", "team_member_select_title": "Team members", "team_name_input_placeholder": "Best Team Ever", "team_title": "Team", diff --git a/frontend/src/components/forms/player_create_csv_input.tsx b/frontend/src/components/forms/player_create_csv_input.tsx index 2dbcb4af..6267c6c5 100644 --- a/frontend/src/components/forms/player_create_csv_input.tsx +++ b/frontend/src/components/forms/player_create_csv_input.tsx @@ -1,4 +1,4 @@ -import { Textarea } from '@mantine/core'; +import { Code, Text, Textarea } from '@mantine/core'; import { UseFormReturnType } from '@mantine/form'; import { useTranslation } from 'next-i18next'; import React from 'react'; @@ -18,11 +18,21 @@ export function MultiPlayersInput({ form }: { form: UseFormReturnType }) { export function MultiTeamsInput({ form }: { form: UseFormReturnType }) { const { t } = useTranslation(); return ( -