diff --git a/src/apps/api/serializers/competitions.py b/src/apps/api/serializers/competitions.py index 2d1e1639c..e3bc20fb7 100644 --- a/src/apps/api/serializers/competitions.py +++ b/src/apps/api/serializers/competitions.py @@ -327,6 +327,7 @@ class CompetitionCreateSerializer(CompetitionSerializer): class CompetitionDetailSerializer(serializers.ModelSerializer): created_by = serializers.CharField(source='created_by.username', read_only=True) + logo_icon = NamedBase64ImageField(allow_null=True) pages = PageSerializer(many=True) phases = PhaseDetailSerializer(many=True) leaderboards = serializers.SerializerMethodField() @@ -347,6 +348,7 @@ class Meta: 'created_by', 'created_when', 'logo', + 'logo_icon', 'terms', 'pages', 'phases', diff --git a/src/apps/competitions/migrations/0045_auto_20240129_2314.py b/src/apps/competitions/migrations/0045_auto_20240129_2314.py new file mode 100644 index 000000000..2cb752724 --- /dev/null +++ b/src/apps/competitions/migrations/0045_auto_20240129_2314.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.17 on 2024-01-29 23:14 + +from django.db import migrations, models +import utils.data + + +class Migration(migrations.Migration): + + dependencies = [ + ('competitions', '0044_merge_20231221_1416'), + ] + + operations = [ + migrations.AddField( + model_name='competition', + name='logo_icon', + field=models.ImageField(blank=True, null=True, upload_to=utils.data.PathWrapper('logos', manual_override=True)), + ), + ] diff --git a/src/apps/competitions/models.py b/src/apps/competitions/models.py index dc8e79b9a..1f96cc84e 100644 --- a/src/apps/competitions/models.py +++ b/src/apps/competitions/models.py @@ -1,9 +1,12 @@ import logging import uuid +import os +import io from django.conf import settings from django.contrib.sites.models import Site from django.contrib.postgres.fields import JSONField +from django.core.files.base import ContentFile from django.db import models from django.db.models import Q from django.urls import reverse @@ -15,6 +18,7 @@ from profiles.models import User, Organization from utils.data import PathWrapper from utils.storage import BundleStorage +from PIL import Image from tasks.models import Task @@ -32,6 +36,7 @@ class Competition(ChaHubSaveMixin, models.Model): title = models.CharField(max_length=256) logo = models.ImageField(upload_to=PathWrapper('logos'), null=True, blank=True) + logo_icon = models.ImageField(upload_to=PathWrapper('logos', manual_override=True), null=True, blank=True) created_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="competitions") created_when = models.DateTimeField(default=now) @@ -214,8 +219,37 @@ def get_chahub_data(self): return self.clean_private_data(data) + def make_logo_icon(self): + if self.logo: + # Read the content of the logo file + self.logo.name + self.logo_icon + icon_dirname_only = os.path.dirname(self.logo.name) # Get just the path + icon_basename_only = os.path.basename(self.logo.name) # Get just the filename + file_name = os.path.splitext(icon_basename_only)[0] + ext = os.path.splitext(icon_basename_only)[1] + new_path = os.path.join(icon_dirname_only, f"{file_name}_icon{ext}") + logo_content = self.logo.read() + original_logo = Image.open(io.BytesIO(logo_content)) + # Resize the image to a smaller size for logo_icon + width, height = original_logo.size + new_width = 100 # Specify the desired width for the logo_icon + new_height = int((new_width / width) * height) + resized_logo = original_logo.resize((new_width, new_height)) + # Create a BytesIO object to save the resized image + icon_content = io.BytesIO() + resized_logo.save(icon_content, format='PNG') + # Save the resized logo as logo_icon + self.logo_icon.save(new_path, ContentFile(icon_content.getvalue()), save=False) + def save(self, *args, **kwargs): super().save(*args, **kwargs) + if not self.logo: + pass + elif not self.logo_icon: + self.make_logo_icon() + elif os.path.dirname(self.logo.name) != os.path.dirname(self.logo_icon.name): + self.make_logo_icon() to_create = User.objects.filter( Q(id=self.created_by_id) | Q(id__in=self.collaborators.all().values_list('id', flat=True)) ).exclude(id__in=self.participants.values_list('user_id', flat=True)).distinct() diff --git a/src/static/riot/competitions/public-list.tag b/src/static/riot/competitions/public-list.tag index 2eb854a08..c9c8e4680 100644 --- a/src/static/riot/competitions/public-list.tag +++ b/src/static/riot/competitions/public-list.tag @@ -13,7 +13,7 @@
- +
diff --git a/src/utils/data.py b/src/utils/data.py index 0303888d6..7d743f361 100644 --- a/src/utils/data.py +++ b/src/utils/data.py @@ -19,20 +19,28 @@ class PathWrapper(object): """Helper to generate UUID's in file names while maintaining their extension""" - def __init__(self, base_directory): + def __init__(self, base_directory, manual_override=False): self.path = base_directory + self.manual_override = manual_override def __call__(self, instance, filename): - name, extension = os.path.splitext(filename) - truncated_uuid = uuid.uuid4().hex[0:12] - truncated_name = name[0:35] - - return os.path.join( - self.path, - now().strftime('%Y-%m-%d-%s'), - truncated_uuid, - "{0}{1}".format(truncated_name, extension) - ) + if not self.manual_override: + name, extension = os.path.splitext(filename) + truncated_uuid = uuid.uuid4().hex[0:12] + truncated_name = name[0:35] + + path = os.path.join( + self.path, + now().strftime('%Y-%m-%d-%s'), + truncated_uuid, + "{0}{1}".format(truncated_name, extension) + ) + else: + path = os.path.join( + filename + ) + + return path def make_url_sassy(path, permission='r', duration=60 * 60 * 24, content_type='application/zip'):