Пример #1
0
def enter_grades_confirm():
    try:
        f_step = request.form.get("f_step")
        if f_step not in ("1", "2"):
            fail_validation("Enum out of range (probably a programming error)")
        step = int(f_step)
        assignment_name = request.form.get("f_assignment")
        if not assignment_name:
            fail_validation("Assignment name is required")
        assignment = get_assignment_by_name(assignment_name)
        if assignment is None:
            fail_validation("Assignment not found: %s" % assignment_name)
        min_score, max_score = assignment.min_score, assignment.max_score

        description = request.form.get("f_description")
        if not description:
            fail_validation("Transaction description is required")

        transaction_source = github_username()

        entries = []
        user_id_set = set()

        with DbCursor() as c:
            valid_identifiers, ambiguous_identifiers = get_valid_ambiguous_identifiers(c)

            def try_add(f_student, f_score, f_slipunits):
                if not any((f_student, f_score, f_slipunits)):
                    return
                elif not f_student and (f_score or f_slipunits):
                    fail_validation("Expected student SID, login, or name, but none provided")
                elif f_student in ambiguous_identifiers:
                    fail_validation("The identifier '%s' is ambiguous. Please use another." %
                                    f_student)
                else:
                    if step == 1:
                        students = get_users_by_identifier(c, f_student)
                    elif step == 2:
                        student = get_user_by_id(c, f_student)
                        # Let the usual error handling take care of this case
                        students = [student] if student else []
                    if not students:
                        fail_validation("Student or group not found: %s" % f_student)
                    for student in students:
                        user_id, student_name, _, _, _, _ = student
                        if user_id in user_id_set:
                            fail_validation("Student was listed more than once: %s" % student_name)
                        try:
                            score = float_or_none(f_score)
                        except ValueError:
                            fail_validation("Not a valid score: %s" % f_score)
                        try:
                            slipunits = int_or_none(f_slipunits)
                        except ValueError:
                            fail_validation("Slip %s amount not valid: %s" % (slip_unit_name_plural,
                                                                              f_slipunits))
                        if slipunits is not None and slipunits < 0:
                            fail_validation("Slip %s cannot be negative" % slip_unit_name_plural)
                        if score is not None and not min_score <= score <= max_score:
                            fail_validation("Score is out of allowed range: %s (Range: %s to %s)" %
                                            (f_score, str(min_score), str(max_score)))
                        entries.append([user_id, score, slipunits])
                        user_id_set.add(user_id)

            if step == 1:
                f_students = request.form.getlist("f_student")
                f_scores = request.form.getlist("f_score")
                f_slipunitss = request.form.getlist("f_slipunits")
                if not same_length(f_students, f_scores, f_slipunitss):
                    fail_validation("Different numbers of students, scores, and slip %s " +
                                    "reported. Browser bug?" % slip_unit_name_plural)
                for f_student, f_score, f_slipunits in zip(f_students, f_scores, f_slipunitss):
                    try_add(f_student, f_score, f_slipunits)

            f_csv = request.form.get("f_csv", "")
            for row in csv.reader(StringIO.StringIO(f_csv), delimiter=",", quotechar='"'):
                if len(row) != 3:
                    fail_validation("CSV rows must contain 3 entries")
                try_add(*row)

            if not entries:
                fail_validation("No grade or slip %s changes entered" % slip_unit_name_plural)

            if step == 1:
                c.execute('''SELECT id, name, sid, login, github FROM users
                             WHERE id IN (%s)''' % (",".join(["?"] * len(entries))),
                          [user_id for user_id, _, _ in entries])
                students = c.fetchall()
                details_user = {}
                for user_id, name, sid, login, github in students:
                    details_user[user_id] = [name, sid, login, github]
                c.execute('''SELECT user, score, slipunits, updated FROM grades
                             WHERE assignment = ? AND user IN (%s)''' %
                          (",".join(["?"] * len(entries))),
                          [assignment.name] + [user_id for user_id, _, _ in entries])
                grades = c.fetchall()
                details_grade = {}
                for user_id, score, slipunits, updated in grades:
                    details_grade[user_id] = [score, slipunits, updated]
                entries_details = []
                for entry in entries:
                    user_id = entry[0]
                    entry_details = (entry + details_user.get(user_id, [None] * 4)
                                           + details_grade.get(user_id, [None] * 3))
                    entries_details.append(entry_details)
            elif step == 2:
                transaction_number = get_next_autoincrementing_value(
                    c, "enter_grades_last_transaction_number")
                transaction_name = "enter-grades-%s" % transaction_number
                for user_id, score, slipunits in entries:
                    assign_grade_batch(c, [user_id], assignment.name, score, slipunits,
                                       transaction_name, description, transaction_source,
                                       manual=True, dont_lower=False)
        if step == 1:
            entries_csv = StringIO.StringIO()
            entries_csv_writer = csv.writer(entries_csv, delimiter=",", quotechar='"')
            for entry in entries:
                entries_csv_writer.writerow(entry)
            return render_template("ta/enter_grades_confirm.html",
                                   entries_details=entries_details,
                                   entries_csv=entries_csv.getvalue(),
                                   assignment_name=assignment.name,
                                   description=description,
                                   full_score=assignment.full_score,
                                   **_template_common())
        elif step == 2:
            if len(entries) == 1:
                flash("1 grade committed", "success")
            else:
                flash("%d grades committed" % len(entries), "success")
            return redirect(url_for("ta.enter_grades"))
    except ValidationError as e:
        return redirect_with_error(url_for("ta.enter_grades"), e)
