Skip to content

Commit b020170

Browse files
authored
Merge branch 'main' into sponsor-mgmt-ui
2 parents b942a20 + c84ea20 commit b020170

File tree

9 files changed

+82
-32
lines changed

9 files changed

+82
-32
lines changed

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ jobs:
2121
with:
2222
python-version: "3.x"
2323

24-
- uses: j178/prek-action@v1
24+
- uses: j178/prek-action@v2

apps/sponsors/admin.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -736,24 +736,35 @@ def get_sponsor_landing_page_url(self, obj):
736736
@admin.display(description="Web Logo")
737737
def get_sponsor_web_logo(self, obj):
738738
"""Render and return the sponsor's web logo as a thumbnail image."""
739-
html = "{% load thumbnail %}{% thumbnail sponsor.web_logo '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}'/>{% endthumbnail %}"
739+
img = obj.sponsor.web_logo
740+
if not img:
741+
return "---"
742+
if img.name and img.name.lower().endswith(".svg"):
743+
return format_html(
744+
'<img src="{}" style="max-width:150px;max-height:150px"/>',
745+
img.url,
746+
)
747+
html = "{% load thumbnail %}{% thumbnail img '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}'/>{% endthumbnail %}"
740748
template = Template(html)
741-
context = Context({"sponsor": obj.sponsor})
742-
html = template.render(context)
743-
return mark_safe(html) # noqa: S308
749+
context = Context({"img": img})
750+
return mark_safe(template.render(context)) # noqa: S308
744751

