def staff_add_extension(netid, cid, aid): assignment = db.get_assignment(cid, aid) if not assignment: return util.error( "Invalid course or assignment. Please try again.") if util.check_missing_fields(request.form, "netids", "max_runs", "start", "end"): return util.error("Missing fields. Please try again.") student_netids = request.form["netids"].replace( " ", "").lower().split(",") for student_netid in student_netids: if not util.valid_id(student_netid) or not verify_student( student_netid, cid): return util.error( f"Invalid or non-existent student NetID: {student_netid}" ) try: max_runs = int(request.form["max_runs"]) if max_runs < 1: return util.error("Max Runs must be a positive integer.") except ValueError: return util.error("Max Runs must be a positive integer.") start = util.parse_form_datetime(request.form["start"]).timestamp() end = util.parse_form_datetime(request.form["end"]).timestamp() if start >= end: return util.error("Start must be before End.") for student_netid in student_netids: db.add_extension(cid, aid, student_netid, max_runs, start, end) return util.success("")
def demote_course_admin(netid, cid): staff_id = request.form.get('netid') if not verify_staff(staff_id, cid) or not verify_admin( staff_id, cid): return util.error(f"'{staff_id}' is not a admin") db.remove_admin_from_course(cid, staff_id) return util.success( f"Successfully removed '{staff_id}' from admin")
def staff_get_workers(netid, cid): if not verify_staff(netid, cid): return abort(HTTPStatus.FORBIDDEN) workers = bw_api.get_workers(cid) if workers is not None: return util.success(jsonify(workers), HTTPStatus.OK) return util.error("")
def promote_course_staff(netid, cid): staff_id = request.form.get('netid') if not verify_staff(staff_id, cid): return util.error(f"'{staff_id}' is not a staff") result = db.add_admin_to_course(cid, staff_id) if none_modified(result): return util.error(f"'{staff_id}' is already an admin") return util.success(f"Successfully made '{staff_id}' admin")
def staff_get_run_log(netid, cid, aid, run_id): if not verify_staff(netid, cid): return abort(HTTPStatus.FORBIDDEN) log = bw_api.get_grading_run_log(cid, run_id) if log: return util.success(jsonify(log), HTTPStatus.OK) return util.error("")
def staff_delete_scheduled_run(netid, cid, aid, run_id): sched_run = db.get_scheduled_run(cid, aid, run_id) if sched_run is None: return util.error("Cannot find scheduled run") sched_api.delete_scheduled_run(sched_run["scheduled_run_id"]) if not db.delete_scheduled_run(cid, aid, run_id): return util.error( "Failed to delete scheduled run. Please try again") return util.success("")
def add_course_staff(netid, cid): new_staff_id = request.form.get('netid').lower() if new_staff_id is None: return util.error("Cannot find netid field") if not util.is_valid_netid(new_staff_id): return util.error(f"Poorly formatted NetID: '{new_staff_id}'") result = db.add_staff_to_course(cid, str(new_staff_id)) if none_modified(result): return util.error( f"'{new_staff_id}' is already a course staff") return util.success(f"Successfully added {new_staff_id}")
def staff_delete_extension(netid, cid, aid): extension_id = request.form["_id"] delete_result = db.delete_extension(extension_id) if delete_result is None: return util.error( "Invalid extension, please refresh the page.") if delete_result.deleted_count != 1: return util.error("Failed to delete extension.") return util.success("")
def admin_update_roster(netid, cid): netids = request.form["roster"].strip().lower().split("\n") for i, student_id in enumerate(netids): if not util.is_valid_netid(student_id): return util.error( f"Poorly formatted NetID on line {i + 1}: '{student_id}'" ) db.overwrite_student_roster(cid, netids) return util.success("Successfully updated roster.", HTTPStatus.OK)
def add_assignment(netid, cid): missing = util.check_missing_fields( request.form, *[ "aid", "max_runs", "quota", "start", "end", "config", "visibility" ]) if missing: return util.error(f"Missing fields ({', '.join(missing)}).") aid = request.form["aid"] if not util.valid_id(aid): return util.error( "Invalid Assignment ID. Allowed characters: a-z A-Z _ - .") new_assignment = db.get_assignment(cid, aid) if new_assignment: return util.error("Assignment ID already exists.") try: max_runs = int(request.form["max_runs"]) if max_runs < MIN_PREDEADLINE_RUNS: return util.error( f"Max Runs must be at least {MIN_PREDEADLINE_RUNS}.") except ValueError: return util.error("Max Runs must be a positive integer.") quota = request.form["quota"] if not db.Quota.is_valid(quota): return util.error("Quota Type is invalid.") start = util.parse_form_datetime(request.form["start"]).timestamp() end = util.parse_form_datetime(request.form["end"]).timestamp() if start is None or end is None: return util.error("Missing or invalid Start or End.") if start >= end: return util.error("Start must be before End.") try: config = json.loads(request.form["config"]) msg = bw_api.set_assignment_config(cid, aid, config) if msg: return util.error( f"Failed to add assignment to Broadway: {msg}") except json.decoder.JSONDecodeError: return util.error("Failed to decode config JSON") visibility = request.form["visibility"] db.add_assignment(cid, aid, max_runs, quota, start, end, visibility) return util.success("")
def edit_assignment(netid, cid, aid): course = db.get_course(cid) assignment = db.get_assignment(cid, aid) if course is None or assignment is None: return abort(HTTPStatus.NOT_FOUND) missing = util.check_missing_fields( request.form, *["max_runs", "quota", "start", "end", "visibility"]) if missing: return util.error(f"Missing fields ({', '.join(missing)}).") try: max_runs = int(request.form["max_runs"]) if max_runs < MIN_PREDEADLINE_RUNS: return util.error( f"Max Runs must be at least {MIN_PREDEADLINE_RUNS}.") except ValueError: return util.error("Max Runs must be a positive integer.") quota = request.form["quota"] if not db.Quota.is_valid(quota): return util.error("Quota Type is invalid.") start = util.parse_form_datetime(request.form["start"]).timestamp() end = util.parse_form_datetime(request.form["end"]).timestamp() if start is None or end is None: return util.error("Missing or invalid Start or End.") if start >= end: return util.error("Start must be before End.") try: config_str = request.form.get("config") if config_str is not None: # skip update otherwise config = json.loads(request.form["config"]) msg = bw_api.set_assignment_config(cid, aid, config) if msg: return util.error( f"Failed to update assignment config to Broadway: {msg}" ) except json.decoder.JSONDecodeError: return util.error("Failed to decode config JSON") visibility = request.form["visibility"] if not db.update_assignment(cid, aid, max_runs, quota, start, end, visibility): return util.error("Save failed or no changes were made.") return util.success("")
def upload_roster_file(netid, cid): file_content = request.form.get('content') netids = file_content.strip().lower().split('\n') for i, student_id in enumerate(netids): if not util.is_valid_netid(student_id): return util.error( f"Poorly formatted NetID on line {i + 1}: '{student_id}'" ) result = db.overwrite_student_roster(cid, netids) if none_modified(result): return util.error( "The new roster is the same as the current one.") return util.success("Successfully updated roster.")
def student_grade_assignment(netid, cid, aid): if not verify_student_or_staff(netid, cid): return abort(HTTPStatus.FORBIDDEN) if not verify_csrf_token(request.form.get("csrf_token")): return abort(HTTPStatus.BAD_REQUEST) now = util.now_timestamp() ext_to_use = None current_csrf_token = request.form.get("csrf_token") if not verify_staff(netid, cid): # not a staff member; perform quota checks num_available_runs = get_available_runs(cid, aid, netid, now) active_extensions, num_extension_runs = get_active_extensions( cid, aid, netid, now) if num_available_runs + num_extension_runs <= 0: restore_csrf_token(current_csrf_token) return util.error("No grading runs available.") if num_available_runs <= 0: # find the extension that is closest to expiration ext_to_use = min(active_extensions, key=lambda ext: ext["end"]) now_rounded = util.timestamp_round_up_minute(now) run_id = bw_api.start_grading_run(cid, aid, [netid], now_rounded) if run_id is None: restore_csrf_token(current_csrf_token) return util.error( "Failed to start grading run. Please try again.") db.add_grading_run(cid, aid, netid, now, run_id, extension_used=ext_to_use) return util.success("")
def trigger_scheduled_run(cid, aid, scheduled_run_id): sched_run = db.get_scheduled_run_by_scheduler_id( cid, aid, scheduled_run_id) if sched_run is None: logging.warning( "Received trigger scheduled run request for scheduled_run_id '%s' but cannot find corresponding run.", scheduled_run_id) return util.error("") if sched_run["status"] != ScheduledRunStatus.SCHEDULED: logging.warning( "Received trigger scheduled run for _id '%s' but this run has status '%s', which is not 'scheduled'.", str(sched_run["_id"]), sched_run["status"]) return util.error("") # If roster is not provided, use course roster if sched_run["roster"] is None: course = db.get_course(cid) if course is None: return util.error("") netids = course["student_ids"] # If a roster is provided, use it else: netids = sched_run["roster"] # Start broadway grading run bw_run_id = bw_api.start_grading_run(cid, f"{aid}_{sched_run['_id']}", netids, sched_run["due_time"]) if bw_run_id is None: logging.warning("Failed to trigger run with broadway") db.update_scheduled_run_status(sched_run["_id"], ScheduledRunStatus.FAILED) return util.error("") else: db.update_scheduled_run_status(sched_run["_id"], ScheduledRunStatus.RAN) db.update_scheduled_run_bw_run_id(sched_run["_id"], bw_run_id) return util.success("")
def add_or_edit_scheduled_run(cid, aid, run_id, form, scheduled_run_id): # course and assignment name validation course = db.get_course(cid) assignment = db.get_assignment(cid, aid) if course is None or assignment is None: return abort(HTTPStatus.NOT_FOUND) # form validation missing = util.check_missing_fields(request.form, "run_time", "due_time", "name", "config") if missing: return util.error(f"Missing fields ({', '.join(missing)}).") run_time = util.parse_form_datetime( request.form["run_time"]).timestamp() if run_time is None: return util.error("Missing or invalid run time.") if run_time <= util.now_timestamp(): return util.error("Run time must be in the future.") due_time = util.parse_form_datetime( request.form["due_time"]).timestamp() if due_time is None: return util.error("Missing or invalid due time.") if "roster" not in request.form or not request.form["roster"]: roster = None else: roster = request.form["roster"].replace(" ", "").lower().split(",") for student_netid in roster: if not util.valid_id(student_netid) or not verify_student( student_netid, cid): return util.error( f"Invalid or non-existent student NetID: {student_netid}" ) try: config = json.loads(request.form["config"]) msg = bw_api.set_assignment_config(cid, f"{aid}_{run_id}", config) if msg: return util.error( f"Failed to upload config to Broadway: {msg}") except json.decoder.JSONDecodeError: return util.error("Failed to decode config JSON") # Schedule a new run with scheduler if scheduled_run_id is None: scheduled_run_id = sched_api.schedule_run(run_time, cid, aid) if scheduled_run_id is None: return util.error("Failed to schedule run with scheduler") # Or if the run was already scheduled, update the time else: if not sched_api.update_scheduled_run(scheduled_run_id, run_time): return util.error( "Failed to update scheduled run time with scheduler") assert scheduled_run_id is not None if not db.add_or_update_scheduled_run( run_id, cid, aid, run_time, due_time, roster, request.form["name"], scheduled_run_id): return util.error( "Failed to save the changes, please try again.") return util.success("")
def delete_assignment(netid, cid, aid): if not db.remove_assignment(cid, aid): return util.error("Assignment doesn't exist") return util.success("")
def staff_get_scheduled_run(netid, cid, aid, run_id): sched_run = db.get_scheduled_run(cid, aid, run_id) if sched_run is None: return util.error("Cannot find scheduled run") del sched_run["_id"] return util.success(json.dumps(sched_run), 200)
def staff_get_extensions(netid, cid, aid): extensions = list(db.get_extensions(cid, aid)) for ext in extensions: ext["_id"] = str(ext["_id"]) return util.success(jsonify(extensions), HTTPStatus.OK)
def remove_course_staff(netid, cid): staff_id = request.form.get('netid') result = db.remove_staff_from_course(cid, staff_id) if none_modified(result): return util.error(f"'{staff_id}' is not a staff") return util.success(f"Successfully removed '{staff_id}'")