-
Notifications
You must be signed in to change notification settings - Fork 616
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add management command to create sponsor vouchers for PyCon 2023 (#2233)
* ignore Makefile .state folder * add test and docker_shell command to Makefile * Add command to create pycon vouchers for sponsors * Update sponsors/management/commands/create_pycon_vouchers_for_sponsors.py * Update sponsors/management/commands/create_pycon_vouchers_for_sponsors.py Co-authored-by: Ee Durbin <[email protected]>
- Loading branch information
1 parent
afe3cdb
commit f01f67f
Showing
4 changed files
with
194 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,3 +25,4 @@ __pycache__ | |
.env | ||
.DS_Store | ||
.envrc | ||
.state/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
sponsors/management/commands/create_pycon_vouchers_for_sponsors.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import os | ||
from hashlib import sha1 | ||
from calendar import timegm | ||
from datetime import datetime | ||
import sys | ||
from urllib.parse import urlencode | ||
|
||
import requests | ||
from requests.exceptions import RequestException | ||
|
||
from django.db.models import Q | ||
from django.conf import settings | ||
from django.core.management import BaseCommand | ||
|
||
from sponsors.models import ( | ||
SponsorBenefit, | ||
BenefitFeature, | ||
ProvidedTextAsset, | ||
TieredBenefit, | ||
) | ||
|
||
BENEFITS = { | ||
121: { | ||
"internal_name": "full_conference_passes_2023_code", | ||
"voucher_type": "SPNS_COMP_", | ||
}, | ||
139: { | ||
"internal_name": "expo_hall_only_passes_2023_code", | ||
"voucher_type": "SPNS_EXPO_COMP_", | ||
}, | ||
148: { | ||
"internal_name": "additional_full_conference_passes_2023_code", | ||
"voucher_type": "SPNS_EXPO_DISC_", | ||
}, | ||
166: { | ||
"internal_name": "online_only_conference_passes_2023_code", | ||
"voucher_type": "SPNS_ONLINE_COMP_", | ||
}, | ||
} | ||
|
||
|
||
def api_call(uri, query): | ||
method = "GET" | ||
body = "" | ||
|
||
timestamp = timegm(datetime.utcnow().timetuple()) | ||
base_string = "".join( | ||
( | ||
settings.PYCON_API_SECRET, | ||
str(timestamp), | ||
method.upper(), | ||
f"{uri}?{urlencode(query)}", | ||
body, | ||
) | ||
) | ||
|
||
headers = { | ||
"X-API-Key": str(settings.PYCON_API_KEY), | ||
"X-API-Signature": str(sha1(base_string.encode("utf-8")).hexdigest()), | ||
"X-API-Timestamp": str(timestamp), | ||
} | ||
scheme = "http" if settings.DEBUG else "https" | ||
url = f"{scheme}://{settings.PYCON_API_HOST}{uri}" | ||
try: | ||
return requests.get(url, headers=headers, params=query).json() | ||
except RequestException: | ||
raise | ||
|
||
|
||
def generate_voucher_codes(year): | ||
for benefit_id, code in BENEFITS.items(): | ||
for sponsorbenefit in ( | ||
SponsorBenefit.objects.filter(sponsorship_benefit_id=benefit_id) | ||
.filter(sponsorship__status="finalized") | ||
.all() | ||
): | ||
try: | ||
quantity = BenefitFeature.objects.instance_of(TieredBenefit).get( | ||
sponsor_benefit=sponsorbenefit | ||
) | ||
except BenefitFeature.DoesNotExist: | ||
print( | ||
f"No quantity found for {sponsorbenefit.sponsorship.sponsor.name} and {code['internal_name']}" | ||
) | ||
continue | ||
try: | ||
asset = ProvidedTextAsset.objects.filter( | ||
sponsor_benefit=sponsorbenefit | ||
).get(internal_name=code["internal_name"]) | ||
except ProvidedTextAsset.DoesNotExist: | ||
print( | ||
f"No provided asset found for {sponsorbenefit.sponsorship.sponsor.name} with internal name {code['internal_name']}" | ||
) | ||
continue | ||
|
||
result = api_call( | ||
f"/{year}/api/vouchers/", | ||
query={ | ||
"voucher_type": code["voucher_type"], | ||
"quantity": quantity.quantity, | ||
"sponsor_name": sponsorbenefit.sponsorship.sponsor.name, | ||
}, | ||
) | ||
if result["code"] == 200: | ||
print( | ||
f"Fullfilling {code['internal_name']} for {sponsorbenefit.sponsorship.sponsor.name}: {quantity.quantity}" | ||
) | ||
promo_code = result["data"]["promo_code"] | ||
asset.value = promo_code | ||
asset.save() | ||
else: | ||
print( | ||
f"Error from PyCon when fullfilling {code['internal_name']} for {sponsorbenefit.sponsorship.sponsor.name}: {result}" | ||
) | ||
print(f"Done!") | ||
|
||
|
||
class Command(BaseCommand): | ||
""" | ||
Create Contract objects for existing approved Sponsorships. | ||
Run this command as a initial data migration or to make sure | ||
all approved Sponsorships do have associated Contract objects. | ||
""" | ||
|
||
help = "Create Contract objects for existing approved Sponsorships." | ||
|
||
def add_arguments(self, parser): | ||
parser.add_argument("year") | ||
|
||
def handle(self, **options): | ||
year = options["year"] | ||
generate_voucher_codes(year) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
from django.test import TestCase | ||
|
||
from model_bakery import baker | ||
|
||
from unittest import mock | ||
|
||
from sponsors.models import ProvidedTextAssetConfiguration, ProvidedTextAsset | ||
from sponsors.models.enums import AssetsRelatedTo | ||
|
||
from sponsors.management.commands.create_pycon_vouchers_for_sponsors import ( | ||
generate_voucher_codes, | ||
BENEFITS, | ||
) | ||
|
||
|
||
class CreatePyConVouchersForSponsorsTestCase(TestCase): | ||
@mock.patch( | ||
"sponsors.management.commands.create_pycon_vouchers_for_sponsors.api_call", | ||
return_value={"code": 200, "data": {"promo_code": "test-promo-code"}}, | ||
) | ||
def test_generate_voucher_codes(self, mock_api_call): | ||
for benefit_id, code in BENEFITS.items(): | ||
sponsor = baker.make("sponsors.Sponsor", name="Foo") | ||
sponsorship = baker.make( | ||
"sponsors.Sponsorship", status="finalized", sponsor=sponsor | ||
) | ||
sponsorship_benefit = baker.make( | ||
"sponsors.SponsorshipBenefit", id=benefit_id | ||
) | ||
sponsor_benefit = baker.make( | ||
"sponsors.SponsorBenefit", | ||
id=benefit_id, | ||
sponsorship=sponsorship, | ||
sponsorship_benefit=sponsorship_benefit, | ||
) | ||
quantity = baker.make( | ||
"sponsors.TieredBenefit", | ||
sponsor_benefit=sponsor_benefit, | ||
) | ||
config = baker.make( | ||
ProvidedTextAssetConfiguration, | ||
related_to=AssetsRelatedTo.SPONSORSHIP.value, | ||
_fill_optional=True, | ||
internal_name=code["internal_name"], | ||
) | ||
asset = config.create_benefit_feature(sponsor_benefit=sponsor_benefit) | ||
|
||
generate_voucher_codes(2020) | ||
|
||
for benefit_id, code in BENEFITS.items(): | ||
asset = ProvidedTextAsset.objects.get( | ||
sponsor_benefit__id=benefit_id, internal_name=code["internal_name"] | ||
) | ||
self.assertEqual(asset.value, "test-promo-code") |