def add_favourite(): if not current_user.is_authenticated: abort(401) 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)) 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) db.session.commit() return redirect( url_for(".main_year", year=event_year()) + "#event-{}".format(event.id))
def make_root(): root = etree.Element("schedule") _add_sub_with_text(root, "version", "1.0-public") conference = etree.SubElement(root, "conference") _add_sub_with_text(conference, "title", "Electromagnetic Field {}".format(event_year())) _add_sub_with_text(conference, "acronym", "emf{}".format(event_year())) _add_sub_with_text(conference, "start", event_start().strftime("%Y-%m-%d")) _add_sub_with_text(conference, "end", event_end().strftime("%Y-%m-%d")) _add_sub_with_text(conference, "days", "3") _add_sub_with_text(conference, "timeslot_duration", "00:10") return root
def favourites_ical(): code = request.args.get("token", None) user = None if code: user = User.get_by_api_token(app.config.get("SECRET_KEY"), str(code)) if not current_user.is_anonymous: user = current_user if not user: abort(404) schedule = _get_scheduled_proposals(request.args, override_user=user) title = "EMF {} Favourites for {}".format(event_year(), user.name) cal = Calendar() cal.add("summary", title) cal.add("X-WR-CALNAME", title) cal.add("X-WR-CALDESC", title) cal.add("version", "2.0") for event in schedule: if not event["is_fave"]: continue cal_event = Event() cal_event.add("uid", event["id"]) cal_event.add("summary", event["title"]) cal_event.add("description", _format_event_description(event)) cal_event.add("location", event["venue"]) cal_event.add("dtstart", event["start_date"]) cal_event.add("dtend", event["end_date"]) cal.add_component(cal_event) return Response(cal.to_ical(), mimetype="text/calendar")
def attach_tickets(msg, user): # Attach tickets to a mail Message page = render_receipt(user, pdf=True) url = external_url("tickets.receipt", user_id=user.id) pdf = render_pdf(url, page) msg.attach("EMF{}.pdf".format(event_year()), pdf.read(), "application/pdf")
def get_auth_tag(): tag = "emf-{}-".format(event_year()) msg = b"bar-training-" + tag.encode("utf-8") tag += hmac.new( app.config["SECRET_KEY"].encode("utf-8"), msg, digestmod=sha256 ).hexdigest() return tag
def schedule_json(year): if year != event_year(): return feed_historic(year, "json") schedule = [ _convert_time_to_str(p) for p in _get_scheduled_proposals(request.args) ] # NB this is JSON in a top-level array (security issue for low-end browsers) return Response(json.dumps(schedule), mimetype="application/json")
def schedule_current(): token = None if current_user.is_authenticated: token = generate_api_token(app.config["SECRET_KEY"], current_user.id) return render_template( "schedule/user_schedule.html", token=token, debug=app.config.get("DEBUG"), year=event_year(), )
def _get_proposal_dict(proposal: Proposal, favourites_ids): res = { "id": proposal.id, "slug": proposal.slug, "start_date": event_tz.localize(proposal.start_date), "end_date": event_tz.localize(proposal.end_date) if proposal.end_date else None, "venue": proposal.scheduled_venue.name, "latlon": proposal.latlon, "map_link": proposal.map_link, "title": proposal.display_title, "speaker": proposal.published_names or proposal.user.name, "pronouns": proposal.published_pronouns, "user_id": proposal.user.id, "description": proposal.published_description or proposal.description, "type": proposal.type, "may_record": proposal.may_record, "is_fave": proposal.id in favourites_ids, "is_family_friendly": proposal.family_friendly, "is_from_cfp": not proposal.user_scheduled, "content_note": proposal.content_note, "source": "database", "link": external_url( ".item", year=event_year(), proposal_id=proposal.id, slug=proposal.slug, ), } if proposal.type in ["workshop", "youthworkshop"]: res["cost"] = proposal.display_cost res["equipment"] = proposal.display_participant_equipment res["age_range"] = proposal.display_age_range res["attendees"] = proposal.attendees return res
def feed_redirect(fmt): routes = { "json": "schedule.schedule_json", "frab": "schedule.schedule_frab", "ical": "schedule.schedule_ical", "ics": "schedule.schedule_ical", } if fmt not in routes: abort(404) return redirect(url_for(routes[fmt], year=event_year()))
def register(): if current_user.village and current_user.village.admin: return redirect( url_for(".edit", year=event_year(), village_id=current_user.village.village.id)) form = VillageForm() if form.validate_on_submit(): if Village.get_by_name(form.name.data): flash( "A village already exists with that name, please choose another" ) return redirect(url_for(".register")) village = Village(name=form.name.data, description=form.description.data) member = VillageMember(village=village, user=current_user, admin=True) requirements = VillageRequirements( village=village, num_attendees=form.num_attendees.data, size_sqm=form.size_sqm.data, power_requirements=form.power_requirements.data, noise=form.noise.data, structures=form.structures.data, ) db.session.add(village) db.session.add(member) db.session.add(requirements) db.session.commit() flash( "Your village registration has been received, thanks! You can edit it here." ) return redirect( url_for(".edit", year=event_year(), village_id=village.id)) return render_template("villages/register.html", form=form)
def load_village(year, village_id, require_admin=False): """ Helper to return village or 404 """ if year != event_year(): abort(404) village = Village.get_by_id(village_id) if not village: abort(404) if require_admin and not (current_user.village.village == village and current_user.village.admin): abort(404) return village
def add_event(room, event): url = external_url("schedule.item", year=event_year(), proposal_id=event["id"], slug=event["slug"]) event_node = etree.SubElement(room, "event", id=str(event["id"]), guid=str(uuid5(NAMESPACE_URL, url))) _add_sub_with_text(event_node, "room", room.attrib["name"]) _add_sub_with_text(event_node, "title", event["title"]) _add_sub_with_text(event_node, "type", event.get("type", "talk")) _add_sub_with_text(event_node, "date", event["start_date"].isoformat()) # Start time _add_sub_with_text(event_node, "start", event["start_date"].strftime("%H:%M")) duration = get_duration(event["start_date"], event["end_date"]) _add_sub_with_text(event_node, "duration", duration) _add_sub_with_text(event_node, "abstract", event["description"]) _add_sub_with_text(event_node, "description", "") _add_sub_with_text( event_node, "slug", "emf%s-%s-%s" % (event_year(), event["id"], event["slug"]), ) _add_sub_with_text(event_node, "subtitle", "") _add_sub_with_text(event_node, "track", "") add_persons(event_node, event) add_recording(event_node, event)
def schedule_ical(year): if year != event_year(): return feed_historic(year, "ics") schedule = _get_scheduled_proposals(request.args) title = "EMF {}".format(event_year()) cal = Calendar() cal.add("summary", title) cal.add("X-WR-CALNAME", title) cal.add("X-WR-CALDESC", title) cal.add("version", "2.0") for event in schedule: cal_event = Event() cal_event.add("uid", event["id"]) cal_event.add("summary", event["title"]) cal_event.add("description", _format_event_description(event)) cal_event.add("location", event["venue"]) cal_event.add("dtstart", event["start_date"]) cal_event.add("dtend", event["end_date"]) cal.add_component(cal_event) return Response(cal.to_ical(), mimetype="text/calendar")
def main_year(year): # Do we want to show the current year's schedule from the DB, # or a previous year's from the static archive? if year == event_year(): if app.config.get("SCHEDULE"): # Schedule is ready, show it return schedule_current() elif app.config.get("LINE_UP"): # Show the lineup (list of talks without times/venues) return line_up() else: # No schedule should be shown yet. return render_template("schedule/no-schedule.html") else: return talks_historic(year)
def schedule_frab(year): if year != event_year(): return feed_historic(year, "frab") schedule = (Proposal.query.filter( Proposal.state.in_(["accepted", "finished"]), Proposal.scheduled_time.isnot(None), Proposal.scheduled_venue_id.isnot(None), Proposal.scheduled_duration.isnot(None), ).order_by(Proposal.scheduled_time).all()) schedule = [_get_proposal_dict(p, []) for p in schedule] frab = export_frab(schedule) return Response(frab, mimetype="application/xml")
def render_installation(installation: InstallationProposal): return { "id": installation.id, "name": installation.display_title, "url": url_for( "schedule.item", year=event_year(), proposal_id=installation.id, _external=True, ), "description": installation.published_description, "location": to_shape(installation.scheduled_venue.location).__geo_interface__ if installation.scheduled_venue and installation.scheduled_venue.location else None, }
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 item_json(year, proposal_id, slug=None): if year != event_year(): abort(404) proposal = Proposal.query.get_or_404(proposal_id) if proposal.state not in ("accepted", "finished"): abort(404) if not current_user.is_anonymous: favourites_ids = [f.id for f in current_user.favourites] else: favourites_ids = [] data = _get_proposal_dict(proposal, favourites_ids) data["start_date"] = data["start_date"].strftime("%Y-%m-%d %H:%M:%S") data["end_date"] = data["end_date"].strftime("%Y-%m-%d %H:%M:%S") # Remove unnecessary data for now del data["link"] del data["source"] del data["id"] return data
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, )
def export_db(): """Export data from the DB to disk. This command is run as a last step before wiping the DB after an event, to export all the data we want to save. It saves a private and a public export to the exports directory. Model classes should implement get_export_data, which returns a dict with keys: public Public data to save in git private Private data that should be stored for a limited amount of time tables Tables this method exported, used to sanity check the export process Alternatively, add __export_data__ = False to a class to state that get_export_data shouldn't be called, and that its associated table doesn't need to be checked. """ # As we go, we check against the list of all tables, in case we forget about some # new object type (e.g. association table). # Exclude tables we know will never be exported ignore = ["alembic_version", "transaction"] all_model_classes = { cls for cls in db.Model._decl_class_registry.values() if isinstance(cls, type) and issubclass(cls, db.Model) } all_version_classes = { version_class(c) for c in all_model_classes if is_versioned(c) } seen_model_classes = set() remaining_tables = set(db.metadata.tables) year = event_year() path = os.path.join("exports", str(year)) for dirname in ["public", "private"]: os.makedirs(os.path.join(path, dirname), exist_ok=True) for model_class in all_model_classes: if model_class in seen_model_classes: continue seen_model_classes.add(model_class) table = model_class.__table__.name model = model_class.__name__ if table in ignore: app.logger.debug("Ignoring %s", model) remaining_tables.remove(table) continue if not getattr(model_class, "__export_data__", True): # We don't remove the version table, as we want # to be explicit about chucking away edit stats app.logger.debug("Skipping %s", model) remaining_tables.remove(table) continue if model_class in all_version_classes: # Version tables are explicitly dumped by their parents, # as they don't make sense to be exported on their own app.logger.debug("Ignoring version model %s", model) continue if hasattr(model_class, "get_export_data"): try: export = model_class.get_export_data() for dirname in ["public", "private"]: if dirname in export: filename = os.path.join(path, dirname, "{}.json".format(model)) simplejson.dump( export[dirname], open(filename, "w"), indent=4, cls=ExportEncoder, ) app.logger.info("Exported data from %s to %s", model, filename) except Exception as e: app.logger.error("Error exporting %s", model) raise exported_tables = export.get("tables", [table]) remaining_tables -= set(exported_tables) if remaining_tables: app.logger.warning("Remaining tables: %s", ", ".join(remaining_tables)) data = { "timestamp": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"), "remaining_tables": sorted(list(remaining_tables)), } filename = os.path.join(path, "export.json") simplejson.dump(data, open(filename, "w"), indent=4, cls=ExportEncoder) with app.test_client() as client: for schedule in ["schedule.frab", "schedule.json", "schedule.ics"]: resp = client.get("/{}".format(schedule)) with open(os.path.join(path, "public", schedule), "wb") as f: f.write(resp.data) app.logger.info("Export complete, summary written to %s", filename)
def create_gc_payment(payment): try: logger.info("Creating GC payment for %s (%s)", payment.id, payment.mandate) # The idempotency key identifies a unique payment to GoCardless. # In production this is the event year and payment ID but in dev, payment # IDs get reused when the DB is reset, so don't use idempotency keys in dev. headers = {"Idempotency-Key": f"{event_year()}-{payment.id}"} if app.config.get("DEBUG"): headers = {} gc_payment = gocardless_client.payments.create( params={ "amount": payment.amount_int, "currency": payment.currency, "links": {"mandate": payment.mandate}, "metadata": { "payment_id": str(payment.id), "event_year": str(event_year()), }, }, headers=headers, ) payment.gcid = gc_payment.id payment.state = "inprogress" except gocardless_pro.errors.ValidationFailedError as exc: currency_errors = [e for e in exc.errors if e["field"] == "currency"] if currency_errors: # e['message'] will be one of: # 'must be GBP for a bacs mandate' # 'must be EUR for a sepa_core mandate' logger.error("Currency exception %r confirming payment", exc) flash( "Your account cannot be used for {} payments".format(payment.currency) ) else: logger.error("Exception %r confirming payment", exc) flash( "An error occurred with your payment, please contact {}".format( app.config["TICKETS_EMAIL"][1] ) ) return redirect(url_for("users.purchases")) except Exception as e: logger.error("Exception %r confirming payment", e) flash( "An error occurred with your payment, please contact {}".format( app.config["TICKETS_EMAIL"][1] ) ) return redirect(url_for("users.purchases")) # We need to make sure of a 5 working days grace # for gocardless payments, so push the payment expiry forwards payment.expires = datetime.utcnow() + timedelta( days=app.config["EXPIRY_DAYS_GOCARDLESS"] ) for purchase in payment.purchases: purchase.set_state("payment-pending") db.session.commit() logger.info("Reset expiry for payment %s", payment.id) # FIXME: determine whether these are tickets or generic products msg = Message( "Your EMF ticket purchase", sender=app.config["TICKETS_EMAIL"], recipients=[payment.user.email], ) msg.body = render_template( "emails/tickets-purchased-email-gocardless.txt", user=payment.user, payment=payment, ) mail.send(msg) return redirect(url_for(".gocardless_waiting", payment_id=payment.id))
def abort_if_invalid_year(year): if not 2012 <= year < event_year(): abort(404)
def main(year): if year != event_year(): abort(404) villages = Village.query.all() return render_template("villages/villages.html", villages=villages)
def villages_redirect(): return redirect(url_for(".main", year=event_year()))
def main(): return redirect(url_for(".main_year", year=event_year()))
def item(year, proposal_id, slug=None): """ Display a detail page for a schedule item """ if year == event_year(): return item_current(year, proposal_id, slug) else: return item_historic(year, proposal_id, slug)