add sponsor image model

sync_v2
Raz 6 months ago
parent 3b3cf56896
commit 7c68762178
  1. 16
      api/serializers.py
  2. 1
      api/urls.py
  3. 32
      api/views.py
  4. 4
      padelclub_backend/settings.py
  5. 2
      padelclub_backend/settings_app.py
  6. 6
      padelclub_backend/urls.py
  7. 1
      requirements.txt
  8. 67
      tournaments/admin.py
  9. 33
      tournaments/migrations/0120_image.py
  10. 26
      tournaments/migrations/0121_alter_image_options_remove_image_is_primary_and_more.py
  11. 1
      tournaments/models/__init__.py
  12. 44
      tournaments/models/image.py
  13. 7
      tournaments/static/tournaments/css/broadcast.css
  14. 2
      tournaments/static/tournaments/css/style.css
  15. 13
      tournaments/templates/tournaments/broadcast/broadcast.html
  16. 5
      tournaments/templates/tournaments/broadcast/broadcast_base.html
  17. 15
      tournaments/templates/tournaments/broadcast/broadcasted_auto.html
  18. 14
      tournaments/templates/tournaments/broadcast/broadcasted_bracket.html
  19. 14
      tournaments/templates/tournaments/broadcast/broadcasted_group_stages.html
  20. 15
      tournaments/templates/tournaments/broadcast/broadcasted_matches.html
  21. 14
      tournaments/templates/tournaments/broadcast/broadcasted_prog.html
  22. 13
      tournaments/templates/tournaments/broadcast/broadcasted_rankings.html
  23. 13
      tournaments/templates/tournaments/broadcast/broadcasted_summons.html
  24. 53
      tournaments/templates/tournaments/tournament_info.html

@ -1,6 +1,6 @@
from rest_framework import serializers
from tournaments.models.court import Court
from tournaments.models import Club, LiveMatch, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, FailedApiCall, DateInterval, Log, DeviceToken, UnregisteredTeam, UnregisteredPlayer
from tournaments.models import Club, LiveMatch, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, FailedApiCall, DateInterval, Log, DeviceToken, UnregisteredTeam, UnregisteredPlayer, Image
from django.db.utils import IntegrityError
from django.conf import settings
@ -239,3 +239,17 @@ class UnregisteredPlayerSerializer(serializers.ModelSerializer):
model = UnregisteredPlayer
fields = '__all__'
# ['id', 'team_registration_id', 'first_name', 'last_name', 'licence_id', 'rank', 'has_paid']
class ImageSerializer(serializers.ModelSerializer):
image_url = serializers.SerializerMethodField()
def get_image_url(self, obj):
if obj.image:
return self.context['request'].build_absolute_uri(obj.image.url)
return None
class Meta:
model = Image
fields = ['id', 'title', 'description', 'image', 'image_url', 'uploaded_at',
'event', 'image_type']
read_only_fields = ['id', 'uploaded_at', 'image_url']

@ -11,6 +11,7 @@ router.register(r'users', views.UserViewSet)
router.register(r'user-names', views.ShortUserViewSet)
router.register(r'clubs', views.ClubViewSet)
router.register(r'tournaments', views.TournamentViewSet)
router.register(r'images', views.ImageViewSet)
router.register(r'events', views.EventViewSet)
router.register(r'rounds', views.RoundViewSet)
router.register(r'group-stages', views.GroupStageViewSet)

