from django.contrib import admin from django.contrib.auth.admin import UserAdmin from django.utils import timezone from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION from django.utils.html import escape from django.urls import reverse, path # Add path import from django.utils.safestring import mark_safe from django.shortcuts import render # Add this import from django.db.models import Sum, Count, Avg, Q # Add these imports from datetime import datetime, timedelta # Add this import from .models import Club, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, Court, DateInterval, FailedApiCall, Log, DeviceToken, DrawLog, UnregisteredTeam, UnregisteredPlayer, Image from .forms import CustomUserCreationForm, CustomUserChangeForm from .filters import TeamScoreTournamentListFilter, MatchTournamentListFilter, SimpleTournamentListFilter, MatchTypeListFilter, SimpleIndexListFilter, StartDateRangeFilter from sync.admin import SyncedObjectAdmin class CustomUserAdmin(UserAdmin): form = CustomUserChangeForm add_form = CustomUserCreationForm model = CustomUser search_fields = ['username', 'email', 'phone', 'first_name', 'last_name', 'licence_id'] list_display = ['email', 'first_name', 'last_name', 'username', 'date_joined', 'latest_event_club_name', 'is_active', 'event_count', 'origin', 'registration_payment_mode', 'licence_id'] list_filter = ['is_active', 'origin'] ordering = ['-date_joined'] fieldsets = [ (None, {'fields': ['id', 'username', 'email', 'password', 'first_name', 'last_name', 'is_active', 'registration_payment_mode', 'clubs', 'country', 'phone', 'licence_id', 'umpire_code', 'summons_message_body', 'summons_message_signature', 'summons_available_payment_methods', 'summons_display_format', 'summons_display_entry_fee', 'summons_use_full_custom_message', 'match_formats_default_duration', 'bracket_match_format_preference', 'group_stage_match_format_preference', 'loser_bracket_match_format_preference', 'device_id', 'loser_bracket_mode', 'groups', 'origin', 'agents', 'should_synchronize' ]}), ] add_fieldsets = [ ( None, { "classes": ["wide"], "fields": ['username', 'email', 'password1', 'password2', 'first_name', 'last_name', 'clubs', 'country', 'phone', 'licence_id', 'umpire_code', 'groups'], }, ), ] def save_model(self, request, obj, form, change): obj.last_update = timezone.now() super().save_model(request, obj, form, change) class EventAdmin(SyncedObjectAdmin): list_display = ['creation_date', 'name', 'club', 'creator', 'creator_full_name', 'tenup_id', 'display_images'] list_filter = ['creator', 'tenup_id'] raw_id_fields = ['creator'] ordering = ['-creation_date'] readonly_fields = ['display_images_preview'] fieldsets = [ (None, {'fields': ['name', 'club', 'creator', 'creation_date', 'tenup_id']}), ('Images', {'fields': ['display_images_preview'], 'classes': ['collapse']}), ] def display_images(self, obj): count = obj.images.count() return count if count > 0 else '-' display_images.short_description = 'Images' def display_images_preview(self, obj): html = '
{image.title or "Untitled"}
Type: {image.get_image_type_display()}
No images uploaded for this event.
' return mark_safe(html) display_images_preview.short_description = 'Images Preview' class TournamentAdmin(SyncedObjectAdmin): list_display = ['display_name', 'event', 'is_private', 'start_date', 'payment', 'creator', 'is_canceled'] list_filter = [StartDateRangeFilter, 'is_deleted', 'event__creator'] ordering = ['-start_date'] search_fields = ['id'] def dashboard_view(self, request): """Tournament dashboard view with comprehensive statistics""" # Calculate date ranges now = timezone.now() today = now.date() week_ago = today - timedelta(days=7) month_ago = today - timedelta(days=30) # Tournament statistics - running today tournaments_today = Tournament.objects.filter( start_date__date__lte=today, end_date__date__gte=today ).exclude(is_deleted=True) tournaments_today_private = tournaments_today.filter(is_private=True).count() tournaments_today_public = tournaments_today.filter(is_private=False).count() tournaments_today_total = tournaments_today.count() # Tournament statistics - running this week tournaments_this_week = Tournament.objects.filter( Q(start_date__date__gte=week_ago) | Q(end_date__date__gte=week_ago, start_date__date__lte=today) ).exclude(is_deleted=True) tournaments_week_private = tournaments_this_week.filter(is_private=True).count() tournaments_week_public = tournaments_this_week.filter(is_private=False).count() tournaments_week_total = tournaments_this_week.count() # Tournament statistics - running this month tournaments_this_month = Tournament.objects.filter( Q(start_date__date__gte=month_ago) | Q(end_date__date__gte=month_ago, start_date__date__lte=today) ).exclude(is_deleted=True) tournaments_month_private = tournaments_this_month.filter(is_private=True).count() tournaments_month_public = tournaments_this_month.filter(is_private=False).count() tournaments_month_total = tournaments_this_month.count() # All time tournament statistics all_tournaments = Tournament.objects.exclude(is_deleted=True) tournaments_all_private = all_tournaments.filter(is_private=True).count() tournaments_all_public = all_tournaments.filter(is_private=False).count() tournaments_all_total = all_tournaments.count() # Ended tournaments tournaments_ended_today = Tournament.objects.filter( end_date__date=today ).exclude(is_deleted=True) tournaments_ended_week = Tournament.objects.filter( end_date__date__gte=week_ago, end_date__date__lte=today ).exclude(is_deleted=True) tournaments_ended_month = Tournament.objects.filter( end_date__date__gte=month_ago, end_date__date__lte=today ).exclude(is_deleted=True) tournaments_ended_all = Tournament.objects.filter( end_date__lt=now ).exclude(is_deleted=True) # Team and player statistics total_teams = TeamRegistration.objects.count() total_players = PlayerRegistration.objects.count() # Match statistics total_matches = Match.objects.count() matches_played = Match.objects.filter(end_date__isnull=False).count() matches_pending = Match.objects.filter(end_date__isnull=True).count() # Additional statistics tournaments_with_online_reg = Tournament.objects.filter( enable_online_registration=True ).exclude(is_deleted=True).count() tournaments_with_payment = Tournament.objects.filter( enable_online_payment=True ).exclude(is_deleted=True).count() # Average statistics avg_teams_per_tournament = TeamRegistration.objects.aggregate( avg_teams=Avg('tournament__team_count') )['avg_teams'] or 0 avg_entry_fee = Tournament.objects.exclude(is_deleted=True).aggregate( avg_fee=Avg('entry_fee') )['avg_fee'] or 0 context = { 'title': 'Tournament Dashboard', 'app_label': 'tournaments', 'opts': Tournament._meta, # Today statistics 'tournaments_today_total': tournaments_today_total, 'tournaments_today_private': tournaments_today_private, 'tournaments_today_public': tournaments_today_public, # Week statistics 'tournaments_week_total': tournaments_week_total, 'tournaments_week_private': tournaments_week_private, 'tournaments_week_public': tournaments_week_public, # Month statistics 'tournaments_month_total': tournaments_month_total, 'tournaments_month_private': tournaments_month_private, 'tournaments_month_public': tournaments_month_public, # All time statistics 'tournaments_all_total': tournaments_all_total, 'tournaments_all_private': tournaments_all_private, 'tournaments_all_public': tournaments_all_public, # Ended tournaments 'tournaments_ended_today': tournaments_ended_today.count(), 'tournaments_ended_week': tournaments_ended_week.count(), 'tournaments_ended_month': tournaments_ended_month.count(), 'tournaments_ended_all': tournaments_ended_all.count(), # Teams and players 'total_teams': total_teams, 'total_players': total_players, # Matches 'total_matches': total_matches, 'matches_played': matches_played, 'matches_pending': matches_pending, # Additional stats 'tournaments_with_online_reg': tournaments_with_online_reg, 'tournaments_with_payment': tournaments_with_payment, 'avg_teams_per_tournament': round(avg_teams_per_tournament, 1), 'avg_entry_fee': round(avg_entry_fee, 2), } return render(request, 'admin/tournaments/dashboard.html', context) def get_urls(self): urls = super().get_urls() custom_urls = [ path('dashboard/', self.admin_site.admin_view(self.dashboard_view), name='tournaments_tournament_dashboard'), ] return custom_urls + urls class TeamRegistrationAdmin(SyncedObjectAdmin): list_display = ['player_names', 'group_stage', 'name', 'tournament', 'registration_date'] list_filter = [SimpleTournamentListFilter] search_fields = ['id'] class TeamScoreAdmin(SyncedObjectAdmin): list_display = ['team_registration', 'score', 'walk_out', 'match'] list_filter = [TeamScoreTournamentListFilter] search_fields = ['id'] raw_id_fields = ['team_registration', 'match'] # Add this line list_per_page = 50 # Controls pagination on the list view def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('team_registration', 'match') class RoundAdmin(SyncedObjectAdmin): list_display = ['tournament', 'name', 'parent', 'index'] list_filter = [SimpleTournamentListFilter, SimpleIndexListFilter] search_fields = ['id'] ordering = ['parent', 'index'] raw_id_fields = ['parent'] # Add this line list_per_page = 50 # Controls pagination on the list view def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('parent') class PlayerRegistrationAdmin(SyncedObjectAdmin): list_display = ['first_name', 'last_name', 'licence_id', 'rank'] search_fields = ['id', 'first_name', 'last_name', 'licence_id__icontains'] list_filter = ['registered_online', TeamScoreTournamentListFilter] ordering = ['last_name', 'first_name'] raw_id_fields = ['team_registration'] # Add this line list_per_page = 50 # Controls pagination on the list view def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('team_registration') class MatchAdmin(SyncedObjectAdmin): list_display = ['__str__', 'round', 'group_stage', 'start_date', 'end_date', 'index'] list_filter = [MatchTypeListFilter, MatchTournamentListFilter, SimpleIndexListFilter] ordering = ['-group_stage', 'round', 'index'] raw_id_fields = ['round', 'group_stage'] # Add this line list_per_page = 50 # Controls pagination on the list view def get_queryset(self, request): qs = super().get_queryset(request) return qs.select_related('round', 'group_stage') class GroupStageAdmin(SyncedObjectAdmin): list_display = ['tournament', 'start_date', 'index'] list_filter = [SimpleTournamentListFilter] ordering = ['-start_date', 'index'] class ClubAdmin(SyncedObjectAdmin): list_display = ['name', 'acronym', 'city', 'creator', 'events_count', 'broadcast_code'] search_fields = ['name', 'acronym', 'city'] ordering = ['creator'] raw_id_fields = ['creator', 'related_user'] class PurchaseAdmin(SyncedObjectAdmin): list_display = ['id', 'user', 'product_id', 'quantity', 'purchase_date', 'revocation_date', 'expiration_date'] list_filter = ['user'] ordering = ['-purchase_date'] class CourtAdmin(SyncedObjectAdmin): list_display = ['index', 'name', 'club'] ordering = ['club'] class DateIntervalAdmin(SyncedObjectAdmin): list_display = ['court_index', 'event'] class FailedApiCallAdmin(admin.ModelAdmin): list_display = ['date', 'user', 'type', 'error'] list_filter = ['user'] class LogAdmin(admin.ModelAdmin): list_display = ['date', 'user', 'message'] list_filter = ['user'] class DeviceTokenAdmin(admin.ModelAdmin): list_display = ['user', 'value'] list_filter = ['user'] class DrawLogAdmin(SyncedObjectAdmin): list_display = ['tournament', 'draw_date', 'draw_seed', 'draw_match_index', 'draw_team_position'] list_filter = [SimpleTournamentListFilter] ordering = ['draw_date'] class UnregisteredTeamAdmin(admin.ModelAdmin): list_display = ['player_names', 'tournament'] list_filter = [SimpleTournamentListFilter] class UnregisteredPlayerAdmin(admin.ModelAdmin): list_display = ['first_name', 'last_name', 'licence_id'] search_fields = ['first_name', 'last_name'] list_filter = [] ordering = ['last_name', 'first_name'] class ImageAdmin(admin.ModelAdmin): list_display = ['title', 'event', 'image_type', 'order', 'uploaded_at', 'file_size', 'image_preview_small'] list_filter = ['event', 'image_type', 'uploaded_at'] search_fields = ['title', 'description', 'event__name'] ordering = ['order'] readonly_fields = ['id', 'uploaded_at', 'image_preview', 'file_size'] raw_id_fields = ['event'] def image_preview(self, obj): if obj.image: return mark_safe(f'