From 88a6c63f9f9a855487fcf2da8f4113b5cbfcfac0 Mon Sep 17 00:00:00 2001 From: Bernard Siebens Date: Mon, 27 Jul 2026 10:27:07 +0200 Subject: [PATCH] Allow SVG club logos ImageField validates uploads through Pillow, which can't read SVGs, so a club crest that's a vector logo was rejected outright. Switches to a plain FileField with an extension allowlist (png/jpg/jpeg/gif/webp/svg) instead, and restricts the file picker to image types via the widget's accept attribute. --- club/migrations/0015_alter_club_logo.py | 20 ++++++++++++++++++++ club/models.py | 12 ++++++++++-- controlpanel/forms.py | 1 + 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 club/migrations/0015_alter_club_logo.py diff --git a/club/migrations/0015_alter_club_logo.py b/club/migrations/0015_alter_club_logo.py new file mode 100644 index 0000000..8c68c38 --- /dev/null +++ b/club/migrations/0015_alter_club_logo.py @@ -0,0 +1,20 @@ +# Generated by Django 6.0.6 on 2026-07-24 16:40 + +import club.models +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('club', '0014_club_secondary_color'), + ] + + operations = [ + migrations.AlterField( + model_name='club', + name='logo', + field=models.FileField(blank=True, help_text="Shown on the club's own pages. Without one, the club's initials are used.", upload_to=club.models.club_logo_path, validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'])], verbose_name='logo'), + ), + ] diff --git a/club/models.py b/club/models.py index 80c3602..e3635fe 100644 --- a/club/models.py +++ b/club/models.py @@ -1,6 +1,6 @@ import datetime -from django.core.validators import RegexValidator +from django.core.validators import FileExtensionValidator, RegexValidator from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -31,7 +31,15 @@ class Club(UUIDModel): name = models.CharField(_("name"), max_length=255) slug = models.SlugField(_("slug"), max_length=255, unique=True, blank=True, help_text=_("Drives subdomain / path resolution (e.g. ajax-united.rosterchief.app).")) - logo = models.ImageField(_("logo"), upload_to=club_logo_path, blank=True, help_text=_("Shown on the club's own pages. Without one, the club's initials are used.")) + logo = models.FileField( + _("logo"), + upload_to=club_logo_path, + blank=True, + # A plain FileField, not ImageField: Pillow (which ImageField validates through) + # cannot read SVGs, and club crests are commonly vector logos. + validators=[FileExtensionValidator(allowed_extensions=["png", "jpg", "jpeg", "gif", "webp", "svg"])], + help_text=_("Shown on the club's own pages. Without one, the club's initials are used."), + ) primary_color = models.CharField( _("primary colour"), max_length=7, diff --git a/controlpanel/forms.py b/controlpanel/forms.py index 6c6f33f..8e188f8 100644 --- a/controlpanel/forms.py +++ b/controlpanel/forms.py @@ -21,6 +21,7 @@ class ClubForm(forms.ModelForm): widgets = { "primary_color": forms.TextInput(attrs={"placeholder": "#1e40af"}), "secondary_color": forms.TextInput(attrs={"placeholder": "#be185d"}), + "logo": forms.ClearableFileInput(attrs={"accept": "image/png,image/jpeg,image/gif,image/webp,image/svg+xml"}), } def __init__(self, *args, **kwargs):