コード例 #1
0
ファイル: compile_all.py プロジェクト: pchae97/examtool
def compile_all(exam, subtitle, out, do_twice, email, exam_type, semester,
                deadline):
    """
    Compile individualized PDFs for the specified exam.
    Exam must have been deployed first.
    """
    if not out:
        out = "out/latex/" + exam

    pathlib.Path(out).mkdir(parents=True, exist_ok=True)

    exam_data = get_exam(exam=exam)
    password = exam_data.pop("secret")[:-1]
    print(password)
    exam_str = json.dumps(exam_data)

    roster = get_roster(exam=exam)

    if email:
        roster = [line_info for line_info in roster if line_info[0] == email]
        if len(roster) == 0:
            if deadline:
                roster = [(email, deadline)]
            else:
                raise ValueError("Email does not exist in the roster!")

    for email, deadline in roster:
        if not deadline:
            continue
        exam_data = json.loads(exam_str)
        scramble(email, exam_data)
        deadline_utc = datetime.utcfromtimestamp(int(deadline))
        deadline_pst = pytz.utc.localize(deadline_utc).astimezone(
            pytz.timezone("America/Los_Angeles"))
        deadline_string = deadline_pst.strftime("%I:%M%p")

        with render_latex(
                exam_data,
            {
                "emailaddress": sanitize_email(email),
                "deadline": deadline_string,
                "coursecode": prettify(exam.split("-")[0]),
                "description": subtitle,
                "examtype": exam_type,
                "semester": semester,
            },
                do_twice=do_twice,
        ) as pdf:
            pdf = Pdf.open(BytesIO(pdf))
            pdf.save(
                os.path.join(
                    out, "exam_" + email.replace("@", "_").replace(".", "_") +
                    ".pdf"),
                encryption=Encryption(owner=password, user=password),
            )
            pdf.close()
コード例 #2
0
def find_unexpected_words(exam, logs):
    data = get_exam(exam=exam)
    exam_json = json.dumps(data)
    for i, (email, log) in enumerate(logs):
        all_alternatives = get_substitutions(data)
        selected = {
            question["id"]: question["substitutions"]
            for question in extract_questions(
                scramble(email, json.loads(exam_json), keep_data=True))
        }
        for record in log:
            record.pop("timestamp")
            question = next(iter(record.keys()))
            answer = next(iter(record.values()))
            if question not in all_alternatives:
                continue
            for keyword, variants in all_alternatives[question].items():
                for variant in variants:
                    if variant == selected[question][keyword]:
                        continue
                    if variant in answer:
                        # check for false positive
                        for other in selected[question].values():
                            if variant in other and other != variant:
                                break
                        else:
                            print(email, selected[question], variant, answer)
                            break
                else:
                    continue
                break
            else:
                continue
            break
コード例 #3
0
def find_keyword(exam, phrase):
    data = get_exam(exam=exam)
    exam_json = json.dumps(data)
    for email, _ in get_roster(exam=exam):
        scrambled = scramble(email, json.loads(exam_json))
        if phrase in json.dumps(scrambled):
            print(email)
コード例 #4
0
ファイル: download.py プロジェクト: chrononyan/examtool
def download(exam, out, name_question, sid_question, compact, dispatch=None):
    exam_json = get_exam(exam=exam)
    exam_json.pop("secret")
    exam_json = json.dumps(exam_json)

    out = out or "out/export/" + exam

    pathlib.Path(out).mkdir(parents=True, exist_ok=True)

    template_questions = list(extract_questions(json.loads(exam_json)))

    pdf = write_exam(
        {},
        exam,
        template_questions,
        template_questions,
        name_question,
        sid_question,
        compact,
        dispatch,
    )
    pdf.output(os.path.join(out, "OUTLINE.pdf"))

    total = [["Email"] + [
        question["text"]
        for question in extract_questions(json.loads(exam_json))
    ]]

    for email, response in get_submissions(exam=exam):
        if 1 < len(response) < 10:
            print(email, response)

        total.append([email])
        for question in template_questions:
            total[-1].append(response.get(question["id"], ""))

        student_questions = list(
            extract_questions(
                scramble(email, json.loads(exam_json), keep_data=True)))

        pdf = write_exam(
            response,
            exam,
            template_questions,
            student_questions,
            name_question,
            sid_question,
            compact,
            dispatch,
        )
        pdf.output(os.path.join(out, "{}.pdf".format(email)))

    with open(os.path.join(out, "summary.csv"), "w") as f:
        writer = csv.writer(f)
        for row in total:
            writer.writerow(row)
