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
18 changes: 18 additions & 0 deletions apps/sponsors/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ class SponsorshipAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
"get_sponsor_description",
"get_sponsor_landing_page_url",
"get_sponsor_web_logo",
"get_sponsor_white_logo",
"get_sponsor_print_logo",
"get_sponsor_primary_phone",
"get_sponsor_mailing_address",
Expand Down Expand Up @@ -630,6 +631,7 @@ def get_readonly_fields(self, request, obj):
"get_sponsor_description",
"get_sponsor_landing_page_url",
"get_sponsor_web_logo",
"get_sponsor_white_logo",
"get_sponsor_print_logo",
"get_sponsor_primary_phone",
"get_sponsor_mailing_address",
Expand Down Expand Up @@ -749,6 +751,22 @@ def get_sponsor_web_logo(self, obj):
context = Context({"img": img})
return mark_safe(template.render(context)) # noqa: S308

@admin.display(description="White Logo")
def get_sponsor_white_logo(self, obj):
"""Render and return the sponsor's white logo as a thumbnail image."""
img = obj.sponsor.white_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;background:#333"/>',
img.url,
)
html = "{% load thumbnail %}{% thumbnail img '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}' style='background:#333'/>{% endthumbnail %}"
template = Template(html)
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."""
Expand Down
1 change: 1 addition & 0 deletions apps/sponsors/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def get(self, request, *args, **kwargs):
"level_order": sponsorship.package.order,
"description": sponsor.description,
"logo": sponsor.web_logo.url,
"white_logo": sponsor.white_logo.url if sponsor.white_logo else None,
"sponsor_url": sponsor.landing_page_url,
"start_date": sponsorship.start_date,
"end_date": sponsorship.end_date,
Expand Down
12 changes: 12 additions & 0 deletions apps/sponsors/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ class SponsorshipApplicationForm(forms.Form):
help_text="For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than 256px",
required=False,
)
white_logo = forms.ImageField(
label="Sponsor white logo",
help_text="For display on dark backgrounds (e.g. PyPI footer). Transparent PNG, smallest dimension no less than 256px",
required=False,
)
print_logo = forms.FileField(
label="Sponsor print logo",
help_text="For printed materials, signage, and projection. SVG or EPS",
Expand Down Expand Up @@ -396,6 +401,7 @@ def save(self):
landing_page_url=self.cleaned_data.get("landing_page_url", ""),
twitter_handle=self.cleaned_data["twitter_handle"],
linked_in_page_url=self.cleaned_data["linked_in_page_url"],
white_logo=self.cleaned_data.get("white_logo"),
print_logo=self.cleaned_data.get("print_logo"),
country_of_incorporation=self.cleaned_data.get("country_of_incorporation", ""),
state_of_incorporation=self.cleaned_data.get("state_of_incorporation", ""),
Expand Down Expand Up @@ -606,6 +612,11 @@ class SponsorUpdateForm(forms.ModelForm):
help_text="For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than 256px",
required=False,
)
white_logo = forms.ImageField(
widget=forms.widgets.FileInput,
help_text="For display on dark backgrounds (e.g. PyPI footer). Transparent PNG, smallest dimension no less than 256px",
required=False,
)
print_logo = forms.FileField(
widget=forms.widgets.FileInput,
help_text="For printed materials, signage, and projection. SVG or EPS",
Expand Down Expand Up @@ -647,6 +658,7 @@ class Meta:
"twitter_handle",
"linked_in_page_url",
"web_logo",
"white_logo",
"print_logo",
"primary_phone",
"mailing_address_line_1",
Expand Down
23 changes: 23 additions & 0 deletions apps/sponsors/migrations/0104_add_sponsor_white_logo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.2.11 on 2026-04-06 18:04

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("sponsors", "0103_alter_benefitfeature_polymorphic_ctype_and_more"),
]

operations = [
migrations.AddField(
model_name="sponsor",
name="white_logo",
field=models.ImageField(
blank=True,
help_text="For display on dark backgrounds (e.g. PyPI footer). Transparent PNG, smallest dimension no less than 256px",
null=True,
upload_to="sponsor_white_logos",
verbose_name="White logo",
),
),
]
7 changes: 7 additions & 0 deletions apps/sponsors/models/sponsors.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ class Sponsor(ContentManageable):
help_text="For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than "
"256px",
)
white_logo = models.ImageField(
upload_to="sponsor_white_logos",
blank=True,
null=True,
verbose_name="White logo",
help_text="For display on dark backgrounds (e.g. PyPI footer). Transparent PNG, smallest dimension no less than 256px",
)
print_logo = models.FileField(
upload_to="sponsor_print_logos",
validators=[FileExtensionValidator(["eps", "epsfepsi", "svg", "png"])],
Expand Down
1 change: 1 addition & 0 deletions apps/sponsors/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class LogoPlacementSerializer(serializers.Serializer):
sponsor_slug = serializers.CharField()
description = serializers.CharField()
logo = serializers.URLField()
white_logo = serializers.URLField(required=False, allow_null=True)
start_date = serializers.DateField()
end_date = serializers.DateField()
sponsor_url = serializers.URLField()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,70 +108,68 @@ <h2>Basics</h2>
{% endif %}
</p>

<div class="inline_fields">
<div>
<p class="form_field">
<label>{{ form.landing_page_url.label }} <span class="error-message">{% if form.landing_page_url.errors %}{{ form.landing_page_url.errors.as_text }}</span>{% endif %}</label>
{% render_field form.landing_page_url %}
{% if form.landing_page_url.help_text %}
<br/>
<span class="helptext">{{ form.landing_page_url.help_text }}</span>
{% endif %}
</p>
</div>
<p class="form_field">
<label>{{ form.landing_page_url.label }} <span class="error-message">{% if form.landing_page_url.errors %}{{ form.landing_page_url.errors.as_text }}</span>{% endif %}</label>
{% render_field form.landing_page_url %}
{% if form.landing_page_url.help_text %}
<br/>
<span class="helptext">{{ form.landing_page_url.help_text }}</span>
{% endif %}
</p>

<div>
<p class="form_field">
<label>{{ form.twitter_handle.label }} <span class="error-message">{% if form.twitter_handle.errors %}{{ form.twitter_handle.errors.as_text }}</span>{% endif %}</label>
{% render_field form.twitter_handle %}
{% if form.twitter_handle.help_text %}
<br/>
<span class="helptext">{{ form.twitter_handle.help_text }}</span>
{% endif %}
</p>
</div>
<p class="form_field">
<label>{{ form.twitter_handle.label }} <span class="error-message">{% if form.twitter_handle.errors %}{{ form.twitter_handle.errors.as_text }}</span>{% endif %}</label>
{% render_field form.twitter_handle %}
{% if form.twitter_handle.help_text %}
<br/>
<span class="helptext">{{ form.twitter_handle.help_text }}</span>
{% endif %}
</p>
Comment on lines +111 to +127
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

This template also removes an .inline_fields grouping for the landing page / Twitter / LinkedIn fields. Since .inline_fields has specific 2-column styling (static/css/style.css:3485-3493), please confirm the layout change is intentional; otherwise, restore the wrapper or adjust the grouping to keep the intended column layout.

Copilot uses AI. Check for mistakes.

<div>
<p class="form_field">
<label>{{ form.linked_in_page_url.label }} <span class="error-message">{% if form.linked_in_page_url.errors %}{{ form.linked_in_page_url.errors.as_text }}</span>{% endif %}</label>
{% render_field form.linked_in_page_url%}
{% if form.linked_in_page_url.help_text %}
<br/>
<span class="helptext">{{ form.linked_in_page_url.help_text }}</span>
{% endif %}
</p>
</div>
</div>
<p class="form_field">
<label>{{ form.linked_in_page_url.label }} <span class="error-message">{% if form.linked_in_page_url.errors %}{{ form.linked_in_page_url.errors.as_text }}</span>{% endif %}</label>
{% render_field form.linked_in_page_url%}
{% if form.linked_in_page_url.help_text %}
<br/>
<span class="helptext">{{ form.linked_in_page_url.help_text }}</span>
{% endif %}
</p>

<div class="inline_fields">
<div>
<p class="form_field">
<p class="form_field">
<label>{{ form.web_logo.label }} <span class="error-message">{% if form.web_logo.errors %}{{ form.web_logo.errors.as_text }}</span>{% endif %}</label>
{% render_field form.web_logo %}
{% if sponsor.web_logo %}
<p>Currently: <a href="{{ sponsor.web_logo.url }}">{{ sponsor.web_logo.name }}</a></p>
{% endif %}
{% if form.web_logo.help_text %}
<br/>
<span class="helptext">{{ form.web_logo.help_text }}</span>
{% endif %}
</p>
</div>
{% render_field form.web_logo %}
{% if sponsor.web_logo %}
<br/>Currently: <a href="{{ sponsor.web_logo.url }}">{{ sponsor.web_logo.name }}</a>
{% endif %}
{% if form.web_logo.help_text %}
<br/>
<span class="helptext">{{ form.web_logo.help_text }}</span>
{% endif %}
</p>
Comment on lines +138 to +148
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

The .inline_fields wrapper around the logo upload fields was removed, which will change the 2-column layout controlled by static/css/style.css:3485-3493. With the new white_logo field, consider regrouping the logo inputs into one or more .inline_fields sections (e.g., web_logo + white_logo side-by-side, print_logo below) so the layout remains consistent with existing styling.

Copilot uses AI. Check for mistakes.

<div>
<p class="form_field">
<label>{{ form.print_logo.label }} <span class="error-message">{% if form.print_logo.errors %}{{ form.print_logo.errors.as_text }}</span>{% endif %}</label>
{% render_field form.print_logo %}
{% if sponsor.print_logo %}
<p>Currently: <a href="{{ sponsor.print_logo.url }}">{{ sponsor.print_logo.name }}</a></p>
{% endif %}
{% if form.print_logo.help_text %}
<br/>
<span class="helptext">{{ form.print_logo.help_text }}</span>
{% endif %}
</p>
</div>
</div>
<p class="form_field">
<label>{{ form.white_logo.label }} <span class="error-message">{% if form.white_logo.errors %}{{ form.white_logo.errors.as_text }}</span>{% endif %}</label>
{% render_field form.white_logo %}
{% if sponsor.white_logo %}
<br/>Currently: <a href="{{ sponsor.white_logo.url }}">{{ sponsor.white_logo.name }}</a>
{% endif %}
{% if form.white_logo.help_text %}
<br/>
<span class="helptext">{{ form.white_logo.help_text }}</span>
{% endif %}
</p>

<p class="form_field">
<label>{{ form.print_logo.label }} <span class="error-message">{% if form.print_logo.errors %}{{ form.print_logo.errors.as_text }}</span>{% endif %}</label>
{% render_field form.print_logo %}
{% if sponsor.print_logo %}
<br/>Currently: <a href="{{ sponsor.print_logo.url }}">{{ sponsor.print_logo.name }}</a>
{% endif %}
{% if form.print_logo.help_text %}
<br/>
<span class="helptext">{{ form.print_logo.help_text }}</span>
{% endif %}
</p>

<hr>

Expand Down
21 changes: 21 additions & 0 deletions apps/sponsors/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,30 @@ def tearDown(self):
for sponsor in Sponsor.objects.all():
if sponsor.web_logo:
sponsor.web_logo.delete()
if sponsor.white_logo:
sponsor.white_logo.delete()
if sponsor.print_logo:
sponsor.print_logo.delete()

def test_white_logo_null_when_not_set(self):
response = self.client.get(self.url, headers={"authorization": self.authorization})
data = response.json()
self.assertEqual(200, response.status_code)
for placement in data:
self.assertIn("white_logo", placement)
self.assertIsNone(placement["white_logo"])

def test_white_logo_url_when_set(self):
sponsor = self.sponsors[0]
sponsor.white_logo = SimpleUploadedFile(name="white.png", content=b"img", content_type="image/png")
sponsor.save()
response = self.client.get(self.url, headers={"authorization": self.authorization})
data = response.json()
sponsor_placements = [p for p in data if p["sponsor"] == sponsor.name]
for placement in sponsor_placements:
self.assertIsNotNone(placement["white_logo"])
self.assertIn("white.png", placement["white_logo"])

def test_list_logo_placement_as_expected(self):
response = self.client.get(self.url, headers={"authorization": self.authorization})
data = response.json()
Expand Down
74 changes: 39 additions & 35 deletions apps/users/templates/users/sponsor_info_update.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,41 +71,33 @@ <h2>Sponsor Information</h2>
{% render_field form.description %}
</p>

<div class="inline_fields">
<div>
<p class="form_field">
<label>{{ form.landing_page_url.label }} <span class="error-message">{% if form.landing_page_url.errors %}
{{ form.landing_page_url.errors.as_text }}</span>{% endif %}</label>
<span
class="helptext">Landing page URL. The linked page may not contain any sales or marketing information.</span>
{% render_field form.landing_page_url %}
</p>
</div>
<p class="form_field">
<label>{{ form.landing_page_url.label }} <span class="error-message">{% if form.landing_page_url.errors %}
{{ form.landing_page_url.errors.as_text }}</span>{% endif %}</label>
<span
class="helptext">Landing page URL. The linked page may not contain any sales or marketing information.</span>
{% render_field form.landing_page_url %}
</p>
Comment on lines +74 to +80
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

This template refactors several fields from being inside .inline_fields wrappers to standalone <p class="form_field"> blocks (e.g., landing page / Twitter / LinkedIn). That changes the layout behavior tied to the .inline_fields CSS rules in static/css/style.css:3485-3493 (inline-block 49% columns). If the layout change isn’t intended as part of “allow white logos”, consider reverting/splitting these markup changes or adjusting the grouping so the original 2-column layout is preserved.

Copilot uses AI. Check for mistakes.

<div>
<p class="form_field">
<label>{{ form.twitter_handle.label }} <span class="error-message">{% if form.twitter_handle.errors %}
{{ form.twitter_handle.errors.as_text }}</span>{% endif %}</label>
{% render_field form.twitter_handle %}
{% if form.twitter_handle.help_text %}
<br/>
<span class="helptext">{{ form.twitter_handle.help_text }}</span>
{% endif %}
</p>
</div>
<p class="form_field">
<label>{{ form.twitter_handle.label }} <span class="error-message">{% if form.twitter_handle.errors %}
{{ form.twitter_handle.errors.as_text }}</span>{% endif %}</label>
{% render_field form.twitter_handle %}
{% if form.twitter_handle.help_text %}
<br/>
<span class="helptext">{{ form.twitter_handle.help_text }}</span>
{% endif %}
</p>

<div>
<p class="form_field">
<label>{{ form.linked_in_page_url.label }} <span class="error-message">{% if form.linked_in_page_url.errors %}
{{ form.linked_in_page_url.errors.as_text }}</span>{% endif %}</label>
{% render_field form.linked_in_page_url%}
{% if form.linked_in_page_url.help_text %}
<br/>
<span class="helptext">{{ form.linked_in_page_url.help_text }}</span>
{% endif %}
</p>
</div>
</div>
<p class="form_field">
<label>{{ form.linked_in_page_url.label }} <span class="error-message">{% if form.linked_in_page_url.errors %}
{{ form.linked_in_page_url.errors.as_text }}</span>{% endif %}</label>
{% render_field form.linked_in_page_url%}
{% if form.linked_in_page_url.help_text %}
<br/>
<span class="helptext">{{ form.linked_in_page_url.help_text }}</span>
{% endif %}
</p>
<div class="inline_fields">
<div>
<p class="form_field">
Expand Down Expand Up @@ -227,19 +219,31 @@ <h2>Sponsor Information</h2>
{{ form.web_logo.errors.as_text }}</span>{% endif %}</label>
{% render_field form.web_logo %}
{% if sponsor.web_logo %}
<p>Currently: <a href="{{ sponsor.web_logo.url }}">{{ sponsor.web_logo.name }}</a></p>
<br/>Currently: <a href="{{ sponsor.web_logo.url }}">{{ sponsor.web_logo.name }}</a>
{% endif %}
{% if form.web_logo.help_text %}
<span class="helptext">{{ form.web_logo.help_text }}</span>
{% endif %}
Comment on lines +222 to 226
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

Currently: is rendered inline via <br/>Currently: ... and then the help text <span class="helptext">…</span> follows without a line break or block wrapper. This will cause the help text to run together with the “Currently” link on the same line. Consider adding a <br/> before the helptext (or wrapping the “Currently” link in its own block element) for consistent spacing.

Copilot uses AI. Check for mistakes.
</p>

<p class="form_field">
<label>{{ form.white_logo.label }} <span class="error-message">{% if form.white_logo.errors %}
{{ form.white_logo.errors.as_text }}</span>{% endif %}</label>
{% render_field form.white_logo %}
{% if sponsor.white_logo %}
<br/>Currently: <a href="{{ sponsor.white_logo.url }}">{{ sponsor.white_logo.name }}</a>
{% endif %}
{% if form.white_logo.help_text %}
<span class="helptext">{{ form.white_logo.help_text }}</span>
{% endif %}
Comment on lines +234 to +238
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

Same spacing issue as web_logo: the “Currently” link and the help text are rendered back-to-back without a line break, so the help text will appear immediately after the link. Add a <br/> before the helptext (or wrap “Currently” in its own block element).

Copilot uses AI. Check for mistakes.
</p>

<p class="form_field">
<label>{{ form.print_logo.label }} <span class="error-message">{% if form.print_logo.errors %}
{{ form.print_logo.errors.as_text }}</span>{% endif %}</label>
{% render_field form.print_logo %}
{% if sponsor.print_logo %}
<p>Currently: <a href="{{ sponsor.print_logo.url }}">{{ sponsor.print_logo.name }}</a></p>
<br/>Currently: <a href="{{ sponsor.print_logo.url }}">{{ sponsor.print_logo.name }}</a>
{% endif %}
{% if form.print_logo.help_text %}
<span class="helptext">{{ form.print_logo.help_text }}</span>
Expand Down
Loading