Пример #2
0
def enter_grades_confirm():
    try:
        f_step = request.form.get("f_step")
        if f_step not in ("1", "2"):
            fail_validation("Enum out of range (probably a programming error)")
        step = int(f_step)
        assignment_name = request.form.get("f_assignment")
        if not assignment_name:
            fail_validation("Assignment name is required")
        assignment = get_assignment_by_name(assignment_name)
        if assignment is None:
            fail_validation("Assignment not found: %s" % assignment_name)
        min_score, max_score = assignment.min_score, assignment.max_score

        description = request.form.get("f_description")
        if not description:
            fail_validation("Transaction description is required")

        transaction_source = github_username()

        entries = []
        user_id_set = set()

        with DbCursor() as c:
            valid_identifiers, ambiguous_identifiers = get_valid_ambiguous_identifiers(
                c)

            def try_add(f_student, f_score, f_slipunits):
                if not any((f_student, f_score, f_slipunits)):
                    return
                elif not f_student and (f_score or f_slipunits):
                    fail_validation(
                        "Expected student SID, login, or name, but none provided"
                    )
                elif f_student in ambiguous_identifiers:
                    fail_validation(
                        "The identifier '%s' is ambiguous. Please use another."
                        % f_student)
                else:
                    if step == 1:
                        students = get_users_by_identifier(c, f_student)
                    elif step == 2:
                        student = get_user_by_id(c, f_student)
                        # Let the usual error handling take care of this case
                        students = [student] if student else []
                    if not students:
                        fail_validation("Student or group not found: %s" %
                                        f_student)
                    for student in students:
                        user_id, student_name, _, _, _, _ = student
                        if user_id in user_id_set:
                            fail_validation(
                                "Student was listed more than once: %s" %
                                student_name)
                        try:
                            score = float_or_none(f_score)
                        except ValueError:
                            fail_validation("Not a valid score: %s" % f_score)
                        try:
                            slipunits = int_or_none(f_slipunits)
                        except ValueError:
                            fail_validation(
                                "Slip %s amount not valid: %s" %
                                (slip_unit_name_plural, f_slipunits))
                        if slipunits is not None and slipunits < 0:
                            fail_validation("Slip %s cannot be negative" %
                                            slip_unit_name_plural)
                        if score is not None and not min_score <= score <= max_score:
                            fail_validation(
                                "Score is out of allowed range: %s (Range: %s to %s)"
                                % (f_score, str(min_score), str(max_score)))
                        entries.append([user_id, score, slipunits])
                        user_id_set.add(user_id)

            if step == 1:
                f_students = request.form.getlist("f_student")
                f_scores = request.form.getlist("f_score")
                f_slipunitss = request.form.getlist("f_slipunits")
                if not same_length(f_students, f_scores, f_slipunitss):
                    fail_validation(
                        "Different numbers of students, scores, and slip %s " +
                        "reported. Browser bug?" % slip_unit_name_plural)
                for f_student, f_score, f_slipunits in zip(
                        f_students, f_scores, f_slipunitss):
                    try_add(f_student, f_score, f_slipunits)

            f_csv = request.form.get("f_csv", "")
            for row in csv.reader(StringIO.StringIO(f_csv),
                                  delimiter=",",
                                  quotechar='"'):
                if len(row) != 3:
                    fail_validation("CSV rows must contain 3 entries")
                try_add(*row)

            if not entries:
                fail_validation("No grade or slip %s changes entered" %
                                slip_unit_name_plural)

            if step == 1:
                c.execute(
                    '''SELECT id, name, sid, login, github FROM users
                             WHERE id IN (%s)''' %
                    (",".join(["?"] * len(entries))),
                    [user_id for user_id, _, _ in entries])
                students = c.fetchall()
                details_user = {}
                for user_id, name, sid, login, github in students:
                    details_user[user_id] = [name, sid, login, github]
                c.execute(
                    '''SELECT user, score, slipunits, updated FROM grades
                             WHERE assignment = ? AND user IN (%s)''' %
                    (",".join(["?"] * len(entries))),
                    [assignment.name] + [user_id for user_id, _, _ in entries])
                grades = c.fetchall()
                details_grade = {}
                for user_id, score, slipunits, updated in grades:
                    details_grade[user_id] = [score, slipunits, updated]
                entries_details = []
                for entry in entries:
                    user_id = entry[0]
                    entry_details = (entry +
                                     details_user.get(user_id, [None] * 4) +
                                     details_grade.get(user_id, [None] * 3))
                    entries_details.append(entry_details)
            elif step == 2:
                transaction_number = get_next_autoincrementing_value(
                    c, "enter_grades_last_transaction_number")
                transaction_name = "enter-grades-%s" % transaction_number
                for user_id, score, slipunits in entries:
                    assign_grade_batch(c, [user_id],
                                       assignment.name,
                                       score,
                                       slipunits,
                                       transaction_name,
                                       description,
                                       transaction_source,
                                       manual=True,
                                       dont_lower=False)
        if step == 1:
            entries_csv = StringIO.StringIO()
            entries_csv_writer = csv.writer(entries_csv,
                                            delimiter=",",
                                            quotechar='"')
            for entry in entries:
                entries_csv_writer.writerow(entry)
            return render_template("ta/enter_grades_confirm.html",
                                   entries_details=entries_details,
                                   entries_csv=entries_csv.getvalue(),
                                   assignment_name=assignment.name,
                                   description=description,
                                   full_score=assignment.full_score,
                                   **_template_common())
        elif step == 2:
            if len(entries) == 1:
                flash("1 grade committed", "success")
            else:
                flash("%d grades committed" % len(entries), "success")
            return redirect(url_for("ta.enter_grades"))
    except ValidationError as e:
        return redirect_with_error(url_for("ta.enter_grades"), e)
