Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions apps/sponsors/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,24 +736,35 @@ def get_sponsor_landing_page_url(self, obj):
@admin.display(description="Web Logo")
def get_sponsor_web_logo(self, obj):
"""Render and return the sponsor's web logo as a thumbnail image."""
html = "{% load thumbnail %}{% thumbnail sponsor.web_logo '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}'/>{% endthumbnail %}"
img = obj.sponsor.web_logo
if not img:
return "---"
if img.name and img.name.lower().endswith(".svg"):
return format_html(
'<img src="{}" style="max-width:150px;max-height:150px"/>',
img.url,
Comment on lines 736 to +745
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These methods’ docstrings say they always return a “thumbnail image”, but the SVG branch returns the original asset (just CSS-constrained). Consider updating the docstrings (and/or the @admin.display description) to reflect the mixed behavior so future maintainers don’t assume everything is thumbnailed/rasterized.

Copilot uses AI. Check for mistakes.
)
html = "{% load thumbnail %}{% thumbnail img '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}'/>{% endthumbnail %}"
template = Template(html)
context = Context({"sponsor": obj.sponsor})
html = template.render(context)
return mark_safe(html) # noqa: S308
context = Context({"img": img})
return mark_safe(template.render(context)) # noqa: S308

@admin.display(description="Print Logo")
def get_sponsor_print_logo(self, obj):
"""Render and return the sponsor's print logo as a thumbnail image."""
img = obj.sponsor.print_logo
html = "---"
if img:
template = Template(
"{% load thumbnail %}{% thumbnail img '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}'/>{% endthumbnail %}"
if not img:
return "---"
if img.name and img.name.lower().endswith(".svg"):
return format_html(
'<img src="{}" style="max-width:150px;max-height:150px"/>',
img.url,
)
context = Context({"img": img})
html = mark_safe(template.render(context)) # noqa: S308
return html
template = Template(
"{% load thumbnail %}{% thumbnail img '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}'/>{% endthumbnail %}"
)
context = Context({"img": img})
return mark_safe(template.render(context)) # noqa: S308
Comment on lines 736 to +767
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s no automated coverage around the new SVG-vs-non-SVG branching for admin logo rendering. Given this was added to prevent failures/OOMs, consider adding a unit test that exercises both branches (e.g., .svg returns raw img.url via format_html, non-SVG goes through the thumbnail path) so regressions are caught early.

Copilot uses AI. Check for mistakes.

@admin.display(description="Primary Phone")
def get_sponsor_primary_phone(self, obj):
Expand Down
16 changes: 12 additions & 4 deletions apps/sponsors/templates/sponsors/partials/sponsors-list.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ <h2 class="widget-title" style="text-align: center;">Sponsors</h2>
<p style="text-align: center;">Visionary sponsors help to host Python downloads.</p>
<div style="display: grid; grid-gap: 2em; grid-template-columns: repeat(auto-fit, minmax(150px, 0fr)); align-items: center; justify-content: center; margin-top: 1.5em;">
{% for sponsorship in sponsorships %}
{% thumbnail sponsorship.sponsor.web_logo "x150" format="PNG" quality=100 as im %}
<div style="text-align: center;">
<a href="{{ sponsorship.sponsor.landing_page_url }}" rel="sponsored noopener" target="_blank" style="border-bottom: 0;">
{% if sponsorship.sponsor.web_logo.name|lower|slice:"-4:" == ".svg" %}
<img src="{{ sponsorship.sponsor.web_logo.url }}" alt="{{ sponsorship.sponsor.name }} logo" style="max-height:150px;max-width:150px;height:auto;width:auto;">
{% else %}
{% thumbnail sponsorship.sponsor.web_logo "x150" format="PNG" quality=100 as im %}
<img src="{{ im.url }}" alt="{{ sponsorship.sponsor.name }} logo" style="max-height:150px;max-width:150px;height:auto;width:auto;">
{% endthumbnail %}
Comment on lines +15 to +20
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template now has conditional behavior based on file extension (SVG served directly vs raster thumbnailed). Consider adding a regression test that renders this partial (via list_sponsors) with a .svg web_logo and asserts it does not invoke the thumbnail tag / does not try to generate a PNG, since that’s the behavior intended to avoid OOMs.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +20
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change serves uploaded SVGs directly for sponsor.web_logo (bypassing rasterization). Note that Sponsor.web_logo is an ImageField with help_text indicating PNG/JPG only, so allowing/rendering SVG here creates a mismatch with the model/form expectations and can also introduce SVG-specific security concerns (e.g., untrusted SVG content being rendered in browsers). Consider either (a) enforcing/repairing data so web_logo is always raster (and keep thumbnailing), or (b) formally supporting SVG by updating validation/docs and adding SVG sanitization and/or converting SVG->PNG at upload time in a controlled way.

Copilot uses AI. Check for mistakes.
{% endif %}
Comment on lines +15 to +21
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SVG detection / rendering logic is duplicated in both the download and jobs sections. To reduce the chance of future drift (e.g., adding another placement/size), consider extracting this into a small reusable include or a custom template filter/tag (e.g., render_sponsor_logo sponsorship.sponsor.web_logo size=150).

Copilot uses AI. Check for mistakes.
</a>
<p style="margin-top: 0.5em; margin-bottom: 0; font-size: 0.875em; color: #4d4d4d;">{{ sponsorship.sponsor.name }}</p>
</div>
{% endthumbnail %}
{% endfor %}
</div>
{% endcache CACHED_DOWNLOAD_SPONSORS_LIST %}
Expand All @@ -28,11 +32,15 @@ <h2 class="widget-title" style="text-align: center;">Sponsors</h2>
<h3 class="widget-title">Job Board Sponsors</h3>
<div style="display: grid; grid-gap: 1em; grid-template-columns: repeat(auto-fit, minmax(100px, 0fr)); grid-template-rows: repeat(1, minmax(50px, 0fr)); align-items: center; justify-content: center; margin-top: 1em;">
{% for sponsorship in sponsorships %}
{% thumbnail sponsorship.sponsor.web_logo "x100" format="PNG" quality=100 as im %}
<div>
{% if sponsorship.sponsor.web_logo.name|lower|slice:"-4:" == ".svg" %}
<img src="{{ sponsorship.sponsor.web_logo.url }}" alt="{{ sponsorship.sponsor.name }} logo" style="max-height:100px;max-width:100px;height:auto;width:auto;">
{% else %}
{% thumbnail sponsorship.sponsor.web_logo "x100" format="PNG" quality=100 as im %}
<img src="{{ im.url }}" alt="{{ sponsorship.sponsor.name }} logo" style="max-height:100px;max-width:100px;height:auto;width:auto;">
{% endthumbnail %}
Comment on lines +36 to +41
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern as the download sponsors block: serving web_logo SVGs directly here diverges from the ImageField/PNG-JPG expectation for Sponsor.web_logo and may require explicit validation/sanitization or an upload-time conversion approach to avoid rendering untrusted SVG content.

Copilot uses AI. Check for mistakes.
{% endif %}
</div>
{% endthumbnail %}
{% endfor %}
</div>
{% endcache CACHED_JOBS_SPONSORS_LIST %}
Expand Down
Loading