From 0f66669afd9101ab1f4c7903bcfdd9ce1f4dd0cd Mon Sep 17 00:00:00 2001 From: odkhang Date: Fri, 2 Aug 2024 18:41:30 +0700 Subject: [PATCH] Provide export of personal schedule (#168) Co-authored-by: lcduong --- .../agenda/templates/agenda/header_row.html | 19 ++++++ src/pretalx/agenda/views/schedule.py | 19 +++++- src/pretalx/common/signals.py | 2 + src/pretalx/schedule/exporters.py | 65 +++++++++++++++++-- src/pretalx/schedule/signals.py | 31 ++++++++- src/pretalx/static/common/scss/_pretalx.scss | 4 ++ 6 files changed, 133 insertions(+), 7 deletions(-) diff --git a/src/pretalx/agenda/templates/agenda/header_row.html b/src/pretalx/agenda/templates/agenda/header_row.html index ce40122b6..42c3a6205 100644 --- a/src/pretalx/agenda/templates/agenda/header_row.html +++ b/src/pretalx/agenda/templates/agenda/header_row.html @@ -45,6 +45,25 @@ {% endif %} {% endif %}{% endfor %} +
+ {% for exporter in my_exporters %}{% if exporter.public %} +
  • + {% if exporter.icon|slice:":3" == "fa-" %} + + {% else %} + {{ exporter.icon }} + {% endif %} + {{ exporter.verbose_name }} + {% if exporter.show_qrcode %} + +
    + +
    +
    {{ exporter.get_qrcode }}
    +
    + {% endif %} +
  • + {% endif %}{% endfor %} {% endif %} diff --git a/src/pretalx/agenda/views/schedule.py b/src/pretalx/agenda/views/schedule.py index c75c74271..6b9d6db2d 100644 --- a/src/pretalx/agenda/views/schedule.py +++ b/src/pretalx/agenda/views/schedule.py @@ -20,10 +20,11 @@ from django_context_decorator import context from pretalx.common.mixins.views import EventPermissionRequired -from pretalx.common.signals import register_data_exporters +from pretalx.common.signals import register_data_exporters, register_my_data_exporters from pretalx.common.utils import safe_filename from pretalx.schedule.ascii import draw_ascii_schedule from pretalx.schedule.exporters import ScheduleData +from pretalx.submission.models.submission import SubmissionFavourite logger = logging.getLogger(__name__) @@ -93,7 +94,8 @@ def get_exporter(self, request): exporter = ( exporter[len("export.") :] if exporter.startswith("export.") else exporter ) - responses = register_data_exporters.send(request.event) + responses = (register_data_exporters.send(request.event) + + register_my_data_exporters.send(request.event)) for __, response in responses: ex = response(request.event) if ex.identifier == exporter: @@ -111,6 +113,12 @@ def get(self, request, *args, **kwargs): activate(request.event.locale) exporter.schedule = self.schedule + if "-my" in exporter.identifier and self.request.user.id is None: + return HttpResponseRedirect(self.request.event.urls.login) + favs_talks = SubmissionFavourite.objects.filter(user=self.request.user.id) + if favs_talks.exists(): + exporter.talk_ids = favs_talks[0].talk_list + exporter.is_orga = getattr(self.request, "is_orga", False) try: @@ -214,6 +222,13 @@ def exporters(self): for _, exporter in register_data_exporters.send(self.request.event) ) + @context + def my_exporters(self): + return list( + exporter(self.request.event) + for _, exporter in register_my_data_exporters.send(self.request.event) + ) + @context def show_talk_list(self): return ( diff --git a/src/pretalx/common/signals.py b/src/pretalx/common/signals.py index ad70233d8..23e4c80d2 100644 --- a/src/pretalx/common/signals.py +++ b/src/pretalx/common/signals.py @@ -202,6 +202,8 @@ def wrapper(*args, **kwargs): """ register_data_exporters = EventPluginSignal() + +register_my_data_exporters = EventPluginSignal() """ This signal is sent out to get all known data exporters. Receivers should return a subclass of pretalx.common.exporter.BaseExporter diff --git a/src/pretalx/schedule/exporters.py b/src/pretalx/schedule/exporters.py index 6d4b15601..b786a9720 100644 --- a/src/pretalx/schedule/exporters.py +++ b/src/pretalx/schedule/exporters.py @@ -6,7 +6,9 @@ import vobject from django.template.loader import get_template from django.utils.functional import cached_property +from django.utils.safestring import SafeString from i18nfield.utils import I18nJSONEncoder +import xml.etree.ElementTree as ET from pretalx import __version__ from pretalx.common.exporter import BaseExporter @@ -60,7 +62,7 @@ def data(self): "index": index + 1, "start": current_date.replace(hour=4, minute=0).astimezone(event.tz), "end": current_date.replace(hour=3, minute=59).astimezone(event.tz) - + dt.timedelta(days=1), + + dt.timedelta(days=1), "first_start": None, "last_end": None, "rooms": {}, @@ -115,6 +117,8 @@ class FrabXmlExporter(ScheduleData): verbose_name = "XML (frab compatible)" public = True show_qrcode = True + favs_retrieve = False + talk_ids = [] icon = "fa-code" cors = "*" @@ -128,13 +132,31 @@ def render(self, **kwargs): "base_url": get_base_url(self.event), } content = get_template("agenda/schedule.xml").render(context=context) + if self.favs_retrieve: + root = ET.fromstring(content) + for day in root.findall('day'): + for room in day.findall('room'): + for event in room.findall('event'): + event_slug = event.find('url').text.split('/')[-2] + if event_slug not in self.talk_ids: + room.remove(event) + filtered_xml_data = ET.tostring(root, encoding='unicode') + content = SafeString(filtered_xml_data) return f"{self.event.slug}-schedule.xml", "text/xml", content +class MyFrabXmlExporter(FrabXmlExporter): + identifier = "schedule-my.xml" + verbose_name = "My XML (frab compatible)" + favs_retrieve = True + + class FrabXCalExporter(ScheduleData): identifier = "schedule.xcal" verbose_name = "XCal (frab compatible)" public = True + favs_retrieve = False + talk_ids = [] icon = "fa-calendar" cors = "*" @@ -142,13 +164,30 @@ def render(self, **kwargs): url = get_base_url(self.event) context = {"data": self.data, "url": url, "domain": urlparse(url).netloc} content = get_template("agenda/schedule.xcal").render(context=context) + if self.favs_retrieve: + root = ET.fromstring(content) + for vcalendar in root.findall('vcalendar'): + for vevent in vcalendar.findall('vevent'): + event_uid = vevent.find('uid').text.split('@@')[0] + if event_uid not in self.talk_ids: + vcalendar.remove(vevent) + filtered_xcal_data = ET.tostring(root, encoding='unicode') + content = SafeString(filtered_xcal_data) return f"{self.event.slug}.xcal", "text/xml", content +class MyFrabXCalExporter(FrabXCalExporter): + identifier = "schedule-my.xcal" + verbose_name = "My XCal (frab compatible)" + favs_retrieve = True + + class FrabJsonExporter(ScheduleData): identifier = "schedule.json" verbose_name = "JSON (frab compatible)" public = True + favs_retrieve = False + talk_ids = [] icon = "{ }" cors = "*" @@ -225,7 +264,7 @@ def get_data(self, **kwargs): "code": person.code, "public_name": person.get_display_name(), "avatar": person.get_avatar_url(self.event) - or None, + or None, "biography": getattr( person.profiles.filter( event=self.event @@ -240,7 +279,8 @@ def get_data(self, **kwargs): "answer": answer.answer, "options": [ option.answer - for option in answer.options.all() + for option in + answer.options.all() ], } for answer in person.answers.all() @@ -269,7 +309,8 @@ def get_data(self, **kwargs): else [] ), } - for talk in room["talks"] + for talk in room["talks"] if ( + self.favs_retrieve is True and talk.submission.code in self.talk_ids) or not self.favs_retrieve ] for room in day["rooms"] }, @@ -295,11 +336,19 @@ def render(self, **kwargs): ) +class MyFrabJsonExporter(FrabJsonExporter): + identifier = "schedule-my.json" + verbose_name = "My JSON (frab compatible)" + favs_retrieve = True + + class ICalExporter(BaseExporter): identifier = "schedule.ics" verbose_name = "iCal" public = True show_qrcode = True + favs_retrieve = False + talk_ids = [] icon = "fa-calendar" cors = "*" @@ -320,6 +369,14 @@ def render(self, **kwargs): .order_by("start") ) for talk in talks: + if talk.submission.code not in self.talk_ids: + continue talk.build_ical(cal, creation_time=creation_time, netloc=netloc) return f"{self.event.slug}.ics", "text/calendar", cal.serialize() + + +class MyICalExporter(ICalExporter): + identifier = "schedule-my.ics" + verbose_name = "My iCal" + favs_retrieve = True diff --git a/src/pretalx/schedule/signals.py b/src/pretalx/schedule/signals.py index 07c711934..47ef15e63 100644 --- a/src/pretalx/schedule/signals.py +++ b/src/pretalx/schedule/signals.py @@ -1,6 +1,6 @@ from django.dispatch import receiver -from pretalx.common.signals import EventPluginSignal, register_data_exporters +from pretalx.common.signals import EventPluginSignal, register_data_exporters, register_my_data_exporters schedule_release = EventPluginSignal() """ @@ -22,6 +22,13 @@ def register_ical_exporter(sender, **kwargs): return ICalExporter +@receiver(register_my_data_exporters, dispatch_uid="exporter_builtin_my_ical") +def register_my_ical_exporter(sender, **kwargs): + from .exporters import MyICalExporter + + return MyICalExporter + + @receiver(register_data_exporters, dispatch_uid="exporter_builtin_xml") def register_xml_exporter(sender, **kwargs): from .exporters import FrabXmlExporter @@ -29,6 +36,13 @@ def register_xml_exporter(sender, **kwargs): return FrabXmlExporter +@receiver(register_my_data_exporters, dispatch_uid="exporter_builtin_my_xml") +def register_my_xml_exporter(sender, **kwargs): + from .exporters import MyFrabXmlExporter + + return MyFrabXmlExporter + + @receiver(register_data_exporters, dispatch_uid="exporter_builtin_xcal") def register_xcal_exporter(sender, **kwargs): from .exporters import FrabXCalExporter @@ -36,8 +50,23 @@ def register_xcal_exporter(sender, **kwargs): return FrabXCalExporter +@receiver(register_my_data_exporters, dispatch_uid="exporter_builtin_my_xcal") +def register_my_xcal_exporter(sender, **kwargs): + from .exporters import MyFrabXCalExporter + + return MyFrabXCalExporter + + @receiver(register_data_exporters, dispatch_uid="exporter_builtin_json") def register_json_exporter(sender, **kwargs): from .exporters import FrabJsonExporter return FrabJsonExporter + + +@receiver(register_my_data_exporters, dispatch_uid="exporter_builtin_my_json") +def register_my_json_exporter(sender, **kwargs): + from .exporters import MyFrabJsonExporter + + return MyFrabJsonExporter + diff --git a/src/pretalx/static/common/scss/_pretalx.scss b/src/pretalx/static/common/scss/_pretalx.scss index 6b0ba348f..1e53570c0 100644 --- a/src/pretalx/static/common/scss/_pretalx.scss +++ b/src/pretalx/static/common/scss/_pretalx.scss @@ -108,6 +108,10 @@ table td.numeric-left { position: absolute; background: white; } +.export-break { + height: 1px; + background: #B0B0B0; +} .export-qrcode:hover .export-qrcode-image, .qrcode:hover .qrcode-image { display: block;