745752
@admin.display(description="Print Logo")
746753
def get_sponsor_print_logo(self, obj):
747754
"""Render and return the sponsor's print logo as a thumbnail image."""
748755
img = obj.sponsor.print_logo
749-
html = "---"
750-
if img:
751-
template = Template(
752-
"{% load thumbnail %}{% thumbnail img '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}'/>{% endthumbnail %}"
756+
if not img:
757+
return "---"
758+
if img.name and img.name.lower().endswith(".svg"):
759+
return format_html(
760+
'<img src="{}" style="max-width:150px;max-height:150px"/>',
761+
img.url,
753762
)
754-
context = Context({"img": img})
755-
html = mark_safe(template.render(context)) # noqa: S308
756-
return html
763+
template = Template(
764+
"{% load thumbnail %}{% thumbnail img '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}'/>{% endthumbnail %}"
765+
)
766+
context = Context({"img": img})
767+
return mark_safe(template.render(context)) # noqa: S308
757768

758769
@admin.display(description="Primary Phone")
759770
def get_sponsor_primary_phone(self, obj):
@@ -807,7 +818,7 @@ def get_custom_benefits_added_by_user(self, obj):
807818
if not benefits:
808819
return "---"
809820

810-
return format_html_join("", "<p>{}</p>", benefits)
821+
return format_html_join("", "<p>{}</p>", ((b,) for b in benefits))
811822

812823
@admin.display(description="Removed by User")
813824
def get_custom_benefits_removed_by_user(self, obj):
@@ -816,7 +827,7 @@ def get_custom_benefits_removed_by_user(self, obj):
816827
if not benefits:
817828
return "---"
818829

819-
return format_html_join("", "<p>{}</p>", benefits)
830+
return format_html_join("", "<p>{}</p>", ((b,) for b in benefits))
820831

821832
def rollback_to_editing_view(self, request, pk):
822833
"""Delegate to the rollback_to_editing admin view."""
@@ -1277,7 +1288,7 @@ def get_value(self, obj):
12771288
"""Return the asset value, linking to the file URL if applicable."""
12781289
html = obj.value
12791290
if obj.value and getattr(obj.value, "url", None):
1280-
html = format_html("<a href='{}' target='_blank'>{}</a>", (obj.value.url, obj.value))
1291+
html = format_html("<a href='{}' target='_blank'>{}</a>", obj.value.url, obj.value)
12811292
return html
12821293

12831294
@admin.display(description="Associated with")
@@ -1289,9 +1300,9 @@ def get_related_object(self, obj):
12891300
"""
12901301
content_object = None
12911302
if obj.from_sponsorship:
1292-
content_object = self.all_sponsorships[obj.object_id]
1303+
content_object = self.all_sponsorships.get(obj.object_id)
12931304
elif obj.from_sponsor:
1294-
content_object = self.all_sponsors[obj.object_id]
1305+
content_object = self.all_sponsors.get(obj.object_id)
12951306

12961307
if not content_object: # safety belt
12971308
return obj.content_object

apps/sponsors/models/benefits.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Benefit feature and configuration models for the sponsors app."""
22

33
from django import forms
4-
from django.db import models
4+
from django.db import IntegrityError, models, transaction
55
from django.db.models import UniqueConstraint
66
from django.urls import reverse
77
from polymorphic.models import PolymorphicModel
@@ -155,11 +155,16 @@ def create_benefit_feature(self, sponsor_benefit, **kwargs):
155155

156156
asset_qs = content_object.assets.filter(internal_name=self.internal_name)
157157
if not asset_qs.exists():
158-
asset = self.ASSET_CLASS(
159-
content_object=content_object,
160-
internal_name=self.internal_name,
161-
)
162-
asset.save()
158+
try:
159+
with transaction.atomic():
160+
asset = self.ASSET_CLASS(
161+
content_object=content_object,
162+
internal_name=self.internal_name,
163+
)
164+
asset.save()
165+
except IntegrityError:
166+
if not content_object.assets.filter(internal_name=self.internal_name).exists():
167+
raise
163168

164169
return benefit_feature
165170

apps/sponsors/templates/sponsors/partials/sponsors-list.html

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@ <h2 class="widget-title" style="text-align: center;">Sponsors</h2>
1010
<p style="text-align: center;">Visionary sponsors help to host Python downloads.</p>
1111
<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;">
1212
{% for sponsorship in sponsorships %}
13-
{% thumbnail sponsorship.sponsor.web_logo "x150" format="PNG" quality=100 as im %}
1413
<div style="text-align: center;">
1514
<a href="{{ sponsorship.sponsor.landing_page_url }}" rel="sponsored noopener" target="_blank" style="border-bottom: 0;">
15+
{% if sponsorship.sponsor.web_logo.name|lower|slice:"-4:" == ".svg" %}
16+
<img src="{{ sponsorship.sponsor.web_logo.url }}" alt="{{ sponsorship.sponsor.name }} logo" style="max-height:150px;max-width:150px;height:auto;width:auto;">
17+
{% else %}
18+
{% thumbnail sponsorship.sponsor.web_logo "x150" format="PNG" quality=100 as im %}
1619
<img src="{{ im.url }}" alt="{{ sponsorship.sponsor.name }} logo" style="max-height:150px;max-width:150px;height:auto;width:auto;">
20+
{% endthumbnail %}
21+
{% endif %}
1722
</a>
1823
<p style="margin-top: 0.5em; margin-bottom: 0; font-size: 0.875em; color: #4d4d4d;">{{ sponsorship.sponsor.name }}</p>
1924
</div>
20-
{% endthumbnail %}
2125
{% endfor %}
2226
</div>
2327
{% endcache CACHED_DOWNLOAD_SPONSORS_LIST %}
@@ -28,11 +32,15 @@ <h2 class="widget-title" style="text-align: center;">Sponsors</h2>
2832
<h3 class="widget-title">Job Board Sponsors</h3>
2933
<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;">
3034
{% for sponsorship in sponsorships %}
31-
{% thumbnail sponsorship.sponsor.web_logo "x100" format="PNG" quality=100 as im %}
3235
<div>
36+
{% if sponsorship.sponsor.web_logo.name|lower|slice:"-4:" == ".svg" %}
37+
<img src="{{ sponsorship.sponsor.web_logo.url }}" alt="{{ sponsorship.sponsor.name }} logo" style="max-height:100px;max-width:100px;height:auto;width:auto;">
38+
{% else %}
39+
{% thumbnail sponsorship.sponsor.web_logo "x100" format="PNG" quality=100 as im %}
3340
<img src="{{ im.url }}" alt="{{ sponsorship.sponsor.name }} logo" style="max-height:100px;max-width:100px;height:auto;width:auto;">
41+
{% endthumbnail %}
42+
{% endif %}
3443
</div>
35-
{% endthumbnail %}
3644
{% endfor %}
3745
</div>
3846
{% endcache CACHED_JOBS_SPONSORS_LIST %}

apps/sponsors/tests/test_models.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,30 @@ def test_cant_create_same_asset_twice(self):
11271127
self.config.create_benefit_feature(self.sponsor_benefit)
11281128
self.assertEqual(1, TextAsset.objects.count())
11291129

1130+
def test_create_benefit_feature_with_preexisting_asset_no_crash(self):
1131+
"""Regression: submitting a new sponsorship when the sponsor already
1132+
has an asset with the same internal_name (from a prior application)
1133+
should not raise an IntegrityError."""
1134+
sponsor = self.sponsor_benefit.sponsorship.sponsor
1135+
TextAsset.objects.create(
1136+
content_object=sponsor,
1137+
internal_name=self.config.internal_name,
1138+
)
1139+
# should not raise IntegrityError
1140+
self.config.create_benefit_feature(self.sponsor_benefit)
1141+
self.assertEqual(1, TextAsset.objects.count())
1142+
1143+
def test_integrity_error_reraised_when_asset_missing(self):
1144+
"""An IntegrityError that is NOT a duplicate-asset collision must
1145+
propagate so it doesn't get silently swallowed."""
1146+
from unittest.mock import patch
1147+
1148+
with (
1149+
patch.object(TextAsset, "save", side_effect=IntegrityError("unrelated")),
1150+
self.assertRaises(IntegrityError),
1151+
):
1152+
self.config.create_benefit_feature(self.sponsor_benefit)
1153+
11301154
def test_clone_configuration_for_new_sponsorship_benefit_with_new_due_date(self):
11311155
sp_benefit = baker.make(SponsorshipBenefit, year=2023)
11321156

bin/start-nginx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ fi
5454
# We expect nginx to run in foreground.
5555
# We also expect a socket to be at /tmp/nginx.socket.
5656
echo 'buildpack=nginx at=nginx-start'
57+
rm -f /var/run/cabotage/cabotage.sock
5758
cd /tmp
5859
/usr/bin/nginx -p . -c /code/config/nginx.conf
5960
echo 'nginx' >$psmgr

gunicorn.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
bind = 'unix:/var/run/cabotage/nginx.sock'
22
backlog = 1024
3+
workers = 2
34
preload_app = True
45
max_requests = 2048
56
max_requests_jitter = 128

infra/main.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module "fastly_production" {
55
domain = "python.org"
66
subdomain = "www.python.org"
77
extra_domains = ["www.python.org"]
8-
backend_address = "pythondotorg.ingress.us-east-2.psfhosted.computer"
8+
backend_address = "psf-pythondotorg-pythondotorg-b0dbd19e-web.psfhosted.net"
99
default_ttl = 3600
1010

1111
datadog_key = var.DATADOG_API_KEY
@@ -27,7 +27,7 @@ module "fastly_staging" {
2727
subdomain = "www.test.python.org"
2828
extra_domains = ["www.test.python.org"]
2929
# TODO: adjust to test-pythondotorg when done testing NGWAF
30-
backend_address = "pythondotorg.ingress.us-east-2.psfhosted.computer"
30+
backend_address = "psf-pythondotorg-pythondotorg-b0dbd19e-web.psfhosted.net"
3131
default_ttl = 3600
3232

3333
datadog_key = var.DATADOG_API_KEY

uv.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)