Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eventyay Common: Create a dashboard and enhance settings page #498

Merged
merged 19 commits into from
Jan 18, 2025
Merged
Show file tree
Hide file tree
Changes from 13 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
21 changes: 15 additions & 6 deletions src/pretix/base/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
from ..settings import settings_hierarkey
from .organizer import Organizer, OrganizerBillingModel, Team

TALK_HOSTNAME = settings.TALK_HOSTNAME


class EventMixin:

Expand Down Expand Up @@ -1134,20 +1136,27 @@ def has_paid_things(self):

@property
def talk_schedule_url(self):
talk_host = settings.TALK_HOSTNAME
url = urljoin(talk_host, f"{self.slug}/schedule")
url = urljoin(TALK_HOSTNAME, f"{self.slug}/schedule")
return url

@property
def talk_session_url(self):
talk_host = settings.TALK_HOSTNAME
url = urljoin(talk_host, f"{self.slug}/talk")
url = urljoin(TALK_HOSTNAME, f"{self.slug}/talk")
return url

@property
def talk_speaker_url(self):
talk_host = settings.TALK_HOSTNAME
url = urljoin(talk_host, f"{self.slug}/speaker")
url = urljoin(TALK_HOSTNAME, f"{self.slug}/speaker")
return url

@property
def talk_dashboard_url(self):
url = urljoin(TALK_HOSTNAME, f"/orga/event/{self.slug}")
return url

@property
def talk_settings_url(self):
url = urljoin(TALK_HOSTNAME, f"/orga/event/{self.slug}/settings")
return url

@cached_property
Expand Down
2 changes: 1 addition & 1 deletion src/pretix/control/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
get_organizer_navigation,
)

from ..eventyay_common.views.event import EventCreatedFor
from ..eventyay_common.utils import EventCreatedFor
from ..helpers.i18n import (
get_javascript_format, get_javascript_output_format, get_moment_locale,
)
Expand Down
8 changes: 0 additions & 8 deletions src/pretix/control/forms/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,10 +369,6 @@ class Meta:


class EventUpdateForm(I18nModelForm):
is_video_creation = forms.BooleanField(
required=False,
)

def __init__(self, *args, **kwargs):
self.change_slug = kwargs.pop("change_slug", False)
self.domain = kwargs.pop("domain", False)
Expand Down Expand Up @@ -411,9 +407,6 @@ def __init__(self, *args, **kwargs):
),
widget=forms.CheckboxSelectMultiple,
)
self.is_video_creation = self.initial.get("is_video_creation")
if self.is_video_creation:
self.fields["is_video_creation"].disabled = True

