Skip to content

Commit 12a1299

Browse files
authored
Merge pull request #49 from zorexsalvo/speakers
Fetch and display confirmed speakers from Pretalx API
2 parents ab80312 + ffb6031 commit 12a1299

7 files changed

Lines changed: 5031 additions & 20 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,6 @@ dmypy.json
134134

135135
# NodeJS/npm
136136
node_modules/
137+
138+
# Pretalx export files
139+
*_python-asia-*.json

app/home/templates/home/sections/speakers-section.html

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,26 +51,25 @@ <h2 class="font-td_pinoy text-6xl text-orange-2 text-center m-0">Speakers</h2>
5151
</div>
5252
<div class="speakers-wrapper flex items-center gap-[30px] flex-row flex-wrap justify-between">
5353
{% for speaker in speakers %}
54+
{% if speaker.avatar_url %}
5455
<div class="rounded-[24px] flex flex-col gap-5 w-full max-w-[100%] lg:max-w-[250px] grow items-center">
5556
<div class="overflow-hidden flex-shrink-0 w-[250px] h-[250px]">
56-
{% if speaker.photo_url %}
57-
<img src="{{ speaker.photo_url }}" class="w-[250px] h-[250px] object-cover rounded-[85px]"
58-
alt="{{ speaker.full_name }} - Image" style="height: 250px;">
59-
{% else %}
60-
<img src="#" class="w-[250px] h-[250px] object-cover rounded-[85px]"
61-
alt="{{ speaker.full_name }} - Image" style="height: 250px;">
62-
{% endif %}
57+
<img src="{{ speaker.avatar_url }}" class="w-[250px] h-[250px] object-cover rounded-[85px]"
58+
alt="{{ speaker.name }} - Image" width="250" height="250">
6359
</div>
6460
<div>
65-
<h3 class="font-bantayog text-brown-1 text-2xl uppercase">{{ speaker.full_name }}</h3>
61+
<h3 class="font-bantayog text-brown-1 text-2xl uppercase">{{ speaker.name }}</h3>
6662
{% if speaker.title %}<p class="font-nunito text-brown-2 text-[13px] mb-2">{{ speaker.title }}</p>{% endif %}
6763
{% if speaker.introduction %}
6864
<p class="font-nunito text-brown-2 text-[13px]">{{ speaker.introduction|truncatewords:30 }}</p>
6965
{% elif speaker.bio %}
7066
<p class="font-nunito text-brown-2 text-[13px]">{{ speaker.bio|truncatewords:30 }}</p>
67+
{% elif speaker.biography %}
68+
<p class="font-nunito text-brown-2 text-[13px]">{{ speaker.biography|truncatewords:30 }}</p>
7169
{% endif %}
7270
</div>
7371
</div>
72+
{% endif %}
7473
{% empty %}
7574
<p class="text-center text-brown-2">No speakers available yet.</p>
7675
{% endfor %}

app/home/views.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from app.speakers.models import Speaker
77
from app.sponsors.models import Sponsor
88
from config.constants import SPONSOR_TYPE_ORDER
9+
from services.pretalx_service import PretalxService
910

1011

1112
class HomeView(BuildableTemplateView):
@@ -25,9 +26,14 @@ def get_sponsors(self):
2526

2627
def get_context_data(self, **kwargs):
2728
context = super().get_context_data(**kwargs)
29+
service = PretalxService()
2830
context["sponsors_by_type"] = self.get_sponsors()
29-
context["featured_speakers"] = Speaker.objects.filter(is_featured=True).order_by("first_name", "last_name")
30-
context["speakers"] = Speaker.objects.filter(is_featured=False).order_by("first_name", "last_name")
31+
featured_speakers = Speaker.objects.filter(is_featured=True).order_by("first_name", "last_name")
32+
context["featured_speakers"] = featured_speakers
33+
keynote_names = {s.full_name for s in featured_speakers}
34+
context["speakers"] = [
35+
s for s in service.get_speakers("python-asia-2026")["results"] if s.get("name") not in keynote_names
36+
]
3137
return context
3238