@ -1,5 +1,5 @@
from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, DrawLogSerializer, TournamentSerializer, UserSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, PurchaseSerializer, ShortUserSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer, CustomUserSerializer, UnregisteredTeamSerializer, UnregisteredPlayerSerializer
from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken, DrawLog, UnregisteredTeam, UnregisteredPlayer
from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, DrawLogSerializer, TournamentSerializer, UserSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, PurchaseSerializer, ShortUserSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer, CustomUserSerializer, UnregisteredTeamSerializer, UnregisteredPlayerSerializer, ImageSerializer
from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken, DrawLog, UnregisteredTeam, UnregisteredPlayer, Image
from rest_framework import viewsets
from rest_framework.response import Response
@ -300,6 +300,34 @@ class ShortUserViewSet(viewsets.ModelViewSet):
serializer_class = ShortUserSerializer
permission_classes = [] # Users are public whereas the other requests are only for logged users
class ImageViewSet(viewsets.ModelViewSet):
"""
Viewset for handling event image uploads and retrieval.
This allows umpires/organizers to upload images for events from the iOS app,
which can then be displayed on the event pages.
"""
serializer_class = ImageSerializer
queryset = Image.objects.all()
def get_queryset(self):
queryset = Image.objects.all()
# Filter by event
event_id = self.request.query_params.get('event_id')
image_type = self.request.query_params.get('image_type')
if event_id:
queryset = queryset.filter(event_id=event_id)
if image_type:
queryset = queryset.filter(image_type=image_type)
return queryset
def perform_create(self, serializer):
serializer.save()
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def process_refund(request, team_registration_id):

@ -147,6 +147,10 @@ USE_L10N = True
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
# Media files (User uploads)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field

@ -46,7 +46,7 @@ QR_CODE_CACHE_ALIAS = 'qr-code'
SYNC_APPS = {
'sync': {},
'tournaments': { 'exclude': ['Log', 'FailedApiCall', 'DeviceToken'] }
'tournaments': { 'exclude': ['Log', 'FailedApiCall', 'DeviceToken', 'Image'] }
}
STRIPE_CURRENCY = 'eur'

@ -15,6 +15,8 @@ Including another URLconf
"""
from django.contrib import admin
from django.urls import include, path
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
@ -27,3 +29,7 @@ urlpatterns = [
path('dj-auth/', include('django.contrib.auth.urls')),
]
# Serve media files in development
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

@ -17,3 +17,4 @@ django-filter==24.3
cryptography==41.0.7
stripe==11.6.0
django-background-tasks==1.2.8
Pillow==10.2.0

@ -6,7 +6,7 @@ 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
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
@ -45,10 +45,39 @@ class CustomUserAdmin(UserAdmin):
super().save_model(request, obj, form, change)
class EventAdmin(SyncedObjectAdmin):
list_display = ['creation_date', 'name', 'club', 'creator', 'creator_full_name', 'tenup_id']
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']
@ -157,6 +186,39 @@ class UnregisteredPlayerAdmin(admin.ModelAdmin):
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',
@ -220,3 +282,4 @@ 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)

@ -0,0 +1,33 @@
# Generated by Django 5.1 on 2025-05-07 07:58
import django.db.models.deletion
import django.utils.timezone
import tournaments.models.image
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0119_alter_tournament_animation_type'),
]
operations = [
migrations.CreateModel(
name='Image',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('title', models.CharField(blank=True, max_length=255)),
('description', models.TextField(blank=True)),
('image', models.ImageField(upload_to=tournaments.models.image.image_upload_path)),
('uploaded_at', models.DateTimeField(default=django.utils.timezone.now)),
('image_type', models.CharField(choices=[('sponsor', 'Sponsor'), ('club', 'Club')], default='sponsor', max_length=20)),
('is_primary', models.BooleanField(default=False)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='tournaments.event')),
],
options={
'ordering': ['-uploaded_at'],
},
),
]

@ -0,0 +1,26 @@
# Generated by Django 5.1 on 2025-05-07 11:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0120_image'),
]
operations = [
migrations.AlterModelOptions(
name='image',
options={'ordering': ['order']},
),
migrations.RemoveField(
model_name='image',
name='is_primary',
),
migrations.AddField(
model_name='image',
name='order',
field=models.IntegerField(default=0),
),
]

@ -21,3 +21,4 @@ from .device_token import DeviceToken
from .draw_log import DrawLog
from .unregistered_team import UnregisteredTeam
from .unregistered_player import UnregisteredPlayer
from .image import Image

@ -0,0 +1,44 @@
from django.db import models
import uuid
import os
from django.utils.timezone import now
from .event import Event
def image_upload_path(instance, filename):
"""Generate a unique file path for the uploaded image."""
# Get the file extension from the original filename
ext = filename.split('.')[-1]
# Create a unique filename using UUID
unique_filename = f"{uuid.uuid4().hex}.{ext}"
# Determine the folder based on the event
folder = f"event_{instance.event.id}"
# Return the complete upload path
return os.path.join('images', folder, unique_filename)
class Image(models.Model):
"""Model for storing uploaded images for events."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=255, blank=True)
description = models.TextField(blank=True)
image = models.ImageField(upload_to=image_upload_path)
uploaded_at = models.DateTimeField(default=now)
# Relation to event model
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='images')
# Image type for filtering
IMAGE_TYPES = (
('sponsor', 'Sponsor'),
('club', 'Club'),
)
image_type = models.CharField(max_length=20, choices=IMAGE_TYPES, default='sponsor')
order = models.IntegerField(default=0)
class Meta:
ordering = ['order']
def __str__(self):
return self.title or f"Event Image {self.id}"

