diff --git a/apps/admin/__init__.py b/apps/admin/__init__.py index c8ebc55a8..3120d516f 100644 --- a/apps/admin/__init__.py +++ b/apps/admin/__init__.py @@ -14,23 +14,20 @@ from flask_login import current_user -from wtforms.validators import Optional, DataRequired, URL +from wtforms.validators import Optional, DataRequired from wtforms import ( SubmitField, BooleanField, HiddenField, - StringField, FieldList, FormField, SelectField, - IntegerField, ) import logging_tree from main import db from models.payment import Payment, BankAccount, BankPayment, BankTransaction from models.purchase import Purchase -from models.ical import CalendarSource from models.feature_flag import FeatureFlag, DB_FEATURE_FLAGS, refresh_flags from models.site_state import SiteState, VALID_STATES, refresh_states, get_states from models.scheduled_task import tasks, ScheduledTaskResult @@ -278,86 +275,6 @@ def payment_config_verify(): ) -@admin.route("/schedule-feeds") -def schedule_feeds(): - feeds = CalendarSource.query.all() - return render_template("admin/schedule-feeds.html", feeds=feeds) - - -class ScheduleForm(Form): - feed_name = StringField("Feed Name", [DataRequired()]) - url = StringField("URL", [DataRequired(), URL()]) - enabled = BooleanField("Feed Enabled") - location = SelectField("Location") - published = BooleanField("Publish events from this feed") - priority = IntegerField("Priority", [Optional()]) - preview = SubmitField("Preview") - submit = SubmitField("Save") - delete = SubmitField("Delete") - - def update_feed(self, feed): - feed.name = self.feed_name.data - feed.url = self.url.data - feed.enabled = self.enabled.data - feed.published = self.published.data - feed.priority = self.priority.data - - def init_from_feed(self, feed): - self.feed_name.data = feed.name - self.url.data = feed.url - self.enabled.data = feed.enabled - self.published.data = feed.published - self.priority.data = feed.priority - - if feed.mapobj: - self.location.data = str(feed.mapobj.id) - else: - self.location.data = "" - - -@admin.route("/schedule-feeds/", methods=["GET", "POST"]) -def schedule_feed(feed_id): - feed = CalendarSource.query.get_or_404(feed_id) - form = ScheduleForm() - - form.location.choices = [] - - if form.validate_on_submit(): - if form.delete.data: - for event in feed.events: - db.session.delete(event) - db.session.delete(feed) - db.session.commit() - flash("Feed deleted") - return redirect(url_for(".schedule_feeds", feed_id=feed_id)) - - form.update_feed(feed) - db.session.commit() - msg = "Updated feed %s" % feed.name - flash(msg) - app.logger.info(msg) - return redirect(url_for(".schedule_feed", feed_id=feed_id)) - - form.init_from_feed(feed) - return render_template("admin/edit-feed.html", feed=feed, form=form) - - -@admin.route("/schedule-feeds/new", methods=["GET", "POST"]) -def new_feed(): - form = ScheduleForm() - - if form.validate_on_submit(): - feed = CalendarSource("") - form.update_feed(feed) - db.session.add(feed) - db.session.commit() - msg = "Created feed %s" % feed.name - flash(msg) - app.logger.info(msg) - return redirect(url_for(".schedule_feed", feed_id=feed.id)) - return render_template("admin/edit-feed.html", form=form) - - @admin.route("/scheduled-tasks") def scheduled_tasks(): data = [] diff --git a/apps/api/schedule.py b/apps/api/schedule.py index f775d8d72..ee20faa18 100644 --- a/apps/api/schedule.py +++ b/apps/api/schedule.py @@ -8,7 +8,6 @@ from . import api from main import db from models.cfp import Proposal -from models.ical import CalendarEvent from models.admin_message import AdminMessage from models.event_tickets import EventTicket @@ -101,40 +100,6 @@ def put(self, proposal_id): return {"is_favourite": new_state} -class FavouriteExternal(Resource): - def get(self, event_id): - if not current_user.is_authenticated: - abort(401) - - event = CalendarEvent.query.get_or_404(event_id) - current_state = event in current_user.calendar_favourites - - return {"is_favourite": current_state} - - def put(self, event_id): - """Put with no data to toggle""" - if not current_user.is_authenticated: - abort(401) - - event = CalendarEvent.query.get_or_404(event_id) - current_state = event in current_user.calendar_favourites - - data = request.get_json() - if data.get("state") is not None: - new_state = bool(data["state"]) - else: - new_state = not current_state - - if new_state and not current_state: - current_user.calendar_favourites.append(event) - elif current_state and not new_state: - current_user.calendar_favourites.remove(event) - - db.session.commit() - - return {"is_favourite": new_state} - - class UpdateLotteryPreferences(Resource): def post(self, proposal_type): if proposal_type not in ["workshop", "youthworkshop"]: @@ -183,6 +148,5 @@ def get(self): api.add_resource(ProposalResource, "/proposal/") api.add_resource(FavouriteProposal, "/proposal//favourite") -api.add_resource(FavouriteExternal, "/external//favourite") api.add_resource(ScheduleMessage, "/schedule_messages") api.add_resource(UpdateLotteryPreferences, "/schedule/tickets//preferences") diff --git a/apps/schedule/__init__.py b/apps/schedule/__init__.py index cd83e938f..2d38e0233 100644 --- a/apps/schedule/__init__.py +++ b/apps/schedule/__init__.py @@ -69,6 +69,5 @@ def feed_redirect(fmt): from . import base # noqa from . import feeds # noqa -from . import external # noqa from . import attendee_content # noqa -from . import tasks # noqa + diff --git a/apps/schedule/base.py b/apps/schedule/base.py index b64fa982c..1f2700dd6 100644 --- a/apps/schedule/base.py +++ b/apps/schedule/base.py @@ -20,7 +20,6 @@ from main import db from models import event_year from models.cfp import Proposal, Venue -from models.ical import CalendarSource, CalendarEvent from models.user import generate_api_token from models.admin_message import AdminMessage from models.event_tickets import EventTicket @@ -87,10 +86,8 @@ def line_up(): # (Because we don't want a bias in starring) random.Random(current_user.get_id()).shuffle(proposals) - externals = CalendarSource.get_enabled_events() - return render_template( - "schedule/line-up.html", proposals=proposals, externals=externals + "schedule/line-up.html", proposals=proposals, ) @@ -101,30 +98,20 @@ def add_favourite(): event_id = int(request.form["fave"]) event_type = request.form["event_type"] - if event_type == "proposal": - proposal = Proposal.query.get_or_404(event_id) - if proposal in current_user.favourites: - current_user.favourites.remove(proposal) - else: - current_user.favourites.append(proposal) - - db.session.commit() - return redirect( - url_for(".main_year", year=event_year()) - + "#proposal-{}".format(proposal.id) - ) + if event_type != "proposal": + abort(400) + proposal = Proposal.query.get_or_404(event_id) + if proposal in current_user.favourites: + current_user.favourites.remove(proposal) else: - event = CalendarEvent.query.get_or_404(event_id) - if event in current_user.calendar_favourites: - current_user.calendar_favourites.remove(event) - else: - current_user.calendar_favourites.append(event) + current_user.favourites.append(proposal) - db.session.commit() - return redirect( - url_for(".main_year", year=event_year()) + "#event-{}".format(event.id) - ) + db.session.commit() + return redirect( + url_for(".main_year", year=event_year()) + + "#proposal-{}".format(proposal.id) + ) @schedule.route("/favourites", methods=["GET", "POST"]) @@ -133,38 +120,28 @@ def favourites(): if (request.method == "POST") and current_user.is_authenticated: event_id = int(request.form["fave"]) event_type = request.form["event_type"] - if event_type == "proposal": - proposal = Proposal.query.get_or_404(event_id) - if proposal in current_user.favourites: - current_user.favourites.remove(proposal) - else: - current_user.favourites.append(proposal) - - db.session.commit() - return redirect(url_for(".favourites") + "#proposal-{}".format(proposal.id)) + if event_type != "proposal": + abort(400) + proposal = Proposal.query.get_or_404(event_id) + if proposal in current_user.favourites: + current_user.favourites.remove(proposal) else: - event = CalendarEvent.query.get_or_404(event_id) - if event in current_user.calendar_favourites: - current_user.calendar_favourites.remove(event) - else: - current_user.calendar_favourites.append(event) + current_user.favourites.append(proposal) - db.session.commit() - return redirect(url_for(".favourites") + "#event-{}".format(event.id)) + db.session.commit() + return redirect(url_for(".favourites") + "#proposal-{}".format(proposal.id)) if current_user.is_anonymous: return redirect(url_for("users.login", next=url_for(".favourites"))) proposals = [p for p in current_user.favourites if not p.hide_from_schedule] - externals = current_user.calendar_favourites token = generate_api_token(app.config["SECRET_KEY"], current_user.id) return render_template( "schedule/favourites.html", proposals=proposals, - externals=externals, token=token, ) diff --git a/apps/schedule/data.py b/apps/schedule/data.py index d313b0e38..2570ed517 100644 --- a/apps/schedule/data.py +++ b/apps/schedule/data.py @@ -6,7 +6,6 @@ from models import event_year from models.cfp import Proposal, Venue -from models.ical import CalendarSource from main import external_url from . import event_tz @@ -59,33 +58,6 @@ def _get_proposal_dict(proposal: Proposal, favourites_ids): return res -def _get_ical_dict(event, favourites_ids): - res = { - "id": -event.id, - "start_date": event_tz.localize(event.start_dt), - "end_date": event_tz.localize(event.end_dt), - "venue": event.location or "(Unknown)", - "latlon": event.latlon, - "map_link": event.map_link, - "title": event.summary, - "speaker": "", - "user_id": None, - "description": event.description, - "type": "talk", - "may_record": False, - "is_fave": event.id in favourites_ids, - "source": "external", - "link": external_url( - ".item_external", year=event_year(), slug=event.slug, event_id=event.id - ), - } - if event.type in ["workshop", "youthworkshop"]: - res["cost"] = event.display_cost - res["equipment"] = event.display_participant_equipment - res["age_range"] = event.display_age_range - return res - - def _filter_obj_to_dict(filter_obj): """Request.args uses a MulitDict this lets us pass filter_obj as plain dicts and have everything work as expected. @@ -103,10 +75,9 @@ def _get_scheduled_proposals(filter_obj={}, override_user=None): user = current_user if user.is_anonymous: - proposal_favourites = external_favourites = [] + proposal_favourites = [] else: proposal_favourites = [f.id for f in user.favourites] - external_favourites = [f.id for f in user.calendar_favourites] schedule = Proposal.query.filter( Proposal.is_accepted, @@ -118,14 +89,6 @@ def _get_scheduled_proposals(filter_obj={}, override_user=None): schedule = [_get_proposal_dict(p, proposal_favourites) for p in schedule] - ical_sources = CalendarSource.query.filter_by(enabled=True, published=True) - - for source in ical_sources: - for e in source.events: - d = _get_ical_dict(e, external_favourites) - d["venue"] = source.mapobj.name - schedule.append(d) - if "is_favourite" in filter_obj and filter_obj["is_favourite"]: schedule = [s for s in schedule if s.get("is_fave", False)] @@ -171,16 +134,9 @@ def _get_priority_sorted_venues(venues_to_allow): main_venues = Venue.query.filter().all() main_venue_names = [(v.name, "main", v.priority) for v in main_venues] - ical_sources = CalendarSource.query.filter_by(enabled=True, published=True) - ical_source_names = [ - (v.mapobj.name, "ical", v.priority) - for v in ical_sources - if v.mapobj and v.events - ] - res = [] seen_names = [] - for venue in main_venue_names + ical_source_names: + for venue in main_venue_names: name = venue[0] if name not in seen_names and name in venues_to_allow: seen_names.append(name) @@ -193,5 +149,5 @@ def _get_priority_sorted_venues(venues_to_allow): } ) - res = sorted(res, key=lambda v: (v["source"] != "ical", v["order"]), reverse=True) + res = sorted(res, key=lambda v: v["order"], reverse=True) return res diff --git a/apps/schedule/external.py b/apps/schedule/external.py deleted file mode 100644 index a9fa02afd..000000000 --- a/apps/schedule/external.py +++ /dev/null @@ -1,153 +0,0 @@ -""" Views for dealing with external schedules provided by villages.""" - -from wtforms import StringField, SubmitField, BooleanField -from wtforms.validators import DataRequired, URL -from flask_login import login_required, current_user -from flask import render_template, redirect, url_for, flash, request, abort - -from main import db -from models import event_year -from models.ical import CalendarSource, CalendarEvent - -from ..common.forms import Form -from ..common import feature_flag - -from . import schedule - - -@schedule.route("/schedule//external/", methods=["GET", "POST"]) -@schedule.route( - "/schedule//external/-", methods=["GET", "POST"] -) -@feature_flag("LINE_UP") -def item_external(year, event_id, slug=None): - if year != event_year(): - abort(404) - - event = CalendarEvent.query.get_or_404(event_id) - - if not current_user.is_anonymous: - is_fave = event in current_user.calendar_favourites - else: - is_fave = False - - if (request.method == "POST") and not current_user.is_anonymous: - if is_fave: - current_user.calendar_favourites.remove(event) - msg = 'Removed "%s" from favourites' % event.title - else: - current_user.calendar_favourites.append(event) - msg = 'Added "%s" to favourites' % event.title - db.session.commit() - flash(msg) - return redirect( - url_for(".item_external", year=year, event_id=event.id, slug=event.slug) - ) - - if slug != event.slug: - return redirect( - url_for(".item_external", year=year, event_id=event.id, slug=event.slug) - ) - - return render_template( - "schedule/item-external.html", - event=event, - is_fave=is_fave, - venue_name=event.venue, - ) - - -class AddExternalFeedForm(Form): - url = StringField("URL", [DataRequired(), URL()]) - preview = SubmitField("Preview") - - -@schedule.route("/schedule/external/feeds", methods=["GET", "POST"]) -def external_feeds_redirect(): - return redirect(url_for(".external_feeds")) - - -@schedule.route("/schedule/publish", methods=["GET", "POST"]) -@login_required -@feature_flag("LINE_UP") -def external_feeds(): - form = AddExternalFeedForm() - - if form.validate_on_submit(): - url = form.url.data.strip() - source = CalendarSource.query.filter_by( - user_id=current_user.id, url=url - ).one_or_none() - - if not source: - source = CalendarSource(url=url, user=current_user) - db.session.commit() - - return redirect(url_for(".external_feed", source_id=source.id)) - - calendars = current_user.calendar_sources - return render_template( - "schedule/external/feeds.html", form=form, calendars=calendars - ) - - -class UpdateExternalFeedForm(Form): - url = StringField("URL", [DataRequired(), URL()]) - name = StringField("Feed Name", [DataRequired()]) - published = BooleanField("Publish events from this feed") - preview = SubmitField("Preview") - save = SubmitField("Save") - - -@schedule.route("/schedule/publish/", methods=["GET", "POST"]) -@login_required -@feature_flag("LINE_UP") -def external_feed(source_id): - calendar = CalendarSource.query.get(source_id) - if calendar.user != current_user: - abort(403) - - form = UpdateExternalFeedForm(obj=calendar) - - if request.method != "POST": - if calendar.mapobj: - form.location.data = str(calendar.mapobj.id) - else: - form.location.data = "" - - if form.validate_on_submit(): - if form.save.data: - calendar.url = form.url.data - calendar.name = form.name.data - calendar.published = form.published.data - - try: - calendar.refresh() - except Exception: - pass - db.session.commit() - return redirect(url_for(".external_feeds")) - - calendar.url = form.url.data - - try: - alerts = calendar.refresh() - except Exception: - alerts = [("danger", "An error occurred trying to load the feed")] - preview_events = [] - else: - preview_events = list(calendar.events) - - if not preview_events: - alerts = [("danger", "We could not load any events from this feed")] - - db.session.rollback() - - return render_template( - "schedule/external/feed.html", - form=form, - calendar=calendar, - preview_events=preview_events, - alerts=alerts, - preview=True, - ) diff --git a/apps/schedule/tasks.py b/apps/schedule/tasks.py deleted file mode 100644 index b664e0ab9..000000000 --- a/apps/schedule/tasks.py +++ /dev/null @@ -1,72 +0,0 @@ -""" Schedule CLI tasks """ -import json -from collections import OrderedDict - -from flask import current_app as app - -from main import db -from models.ical import CalendarSource - -from . import schedule - - -@schedule.cli.command("create_calendars") -def create_calendars(self): - icals = json.load(open("calendars.json")) - - for cal in icals: - existing_calendar = CalendarSource.query.filter_by(url=cal["url"]).first() - if existing_calendar: - source = existing_calendar - app.logger.info("Refreshing calendar %s", source.name) - else: - source = CalendarSource(cal["url"]) - app.logger.info("Adding calendar %s", cal["name"]) - - cal["lat"] = cal.get("lat") - cal["lon"] = cal.get("lon") - - for f in ["name", "type", "priority", "main_venue", "lat", "lon"]: - cur_val = getattr(source, f) - new_val = cal[f] - - if cur_val != new_val: - app.logger.info(" %10s: %r -> %r", f, cur_val, new_val) - setattr(source, f, new_val) - - db.session.add(source) - - db.session.commit() - - -@schedule.cli.command("refresh_calendars") -def refresh_calendars(self): - for source in CalendarSource.query.filter_by(enabled=True).all(): - source.refresh() - - db.session.commit() - - -@schedule.cli.command("export_calendars") -def export_calendars(self): - data = [] - calendars = CalendarSource.query.filter_by(enabled=True).order_by( - CalendarSource.priority, CalendarSource.id - ) - for source in calendars: - source_data = OrderedDict( - [ - ("name", source.name), - ("url", source.url), - ("type", source.type), - ("priority", source.priority), - ("main_venue", source.main_venue), - ] - ) - if source.lat: - source_data["lat"] = source.lat - source_data["lon"] = source.lon - - data.append(source_data) - - json.dump(data, open("calendars.json", "w"), indent=4, separators=(",", ": ")) diff --git a/calendars.json b/calendars.json deleted file mode 100644 index bdc4764f3..000000000 --- a/calendars.json +++ /dev/null @@ -1,264 +0,0 @@ -[ - { - "name": "ISS", - "url": "http://jonty.co.uk/bits/ISS-Guildford.ics", - "type": "Space", - "priority": -10, - "main_venue": "The Sky" - }, - { - "name": "Sai's blind navigation crash course", - "url": "https://calendar.google.com/calendar/ical/0vtdvsar622kkghhbmn2gj1amg%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": -2, - "main_venue": "Contact @saizai" - }, - { - "name": "Radio Village", - "url": "https://calendar.google.com/calendar/ical/lhifjvb20rqasl45up83uug6v4%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": -1, - "main_venue": "Radio Village", - "lat": 51.2125, - "lon": -0.60941 - }, - { - "name": "EMF Music", - "url": "https://calendar.google.com/calendar/ical/3s832k79jjbrl9o9e8ifmgflhg%40group.calendar.google.com/public/basic.ics", - "type": "Music", - "priority": 0, - "main_venue": "Stage A", - "lat": 51.21191, - "lon": -0.6068 - }, - { - "name": "EMF Film", - "url": "http://p04-calendars.icloud.com/published/2/iSu19GpxhFD47NHBtEuNRQWpt0qew4aaj3FVRj-Fjglz2QMdy6opkoZBTTTvPN_cxUU7ZxlBg8ZoTELIBiyjyenVNi82c_10agpwUpaXJro", - "type": "Film", - "priority": 0, - "main_venue": "Stage B", - "lat": 51.21304, - "lon": -0.60838 - }, - { - "name": "Maths Village", - "url": "http://www.mscroggs.co.uk/emfcal", - "type": "Village", - "priority": 0, - "main_venue": "Maths Village", - "lat": 51.21211, - "lon": -0.60869 - }, - { - "name": "Hacking Hamlet", - "url": "https://calendar.google.com/calendar/ical/nvs1s1r6pmvf7v4d82349ns9u4%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 0, - "main_venue": "Hacking Hamlet", - "lat": 51.212925, - "lon": -0.60971 - }, - { - "name": "Nottinghack", - "url": "https://calendar.google.com/calendar/ical/o8apcgfbmjkkv0uk5fos4tuc3o%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 0, - "main_venue": "Nottinghack Village", - "lat": 51.212692, - "lon": -0.607659 - }, - { - "name": "HABville Village", - "url": "https://calendar.google.com/calendar/ical/hp2b34jg53gghdpn4e6evg5c34%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 0, - "main_venue": "HABville Village", - "lat": 51.21202, - "lon": -0.60861 - }, - { - "name": "Haxors of the Roses", - "url": "https://calendar.google.com/calendar/ical/bowerham.net_8qqgj22h6pvgubuloa6uv6de14%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 0, - "main_venue": "Haxors of the Roses Village", - "lat": 51.212831, - "lon": -0.607225 - }, - { - "name": "Microsoft", - "url": "https://calendar.google.com/calendar/ical/msftemfcamp%40gmail.com/public/basic.ics", - "type": "Village", - "priority": 0, - "main_venue": "Make. Invent. Do. Village", - "lat": 51.21222, - "lon": -0.607215 - }, - { - "name": "Never Too Much Bunting", - "url": "https://calendar.google.com/calendar/ical/kp6kvl468ijk9hsmva85v4gl88%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 0, - "main_venue": "Never Too Much Bunting Village", - "lat": 51.21275, - "lon": -0.60973 - }, - { - "name": "Fablab Truck", - "url": "http://jonty.co.uk/bits/Fablab_camp.ics", - "type": "Village", - "priority": 0, - "main_venue": "Fablab Truck", - "lat": 51.21215, - "lon": -0.60671 - }, - { - "name": "Millers Hollow", - "url": "https://calendar.google.com/calendar/ical/1adatrf0m3plpfv8gh47mrk7ko%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 0, - "main_venue": "Millers Hollow Village", - "lat": 51.21297, - "lon": -0.60874 - }, - { - "name": "Scottish Consulate", - "url": "https://calendar.google.com/calendar/ical/ebms9h8dn8mcthk3k0i2cttjsg%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 0, - "main_venue": "Scottish Consulate Village", - "lat": 51.21253, - "lon": -0.60887 - }, - { - "name": "Minecraft Village", - "url": "https://calendar.google.com/calendar/ical/kr1iudroe96pnq2l18hnghvp38%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 0, - "main_venue": "Minecraft Village", - "lat": 51.21249, - "lon": -0.60822 - }, - { - "name": "Makespace Village", - "url": "https://calendar.google.com/calendar/ical/1esdfs4rg0oq0du82jvi3alaec%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 0, - "main_venue": "Makespace Village", - "lat": 51.21229, - "lon": -0.60764 - }, - { - "name": "Games", - "url": "https://calendar.google.com/calendar/ical/gueemqqifr9tup0lsfplnu2pcc%40group.calendar.google.com/public/basic.ics", - "type": "Performance", - "priority": 0, - "main_venue": "Stage C", - "lat": 51.21214, - "lon": -0.60757 - }, - { - "name": "HABville Village 2", - "url": "https://calendar.google.com/calendar/ical/21i3eglok30ei233bj3iter1b0%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 0, - "main_venue": "HABville Village" - }, - { - "name": "Blacksmiths", - "url": "https://calendar.google.com/calendar/ical/i1fa3r5jokroruadgp15aqfo5c%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 10, - "main_venue": "Blacksmiths", - "lat": 51.21158, - "lon": -0.60814 - }, - { - "name": "SawuGo, The One Ring Workshop", - "url": "https://calendar.google.com/calendar/ical/gum1il3s07gvpv42l22ed32bmg%40group.calendar.google.com/private-c0607d549af2324117298b0b04f02cb4/basic.ics", - "type": "Village", - "priority": 10, - "main_venue": "SawuGo, The One Ring Workshop", - "lat": 51.212, - "lon": -0.60851 - }, - { - "name": "Empty Epsilon", - "url": "http://jonty.co.uk/bits/EmptyEpsilon_spacheship_simulator.ics", - "type": "Village", - "priority": 10, - "main_venue": "Empty Epsilon Village", - "lat": 51.21302, - "lon": -0.60875 - }, - { - "name": "Laser Tag", - "url": "https://outlook.office365.com/owa/calendar/6da5d9aa319d42c3ab887f1af52bc239@serviceinmotion.co.uk/87751db86eed4989a2531c3a59f10ed516309432096381974961/calendar.ics", - "type": "Activity", - "priority": 12, - "main_venue": "Up by the EMF sign", - "lat": 51.21469, - "lon": -0.60864 - }, - { - "name": "Nature Chamber", - "url": "https://calendar.google.com/calendar/ical/l27kb7id5cpsqjknoq5tf5i3bg%40group.calendar.google.com/public/basic.ics", - "type": "Workshop", - "priority": 15, - "main_venue": "The Lounge" - }, - { - "name": "Algorave", - "url": "https://calendar.google.com/calendar/ical/slab.org_77m028f92l9guddfbhrrtghj2g%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 20, - "main_venue": "Algorave Village", - "lat": 51.21133, - "lon": -0.6075 - }, - { - "name": "Badge Operations Center", - "url": "https://calendar.google.com/calendar/ical/bowerham.net_47cgvma5a74hs7fnlashjc0gpg%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 20, - "main_venue": "Badge Operations Center", - "lat": 51.21174, - "lon": -0.60775 - }, - { - "name": "UAV Flying Field", - "url": "https://calendar.google.com/calendar/ical/e6t2qrog0t6343bf1gebnh2jl8%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 20, - "main_venue": "UAV Flying Field", - "lat": 51.21001, - "lon": -0.60738 - }, - { - "name": "Portcullis", - "url": "https://calendar.google.com/calendar/ical/iojq4b3ctp3kegomjogac2a4gc%40group.calendar.google.com/public/basic.ics", - "type": "Village", - "priority": 21, - "main_venue": "Portcullis Village", - "lat": 51.21237, - "lon": -0.60856 - }, - { - "name": "Hack Center", - "url": "https://calendar.google.com/calendar/ical/ublklsm8g031s6mtjv8ds08k4k%40group.calendar.google.com/private-63dd14bde661eb38b31c53a73f8a3bec/basic.ics", - "type": "Village", - "priority": 30, - "main_venue": "Hack Center", - "lat": 51.21121, - "lon": -0.60681 - }, - { - "name": "EMF Youth", - "url": "https://calendar.google.com/calendar/ical/5nkm5d7nahs9bcgn4q1btjg3c4%40group.calendar.google.com/public/basic.ics", - "type": "Youth", - "priority": 95, - "main_venue": "Workshop 3", - "lat": 51.21331, - "lon": -0.60804 - } -] \ No newline at end of file diff --git a/js/line-up.js b/js/line-up.js index d04bd10d3..9cc697f6b 100644 --- a/js/line-up.js +++ b/js/line-up.js @@ -2,12 +2,8 @@ $(function() { $('.favourite-button').click(async (event) => { event.preventDefault(); const btn = event.target.closest('.favourite-button'); - let event_type = 'proposal'; - if (btn.closest('.event')?.classList?.contains('external')) { - event_type = 'external'; - } const event_id = btn.value; - const response = await fetch(`/api/${event_type}/${event_id}/favourite`, { + const response = await fetch(`/api/proposal/${event_id}/favourite`, { method: 'PUT', credentials: 'include', headers: { diff --git a/js/schedule/App.jsx b/js/schedule/App.jsx index 41531b506..03a78c773 100644 --- a/js/schedule/App.jsx +++ b/js/schedule/App.jsx @@ -76,8 +76,7 @@ function App() { }); function toggleFavourite(event) { - let endpoint = event.source === 'database' ? `/api/proposal/${event.id}/favourite` : `/api/external/${Math.abs(event.id)}/favourite`; - fetch(endpoint, { headers: { 'Authorization': apiToken, 'Content-Type': 'application/json' }, method: 'put', body: '{}' }) + fetch(`/api/proposal/${event.id}/favourite`, { headers: { 'Authorization': apiToken, 'Content-Type': 'application/json' }, method: 'put', body: '{}' }) .then((response) => response.json()) .then((data) => { let schedule = JSON.parse(JSON.stringify(rawSchedule)) diff --git a/migrations/versions/bdcf93945e0c_remove_calendarsource_and_external_.py b/migrations/versions/bdcf93945e0c_remove_calendarsource_and_external_.py new file mode 100644 index 000000000..3c424ee73 --- /dev/null +++ b/migrations/versions/bdcf93945e0c_remove_calendarsource_and_external_.py @@ -0,0 +1,69 @@ +"""Remove CalendarSource and external events + +Revision ID: bdcf93945e0c +Revises: 09f776ea71f0 +Create Date: 2024-05-22 23:57:09.873592 + +""" + +# revision identifiers, used by Alembic. +revision = 'bdcf93945e0c' +down_revision = '09f776ea71f0' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index('ix_calendar_source_user_id', table_name='calendar_source') + op.drop_table('calendar_source') + op.drop_index('ix_calendar_event_source_id', table_name='calendar_event') + op.drop_table('calendar_event') + op.drop_table('favourite_calendar_event') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('favourite_calendar_event', + sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('event_id', sa.INTEGER(), autoincrement=False, nullable=False), + sa.ForeignKeyConstraint(['event_id'], ['calendar_event.id'], name='fk_favourite_calendar_event_event_id_calendar_event'), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], name='fk_favourite_calendar_event_user_id_user'), + sa.PrimaryKeyConstraint('user_id', 'event_id', name='pk_favourite_calendar_event') + ) + op.create_table('calendar_event', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('uid', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('start_dt', postgresql.TIMESTAMP(), autoincrement=False, nullable=False), + sa.Column('end_dt', postgresql.TIMESTAMP(), autoincrement=False, nullable=False), + sa.Column('source_id', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('summary', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('description', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('location', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('displayed', sa.BOOLEAN(), autoincrement=False, nullable=False), + sa.ForeignKeyConstraint(['source_id'], ['calendar_source.id'], name='fk_calendar_event_source_id_calendar_source'), + sa.PrimaryKeyConstraint('id', name='pk_calendar_event'), + sa.UniqueConstraint('source_id', 'uid', name='uq_calendar_event_source_id') + ) + op.create_index('ix_calendar_event_source_id', 'calendar_event', ['source_id'], unique=False) + op.create_table('calendar_source', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('url', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('enabled', sa.BOOLEAN(), autoincrement=False, nullable=False), + sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('type', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('main_venue', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('contact_phone', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('contact_email', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('priority', sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column('published', sa.BOOLEAN(), autoincrement=False, nullable=False), + sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column('displayed', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False), + sa.Column('refreshed_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], name='fk_calendar_source_user_id_user'), + sa.PrimaryKeyConstraint('id', name='pk_calendar_source') + ) + op.create_index('ix_calendar_source_user_id', 'calendar_source', ['user_id'], unique=False) + # ### end Alembic commands ### diff --git a/models/__init__.py b/models/__init__.py index ec34a1756..0d6aa597d 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -187,7 +187,6 @@ def config_date(key): from .cfp import * # noqa: F401,F403 from .permission import * # noqa: F401,F403 from .email import * # noqa: F401,F403 -from .ical import * # noqa: F401,F403 from .product import * # noqa: F401,F403 from .purchase import * # noqa: F401,F403 from .basket import * # noqa: F401,F403 diff --git a/models/ical.py b/models/ical.py deleted file mode 100644 index 8adc17d6a..000000000 --- a/models/ical.py +++ /dev/null @@ -1,293 +0,0 @@ -import requests -import re - -from geoalchemy2.shape import to_shape -from icalendar import Calendar -import pendulum -from shapely.geometry import Point -from slugify import slugify_unicode -from sqlalchemy import UniqueConstraint, func, select -from sqlalchemy.orm import column_property -from sqlalchemy.orm.exc import NoResultFound - -from main import db -from . import BaseModel, event_start, event_end - - -class CalendarSource(BaseModel): - __tablename__ = "calendar_source" - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey("user.id"), index=True) - url = db.Column(db.String, nullable=False) - name = db.Column(db.String) - type = db.Column(db.String, default="Village") - priority = db.Column(db.Integer, default=0) - enabled = db.Column(db.Boolean, nullable=False, default=False) - refreshed_at = db.Column(db.DateTime()) - - displayed = db.Column(db.Boolean, nullable=False, default=False) - published = db.Column(db.Boolean, nullable=False, default=False) - main_venue = db.Column(db.String) - contact_phone = db.Column(db.String) - contact_email = db.Column(db.String) - - user = db.relationship("User", backref="calendar_sources") - - # Make sure these are identifiable to the memoize cache - def __repr__(self): - return "<%s %s: %s>" % (self.__class__.__name__, self.id, self.url) - - @classmethod - def get_export_data(cls): - sources = cls.query.with_entities( - cls.id, - cls.name, - cls.type, - cls.enabled, - cls.url, - cls.main_venue, - cls.priority, - ).order_by(cls.id) - - data = {"public": {"sources": sources}, "tables": ["calendar_source"]} - - return data - - def refresh(self): - request = requests.get(self.url) - - cal = Calendar.from_ical(request.text) - if self.name is None: - self.name = cal.get("X-WR-CALNAME") - - for event in self.events: - event.displayed = False - - local_tz = pendulum.timezone("Europe/London") - alerts = [] - uids_seen = set() - out_of_range_event = False - for component in cal.walk(): - if component.name == "VEVENT": - summary = component.get("Summary") - - # postgres converts to UTC if given an aware datetime, so strip it up front - start_dt = pendulum.instance(component.get("dtstart").dt) - start_dt = local_tz.convert(start_dt).naive() - - end_dt = pendulum.instance(component.get("dtend").dt) - end_dt = local_tz.convert(end_dt).naive() - - name = summary - if summary and start_dt: - name = "'{}' at {}".format(summary, start_dt) - elif summary: - name = "'{}'".format(summary) - elif start_dt: - name = "Event at {}".format(start_dt) - else: - name = len(self.events) + 1 - - if not component.get("uid"): - alerts.append(("danger", "{} has no UID".format(name))) - continue - - uid = str(component["uid"]) - if uid in uids_seen: - alerts.append( - ("danger", "{} has duplicate UID {}".format(name, uid)) - ) - continue - uids_seen.add(uid) - - if "rrule" in component: - alerts.append( - ("warning", "{} has rrule, which is not processed".format(uid)) - ) - - # Allow a bit of slop for build-up events - if ( - start_dt < event_start() - pendulum.duration(days=2) - and not out_of_range_event - ): - alerts.append( - ( - "warning", - "At least one event ({}) is before the start of the event".format( - uid - ), - ) - ) - out_of_range_event = True - - if ( - end_dt > event_end() + pendulum.duration(days=1) - and not out_of_range_event - ): - alerts.append( - ( - "warning", - "At least one event ({}) is after the end of the event".format( - uid - ), - ) - ) - out_of_range_event = True - - if start_dt > end_dt: - alerts.append( - ( - "danger", - "Start time for {} is after its end time".format(uid), - ) - ) - out_of_range_event = True - - try: - event = CalendarEvent.query.filter_by( - source_id=self.id, uid=uid - ).one() - - except NoResultFound: - event = CalendarEvent(uid=uid) - self.events.append(event) - if len(self.events) > 1000: - raise Exception("Too many events in feed") - - event.start_dt = start_dt - event.end_dt = end_dt - event.summary = component.get("summary") - event.description = component.get("description") - event.location = component.get("location") - event.displayed = True - - self.refreshed_at = pendulum.now() - - return alerts - - @property - def latlon(self): - if self.mapobj: - obj = to_shape(self.mapobj.geom) - if isinstance(obj, Point): - return (obj.y, obj.x) - return None - - @classmethod - def get_enabled_events(self): - sources = CalendarSource.query.filter_by(published=True, displayed=True) - events = [] - for source in sources: - events.extend(source.events) - return events - - -FavouriteCalendarEvent = db.Table( - "favourite_calendar_event", - BaseModel.metadata, - db.Column("user_id", db.Integer, db.ForeignKey("user.id"), primary_key=True), - db.Column( - "event_id", db.Integer, db.ForeignKey("calendar_event.id"), primary_key=True - ), -) - - -class CalendarEvent(BaseModel): - __tablename__ = "calendar_event" - - id = db.Column(db.Integer, primary_key=True) - uid = db.Column(db.String) - start_dt = db.Column(db.DateTime(), nullable=False) - end_dt = db.Column(db.DateTime(), nullable=False) - displayed = db.Column(db.Boolean, nullable=False, default=True) - - source_id = db.Column( - db.Integer, db.ForeignKey(CalendarSource.id), nullable=False, index=True - ) - summary = db.Column(db.String, nullable=True) - description = db.Column(db.String, nullable=True) - location = db.Column(db.String, nullable=True) - - source = db.relationship(CalendarSource, backref="events") - calendar_favourites = db.relationship( - "User", secondary=FavouriteCalendarEvent, backref="calendar_favourites" - ) - - favourite_count = column_property( - select([func.count(FavouriteCalendarEvent.c.user_id)]) - .where(FavouriteCalendarEvent.c.user_id == id) - .scalar_subquery(), # type: ignore[attr-defined] - deferred=True, - ) - - @classmethod - def get_export_data(cls): - events = cls.query.with_entities( - cls.source_id, - cls.uid, - cls.start_dt, - cls.end_dt, - cls.summary, - cls.description, - cls.location, - cls.favourite_count, - ).order_by(cls.source_id, cls.id) - - data = { - "public": {"events": events}, - "tables": ["calendar_event", "favourite_calendar_event"], - } - - return data - - @property - def title(self): - return self.summary - - @property - def venue(self): - if self.source.main_venue: - return self.source.main_venue - else: - return self.location - - @property - def type(self): - return self.source.type - - @property - def slug(self): - slug = slugify_unicode(self.summary).lower() - if len(slug) > 60: - words = re.split(" +|[,.;:!?]+", self.summary) - break_words = ["and", "which", "with", "without", "for", "-", ""] - - for i, word in reversed(list(enumerate(words))): - new_slug = slugify_unicode(" ".join(words[:i])).lower() - if word in break_words: - if len(new_slug) > 10 and not len(new_slug) > 60: - slug = new_slug - break - - elif len(slug) > 60 and len(new_slug) > 10: - slug = new_slug - - if len(slug) > 60: - slug = slug[:60] + "-" - - return slug - - @property - def latlon(self): - if self.source.latlon: - return self.source.latlon - return None - - @property - def map_link(self): - latlon = self.latlon - if latlon: - return "https://map.emfcamp.org/#20/%s/%s" % (latlon[0], latlon[1]) - return None - - __table_args__ = (UniqueConstraint(source_id, uid),) diff --git a/models/user.py b/models/user.py index c9b488e97..3ec9da742 100644 --- a/models/user.py +++ b/models/user.py @@ -188,7 +188,7 @@ def verify_checkin_code(key, uid): class User(BaseModel, UserMixin): __tablename__ = "user" - __versioned__ = {"exclude": ["favourites", "calendar_favourites"]} + __versioned__ = {"exclude": ["favourites"]} id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String, unique=True, index=True) diff --git a/templates/admin/edit-feed.html b/templates/admin/edit-feed.html deleted file mode 100644 index 045ba1dda..000000000 --- a/templates/admin/edit-feed.html +++ /dev/null @@ -1,39 +0,0 @@ -{% from "_formhelpers.html" import render_field %} -{% from "schedule/_external_lister.html" import list_externals with context %} -{% extends "admin/base.html" %} -{% set nav_active = 'schedule-feeds' %} -{% block title %} -{%- if feed -%} - Edit Feed - {{ feed.name }} -{%- else -%} - New Feed -{%- endif -%} -{% endblock %} -{% block body %} -{% if feed %} -