コード例 #5
0
def convert():
    text = request.json["text"]
    draft = request.json.get("draft", False)
    seed = request.json.get("seed", False)
    text = text.replace("\r", "")
    try:
        exam = json.loads(convert_str(text, draft=draft, num_threads=1))
        if seed:
            exam = scramble(seed, exam)
        return jsonify({"success": True, "examJSON": json.dumps(exam)})
    except SyntaxError as e:
        return jsonify({"success": False, "error": str(e)})
コード例 #6
0
def find_unexpected_words(exam, logs):
    data = get_exam(exam=exam)
    exam_json = json.dumps(data)
    original_questions = {
        q["id"]: q
        for q in extract_questions(json.loads(exam_json))
    }
    for i, (email, log) in enumerate(logs):
        all_alternatives = get_substitutions(data)
        scrambled_questions = {
            q["id"]: q
            for q in extract_questions(scramble(
                email, json.loads(exam_json), keep_data=True),
                                       nest_all=True)
        }
        flagged_questions = set()
        for record in log:
            record.pop("timestamp")
            question = next(iter(record.keys()))
            answer = next(iter(record.values()))

            if question not in all_alternatives or question in flagged_questions:
                continue

            student_substitutions = scrambled_questions[question][
                "substitutions"]

            for keyword in student_substitutions:
                for variant in all_alternatives[question][keyword]:
                    if variant == student_substitutions[keyword]:
                        continue
                    if variant in answer:
                        # check for false positives
                        if variant in scrambled_questions[question]["text"]:
                            continue

                        flagged_questions.add(question)

                        print(
                            "In question {}, Student {} used keyword {} for {}, when they should have used {}"
                            .format(
                                get_name(original_questions[question]),
                                email,
                                variant,
                                keyword,
                                student_substitutions[keyword],
                            ))

                        print(
                            "\tThey wrote `{}`. Their substitutions were: {}".
                            format(" ".join(answer.split()),
                                   student_substitutions))
コード例 #7
0
def substitutions(exam, email, show_all):
    """
    Show the substitutions a particular student received
    """
    original_exam = get_exam(exam=exam)
    exam = get_exam(exam=exam)
    exam = scramble(email, exam, keep_data=True)
    question_substitutions = get_all_substitutions(original_exam, exam)
    questions = extract_questions(exam)
    for question in questions:
        substitutions = question_substitutions[question["id"]]
        if substitutions or show_all:
            print(get_name(question), substitutions)
コード例 #8
0
def bit_distance(observed_points: List[Point], exam_data, email):
    scrambled_exam = scramble(email, loads(dumps(exam_data)))
    expected_points = get_watermark_points(
        scrambled_exam["watermark"]["value"])
    assert len(
        observed_points) >= len(expected_points) / 2, "Too few observed bits"
    costs = []
    for observed_point in observed_points:
        closest = min(expected_points, key=observed_point.dist)
        costs.append(
            observed_point.dist(closest)**2)  # large penalty for misalignment
    costs.sort()
    del costs[-5:]
    return sum(costs) / len(costs)
コード例 #9
0
def find_unexpected_words(exam, logs):
    data = get_exam(exam=exam)
    exam_json = json.dumps(data)
    original_questions = {q["id"]: q for q in extract_questions(json.loads(exam_json))}
    suspected_cheating = []
    for i, (email, log) in enumerate(tqdm(logs)):
        all_alternatives = get_substitutions(data)
        scrambled_questions = {
            q["id"]: q
            for q in extract_questions(
                scramble(email, json.loads(exam_json), keep_data=True), nest_all=True
            )
        }
        flagged_question_variants = set()
        for record in log:
            record.pop("timestamp")
            for question, answer in record.items():
                question = question.split("|")[0]
                if question not in all_alternatives:
                    continue

                student_substitutions = scrambled_questions[question]["substitutions"]

                for keyword in student_substitutions:
                    for variant in all_alternatives[question][keyword]:
                        if variant == student_substitutions[keyword]:
                            continue
                        if (question, keyword, variant) in flagged_question_variants:
                            continue
                        if variant in answer:
                            # check for false positives
                            if variant in scrambled_questions[question]["text"]:
                                continue

                            flagged_question_variants.add((question, keyword, variant))

                            suspected_cheating.append(
                                SuspectedCheating(
                                    get_name(original_questions[question]),
                                    email,
                                    keyword,
                                    student_substitutions[keyword],
                                    variant,
                                    answer,
                                    student_substitutions,
                                )
                            )

    return suspected_cheating
