Skip to content

Commit 1c05562

Browse files
artczclytaemnestra
andauthored
Download pretix data (#33)
First draft of downloading basic pretix data for later reporting – based on the pretalx data download implementation. Co-authored-by: Mia Bajić <38294198+clytaemnestra@users.noreply.github.com>
1 parent 0f2483f commit 1c05562

File tree

14 files changed

+763
-38
lines changed

14 files changed

+763
-38
lines changed

deploy/playbooks/04_cron.yml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@
22
hosts: intbot_app
33

44
tasks:
5-
- name: "Download pretalx data every hour"
5+
- name: "Download pretalx data once a day"
66
ansible.builtin.cron:
7-
name: "Download pretalx data every hour"
7+
name: "Download pretalx data once a day at 05:05am"
88
minute: "5"
9-
hour: "8" # run once a day at 08:05 am
9+
hour: "5"
1010
job: "make prod/cron/pretalx"
11+
12+
- name: "Download pretix data once a day"
13+
ansible.builtin.cron:
14+
name: "Download pretix data once a day at 06:05am"
15+
minute: "5"
16+
hour: "6"
17+
job: "make prod/cron/pretix"

deploy/templates/app/Makefile.app.j2

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,9 @@ prod/manage:
1818
prod/cron/pretalx:
1919
$(MAKE_APP) in-container/manage ARG="download_pretalx_data --event=europython-2025"
2020

21+
22+
prod/cron/pretix:
23+
$(MAKE_APP) in-container/manage ARG="download_pretix_data --event=ep2025"
24+
2125
logs:
2226
docker compose logs -f

deploy/templates/app/intbot.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,9 @@ ZAMMAD_GROUP_PROGRAMME="zammad-programme-group-name-goes-here"
5858
ZAMMAD_GROUP_FINAID="zammad-finaid-group-name-goes-here"
5959
ZAMMAD_GROUP_SPONSORS="zammad-sponsors-group-name-goes-here"
6060
ZAMMAD_GROUP_GRANTS="zammad-grants-group-name-goes-here"
61+
62+
# Pretalx
63+
PRETALX_API_TOKEN="pretalx-api-token"
64+
65+
# Pretix
66+
PRETIX_API_TOKEN="pretix-api-token"

intbot/core/admin.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22

3-
from core.models import DiscordMessage, PretalxData, Webhook
3+
from core.models import DiscordMessage, PretalxData, PretixData, Webhook
44
from django.contrib import admin
55
from django.utils.html import format_html
66

@@ -93,6 +93,33 @@ def pretty_content(self, obj: PretalxData):
9393
pretty_content.short_description = "Content"
9494

9595

96+
class PretixDataAdmin(admin.ModelAdmin):
97+
list_display = [
98+
"uuid",
99+
"resource",
100+
"created_at",
101+
"modified_at",
102+
]
103+
list_filter = [
104+
"created_at",
105+
"resource",
106+
]
107+
readonly_fields = fields = [
108+
"uuid",
109+
"resource",
110+
"pretty_content",
111+
"created_at",
112+
"modified_at",
113+
"processed_at",
114+
]
115+
116+
def pretty_content(self, obj: PretixData):
117+
return format_html("<pre>{}</pre>", json.dumps(obj.content, indent=4))
118+
119+
pretty_content.short_description = "Content"
120+
121+
96122
admin.site.register(Webhook, WebhookAdmin)
97123
admin.site.register(DiscordMessage, DiscordMessageAdmin)
98124
admin.site.register(PretalxData, PretalxDataAdmin)
125+
admin.site.register(PretixData, PretixDataAdmin)

intbot/core/integrations/pretix.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import logging
2+
from typing import Any
3+
4+
import httpx
5+
from core.models import PretixData
6+
from django.conf import settings
7+
8+
logger = logging.getLogger(__name__)
9+
10+
PRETIX_EVENTS = [
11+
"2022",
12+
"ep2023",
13+
"ep2024",
14+
"ep2025",
15+
]
16+
17+
ENDPOINTS = {
18+
PretixData.PretixResources.orders: "orders/",
19+
PretixData.PretixResources.products: "items/",
20+
PretixData.PretixResources.vouchers: "vouchers/",
21+
}
22+
23+
24+
JsonType = dict[str, Any]
25+
26+
27+
def get_event_url(event: str) -> str:
28+
assert event in PRETIX_EVENTS
29+
30+
pretix_url = "https://tickets.europython.eu"
31+
return f"{pretix_url}/api/v1/organizers/europython/events/{event}/"
32+
33+
34+
def fetch_pretix_data(
35+
event: str, resource: PretixData.PretixResources
36+
) -> list[JsonType]:
37+
headers = {
38+
"Authorization": f"Token {settings.PRETIX_API_TOKEN}",
39+
"Content-Type": "application/json",
40+
}
41+
42+
base_url = get_event_url(event)
43+
endpoint = ENDPOINTS[resource]
44+
url = f"{base_url}{endpoint}"
45+
46+
# Pretix paginates the output, so we will need to do multiple requests and
47+
# then merge multiple pages to one big dictionary
48+
results = []
49+
page = 0
50+
51+
# This takes advantage of the fact that url will contain a url to the
52+
# next page, until there is more data to fetch. If this is the last page,
53+
# then the url will be None (falsy), and thus stop the while loop.
54+
while url:
55+
page += 1
56+
response = httpx.get(url, headers=headers)
57+
58+
if response.status_code != 200:
59+
raise Exception(f"Error {response.status_code}: {response.text}")
60+
61+
logger.info("Fetching data from %s, page %s", url, page)
62+
63+
data = response.json()
64+
results += data["results"]
65+
url = data["next"]
66+
67+
return results
68+
69+
70+
def download_latest_orders(event: str) -> PretixData:
71+
data = fetch_pretix_data(event, PretixData.PretixResources.orders)
72+
73+
pretix_data = PretixData.objects.create(
74+
resource=PretixData.PretixResources.orders,
75+
content=data,
76+
)
77+
78+
return pretix_data
79+
80+
81+
def download_latest_products(event: str) -> PretixData:
82+
data = fetch_pretix_data(event, PretixData.PretixResources.products)
83+
84+
pretix_data = PretixData.objects.create(
85+
resource=PretixData.PretixResources.products,
86+
content=data,
87+
)
88+
89+
return pretix_data
90+
91+
92+
def download_latest_vouchers(event: str) -> PretixData:
93+
data = fetch_pretix_data(event, PretixData.PretixResources.vouchers)
94+
95+
pretix_data = PretixData.objects.create(
96+
resource=PretixData.PretixResources.vouchers,
97+
content=data,
98+
)
99+
100+
return pretix_data
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from core.integrations.pretix import (
2+
PRETIX_EVENTS,
3+
download_latest_orders,
4+
download_latest_products,
5+
download_latest_vouchers,
6+
)
7+
from django.core.management.base import BaseCommand
8+
9+
10+
class Command(BaseCommand):
11+
help = "Downloads latest pretix data"
12+
13+
def add_arguments(self, parser):
14+
# Add keyword argument event
15+
parser.add_argument(
16+
"--event",
17+
choices=PRETIX_EVENTS,
18+
help="slug of the event (for example `ep2025`)",
19+
required=True,
20+
)
21+
22+
def handle(self, **kwargs):
23+
event = kwargs["event"]
24+
25+
self.stdout.write(f"Downloading latest products from pretix... {event}")
26+
download_latest_products(event)
27+
28+
self.stdout.write(f"Downloading latest vouchers from pretix... {event}")
29+
download_latest_vouchers(event)
30+
31+
self.stdout.write(f"Downloading latest orders from pretix... {event}")
32+
download_latest_orders(event)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Generated by Django 5.1.4 on 2025-04-24 22:08
2+
3+
import uuid
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
dependencies = [
9+
("core", "0005_add_pretalx_data_model"),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name="PretixData",
15+
fields=[
16+
(
17+
"id",
18+
models.BigAutoField(
19+
auto_created=True,
20+
primary_key=True,
21+
serialize=False,
22+
verbose_name="ID",
23+
),
24+
),
25+
("uuid", models.UUIDField(default=uuid.uuid4)),
26+
(
27+
"resource",
28+
models.CharField(
29+
choices=[
30+
("orders", "Orders"),
31+
("products", "Products"),
32+
("vouchers", "Vouchers"),
33+
],
34+
max_length=255,
35+
),
36+
),
37+
("content", models.JSONField()),
38+
("created_at", models.DateTimeField(auto_now_add=True)),
39+
("modified_at", models.DateTimeField(auto_now=True)),
40+
("processed_at", models.DateTimeField(blank=True, null=True)),
41+
],
42+
),
43+
]

