Use surrogate keys for PlayerTeam models

master
John Montagu, the 4th Earl of Sandvich 2024-11-21 18:21:43 -08:00
parent 061499b822
commit ba2b568259
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
8 changed files with 253 additions and 47 deletions

View File

@ -0,0 +1,34 @@
"""Add availability index
Revision ID: 47f0722b02b0
Revises: 7361c978e53d
Create Date: 2024-11-21 13:10:45.098947
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '47f0722b02b0'
down_revision = '7361c978e53d'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('players_teams_availability', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_players_teams_availability_end_time'), ['end_time'], unique=False)
batch_op.create_index(batch_op.f('ix_players_teams_availability_start_time'), ['start_time'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('players_teams_availability', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_players_teams_availability_start_time'))
batch_op.drop_index(batch_op.f('ix_players_teams_availability_end_time'))
# ### end Alembic commands ###

View File

@ -0,0 +1,64 @@
"""Add surrogate key to player_team and others
Revision ID: 6296c347731b
Revises: 5debac4cdf37
Create Date: 2024-11-21 10:30:09.333087
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6296c347731b'
down_revision = '5debac4cdf37'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('players_teams', schema=None) as batch_op:
batch_op.add_column(sa.Column('player_team_id', sa.Integer(), autoincrement=True, nullable=False))
with op.batch_alter_table('players_teams_availability', schema=None) as batch_op:
batch_op.add_column(sa.Column('player_team_id', sa.Integer(), nullable=False))
batch_op.drop_constraint(None, type_='foreignkey')
batch_op.create_foreign_key(None, 'players_teams', ['player_team_id'], ['player_team_id'])
batch_op.drop_column('team_id')
batch_op.drop_column('player_id')
with op.batch_alter_table('players_teams_roles', schema=None) as batch_op:
batch_op.add_column(sa.Column('player_team_role_id', sa.Integer(), autoincrement=True, nullable=False))
batch_op.add_column(sa.Column('player_team_id', sa.Integer(), nullable=False))
batch_op.create_unique_constraint(None, ['player_team_id', 'role'])
batch_op.drop_constraint(None, type_='foreignkey')
batch_op.create_foreign_key(None, 'players_teams', ['player_team_id'], ['player_team_id'])
batch_op.drop_column('team_id')
batch_op.drop_column('player_id')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('players_teams_roles', schema=None) as batch_op:
batch_op.add_column(sa.Column('player_id', sa.INTEGER(), nullable=False))
batch_op.add_column(sa.Column('team_id', sa.INTEGER(), nullable=False))
batch_op.drop_constraint(None, type_='foreignkey')
batch_op.create_foreign_key(None, 'players_teams', ['player_id', 'team_id'], ['player_id', 'team_id'])
batch_op.drop_constraint(None, type_='unique')
batch_op.drop_column('player_team_id')
batch_op.drop_column('player_team_role_id')
with op.batch_alter_table('players_teams_availability', schema=None) as batch_op:
batch_op.add_column(sa.Column('player_id', sa.INTEGER(), nullable=False))
batch_op.add_column(sa.Column('team_id', sa.INTEGER(), nullable=False))
batch_op.drop_constraint(None, type_='foreignkey')
batch_op.create_foreign_key(None, 'players_teams', ['player_id', 'team_id'], ['player_id', 'team_id'])
batch_op.drop_column('player_team_id')
with op.batch_alter_table('players_teams', schema=None) as batch_op:
batch_op.drop_column('player_team_id')
# ### end Alembic commands ###

View File