コード例 #10
0
ファイル: api.py プロジェクト: 61a-ide/cs61a-apps
def get_student_question_mapping(student, exam):
    elements = list(extract_questions(exam, include_groups=True))
    for element in elements:
        element["id"] = element.get("id", rand_id())  # add IDs to groups
    elements = {
        element["id"]: get_name(element)
        for element in elements
        if element["type"] != "group" or not is_compressible_group(element)
    }
    old_seed = int(random.random() * 1000000)
    out = [{
        "student_question_name": get_name(element),
        "canonical_question_name": elements[element["id"]],
    } for element in list(
        extract_questions(scramble(student, exam), include_groups=True))]
    random.seed(old_seed)
    return out
コード例 #11
0
ファイル: download.py プロジェクト: cmcmahon226/cs61a-apps
def download(exam, emails_to_download: [str] = None, debug: bool = False):
    exam_json = get_exam(exam=exam)
    exam_json.pop("secret")
    exam_json = json.dumps(exam_json)

    template_questions = list(extract_questions(json.loads(exam_json)))

    total = [["Email"] + [
        question["text"]
        for question in extract_questions(json.loads(exam_json))
    ]]

    email_to_data_map = {}

    if emails_to_download is None:
        roster = get_roster(exam=exam)
        emails_to_download = [email for email, _ in roster]

    i = 1
    for email, response in tqdm(get_submissions(exam=exam),
                                dynamic_ncols=True,
                                desc="Downloading",
                                unit="Exam"):
        i += 1
        if emails_to_download is not None and email not in emails_to_download:
            continue

        if debug and 1 < len(response) < 10:
            tqdm.write(email, response)

        total.append([email])
        for question in template_questions:
            total[-1].append(response.get(question["id"], ""))

        student_questions = list(
            extract_questions(
                scramble(email, json.loads(exam_json), keep_data=True)))

        email_to_data_map[email] = {
            "student_questions": student_questions,
            "responses": response,
        }

    return json.loads(exam_json), template_questions, email_to_data_map, total
コード例 #12
0
ファイル: compile.py プロジェクト: chrononyan/examtool
def compile(exam, json, md, seed, json_out, out):
    """
    Compile one PDF or JSON (from Markdown), unencrypted.
    The exam may be deployed or local (in Markdown or JSON).
    If a seed is specified, it will scramble the exam.
    """
    if not out:
        out = ""

    pathlib.Path(out).mkdir(parents=True, exist_ok=True)

    if json:
        print("Loading exam...")
        exam_data = load(json)
    elif md:
        print("Compiling exam...")
        exam_text_data = md.read()
        exam_data = convert(exam_text_data)
    else:
        print("Fetching exam...")
        exam_data = get_exam(exam=exam)

    if seed:
        print("Scrambling exam...")
        exam_data = scramble(
            seed,
            exam_data,
        )

    if json_out:
        print("Dumping json...")
        dump(exam_data, json_out)
        return

    print("Rendering exam...")
    with render_latex(exam_data, {
            "coursecode": prettify(exam.split("-")[0]),
            "description": "Sample Exam."
    }) as pdf:
        pdf = Pdf.open(BytesIO(pdf))
        pdf.save(os.path.join(out, exam + ".pdf"))
        pdf.close()
コード例 #13
0
ファイル: find_errors.py プロジェクト: pchae97/examtool
db = firestore.Client()
exams = [x.id for x in db.collection("exams").stream()]

