Skip to content

Commit ab92fed

Browse files
authored
Merge branch 'main' into feature/issue-2611-deprecate-provided-assets-view
2 parents 6f4c5ce + de5afa0 commit ab92fed

File tree

10 files changed

+186
-97
lines changed

10 files changed

+186
-97
lines changed

apps/companies/tests.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ def test_render_email(self):
1010
render_email("firstname.lastname@domain.com"),
1111
"firstname<span>.</span>lastname<span>@</span>domain<span>.</span>com",
1212
)
13+
self.assertEqual(
14+
render_email('"escape.>me"@domain.com'),
15+
"&quot;escape<span>.</span>&gt;me&quot;<span>@</span>domain<span>.</span>com",
16+
)

apps/sponsors/admin.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,7 @@ class SponsorshipAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
559559
"get_sponsor_description",
560560
"get_sponsor_landing_page_url",
561561
"get_sponsor_web_logo",
562+
"get_sponsor_white_logo",
562563
"get_sponsor_print_logo",
563564
"get_sponsor_primary_phone",
564565
"get_sponsor_mailing_address",
@@ -630,6 +631,7 @@ def get_readonly_fields(self, request, obj):
630631
"get_sponsor_description",
631632
"get_sponsor_landing_page_url",
632633
"get_sponsor_web_logo",
634+
"get_sponsor_white_logo",
633635
"get_sponsor_print_logo",
634636
"get_sponsor_primary_phone",
635637
"get_sponsor_mailing_address",
@@ -749,6 +751,22 @@ def get_sponsor_web_logo(self, obj):
749751
context = Context({"img": img})
750752
return mark_safe(template.render(context)) # noqa: S308
751753

754+
@admin.display(description="White Logo")
755+
def get_sponsor_white_logo(self, obj):
756+
"""Render and return the sponsor's white logo as a thumbnail image."""
757+
img = obj.sponsor.white_logo
758+
if not img:
759+
return "---"
760+
if img.name and img.name.lower().endswith(".svg"):
761+
return format_html(
762+
'<img src="{}" style="max-width:150px;max-height:150px;background:#333"/>',
763+
img.url,
764+
)
765+
html = "{% load thumbnail %}{% thumbnail img '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}' style='background:#333'/>{% endthumbnail %}"
766+
template = Template(html)
767+
context = Context({"img": img})
768+
return mark_safe(template.render(context)) # noqa: S308
769+
752770
@admin.display(description="Print Logo")
753771
def get_sponsor_print_logo(self, obj):
754772
"""Render and return the sponsor's print logo as a thumbnail image."""
@@ -818,7 +836,7 @@ def get_custom_benefits_added_by_user(self, obj):
818836
if not benefits:
819837
return "---"
820838

821-
return format_html_join("", "<p>{}</p>", ((b,) for b in benefits))
839+
return format_html_join("", "<p>{}</p>", [(benefit,) for benefit in benefits])
822840

823841
@admin.display(description="Removed by User")
824842
def get_custom_benefits_removed_by_user(self, obj):
@@ -827,7 +845,7 @@ def get_custom_benefits_removed_by_user(self, obj):
827845
if not benefits:
828846
return "---"
829847

830-
return format_html_join("", "<p>{}</p>", ((b,) for b in benefits))
848+
return format_html_join("", "<p>{}</p>", [(benefit,) for benefit in benefits])
831849

832850
def rollback_to_editing_view(self, request, pk):
833851
"""Delegate to the rollback_to_editing admin view."""
@@ -936,7 +954,7 @@ def other_years(self, obj):
936954