@ -0,0 +1,50 @@
"""Change player_team surrogate key name
Revision ID: 6e9d70f835d7
Revises: 6296c347731b
Create Date: 2024-11-21 12:13:44.989797
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6e9d70f835d7'
down_revision = '6296c347731b'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('players_teams', schema=None) as batch_op:
batch_op.add_column(sa.Column('id', sa.Integer(), autoincrement=True, nullable=False))
batch_op.drop_column('player_team_id')
with op.batch_alter_table('players_teams_availability', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='foreignkey')
batch_op.create_foreign_key(None, 'players_teams', ['player_team_id'], ['id'])
with op.batch_alter_table('players_teams_roles', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='foreignkey')
batch_op.create_foreign_key(None, 'players_teams', ['player_team_id'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('players_teams_roles', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='foreignkey')
batch_op.create_foreign_key(None, 'players_teams', ['player_team_id'], ['player_team_id'])
with op.batch_alter_table('players_teams_availability', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='foreignkey')
batch_op.create_foreign_key(None, 'players_teams', ['player_team_id'], ['player_team_id'])
with op.batch_alter_table('players_teams', schema=None) as batch_op:
batch_op.add_column(sa.Column('player_team_id', sa.INTEGER(), nullable=False))
batch_op.drop_column('id')
# ### end Alembic commands ###

View File

@ -0,0 +1,40 @@
"""Fix integrity
Revision ID: 7361c978e53d
Revises: 6e9d70f835d7
Create Date: 2024-11-21 12:43:01.786598
"""
from alembic import op
import sqlalchemy as sa
import app_db
# revision identifiers, used by Alembic.
revision = '7361c978e53d'
down_revision = '6e9d70f835d7'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('players_teams_availability', schema=None, naming_convention=app_db.convention) as batch_op:
batch_op.create_foreign_key(batch_op.f("fk_players_teams_availability_player_team_id_players_teams"), 'players_teams', ['player_team_id'], ['id'])
with op.batch_alter_table('players_teams_roles', schema=None) as batch_op:
batch_op.create_foreign_key(batch_op.f("fk_players_teams_roles_player_team_id_players_teams"), 'players_teams', ['player_team_id'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('players_teams_roles', schema=None, naming_convention=app_db.convention) as batch_op:
batch_op.drop_constraint(None, type_='foreignkey')
with op.batch_alter_table('players_teams_availability', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='foreignkey')
# ### end Alembic commands ###

View File

@ -4,7 +4,7 @@ from sqlalchemy.orm import mapped_column, relationship
from sqlalchemy.orm.attributes import Mapped
from sqlalchemy.orm.properties import ForeignKey
from sqlalchemy.sql import func
from sqlalchemy.types import TIMESTAMP, Boolean, Enum, Interval
from sqlalchemy.types import TIMESTAMP, Boolean, Enum, Integer, Interval
import app_db
import spec
@ -16,8 +16,16 @@ class PlayerTeam(app_db.BaseModel):
Player = 0
CoachMentor = 1
player_id: Mapped[int] = mapped_column(ForeignKey("players.steam_id"), primary_key=True)
team_id: Mapped[int] = mapped_column(ForeignKey("teams.id"), primary_key=True)
# surrogate key
id: Mapped[int] = mapped_column(
Integer,
autoincrement=True,
primary_key=True,
)
# primary key
player_id: Mapped[int] = mapped_column(ForeignKey("players.steam_id"))
team_id: Mapped[int] = mapped_column(ForeignKey("teams.id"))
player: Mapped["Player"] = relationship(back_populates="teams")
team: Mapped["Team"] = relationship(back_populates="players")

View File

@ -1,8 +1,11 @@
from datetime import datetime, timedelta
from sqlalchemy.orm.properties import ForeignKey
import spec
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.schema import ForeignKeyConstraint
from sqlalchemy.types import Integer
from sqlalchemy.types import BigInteger, Integer
from sqlalchemy_utc import UtcDateTime
import app_db
@ -10,25 +13,16 @@ import app_db
class PlayerTeamAvailability(app_db.BaseModel):
__tablename__ = "players_teams_availability"
player_id: Mapped[int] = mapped_column(primary_key=True)
team_id: Mapped[int] = mapped_column(primary_key=True)
start_time: Mapped[datetime] = mapped_column(UtcDateTime, primary_key=True)
player_team_id = mapped_column(ForeignKey("players_teams.id"), primary_key=True)
start_time: Mapped[datetime] = mapped_column(UtcDateTime, primary_key=True, index=True)
player_team: Mapped["PlayerTeam"] = relationship(
"PlayerTeam", back_populates="availability")
"PlayerTeam",
back_populates="availability",
)
availability: Mapped[int] = mapped_column(Integer, default=2)
end_time: Mapped[datetime] = mapped_column(UtcDateTime)
from models.player_team import PlayerTeam
__table_args__ = (
ForeignKeyConstraint(
[player_id, team_id],
[PlayerTeam.player_id, PlayerTeam.team_id]
),
)
end_time: Mapped[datetime] = mapped_column(UtcDateTime, index=True)
class AvailabilitySchema(spec.BaseModel):
steam_id: str
@ -48,7 +42,7 @@ class AvailabilitySchema(spec.BaseModel):
i = max(0, relative_start_hour)
while i < window_size_hours and i < relative_end_hour:
print(i, "=", region.availability)
#print(i, "=", region.availability)
self.availability[i] = region.availability
i += 1
@ -60,3 +54,6 @@ class PlayerTeamAvailabilityRoleSchema(spec.BaseModel):
playtime: int
availability: int
roles: list[RoleSchema]
from models.player_team import PlayerTeam

View File

@ -1,9 +1,11 @@
import enum
from sqlalchemy.orm.properties import ForeignKey
import spec
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.schema import ForeignKeyConstraint
from sqlalchemy.types import Boolean, Enum
from sqlalchemy.schema import ForeignKeyConstraint, UniqueConstraint
from sqlalchemy.types import BigInteger, Boolean, Enum, Integer
import app_db
@ -29,25 +31,29 @@ class PlayerTeamRole(app_db.BaseModel):
Sniper = 12
Spy = 13
player_id: Mapped[int] = mapped_column(primary_key=True)
team_id: Mapped[int] = mapped_column(primary_key=True)
# surrogate key
player_team_role_id: Mapped[int] = mapped_column(
Integer,
autoincrement=True,
primary_key=True,
)
player_team: Mapped["PlayerTeam"] = relationship("PlayerTeam", back_populates="player_roles")
# primary key
player_team_id = mapped_column(ForeignKey("players_teams.id"), nullable=False)
role: Mapped[Role] = mapped_column(Enum(Role), nullable=False)
#player: Mapped["Player"] = relationship(back_populates="teams")
player_team: Mapped["PlayerTeam"] = relationship(
"PlayerTeam",
back_populates="player_roles"
)
role: Mapped[Role] = mapped_column(Enum(Role), primary_key=True)
is_main: Mapped[bool] = mapped_column(Boolean)
from models.player_team import PlayerTeam
__table_args__ = (
ForeignKeyConstraint(
[player_id, team_id],
[PlayerTeam.player_id, PlayerTeam.team_id]
),
UniqueConstraint("player_team_id", "role"),
)
class RoleSchema(spec.BaseModel):
role: str
is_main: bool
@ -55,3 +61,6 @@ class RoleSchema(spec.BaseModel):
@classmethod
def from_model(cls, role: PlayerTeamRole):
return cls(role=role.role.name, is_main=role.is_main)
from models.player_team import PlayerTeam

View File

@ -2,14 +2,14 @@ import datetime
from typing import cast
from flask import Blueprint, abort, jsonify, make_response, request
from spectree import Response
from sqlalchemy.orm import joinedload
from sqlalchemy.sql import and_
from sqlalchemy.orm import contains_eager, joinedload
from sqlalchemy.sql import and_, select
from app_db import db
from models.player import Player, PlayerSchema
from models.player_team import PlayerTeam
from models.player_team_availability import AvailabilitySchema, PlayerTeamAvailability, PlayerTeamAvailabilityRoleSchema
from models.player_team_role import PlayerTeamRole, RoleSchema
from middleware import requires_authentication
from middleware import requires_authentication, requires_team_membership
from spec import spec, BaseModel
@ -30,16 +30,15 @@ class ViewScheduleResponse(BaseModel):
)
)
@requires_authentication
def get(query: ViewScheduleForm, player: Player, **kwargs):
@requires_team_membership(query_param="team_id")
def get(query: ViewScheduleForm, player_team: PlayerTeam, **kwargs):
window_start = query.window_start
window_end = window_start + datetime.timedelta(days=query.window_size_days)
availability_regions = db.session.query(
PlayerTeamAvailability
).where(
PlayerTeamAvailability.player_id == player.steam_id
).where(
PlayerTeamAvailability.team_id == query.team_id
PlayerTeamAvailability.player_team_id == player_team.id
).where(
PlayerTeamAvailability.start_time.between(window_start, window_end) |
PlayerTeamAvailability.end_time.between(window_start, window_end) |
@ -101,7 +100,8 @@ def find_consecutive_blocks(arr: list[int]) -> list[tuple[int, int, int]]:
@api_schedule.put("/")
@spec.validate()
@requires_authentication
def put(json: PutScheduleForm, player: Player, **kwargs):
@requires_team_membership(json_param="team_id")
def put(player_team: PlayerTeam, json: PutScheduleForm, player: Player, **kwargs):
window_start = json.window_start
window_end = window_start + datetime.timedelta(days=json.window_size_days)
@ -114,9 +114,7 @@ def put(json: PutScheduleForm, player: Player, **kwargs):
cur_availability = db.session.query(
PlayerTeamAvailability
).where(
PlayerTeamAvailability.player_id == player.steam_id
).where(
PlayerTeamAvailability.team_id == json.team_id
PlayerTeamAvailability.player_team == player_team
).where(
PlayerTeamAvailability.start_time.between(window_start, window_end) |
PlayerTeamAvailability.end_time.between(window_start, window_end)
@ -167,8 +165,7 @@ def put(json: PutScheduleForm, player: Player, **kwargs):
new_availability.availability = availability_value
new_availability.start_time = abs_start
new_availability.end_time = abs_end
new_availability.player_id = player.steam_id
new_availability.team_id = json.team_id
new_availability.player_team = player_team
availability_blocks.append(new_availability)
@ -206,6 +203,7 @@ def get_team_availability(query: ViewScheduleForm, player: Player, **kwargs):
).outerjoin(
PlayerTeamAvailability,
and_(
PlayerTeamAvailability.player_team_id == PlayerTeam.id,
PlayerTeamAvailability.start_time.between(window_start, window_end) |
PlayerTeamAvailability.end_time.between(window_start, window_end) |
@ -218,6 +216,12 @@ def get_team_availability(query: ViewScheduleForm, player: Player, **kwargs):
Player
).where(
PlayerTeam.team_id == query.team_id
).options(
# only populate PlayerTeam.availability with the availability regions
# that are within the window
contains_eager(PlayerTeam.availability),
joinedload(PlayerTeam.player),
).populate_existing(
).all()
ret: dict[str, AvailabilitySchema] = { }
@ -264,7 +268,7 @@ def view_available_at_time(query: ViewAvailablePlayersQuery, player: Player, **k
).join(
PlayerTeamRole
).where(
PlayerTeamAvailability.team_id == query.team_id
PlayerTeam.team_id == query.team_id
).where(
(PlayerTeamAvailability.start_time <= start_time) &
(PlayerTeamAvailability.end_time > start_time)