3339

config/settings.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@
206206

207207
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
208208

209+
CACHES = {
210+
"default": {
211+
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
212+
"LOCATION": "django_cache",
213+
}
214+
}
215+
209216
# Build directory for bakery
210217
BUILD_DIR = BASE_DIR / "build"
211218
BAKERY_VIEWS = ("app.home.views.HomeView",)

deploy/build.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ uv sync
1313
echo "Applying database migrations..."
1414
uv run manage.py migrate --noinput
1515

16+
# Create cache table
17+
echo "Creating cache table..."
18+
uv run manage.py createcachetable
19+
1620
# Collect static files
1721
echo "Collecting static files..."
1822
uv run manage.py collectstatic --noinput

services/pretalx_service.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import requests
2+
from django.core.cache import cache
23

34
from config.environment import settings
45

56

67
class PretalxService:
7-
def __init__(self, base_url: str):
8-
self.base_url = settings.PRETALX.BASE_URL.rstrip("/")
8+
def __init__(self, base_url: str | None = None):
9+
self.base_url = base_url or settings.PRETALX.BASE_URL.rstrip("/")
910
self.headers = {
10-
"Authorization": f"Token {settings.PRETALX.API_TOKEN}",
1111
"Content-Type": "application/json",
1212
}
1313

@@ -17,14 +17,39 @@ def get_event(self, event_slug: str):
1717
return response.json() if response.ok else response.raise_for_status()
1818

1919
def get_submissions(self, event_slug: str):
20-
url = f"{self.base_url}/api/events/{event_slug}/submissions/"
20+
cache_key = f"pretalx_submissions_{event_slug}"
21+
cached = cache.get(cache_key)
22+
if cached:
23+
return cached
24+
url = f"{self.base_url}/api/events/{event_slug}/submissions/?page_size=999"
2125
response = requests.get(url, headers=self.headers)
22-
return response.json() if response.ok else response.raise_for_status()
26+
data = response.json() if response.ok else response.raise_for_status()
27+
cache.set(cache_key, data)
28+
return data
2329

2430
def get_speakers(self, event_slug: str):
25-
url = f"{self.base_url}/api/events/{event_slug}/speakers/"
26-
response = requests.get(url, headers=self.headers)
27-
return response.json() if response.ok else response.raise_for_status()
31+
try:
32+
cache_key = f"pretalx_speakers_{event_slug}"
33+
cached = cache.get(cache_key)
34+
if cached:
35+
return cached
36+
37+
url = f"{self.base_url}/api/events/{event_slug}/speakers/?page_size=999"
38+
response = requests.get(url, headers=self.headers)
39+
data = response.json() if response.ok else response.raise_for_status()
40+
41+
submissions = self.get_submissions(event_slug)
42+
confirmed_codes = {sub["code"] for sub in submissions.get("results", []) if sub.get("state") == "confirmed"}
43+
44+
data["results"] = [
45+
speaker
46+
for speaker in data.get("results", [])
47+
if any(code in confirmed_codes for code in speaker.get("submissions", []))
48+
]
49+
cache.set(cache_key, data)
50+
return data
51+
except requests.RequestException:
52+
return {"results": []}
2853

2954
def get_talks(self, event_slug: str):
3055
url = f"{self.base_url}/api/events/{event_slug}/talks?limit=999&state=confirmed"
@@ -40,3 +65,8 @@ def send_feedback(self, event_slug: str, submission_id: str, feedback: dict):
4065
url = f"{self.base_url}/api/events/{event_slug}/submissions/{submission_id}/feedback/"
4166
response = requests.post(url, json=feedback, headers=self.headers)
4267
return response.json() if response.ok else response.raise_for_status()
68+
69+
def get_sessions(self, event_slug: str):
70+
url = f"{self.base_url}/api/events/{event_slug}/sessions/"
71+
response = requests.get(url, headers=self.headers)
72+
return response.json() if response.ok else response.raise_for_status()

static/css/app.css

Lines changed: 4964 additions & 2 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)