Edit Feed - {{ feed.name }}

-{% else %} -

New Feed

-{% endif %} - -

Created by {{feed.user.name}}

- -
- {{ form.hidden_tag() }} - - {{ render_field(form.feed_name, true) }} - {{ render_field(form.url, true) }} - {{ render_field(form.location, true) }} - {{ render_field(form.priority, true) }} - {{ render_field(form.enabled, true) }} - {{ render_field(form.published, true) }} - - {{ form.submit(class_="btn btn-success") }} - {{ form.delete(class_="btn btn-danger") }} - Back -
- -

Feed events

-{{ list_externals(feed.events) }} - -{% endblock %} diff --git a/templates/admin/schedule-feeds.html b/templates/admin/schedule-feeds.html deleted file mode 100644 index ddf320eaf..000000000 --- a/templates/admin/schedule-feeds.html +++ /dev/null @@ -1,33 +0,0 @@ -{% extends "admin/base.html" %} -{% set nav_active = 'schedule-feeds' %} -{% block body %} -

iCal feeds

-

- These are external feeds to use in the schedule. -

- - - - - - - - - - - - - - {% for f in feeds %} - - - - - - - - - {% endfor %} - -
IdNameVenueEmailPublishedEnabled
{{ f.id }}{{ f.name }}{{ (f.mapobj and f.mapobj.name) or '' }}{{ f.user.email }}{{ f.published }}{{ f.enabled }}
-{% endblock %} diff --git a/templates/schedule/_external_lister.html b/templates/schedule/_external_lister.html deleted file mode 100644 index eb0c539d1..000000000 --- a/templates/schedule/_external_lister.html +++ /dev/null @@ -1,51 +0,0 @@ -{% from "schedule/_faves.html" import favourite_icons %} -{% macro list_externals(events) %} - {% for type, type_events in events | groupby('type') %} - {#

{{ type | title }}

#} -
- -
- {% for event in type_events | sort(attribute='start_dt') %} - {% if event.id == None %} - - {% else %} - - {% endif %} -
-
-
- {{ event.start_dt.strftime('%A') }} - {% if event.start_dt < event_start or event.end_dt > event_end %} - {{ event.start_dt.strftime('%-d %B %Y') }} - {% endif %} - {{ event.start_dt.strftime('%H:%M') }}– - {%- if event.start_dt.day == event.end_dt.day %} - {{- event.end_dt.strftime('%H:%M') }} - {%- else %} - {{- event.end_dt.strftime('%A') }} - {% if event.start_dt < event_start or event.end_dt > event_end %} - {{ event.start_dt.strftime('%-d %B %Y') }} - {% endif %} - {{- event.end_dt.strftime('%A %H:%M') }} - {% endif %} - -
-
- {{ event.title }} -
-
- {% if current_user.is_authenticated %} -
- -
- {% endif %} -
-
- {% endfor %} -
-
- {% endfor %} -{% endmacro %} diff --git a/templates/schedule/external/feed.html b/templates/schedule/external/feed.html deleted file mode 100644 index 883fa46be..000000000 --- a/templates/schedule/external/feed.html +++ /dev/null @@ -1,98 +0,0 @@ -{% from "_formhelpers.html" import render_field %} -{% from "schedule/_external_lister.html" import list_externals with context %} - -{% extends "base.html" %} -{% block title %}Your Feeds{% endblock %} -{% block body %} - - - -{% if calendar.name %} -

{{ calendar.name }}

-{% endif %} - -{% if preview_events and preview %} -

We've loaded your feed below. If something doesn't look right, email {{ config['VILLAGECONTENT_EMAIL'][1] }}.

-

Note that if you're using Google Calendar, there can be a significant delay for changes to show up.

-{% elif alerts %} -

We encountered problems loading your feed. We also weren't able to find any events.

-{% else %} -

We successfully processed your feed, but we weren't able to find any events.

-{% endif %} - -{% if calendar.enabled and calendar.published %} - {% if calendar.refreshed_at %} -

Your feed was last refreshed at {{ calendar.refreshed_at.strftime('%Y-%m-%d %H:%M') }}. - {% else %} -

Your feed has been published.

- {% endif %} -{% elif calendar.published %} -

We are checking your feed and it will be published soon.

-{% endif %} - -
-
-
-
- {{ form.hidden_tag() }} - {{ render_field(form.url, horizontal=10) }} - {{ render_field(form.name, horizontal=10) }} - {{ render_field(form.location, horizontal=10) }} -

- Add a location -

-
-
- {{ form.published(class_='big-checkbox') }} - -
-
-
- {{ form.save(class_="btn btn-success debounce") }} - {{ form.preview(class_="btn btn-default debounce") }} -
-
-
-
-
- -{% if alerts %} -
-
-

Problems encountered

-
-
- {% for type, message in alerts %} -
{{ message }}
- {% endfor -%} -
-
-{% endif %} - -{% if calendars %} -
-
-
-
- {{ form.hidden_tag() }} - {{ render_field(form.url, horizontal=11) }} - {{ form.preview(class_="btn btn-primary debounce pull-right") }} -
-
-
-
-{% endif %} - -{% if preview_events %} -
-{{ list_externals(preview_events) }} -
-{% endif %} - -{% endblock %} diff --git a/templates/schedule/external/feeds.html b/templates/schedule/external/feeds.html deleted file mode 100644 index 4e30d997c..000000000 --- a/templates/schedule/external/feeds.html +++ /dev/null @@ -1,40 +0,0 @@ -{% from "_formhelpers.html" import render_field %} -{% extends "base.html" %} -{% block title %}Your Feeds{% endblock %} -{% block body %} - -

Your event feeds

-

If you want to publish events into the official EMF schedule you can import them here. It's fine to use this for both village content and events you're running all over the site.

-

Schedules should be published in iCal format and will be updated every ten minutes. If you're not sure what this means, there's an example in the documentation for the EMF official API here. - -

-
-
-
- {{ form.hidden_tag() }} -

Please enter the URL of an iCal feed you'd like to import

- {{ render_field(form.url, horizontal=11) }} - {{ form.preview(class_="btn btn-primary debounce pull-right") }} -

-
-
-
- -{% if calendars %} -

Existing iCal feeds

-{% for calendar in calendars %} -
    -
  • -
    - -
    - {{ calendar.events|length }} events {%- if calendar.refreshed_at %}, last refreshed at {{ calendar.refreshed_at.strftime('%Y-%m-%d %H:%M') }}{% endif %} -
    -
    -
  • -
-{% endfor %} -{% endif %} -{% endblock %} diff --git a/templates/schedule/favourites.html b/templates/schedule/favourites.html index 2a7116177..334638b96 100644 --- a/templates/schedule/favourites.html +++ b/templates/schedule/favourites.html @@ -1,5 +1,4 @@ {% from "schedule/_proposal_lister.html" import list_proposals with context %} -{% from "schedule/_external_lister.html" import list_externals with context %} {% extends "base.html" %} {% block title %}Favourites{% endblock %} {% block body %} @@ -16,12 +15,8 @@

Favourites

{{ list_proposals(proposals) }}
-
-

Other Events

-{{ list_externals(externals) }} -
-{% if proposals | count == 0 and externals | count == 0 %} +{% if proposals | count == 0 %}

You currently don't have any favourites, add some by going to our line-up, clicking anything diff --git a/templates/schedule/item-external.html b/templates/schedule/item-external.html deleted file mode 100644 index 6ca7e230b..000000000 --- a/templates/schedule/item-external.html +++ /dev/null @@ -1,68 +0,0 @@ -{% extends "base.html" %} -{% block title %}{{ event.title }}{% endblock %} -{% block body %} - -

{{ event.title }}

- -

- - {{ event.start_dt.strftime('%A') }} - {% if event.start_dt < event_start or event.end_dt > event_end %} - {{ event.start_dt.strftime('%-d %B %Y') }} - {% endif %} - {{ event.start_dt.strftime('%H:%M') }}– - {%- if event.start_dt.day == event.end_dt.day %} - {{- event.end_dt.strftime('%H:%M') }} - {%- else %} - {{- event.end_dt.strftime('%A') }} - {% if event.start_dt < event_start or event.end_dt > event_end %} - {{ event.start_dt.strftime('%-d %B %Y') }} - {% endif %} - {{- event.end_dt.strftime('%A %H:%M') }} - {% endif %} - in - {% if event.map_link %} - {{ venue_name }} - {% else %} - {{ venue_name }} - {% endif %} - -

- -
-

{{ event.description | urlize }}

-
-{% if current_user.is_authenticated %} -
- -
-{% else %} -

If you would like to mark this as a favourite please - log in. -

-{% endif %} -
- -

Schedule

- -{% endblock %} - -{% block foot %} - -{% endblock %} diff --git a/templates/schedule/line-up.html b/templates/schedule/line-up.html index 967c5913f..80f6032c4 100644 --- a/templates/schedule/line-up.html +++ b/templates/schedule/line-up.html @@ -1,5 +1,4 @@ {% from "schedule/_proposal_lister.html" import list_proposals with context %} -{% from "schedule/_external_lister.html" import list_externals with context %} {% extends "base.html" %} {% block title %}Line-up{% endblock %} @@ -34,13 +33,6 @@

Line-up

{{ list_proposals(proposals) }} -{% if externals|length > 0 %} -
-

Other Events

-{{ list_externals(externals) }} -
-{% endif %} - {% endblock %} {% block foot %}