Beispiel #1
0
def delete_talk(semid, semctr):
    try:
        talk = WebTalk(semid, semctr)
    except ValueError as err:
        flash_error(str(err))
        return redirect(url_for(".edit_seminar_schedule", shortname=semid),
                        302)

    def failure():
        return render_template(
            "edit_talk.html",
            talk=talk,
            seminar=talk.seminar,
            title="Edit talk",
            section="Manage",
            subsection="edittalk",
            institutions=institutions(),
            timezones=timezones,
        )

    if not talk.user_can_delete():
        flash_error("Only the organizers of a seminar can delete talks in it")
        return failure()
    else:
        if talk.delete():
            flash("Talk deleted")
            return redirect(
                url_for(".edit_seminar_schedule", shortname=talk.seminar_id),
                302)
        else:
            flash_error(
                "Only the organizers of a seminar can delete talks in it")
            return failure()
Beispiel #2
0
def save_talk():
    raw_data = request.form
    resp, seminar, talk = can_edit_talk(raw_data.get("seminar_id", ""),
                                        raw_data.get("seminar_ctr", ""),
                                        raw_data.get("token", ""))
    if resp is not None:
        return resp

    data = {
        'seminar_id': talk.seminar_id,
        'token': talk.token,
        'display': talk.display,  # could be being edited by anonymous user
    }
    if talk.new:
        curmax = talks_max('seminar_ctr', {'seminar_id': talk.seminar_id})
        if curmax is None:
            curmax = 0
        data['seminar_ctr'] = curmax + 1
    else:
        data['seminar_ctr'] = talk.seminar_ctr
    for col in db.talks.search_cols:
        if col in data: continue
        try:
            val = raw_data.get(col)
            if not val:
                data[col] = None
            else:
                data[col] = process_user_input(val, db.talks.col_type[col])
            if col == 'speaker_homepage' and val and not val.startswith(
                    "http"):
                data[col] = "http://" + data[col]
            if col == "access" and val not in ["open", "users", "endorsed"]:
                raise ValueError("Invalid access type")
        except Exception as err:
            flash_error("Error processing %s: %s", [col, err])
            talk = WebTalk(talk.seminar_id, talk.seminar_ctr, data=raw_data)
            title = "Create talk" if talk.new else "Edit talk"
            return render_template("edit_talk.html",
                                   talk=talk,
                                   seminar=seminar,
                                   title=title,
                                   top_menu=basic_top_menu(),
                                   categories=categories(),
                                   institutions=institutions(),
                                   timezones=timezones)
    new_version = WebTalk(talk.seminar_id, data['seminar_ctr'], data=data)
    if new_version == talk:
        flash("No changes made to talk.")
    else:
        new_version.save()
        edittype = "created" if talk.new else "edited"
        flash("Talk successfully %s!" % edittype)
    return redirect(
        url_for("show_talk",
                semid=new_version.seminar_id,
                talkid=new_version.seminar_ctr), 301)
Beispiel #3
0
def deleted_talk(semid, semctr):
    try:
        talk = WebTalk(semid, semctr, deleted=True)
    except ValueError as err:
        flash_error(str(err))
        return redirect(url_for(".edit_seminar_schedule", shortname=semid),
                        302)
    return render_template("deleted_talk.html", talk=talk, title="Deleted")