for exam in exams:
    print("checking", exam)
    exam_json = json.dumps(get_exam(exam=exam))
    roster = get_roster(exam=exam)

    flagged = set()

    for email, _ in roster:
        template_questions = extract_questions(json.loads(exam_json))
        student_questions = list(
            extract_questions(
                scramble(email, json.loads(exam_json), keep_data=True)))
        student_question_lookup = {q['id']: q for q in student_questions}
        for question in template_questions:
            if question["id"] not in student_question_lookup:
                continue
            if question["type"] not in ["multiple_choice", "select_all"]:
                continue
            if question["id"] in flagged:
                continue

            for i, option in enumerate(question["options"]):
                option["index"] = i

            s = lambda options: sorted(options, key=lambda q: q["text"])

            for a, b in zip(
コード例 #14
0
ファイル: compile.py プロジェクト: 61a-ide/cs61a-apps
def compile(
    exam,
    json,
    md,
    seed,
    subtitle,
    with_solutions,
    exam_type,
    semester,
    json_out,
    merged_md,
    draft,
    out,
):
    """
    Compile one PDF or JSON (from Markdown), unencrypted.
    The exam may be deployed or local (in Markdown or JSON).
    If a seed is specified, it will scramble the exam.
    """
    if not out:
        out = ""

    pathlib.Path(out).mkdir(parents=True, exist_ok=True)

    if json:
        print("Loading exam...")
        exam_data = load(json)
    elif md:
        exam_text_data = md.read()
        if merged_md:
            buff = LineBuffer(exam_text_data)
            handle_imports(buff, path=os.path.dirname(md.name))
            merged_md.write("\n".join(buff.lines))
            return
        print("Compiling exam...")
        exam_data = convert(exam_text_data, path=os.path.dirname(md.name), draft=draft)
    else:
        print("Fetching exam...")
        exam_data = get_exam(exam=exam)

    if seed:
        print("Scrambling exam...")
        exam_data = scramble(seed, exam_data, keep_data=with_solutions)

    def remove_solutions_from_groups(groups):
        for group in groups:
            # if isinstance(group, dict):
            group.pop("solution", None)
            if group.get("type") == "group":
                remove_solutions_from_groups(group.get("elements", []))

    if not seed and not with_solutions:
        print("Removing solutions...")
        groups = exam_data.get("groups", [])
        remove_solutions_from_groups(groups)

    if json_out:
        print("Dumping json...")
        dump(exam_data, json_out, indent=4, sort_keys=True)
        return

    print("Rendering exam...")
    settings = {
        "coursecode": prettify(exam.split("-")[0]),
        "description": subtitle,
        "examtype": exam_type,
        "semester": semester,
    }
    if seed:
        settings["emailaddress"] = sanitize_email(seed)
    with render_latex(exam_data, settings) as pdf:
        pdf = Pdf.open(BytesIO(pdf))
        pdf.save(os.path.join(out, exam + ".pdf"))
        pdf.close()
コード例 #15
0
ファイル: main.py プロジェクト: 61a-ide/cs61a-apps
def index(request):
    try:
        if getenv("ENV") == "dev":
            update_cache()

        db = SafeFirestore()

        if request.path.endswith("main.js"):
            return main_js

        if request.path.endswith("list_exams"):
            return jsonify(
                db.collection("exams").document("all").get().to_dict()
                ["exam-list"])

        if request.path == "/" or request.json is None:
            return main_html

        if request.path.endswith("get_exam"):
            exam = request.json["exam"]
            email = get_email(request)
            ref = db.collection(exam).document(email)
            try:
                answers = ref.get().to_dict() or {}
            except NotFound:
                answers = {}

            deadline = get_deadline(exam, email, db)

            exam_data = get_exam_dict(exam, db)
            exam_data = scramble(
                email,
                exam_data,
            )

            # 120 second grace period in case of network latency or something
            if deadline + 120 < time.time():
                abort(401)
                return

            return jsonify({
                "success":
                True,
                "exam":
                exam,
                "publicGroup":
                exam_data["public"],
                "privateGroups": (Fernet(exam_data["secret"]).encrypt_at_time(
                    json.dumps(exam_data["groups"]).encode("ascii"),
                    0).decode("ascii")),
                "answers":
                answers,
                "deadline":
                deadline,
                "timestamp":
                time.time(),
            })

        if request.path.endswith("submit_question"):
            exam = request.json["exam"]
            question_id = request.json["id"]
            value = request.json["value"]
            sent_time = request.json.get("sentTime", 0)
            email = get_email(request)

            db.collection(exam).document(email).collection(
                "log").document().set({
                    "timestamp": time.time(),
                    "sentTime": sent_time,
                    question_id: value
                })

            deadline = get_deadline(exam, email, db)

            if deadline + 120 < time.time():
                abort(401)
                return

            recency_ref = (db.collection(exam).document(email).collection(
                "recency").document(question_id))
            try:
                recency = recency_ref.get().to_dict() or {}
            except NotFound:
                recency = {}

            recent_time = recency.get("sentTime", -1)
            if recent_time - 300 <= sent_time <= recent_time:
                # the current request was delayed and is now out of date
                abort(409)
                return

            recency_ref.set({"sentTime": sent_time})

            db.collection(exam).document(email).set({question_id: value},
                                                    merge=True)
            return jsonify({"success": True})

        if getenv("ENV") == "dev" and "alerts" in request.path:
            from alerts import index as alerts_index

            return alerts_index(request)

    except:
        print(dict(request.json))
        return jsonify({"success": False})

    return request.path
コード例 #16
0
ファイル: find_errors.py プロジェクト: cmcmahon226/cs61a-apps

db = firestore.Client()
exams = [x.id for x in db.collection("exams").stream()]

for exam in exams:
    print("checking", exam)
    exam_json = json.dumps(get_exam(exam=exam))
    roster = get_roster(exam=exam)

    flagged = set()

    for email, _ in roster:
        template_questions = extract_questions(json.loads(exam_json))
        student_questions = list(
            extract_questions(scramble(email, json.loads(exam_json), keep_data=True))
        )
        student_question_lookup = {q["id"]: q for q in student_questions}
        for question in template_questions:
            if question["id"] not in student_question_lookup:
                continue
            if question["type"] not in ["multiple_choice", "select_all"]:
                continue
            if question["id"] in flagged:
                continue

            for i, option in enumerate(question["options"]):
                option["index"] = i

            s = lambda options: sorted(options, key=lambda q: q["text"])
コード例 #17
0
ファイル: main.py プロジェクト: cmcmahon226/cs61a-apps
def index(request):
    try:
        if getenv("ENV") == "dev":
            update_cache()

        db = SafeFirestore()

        if request.path.endswith("main.js"):
            return main_js

        if request.path.endswith("list_exams"):
            email, is_admin = get_email(request)
            all_exams = list_exams(db)
            roster_exams = get_roster_exams(email, db)
            valid = [exam for exam in all_exams if exam in roster_exams]
            return jsonify(valid)

        if request.path.endswith("watermark.svg"):
            watermark = create_watermark(
                int(request.args["seed"]),
                brightness=int(request.args["brightness"]),
            )
            return Response(watermark, mimetype="image/svg+xml")

        if request.path == "/" or request.json is None:
            return main_html

        if request.path.endswith("get_exam"):
            exam = request.json["exam"]
            email, is_admin = get_email(request)
            ref = db.collection(exam).document(email)
            try:
                answers = ref.get().to_dict() or {}
            except NotFound:
                answers = {}

            student_data = get_student_data(exam, email, db)
            deadline = student_data["deadline"]
            no_watermark = student_data.get("no_watermark", False)

            exam_data = get_exam_dict(exam, db)
            exam_data = scramble(
                email,
                exam_data,
            )

            # 120 second grace period in case of network latency or something
            if deadline + 120 < time.time() and not is_admin:
                abort(401)
                return

            return jsonify({
                "success":
                True,
                "exam":
                exam,
                "publicGroup":
                exam_data["public"],
                "privateGroups": (Fernet(exam_data["secret"]).encrypt_at_time(
                    json.dumps(exam_data["groups"]).encode("ascii"),
                    0).decode("ascii")),
                # `or None` is to handle the case of watermark={}, which is truthy in JS
                "watermark": (None if no_watermark else
                              (exam_data.get("watermark") or None)),
                "answers":
                answers,
                "deadline":
                deadline,
                "timestamp":
                time.time(),
            })

        if request.path.endswith("submit_question"):
            exam = request.json["exam"]
            question_id = request.json["id"]
            value = request.json["value"]
            sent_time = request.json.get("sentTime", 0)
            email, is_admin = get_email(request)

            if exam not in list_exams(db):
                abort(401)

            db.collection(exam).document(email).collection(
                "log").document().set({
                    "timestamp": time.time(),
                    "sentTime": sent_time,
                    question_id: value
                })

            deadline = get_deadline(exam, email, db)

            if deadline + 120 < time.time() and not is_admin:
                abort(401)
                return

            recency_ref = (db.collection(exam).document(email).collection(
                "recency").document(question_id))
            try:
                recency = recency_ref.get().to_dict() or {}
            except NotFound:
                recency = {}

            recent_time = recency.get("sentTime", -1)
            if recent_time - 300 <= sent_time <= recent_time:
                # the current request was delayed and is now out of date
                abort(409)
                return

            recency_ref.set({"sentTime": sent_time})

            db.collection(exam).document(email).set({question_id: value},
                                                    merge=True)
            return jsonify({"success": True})

        if request.path.endswith("backup_all"):
            exam = request.json["exam"]
            if exam not in list_exams(db):
                abort(401)
            email, is_admin = get_email(request)
            history = request.json["history"]
            snapshot = request.json["snapshot"]
            db.collection(exam).document(email).collection(
                "history").document().set({
                    "timestamp": time.time(),
                    "history": history,
                    "snapshot": snapshot
                })
            return jsonify({"success": True})

        if request.path.endswith("log_event"):
            exam = request.json["exam"]
            email, is_admin = get_email(request)
            if exam not in list_exams(db):
                abort(401)
            event = request.json["event"]
            db.collection(exam).document(email).collection(
                "history").document().set({
                    "timestamp": time.time(),
                    "event": event,
                })
            return jsonify({"success": True})

        if getenv("ENV") == "dev" and "alerts" in request.path:
            from alerts import index as alerts_index

            return alerts_index(request)

    except Exception as e:
        if getenv("ENV") == "dev":
            raise
        print(e)
        print(dict(request.json))
        return jsonify({"success": False})

    return request.path
コード例 #18
0
def index(request):
    try:
        if getenv("ENV") == "dev":
            update_cache()

        db = SafeFirestore()

        if request.path.endswith("main.js"):
            return main_js

        if request.path.endswith("list_exams"):
            return jsonify(
                db.collection("exam-alerts").document("all").get().to_dict()
                ["exam-list"])

        if request.path == "/" or request.json is None:
            return main_html

        exam = request.json["exam"]
        course = exam.split("-")[0]
        prev_latest_timestamp = float(request.json["latestTimestamp"])

        def get_message(id):
            message = (db.collection("exam-alerts").document(exam).collection(
                "messages").document(id).get())

            return {
                **message.to_dict(),
                "id": message.id,
            }

        student_reply = False

        if request.path.endswith("ask_question"):
            email = get_email(request)
            student_question_name = request.json["question"]
            message = request.json["message"]

            student_data = get_student_data(db, email, exam)

            if student_question_name is not None:
                canonical_question_name = get_canonical_question_name(
                    student_data, student_question_name)
                if canonical_question_name is None:
                    return abort(400)
            else:
                canonical_question_name = None

            db.collection("exam-alerts").document(exam).collection(
                "messages").document().set(
                    dict(
                        question=canonical_question_name,
                        message=message,
                        email=email,
                        timestamp=time.time(),
                    ))
            student_reply = True

        if request.path.endswith("fetch_data") or student_reply:
            received_audio = request.json.get("receivedAudio")
            email = get_email(request)
            exam_data = db.collection("exam-alerts").document(
                exam).get().to_dict()
            student_data = get_student_data(db, email, exam)
            announcements = list(
                db.collection("exam-alerts").document(exam).collection(
                    "announcements").stream())
            messages = [{
                **message.to_dict(), "id": message.id
            } for message in (
                db.collection("exam-alerts").document(exam).collection(
                    "messages").where("timestamp", ">", prev_latest_timestamp
                                      ).where("email", "==", email).stream())
                        if message.to_dict()["email"] == email]

            messages, latest_timestamp = group_messages(messages, get_message)
            messages = messages[email]
            latest_timestamp = max(latest_timestamp, prev_latest_timestamp)

            for message in messages:
                if message["question"] is not None:
                    message["question"] = get_student_question_name(
                        student_data, message["question"])

            return jsonify({
                "success":
                True,
                "exam_type":
                "ok-exam",
                "enableClarifications":
                exam_data.get("enable_clarifications", False),
                "startTime":
                student_data["start_time"],
                "endTime":
                student_data["end_time"],
                "timestamp":
                time.time(),
                "questions": [
                    question["student_question_name"]
                    for question in student_data["questions"]
                ] if time.time() > student_data["start_time"] else [],
                "announcements":
                get_announcements(
                    student_data,
                    announcements,
                    messages,
                    received_audio,
                    lambda x: (db.collection("exam-alerts").document(
                        exam).collection("announcement_audio").document(x).get(
                        ).to_dict() or {}).get("audio"),
                ),
                "messages":
                sorted(
                    [{
                        "id": message["id"],
                        "message": message["message"],
                        "timestamp": message["timestamp"],
                        "question": message["question"] or "Overall Exam",
                        "responses": message["responses"],
                    } for message in messages],
                    key=lambda message: message["timestamp"],
                    reverse=True,
                ),
                "latestTimestamp":
                latest_timestamp,
            })

        # only staff endpoints from here onwards
        email = (get_email_from_secret(request.json["secret"])
                 if "secret" in request.json else get_email(request))
        if not is_admin(email, course):
            abort(401)

        if request.path.endswith("fetch_staff_data"):
            pass
        elif request.path.endswith("add_announcement"):
            announcement = request.json["announcement"]
            announcement["timestamp"] = time.time()
            ref = (db.collection("exam-alerts").document(exam).collection(
                "announcements").document())
            ref.set(announcement)
            spoken_message = announcement.get("spoken_message",
                                              announcement["message"])

            if spoken_message:
                audio = generate_audio(spoken_message)
                db.collection("exam-alerts").document(exam).collection(
                    "announcement_audio").document(ref.id).set(
                        {"audio": audio})

        elif request.path.endswith("clear_announcements"):
            clear_collection(
                db,
                db.collection("exam-alerts").document(exam).collection(
                    "announcements"),
            )
            clear_collection(
                db,
                db.collection("exam-alerts").document(exam).collection(
                    "announcement_audio"),
            )
        elif request.path.endswith("delete_announcement"):
            target = request.json["id"]
            db.collection("exam-alerts").document(exam).collection(
                "announcements").document(target).delete()
        elif request.path.endswith("send_response"):
            message_id = request.json["id"]
            reply = request.json["reply"]
            message = (db.collection("exam-alerts").document(exam).collection(
                "messages").document(message_id).get())
            ref = (db.collection("exam-alerts").document(exam).collection(
                "messages").document())
            ref.set({
                "timestamp": time.time(),
                "email": message.to_dict()["email"],
                "reply_to": message.id,
                "message": reply,
            })
            audio = generate_audio(
                reply, prefix="A staff member sent the following reply: ")
            db.collection("exam-alerts").document(exam).collection(
                "announcement_audio").document(ref.id).set({"audio": audio})
        elif request.path.endswith("get_question"):
            question_title = request.json["id"]
            student = request.json["student"]
            student_data = get_student_data(db, student, exam)
            question_title = get_student_question_name(student_data,
                                                       question_title)
            exam = db.collection("exams").document(exam).get().to_dict()
            questions = extract_questions(scramble(student, exam),
                                          include_groups=True)
            for question in questions:
                if get_name(question).strip() == question_title.strip():
                    return jsonify({"success": True, "question": question})
            abort(400)
        else:
            abort(404)

        # (almost) all staff endpoints return an updated state
        exam_data = db.collection("exam-alerts").document(exam).get().to_dict()
        announcements = sorted(
            ({
                "id": announcement.id,
                **announcement.to_dict()
            } for announcement in db.collection("exam-alerts").document(
                exam).collection("announcements").stream()),
            key=lambda announcement: announcement["timestamp"],
            reverse=True,
        )
        grouped_messages, latest_timestamp = group_messages(
            [{
                **message.to_dict(), "id": message.id
            } for message in db.collection(
                "exam-alerts").document(exam).collection("messages").where(
                    "timestamp", ">", prev_latest_timestamp).stream()],
            get_message,
        )
        latest_timestamp = max(prev_latest_timestamp, latest_timestamp)
        messages = sorted(
            [{
                "email": email,
                **message
            } for email, messages in grouped_messages.items()
             for message in messages],
            key=lambda message: (
                len(message["responses"]) > 0,
                -message["timestamp"],
                message["email"],
            ),
        )

        return jsonify({
            "success": True,
            "exam": exam_data,
            "announcements": announcements,
            "messages": messages,
            "latestTimestamp": latest_timestamp,
        })

    except Exception as e:
        if getenv("ENV") == "dev":
            raise
        print(e)
        print(dict(request.json))
        return jsonify({"success": False})
コード例 #19
0
ファイル: main.py プロジェクト: chrononyan/examtool-web
def index(request):
    try:
        if getenv("ENV") == "dev":
            update_cache()

        db = firestore.Client()

        if request.path.endswith("main.js"):
            return main_js

        if request.path == "/":
            return main_html

        if request.path.endswith("list_exams"):
            return jsonify(
                db.collection("exams").document("all").get().to_dict()
                ["exam-list"])

        if request.path.endswith("get_exam"):
            exam = request.json["exam"]
            email = get_email(request)
            ref = db.collection(exam).document(email)
            try:
                answers = ref.get().to_dict() or {}
            except NotFound:
                answers = {}

            deadline = get_deadline(exam, email, db)

            exam_data = get_exam_dict(exam, db)
            exam_data = scramble(
                email,
                exam_data,
            )

            # 120 second grace period in case of network latency or something
            if deadline + 120 < time.time():
                abort(401)
                return

            return jsonify({
                "success":
                True,
                "exam":
                exam,
                "publicGroup":
                exam_data["public"],
                "privateGroups": (Fernet(exam_data["secret"]).encrypt(
                    json.dumps(
                        exam_data["groups"]).encode("ascii")).decode("ascii")),
                "answers":
                answers,
                "deadline":
                deadline,
                "timestamp":
                time.time(),
            })

        if request.path.endswith("submit_question"):
            exam = request.json["exam"]
            question_id = request.json["id"]
            value = request.json["value"]
            email = get_email(request)

            db.collection(exam).document(email).collection(
                "log").document().set({
                    "timestamp": time.time(),
                    question_id: value
                })

            deadline = get_deadline(exam, email, db)

            if deadline + 120 < time.time():
                abort(401)
                return

            db.collection(exam).document(email).set({question_id: value},
                                                    merge=True)
            return jsonify({"success": True})
    except:
        print(dict(request.json))
        return jsonify({"success": False})

    return request.path
コード例 #20
0
def deploy(exam, json, roster, start_time, default_deadline):
    """
    Deploy an exam to the website. You must specify an exam JSON and associated roster CSV.
    You can deploy the JSON multiple times and the password will remain unchanged.
    """
    json = json.read()
    roster = csv.reader(roster, delimiter=",")

    exam_content = loads(json)

    exam_content["default_deadline"] = default_deadline
    exam_content["secret"] = Fernet.generate_key().decode("utf-8")

    try:
        exam_content["secret"] = get_exam(exam=exam)["secret"]
    except:
        pass

    set_exam(exam=exam, json=exam_content)

    next(roster)  # ditch headers
    roster = list(roster)
    set_roster(exam=exam, roster=roster)

    print("Exam uploaded with password:"******"secret"][:-1])

    print("Exam deployed to https://exam.cs61a.org/{}".format(exam))

    print("Initializing announcements...")
    elements = list(extract_questions(exam_content, include_groups=True))
    for element in elements:
        element["id"] = element.get("id", rand_id())  # add IDs to groups
    elements = {
        element["id"]: get_name(element)
        for element in elements
        if element["type"] != "group" or not is_compressible_group(element)
    }
    json = dumps(exam_content)  # re-serialize with group IDs

    students = [{
        "email":
        email,
        "questions": [{
            "start_time": start_time,
            "end_time": int(deadline),
            "student_question_name": get_name(element),
            "canonical_question_name": elements[element["id"]],
        } for element in list(
            extract_questions(scramble(email, loads(json)),
                              include_groups=True))],
        "start_time":
        start_time,
        "end_time":
        int(deadline),
    } for email, deadline in roster]

    print("Updating announcements roster with {} students...".format(
        len(students)))

    for i in range(0, len(students), 100):
        print("Uploading from student #{} to #{}".format(
            i, min(i + 100, len(students))))
        process_ok_exam_upload(
            exam=exam,
            data={
                "students":
                students[i:i + 100],
                "questions": [{
                    "canonical_question_name": name
                } for name in elements.values()],
            },
            clear=i == 0,
        )

    print("Announcements deployed to https://announcements.cs61a.org")