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

schedule/feeds: make json feed schema compatible #1612

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3272363
schedule/feeds: make json feed schema compatible
Kunsi May 24, 2024
239e5bc
run `ruff format` for apps/schedule/feeds.py
Kunsi May 24, 2024
bf214a4
make schedule-compatible json the default view
Kunsi Jun 12, 2024
c9b4a48
schedule json: fix trailing comma
Kunsi Jun 12, 2024
78a4eda
move frab json export to /schedule/frab-<year>.json
Kunsi Aug 14, 2024
550b857
move frab xml schedule to /schedule/frab-<year>.xml
Kunsi Aug 14, 2024
4d13889
schedule_frab_json: proposal.may_record has been replaced by proposal…
Kunsi Aug 14, 2024
ee99a1a
schedule_json: re-add CORS header
Kunsi Aug 14, 2024
b153466
schedule_frab_json: remove comment about tracks
Kunsi Aug 14, 2024
d375a70
schedule_frab_json: use existing functions from schedule_xml
Kunsi Aug 14, 2024
c2e1603
move generation of frab-style json schedule to separate file
Kunsi Aug 14, 2024
e77a8e7
export_db: adjust for changed frab xml endpoint and added frab json e…
Kunsi Aug 14, 2024
c499dca
lineup_talk_redirect: frab xml has new function name
Kunsi Aug 14, 2024
d41b13b
templates/schedule: mention frab xml and frab json export
Kunsi Aug 14, 2024
7f311c6
apps.schedule.schedule_json: fix some mistakes
Kunsi Aug 31, 2024
96253c8
apps.schedule: rename schedule_{json,xml} to schedule_frab_{json,xml}
Kunsi Aug 31, 2024
28c3a8c
apps.schedule.feeds: move /schedule/frab-{year}.{json,xml} to /schedu…
Kunsi Aug 31, 2024
d015bb7
apps.schedule.schedule_frab_json: fix licence version
Kunsi Aug 31, 2024
33ecbd2
tests/test_{frab_export,schedule}: fix imports of apps.schedule.sched…
Kunsi Aug 31, 2024
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
9 changes: 7 additions & 2 deletions apps/base/tasks_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,13 @@ def export_db(table):
return

with app.test_client() as client:
for file_type in ["frab", "json", "ics"]:
url = f"/schedule/{year}.{file_type}"
for file_type, file_url in (
("frab", f"frab-{year}.xml"),
("frab_json", f"frab-{year}.json"),
("json", f"{year}.json"),
("ics", f"{year}.ics"),
):
url = f"/schedule/{file_url}"
dest_path = os.path.join(path, "public", f"schedule.{file_type}")
response = client.get(url)
if response.status_code != 200:
Expand Down
2 changes: 1 addition & 1 deletion apps/schedule/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def lineup_talk_redirect(year, proposal_id, slug=None):
def feed_redirect(fmt):
routes = {
"json": "schedule.schedule_json",
"frab": "schedule.schedule_frab",
"frab": "schedule.schedule_frab_xml",
"ical": "schedule.schedule_ical",
"ics": "schedule.schedule_ical",
}
Expand Down
58 changes: 48 additions & 10 deletions apps/schedule/feeds.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
from icalendar import Calendar, Event
from flask import request, abort, current_app as app, Response
from flask import request, abort, current_app as app, redirect, url_for, Response
from flask_cors import cross_origin
from flask_login import current_user

Expand All @@ -9,7 +9,8 @@
from models.cfp import Proposal