Beispiel #4
0
def save_talk(version=0, user=None):
    if version != 0:
        raise APIError({"code": "invalid_version",
                        "description": "Unknown API version: %s" % version})
    raw_data = get_request_json()
    # Temporary measure while we rename seminar_id
    series_id = raw_data.pop("series_id", None)
    raw_data["seminar_id"] = series_id
    if series_id is None:
        raise APIError({"code": "unspecified_series_id",
                        "description": "You must specify series_id when saving a talk"})
    series = seminars_lookup(series_id)
    if series is None:
        raise APIError({"code": "no_series",
                        "description": "The series %s does not exist (or is deleted)" % series_id})
    else:
        # Make sure user has permission to edit
        if not series.user_can_edit(user):
            raise APIError({"code": "unauthorized_user",
                            "description": "You do not have permission to edit %s." % series_id}, 401)
    # Temporary measure while we rename seminar_ctr
    series_ctr = raw_data.pop("series_ctr", None)
    raw_data["seminar_ctr"] = series_ctr
    if series_ctr is None:
        # Creating new talk
        talk = WebTalk(series_id, seminar=series, editing=True)
    else:
        talk = talks_lookup(series_id, series_ctr)
        if talk is None:
            raise APIError({"code": "no_talk",
                            "description": "The talk %s/%s does not exist (or is deleted)" % (series_id, series_ctr)})
    warnings = []
    def warn(msg, *args):
        warnings.append(msg % args)
    new_version, errmsgs = process_save_talk(talk, raw_data, warn, format_error, format_input_error, incremental_update=True) # doesn't currently use the user
    if new_version is None:
        raise APIError({"code": "processing_error",
                        "description": "Error in processing input",
                        "errors": errmsgs})
    if talk.new or new_version != talk:
        # Talks saved by the API are not displayed until user approves
        new_version.display = False
        new_version.by_api = True
        new_version.save(user)
    else:
        raise APIError({"code": "no_changes",
                        "description": "No changes detected"})
    edittype = "created" if talk.new else "edited"
    if warnings:
        response = jsonify({"code": "warning",
                            "series_ctr": new_version.seminar_ctr, # FIXME seminar_ctr -> series_ctr
                            "description": "series successfully %s, but..." % edittype,
                            "warnings": warnings})
    else:
        response = jsonify({"code": "success",
                            "series_ctr": new_version.seminar_ctr, # FIXME seminar_ctr -> series_ctr
                            "description": "series successfully %s" % edittype})
    return response
Beispiel #5
0
 def make_error(talk, col=None, err=None):
     if err is not None:
         flash_error("Error processing %s: {0}".format(err), col)
     talk = WebTalk(talk.seminar_id, talk.seminar_ctr, data=raw_data)
     title = "Create talk error" if talk.new else "Edit talk error"
     return render_template(
         "edit_talk.html",
         talk=talk,
         seminar=talk.seminar,
         title=title,
         section="Manage",
         subsection="edittalk",
         institutions=institutions(),
         timezones=timezones,
     )
Beispiel #6
0
    def talks(self):
        res = []
        for shortname, ctrs in self.talk_subscriptions.items():
            for ctr in ctrs:
                try:
                    res.append(WebTalk(shortname, ctr))
                except ValueError:
                    self._data["talk_subscriptions"][shortname].remove(ctr)
                    self._dirty = True

        if self._dirty:
            for shortname in self._data["talk_subscriptions"]:
                if not self._data["talk_subscriptions"]:
                    self._data["talk_subscriptions"].pop("shortname")
            self.save()

        res.sort(key=lambda elt: elt.start_time)
        return res
