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 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 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) 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(), ) queryset = 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), ), ) return queryset.distinct() 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", ).prefetch_related("aliases")