def clean_domain(self):
d = self.cleaned_data["domain"]
Expand Down Expand Up @@ -484,7 +477,6 @@ class Meta:
"geo_lat",
"geo_lon",
"sales_channels",
"is_video_creation",
]
field_classes = {
"date_from": SplitDateTimeField,
Expand Down
228 changes: 228 additions & 0 deletions src/pretix/eventyay_common/base_tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import logging
from importlib import import_module
from importlib.util import find_spec
from typing import Any

from celery import Task
from django_scopes import scopes_disabled
from pretix_venueless.views import VenuelessSettingsForm

from ..base.models import Event
from .utils import EventCreatedFor

logger = logging.getLogger(__name__)


class CreateWorldTask(Task):
"""
A base class for handling video platform creation tasks for events.

This class is responsible for:
- Setting up video plugins for events
- Managing plugin attachments
- Configuring video settings and JWT authentication
- Handling post-task success operations

Raises:
ValueError: When event doesn't exist or validation fails
ConfigurationError: When plugin setup fails
"""

def add_plugin(self, event: Event, plugin_name: str) -> str:
"""
Add a plugin to an event's plugin list.

Args:
event: The event instance to add the plugin to
plugin_name: Name of the plugin to add

Returns:
str: Comma-separated string of all plugins including the new one
"""
if not event.plugins:
return plugin_name
plugins = set(event.plugins.split(","))
plugins.add(plugin_name)
return ",".join(plugins)

def attach_plugin_to_event(self, plugin_name: str, event_slug: str) -> None:
"""
Attach a plugin to an event by updating its plugins list.

Args:
plugin_name: Name of the plugin to attach
event_slug: Unique slug identifier of the event

Raises:
ValueError: If the event does not exist
"""
try:
with scopes_disabled():
event = Event.objects.get(slug=event_slug)
event.plugins = self.add_plugin(event, plugin_name)
event.save()
except Event.DoesNotExist:
logger.error("Event does not exist: %s", event_slug)
raise ValueError(f"Event with slug '{event_slug}' does not exist")

def save_video_settings_information(
self, event_id: str, video_settings: dict
) -> None:
"""
Save video configuration settings for an event.

Args:
event_id: The event identifier
video_settings: Dictionary containing video configuration parameters

Raises:
ValueError: If event doesn't exist or settings validation fails
"""
try:
with scopes_disabled():
event_instance = Event.objects.get(slug=event_id)
video_config_form = VenuelessSettingsForm(
data=video_settings, obj=event_instance
)
if video_config_form.is_valid():
video_config_form.save()
else:
errors = video_config_form.errors
logger.error(
"Video integration configuration failed - Validation errors: %s",
errors,
)
raise ValueError(
f"Failed to validate video integration settings: {errors}"
)
except Event.DoesNotExist:
logger.error("Event does not exist: %s", event_id)
raise ValueError(f"Event with ID '{event_id}' does not exist")

def check_installed_plugin(self, plugin_name: str) -> bool:
"""
Check if a plugin is installed.

Args:
plugin_name: Name of the plugin to check

Returns:
Boolean: True if installed, False otherwise
"""
try:
if find_spec(plugin_name) is not None:
import_module(plugin_name)
return True
return False
except ImportError:
logger.warning("Failed to import plugin: %s", plugin_name)
return False

def extract_jwt_config(self, world_data: dict) -> dict:
"""
Extract JWT configuration from world data.

Args:
world_data: Dictionary containing world configuration data

Returns:
dict: JWT configuration with secret, issuer, and audience
"""
try:
config = world_data.get("config", {})
jwt_secrets = config.get("JWT_secrets", [])
jwt_config = jwt_secrets[0] if jwt_secrets else {}

return {
"secret": jwt_config.get("secret", ""),
"issuer": jwt_config.get("issuer", ""),
"audience": jwt_config.get("audience", ""),
}
except (KeyError, IndexError) as e:
logger.warning("Failed to extract JWT config: %s", e)
return {"secret": "", "issuer": "", "audience": ""}

def setup_video_plugin(self, world_data: dict) -> None:
"""
Setup and configure the video plugin for an event.

Args:
world_data: Dictionary containing world configuration data

Raises:
ValueError: If plugin is not installed or configuration fails
"""
plugin_name = "pretix_venueless"
if not self.check_installed_plugin(plugin_name):
logger.error(
"Video integration configuration failed - Plugin not installed"
)
raise ValueError(f"Plugin '{plugin_name}' is not installed")

event_id = world_data.get("id")
if not event_id:
raise ValueError("World data missing event ID")

# Setup plugin
self.attach_plugin_to_event(plugin_name, event_id)

# Configure video settings
jwt_config = self.extract_jwt_config(world_data)
video_settings = {
"venueless_url": world_data.get("domain", ""),
"venueless_secret": jwt_config["secret"],
"venueless_issuer": jwt_config["issuer"],
"venueless_audience": jwt_config["audience"],
"venueless_all_items": True,
"venueless_items": [],
"venueless_questions": [],
}

self.save_video_settings_information(event_id, video_settings)

def on_success(self, retval: Any, task_id: str, args: tuple, kwargs: dict) -> Any:
"""
Handle successful task completion by setting up the video plugin.
Enable video plugin for the event

Args:
retval: Task return value containing world data
task_id: ID of the completed task
args: Original task arguments
kwargs: Original task keyword arguments

Returns:
Any: Result from parent class on_success method
"""
if isinstance(retval, dict):
try:
self.setup_video_plugin(retval)
except ValueError as e:
logger.error("Video integration configuration failed: %s", e)
return super().on_success(retval, task_id, args, kwargs)


class SendEventTask(Task):
def on_success(self, retval: Any, task_id: str, args: tuple, kwargs: dict) -> Any:
"""
Handle successful task completion by updating event settings.
Set the event settings to "create_for" both:
indicate that the event is created for both tickets and talk

Args:
retval: Task return value
task_id: ID of the completed task
args: Original task arguments
kwargs: Original task keyword arguments

Returns:
Any: Result from parent class on_success method
"""
event = kwargs.get("event", {}).get("slug", "")
try:
event = Event.objects.get(slug=event)
event.settings.set("create_for", EventCreatedFor.BOTH.value)
event.save()
except Event.DoesNotExist:
logger.error("Event with slug %s does not exist", event)
return super().on_success(retval, task_id, args, kwargs)
Loading
Loading