Beispiel #7
0
def save_seminar_schedule():
    raw_data = request.form
    shortname = raw_data["shortname"]
    resp, seminar = can_edit_seminar(shortname, new=False)
    if resp is not None:
        return resp
    frequency = raw_data.get("frequency")
    try:
        frequency = int(frequency)
    except Exception:
        pass
    slots = int(raw_data["slots"])
    curmax = talks_max("seminar_ctr", {"seminar_id": shortname})
    if curmax is None:
        curmax = 0
    ctr = curmax + 1
    updated = 0
    warned = False
    errmsgs = []
    tz = seminar.tz
    to_save = []
    for i in list(range(slots)):
        seminar_ctr = raw_data.get("seminar_ctr%s" % i)
        speaker = process_user_input(raw_data.get("speaker%s" % i, ""),
                                     "speaker", "text", tz)
        if not speaker:
            if not warned and any(
                    raw_data.get("%s%s" % (col, i), "").strip()
                    for col in optional_cols):
                warned = True
                flash_warning("Talks are only saved if you specify a speaker")
            elif (not warned and seminar_ctr and not any(
                    raw_data.get("%s%s" % (col, i), "").strip()
                    for col in optional_cols)):
                warned = True
                flash_warning(
                    "To delete an existing talk, click Details and then click delete on the Edit talk page"
                )
            continue
        date = start_time = end_time = None
        dateval = raw_data.get("date%s" % i).strip()
        timeval = raw_data.get("time%s" % i).strip()
        if dateval and timeval:
            try:
                date = process_user_input(dateval, "date", "date", tz)
            except Exception as err:  # should only be ValueError's but let's be cautious
                errmsgs.append(format_input_errmsg(err, dateval, "date"))
            if date:
                try:
                    interval = process_user_input(timeval, "time", "daytimes",
                                                  tz)
                    start_time, end_time = date_and_daytimes_to_times(
                        date, interval, tz)
                except Exception as err:  # should only be ValueError's but let's be cautious
                    errmsgs.append(format_input_errmsg(err, timeval, "time"))
        if not date or not start_time or not end_time:
            errmsgs.append(
                format_errmsg(
                    "You must specify a date and time for the talk by %s",
                    speaker))

        # we need to flag date and time errors before we go any further
        if errmsgs:
            return show_input_errors(errmsgs)

        if daytimes_early(interval):
            flash_warning(
                "Talk for speaker %s includes early AM hours, please correct if this is not intended (use 24-hour time format).",
                speaker,
            )
        elif daytimes_long(interval) > 8 * 60:
            flash_warning(
                "Time s %s is longer than 8 hours, please correct if this is not intended.",
                speaker),

        if seminar_ctr:
            # existing talk
            seminar_ctr = int(seminar_ctr)
            talk = WebTalk(shortname, seminar_ctr, seminar=seminar)
        else:
            # new talk
            talk = WebTalk(shortname, seminar=seminar, editing=True)

        data = dict(talk.__dict__)
        data["speaker"] = speaker
        data["start_time"] = start_time
        data["end_time"] = end_time

        for col in optional_cols:
            typ = db.talks.col_type[col]
            try:
                val = raw_data.get("%s%s" % (col, i), "")
                data[
                    col] = None  # make sure col is present even if process_user_input fails
                data[col] = process_user_input(val, col, typ, tz)
            except Exception as err:
                errmsgs.append(format_input_errmsg(err, val, col))

        # Don't try to create new_version using invalid input
        if errmsgs:
            return show_input_errors(errmsgs)

        if seminar_ctr:
            new_version = WebTalk(talk.seminar_id, data=data)
            if new_version != talk:
                updated += 1
                to_save.append(
                    new_version)  # defer save in case of errors on other talks
        else:
            data["seminar_ctr"] = ctr
            ctr += 1
            new_version = WebTalk(talk.seminar_id, data=data)
            to_save.append(
                new_version)  # defer save in case of errors on other talks

    for newver in to_save:
        newver.save()

    if raw_data.get("detailctr"):
        return redirect(
            url_for(
                ".edit_talk",
                seminar_id=shortname,
                seminar_ctr=int(raw_data.get("detailctr")),
            ),
            302,
        )
    else:
        flash("%s talks updated, %s talks created" %
              (updated, ctr - curmax - 1))
        if warned:
            return redirect(url_for(".edit_seminar_schedule", **raw_data), 302)
        else:
            return redirect(
                url_for(
                    ".edit_seminar_schedule",
                    shortname=shortname,
                    begin=raw_data.get("begin"),
                    end=raw_data.get("end"),
                    frequency=raw_data.get("frequency"),
                    weekday=raw_data.get("weekday"),
                ),
                302,
            )