937955
html += format_html(
938956
dedent("""
939-
<li><b>{year}</b>:"
957+
<li><b>{year}</b>:
940958
<ul>
941959
<li><a target='_blank' href='{year_packages_url}'>List packages</a>
942960
<li><a target='_blank' href='{year_benefits_url}'>List benefits</a>

apps/sponsors/api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def get(self, request, *args, **kwargs):
5353
"level_order": sponsorship.package.order,
5454
"description": sponsor.description,
5555
"logo": sponsor.web_logo.url,
56+
"white_logo": sponsor.white_logo.url if sponsor.white_logo else None,
5657
"sponsor_url": sponsor.landing_page_url,
5758
"start_date": sponsorship.start_date,
5859
"end_date": sponsorship.end_date,

apps/sponsors/forms.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,11 @@ class SponsorshipApplicationForm(forms.Form):
229229
help_text="For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than 256px",
230230
required=False,
231231
)
232+
white_logo = forms.ImageField(
233+
label="Sponsor white logo",
234+
help_text="For display on dark backgrounds (e.g. PyPI footer). Transparent PNG, smallest dimension no less than 256px",
235+
required=False,
236+
)
232237
print_logo = forms.FileField(
233238
label="Sponsor print logo",
234239
help_text="For printed materials, signage, and projection. SVG or EPS",
@@ -396,6 +401,7 @@ def save(self):
396401
landing_page_url=self.cleaned_data.get("landing_page_url", ""),
397402
twitter_handle=self.cleaned_data["twitter_handle"],
398403
linked_in_page_url=self.cleaned_data["linked_in_page_url"],
404+
white_logo=self.cleaned_data.get("white_logo"),
399405
print_logo=self.cleaned_data.get("print_logo"),
400406
country_of_incorporation=self.cleaned_data.get("country_of_incorporation", ""),
401407
state_of_incorporation=self.cleaned_data.get("state_of_incorporation", ""),
@@ -606,6 +612,11 @@ class SponsorUpdateForm(forms.ModelForm):
606612
help_text="For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than 256px",
607613
required=False,
608614
)
615+
white_logo = forms.ImageField(
616+
widget=forms.widgets.FileInput,
617+
help_text="For display on dark backgrounds (e.g. PyPI footer). Transparent PNG, smallest dimension no less than 256px",
618+
required=False,
619+
)
609620
print_logo = forms.FileField(
610621
widget=forms.widgets.FileInput,
611622
help_text="For printed materials, signage, and projection. SVG or EPS",
@@ -647,6 +658,7 @@ class Meta:
647658
"twitter_handle",
648659
"linked_in_page_url",
649660
"web_logo",
661+
"white_logo",
650662
"print_logo",
651663
"primary_phone",
652664
"mailing_address_line_1",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 5.2.11 on 2026-04-06 18:04
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("sponsors", "0103_alter_benefitfeature_polymorphic_ctype_and_more"),
9+
]
10+
11+
operations = [
12+
migrations.AddField(
13+
model_name="sponsor",
14+
name="white_logo",
15+
field=models.ImageField(
16+
blank=True,
17+
help_text="For display on dark backgrounds (e.g. PyPI footer). Transparent PNG, smallest dimension no less than 256px",
18+
null=True,
19+
upload_to="sponsor_white_logos",
20+
verbose_name="White logo",
21+
),
22+
),
23+
]

apps/sponsors/models/sponsors.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ class Sponsor(ContentManageable):
4949
help_text="For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than "
5050
"256px",
5151
)
52+
white_logo = models.ImageField(
53+
upload_to="sponsor_white_logos",
54+
blank=True,
55+
null=True,
56+
verbose_name="White logo",
57+
help_text="For display on dark backgrounds (e.g. PyPI footer). Transparent PNG, smallest dimension no less than 256px",
58+
)
5259
print_logo = models.FileField(
5360
upload_to="sponsor_print_logos",
5461
validators=[FileExtensionValidator(["eps", "epsfepsi", "svg", "png"])],

apps/sponsors/serializers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class LogoPlacementSerializer(serializers.Serializer):
1616
sponsor_slug = serializers.CharField()
1717
description = serializers.CharField()
1818
logo = serializers.URLField()
19+
white_logo = serializers.URLField(required=False, allow_null=True)
1920
start_date = serializers.DateField()
2021
end_date = serializers.DateField()
2122
sponsor_url = serializers.URLField()

apps/sponsors/templates/sponsors/new_sponsorship_application_form.html

Lines changed: 57 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -108,70 +108,68 @@ <h2>Basics</h2>
108108
{% endif %}
109109
</p>
110110

111-
<div class="inline_fields">
112-
<div>
113-
<p class="form_field">
114-
<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>
115-
{% render_field form.landing_page_url %}
116-
{% if form.landing_page_url.help_text %}
117-
<br/>
118-
<span class="helptext">{{ form.landing_page_url.help_text }}</span>
119-
{% endif %}
120-
</p>
121-
</div>
111+
<p class="form_field">
112+
<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>
113+
{% render_field form.landing_page_url %}
114+
{% if form.landing_page_url.help_text %}
115+
<br/>
116+
<span class="helptext">{{ form.landing_page_url.help_text }}</span>
117+
{% endif %}
118+
</p>
122119

123-
<div>
124-
<p class="form_field">
125-
<label>{{ form.twitter_handle.label }} <span class="error-message">{% if form.twitter_handle.errors %}{{ form.twitter_handle.errors.as_text }}</span>{% endif %}</label>
126-
{% render_field form.twitter_handle %}
127-
{% if form.twitter_handle.help_text %}
128-
<br/>
129-
<span class="helptext">{{ form.twitter_handle.help_text }}</span>
130-
{% endif %}
131-
</p>
132-
</div>
120+
<p class="form_field">
121+
<label>{{ form.twitter_handle.label }} <span class="error-message">{% if form.twitter_handle.errors %}{{ form.twitter_handle.errors.as_text }}</span>{% endif %}</label>
122+
{% render_field form.twitter_handle %}
123+
{% if form.twitter_handle.help_text %}
124+
<br/>
125+
<span class="helptext">{{ form.twitter_handle.help_text }}</span>
126+
{% endif %}
127+
</p>
133128

134-
<div>
135-
<p class="form_field">
136-
<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>
137-
{% render_field form.linked_in_page_url%}
138-
{% if form.linked_in_page_url.help_text %}
139-
<br/>
140-
<span class="helptext">{{ form.linked_in_page_url.help_text }}</span>
141-
{% endif %}
142-
</p>
143-
</div>
144-
</div>
129+
<p class="form_field">
130+
<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>
131+
{% render_field form.linked_in_page_url%}
132+
{% if form.linked_in_page_url.help_text %}
133+
<br/>
134+
<span class="helptext">{{ form.linked_in_page_url.help_text }}</span>
135+
{% endif %}
136+
</p>
145137

146-
<div class="inline_fields">
147-
<div>
148-
<p class="form_field">
138+
<p class="form_field">
149139
<label>{{ form.web_logo.label }} <span class="error-message">{% if form.web_logo.errors %}{{ form.web_logo.errors.as_text }}</span>{% endif %}</label>
150-
{% render_field form.web_logo %}
151-
{% if sponsor.web_logo %}
152-
<p>Currently: <a href="{{ sponsor.web_logo.url }}">{{ sponsor.web_logo.name }}</a></p>
153-
{% endif %}
154-
{% if form.web_logo.help_text %}
155-
<br/>
156-
<span class="helptext">{{ form.web_logo.help_text }}</span>
157-
{% endif %}
158-
</p>
159-
</div>
140+
{% render_field form.web_logo %}
141+
{% if sponsor.web_logo %}
142+
<br/>Currently: <a href="{{ sponsor.web_logo.url }}">{{ sponsor.web_logo.name }}</a>
143+
{% endif %}
144+
{% if form.web_logo.help_text %}
145+
<br/>
146+
<span class="helptext">{{ form.web_logo.help_text }}</span>
147+
{% endif %}
148+
</p>
160149

161-
<div>
162-
<p class="form_field">
163-
<label>{{ form.print_logo.label }} <span class="error-message">{% if form.print_logo.errors %}{{ form.print_logo.errors.as_text }}</span>{% endif %}</label>
164-
{% render_field form.print_logo %}
165-
{% if sponsor.print_logo %}
166-
<p>Currently: <a href="{{ sponsor.print_logo.url }}">{{ sponsor.print_logo.name }}</a></p>
167-
{% endif %}
168-
{% if form.print_logo.help_text %}
169-
<br/>
170-
<span class="helptext">{{ form.print_logo.help_text }}</span>
171-
{% endif %}
172-
</p>
173-
</div>
174-
</div>
150+
<p class="form_field">
151+
<label>{{ form.white_logo.label }} <span class="error-message">{% if form.white_logo.errors %}{{ form.white_logo.errors.as_text }}</span>{% endif %}</label>
152+
{% render_field form.white_logo %}
153+
{% if sponsor.white_logo %}
154+
<br/>Currently: <a href="{{ sponsor.white_logo.url }}">{{ sponsor.white_logo.name }}</a>
155+
{% endif %}
156+
{% if form.white_logo.help_text %}
157+
<br/>
158+
<span class="helptext">{{ form.white_logo.help_text }}</span>
159+
{% endif %}
160+
</p>
161+
162+
<p class="form_field">
163+
<label>{{ form.print_logo.label }} <span class="error-message">{% if form.print_logo.errors %}{{ form.print_logo.errors.as_text }}</span>{% endif %}</label>
164+
{% render_field form.print_logo %}
165+
{% if sponsor.print_logo %}
166+
<br/>Currently: <a href="{{ sponsor.print_logo.url }}">{{ sponsor.print_logo.name }}</a>
167+
{% endif %}
168+
{% if form.print_logo.help_text %}
169+
<br/>
170+
<span class="helptext">{{ form.print_logo.help_text }}</span>
171+
{% endif %}
172+
</p>
175173

176174
<hr>
177175

apps/sponsors/tests/test_api.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,30 @@ def tearDown(self):
4242
for sponsor in Sponsor.objects.all():
4343
if sponsor.web_logo:
4444
sponsor.web_logo.delete()
45+
if sponsor.white_logo:
46+
sponsor.white_logo.delete()
4547
if sponsor.print_logo:
4648
sponsor.print_logo.delete()
4749

50+
def test_white_logo_null_when_not_set(self):
51+
response = self.client.get(self.url, headers={"authorization": self.authorization})
52+
data = response.json()
53+
self.assertEqual(200, response.status_code)
54+
for placement in data:
55+
self.assertIn("white_logo", placement)
56+
self.assertIsNone(placement["white_logo"])
57+
58+
def test_white_logo_url_when_set(self):
59+
sponsor = self.sponsors[0]
60+
sponsor.white_logo = SimpleUploadedFile(name="white.png", content=b"img", content_type="image/png")
61+
sponsor.save()
62+
response = self.client.get(self.url, headers={"authorization": self.authorization})
63+
data = response.json()
64+
sponsor_placements = [p for p in data if p["sponsor"] == sponsor.name]
65+
for placement in sponsor_placements:
66+
self.assertIsNotNone(placement["white_logo"])
67+
self.assertIn("white.png", placement["white_logo"])
68+
4869
def test_list_logo_placement_as_expected(self):
4970
response = self.client.get(self.url, headers={"authorization": self.authorization})
5071
data = response.json()

0 commit comments

Comments
 (0)