235 lines
8.7 KiB
Python
235 lines
8.7 KiB
Python
from datetime import date, timedelta
|
|
|
|
from django.db.models import (
|
|
Case,
|
|
DecimalField,
|
|
ExpressionWrapper,
|
|
F,
|
|
FloatField,
|
|
IntegerField,
|
|
Max,
|
|
Q,
|
|
Value,
|
|
When,
|
|
)
|
|
from django.db.models.functions import Coalesce
|
|
|
|
from apps.players.models import Player
|
|
|
|
METRIC_SORT_KEYS = {"ppg_desc", "ppg_asc", "mpg_desc", "mpg_asc"}
|
|
|
|
|
|
def _years_ago_today(years: int) -> date:
|
|
today = date.today()
|
|
try:
|
|
return today.replace(year=today.year - years)
|
|
except ValueError:
|
|
# Feb 29 -> Feb 28 when target year is not leap year.
|
|
return today.replace(month=2, day=28, year=today.year - years)
|
|
|
|
|
|
def _apply_min_max_filter(queryset, min_key: str, max_key: str, field_name: str, data: dict):
|
|
min_value = data.get(min_key)
|
|
max_value = data.get(max_key)
|
|
if min_value is not None:
|
|
queryset = queryset.filter(**{f"{field_name}__gte": min_value})
|
|
if max_value is not None:
|
|
queryset = queryset.filter(**{f"{field_name}__lte": max_value})
|
|
return queryset
|
|
|
|
|
|
def _needs_distinct(data: dict) -> bool:
|
|
join_filter_keys = (
|
|
"q",
|
|
"team",
|
|
"competition",
|
|
"season",
|
|
"games_played_min",
|
|
"games_played_max",
|
|
"minutes_per_game_min",
|
|
"minutes_per_game_max",
|
|
"points_per_game_min",
|
|
"points_per_game_max",
|
|
"rebounds_per_game_min",
|
|
"rebounds_per_game_max",
|
|
"assists_per_game_min",
|
|
"assists_per_game_max",
|
|
"steals_per_game_min",
|
|
"steals_per_game_max",
|
|
"blocks_per_game_min",
|
|
"blocks_per_game_max",
|
|
"turnovers_per_game_min",
|
|
"turnovers_per_game_max",
|
|
"fg_pct_min",
|
|
"fg_pct_max",
|
|
"three_pct_min",
|
|
"three_pct_max",
|
|
"ft_pct_min",
|
|
"ft_pct_max",
|
|
"efficiency_metric_min",
|
|
"efficiency_metric_max",
|
|
)
|
|
return any(data.get(key) not in (None, "") for key in join_filter_keys)
|
|
|
|
|
|
def filter_players(queryset, data: dict):
|
|
query = data.get("q")
|
|
if query:
|
|
queryset = queryset.filter(Q(full_name__icontains=query) | Q(aliases__alias__icontains=query))
|
|
|
|
if data.get("nominal_position"):
|
|
queryset = queryset.filter(nominal_position=data["nominal_position"])
|
|
if data.get("inferred_role"):
|
|
queryset = queryset.filter(inferred_role=data["inferred_role"])
|
|
if data.get("nationality"):
|
|
queryset = queryset.filter(nationality=data["nationality"])
|
|
if data.get("origin_competition"):
|
|
queryset = queryset.filter(origin_competition=data["origin_competition"])
|
|
if data.get("origin_team"):
|
|
queryset = queryset.filter(origin_team=data["origin_team"])
|
|
|
|
if data.get("team"):
|
|
queryset = queryset.filter(player_seasons__team=data["team"])
|
|
if data.get("competition"):
|
|
queryset = queryset.filter(player_seasons__competition=data["competition"])
|
|
if data.get("season"):
|
|
queryset = queryset.filter(player_seasons__season=data["season"])
|
|
|
|
queryset = _apply_min_max_filter(queryset, "height_min", "height_max", "height_cm", data)
|
|
queryset = _apply_min_max_filter(queryset, "weight_min", "weight_max", "weight_kg", data)
|
|
|
|
age_min = data.get("age_min")
|
|
age_max = data.get("age_max")
|
|
if age_min is not None:
|
|
queryset = queryset.filter(birth_date__lte=_years_ago_today(age_min))
|
|
if age_max is not None:
|
|
earliest_birth = _years_ago_today(age_max + 1) + timedelta(days=1)
|
|
queryset = queryset.filter(birth_date__gte=earliest_birth)
|
|
|
|
queryset = _apply_min_max_filter(
|
|
queryset,
|
|
"games_played_min",
|
|
"games_played_max",
|
|
"player_seasons__games_played",
|
|
data,
|
|
)
|
|
|
|
mpg_min = data.get("minutes_per_game_min")
|
|
mpg_max = data.get("minutes_per_game_max")
|
|
if mpg_min is not None:
|
|
queryset = queryset.filter(player_seasons__games_played__gt=0).filter(
|
|
player_seasons__minutes_played__gte=F("player_seasons__games_played") * mpg_min
|
|
)
|
|
if mpg_max is not None:
|
|
queryset = queryset.filter(player_seasons__games_played__gt=0).filter(
|
|
player_seasons__minutes_played__lte=F("player_seasons__games_played") * mpg_max
|
|
)
|
|
|
|
stat_pairs = (
|
|
("points_per_game_min", "points_per_game_max", "player_seasons__stats__points"),
|
|
("rebounds_per_game_min", "rebounds_per_game_max", "player_seasons__stats__rebounds"),
|
|
("assists_per_game_min", "assists_per_game_max", "player_seasons__stats__assists"),
|
|
("steals_per_game_min", "steals_per_game_max", "player_seasons__stats__steals"),
|
|
("blocks_per_game_min", "blocks_per_game_max", "player_seasons__stats__blocks"),
|
|
("turnovers_per_game_min", "turnovers_per_game_max", "player_seasons__stats__turnovers"),
|
|
("fg_pct_min", "fg_pct_max", "player_seasons__stats__fg_pct"),
|
|
("three_pct_min", "three_pct_max", "player_seasons__stats__three_pct"),
|
|
("ft_pct_min", "ft_pct_max", "player_seasons__stats__ft_pct"),
|
|
(
|
|
"efficiency_metric_min",
|
|
"efficiency_metric_max",
|
|
"player_seasons__stats__player_efficiency_rating",
|
|
),
|
|
)
|
|
for min_key, max_key, field_name in stat_pairs:
|
|
queryset = _apply_min_max_filter(queryset, min_key, max_key, field_name, data)
|
|
|
|
if _needs_distinct(data):
|
|
return queryset.distinct()
|
|
return queryset
|
|
|
|
|
|
def annotate_player_metrics(queryset):
|
|
mpg_expression = Case(
|
|
When(
|
|
player_seasons__games_played__gt=0,
|
|
then=ExpressionWrapper(
|
|
F("player_seasons__minutes_played") * 1.0 / F("player_seasons__games_played"),
|
|
output_field=FloatField(),
|
|
),
|
|
),
|
|
default=Value(0.0),
|
|
output_field=FloatField(),
|
|
)
|
|
|
|
return queryset.annotate(
|
|
games_played_value=Coalesce(
|
|
Max("player_seasons__games_played"),
|
|
Value(0, output_field=IntegerField()),
|
|
output_field=IntegerField(),
|
|
),
|
|
mpg_value=Coalesce(Max(mpg_expression), Value(0.0)),
|
|
ppg_value=Coalesce(
|
|
Max("player_seasons__stats__points"),
|
|
Value(0, output_field=DecimalField(max_digits=6, decimal_places=2)),
|
|
output_field=DecimalField(max_digits=6, decimal_places=2),
|
|
),
|
|
rpg_value=Coalesce(
|
|
Max("player_seasons__stats__rebounds"),
|
|
Value(0, output_field=DecimalField(max_digits=6, decimal_places=2)),
|
|
output_field=DecimalField(max_digits=6, decimal_places=2),
|
|
),
|
|
apg_value=Coalesce(
|
|
Max("player_seasons__stats__assists"),
|
|
Value(0, output_field=DecimalField(max_digits=6, decimal_places=2)),
|
|
output_field=DecimalField(max_digits=6, decimal_places=2),
|
|
),
|
|
spg_value=Coalesce(
|
|
Max("player_seasons__stats__steals"),
|
|
Value(0, output_field=DecimalField(max_digits=6, decimal_places=2)),
|
|
output_field=DecimalField(max_digits=6, decimal_places=2),
|
|
),
|
|
bpg_value=Coalesce(
|
|
Max("player_seasons__stats__blocks"),
|
|
Value(0, output_field=DecimalField(max_digits=6, decimal_places=2)),
|
|
output_field=DecimalField(max_digits=6, decimal_places=2),
|
|
),
|
|
top_efficiency=Coalesce(
|
|
Max("player_seasons__stats__player_efficiency_rating"),
|
|
Value(0, output_field=DecimalField(max_digits=6, decimal_places=2)),
|
|
output_field=DecimalField(max_digits=6, decimal_places=2),
|
|
),
|
|
)
|
|
|
|
|
|
def apply_sorting(queryset, sort_key: str):
|
|
if sort_key == "name_desc":
|
|
return queryset.order_by("-full_name", "id")
|
|
if sort_key == "age_youngest":
|
|
return queryset.order_by(F("birth_date").desc(nulls_last=True), "full_name")
|
|
if sort_key == "age_oldest":
|
|
return queryset.order_by(F("birth_date").asc(nulls_last=True), "full_name")
|
|
if sort_key == "height_desc":
|
|
return queryset.order_by(F("height_cm").desc(nulls_last=True), "full_name")
|
|
if sort_key == "height_asc":
|
|
return queryset.order_by(F("height_cm").asc(nulls_last=True), "full_name")
|
|
if sort_key == "ppg_desc":
|
|
return queryset.order_by(F("ppg_value").desc(nulls_last=True), "full_name")
|
|
if sort_key == "ppg_asc":
|
|
return queryset.order_by(F("ppg_value").asc(nulls_last=True), "full_name")
|
|
if sort_key == "mpg_desc":
|
|
return queryset.order_by(F("mpg_value").desc(nulls_last=True), "full_name")
|
|
if sort_key == "mpg_asc":
|
|
return queryset.order_by(F("mpg_value").asc(nulls_last=True), "full_name")
|
|
return queryset.order_by("full_name", "id")
|
|
|
|
|
|
def base_player_queryset():
|
|
return Player.objects.select_related(
|
|
"nationality",
|
|
"nominal_position",
|
|
"inferred_role",
|
|
"origin_competition",
|
|
"origin_team",
|
|
)
|