8000 give admin user a preview of sponsorship package revenue splits (#2529) · python/pythondotorg@ce1c391 · GitHub
[go: up one dir, main page]

Skip to content

Commit ce1c391

Browse files
authored
give admin user a preview of sponsorship package revenue splits (#2529)
* Give the admin an indication of how revenue for sponsorships in this package will be divvied up * implement Ee's feedback
1 parent f72533b commit ce1c391

File tree

3 files changed

+56
-2
lines changed

3 files changed

+56
-2
lines changed

sponsors/admin.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class ProvidedFileAssetConfigurationInline(StackedPolymorphicInline.Child):
110110
ProvidedFileAssetConfigurationInline,
111111
]
112112

113+
113114
@admin.register(SponsorshipBenefit)
114115
class SponsorshipBenefitAdmin(PolymorphicInlineSupportMixin, OrderedModelAdmin):
115116
change_form_template = "sponsors/admin/sponsorshipbenefit_change_form.html"
@@ -179,12 +180,12 @@ def update_related_sponsorships(self, *args, **kwargs):
179180
@admin.register(SponsorshipPackage)
180181
class SponsorshipPackageAdmin(OrderedModelAdmin):
181182
ordering = ("-year", "order",)
182-
list_display = ["name", "year", "advertise", "allow_a_la_carte", "move_up_down_links"]
183+
list_display = ["name", "year", "advertise", "allow_a_la_carte", "get_benefit_split", "move_up_down_links"]
183184
list_filter = ["advertise", "year", "allow_a_la_carte"]
184185
search_fields = ["name"]
185186

186187
def get_readonly_fields(self, request, obj=None):
187-
readonly = []
188+
readonly = ["get_benefit_split"]
188189
if obj:
189190
readonly.append("slug")
190191
if not request.user.is_superuser:
@@ -196,6 +197,30 @@ def get_prepopulated_fields(self, request, obj=None):
196197
return {'slug': ['name']}
197198
return {}
198199

200+
def get_benefit_split(self, obj: SponsorshipPackage) -> str:
201+
colors = [
202+
"#ffde57", # Python Gold
203+
"#4584b6", # Python Blue
204+
"#646464", # Python Grey
205+
]
206+
split = obj.get_default_revenue_split()
207+
# rotate colors through our available palette
208+
if len(split) > len(colors):
209+
colors = colors * (1 + (len(split) // len(colors)))
210+
# build some span elements to show the percentages and have the program name in the title (to show on hover)
211+
widths, spans = [], []
212+
for i, (name, pct) in enumerate(split):
213+
pct_str = f"{pct:.0f}%"
214+
widths.append(pct_str)
215+
spans.append(f"<span title='{name}' style='background-color:var(--{colors[i]})'>{pct_str}</span>")
216+
# define a style that will show our span elements like a single horizontal stacked bar chart
217+
style = f'color:#fff;text-align:center;cursor:pointer;display:grid;grid-template-columns:{" ".join(widths)}'
218+
# wrap it all up and put a bow on it
219+
html = f"<div style='{style}'>{''.join(spans)}</div>"
220+
return mark_safe(html)
221+
222+
get_benefit_split.short_description = "Revenue split"
223+
199224

200225
class SponsorContactInline(admin.TabularInline):
201226
model = SponsorContact

sponsors/models/sponsorship.py

Lines changed: 12 additions & 0 deletions
Original 8000 file line numberDiff line numberDiff line change
@@ -117,6 +117,18 @@ def clone(self, year: int):
117117
slug=self.slug, year=year, defaults=defaults
118118
)
119119

120+
def get_default_revenue_split(self) -> list[tuple[str, float]]:
121+
"""
122+
Give the admin an indication of how revenue for sponsorships in this package will be divvied up
123+
"""
124+
values, key = {}, "program__name"
125+
for benefit in self.benefits.values(key).annotate(amount=Sum("internal_value", default=0)).order_by("-amount"):
126+
values[benefit[key]] = values.get(benefit[key], 0) + (benefit["amount"] or 0)
127+
total = sum(values.values())
128+
if not total:
129+
return [] # nothing to split!
130+
return [(k, round(v / total * 100, 3)) for k, v in values.items()]
131+
120132

121133
class SponsorshipProgram(OrderedModel):
122134
"""

sponsors/tests/test_models.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from datetime import date, timedelta
2+
import random
23

34
from django.core.cache import cache
45
from django.db import IntegrityError
@@ -433,6 +434,22 @@ def test_clone_does_not_repeate_already_cloned_package(self):
433434
self.assertFalse(created)
434435
self.assertEqual(pkg_2023.pk, repeated_pkg_2023.pk)
435436

437+
def test_get_default_revenue_split(self):
438+
benefits = baker.make(SponsorshipBenefit, internal_value=int(random.random() * 1000), _quantity=12)
439+
program_names = set((b.program.name for b in benefits))
440+
pkg1 = baker.make(SponsorshipPackage, year=2024, advertise=True, logo_dimension=300, benefits=benefits[:3])
441+
pkg2 = baker.make(SponsorshipPackage, year=2024, advertise=True, logo_dimension=300, benefits=benefits[3:7])
442+
pkg3 = baker.make(SponsorshipPackage, year=2024, advertise=True, logo_dimension=300, benefits=benefits[7:])
443+
splits = [pkg.get_default_revenue_split() for pkg in (pkg1, pkg2, pkg3)]
444+
split_names = set((name for split in splits for name, _ in split))
445+
totals = [sum((pct for _, pct in split)) for split in splits]
446+
# since the split percentages are rounded, they may not always total exactly 100.000
447+
self.assertAlmostEqual(totals[0], 100, delta=0.1)
448+
self.assertAlmostEqual(totals[1], 100, delta=0.1)
449+
self.assertAlmostEqual(totals[2], 100, delta=0.1)
450+
self.assertEqual(split_names, program_names)
451+
452+
436453
class SponsorContactModelTests(TestCase):
437454
def test_get_primary_contact_for_sponsor(self):
438455
sponsor = baker.make(Sponsor)

0 commit comments

Comments
 (0)
0