def edit_seminar_schedule(): # It would be good to have a version of this that worked for a conference, but that's a project for later if request.method == "POST": data = dict(request.form) else: data = dict(request.args) shortname = data.get("shortname", "") resp, seminar = can_edit_seminar(shortname, new=False) if resp is not None: return resp if not seminar.topics: flash_warning( "This series has no topics selected; don't forget to set the topics for each new talk individually." ) schedule = layout_schedule(seminar, data) title = "Edit %s schedule" % ("conference" if seminar.is_conference else "seminar") return render_template( "edit_seminar_schedule.html", seminar=seminar, raw_data=data, title=title, schedule=schedule, section="Manage", subsection="schedule", )
def parse_date(key): date = data.get(key) if date: try: return process_user_input(date, "date", "date", tz) except ValueError: flash_warning( "Invalid date %s ignored, please use a format like mmm dd, yyyy or dd-mmm-yyyy or mm/dd/yyyy", date)
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, )
def layout_schedule(seminar, data): """ Returns a list of schedule slots in specified date range (date, daytime-interval, talk) where talk is a WebTalk or none. Picks default dates if none specified """ tz = seminar.tz def parse_date(key): date = data.get(key) if date: try: return process_user_input(date, "date", "date", tz) except ValueError: flash_warning( "Invalid date %s ignored, please use a format like mmm dd, yyyy or dd-mmm-yyyy or mm/dd/yyyy", date) def slot_start_time(s): # put slots with no time specified at the end of the day return date_and_daytimes_to_times(parse_time(s[0]), s[1] if s[1] else "23:59-23:59", tz)[0] begin = parse_date("begin") end = parse_date("end") shortname = seminar.shortname now = datetime.now(tz=tz) today = now.date() day = timedelta(days=1) if seminar.is_conference and (seminar.start_date is None or seminar.end_date is None): flash_warning( "You have not specified the start and end dates of your conference (we chose a date range to layout your schedule)." ) begin = seminar.start_date if begin is None and seminar.is_conference else begin begin = today if begin is None else begin end = seminar.end_date if end is None and seminar.is_conference else end if end is None: if seminar.is_conference: if seminar.per_day: end = begin + day * ceil(SCHEDULE_LEN / seminar.per_day) else: end = begin + 7 * day else: if seminar.frequency: end = begin + day * ceil( SCHEDULE_LEN * seminar.frequency / len(seminar.time_slots)) else: end = begin + 14 * day if end < begin: end = begin data["begin"] = seminar.show_input_date(begin) data["end"] = seminar.show_input_date(end) midnight_begin = midnight(begin, tz) midnight_end = midnight(end, tz) query = {"$gte": midnight_begin, "$lt": midnight_end + day} talks = list( talks_search({ "seminar_id": shortname, "start_time": query }, sort=["start_time"])) slots = [(t.show_date(tz), t.show_daytimes(tz), t) for t in talks] if seminar.is_conference: newslots = [] d = midnight_begin while d < midnight_end + day: newslots += [(seminar.show_schedule_date(d), "", None) for i in range(seminar.per_day)] d += day for t in slots: if (t[0], "", None) in newslots: newslots.remove((t[0], "", None)) slots = sorted(slots + newslots, key=lambda t: slot_start_time(t)) return slots if not seminar.frequency: for i in range(max(SCHEDULE_LEN - len(slots), 3)): slots.append(("", "", None)) else: # figure out week to start in. # use the week of the first seminar after begin if any, otherwise last seminar before begin, if any # otherwise just use the week containing begin t = talks_lucky( { "seminar_id": shortname, "start_time": { "$gte": midnight_begin } }, sort=[("start_time", 1)]) if not t: t = talks_lucky( { "seminar_id": shortname, "start_time": { "$lt": midnight_begin } }, sort=[("start_time", -1)]) if t: t = adapt_datetime(t.start_time, newtz=tz) w = t - t.weekday() * day while w > midnight_begin: w -= day * seminar.frequency while w + day * seminar.frequency < midnight_begin: w += day * seminar.frequency else: w = midnight_begin - midnight_begin.weekday() * day # make a list of all seminar time slots in [begin,end) newslots = [] while w < midnight_end: for i in range(len(seminar.weekdays)): d = w + day * seminar.weekdays[i] if d >= midnight_begin and d < midnight_end + day: newslots.append((seminar.show_schedule_date(d), seminar.time_slots[i], None)) w = w + day * seminar.frequency # remove slots that are (exactly) matched by an existing talk # this should handle slots that occur with multiplicity for t in slots: if (t[0], t[1], None) in newslots: newslots.remove((t[0], t[1], None)) slots = sorted(slots + newslots, key=lambda t: slot_start_time(t)) return slots
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)
def save_institution(): raw_data = request.form shortname = raw_data["shortname"] new = raw_data.get("new") == "yes" resp, institution = can_edit_institution(shortname, new) if resp is not None: return resp data = {} data["timezone"] = tz = raw_data.get("timezone", "UTC") tz = pytz.timezone(tz) errmsgs = [] for col in db.institutions.search_cols: if col in data: continue typ = db.institutions.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 == "admin": userdata = userdb.lookup(data[col]) if userdata is None: if not data[col]: errmsgs.append( "You must specify the email address of the maintainer." ) continue else: errmsgs.append( format_errmsg( "User %s does not have an account on this site", data[col])) continue elif not userdata["creator"]: errmsgs.append( format_errmsg("User %s has not been endorsed", data[col])) continue if not userdata["homepage"]: if current_user.email == userdata["email"]: flash_warning( "Your email address will become public if you do not set your homepage in your user profile." ) else: flash_warning( "The email address %s of maintainer %s will be publicly visible.<br>%s", userdata["email"], userdata["name"], "The homepage on the maintainer's user account should be set prevent this.", ) 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["name"]: errmsgs.append("Institution name cannot be blank.") if not errmsgs and not data["homepage"]: errmsgs.append("Institution homepage cannot be blank.") # Don't try to create new_version using invalid input if errmsgs: return show_input_errors(errmsgs) new_version = WebInstitution(shortname, data=data) if new_version == institution: flash("No changes made to institution.") else: new_version.save() edittype = "created" if new else "edited" flash("Institution %s successfully!" % edittype) return redirect(url_for(".edit_institution", shortname=shortname), 302)
def save_seminar(): raw_data = request.form shortname = raw_data["shortname"] new = raw_data.get("new") == "yes" resp, seminar = can_edit_seminar(shortname, new) if resp is not None: return resp errmsgs = [] if seminar.new: data = { "shortname": shortname, "display": current_user.is_creator, "owner": current_user.email, } else: data = { "shortname": shortname, "display": seminar.display, "owner": seminar.owner, } # Have to get time zone first data["timezone"] = tz = raw_data.get("timezone") tz = pytz.timezone(tz) for col in db.seminars.search_cols: if col in data: continue typ = db.seminars.col_type[col] ### Hack to be removed ### if col.endswith("time") and typ == "timestamp with time zone": typ = "time" 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) 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["name"]: errmsgs.append("The name cannot be blank") if data["is_conference"] and data["start_date"] and data[ "end_date"] and data["end_date"] < data["start_date"]: errmsgs.append("End date cannot precede start date") if data["per_day"] is not None and data["per_day"] < 1: errmsgs.append( format_input_errmsg("integer must be positive", data["per_day"], "per_day")) if data["is_conference"] and (not data["start_date"] or not data["end_date"]): errmsgs.append( "Please specify the start and end dates of your conference (you can change these later if needed)." ) if data["is_conference"] and not data["per_day"]: flash_warning( "It will be easier to edit the conference schedule if you specify talks per day (an upper bound is fine)." ) data["institutions"] = clean_institutions(data.get("institutions")) 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(format_errmsg("Please select at least one subject.")) if not data["timezone"] and data["institutions"]: # Set time zone from institution data["timezone"] = WebInstitution(data["institutions"][0]).timezone data["weekdays"] = [] data["time_slots"] = [] for i in range(MAX_SLOTS): weekday = daytimes = None try: col = "weekday" + str(i) val = raw_data.get(col, "") weekday = process_user_input(val, col, "weekday_number", tz) col = "time_slot" + str(i) val = raw_data.get(col, "") daytimes = process_user_input(val, col, "daytimes", tz) except Exception as err: # should only be ValueError's but let's be cautious errmsgs.append(format_input_errmsg(err, val, col)) if weekday is not None and daytimes is not None: data["weekdays"].append(weekday) data["time_slots"].append(daytimes) if daytimes_early(daytimes): flash_warning( "Time slot %s includes early AM hours, please correct if this is not intended (use 24-hour time format).", daytimes, ) elif daytimes_long(daytimes): flash_warning( "Time slot %s is longer than 8 hours, please correct if this is not intended.", daytimes, ) if data["frequency"] and not data["weekdays"]: errmsgs.append( 'You must specify at least one time slot (or set periodicty to "no fixed schedule").' ) if len(data["weekdays"]) > 1: x = sorted( list(zip(data["weekdays"], data["time_slots"])), key=lambda t: t[0] * 24 * 60 + daytime_minutes(t[1].split("-")[0]), ) data["weekdays"], data["time_slots"] = [t[0] for t in x], [t[1] for t in x] organizer_data = [] contact_count = 0 for i in range(10): D = {"seminar_id": seminar.shortname} for col in db.seminar_organizers.search_cols: if col in D: continue name = "org_%s%s" % (col, i) typ = db.seminar_organizers.col_type[col] try: val = raw_data.get(name, "") D[col] = None # make sure col is present even if process_user_input fails D[col] = process_user_input(val, col, typ, tz) except Exception as err: # should only be ValueError's but let's be cautious errmsgs.append(format_input_errmsg(err, val, col)) if D["homepage"] or D["email"] or D["full_name"]: if not D["full_name"]: errmsgs.append( format_errmsg("Organizer name cannot be left blank")) D["order"] = len(organizer_data) # WARNING the header on the template says organizer # but it sets the database column curator, so the # boolean needs to be inverted D["curator"] = not D["curator"] if not errmsgs and D["display"] and D[ "email"] and not D["homepage"]: flash_warning( "The email address %s of organizer %s will be publicly visible.<br>%s", D["email"], D["full_name"], "Set homepage or disable display to prevent this.", ), if D["email"]: r = db.users.lookup(D["email"]) if r and r["email_confirmed"]: if D["full_name"] != r["name"]: errmsgs.append( format_errmsg( "Organizer name %s does not match the name %s of the account with email address %s", D["full_name"], r["name"], D["email"], )) else: if D["homepage"] and r[ "homepage"] and D["homepage"] != r["homepage"]: flash_warning( "The homepage %s does not match the homepage %s of the account with email address %s, please correct if unintended.", D["homepage"], r["homepage"], D["email"], ) if D["display"]: contact_count += 1 organizer_data.append(D) if contact_count == 0: errmsgs.append( format_errmsg( "There must be at least one displayed organizer or curator with a %s so that there is a contact for this listing.<br>%s<br>%s", "confirmed email", "This email will not be visible if homepage is set or display is not checked, it is used only to identify the organizer's account.", "If none of the organizers has a confirmed account, add yourself and leave the organizer box unchecked.", )) # 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 = WebSeminar(shortname, data=data, organizer_data=organizer_data) # Warnings sanity_check_times(new_version.start_time, new_version.end_time) if not data["topics"]: flash_warning( "This series has no topics selected; don't forget to set the topics for each new talk individually." ) if seminar.new or new_version != seminar: new_version.save() edittype = "created" if new else "edited" flash("Series %s successfully!" % edittype) elif seminar.organizer_data == new_version.organizer_data: flash("No changes made to series.") if seminar.new or seminar.organizer_data != new_version.organizer_data: new_version.save_organizers() if not seminar.new: flash("Series organizers updated!") return redirect(url_for(".edit_seminar", shortname=shortname), 302)
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)