Skip to content

Commit 4f5283c

Browse files
authored
Merge branch 'main' into feature/issue-2611-deprecate-provided-assets-view
2 parents 4149c29 + 500173e commit 4f5283c

File tree

4 files changed

+117
-12
lines changed

4 files changed

+117
-12
lines changed

.github/workflows/purge-cache.yml

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,27 @@ permissions: {}
1818
jobs:
1919
purge:
2020
runs-on: ubuntu-latest
21-
env:
22-
KEY: ${{ inputs.target || 'pydotorg-app' }}
2321
steps:
24-
- name: Purge ${{ env.KEY }}
22+
- uses: actions/checkout@v6
23+
if: github.event_name == 'push'
24+
with:
25+
fetch-depth: 0
26+
27+
- name: Resolve keys
28+
id: keys
29+
run: |
30+
if [ -n "${{ inputs.target }}" ]; then
31+
echo "keys=${{ inputs.target }}" >> "$GITHUB_OUTPUT"
32+
elif git diff --name-only ${{ github.event.before }}..${{ github.sha }} | grep -qE '^(static/|templates/)'; then
33+
echo "keys=pydotorg-app" >> "$GITHUB_OUTPUT"
34+
else
35+
echo "keys=$(git diff --name-only ${{ github.event.before }}..${{ github.sha }} | grep -oP '^apps/\K[^/]+(?=/(templates|static)/)' | sort -u | tr '\n' ' ')" >> "$GITHUB_OUTPUT"
36+
fi
37+
38+
- name: Purge ${{ steps.keys.outputs.keys || 'pydotorg-app' }}
2539
run: |
26-
curl -fsS -X POST \
27-
"https://api.fastly.com/service/${{ secrets.FASTLY_SERVICE_ID }}/purge/${{ env.KEY }}" \
28-
-H "Fastly-Key: ${{ secrets.FASTLY_API_KEY }}"
40+
for KEY in ${{ steps.keys.outputs.keys || 'pydotorg-app' }}; do
41+
curl -fsS -X POST \
42+
"https://api.fastly.com/service/${{ secrets.FASTLY_SERVICE_ID }}/purge/$KEY" \
43+
-H "Fastly-Key: ${{ secrets.FASTLY_API_KEY }}"
44+
done

apps/downloads/models.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.conf import settings
66
from django.core.exceptions import ValidationError
77
from django.db import models
8-
from django.db.models.signals import post_save
8+
from django.db.models.signals import post_delete, post_save
99
from django.dispatch import receiver
1010
from django.template.loader import render_to_string
1111
from django.urls import reverse
@@ -332,6 +332,31 @@ def update_download_supernav_and_boxes(sender, instance, **kwargs):
332332
update_homepage_download_box()
333333

334334

