feat(teams): add teams app with roster and staff assignments
Introduce the teams app: Team and Position (both club-scoped, with per-club unique names), TeamMembership (season-scoped roster with jersey number + captain flags, unique member and jersey per team/season), and StaffAssignment (coaching/staff, filtered to staff positions). All uniqueness expressed as UniqueConstraints. Register every model in the admin (with roster + staff inlines on Team) and cover the models with tests. Fix two model bugs surfaced by the system check: StaffAssignment.position reused TeamMembership's reverse accessor (Position.team_memberships) and duplicated its constraint name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
100
teams/migrations/0001_initial.py
Normal file
100
teams/migrations/0001_initial.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# Generated by Django 6.0.6 on 2026-07-12 14:43
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('club', '0008_alter_clubmembership_unique_together_and_more'),
|
||||
('members', '0002_alter_familymembership_unique_together_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Position',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=255, verbose_name='name')),
|
||||
('short_name', models.CharField(max_length=255, verbose_name='short name')),
|
||||
('ordering', models.PositiveSmallIntegerField(default=0, verbose_name='ordering')),
|
||||
('staff_position', models.BooleanField(default=False, verbose_name='staff position')),
|
||||
('club', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='club.club')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'position',
|
||||
'verbose_name_plural': 'positions',
|
||||
'ordering': ['ordering', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Team',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=255, verbose_name='name')),
|
||||
('short_name', models.CharField(max_length=255, verbose_name='short name')),
|
||||
('club', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='club.club')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'team',
|
||||
'verbose_name_plural': 'teams',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='StaffAssignment',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staff_assignments', to='members.member', verbose_name='member')),
|
||||
('position', models.ForeignKey(limit_choices_to={'staff_position': True}, on_delete=django.db.models.deletion.PROTECT, related_name='staff_assignments', to='teams.position', verbose_name='position')),
|
||||
('season', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='staff_assignments', to='club.season', verbose_name='season')),
|
||||
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staff_assignments', to='teams.team', verbose_name='team')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'staff assignment',
|
||||
'verbose_name_plural': 'staff assignments',
|
||||
'ordering': ['team', 'member__last_name', 'member__first_name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TeamMembership',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('jersey_number', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='jersey number')),
|
||||
('is_captain', models.BooleanField(default=False, verbose_name='is captain')),
|
||||
('is_alternate_captain', models.BooleanField(default=False, verbose_name='is alternate captain')),
|
||||
('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='team_memberships', to='members.member', verbose_name='member')),
|
||||
('position', models.ForeignKey(limit_choices_to={'staff_position': False}, on_delete=django.db.models.deletion.PROTECT, related_name='team_memberships', to='teams.position', verbose_name='position')),
|
||||
('season', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='team_memberships', to='club.season', verbose_name='season')),
|
||||
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='roster', to='teams.team', verbose_name='team')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'team membership',
|
||||
'verbose_name_plural': 'team memberships',
|
||||
'ordering': ['team', 'member__last_name', 'member__first_name'],
|
||||
},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='position',
|
||||
constraint=models.UniqueConstraint(fields=('club', 'name'), name='unique_position_name_per_club'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='team',
|
||||
constraint=models.UniqueConstraint(fields=('club', 'name'), name='unique_team_name_per_club'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='staffassignment',
|
||||
constraint=models.UniqueConstraint(fields=('team', 'season', 'member'), name='unique_staff_member_per_team_per_season'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='teammembership',
|
||||
constraint=models.UniqueConstraint(fields=('team', 'season', 'member'), name='unique_member_per_team_per_season'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='teammembership',
|
||||
constraint=models.UniqueConstraint(fields=('team', 'season', 'jersey_number'), name='unique_jersey_number_per_team_per_season'),
|
||||
),
|
||||
]
|
||||
0
teams/migrations/__init__.py
Normal file
0
teams/migrations/__init__.py
Normal file
Reference in New Issue
Block a user