@ -22,6 +22,13 @@ body {
box-shadow: 0 0 0px 0px #fbead6;
}
.bubble-header {
padding: 30px;
background-color: white;
border-radius: 24px;
box-shadow: 0 0 0px 0px #fbead6;
}
.bold {
font-family: "Montserrat-Bold";
}

@ -297,8 +297,6 @@ tr {
.logo {
height: 80px;
margin: 20px 0px;
/* padding: 5px 10px; */
}
.padding-bottom-small {

@ -6,6 +6,19 @@
{% block first_title %}{{ tournament.event.display_name }}{% endblock %}
{% block second_title %}Broadcast{% endblock %}
{% block sponsors %}
{% if tournament.event.images.exists %}
<div class="bubble" style="display: flex; align-items: center; justify-content: center; padding: 10px; margin: 0;">
<div style="display: flex; flex-wrap: wrap; gap: 30px; justify-content: center; align-items: center;">
{% for image in tournament.event.images.all %}
<img src="{{ image.image.url }}" alt="{{ image.title|default:'Sponsor' }}"
style="max-height: 100px; max-width: 100px; object-fit: contain;">
{% endfor %}
</div>
</div>
{% endif %}
{% endblock %}
{% block content %}
<div class="grid-x">

@ -34,7 +34,7 @@
<header>
<div id="header">
<div class="left-content bubble">
<div class="left-content bubble-header">
<img src="{% static 'tournaments/images/PadelClub_logo_512.png' %}" alt="logo" class="logo">
<div class="left-margin">
<h1 class="club">{% block first_title %}Page Title{% endblock %}</h1>
@ -58,4 +58,7 @@
</div>
</body>
<footer style="position: fixed; bottom: 0; left: 0; right: 0; display: flex; justify-content: center; align-items: center; padding: 20px; z-index: 100;">
{% block sponsors %}{% endblock %}
</footer>
</html>

@ -121,7 +121,7 @@
}" x-init="loop()">
<header>
<div id="header">
<div class="left-content bubble">
<div class="left-content bubble-header">
<img src="{% static 'tournaments/images/PadelClub_logo_512.png' %}" alt="logo" class="logo">
<div class="left-margin">
<h1 class="club">{{ tournament.broadcast_event_display_name }}</h1>
@ -177,4 +177,17 @@
</div>
</body>
<footer style="position: fixed; bottom: 0; left: 0; right: 0; display: flex; justify-content: center; align-items: center; padding: 20px; z-index: 100;">
{% if tournament.event.images.exists %}
<div class="bubble" style="display: flex; align-items: center; justify-content: center; padding: 10px; margin: 0;">
<div style="display: flex; flex-wrap: wrap; gap: 30px; justify-content: center; align-items: center;">
{% for image in tournament.event.images.all %}
<img src="{{ image.image.url }}" alt="{{ image.title|default:'Sponsor' }}"
style="max-height: 100px; max-width: 100px; object-fit: contain;">
{% endfor %}
</div>
</div>
{% endif %}
</footer>
</html>

@ -37,7 +37,7 @@
<body>
<header>
<div id="header">
<div class="left-content bubble">
<div class="left-content bubble-header">
<img src="{% static 'tournaments/images/PadelClub_logo_512.png' %}" alt="logo" class="logo">
<div class="left-margin">
<h1 class="club">{{ tournament.broadcast_event_display_name }}</h1>
@ -157,4 +157,16 @@
setInterval(fetchAndRenderBracket, 15000);
</script>
</body>
<footer style="position: fixed; bottom: 0; left: 0; right: 0; display: flex; justify-content: center; align-items: center; padding: 20px; z-index: 100;">
{% if tournament.event.images.exists %}
<div class="bubble" style="display: flex; align-items: center; justify-content: center; padding: 10px; margin: 0;">
<div style="display: flex; flex-wrap: wrap; gap: 30px; justify-content: center; align-items: center;">
{% for image in tournament.event.images.all %}
<img src="{{ image.image.url }}" alt="{{ image.title|default:'Sponsor' }}"
style="max-height: 100px; max-width: 100px; object-fit: contain;">
{% endfor %}
</div>
</div>
{% endif %}
</footer>
</html>

@ -65,7 +65,7 @@
<header>
<div id="header">
<div class="left-content bubble">
<div class="left-content bubble-header">
<img src="{% static 'tournaments/images/PadelClub_logo_512.png' %}" alt="logo" class="logo">
<div class="left-margin">
<h1 class="club">{{ tournament.broadcast_event_display_name }}</h1>
@ -101,4 +101,16 @@
</div>
</body>
<footer style="position: fixed; bottom: 0; left: 0; right: 0; display: flex; justify-content: center; align-items: center; padding: 20px; z-index: 100;">
{% if tournament.event.images.exists %}
<div class="bubble" style="display: flex; align-items: center; justify-content: center; padding: 10px; margin: 0;">
<div style="display: flex; flex-wrap: wrap; gap: 30px; justify-content: center; align-items: center;">
{% for image in tournament.event.images.all %}
<img src="{{ image.image.url }}" alt="{{ image.title|default:'Sponsor' }}"
style="max-height: 100px; max-width: 100px; object-fit: contain;">
{% endfor %}
</div>
</div>
{% endif %}
</footer>
</html>

@ -64,7 +64,7 @@
<header>
<div id="header">
<div class="left-content bubble">
<div class="left-content bubble-header">
<img src="{% static 'tournaments/images/PadelClub_logo_512.png' %}" alt="logo" class="logo">
<div class="left-margin">
<h1 class="club">{{ tournament.broadcast_event_display_name }}</h1>
@ -97,4 +97,17 @@
</main>
</div>
</body>
<footer style="position: fixed; bottom: 0; left: 0; right: 0; display: flex; justify-content: center; align-items: center; padding: 20px; z-index: 100;">
{% if tournament.event.images.exists %}
<div class="bubble" style="display: flex; align-items: center; justify-content: center; padding: 10px; margin: 0;">
<div style="display: flex; flex-wrap: wrap; gap: 30px; justify-content: center; align-items: center;">
{% for image in tournament.event.images.all %}
<img src="{{ image.image.url }}" alt="{{ image.title|default:'Sponsor' }}"
style="max-height: 100px; max-width: 100px; object-fit: contain;">
{% endfor %}
</div>
</div>
{% endif %}
</footer>
</html>

@ -64,7 +64,7 @@
<header>
<div id="header">
<div class="left-content bubble">
<div class="left-content bubble-header">
<img src="{% static 'tournaments/images/PadelClub_logo_512.png' %}" alt="logo" class="logo">
<div class="left-margin">
<h1 class="club">{{ tournament.broadcast_event_display_name }}</h1>
@ -97,4 +97,16 @@
</main>
</div>
</body>
<footer style="position: fixed; bottom: 0; left: 0; right: 0; display: flex; justify-content: center; align-items: center; padding: 20px; z-index: 100;">
{% if tournament.event.images.exists %}
<div class="bubble" style="display: flex; align-items: center; justify-content: center; padding: 10px; margin: 0;">
<div style="display: flex; flex-wrap: wrap; gap: 30px; justify-content: center; align-items: center;">
{% for image in tournament.event.images.all %}
<img src="{{ image.image.url }}" alt="{{ image.title|default:'Sponsor' }}"
style="max-height: 100px; max-width: 100px; object-fit: contain;">
{% endfor %}
</div>
</div>
{% endif %}
</footer>
</html>

@ -3,7 +3,18 @@
{% block head_title %}Classement{% endblock %}
{% block first_title %}{{ tournament.broadcast_event_display_name }}{% endblock %}
{% block second_title %}Classement {{ tournament.broadcast_display_name }}{% endblock %}
{% block sponsors %}
{% if tournament.event.images.exists %}
<div class="bubble" style="display: flex; align-items: center; justify-content: center; padding: 10px; margin: 0;">
<div style="display: flex; flex-wrap: wrap; gap: 30px; justify-content: center; align-items: center;">
{% for image in tournament.event.images.all %}
<img src="{{ image.image.url }}" alt="{{ image.title|default:'Sponsor' }}"
style="max-height: 100px; max-width: 100px; object-fit: contain;">
{% endfor %}
</div>
</div>
{% endif %}
{% endblock %}
{% block content %}
{% load static %}

@ -3,7 +3,18 @@
{% block head_title %}Convocations{% endblock %}
{% block first_title %}{{ tournament.broadcast_event_display_name }}{% endblock %}
{% block second_title %}Convocations {{ tournament.broadcast_display_name }}{% endblock %}
{% block sponsors %}
{% if tournament.event.images.exists %}
<div class="bubble" style="display: flex; align-items: center; justify-content: center; padding: 10px; margin: 0;">
<div style="display: flex; flex-wrap: wrap; gap: 30px; justify-content: center; align-items: center;">
{% for image in tournament.event.images.all %}
<img src="{{ image.image.url }}" alt="{{ image.title|default:'Sponsor' }}"
style="max-height: 100px; max-width: 100px; object-fit: contain;">
{% endfor %}
</div>
</div>
{% endif %}
{% endblock %}
{% block content %}
{% load static %}

@ -268,6 +268,59 @@
{% endwith %}
</div>
</div>
<!-- Sponsors Section -->
{% if tournament.event.images.exists %}
<div class="cell medium-12 large-6 padding10">
<h1 class="club padding10">Sponsors</h1>
<div class="bubble">
<div style="display: flex; flex-wrap: wrap; justify-content: left; gap: 30px; align-items: left;">
{% for image in tournament.event.images.all %}
<div style="text-align: center;">
<a href="{{ image.image.url }}" target="_blank" title="{{ image.title|default:'Sponsor' }}{% if image.description %}: {{ image.description }}{% endif %}">
<img src="{{ image.image.url }}" alt="{{ image.title|default:'Sponsor' }}"
style="width: 128px; height: 128px; object-fit: contain; border-radius: 0px; border: 0px solid #eee; padding: 0px">
</a>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- Debug: Image URLs (Only visible to staff) -->
{% if user.is_staff %}
<div class="cell medium-12 large-6 padding10">
<h1 class="club padding10">Debug: Image URLs</h1>
<div class="bubble">
<table style="width: 100%; font-family: monospace; font-size: 12px; border-collapse: collapse;">
<thead>
<tr style="border-bottom: 1px solid #ccc; text-align: left;">
<th style="padding: 5px;">ID</th>
<th style="padding: 5px;">Type</th>
<th style="padding: 5px;">Title</th>
<th style="padding: 5px;">Primary</th>
<th style="padding: 5px;">URL</th>
</tr>
</thead>
<tbody>
{% for image in tournament.event.images.all %}
<tr style="border-bottom: 1px solid #eee;">
<td style="padding: 5px;">{{ image.id }}</td>
<td style="padding: 5px;">{{ image.image_type }}</td>
<td style="padding: 5px;">{{ image.title|default:"Untitled" }}</td>
<td style="padding: 5px;">{{ image.order }}</td>
<td style="padding: 5px;"><a href="{{ image.image.url }}" target="_blank">{{ image.image.url }}</a></td>
</tr>
{% empty %}
<tr>
<td colspan="5" style="padding: 10px; text-align: center;">No images found for this event.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
{% endblock %}

Loading…
Cancel
Save