예제 #1
0
def lookup_series(version=0, user=None):
    if version != 0:
        raise version_error(version)
    if request.method == "POST":
        raw_data = get_request_json()
    else:
        raw_data = get_request_args_json()
    series_id = _get_col("series_id", raw_data, "looking up a series")

    try:
        if user is None:
            sanitized = True
        else:
            sem = seminars_lookup(series_id)
            sanitized = not sem.user_can_edit(user=user)
        result = seminars_lookup(series_id, objects=False, sanitized=sanitized)
    except Exception as err:
        raise APIError({
            "code": "lookup_error",
            "description": "an error occurred looking up the series",
            "error": str(err)
        })
    talks = list(
        talks_search({"seminar_id": series_id},
                     sort=["start_time"],
                     sanitized=sanitized,
                     objects=False))
    # tz = pytz.timezone(raw_data.get("timezone", result.get("timezone", "UTC")))
    # TODO: adapt the times, support daterange, sort
    ans = {"code": "success", "properties": result, "talks": talks}
    callback = raw_data.get("callback", False)
    return str_jsonify(ans, callback)
예제 #2
0
파일: main.py 프로젝트: tornaria/seminars
def save_talk(version=0, user=None):
    if version != 0:
        raise APIError({"code": "invalid_version",
                        "description": "Unknown API version: %s" % version})
    raw_data = request.get_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)
    data, errmsgs = process_save_talk(talk, raw_data, warn, format_error, format_input_error)
    if errmsgs:
        raise APIError({"code": "processing_error",
                        "description": "Error in processing input",
                        "errors": errmsgs})
    else:
        new_version = WebTalk(talk.seminar_id, data=data)
    sanity_check_times(new_version.start_time, new_version.end_time)
    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",
                            "description": "series successfully %s, but..." % edittype,
                            "warnings": warnings})
    else:
        response = jsonify({"code": "success",
                            "description": "series successfully %s" % edittype})
    return response
예제 #3
0
def can_edit_talk(seminar_id, seminar_ctr, token):
    """
    INPUT:

    - ``seminar_id`` -- the identifier of the seminar
    - ``seminar_ctr`` -- an integer as a string, or the empty string (for new talk)
    - ``token`` -- a string (allows editing by speaker who might not have account)

    OUTPUT:

    - ``resp`` -- a response to return to the user (indicating an error) or None (editing allowed)
    - ``seminar`` -- a WebSeminar object, as returned by ``seminars_lookup(seminar_id)``
    - ``talk`` -- a WebTalk object, as returned by ``talks_lookup(seminar_id, seminar_ctr)``,
                  or ``None`` (if error or talk does not exist)
    """
    new = not seminar_ctr
    if seminar_ctr:
        try:
            seminar_ctr = int(seminar_ctr)
        except ValueError:
            flash_error("Invalid talk id")
            return redirect(url_for("show_seminar", shortname=seminar_id), 301), None, None
    if token and seminar_ctr != "":
        talk = talks_lookup(seminar_id, seminar_ctr)
        if talk is None:
            flash_error("Talk does not exist")
            return redirect(url_for("show_seminar", shortname=seminar_id), 301), None, None
        elif token != talk.token:
            flash_error("Invalid token for editing talk")
            return redirect(url_for("show_talk", semid=seminar_id, talkid=seminar_ctr), 301), None, None
        seminar = seminars_lookup(seminar_id)
    else:
        resp, seminar = can_edit_seminar(seminar_id, new=False)
        if resp is not None:
            return resp, None, None
        if seminar.new:
            # TODO: This is where you might insert the ability to create a talk without first making a seminar
            flash_error("You must first create the seminar %s" % seminar_id)
            return redirect(url_for("edit_seminar", shortname=seminar_id), 301)
        if new:
            talk = WebTalk(seminar_id, seminar=seminar, editing=True)
        else:
            talk = WebTalk(seminar_id, seminar_ctr, seminar=seminar)
    return None, seminar, talk
예제 #4
0
def permdelete_seminar(shortname):
    seminar = seminars_lookup(shortname, include_deleted=True)

    if seminar is None:
        flash_error("Series %s already deleted permanently", shortname)
        return redirect(url_for(".index"), 302)
    if not current_user.is_subject_admin(
            seminar) and seminar.owner != current_user:
        flash_error("You do not have permission to delete seminar %s",
                    shortname)
        return redirect(url_for(".index"), 302)
    if not seminar.deleted:
        flash_error("You must delete seminar %s first", shortname)
        return redirect(url_for(".edit_seminar", shortname=shortname), 302)
    else:
        db.seminars.delete({"shortname": shortname})
        db.talks.delete({"seminar_id": shortname})
        flash("Series %s permanently deleted" % shortname)
        return redirect(url_for(".index"), 302)