Beispiel #8
0
def save_talk():
    raw_data = request.form
    token = raw_data.get("token", "")
    resp, talk = can_edit_talk(raw_data.get("seminar_id", ""),
                               raw_data.get("seminar_ctr", ""), token)
    if resp is not None:
        return resp
    errmsgs = []

    data = {
        "seminar_id": talk.seminar_id,
        "token": talk.token,
        "display": talk.display,  # could be being edited by anonymous user
    }
    if talk.new:
        curmax = talks_max("seminar_ctr", {"seminar_id": talk.seminar_id},
                           include_deleted=True)
        if curmax is None:
            curmax = 0
        data["seminar_ctr"] = curmax + 1
    else:
        data["seminar_ctr"] = talk.seminar_ctr
    default_tz = talk.seminar.timezone
    if not default_tz:
        default_tz = "UTC"
    data["timezone"] = tz = raw_data.get("timezone", default_tz)
    tz = pytz.timezone(tz)
    for col in db.talks.search_cols:
        if col in data:
            continue
        typ = db.talks.col_type[col]
        try:
            val = raw_data.get(col, "")
            data[
                col] = None  # make sure col is present even if process_user_input fails
            data[col] = process_user_input(val, col, typ, tz)
            if col == "access" and data[col] not in [
                    "open", "users", "endorsed"
            ]:
                errmsgs.append(
                    format_errmsg("Access type %s invalid", data[col]))
        except Exception as err:  # should only be ValueError's but let's be cautious
            errmsgs.append(format_input_errmsg(err, val, col))
    if not data["speaker"]:
        errmsgs.append(
            "Speaker name cannot be blank -- use TBA if speaker not chosen.")
    if data["start_time"] is None or data["end_time"] is None:
        errmsgs.append("Talks must have both a start and end time.")
    data["topics"] = clean_topics(data.get("topics"))
    data["language"] = clean_language(data.get("language"))
    data["subjects"] = clean_subjects(data.get("subjects"))
    if not data["subjects"]:
        errmsgs.append("Please select at least one subject")

    # Don't try to create new_version using invalid input
    if errmsgs:
        return show_input_errors(errmsgs)
    else:  # to make it obvious that these two statements should be together
        new_version = WebTalk(talk.seminar_id, data=data)

    # Warnings
    sanity_check_times(new_version.start_time, new_version.end_time)
    if "zoom" in data["video_link"] and not "rec" in data["video_link"]:
        flash_warning(
            "Recorded video link should not be used for Zoom meeting links; be sure to use Livestream link for meeting links."
        )
    if not data["topics"]:
        flash_warning(
            "This talk has no topics, and thus will only be visible to users when they disable their topics filter."
        )
    if new_version == talk:
        flash("No changes made to talk.")
    else:
        new_version.save()
        edittype = "created" if talk.new else "edited"
        flash("Talk successfully %s!" % edittype)
    edit_kwds = dict(seminar_id=new_version.seminar_id,
                     seminar_ctr=new_version.seminar_ctr)
    if token:
        edit_kwds["token"] = token
    else:
        edit_kwds.pop("token", None)
    return redirect(url_for(".edit_talk", **edit_kwds), 302)
