phase3: add normalized domain schema, admin, services, and multistage docker build

This commit is contained in:
Alfredo Di Stasio
2026-03-10 10:39:45 +01:00
parent f47ffe6c15
commit fc7289a343
30 changed files with 1548 additions and 3 deletions

17
apps/scouting/admin.py Normal file
View File

@ -0,0 +1,17 @@
from django.contrib import admin
from .models import FavoritePlayer, SavedSearch
@admin.register(SavedSearch)
class SavedSearchAdmin(admin.ModelAdmin):
list_display = ("name", "user", "is_public", "last_run_at", "updated_at")
list_filter = ("is_public",)
search_fields = ("name", "user__username", "user__email")
@admin.register(FavoritePlayer)
class FavoritePlayerAdmin(admin.ModelAdmin):
list_display = ("user", "player", "created_at")
list_filter = ("created_at",)
search_fields = ("user__username", "player__full_name")

View File

@ -0,0 +1,51 @@
# Generated by Django 5.2.12 on 2026-03-10 09:33
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('players', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='FavoritePlayer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('note', models.CharField(blank=True, max_length=240)),
('created_at', models.DateTimeField(auto_now_add=True)),
('player', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='favorites', to='players.player')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='favorite_players', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-created_at'],
'indexes': [models.Index(fields=['user', 'created_at'], name='scouting_fa_user_id_e79538_idx'), models.Index(fields=['player'], name='scouting_fa_player__0f3abd_idx')],
'constraints': [models.UniqueConstraint(fields=('user', 'player'), name='uq_favorite_player_per_user')],
},
),
migrations.CreateModel(
name='SavedSearch',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=120)),
('filters', models.JSONField(default=dict)),
('is_public', models.BooleanField(default=False)),
('last_run_at', models.DateTimeField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='saved_searches', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-updated_at'],
'indexes': [models.Index(fields=['user', 'updated_at'], name='scouting_sa_user_id_913692_idx'), models.Index(fields=['is_public'], name='scouting_sa_is_publ_ae6b37_idx')],
'constraints': [models.UniqueConstraint(fields=('user', 'name'), name='uq_saved_search_user_name')],
},
),
]

52
apps/scouting/models.py Normal file
View File

@ -0,0 +1,52 @@
from django.conf import settings
from django.db import models
class SavedSearch(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="saved_searches",
)
name = models.CharField(max_length=120)
filters = models.JSONField(default=dict)
is_public = models.BooleanField(default=False)
last_run_at = models.DateTimeField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["-updated_at"]
constraints = [models.UniqueConstraint(fields=["user", "name"], name="uq_saved_search_user_name")]
indexes = [
models.Index(fields=["user", "updated_at"]),
models.Index(fields=["is_public"]),
]
def __str__(self) -> str:
return f"{self.name} ({self.user})"
class FavoritePlayer(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="favorite_players",
)
player = models.ForeignKey(
"players.Player",
on_delete=models.CASCADE,
related_name="favorites",
)
note = models.CharField(max_length=240, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
constraints = [
models.UniqueConstraint(fields=["user", "player"], name="uq_favorite_player_per_user")
]
indexes = [models.Index(fields=["user", "created_at"]), models.Index(fields=["player"])]
def __str__(self) -> str:
return f"{self.user} -> {self.player}"

View File

View File

@ -0,0 +1,28 @@
from django.db.models import QuerySet
from apps.players.models import Player
def apply_saved_search_filters(queryset: QuerySet[Player], filters: dict) -> QuerySet[Player]:
"""Apply structured saved-search filters to a player queryset."""
nationality_id = filters.get("nationality_id")
if nationality_id:
queryset = queryset.filter(nationality_id=nationality_id)
nominal_position_id = filters.get("nominal_position_id")
if nominal_position_id:
queryset = queryset.filter(nominal_position_id=nominal_position_id)
inferred_role_id = filters.get("inferred_role_id")
if inferred_role_id:
queryset = queryset.filter(inferred_role_id=inferred_role_id)
min_height_cm = filters.get("min_height_cm")
if min_height_cm is not None:
queryset = queryset.filter(height_cm__gte=min_height_cm)
max_height_cm = filters.get("max_height_cm")
if max_height_cm is not None:
queryset = queryset.filter(height_cm__lte=max_height_cm)
return queryset