Skip to content

Commit

Permalink
Add management command to create sponsor vouchers for PyCon 2023 (#2233)
Browse files Browse the repository at this point in the history
* 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 <ewdurbin@gmail.com>
  • Loading branch information
renatooliveira and ewdurbin committed Jan 26, 2023
1 parent afe3cdb commit f01f67f
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ __pycache__
.env
.DS_Store
.envrc
.state/
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,9 @@ shell: .state/db-initialized
clean:
docker-compose down -v
rm -f .state/docker-build-web .state/db-initialized .state/db-migrated

test: .state/db-initialized
docker-compose run --rm web ./manage.py test

docker_shell: .state/db-initialized
docker-compose run --rm web /bin/bash
133 changes: 133 additions & 0 deletions sponsors/management/commands/create_pycon_vouchers_for_sponsors.py
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)
54 changes: 54 additions & 0 deletions sponsors/tests/test_management_command.py
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")

0 comments on commit f01f67f

Please sign in to comment.