intbot/core/models.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,7 @@ class PretalxData(models.Model):
8787
"""
8888
Table to store raw data download from pretalx for later parsing.
8989
90-
We first download data from pretalx to this table, and then fire a separate
91-
background task that pulls data from this table and stores in separate
92-
"business" tables, like "Proposal" or "Speaker".
90+
We first download data from pretix, then we later parse the latest jsons.
9391
"""
9492

9593
class PretalxResources(models.TextChoices):
@@ -108,5 +106,38 @@ class PretalxResources(models.TextChoices):
108106
modified_at = models.DateTimeField(auto_now=True)
109107
processed_at = models.DateTimeField(blank=True, null=True)
110108

109+
class Meta:
110+
verbose_name_plural = "Pretalx Data"
111+
112+
def __str__(self):
113+
return f"{self.uuid}"
114+
115+
116+
class PretixData(models.Model):
117+
"""
118+
Table to store raw data download from pretix for later parsing.
119+
120+
We first download data from pretix, then we later parse the latest jsons.
121+
"""
122+
123+
class PretixResources(models.TextChoices):
124+
orders = "orders", "Orders"
125+
products = "products", "Products"
126+
vouchers = "vouchers", "Vouchers"
127+
128+
uuid = models.UUIDField(default=uuid.uuid4)
129+
resource = models.CharField(
130+
max_length=255,
131+
choices=PretixResources.choices,
132+
)
133+
content = models.JSONField()
134+
135+
created_at = models.DateTimeField(auto_now_add=True)
136+
modified_at = models.DateTimeField(auto_now=True)
137+
processed_at = models.DateTimeField(blank=True, null=True)
138+
139+
class Meta:
140+
verbose_name_plural = "Pretix Data"
141+
111142
def __str__(self):
112143
return f"{self.uuid}"

intbot/intbot/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,9 @@ def get(name) -> str:
200200
# Pretalx
201201
PRETALX_API_TOKEN = get("PRETALX_API_TOKEN")
202202

203+
# Pretix
204+
PRETIX_API_TOKEN = get("PRETIX_API_TOKEN")
205+
203206

204207
if DJANGO_ENV == "dev":
205208
DEBUG = True

0 commit comments

Comments
 (0)