diff --git a/README.rst b/README.rst index fa06bdc..cd7f8f2 100644 --- a/README.rst +++ b/README.rst @@ -1,14 +1,7 @@ pretix-congressschedule ======================= -This is a plugin for `pretix`_. It generates a `c3voc-schema`_ compatible `schedule.xml` endpoint and hackertours-compatible markdown table for event-series. -To access the endpoints without logging in, generate an `API token`_ first. - -Subevent language field ------------------------ - -To determine a subevent's language, this plugin adds a language dropdown selector. -Default: `deen` (multi-lingual of German and English) +This is a plugin for `pretix`_. It generates a `c3voc-schema`_ compatible `schedule.xml` endpoint for events. Accessing schedule.xml ---------------------- @@ -19,32 +12,6 @@ Accessing schedule.xml 3. Receive either a 200 status code with an XML document adhering to `schedule.xml.xsd`_ or a 400 error code with additional information inside `` - -Using schedule.md ----------------------- - -1. Create an `event-series`_ in pretix; a singular event or non-event shop will not work, as products won't have required start and end times associated with them - -2. Visit `/api/v1/event/{organizationSlug}/{eventSlug}/schedule.md` and replace `{organizationSlug}` and `{eventSlug}` with the respective slugs - -3. Receive either a 200 status code with a Markdown document containing a table used for hackertours.hamburg.ccc.de or a 400 error code with additional information inside `` - -4. To embed this into Hugo, use the following syntax: - -```hugo -{{ $url := "https://{prefixInstanceRoot}/api/v1/event/{organizationSlug}/{eventSlug}/schedule.md" }} -{{ $opts := dict - "headers" (dict "Authorization" "Token 6r5waszrj1qbdwqbewbmmk7h46ilocmyfh3e2gxqa9oj52vijmzo1dppk39t3hkl") -}} -{{ with try (resources.GetRemote $url $opts) }} - {{ with .Err }} - {{ errorf "%s" . }} - {{ else with .Value }} - {{ .Content | safeHTML }} - {{ end }} -{{ end }} -``` - Development setup ^^^^^^^^^^^^^^^^^ @@ -63,25 +30,6 @@ Development setup the 'plugins' tab in the settings. -Changelog ---------- - -1.1.1 -~~~~~ - -- ```` defaults to ``deen`` across XML and Markdown outputs. - -1.1.0 -~~~~~ - -- Add subevent-level "Language" field and use it to emit ```` per subevent (defaults to ``deen``). - -1.0.0 -~~~~~ - -- Initial release - - License ------- @@ -93,7 +41,6 @@ Released under the terms of the Apache License 2.0 .. _pretix: https://github.com/pretix/pretix .. _pretix development setup: https://docs.pretix.eu/en/latest/development/setup.html -.. _API token: https://docs.pretix.eu/dev/api/tokenauth.html#obtaining-an-api-token .. _c3voc-schema: https://c3voc.de/wiki/schedule#schedule_xml .. _schedule.xml.xsd: https://c3voc.de/schedule/schema.xsd .. _event-series: https://docs.pretix.eu/guides/event-series/?h=dates#how-to-create-an-event-series diff --git a/pretix_congressschedule/__init__.py b/pretix_congressschedule/__init__.py index a82b376..5becc17 100644 --- a/pretix_congressschedule/__init__.py +++ b/pretix_congressschedule/__init__.py @@ -1 +1 @@ -__version__ = "1.1.1" +__version__ = "1.0.0" diff --git a/pretix_congressschedule/api.py b/pretix_congressschedule/api.py index 178d827..5f457cf 100644 --- a/pretix_congressschedule/api.py +++ b/pretix_congressschedule/api.py @@ -1,10 +1,6 @@ from django.http import HttpResponse from rest_framework import views from pretix.base.models import Event, SubEvent -try: - from pretix.base.models import SubEventMetaValue -except Exception: # pragma: no cover - SubEventMetaValue = None import xml.etree.ElementTree as ET from collections import defaultdict from datetime import timedelta @@ -186,133 +182,12 @@ class CongressScheduleView(views.APIView): # track – use room name as a simple track assignment ET.SubElement(ev_el, 'track').text = slugify(room_name) or 'general' - # Optional elements: language – per subevent via SubEventMetaValue - def _get_lang(subevent: SubEvent) -> str: - if SubEventMetaValue is not None: - try: - v = ( - SubEventMetaValue.objects - .filter(subevent=subevent, key='congressschedule_language') - .values_list('value', flat=True) - .first() - ) - return (v or 'deen').strip() or 'deen' - except Exception: - pass - # Fallbacks for environments without pretix DB access - md = getattr(subevent, 'meta_data', None) or {} - if isinstance(md, dict) and 'congressschedule_language' in md: - return (md.get('congressschedule_language') or 'deen') - se_settings = getattr(subevent, 'settings', None) - try: - return se_settings.get('congressschedule_language', 'deen') if se_settings is not None else 'deen' - except Exception: - return 'deen' - - lang = _get_lang(se) - ET.SubElement(ev_el, 'language').text = str(lang or 'deen') + # Optional elements: keep minimal but include language if available + lang = getattr(ev.settings, 'locale', None) + if lang: + ET.SubElement(ev_el, 'language').text = str(lang) # Leave optional complex children (persons, recording, links, attachments) empty for now xml_bytes = ET.tostring(root, encoding='utf-8', xml_declaration=True) - return HttpResponse(xml_bytes, content_type='application/xml') - - -class HackertoursMarkdownView(views.APIView): - def get(self, request, organizer, event, *args, **kwargs): - try: - ev = Event.objects.get(organizer__slug=organizer, slug=event) - except Event.DoesNotExist: - return HttpResponse("Event not found", status=404, content_type='text/plain; charset=utf-8') - - if not ev.has_subevents: - return HttpResponse( - 'Event is not an event-series: https://docs.pretix.eu/guides/event-series/?h=dates#how-to-create-an-event-series', - status=400, - content_type='text/plain; charset=utf-8' - ) - - subs = SubEvent.objects.filter(event=ev).order_by('date_from') - - # Helper: localize strings - def _localize(val): - if hasattr(val, 'localize'): - try: - return val.localize(ev.settings.locale) - except Exception: - return str(val) - return str(val) if val is not None else '' - - # Slugify for links - def slugify(text: str) -> str: - text = (text or '').lower() - text = re.sub(r'\s+', '-', text) - text = re.sub(r'[^a-z0-9\-_]', '', text) - text = text.strip('-_') - return text or 'item' - - # Build day -> events map and gather all start times - days = defaultdict(list) # {date -> [SubEvent]} - start_dts = [] - for se in subs: - if not se.date_from: - continue - days[se.date_from.date()].append(se) - start_dts.append(se.date_from) - - if not start_dts: - return HttpResponse("No subevents found", content_type='text/plain; charset=utf-8') - - unique_days = sorted(days.keys()) - - # Build time slots (minutes since midnight) - def to_minutes(dt): - return dt.hour * 60 + dt.minute - - event_minutes = {to_minutes(dt) for dt in start_dts} - min_hour = min(dt.hour for dt in start_dts) - max_hour = max(dt.hour for dt in start_dts) - hour_minutes = {h * 60 for h in range(min_hour, max_hour + 1)} - all_minutes = sorted(event_minutes | hour_minutes) - - # For each day, map minute -> list of markdown items - day_slots = {d: defaultdict(list) for d in unique_days} - for d in unique_days: - for se in days[d]: - title = _localize(se.name) - tmin = to_minutes(se.date_from) - hhmm = se.date_from.strftime('%H:%M') - v = ( - SubEventMetaValue.objects - .filter(subevent=se, property__name='congressschedule_language') - .values_list('value', flat=True) - .first() - ) - lang = (v or 'deen').strip() or 'deen' - link = f"./{slugify(title)}/" - md_item = f"{hhmm} [{title}]({link}) {{< lang {lang} >}}" - day_slots[d][tmin].append(md_item) - - # Build markdown table - lines = [] - # Header - header = ["Zeit"] + [f"Tag {i} ({d.strftime('%d.%m.')})" for i, d in enumerate(unique_days, start=1)] - lines.append("| " + " | ".join(header) + " |") - lines.append("|" + "|".join(["---"] * len(header)) + "|") - - # Rows - for m in all_minutes: - is_full_hour = (m % 60) == 0 - zeit_label = f"{m // 60:02d}:{m % 60:02d}" if is_full_hour else "" - row = [zeit_label] - for d in unique_days: - items = day_slots[d].get(m, []) - if items: - cell = " ".join(items) - else: - cell = "-" if is_full_hour else "" - row.append(cell) - lines.append("| " + " | ".join(row) + " |") - - md = "\n".join(lines) + "\n" - return HttpResponse(md.encode('utf-8'), content_type='text/markdown; charset=utf-8') + return HttpResponse(xml_bytes, content_type='application/xml') \ No newline at end of file diff --git a/pretix_congressschedule/apps.py b/pretix_congressschedule/apps.py index b09c51d..b756542 100644 --- a/pretix_congressschedule/apps.py +++ b/pretix_congressschedule/apps.py @@ -12,7 +12,7 @@ class PassbookApp(AppConfig): class PretixPluginMeta: name = gettext_lazy("Congress Schedule") author = "Vincent Mahnke" - description = gettext_lazy("Provides c3voc-schedule compatible XML files") + description = gettext_lazy("Provides passbook tickets for pretix") category = "API" visible = True featured = True diff --git a/pretix_congressschedule/locale/django.pot b/pretix_congressschedule/locale/django.pot index 592aa9e..a174392 100644 --- a/pretix_congressschedule/locale/django.pot +++ b/pretix_congressschedule/locale/django.pot @@ -22,5 +22,5 @@ msgid "Congress Schedule" msgstr "" #: pretix_congressschedule/__init__.py:13 -msgid "Provides c3voc-schedule compatible XML files" +msgid "Provides passbook tickets for pretix" msgstr "" \ No newline at end of file diff --git a/pretix_congressschedule/signals.py b/pretix_congressschedule/signals.py index 24ed1e4..e69de29 100644 --- a/pretix_congressschedule/signals.py +++ b/pretix_congressschedule/signals.py @@ -1,87 +0,0 @@ -from django import forms -from django.utils.translation import gettext_lazy as _ -try: - # Available in pretix runtime - from pretix.base.models import SubEventMetaValue -except Exception: # pragma: no cover - during docs build or without pretix - SubEventMetaValue = None - - -class SubEventLanguageForm(forms.Form): - language = forms.ChoiceField( - label=_("Language"), - required=False, - help_text=_("Select the language for this tour."), - choices=[ - ('deen', _("Bilingual")), - ('de', _("German")), - ('en', _("English")), - ], - ) - - def __init__(self, *args, **kwargs): - self.event = kwargs.pop('event') - self.subevent = kwargs.pop('subevent', None) - super().__init__(*args, **kwargs) - # Pre-fill from subevent meta if available - if self.subevent: - val = ( - SubEventMetaValue.objects - .filter(subevent=self.subevent, property__name='congressschedule_language') - .values_list('value', flat=True) - .first() - ) - self.fields['language'].initial = val or '' - elif self.subevent and hasattr(self.subevent, 'settings'): - # Fallback (older pretix): might be event-wide, keep as last resort - self.fields['language'].initial = "deen" - - @property - def title(self): - return _("Language") - - def save(self): - if not self.subevent: - return - val = (self.cleaned_data.get('language') or '').strip() or 'deen' - # Persist as real subevent meta value so it's scoped per subevent - from pretix.base.models import EventMetaProperty - - property_obj, _ = EventMetaProperty.objects.get_or_create( - name='congressschedule_language', - defaults={'default': '', 'organizer': self.event.organizer} - ) - SubEventMetaValue.objects.update_or_create( - subevent=self.subevent, - property=property_obj, - defaults={'value': val}, - ) - - -def subevent_forms(sender, request, subevent, **kwargs): - # Provide our additional subevent form - import logging - logger = logging.getLogger(__name__) - logger.debug("Providing congressschedule subevent form for event %s, subevent %s", sender.slug, getattr(subevent, 'name', 'no-subevent')) - form = SubEventLanguageForm( - data=request.POST if request.method == 'POST' else None, - event=sender, - subevent=subevent, - prefix='congressschedule', - ) - return form - - -def connect_signals(): - # Connect to pretix.control.signals.subevent_forms at import time - try: - from pretix.control import signals as control_signals - - control_signals.subevent_forms.connect(subevent_forms, dispatch_uid='pretix_congressschedule_subevent_language') - except Exception: - # Pretix not fully loaded in some contexts (e.g., docs build) - pass - - -# Connect immediately when module is imported via AppConfig.ready() -connect_signals() diff --git a/pretix_congressschedule/urls.py b/pretix_congressschedule/urls.py index fc50cad..4f9c59d 100644 --- a/pretix_congressschedule/urls.py +++ b/pretix_congressschedule/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .api import CongressScheduleView, HackertoursMarkdownView +from .api import CongressScheduleView urlpatterns = [ path( @@ -7,9 +7,4 @@ urlpatterns = [ CongressScheduleView.as_view(), name='schedule-xml', ), - path( - 'api/v1/event///schedule.md', - HackertoursMarkdownView.as_view(), - name='schedule-md', - ), ] \ No newline at end of file