diff --git a/website/admin/award_admin.py b/website/admin/award_admin.py index 011a88f4..cda960b4 100644 --- a/website/admin/award_admin.py +++ b/website/admin/award_admin.py @@ -1,7 +1,11 @@ +import os + from django import forms from django.contrib import admin from django.urls import reverse from django.utils.html import format_html +from easy_thumbnails.files import get_thumbnailer +from image_cropping import ImageCroppingMixin from website.models import Award from website.admin.admin_site import ml_admin_site from sortedm2m_filter_horizontal_widget.forms import SortedFilteredSelectMultiple @@ -28,12 +32,12 @@ def clean(self): @admin.register(Award, site=ml_admin_site) -class AwardAdmin(admin.ModelAdmin): +class AwardAdmin(ImageCroppingMixin, admin.ModelAdmin): form = AwardAdminForm # get_recipient_names / get_project_names are methods on the Award model; # their column headers come from each method's short_description. - list_display = ('title', 'organization', 'date', + list_display = ('title', 'get_display_thumbnail', 'organization', 'date', 'get_recipient_names', 'get_project_names', 'award_type') list_filter = ('award_type', 'date') @@ -80,7 +84,7 @@ def get_fieldsets(self, request, obj=None): 'fields': ['url', 'description'], }), ('Display', { - 'fields': ['badge', 'badge_alt_text'], + 'fields': ['badge', 'badge_cropping', 'badge_alt_text'], 'description': 'Optional. On the Awards page, faculty honors show a medal icon, ' 'student awards show the recipient’s photo, and project awards ' 'show the project thumbnail. Upload a badge/logo here to override ' @@ -97,4 +101,19 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): """ if db_field.name == 'recipients' or db_field.name == 'projects': kwargs['widget'] = SortedFilteredSelectMultiple() - return super().formfield_for_manytomany(db_field, request, **kwargs) \ No newline at end of file + return super().formfield_for_manytomany(db_field, request, **kwargs) + + def get_display_thumbnail(self, obj): + """Square preview of the uploaded badge (with its crop applied) in the + changelist, mirroring the SponsorAdmin/NewsAdmin logo columns. Awards + without a custom badge fall back to a medal icon on the public page, so + there's nothing to show here.""" + if obj.badge and os.path.isfile(obj.badge.path): + thumbnailer = get_thumbnailer(obj.badge) + options = {'size': (50, 50), 'crop': True, 'box': obj.badge_cropping} + thumbnail_url = thumbnailer.get_thumbnail(options).url + return format_html('', thumbnail_url) + return '—' + + get_display_thumbnail.short_description = 'Badge' \ No newline at end of file diff --git a/website/models/award.py b/website/models/award.py index 9dc033cf..eff30435 100644 --- a/website/models/award.py +++ b/website/models/award.py @@ -1,6 +1,8 @@ from django.db import models from sortedm2m.fields import SortedManyToManyField +from image_cropping import ImageRatioField + from website.utils.fileutils import UniquePathAndRename from website.utils.upload_validators import validate_image_upload @@ -72,6 +74,14 @@ class Award(models.Model): "Student awards default to the recipient's photo and project awards to " "the project thumbnail; uploading a badge overrides those.") + # Square crop box for the badge, applied on the public Awards page so every + # anchor (badge, portrait, project thumbnail, medal) reads as a uniform square + # tile. Stored as an "x1,y1,x2,y2" string; the admin shows a Cropper.js preview + # before the first save (same pattern as Person.cropping / Sponsor.icon_cropping). + badge_cropping = ImageRatioField('badge', '245x245', size_warning=True) + badge_cropping.help_text = ("Crop the badge to a square using the preview above " + "(no need to save first). Keeps award anchors uniform.") + badge_alt_text = models.CharField(max_length=255, blank=True, null=True) badge_alt_text.help_text = "Alt text for the badge image. Defaults to the award title if left blank." diff --git a/website/static/website/css/awards.css b/website/static/website/css/awards.css index 8c1b98e3..96190c98 100644 --- a/website/static/website/css/awards.css +++ b/website/static/website/css/awards.css @@ -80,10 +80,11 @@ border-radius: 50%; } -/* Uploaded emblem/logo: show whole (don't crop) and let it sit on the page. */ +/* Uploaded emblem/logo: editors square-crop it in the admin (Award.badge_cropping), + so it fills the same square tile as the portrait/thumbnail anchors for a uniform + row. The shared .award-anchor-img rules already supply object-fit: cover + border. */ .award-anchor-badge { - object-fit: contain; - border: none; + border-radius: var(--border-radius-md); } /* Faculty honors: medal icon in a soft circular chip. */ diff --git a/website/templates/snippets/display_award_snippet.html b/website/templates/snippets/display_award_snippet.html index 779d05af..df84562f 100644 --- a/website/templates/snippets/display_award_snippet.html +++ b/website/templates/snippets/display_award_snippet.html @@ -25,7 +25,8 @@