Beispiel #9
0
def index():
    # TODO: use a join for the following query
    seminars = {}
    conferences = {}
    deleted_seminars = []
    deleted_talks = []

    def key(elt):
        role_key = {"organizer": 0, "curator": 1, "creator": 3}
        return (role_key[elt[1]], elt[0].name)

    for rec in db.seminar_organizers.search(
        {"email": ilike_query(current_user.email)}, ["seminar_id", "curator"]):
        semid = rec["seminar_id"]
        role = "curator" if rec["curator"] else "organizer"
        seminar = WebSeminar(semid)
        pair = (seminar, role)
        if seminar.is_conference:
            conferences[semid] = pair
        else:
            seminars[semid] = pair
    role = "creator"
    for semid in seminars_search({"owner": ilike_query(current_user.email)},
                                 "shortname",
                                 include_deleted=True):
        if semid not in seminars and semid not in conferences:
            seminar = WebSeminar(semid, deleted=True)  # allow deleted
            pair = (seminar, role)
            if seminar.deleted:
                deleted_seminars.append(seminar)
            elif seminar.is_conference:
                conferences[semid] = pair
            else:
                seminars[semid] = pair
    seminars = sorted(seminars.values(), key=key)
    conferences = sorted(conferences.values(), key=key)
    deleted_seminars.sort(key=lambda sem: sem.name)
    for semid, semctr in db._execute(
            # ~~* is case insensitive amtch
            SQL("""
SELECT DISTINCT ON ({Ttalks}.{Csemid}, {Ttalks}.{Csemctr}) {Ttalks}.{Csemid}, {Ttalks}.{Csemctr}
FROM {Ttalks} INNER JOIN {Tsems} ON {Ttalks}.{Csemid} = {Tsems}.{Csname}
WHERE {Tsems}.{Cowner} ~~* %s AND {Ttalks}.{Cdel} = %s AND {Tsems}.{Cdel} = %s
            """).format(
                Ttalks=IdentifierWrapper("talks"),
                Tsems=IdentifierWrapper("seminars"),
                Csemid=IdentifierWrapper("seminar_id"),
                Csemctr=IdentifierWrapper("seminar_ctr"),
                Csname=IdentifierWrapper("shortname"),
                Cowner=IdentifierWrapper("owner"),
                Cdel=IdentifierWrapper("deleted"),
            ),
        [ilike_escape(current_user.email), True, False],
    ):
        talk = WebTalk(semid, semctr, deleted=True)
        deleted_talks.append(talk)
    deleted_talks.sort(key=lambda talk: (talk.seminar.name, talk.start_time))

    manage = "Manage" if current_user.is_organizer else "Create"
    return render_template(
        "create_index.html",
        seminars=seminars,
        conferences=conferences,
        deleted_seminars=deleted_seminars,
        deleted_talks=deleted_talks,
        institution_known=institution_known,
        institutions=institutions(),
        section=manage,
        subsection="home",
        title=manage,
        user_is_creator=current_user.is_creator,
    )
