Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ env/
.env.prod
.env.prod.db
.env
.env.enc

# Coverage and state
.coverage
Expand Down
1 change: 1 addition & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ services:
DJANGO_DEFAULT_FROM_EMAIL: PyLadiesCon <[email protected]>
DJANGO_EMAIL_HOST: maildev
DJANGO_EMAIL_PORT: 1025
env_file: .env
depends_on:
redis:
condition: service_healthy
Expand Down
10 changes: 10 additions & 0 deletions event/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.contrib import admin

from .models import Event

class EventAdmin(admin.ModelAdmin):
list_display = ("event_slug",)
search_fields = ("event_slug",)
# list_filter = ("region", "application_status")

admin.site.register(Event, EventAdmin)
Empty file added event/api/__init__.py
Empty file.
69 changes: 69 additions & 0 deletions event/api/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import os
import requests
import json

class PretalxClient:
""" A simple client for interacting with Pretalx REST API """
def __init__(self, base_url=None, default_headers=None, timeout=10):
token = os.getenv('PRETALX_API_TOKEN')
if not token:
raise ValueError("Please provide an environment Variable named 'PRETALX_API_TOKEN'")
else:
self.token = token

self.session = requests.Session()

self.base_url = base_url.rstrip('/') if base_url else None
self.timeout = timeout

self.default_headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'User-Agent': 'Python PretalxClient/1.0'
}

if default_headers:
self.default_headers.update(default_headers)

self.session.headers.update(self.default_headers)


def auth(self):
""" Add Authentication parameters to the Pretalx Client Request """
self.session.headers.update({"Authorization": f"Token {self.token}"})

def _build_url(self, endpoint: str) -> str:
url = f"{self.base_url}/{endpoint.lstrip('/')}" if self.base_url else endpoint
return url

def get_events(self, event_name: str):
""" Get a list of events by name search """
url = self._build_url("/events")
query = {
# "q": event_name
# "is_public": True
}
resp = self.session.get(url, params=query)
print(resp)
return resp.text

def get_event(self, event_slug: str):
url = self._build_url(f"/events/{event_slug}")
resp = self.session.get(url)
print(resp)
return resp.json()

def get_speakers(self, event_slug: str):
# https://pretalx.com/api/events/{event}/speakers/
url = self._build_url(f"/events/{event_slug}/speakers")
resp = self.session.get(url)
resp.raise_for_status()
return resp.json()

def get_speaker_information(self, event_slug: str):
# https://pretalx.com/api/events/{event}/speaker-information/
url = self._build_url(f"/events/{event_slug}/speaker-information")
resp = self.session.get(url)
resp.raise_for_status()
return resp.json()

10 changes: 10 additions & 0 deletions event/api/pretalx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from .client import PretalxClient

PRETALX_BASE_URL="https://pretalx.com/api"
EVENT_NAME="mayatest1-2025"

def fetch_event_speakers(event_name: str):
client = PretalxClient(base_url=PRETALX_BASE_URL)
client.auth()
return client.get_speakers(event_name)

34 changes: 34 additions & 0 deletions event/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 5.1.8 on 2025-05-29 11:33

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
("portal", "0001_initial"),
]

operations = [
migrations.CreateModel(
name="Event",
fields=[
(
"basemodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="portal.basemodel",
),
),
("event_slug", models.CharField(blank=True)),
],
bases=("portal.basemodel",),
),
]
Empty file added event/migrations/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions event/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.db import models

from portal.models import BaseModel

class Event(BaseModel):
event_slug = models.CharField(blank=True, null=False)

15 changes: 15 additions & 0 deletions event/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.contrib.auth.decorators import login_required
from django.urls import path

from . import views

app_name = "speaker"

urlpatterns = [
path("", views.index, name="index"),
path(
"load-speakers",
login_required(views.load_speakers),
name="speaker-load",
),
]
51 changes: 51 additions & 0 deletions event/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.http import HttpResponse

from .models import Event
from speaker.models import SpeakerProfile


###
# # Fetch Pretalx Speakers
###
from .api.pretalx import fetch_event_speakers

def pull_event_speakers(event_slug):
speaker_results = fetch_event_speakers(event_slug)
print(speaker_results)
for speaker in speaker_results['results']:
print(speaker)
return speaker_results['results']

def create_speakers(speakers: list[dict]):
for speaker in speakers:
already_created = SpeakerProfile.objects.filter(code__contains=speaker.get('code'))
if len(already_created) == 0:
SpeakerProfile.objects.create(**speaker)
else:
print("Speaker already Created")
print(already_created.first())

print(SpeakerProfile.objects.count())


@login_required
def index(request):
context = {}
event = Event.objects.first()
if event:
context["event_slug"] = event.event_slug
else:
context["event_slug"] = None
return render(request, "event/index.html", context)


