Skip to content

Commit

Permalink
Merge pull request #607 from CTPUG/frab-xml
Browse files Browse the repository at this point in the history
Emit frab compatible XML
  • Loading branch information
stefanor committed Jul 14, 2021
2 parents 6b0cafe + bc7c9e5 commit 0af691a
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 29 deletions.
3 changes: 3 additions & 0 deletions docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Wafer's settings
The name of the Django cache backend that wafer can use.
Defaults to ``'wafer_cache'``.

``WAFER_CONFERENCE_ACRONYM``
The abbreviated name of the conference.

``WAFER_DEFAULT_GROUPS``
A list of groups that any new user is automatically added to.
This can be used to tweak the default permissions available
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
PyYAML
lxml
mock
pep8
pyflakes
Expand Down
16 changes: 16 additions & 0 deletions wafer/schedule/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from uuid import UUID

from django.core.exceptions import ValidationError
from django.db import models
from django.db.models.signals import post_save, post_delete
from django.urls import reverse
from django.utils.crypto import salted_hmac
from django.utils.translation import ugettext_lazy as _
from django.utils.timezone import localtime

Expand Down Expand Up @@ -297,6 +300,13 @@ def get_url(self):
return self.page.get_absolute_url()
return None

def get_slug(self):
if self.talk:
return self.talk.slug
elif self.page:
return self.page.slug
return None

def get_details(self):
return self.get_desc()

Expand Down Expand Up @@ -343,6 +353,12 @@ def get_duration_minutes(self):
duration = self.get_duration()
return int(duration['hours'] * 60 + duration['minutes'])

@property
def guid(self):
"""Return a GUID for the ScheduleItem (for frab xml)"""
hmac = salted_hmac('wafer-event-uuid', str(self.pk))
return UUID(bytes=hmac.digest()[:16])