Beispiel #10
0
def save_seminar_schedule():
    raw_data = request.form
    shortname = raw_data["shortname"]
    resp, seminar = can_edit_seminar(shortname, new=False)
    if resp is not None:
        return resp
    frequency = raw_data.get("frequency")
    try:
        frequency = int(frequency)
    except Exception:
        pass
    schedule_count = int(raw_data["schedule_count"])
    # FIXME not being used
    # update_times = bool(raw_data.get("update_times"))
    curmax = talks_max("seminar_ctr", {"seminar_id": shortname})
    if curmax is None:
        curmax = 0
    ctr = curmax + 1
    updated = 0
    warned = False
    for i in list(range(schedule_count)):
        seminar_ctr = raw_data.get("seminar_ctr%s" % i)
        speaker = process_user_input(raw_data.get("speaker%s" % i, ""),
                                     "text",
                                     tz=seminar.timezone)
        if not speaker:
            if not warned and any(
                    raw_data.get("%s%s" % (col, i), "").strip()
                    for col in optional_cols):
                warned = True
                flash_warning("Talks are only saved if you specify a speaker")
            continue
        date = raw_data.get("date%s" % i).strip()
        if date:
            try:
                date = process_user_input(date, "date", tz=seminar.tz)
            except ValueError as err:
                flash_error("invalid date %s: {0}".format(err), date)
                redirect(
                    url_for(".edit_seminar_schedule",
                            shortname=shortname,
                            **raw_data), 301)
        else:
            date = None
        time_input = raw_data.get("time%s" % i, "").strip()
        if time_input:
            try:
                time_split = time_input.split("-")
                if len(time_split) == 1:
                    raise ValueError("Must specify both start and end times")
                elif len(time_split) > 2:
                    raise ValueError("More than one hyphen")
                # TODO: clean this up
                start_time = process_user_input(time_split[0], "time",
                                                seminar.tz).time()
                end_time = process_user_input(time_split[1], "time",
                                              seminar.tz).time()
                if check_time(start_time, end_time):
                    raise ValueError
            except ValueError as err:
                if str(err):
                    flash_error("invalid time range %s: {0}".format(err),
                                time_input)
                return redirect(url_for(".edit_seminar_schedule", **raw_data),
                                301)
        else:
            start_time = end_time = None
        if any(X is None for X in [start_time, end_time, date]):
            flash_error("You must give a date, start and end time for %s" %
                        speaker)
            return redirect(url_for(".edit_seminar_schedule", **raw_data), 301)
        if seminar_ctr:
            # existing talk
            seminar_ctr = int(seminar_ctr)
            talk = WebTalk(shortname, seminar_ctr, seminar=seminar)
        else:
            # new talk
            talk = WebTalk(shortname, seminar=seminar, editing=True)
        data = dict(talk.__dict__)
        data["speaker"] = speaker
        for col in optional_cols:
            data[col] = process_user_input(raw_data.get("%s%s" % (col, i), ""),
                                           "text",
                                           tz=seminar.timezone)
        data["start_time"] = localize_time(
            datetime.datetime.combine(date, start_time), seminar.tz)
        data["end_time"] = localize_time(
            datetime.datetime.combine(date, end_time), seminar.tz)
        if seminar_ctr:
            new_version = WebTalk(talk.seminar_id,
                                  data["seminar_ctr"],
                                  data=data)
            if new_version != talk:
                updated += 1
                new_version.save()
        else:
            data["seminar_ctr"] = ctr
            ctr += 1
            new_version = WebTalk(talk.seminar_id, ctr, data=data)
            new_version.save()

    if raw_data.get("detailctr"):
        return redirect(
            url_for(
                ".edit_talk",
                seminar_id=shortname,
                seminar_ctr=int(raw_data.get("detailctr")),
            ),
            301,
        )
    else:
        if updated or ctr > curmax + 1:
            flash("%s talks updated, %s talks created" %
                  (updated, ctr - curmax - 1))
        if warned:
            return redirect(url_for(".edit_seminar_schedule", **raw_data), 301)
        else:
            return redirect(
                url_for(".edit_seminar_schedule",
                        shortname=shortname,
                        begin=raw_data.get('begin'),
                        end=raw_data.get('end'),
                        frequency=raw_data.get('frequency'),
                        weekday=raw_data.get('weekday')), 301)