335+
def _update_boxes_for_release_file(instance):
336+
"""Update supernav and download boxes if the file's release is published."""
337+
if instance.release_id and instance.release.is_published:
338+
update_supernav()
339+
update_download_landing_sources_box()
340+
update_homepage_download_box()
341+
purge_url("/box/supernav-python-downloads/")
342+
purge_url("/box/homepage-downloads/")
343+
purge_url("/box/download-sources/")
344+
345+
346+
@receiver(post_save, sender="downloads.ReleaseFile")
347+
def update_boxes_on_release_file_save(sender, instance, **kwargs):
348+
"""Refresh supernav when a release file is added or changed."""
349+
if kwargs.get("raw", False):
350+
return
351+
_update_boxes_for_release_file(instance)
352+
353+
354+
@receiver(post_delete, sender="downloads.ReleaseFile")
355+
def update_boxes_on_release_file_delete(sender, instance, **kwargs):
356+
"""Refresh supernav when a release file is deleted."""
357+
_update_boxes_for_release_file(instance)
358+
359+
335360
class ReleaseFile(ContentManageable, NameSlugModel):
336361
"""Individual files in a release.
337362
@@ -361,7 +386,7 @@ class ReleaseFile(ContentManageable, NameSlugModel):
361386

362387
def validate_unique(self, exclude=None):
363388
"""Ensure only one release file per OS has the download button enabled."""
364-
if self.download_button:
389+
if self.download_button and self.release_id:
365390
qs = ReleaseFile.objects.filter(release=self.release, os=self.os, download_button=True).exclude(pk=self.id)
366391
if qs.count() > 0:
367392
msg = 'Only one Release File per OS can have "Download button" enabled'

apps/downloads/tests/test_models.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime as dt
2+
from unittest.mock import patch
23

34
from apps.downloads.models import Release, ReleaseFile
45
from apps.downloads.tests.base import BaseDownloadTests
@@ -232,3 +233,59 @@ def test_update_supernav_skips_os_without_files(self):
232233

233234
# Android (no files) should not be present
234235
self.assertNotIn("android", content.lower())
236+
237+
@patch("apps.downloads.models.update_supernav")
238+
@patch("apps.downloads.models.update_download_landing_sources_box")
239+
@patch("apps.downloads.models.update_homepage_download_box")
240+
def test_release_file_save_triggers_box_updates(self, mock_home, mock_sources, mock_supernav):
241+
"""Saving a ReleaseFile on a published release should update boxes."""
242+
mock_supernav.reset_mock()
243+
mock_sources.reset_mock()
244+
mock_home.reset_mock()
245+
246+
ReleaseFile.objects.create(
247+
os=self.windows,
248+
release=self.python_3,
249+
name="Windows installer",
250+
url="/ftp/python/3.10.19/python-3.10.19.exe",
251+
download_button=True,
252+
)
253+
254+
mock_supernav.assert_called()
255+
mock_sources.assert_called()
256+
mock_home.assert_called()
257+
258+
@patch("apps.downloads.models.update_supernav")
259+
@patch("apps.downloads.models.update_download_landing_sources_box")
260+
@patch("apps.downloads.models.update_homepage_download_box")
261+
def test_release_file_save_skips_unpublished_release(self, mock_home, mock_sources, mock_supernav):
262+
"""Saving a ReleaseFile on a draft release should not update boxes."""
263+
mock_supernav.reset_mock()
264+
mock_sources.reset_mock()
265+
mock_home.reset_mock()
266+
267+
ReleaseFile.objects.create(
268+
os=self.windows,
269+
release=self.draft_release,
270+
name="Windows installer draft",
271+
url="/ftp/python/9.7.2/python-9.7.2.exe",
272+
)
273+
274+
mock_supernav.assert_not_called()
275+
mock_sources.assert_not_called()
276+
mock_home.assert_not_called()
277+
278+
@patch("apps.downloads.models.update_supernav")
279+
@patch("apps.downloads.models.update_download_landing_sources_box")
280+
@patch("apps.downloads.models.update_homepage_download_box")
281+
def test_release_file_delete_triggers_box_updates(self, mock_home, mock_sources, mock_supernav):
282+
"""Deleting a ReleaseFile on a published release should update boxes."""
283+
mock_supernav.reset_mock()
284+
mock_sources.reset_mock()
285+
mock_home.reset_mock()
286+
287+
self.release_275_windows_32bit.delete()
288+
289+
mock_supernav.assert_called()
290+
mock_sources.assert_called()
291+
mock_home.assert_called()

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@
66
{% if logo_place == "download" %}
77
{% comment %}cache for 1 day{% endcomment %}
88
{% cache 86400 CACHED_DOWNLOAD_SPONSORS_LIST %}
9-
<h2 class="widget-title">Sponsors</h2>
10-
<p>Visionary sponsors help to host Python downloads.</p>
9+
<h2 class="widget-title" style="text-align: center;">Sponsors</h2>
10+
<p style="text-align: center;">Visionary sponsors help to host Python downloads.</p>
11+
<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;">
1112
{% for sponsorship in sponsorships %}
12-
{% thumbnail sponsorship.sponsor.web_logo "x200" format="PNG" quality=100 as im %}
13-
<img src="{{ im.url }}" alt="{{ sponsorship.sponsor.name }} logo" style="max-height:200px;max-width:200px;height:auto;width:auto;">
13+
{% thumbnail sponsorship.sponsor.web_logo "x150" format="PNG" quality=100 as im %}
14+
<div style="text-align: center;">
15+
<a href="{{ sponsorship.sponsor.landing_page_url }}" rel="sponsored noopener" target="_blank" style="border-bottom: 0;">
16+
<img src="{{ im.url }}" alt="{{ sponsorship.sponsor.name }} logo" style="max-height:150px;max-width:150px;height:auto;width:auto;">
17+
</a>
18+
<p style="margin-top: 0.5em; margin-bottom: 0; font-size: 0.875em; color: #4d4d4d;">{{ sponsorship.sponsor.name }}</p>
19+
</div>
1420
{% endthumbnail %}
1521
{% endfor %}
22+
</div>
1623
{% endcache CACHED_DOWNLOAD_SPONSORS_LIST %}
1724

1825
{% elif logo_place == "jobs" %}

0 commit comments

Comments
 (0)