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 @@