Skip to content

Commit 4ad9d40

Browse files
authored
Fulfill promo codes for PyCon 2022 (#1967)
* small fixups for our asset models * Management Command to fulfill pycon2022 promo codes for sponsors
1 parent c5c1d04 commit 4ad9d40

File tree

3 files changed

+125
-3
lines changed

3 files changed

+125
-3
lines changed

pydotorg/settings/base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,8 @@
314314
### pydotorg.middleware.GlobalSurrogateKey
315315

316316
GLOBAL_SURROGATE_KEY = 'pydotorg-app'
317+
318+
### PyCon Integration for Sponsor Voucher Codes
319+
PYCON_API_KEY = config("PYCON_API_KEY", default="deadbeef-dead-beef-dead-beefdeadbeef")
320+
PYCON_API_SECRET = config("PYCON_API_SECRET", default="deadbeef-dead-beef-dead-beefdeadbeef")
321+
PYCON_API_HOST = config("PYCON_API_HOST", default="localhost:8000")
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import os
2+
from hashlib import sha1
3+
from calendar import timegm
4+
from datetime import datetime
5+
import sys
6+
from urllib.parse import urlencode
7+
8+
import requests
9+
from requests.exceptions import RequestException
10+
11+
from django.db.models import Q
12+
from django.conf import settings
13+
from django.core.management import BaseCommand
14+
15+
from sponsors.models import (
16+
SponsorBenefit,
17+
BenefitFeature,
18+
ProvidedTextAsset,
19+
TieredQuantity,
20+
)
21+
22+
API_KEY = settings.PYCON_API_KEY
23+
API_SECRET = settings.PYCON_API_SECRET
24+
API_HOST = settings.PYCON_API_HOST
25+
26+
BENEFITS = {
27+
64: "full_conference_passes",
28+
65: "expo_hall_only_passes",
29+
77: "additional_full_conference_passes",
30+
73: "additional_expo_hall_only_passes",
31+
}
32+
33+
34+
def api_call(uri, query):
35+
method = "GET"
36+
body = ""
37+
38+
timestamp = timegm(datetime.utcnow().timetuple())
39+
base_string = "".join(
40+
(
41+
API_SECRET,
42+
str(timestamp),
43+
method.upper(),
44+
f"{uri}?{urlencode(query)}",
45+
body,
46+
)
47+
)
48+
49+
headers = {
50+
"X-API-Key": str(API_KEY),
51+
"X-API-Signature": str(sha1(base_string.encode("utf-8")).hexdigest()),
52+
"X-API-Timestamp": str(timestamp),
53+
}
54+
scheme = "http" if settings.DEBUG else "https"
55+
url = f"{scheme}://{API_HOST}{uri}"
56+
try:
57+
return requests.get(url, headers=headers, params=query).json()
58+
except RequestException:
59+
raise
60+
61+
62+
class Command(BaseCommand):
63+
"""
64+
Create Contract objects for existing approved Sponsorships.
65+
66+
Run this command as a initial data migration or to make sure
67+
all approved Sponsorships do have associated Contract objects.
68+
"""
69+
70+
help = "Create Contract objects for existing approved Sponsorships."
71+
72+
def handle(self, **options):
73+
for benefit, code_type in BENEFITS.items():
74+
for sponsorbenefit in (
75+
SponsorBenefit.objects.filter(sponsorship_benefit_id=benefit)
76+
.filter(sponsorship__status="finalized")
77+
.all()
78+
):
79+
try:
80+
quantity = BenefitFeature.objects.instance_of(TieredQuantity).get(
81+
sponsor_benefit=sponsorbenefit
82+
)
83+
except BenefitFeature.DoesNotExist:
84+
print(
85+
f"No quantity found for {sponsorbenefit.sponsorship.sponsor.name} and {code_type}"
86+
)
87+
continue
88+
try:
89+
asset = ProvidedTextAsset.objects.filter(
90+
sponsor_benefit=sponsorbenefit
91+
).get(internal_name=f"{code_type}_code")
92+
except ProvidedTextAsset.DoesNotExist:
93+
print(
94+
f"No provided asset found for {sponsorbenefit.sponsorship.sponsor.name} with internal name {code_type}_code"
95+
)
96+
continue
97+
98+
result = api_call(
99+
"/2022/api/promo_codes/generate/",
100+
query={
101+
"type": code_type,
102+
"quantity": quantity.quantity,
103+
"sponsor_name": sponsorbenefit.sponsorship.sponsor.name,
104+
},
105+
)
106+
if result["code"] == 200:
107+
print(
108+
f"Fullfilling {code_type} for {sponsorbenefit.sponsorship.sponsor.name}: {quantity.quantity}"
109+
)
110+
promo_code = result["data"]["promo_code"]
111+
asset.value = promo_code
112+
asset.save()
113+
else:
114+
print(
115+
f"Error from PyCon when fullfilling {code_type} for {sponsorbenefit.sponsorship.sponsor.name}: {result}"
116+
)
117+
print(f"Done!")

sponsors/models/benefits.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ class ProvidedAssetMixin(AssetMixin):
265265
and which is stored in the related asset class.
266266
"""
267267

268-
@property
268+
@AssetMixin.value.getter
269269
def value(self):
270270
if hasattr(self, 'shared') and self.shared:
271271
return self.shared_value()
@@ -497,7 +497,7 @@ def display_modifier(self, name, **kwargs):
497497
return f"{name} ({self.quantity})"
498498

499499
def __str__(self):
500-
return f"{self.quantity} of {self.benefit} for {self.package}"
500+
return f"{self.quantity} of {self.sponsor_benefit} for {self.package}"
501501

502502

503503
class EmailTargetable(BaseEmailTargetable, BenefitFeature):
@@ -553,7 +553,7 @@ class Meta(BaseProvidedTextAsset.Meta, BenefitFeature.Meta):
553553
verbose_name_plural = "Provided Texts"
554554

555555
def __str__(self):
556-
return f"Provided text"
556+
return f"Provided text {self.internal_name}"
557557

558558

559559
class ProvidedFileAsset(ProvidedAssetMixin, BaseProvidedFileAsset, BenefitFeature):

0 commit comments

Comments
 (0)