예제 #5
0
def lookup_talk(version=0, user=None):
    if version != 0:
        raise version_error(version)
    if request.method == "POST":
        raw_data = get_request_json()
    else:
        raw_data = get_request_args_json()
    series_id = _get_col("series_id", raw_data, "looking up a talk")
    series_ctr = _get_col("series_ctr", raw_data, "looking up a talk")
    if user is None:
        sanitized = True
    else:
        sem = seminars_lookup(series_id)
        sanitized = not sem.user_can_edit(user=user)
    result = talks_lookup(series_id,
                          series_ctr,
                          objects=False,
                          sanitized=sanitized)
    ans = {"code": "success", "properties": result}
    callback = raw_data.get("callback", False)
    return str_jsonify(ans, callback)
예제 #6
0
def revive_seminar(shortname):
    seminar = seminars_lookup(shortname, include_deleted=True)

    if seminar is None:
        flash_error("Series %s was deleted permanently", shortname)
        return redirect(url_for(".index"), 302)
    if not current_user.is_subject_admin(
            seminar) and seminar.owner != current_user:
        flash_error("You do not have permission to revive seminar %s",
                    shortname)
        return redirect(url_for(".index"), 302)
    if not seminar.deleted:
        flash_error("Series %s was not deleted, so cannot be revived",
                    shortname)
    else:
        db.seminars.update({"shortname": shortname}, {"deleted": False})
        db.talks.update({"seminar_id": shortname}, {"deleted": False})
        flash(
            "Series %s revived.  You should reset the organizers, and note that any users that were subscribed no longer are."
            % shortname)
    return redirect(url_for(".edit_seminar", shortname=shortname), 302)
예제 #7
0
def save_series(version=0, user=None):
    if version != 0:
        raise version_error(version)
    try:
        raw_data = get_request_json()
    except Exception:
        raw_data = None
    if not isinstance(raw_data, dict):
        raise APIError({
            "code": "invalid_json",
            "description": "request must contain a json dictionary"
        })
    # Temporary measure while we rename shortname
    series_id = raw_data.pop("series_id", None)
    raw_data["shortname"] = series_id
    if series_id is None:
        raise APIError({
            "code":
            "unspecified_series_id",
            "description":
            "You must specify series_id when saving a series"
        })
    series = seminars_lookup(series_id, include_deleted=True)
    if series is None:
        # Creating new series
        if not allowed_shortname(
                series_id) or len(series_id) < 3 or len(series_id) > 32:
            raise APIError({
                "code":
                "invalid_series_id",
                "description":
                "The identifier must be 3 to 32 characters in length and can include only letters, numbers, hyphens and underscores."
            })
        if "organizers" not in raw_data:
            raise APIError({
                "code":
                "organizers_required",
                "description":
                "You must specify organizers when creating new series"
            })
        update_organizers = True
        series = WebSeminar(series_id, data=None, editing=True, user=user)
    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)
        if series.deleted:
            raise APIError({
                "code":
                "norevive",
                "description":
                "You cannot revive a series through the API"
            })
        if "organizers" in raw_data:
            raise APIError({
                "code":
                "organizers_prohibited",
                "description":
                "You may not specify organizers when editing series"
            })
        update_organizers = False
    # Check that there aren't extraneous keys (which might be misspelled)
    extra_keys = [
        key for key in raw_data
        if key not in db.seminars.search_cols + ["slots", "organizers"]
    ]
    if extra_keys:
        raise APIError({
            "code": "extra_keys",
            "description": "Unrecognized keys",
            "errors": extra_keys
        })
    # Time slots/weekdays and organizers are handled differently by the processing code
    # We want to allow them to be unspecified (in which case we fall back on the current value)
    # and for an API call it's also more convenient to specify them using a list
    if "slots" in raw_data:
        slots = raw_data["slots"]
    else:
        slots = [
            short_weekdays[day] + " " + slot
            for (day, slot) in zip(series.weekdays, series.time_slots)
        ]
    if not isinstance(slots, list) or len(slots) > MAX_SLOTS or not all(
            isinstance(slot, str) for slot in slots):
        raise APIError({
            "code":
            "processing_error",
            "description":
            "Error in processing slots",
            "errors": [
                "slots must be a list of strings of length at most %s" %
                MAX_SLOTS
            ]
        })
    for i, slot in enumerate(slots):
        try:
            day, time = slot.split(None, 1)
            day = short_weekdays.index(day)
        except ValueError:
            raise APIError({
                "code":
                "processing_error",
                "description":
                "Error in processing slots",
                "errors": [
                    "slots must be a three letter day-of-week followed by a time range after a space"
                ]
            })
        raw_data["weekday%s" % i] = str(day)
        raw_data["time_slot%s" % i] = time
    for i in range(len(slots), MAX_SLOTS):
        raw_data["weekday%s" % i] = raw_data["time_slot%s" % i] = ""
    raw_data["num_slots"] = len(slots)

    if update_organizers:
        # We require specifying the organizers of a new seminar and don't allow updates,
        # so we don't need to get anything from the seminar object
        organizers = raw_data.get("organizers", [])
        fixed_cols = list(db.seminar_organizers.search_cols)
        i = fixed_cols.index("curator")
        fixed_cols[i] = "organizer"
        if not (isinstance(organizers, list)
                and len(organizers) <= MAX_ORGANIZERS and all(
                    isinstance(OD, dict) and all(key in fixed_cols
                                                 for key in OD)
                    for OD in organizers)):
            raise APIError({
                "code":
                "processing_error",
                "description":
                "Error in processing organizers",
                "errors": [
                    "organizers must be a list of dictionaries (max length %s) with keys %s"
                    % (MAX_ORGANIZERS, ", ".join(fixed_cols))
                ]
            })
        for i, OD in enumerate(organizers):
            for col in db.seminar_organizers.search_cols:
                default = True if col == "display" else ""
                raw_data["org_%s%s" % (col, i)] = OD.get(col, default)
            # We store curator in the database but ask for organizer from the API
            raw_data["org_curator%s" % i] = not OD.get("organizer", True)

    warnings = []

    def warn(msg, *args):
        warnings.append(msg % args)

    new_version, errmsgs = process_save_seminar(series,
                                                raw_data,
                                                warn,
                                                format_error,
                                                format_input_error,
                                                update_organizers,
                                                incremental_update=True,
                                                user=user)
    if new_version is None:
        raise APIError({
            "code": "processing_error",
            "description": "Error in processing input",
            "errors": errmsgs
        })
    if series.new or new_version != series:
        # Series 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"
        })
    if series.new:
        new_version.save_organizers()
    edittype = "created" if series.new else "edited"
    if warnings:
        response = jsonify({
            "code": "warning",
            "description": "series successfully %s, but..." % edittype,
            "warnings": warnings
        })
    else:
        response = jsonify({
            "code": "success",
            "description": "series successfully %s" % edittype
        })
    return response