Beispiel #11
0
def save_talk():
    raw_data = request.form
    token = raw_data.get("token", "")
    resp, talk = can_edit_talk(raw_data.get("seminar_id", ""),
                               raw_data.get("seminar_ctr", ""), token)
    if resp is not None:
        return resp

    def make_error(talk, col=None, err=None):
        if err is not None:
            flash_error("Error processing %s: {0}".format(err), col)
        talk = WebTalk(talk.seminar_id, talk.seminar_ctr, data=raw_data)
        title = "Create talk error" if talk.new else "Edit talk error"
        return render_template(
            "edit_talk.html",
            talk=talk,
            seminar=talk.seminar,
            title=title,
            section="Manage",
            subsection="edittalk",
            institutions=institutions(),
            timezones=timezones,
        )

    data = {
        "seminar_id": talk.seminar_id,
        "token": talk.token,
        "display": talk.display,  # could be being edited by anonymous user
    }
    if talk.new:
        curmax = talks_max("seminar_ctr", {"seminar_id": talk.seminar_id})
        if curmax is None:
            curmax = 0
        data["seminar_ctr"] = curmax + 1
    else:
        data["seminar_ctr"] = talk.seminar_ctr
    default_tz = talk.seminar.timezone
    if not default_tz:
        default_tz = "UTC"
    data["timezone"] = tz = raw_data.get("timezone", default_tz)
    tz = pytz.timezone(tz)
    for col in db.talks.search_cols:
        if col in data:
            continue
        try:
            val = raw_data.get(col, "").strip()
            if not val:
                data[col] = None
            else:
                data[col] = process_user_input(val,
                                               db.talks.col_type[col],
                                               tz=tz)
            if col == "speaker_homepage" and val and not val.startswith(
                    "http"):
                data[col] = "http://" + data[col]
            if col == "access" and val not in ["open", "users", "endorsed"]:
                raise ValueError("Invalid access type")
        except Exception as err:
            return make_error(talk, col, err)
    data["topics"] = clean_topics(data.get("topics"))
    data["language"] = clean_language(data.get("language"))
    new_version = WebTalk(talk.seminar_id, data["seminar_ctr"], data=data)
    if check_time(new_version.start_time,
                  new_version.end_time,
                  check_past=True):
        return make_error(talk)
    if new_version == talk:
        flash("No changes made to talk.")
    else:
        new_version.save()
        edittype = "created" if talk.new else "edited"
        flash("Talk successfully %s!" % edittype)
    edit_kwds = dict(seminar_id=new_version.seminar_id,
                     seminar_ctr=new_version.seminar_ctr)
    if token:
        edit_kwds["token"] = token
    else:
        edit_kwds.pop("token", None)
    return redirect(url_for(".edit_talk", **edit_kwds), 301)
Beispiel #12
0
def save_seminar_schedule():
    raw_data = request.form
    shortname = raw_data["shortname"]
    resp, seminar = can_edit_seminar(shortname, new=False)
    if resp is not None:
        return resp
    schedule_count = int(raw_data["schedule_count"])
    update_times = bool(raw_data.get("update_times"))
    curmax = talks_max('seminar_ctr', {'seminar_id': shortname})
    if curmax is None:
        curmax = 0
    ctr = curmax + 1
    try:
        start_time = datetime.time.fromisoformat(raw_data["start_time"])
        end_time = datetime.time.fromisoformat(raw_data["end_time"])
    except ValueError as err:
        flash_error("Invalid time: %s", err)
        return redirect(url_for(".edit_seminar_schedule", shortname=shortname),
                        301)
    for i in range(schedule_count):
        seminar_ctr = raw_data.get("seminar_ctr%s" % i)
        date = datetime.date.fromisoformat(raw_data["date%s" % i])
        if seminar_ctr:
            # existing talk
            seminar_ctr = int(seminar_ctr)
            talk = WebTalk(shortname, seminar_ctr, seminar=seminar)
            data = dict(talk.__dict__)
            for col in [
                    "speaker", "speaker_affiliation", "speaker_email", "title"
            ]:
                data[col] = process_user_input(raw_data["%s%s" % (col, i)],
                                               'text')
            if update_times:
                data["start_time"] = datetime.datetime.combine(
                    date, start_time)
                data["end_time"] = datetime.datetime.combine(date, end_time)
            new_version = WebTalk(talk.seminar_id,
                                  data['seminar_ctr'],
                                  data=data)
            if new_version != talk:
                print(data)
                new_version.save()
        elif raw_data["speaker%s" % i].strip():
            # new talk
            talk = WebTalk(shortname, seminar=seminar, editing=True)
            data = dict(talk.__dict__)
            for col in [
                    "speaker", "speaker_affiliation", "speaker_email", "title"
            ]:
                data[col] = process_user_input(raw_data["%s%s" % (col, i)],
                                               'text')
            data["start_time"] = datetime.datetime.combine(date, start_time)
            data["end_time"] = datetime.datetime.combine(date, end_time)
            data["seminar_ctr"] = ctr
            ctr += 1
            new_version = WebTalk(talk.seminar_id, ctr, data=data)
            print(data)
            new_version.save()

    return redirect(url_for(".edit_seminar_schedule", shortname=shortname),
                    301)