Пример #3
0
    def _process_job(self, job):
        build_name = job.build_name
        with self.lock:
            self.status = build_name
            self.updated = now()

        # Mark the job as In Progress
        while True:
            try:
                with DbCursor() as c:
                    c.execute('''SELECT source, `commit`, message, job, started FROM builds
                                 WHERE build_name = ? AND status = ? LIMIT 1''',
                              [build_name, QUEUED])
                    row = c.fetchone()
                    if row is None:
                        self._log("Build %s was missing from the database. Skipping." % build_name)
                        return
                    source, commit, message, job_name, started = row
                    owners = get_repo_owners(c, source)
                    owner_emails = {owner: email for owner, (_, _, _, _, _, email)
                                    in get_users_by_ids(c, owners).items()}
                    c.execute("UPDATE builds SET status = ?, updated = ? WHERE build_name = ?",
                              [IN_PROGRESS, now_str(), build_name])
                    break
            except apsw.Error:
                self._log("Exception raised while setting status to IN_PROGRESS. Retrying...",
                          exc=True)
                logging.exception("Failed to retrieve next dockergrader job")

        self._log("Started building %s" % build_name)
        try:
            # if the job doesn't exist for some reason, the resulting TypeError will be caught
            # and logged
            assignment = get_assignment_by_name(job_name)
            due_date = assignment.due_date
            job_handler = get_job(job_name)
            log, score = job_handler(source, commit)
            # Ignore any special encoding inside the log, and just treat it as a bytes
            log = buffer(log)
            min_score, max_score = assignment.min_score, assignment.max_score
            full_score = assignment.full_score
            if score < min_score or score > max_score:
                raise ValueError("A score of %s is not in the acceptable range of %f to %f" %
                                 (str(score), min_score, max_score))
        except JobFailedError as e:
            self._log("Failed %s with JobFailedError" % build_name, exc=True)
            with DbCursor() as c:
                c.execute('''UPDATE builds SET status = ?, updated = ?, log = ?
                             WHERE build_name = ?''', [FAILED, now_str(), str(e), build_name])
            if config.mailer_enabled:
                try:
                    for owner in owners:
                        email = owner_emails.get(owner)
                        if not email:
                            continue
                        subject = "%s failed to complete" % build_name
                        send_template("build_failed", email, subject, build_name=build_name,
                                      job_name=job_name, source=source, commit=commit,
                                      message=message, error_message=str(e))
                except Exception:
                    self._log("Exception raised while reporting JobFailedError", exc=True)
                    logging.exception("Exception raised while reporting JobFailedError")
                else:
                    self._log("JobFailedError successfully reported via email")
            return
        except Exception as e:
            self._log("Exception raised while building %s" % build_name, exc=True)
            logging.exception("Internal error within build %s" % build_name)
            with DbCursor() as c:
                c.execute('''UPDATE builds SET status = ?, updated = ?, log = ?
                             WHERE build_name = ?''',
                          [FAILED, now_str(), "Build failed due to an internal error.", build_name])
            return

        self._log("Autograder build %s complete (score: %s)" % (build_name, str(score)))

        while True:
            try:
                with DbCursor() as c:
                    c.execute('''UPDATE builds SET status = ?, score = ?, updated = ?,
                                 log = ? WHERE build_name = ?''',
                              [SUCCESS, score, now_str(), log, build_name])
                    slipunits = slip_units(due_date, started)
                    affected_users = assign_grade_batch(c, owners, job_name, float(score),
                                                        slipunits, build_name, "Automatic build.",
                                                        "autograder", dont_lower=True)
                    break
            except apsw.Error:
                self._log("Exception raised while assigning grades", exc=True)
                logging.exception("Failed to update build %s after build completed" % build_name)
                return

        if config.mailer_enabled:
            try:
                for owner in owners:
                    email = owner_emails.get(owner)
                    if not email:
                        continue
                    subject = "%s complete - score %s / %s" % (build_name, str(score),
                                                               str(full_score))
                    if owner not in affected_users:
                        subject += " (no effect on grade)"
                    else:
                        if slipunits == 1:
                            subject += " (1 %s used)" % config.slip_unit_name_singular
                        elif slipunits > 0:
                            subject += " (%s slip %s used)" % (str(slipunits),
                                                               config.slip_unit_name_plural)
                    send_template("build_finished", email, subject, build_name=build_name,
                                  job_name=job_name, score=score, full_score=str(full_score),
                                  slipunits=slipunits, log=log, source=source, commit=commit,
                                  message=message, affected=(owner in affected_users))
            except Exception:
                self._log("Exception raised while reporting grade", exc=True)
                logging.exception("Exception raised while reporting grade")
            else:
                self._log("Grade successfully reported via email")