@login_required
def load_speakers(request):
# context = {}
events = Event.objects.all()
for event in events:
pull_event_speakers(event.event_slug)
return HttpResponse("")

27 changes: 27 additions & 0 deletions portal/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from django.contrib import admin

class AdminExtension(admin.AdminSite):
def get_app_list(self, request, app_label=None):
app_list = super().get_app_list(request, app_label)
app_list += [
{
"name": "Events/Speaker Management",
"app_label": "prtlx_mgmt",
"models": [
{
"name": "event",
"object_name": "event",
"admin_url": "/admin/event",
"view_only": True
},
{
"name": "speaker",
"object_name": "speaker",
"admin_url": "/admin/speaker",
"view_only": True
}
]
}
]

return app_list
2 changes: 2 additions & 0 deletions portal/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
"storages",
"portal",
"volunteer",
"speaker",
"event",
"portal_account",
"widget_tweaks",
]
Expand Down
2 changes: 2 additions & 0 deletions portal/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
urlpatterns = [
path("", views.index, name="index"),
path("volunteer/", include("volunteer.urls", namespace="volunteer")),
path("speaker/", include("speaker.urls", namespace="speaker")),
path("event/", include("event.urls", namespace="event")),
path("admin/", admin.site.urls),
path("accounts/", include("allauth.urls")),
path(
Expand Down
Empty file added speaker/__init__.py
Empty file.
10 changes: 10 additions & 0 deletions speaker/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.contrib import admin

from .models import SpeakerProfile

class SpeakerProfileAdmin(admin.ModelAdmin):
list_display = ("code", "name", "email")
search_fields = ("code", "name", "email")
# list_filter = ("region", "application_status")

admin.site.register(SpeakerProfile, SpeakerProfileAdmin)
6 changes: 6 additions & 0 deletions speaker/apps.py
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VolunteerConfig needs renamed

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class VolunteerConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "speaker"
31 changes: 31 additions & 0 deletions speaker/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from enum import StrEnum


# class RoleTypes(StrEnum):
# """Role types for the volunteer."""

# ADMIN = "Admin"
# STAFF = "Staff"
# VENDOR = "Vendor"
# VOLUNTEER = "Volunteer"


class ApplicationStatus(StrEnum):
"""Application status for the volunteer."""

PENDING = "Pending Review"
APPROVED = "Approved"
REJECTED = "Rejected"
CANCELLED = "Cancelled"


class Region(StrEnum):
"""Region where the volunteer usually reside."""

NO_REGION = ""
ASIA = "Asia"
EUROPE = "Europe"
NORTH_AMERICA = "North America"
SOUTH_AMERICA = "South America"
AFRICA = "Africa"
OCEANIA = "Oceania"
Empty file.
Empty file.
67 changes: 67 additions & 0 deletions speaker/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import re

from django import forms
from django.core.exceptions import ValidationError
from django.forms import ModelForm
from django.forms.widgets import SelectMultiple

from .languages import LANGUAGES
from .models import SpeakerProfile

class LanguageSelectMultiple(SelectMultiple):
"""
A custom widget for selecting multiple languages with autocomplete.
"""

def __init__(self, attrs=None, choices=()):
default_attrs = {
"class": "form-control select2-multiple",
"data-placeholder": "Start typing to select languages...",
}
if attrs:
default_attrs.update(attrs)
super().__init__(default_attrs, choices)

class SpeakerProfileForm(ModelForm):

# discord_username = forms.CharField(required=True)
additional_comments = forms.CharField(widget=forms.Textarea, required=False)

class Meta:
model = SpeakerProfile
exclude = ["application_status"]
help_texts = {
# "github_username": "GitHub username (e.g., username)",
# "discord_username": "Required - Your Discord username for team communication (e.g., username#1234)",
# "instagram_username": "Instagram username without @ (e.g., username)",
# "bluesky_username": "Bluesky username (e.g., username or username.bsky.social)",
# "mastodon_url": "Mastodon handle (e.g., @[email protected] or https://instance.tld/@username)",
# "x_username": "X/Twitter username without @ (e.g., username)",
# "linkedin_url": "LinkedIn URL (e.g., linkedin.com/in/username)",
"region": "Region where you normally reside",
}

def clean(self):
cleaned_data = super().clean()
return cleaned_data

def __init__(self, *args, **kwargs):
# self.user = kwargs.pop("user", None)
super().__init__(*args, **kwargs)

sorted_languages = sorted(LANGUAGES, key=lambda x: x[1])

self.fields["discord_username"].required = True
self.fields["languages_spoken"].choices = sorted_languages
self.fields["languages_spoken"].widget = LanguageSelectMultiple(
choices=sorted_languages
)

if self.instance and self.instance.pk:
pass

def save(self, commit=True):
# if self.user:
# self.instance.user = self.user
volunteer_profile = super().save(commit)
return volunteer_profile
Loading