You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
285 lines
12 KiB
285 lines
12 KiB
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
|
|
from django.utils.safestring import mark_safe
|
|
|
|
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 = '<div style="display: flex; flex-wrap: wrap; gap: 10px;">'
|
|
for image in obj.images.all():
|
|
html += f'''
|
|
<div style="text-align: center; margin-bottom: 15px;">
|
|
<img src="{image.image.url}" style="max-width: 150px; max-height: 150px; object-fit: contain;" />
|
|
<p style="margin: 5px 0 0 0; font-size: 12px;">
|
|
<strong>{image.title or "Untitled"}</strong><br>
|
|
Type: {image.get_image_type_display()}<br>
|
|
</p>
|
|
</div>
|
|
'''
|
|
html += '</div>'
|
|
if not obj.images.exists():
|
|
html = '<p>No images uploaded for this event.</p>'
|
|
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']
|
|
list_filter = [StartDateRangeFilter, 'is_deleted', 'event__creator']
|
|
ordering = ['-start_date']
|
|
search_fields = ['id']
|
|
|
|
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'<img src="{obj.image.url}" width="150" height="auto" style="max-height: 150px; object-fit: contain;" />')
|
|
return "No Image"
|
|
image_preview.short_description = 'Preview'
|
|
|
|
def image_preview_small(self, obj):
|
|
if obj.image:
|
|
return mark_safe(f'<img src="{obj.image.url}" width="50" height="auto" style="max-height: 50px; object-fit: contain;" />')
|
|
return "No Image"
|
|
image_preview_small.short_description = 'Preview'
|
|
|
|
def file_size(self, obj):
|
|
if obj.image and hasattr(obj.image, 'size'):
|
|
# Convert bytes to KB or MB
|
|
size_bytes = obj.image.size
|
|
if size_bytes < 1024:
|
|
return f"{size_bytes} bytes"
|
|
elif size_bytes < 1024 * 1024:
|
|
return f"{size_bytes/1024:.1f} KB"
|
|
else:
|
|
return f"{size_bytes/(1024*1024):.1f} MB"
|
|
return "Unknown"
|
|
file_size.short_description = 'File Size'
|
|
|
|
|
|
action_flags = {
|
|
ADDITION: 'Addition',
|
|
CHANGE: 'Change',
|
|
DELETION: 'Deletion',
|
|
}
|
|
|
|
@admin.register(LogEntry)
|
|
class LogEntryAdmin(admin.ModelAdmin):
|
|
date_hierarchy = 'action_time'
|
|
list_filter = ['user', 'content_type', 'action_flag']
|
|
search_fields = ['object_repr', 'change_message']
|
|
list_display = ['action_time', 'user', 'content_type', 'object_link', 'action_flag_display', 'change_message']
|
|
readonly_fields = [field.name for field in LogEntry._meta.get_fields()]
|
|
|
|
def has_add_permission(self, request):
|
|
return False
|
|
|
|
def has_change_permission(self, request, obj=None):
|
|
return False
|
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
return False
|
|
|
|
def object_link(self, obj):
|
|
if obj.action_flag == DELETION:
|
|
link = escape(obj.object_repr)
|
|
else:
|
|
ct = obj.content_type
|
|
try:
|
|
link = '<a href="%s">%s</a>' % (
|
|
reverse('admin:%s_%s_change' % (ct.app_label, ct.model),
|
|
args=[obj.object_id]),
|
|
escape(obj.object_repr),
|
|
)
|
|
except:
|
|
link = escape(obj.object_repr)
|
|
return mark_safe(link)
|
|
object_link.short_description = 'Object'
|
|
|
|
def action_flag_display(self, obj):
|
|
return action_flags.get(obj.action_flag, '')
|
|
action_flag_display.short_description = 'Action'
|
|
|
|
admin.site.register(CustomUser, CustomUserAdmin)
|
|
admin.site.register(Club, ClubAdmin)
|
|
admin.site.register(Event, EventAdmin)
|
|
admin.site.register(Round, RoundAdmin)
|
|
admin.site.register(GroupStage, GroupStageAdmin)
|
|
admin.site.register(Match, MatchAdmin)
|
|
admin.site.register(TeamScore, TeamScoreAdmin)
|
|
admin.site.register(TeamRegistration, TeamRegistrationAdmin)
|
|
admin.site.register(Tournament, TournamentAdmin)
|
|
admin.site.register(PlayerRegistration, PlayerRegistrationAdmin)
|
|
admin.site.register(Purchase, PurchaseAdmin)
|
|
admin.site.register(Court, CourtAdmin)
|
|
admin.site.register(DateInterval, DateIntervalAdmin)
|
|
admin.site.register(FailedApiCall, FailedApiCallAdmin)
|
|
admin.site.register(Log, LogAdmin)
|
|
admin.site.register(DeviceToken, DeviceTokenAdmin)
|
|
admin.site.register(DrawLog, DrawLogAdmin)
|
|
admin.site.register(UnregisteredTeam, UnregisteredTeamAdmin)
|
|
admin.site.register(UnregisteredPlayer, UnregisteredPlayerAdmin)
|
|
admin.site.register(Image, ImageAdmin)
|
|
|