def invalidate_check_schedule(*args, **kw):
sender = kw.pop('sender', None)
Expand Down
49 changes: 25 additions & 24 deletions wafer/schedule/templates/wafer.schedule/penta_schedule.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
{% load i18n %}
<schedule>
<generator name="wafer" version="{{ wafer_version }}" />
<version>{# FIXME: We have no schedule versions #}</version>
<conference>
<title>{{ WAFER_CONFERENCE_NAME }}</title>
{% if schedule_pages %}
Expand All @@ -12,8 +14,10 @@
{% endwith %}
<days>{{ schedule_pages|length }}</days>
{% endif %}
<day_change>00:00</day_change>
<timeslot_duration>00:15</timeslot_duration>
<base_url>https://{{ WAFER_CONFERENCE_DOMAIN }}</base_url>
<time_zone_name>{{ TIME_ZONE }}</time_zone_name>
<acronym>{{ WAFER_CONFERENCE_ACRONYM }}</acronym>
</conference>
{% for page in schedule_pages %}
<day date="{{ page.block.start_time|date:'Y-m-d' }}" index="{{ forloop.counter }}">
Expand All @@ -25,27 +29,24 @@
{% for row_venue, items in row.items.items %}
{% if row_venue == venue and items.item %}
{# The event id is the ScheduleItem pk, which should be unique enough #}
<event id="{{ items.item.pk }}">
<event id="{{ items.item.pk }}" guid="{{ items.item.guid }}">
<date>{{ row.start_time.isoformat }}</date>
<start>{{ row.start_time|time:"H:i" }}</start>
{% with dur=items.item.get_duration %}
<duration>{{ dur.hours|stringformat:"02d" }}:{{ dur.minutes|stringformat:"02d" }}</duration>
{% endwith %}
<room>{{ venue.name }}</room>
<track>{{ items.item.talk.track.name|default:"No Track" }}</track>
{# It's not clear what the difference between abstract and description is meant to be #}
{# Both confclerk and Giggity just lump them together into the same thing anyway, and #}
{# summit only outputs stuff in description, so we keep abstract blank and follow #}
{# summit's pattern and only include stuff in the description #}
{# Abstract is defined to be a 1-paragraph summary, displayed before description. #}
{# That doesn't match our data model, so we leave it blank. #}
<abstract/>
<subtitle/>
<slug>{{ WAFER_CONFERENCE_ACRONYM|lower }}-{{ items.item.pk }}-{{ items.item.get_slug }}</slug>
{% if items.item.talk %}
{% with talk=items.item.talk %}
<title>{{ items.item.get_title }}</title>
{# I'm not sure if the raw markdown is the best thing here, but it seems to match what summit does, #}
{# so hopefully the tools can handle the odd corner cases correctly. Giggity at least does some #}
{# santization here. #}
{# We do allow people to request html if they want it, which may also be a bad idea, but is useful for #}
{# the video team #}
{# description is allowed to be HTML or Markdown #}
{# We let the requester select their desired format #}
{% if talk.abstract and render_description %}
<description>{{ talk.abstract.rendered }}</description>
{% elif talk.abstract %}
Expand All @@ -70,10 +71,14 @@
{% endfor %}
</persons>
<abstract/>
<released>{{ talk.video }}</released>
{% if talk.video %}
<license>{{ WAFER_VIDEO_LICENSE }}</license>
{% endif %}
<recording>
{% if talk.video %}
<optout>false</optout>
<license>{{ WAFER_VIDEO_LICENSE }}</license>
{% else %}
<optout>true</optout>
{% endif %}
</recording>
{% endwith %}
{% else %}
<title>{{ items.item.get_details|escape }}</title>
Expand Down Expand Up @@ -105,16 +110,12 @@
{% else %}
<description/>
{% endif %}
<released>True</released>
<license>{{ WAFER_VIDEO_LICENSE }}</license>
<recording>
<optout>false</optout>
<license>{{ WAFER_VIDEO_LICENSE }}</license>
</recording>
{% endif %}
<conf_url>{{ items.item.get_url }}</conf_url>
{# It's useful to have the full url available in the xml file. The pentabarf.xml format isn't that well #}
{# standardised, so we add our own full_conf_url tag to accomodate this requirement #}

{# forcing https here is a bit horrible - make this configurable somewhere? #}
<full_conf_url>https://{{ WAFER_CONFERENCE_DOMAIN }}{{ items.item.get_url }}</full_conf_url>

<url>https://{{ WAFER_CONFERENCE_DOMAIN }}{{ items.item.get_url }}</url>
</event>
{% endif %}
{% endfor %}
Expand Down
197 changes: 197 additions & 0 deletions wafer/schedule/tests/frab.xml.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
<?xml version="1.0"?>
<!--
The Frab XSD isn't versioned yet (and it seems unlikely to happen, from
https://github.com/frab/schedule.xml/issues/4)
So, grabbed from voc git:
https://github.com/voc/schedule/commit/1e22ff63d1b03e959fdf0b9771beeee7bae1b75e
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="schedule">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="generator">
<xs:complexType>
<xs:sequence />
<xs:attribute type="xs:string" name="name"/>
<xs:attribute type="xs:string" name="version"/>
</xs:complexType>
</xs:element>
<xs:element type="xs:string" name="version"/>
<xs:element name="conference">
<xs:complexType>
<xs:all>
<xs:element type="xs:string" name="title"/>
<xs:element type="acronym" name="acronym"/>

<xs:element type="xs:date" name="start" minOccurs="0"/>
<xs:element type="xs:date" name="end" minOccurs="0"/>
<xs:element type="xs:integer" name="days" minOccurs="0"/>
<xs:element type="duration" name="timeslot_duration" minOccurs="0"/>
<xs:element type="httpURI" name="base_url" minOccurs="0"/>
<xs:element type="xs:string" name="time_zone_name" minOccurs="0" maxOccurs="1"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element type="day" name="day" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

<xs:unique name="guid_unique">
<xs:selector xpath="day/room/event" />
<xs:field xpath="@guid" />
</xs:unique>

<xs:unique name="id_unique">
<xs:selector xpath="day/room/event" />
<xs:field xpath="@id" />
</xs:unique>

<xs:unique name="slug_unique">
<xs:selector xpath="day/room/event/slug" />
<xs:field xpath="." />
</xs:unique>

<xs:unique name="day_index_unique">
<xs:selector xpath="day" />
<xs:field xpath="@index" />
</xs:unique>
</xs:element>

<xs:complexType name="day">
<xs:sequence>
<xs:element type="room" name="room" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute type="xs:date" name="date"/>
<xs:attribute type="dateTimeTZ" name="start"/>
<xs:attribute type="dateTimeTZ" name="end"/>
<xs:attribute type="xs:positiveInteger" name="index"/>
</xs:complexType>

<xs:complexType name="room">
<xs:sequence>
<xs:element type="event" name="event" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute type="xs:string" name="name" use="required"/>
<xs:attribute name="guid" type="uuid"/>
</xs:complexType>

<xs:complexType name="event">
<xs:all>
<xs:element type="xs:string" name="room"/>
<xs:element type="xs:string" name="title"/>
<xs:element type="xs:string" name="subtitle"/>
<xs:element type="xs:string" name="type"/>
<xs:element type="dateTimeTZ" name="date"/>
<xs:element type="start" name="start"/>
<xs:element type="duration" name="duration"/>
<xs:element type="xs:string" name="abstract"/>
<xs:element type="slug" name="slug"/>
<xs:element type="xs:string" name="track"/>

<xs:element type="xs:string" name="logo" minOccurs="0"/>
<xs:element type="persons" name="persons" minOccurs="0"/>
<xs:element type="xs:string" name="language" minOccurs="0"/>
<xs:element type="xs:string" name="description" minOccurs="0"/>
<xs:element type="recording" name="recording" minOccurs="0"/>
<xs:element type="links" name="links" minOccurs="0"/>
<xs:element type="attachments" name="attachments" minOccurs="0"/>
<xs:element type="httpURI" name="video_download_url" minOccurs="0"/>
<xs:element type="httpURI" name="url" minOccurs="0"/>
</xs:all>
<xs:attribute name="id" type="xs:positiveInteger" use="required"/>
<xs:attribute name="guid" type="uuid" use="required"/>
</xs:complexType>

<xs:simpleType name="dateTimeTZ">
<xs:restriction base="xs:dateTime">
<xs:pattern value=".+(Z|\+[0-9]{1,2}:[0-9]{2}|-[0-9]{1,2}:[0-9]{2})"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="duration">
<xs:restriction base="xs:string">
<xs:pattern value="([0-9]{1,2}:[0-9]{2})|([0-9]{1,2}:[0-9]{2}:[0-9]{1,2})"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="start">
<xs:restriction base="xs:string">
<xs:pattern value="([0-2][0-9]:[0-5][0-9])|([0-2][0-9]:[0-5][0-9]:[0-5][0-9])"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="uuid">
<xs:restriction base="xs:string">
<xs:pattern value="[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="acronym">
<xs:restriction base="xs:string">
<xs:pattern value="[a-z0-9_-]{4,}"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="slug">
<xs:restriction base="xs:string">
<xs:pattern value="[a-z0-9]{4,}-[0-9]{1,6}-[a-z0-9\-_]{4,}"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="httpURI">
<xs:restriction base="xs:anyURI">
<xs:pattern value="https?://.*"/>
</xs:restriction>
</xs:simpleType>

<xs:complexType name="recording">
<xs:all>
<xs:element type="xs:string" name="license"/>
<xs:element type="xs:boolean" name="optout"/>
<xs:element type="httpURI" name="url" minOccurs="0"/>
<xs:element type="httpURI" name="link" minOccurs="0"/>
</xs:all>
</xs:complexType>

<xs:complexType name="persons">
<xs:sequence>
<xs:element name="person" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:positiveInteger" name="id"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>

<xs:complexType name="links">
<xs:sequence>
<xs:element name="link" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="href"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>

<xs:complexType name="attachments">
<xs:sequence>
<xs:element name="attachment" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="href"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>

0 comments on commit 0af691a

Please sign in to comment.