diff --git a/tests/unit/sponsors/test_init.py b/tests/unit/sponsors/test_init.py
index 582a25ea890c..fa2fd29da488 100644
--- a/tests/unit/sponsors/test_init.py
+++ b/tests/unit/sponsors/test_init.py
@@ -3,10 +3,8 @@
import pretend
from celery.schedules import crontab
-from sqlalchemy import true
from warehouse import sponsors
-from warehouse.sponsors.models import Sponsor
from warehouse.sponsors.tasks import update_pypi_sponsors
from ...common.db.sponsors import SponsorFactory
@@ -24,6 +22,7 @@ def test_includeme():
assert config.add_request_method.calls == [
pretend.call(sponsors._sponsors, name="sponsors", reify=True),
+ pretend.call(sponsors._footer_sponsors, name="footer_sponsors", reify=True),
]
assert config.add_periodic_task.calls == [
pretend.call(crontab(minute=10), update_pypi_sponsors),
@@ -42,16 +41,40 @@ def test_do_not_schedule_sponsor_api_integration_if_no_token():
assert config.add_request_method.calls == [
pretend.call(sponsors._sponsors, name="sponsors", reify=True),
+ pretend.call(sponsors._footer_sponsors, name="footer_sponsors", reify=True),
]
assert not config.add_periodic_task.calls
def test_list_sponsors(db_request):
- SponsorFactory.create_batch(5)
+ expected = SponsorFactory.create_batch(5)
SponsorFactory.create_batch(3, is_active=False)
result = sponsors._sponsors(db_request)
- expected = db_request.db.query(Sponsor).filter(Sponsor.is_active == true()).all()
- assert result == expected
assert len(result) == 5
+ assert set(result) == set(expected)
+
+
+def test_sponsors_ordered_by_level_then_name_then_infra(db_request):
+ c = SponsorFactory.create
+ infra = c(name="AWS", infra_sponsor=True, level_order=0)
+ vis_b = c(name="Bravo", infra_sponsor=False, level_order=1)
+ vis_a = c(name="Alpha", infra_sponsor=False, level_order=1)
+ sus = c(name="Charlie", infra_sponsor=False, level_order=2)
+
+ result = sponsors._sponsors(db_request)
+
+ assert result == [vis_a, vis_b, sus, infra]
+
+
+def test_footer_sponsors_ordering(db_request):
+ c = SponsorFactory.create
+ infra = c(name="AWS", infra_sponsor=True, footer=False, level_order=0)
+ vis_b = c(name="Bravo", footer=True, infra_sponsor=False, level_order=1)
+ vis_a = c(name="Alpha", footer=True, infra_sponsor=False, level_order=1)
+ sus = c(name="Charlie", footer=True, infra_sponsor=False, level_order=2)
+ c(name="Nobody", footer=False, infra_sponsor=False, level_order=5)
+
+ db_request.sponsors = sponsors._sponsors(db_request)
+ assert sponsors._footer_sponsors(db_request) == [vis_a, vis_b, sus, infra]
diff --git a/tests/unit/sponsors/test_models.py b/tests/unit/sponsors/test_models.py
index 919d261cc244..263c327d2d8f 100644
--- a/tests/unit/sponsors/test_models.py
+++ b/tests/unit/sponsors/test_models.py
@@ -5,7 +5,10 @@
def test_sponsor_color_logo_img_tag(db_request):
sponsor = SponsorFactory.create()
- expected = f''
+ expected = (
+ '
'
+ )
assert sponsor.color_logo_img == expected
diff --git a/tests/unit/sponsors/test_tasks.py b/tests/unit/sponsors/test_tasks.py
index 698f75d58c71..4e376af02d12 100644
--- a/tests/unit/sponsors/test_tasks.py
+++ b/tests/unit/sponsors/test_tasks.py
@@ -196,6 +196,58 @@ def test_update_remote_sponsor_with_same_slug_with_new_logo(
assert db_sponsor.service == "Sponsor description"
+@pytest.mark.parametrize(
+ ("level_name", "expected_footer"),
+ [
+ ("Visionary", True),
+ ("Sustainability", True),
+ ("Partner", False),
+ ],
+)
+def test_footer_set_based_on_level(
+ monkeypatch,
+ db_request,
+ fake_task_request,
+ sponsor_api_data,
+ level_name,
+ expected_footer,
+):
+ sponsor_api_data[0]["level_name"] = level_name
+ response = pretend.stub(
+ raise_for_status=lambda: None, json=lambda: sponsor_api_data
+ )
+ requests = pretend.stub(
+ get=pretend.call_recorder(lambda url, headers, timeout: response)
+ )
+ monkeypatch.setattr(tasks, "requests", requests)
+
+ fake_task_request.db = db_request.db
+ tasks.update_pypi_sponsors(fake_task_request)
+
+ db_sponsor = db_request.db.query(Sponsor).one()
+ assert db_sponsor.footer is expected_footer
+
+
+def test_white_logo_synced_when_provided(
+ monkeypatch, db_request, fake_task_request, sponsor_api_data
+):
+ sponsor_api_data[0]["white_logo"] = "https://logourl.com/white.png"
+ response = pretend.stub(
+ raise_for_status=lambda: None, json=lambda: sponsor_api_data
+ )
+ monkeypatch.setattr(
+ tasks,
+ "requests",
+ pretend.stub(get=pretend.call_recorder(lambda *a, **kw: response)),
+ )
+ fake_task_request.db = db_request.db
+ tasks.update_pypi_sponsors(fake_task_request)
+ assert (
+ db_request.db.query(Sponsor).one().white_logo_url
+ == "https://logourl.com/white.png"
+ )
+
+
def test_flag_existing_psf_sponsor_to_false_if_not_present_in_api_response(
monkeypatch, db_request, fake_task_request, sponsor_api_data
):
diff --git a/warehouse/sponsors/__init__.py b/warehouse/sponsors/__init__.py
index 1fe5f8e62d33..b888de75bb52 100644
--- a/warehouse/sponsors/__init__.py
+++ b/warehouse/sponsors/__init__.py
@@ -8,12 +8,32 @@
def _sponsors(request):
- return request.db.query(Sponsor).filter(Sponsor.is_active == true()).all()
+ return (
+ request.db.query(Sponsor)
+ .filter(Sponsor.is_active == true())
+ .order_by(Sponsor.infra_sponsor, Sponsor.level_order, Sponsor.name)
+ .all()
+ )
+
+
+def _footer_sponsors(request):
+ """Return footer sponsors: PSF by level then name, infra by name."""
+ all_sponsors = request.sponsors
+ psf = sorted(
+ (s for s in all_sponsors if s.footer and not s.infra_sponsor),
+ key=lambda s: (s.level_order or 0, s.name),
+ )
+ infra = sorted(
+ (s for s in all_sponsors if s.infra_sponsor),
+ key=lambda s: s.name,
+ )
+ return psf + infra
def includeme(config):
# Add a request method which will allow to list sponsors
config.add_request_method(_sponsors, name="sponsors", reify=True)
+ config.add_request_method(_footer_sponsors, name="footer_sponsors", reify=True)
# Add a periodic task to update sponsors table
if config.registry.settings.get("pythondotorg.api_token"):
diff --git a/warehouse/sponsors/models.py b/warehouse/sponsors/models.py
index 34940013aaac..46f0325b47a2 100644
--- a/warehouse/sponsors/models.py
+++ b/warehouse/sponsors/models.py
@@ -38,7 +38,10 @@ class Sponsor(db.Model):
@property
def color_logo_img(self):
- return f'
'
+ return (
+ '
'
+ )
@property
def white_logo_img(self):
diff --git a/warehouse/sponsors/tasks.py b/warehouse/sponsors/tasks.py
index 60bdbb142fe2..87aa4927ce3e 100644
--- a/warehouse/sponsors/tasks.py
+++ b/warehouse/sponsors/tasks.py
@@ -50,8 +50,10 @@ def update_pypi_sponsors(request):
sponsor.service = sponsor_info["description"]
sponsor.link_url = sponsor_info["sponsor_url"]
sponsor.color_logo_url = sponsor_info["logo"]
+ sponsor.white_logo_url = sponsor_info.get("white_logo")
sponsor.level_name = sponsor_info["level_name"]
sponsor.level_order = sponsor_info["level_order"]
sponsor.is_active = True
sponsor.psf_sponsor = True
+ sponsor.footer = sponsor_info["level_name"] in {"Visionary", "Sustainability"}
sponsor.origin = "remote"
diff --git a/warehouse/static/sass/blocks/_sponsors.scss b/warehouse/static/sass/blocks/_sponsors.scss
index ff061acd3603..ec76917d0dec 100644
--- a/warehouse/static/sass/blocks/_sponsors.scss
+++ b/warehouse/static/sass/blocks/_sponsors.scss
@@ -50,6 +50,16 @@
height: 0;
}
+ &__group-label {
+ flex-basis: 100%;
+ color: color.adjust(colours.$white, $alpha: -0.4);
+ font-size: 0.7rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin: 4px 0 0;
+ padding: 0;
+ }
+
&__sponsor {
display: inline-grid;
grid-template-rows: fit-content(40%);
@@ -109,4 +119,27 @@
display: none;
}
}
+
+ &__sponsor--infra {
+ opacity: 0.75;
+
+ .sponsors__image {
+ max-width: 80px;
+ opacity: 0.7;
+ }
+
+ .sponsors__name,
+ .sponsors__service {
+ max-width: 80px;
+ font-size: 0.68rem;
+ }
+
+ &:hover {
+ opacity: 0.95;
+
+ .sponsors__image {
+ opacity: 0.9;
+ }
+ }
+ }
}
diff --git a/warehouse/templates/includes/sponsors-footer.html b/warehouse/templates/includes/sponsors-footer.html
index 9c033c991fda..c23534cf97d5 100644
--- a/warehouse/templates/includes/sponsors-footer.html
+++ b/warehouse/templates/includes/sponsors-footer.html
@@ -14,28 +14,25 @@
Supported by
- {% for sponsor in request.sponsors | sort(attribute='name') %} - {# Short-circuit if we don't have an image for them #} - {% if sponsor.white_logo_url %} - {# Check if they belong in the footer #} - {% if sponsor.infra_sponsor or sponsor.footer %} - - {{ sponsor.white_logo_img|camoify|safe }} - {{ sponsor.name }} - - {# If they're an infra sponsor, we should have a service for them, - otherwise they're a PSF sponsor #} - {% if sponsor.infra_sponsor %} - {{ sponsor.service }} - {% elif sponsor.footer %} - PSF Sponsor - {% endif %} - - - {% endif %} + {% for sponsor in request.footer_sponsors %} + {% if sponsor.infra_sponsor and loop.previtem is defined and not loop.previtem.infra_sponsor %} + +Infrastructure partners
+ {% endif %} + + {% if sponsor.white_logo_url %}{{ sponsor.white_logo_img|camoify|safe }}{% endif %} + {{ sponsor.name }} + + {% if sponsor.infra_sponsor %} + {{ sponsor.service }} + {% else %} + PSF Sponsor + {% endif %} + + {% endfor %}