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)
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)
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")