from ..common import feature_flag, feature_enabled, json_response
from .schedule_xml import export_frab
from .schedule_frab_json import export_frab_json
from .schedule_frab_xml import export_frab
from .historic import feed_historic
from .data import (
_get_scheduled_proposals,
Expand All @@ -34,9 +35,9 @@ def _format_event_description(event):
venue_str = event["venue"]
if event["map_link"]:
venue_str = f'{venue_str} ({event["map_link"]})'
footer_block.append(f'Venue: {venue_str}')
footer_block.append(f"Venue: {venue_str}")
if footer_block:
description += '\n\n' + '\n'.join(footer_block)
description += "\n\n" + "\n".join(footer_block)

return description

Expand All @@ -47,7 +48,7 @@ def schedule_json(year):
if year != event_year():
return feed_historic(year, "json")

if not feature_enabled('SCHEDULE'):
if not feature_enabled("SCHEDULE"):
abort(404)

schedule = [_convert_time_to_str(p) for p in _get_scheduled_proposals(request.args)]
Expand All @@ -56,12 +57,51 @@ def schedule_json(year):
return Response(json.dumps(schedule), mimetype="application/json")


@schedule.route("/schedule/<int:year>.frab.json")
def schedule_frab_json(year):
if year != event_year():
return feed_historic(year, "frab_json")

if not feature_enabled("SCHEDULE"):
abort(404)

schedule = (
Proposal.query.filter(
Proposal.is_accepted,
Proposal.scheduled_time.isnot(None),
Proposal.scheduled_venue_id.isnot(None),
Proposal.scheduled_duration.isnot(None),
)
.order_by(Proposal.scheduled_time)
.all()
)

return Response(
json.dumps(
{
"schedule": export_frab_json(schedule),
"$schema": "https://c3voc.de/schedule/schema.json",
"generator": {
"name": "emfcamp-website",
"url": "https://github.com/emfcamp/Website",
},
}
),
mimetype="application/json",
)


@schedule.route("/schedule/<int:year>.frab")
def schedule_frab(year):
return redirect(url_for("schedule_frab_xml", year=year), code=301)


@schedule.route("/schedule/<int:year>.frab.xml")
def schedule_frab_xml(year):
if year != event_year():
return feed_historic(year, "frab")

if not feature_enabled('SCHEDULE'):
if not feature_enabled("SCHEDULE"):
abort(404)

schedule = (
Expand All @@ -88,7 +128,7 @@ def schedule_ical(year):
if year != event_year():
return feed_historic(year, "ics")

if not feature_enabled('SCHEDULE'):
if not feature_enabled("SCHEDULE"):
abort(404)

schedule = _get_scheduled_proposals(request.args)
Expand Down Expand Up @@ -174,9 +214,7 @@ def favourites_ical():

@schedule.route("/schedule/now-and-next.json")
def now_and_next_json():
return Response(
json.dumps(_get_upcoming(request.args)), mimetype="application/json"
)
return Response(json.dumps(_get_upcoming(request.args)), mimetype="application/json")


@schedule.route("/schedule/<int:year>/<int:proposal_id>.json")
Expand Down
128 changes: 128 additions & 0 deletions apps/schedule/schedule_frab_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from datetime import timedelta
from math import ceil

from main import external_url
from models import event_end, event_start, event_year

from . import event_tz
from .schedule_frab_xml import get_day_start_end, get_duration


def events_per_day_and_room(schedule):
days = {
current_date.date(): {
"index": index + 1,
"start": get_day_start_end(event_start() + timedelta(days=index))[0],
"end": get_day_start_end(event_start() + timedelta(days=index))[1],
"rooms": {},
}
for index, current_date in enumerate(
event_start() + timedelta(days=i) for i in range((event_end() - event_start()).days + 1)
)
}

for proposal in schedule:
talk_date = proposal.start_date.date()
if proposal.start_date.hour < 4 and talk_date != event_start().date():
talk_date -= timedelta(days=1)
if talk_date not in days:
# Event is outside the scheduled event duration.
continue
if proposal.scheduled_venue.name not in days[talk_date]:
days[talk_date]["rooms"][proposal.scheduled_venue.name] = [proposal]
else:
days[talk_date]["rooms"][proposal.scheduled_venue.name].append(proposal)

return days.values()


def export_frab_json(schedule):
duration_days = ceil((event_end() - event_start()).total_seconds() / 86400)

rooms = set([proposal.scheduled_venue.name for proposal in schedule])

schedule_json = {
"version": "1.0-public",
"conference": {
"acronym": "emf{}".format(event_year()),
"days": [],
"daysCount": duration_days,
"end": event_end().strftime("%Y-%m-%d"),
"rooms": [
{
"name": room,
}
for room in sorted(rooms)
],
"start": event_start().strftime("%Y-%m-%d"),
"time_zone_name": str(event_tz),
"timeslot_duration": "00:10",
"title": "Electromagnetic Field {}".format(event_year()),
"url": external_url(".main"),
},
}

for day in events_per_day_and_room(schedule):
day_schedule = {
"date": day["start"].strftime("%Y-%m-%d"),
"day_end": day["start"].isoformat(),
"day_start": day["end"].isoformat(),
"index": day["index"],
"rooms": {},
}
for room, events in sorted(day["rooms"].items()):
day_schedule["rooms"][room] = []
for proposal in events:
links = {
proposal.c3voc_url,
proposal.youtube_url,
proposal.thumbnail_url,
proposal.map_link,
}
links.discard(None)
links.discard("")
day_schedule["rooms"][room].append(
{
"abstract": None, # The proposal model does not implement abstracts
"attachments": [],
"date": event_tz.localize(proposal.start_date).isoformat(),
"description": proposal.description,
"do_not_record": proposal.video_privacy != "public",
"duration": get_duration(proposal.start_date, proposal.end_date),
"guid": None,
"id": proposal.id,
# This assumes there will never be a non-english talk,
# which is probably fine for a conference in the UK.
"language": "en",
"links": sorted(links),
"persons": [
{
"name": name.strip(),
"public_name": name.strip(),
}
for name in (proposal.published_names or proposal.user.name).split(",")
],
"recording_license": "CC BY-SA 4.0",
"room": room,
"slug": "emf{}-{}-{}".format(
event_year(),
proposal.id,
proposal.slug,
),
"start": event_tz.localize(proposal.start_date).strftime("%H:%M"),
"subtitle": None,
"title": proposal.display_title,
# Contrary to the infobeamer frab module, the json module does not allow users to set colours
# for tracks themselves. It instead relies on the schedule itself to provide those colours.
"track": None,
"type": proposal.type,
"url": external_url(
".item",
year=event_year(),
proposal_id=proposal.id,
slug=proposal.slug,
),
}
)
schedule_json["conference"]["days"].append(day_schedule)
return schedule_json
File renamed without changes.
4 changes: 4 additions & 0 deletions templates/schedule/line-up.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ <h2>Line-up</h2>
<p>You can also get this list as an <a href="{{ url_for('.schedule_ical', year=event_year) }}">iCal feed</a>
for your calendar, and a <a href="{{ url_for('.schedule_json', year=event_year) }}">json feed</a> for your giant robot.
</p>
<p>If you use an app for viewing the schedule, you might be looking for a
<a href="{{ url_for('.schedule_frab_json', year=year) }}">Frab JSON</a> or
<a href="{{ url_for('.schedule_frab_xml', year=year) }}">Frab XML</a> feed.
</p>
{% endif %}
{% else %}
<div class="alert alert-info">
Expand Down
3 changes: 2 additions & 1 deletion templates/schedule/user_schedule.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
<strong>Schedule feeds:</strong>
<a href="{{ url_for('.schedule_json', year=year) }}">JSON</a> |
<a href="{{ url_for('.schedule_ical', year=year) }}">iCal</a> |
<a href="{{ url_for('.schedule_frab', year=year) }}">Frab</a>
<a href="{{ url_for('.schedule_frab_json', year=year) }}">Frab JSON</a> |
<a href="{{ url_for('.schedule_frab_xml', year=year) }}">Frab XML</a>
</p>
{% endblock %}
{% block foot %}
Expand Down
2 changes: 1 addition & 1 deletion tests/test_frab_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from lxml import etree

from apps.schedule import event_tz
from apps.schedule.schedule_xml import (
from apps.schedule.schedule_frab_xml import (
make_root,
add_day,
add_room,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from apps.schedule import schedule_xml
from apps.schedule import schedule_frab_xml


@pytest.mark.parametrize('start_time, end_time, expected', [
Expand All @@ -17,4 +17,4 @@ def test_get_duration(start_time, end_time, expected):
fmt = '%Y-%m-%d %H:%M:%S'
start_time = datetime.strptime(start_time, fmt)
end_time = datetime.strptime(end_time, fmt)
assert schedule_xml.get_duration(start_time, end_time) == expected
assert schedule_frab_xml.get_duration(start_time, end_time) == expected