예제 #8
0
def import_talks(csv_file):
    talks = []
    ctr = {}
    with open(csv_file) as F:
        for i, line in enumerate(reader(F)):
            if i == 0:
                assert line == [
                    "Timestamp",
                    "Title",
                    "Speaker",
                    "Speaker_inst",
                    "Abstract",
                    "Host",
                    "Seminar",
                    "Site",
                    "In_Charge",
                    "arXiv",
                    "Date",
                    "Start_Time",
                    "End_Time",
                    "Timezone",
                    "Approved",
                ]
                continue
            (
                timestamp,
                title,
                speaker,
                speaker_affiliation,
                abstract,
                host,
                seminar_id,
                site,
                in_charge,
                arXiv,
                date,
                start_time,
                end_time,
                timezone,
                approved,
            ) = line
            # Make sure seminar exists
            seminar = seminars_lookup(seminar_id)
            if not seminar:
                continue
            if seminar is None:
                print("Warning: seminar %s does not exist" % seminar_id)
                continue
            if seminar_id not in ctr:
                m = talks_max("seminar_ctr", {"seminar_id": seminar_id})
                if m is None:
                    m = -1
                ctr[seminar_id] = m + 1
            # This time zone info is specific to importing in early April
            # There is some broken data, where time zones were incrementing the minute.  We reset them all to zero.
            tzdict = {
                -7: "America/Los_Angeles",
                -4: "America/New_York",
                -5: "America/Chicago",
                -3: "America/Buenos_Aires",
                2: "Europe/Paris",
                1: "Europe/London",
            }
            timezone = tzdict[int(timezone[4:7])]
            tz = pytz.timezone(timezone)
            date = parse(date, dayfirst=True).date()
            start_time = tz.localize(
                datetime.datetime.combine(date,
                                          parse(start_time).time()))
            end_time = tz.localize(
                datetime.datetime.combine(date,
                                          parse(end_time).time()))
            # Check to see if a talk at this time in the seminar already exists
            curtalk = talks_lucky({
                "seminar_id": seminar_id,
                "speaker": speaker
            })
            if curtalk is not None:
                print(
                    "Talk with speaker %s already exists in seminar %s; continuing"
                    % (speaker, seminar_id))
                continue
            curtalk = talks_lucky({
                "seminar_id": seminar_id,
                "start_time": start_time
            })
            if curtalk is not None:
                print(
                    "Talk at time %s (speaker %s) already exists in seminar %s; continuing"
                    % (start_time.strftime("%a %b %d %-H:%M"), speaker,
                       seminar_id))
                continue
            topics = (arXiv.replace(" ", "").replace("Math.", "").replace(
                "math.", "").lower().split(","))
            if not topics:
                topics = []
            talks.append(
                dict(
                    title=title,
                    speaker=speaker,
                    speaker_affiliation=speaker_affiliation,
                    abstract=abstract,
                    topics=topics,
                    timezone=timezone,
                    start_time=start_time,
                    end_time=end_time,
                    display=True,
                    token="%016x" % random.randrange(16**16),
                    online=True,
                    live_link=seminar.live_link,
                    room=seminar.room,
                    access=seminar.access,
                    comments=seminar.comments,
                    seminar_id=seminar_id,
                    seminar_ctr=ctr[seminar_id],
                ))
            ctr[seminar_id] += 1
    return talks