def load_assignment(lti=lti): # Get arguments assignment_id = int(request.values.get('assignment_id')) assignment = Assignment.by_id(assignment_id) course_id = get_course_id(True) user, user_id = get_user() # Verify exists check_resource_exists(assignment, "Assignment", assignment_id) # Verify permissions if course_id is None: editor_information = assignment.for_read_only_editor(user_id) else: editor_information = assignment.for_editor(user_id, course_id) browser_info = repr({ 'platform': request.user_agent.platform, 'browser': request.user_agent.browser, 'version': request.user_agent.version, 'language': request.user_agent.language, 'user_agent': request.user_agent.string }) # Log the event if user is not None: make_log_entry(assignment_id, assignment.version, course_id, user_id, 'Session.Start', message=browser_info) # Verify passcode, if necessary if assignment.passcode_fails(request.values.get('passcode')): return ajax_failure("Passcode {!r} rejected".format( request.values.get("passcode"))) return ajax_success(editor_information)
def get_assignment(lti=lti): assignment_id = int(request.values.get('assignment_id')) assignment = Assignment.by_id(assignment_id) is_embedded = ('embed' == request.values.get('menu', "select")) # Verify exists check_resource_exists(assignment, "Assignment", assignment_id) # Perform action select_url = get_select_menu_link(assignment.id, assignment.title(), is_embedded, False) return jsonify(success=True, redirect=url_for('assignments.load', assignment_id=assignment.id), id=assignment.id, name=assignment.name, type=assignment.type, instructions=strip_tags(assignment.instructions)[:255], title=assignment.title(), view=url_for('assignments.load', assignment_id=assignment.id, embed=is_embedded), select=select_url, edit=url_for('assignments.load', assignment_id=assignment.id, course_id=assignment.course_id), date_modified=assignment.pretty_date_modified())
def save_assignment(lti=lti): assignment_id = request.values.get('assignment_id') user, user_id = get_user() course_id = get_course_id() assignment = Assignment.query.get(assignment_id) # Verify exists check_resource_exists(assignment, "Assignment", assignment_id) # Verify permissions if assignment.owner_id != user.id: require_course_grader(user, assignment.course_id) # Parse new settings updates = {} if "hidden" in request.values: updates["hidden"] = maybe_bool(request.values.get("hidden")) if "reviewed" in request.values: updates["reviewed"] = maybe_bool(request.values.get("reviewed")) if "public" in request.values: updates["public"] = maybe_bool(request.values.get("public")) if "url" in request.values: updates["url"] = request.values.get("url") or None if "ip_ranges" in request.values: updates["ip_ranges"] = request.values.get("ip_ranges") if "name" in request.values: updates["name"] = request.values.get("name") if "settings" in request.values: updates["settings"] = request.values.get("settings") # Perform update modified = assignment.edit(updates) make_log_entry(assignment.id, assignment.version, course_id or assignment.course_id, user.id, "X-Instructor.Settings.Edit", "assignment_settings.blockpy", message=json.dumps(updates)) return ajax_success({"modified": modified})
def save_student_file(filename, course_id, user): submission_id = request.values.get("submission_id") code = request.values.get("code") submission = Submission.query.get(submission_id) # Verify exists check_resource_exists(submission, "Submission", submission_id) # Verify permissions if submission.user_id != user.id: require_course_grader(user, submission.course_id) # Validate the maximum file size if app.config["MAXIMUM_CODE_SIZE"] < len(code): return ajax_failure( "Maximum size of code exceeded. Current limit is {}, you uploaded {} characters." .format(app.config["MAXIMUM_CODE_SIZE"], len(code))) # Perform update # TODO: What if submission's assignment version conflicts with current assignments' version? version_change = submission.assignment.version != submission.assignment_version submission.save_code(filename, code) make_log_entry(submission.assignment_id, submission.assignment_version, course_id, user.id, "File.Edit", "answer.py", message=code) return ajax_success({"version_change": version_change})
def update_submission_status(lti, lti_exception=None): # Get parameters submission_id = maybe_int(request.values.get("submission_id")) status = request.values.get('status') course_id = get_course_id() user, user_id = get_user() submission = Submission.by_id(submission_id) # Check resource exists check_resource_exists(submission, "Submission", submission_id) # Verify permissions if submission.user_id != user_id and not user.is_grader( submission.course_id): return ajax_failure( "This is not your submission and you are not a grader in its course." ) # Do action success = submission.update_submission_status(status) make_log_entry(submission.assignment_id, submission.assignment_version, course_id, user_id, "Submit", "answer.py", category=status, message=str(success)) return ajax_success({"success": success})
def dump_logs(): assignment_id = int(request.values.get('assignment_id')) course_id = int(request.values.get('course_id')) assignment = Assignment.by_id(assignment_id) user, user_id = get_user() # Verify exists check_resource_exists(assignment, "Assignment", assignment_id) # Verify permissions if not user.is_grader(course_id): return "You are not a grader in this course." # Get data suas = Submission.by_assignment(assignment_id, course_id) data = { 'assignment': assignment.encode_json(), 'submissions': [{ 'user': u.encode_json(), 'submission': sub.encode_json(), 'history': Log.get_history(course_id, assignment_id, u.id), 'reviews': sub.get_reviews() } for (sub, u, assign) in suas] } filename = assignment.get_filename() + '_submissions.json' return Response(json.dumps(data), mimetype='application/json', headers={ 'Content-Disposition': 'attachment;filename={}'.format(filename) })
def export_submissions(): assignment_id = int(request.values.get('assignment_id')) assignment = Assignment.by_id(assignment_id) course_id = get_course_id(True) user, user_id = get_user() # Verify exists check_resource_exists(assignment, "Assignment", assignment_id) # Verify permissions assignment = Assignment.by_id(int(assignment_id)) if course_id is None or not user.is_instructor(int(course_id)): return "You are not an instructor or the owner of the assignment!" # Get data suas = Submission.by_assignment(assignment_id, course_id) submissions = [sua[0] for sua in suas] users = [sua[1] for sua in suas] bundle = export_zip(assignments=[assignment], submissions=submissions, users=users) filename = assignment.get_filename(extension='.zip') return Response(bundle, mimetype='application/zip', headers={ 'Content-Disposition': 'attachment;filename={}'.format(filename) })
def get_assignments(): assignment_ids = request.values.get('assignment_ids', "") course_id = get_course_id() user, user_id = get_user() # TODO: verify that they have the permissions to see these assignments assignments, groups = [], [] errors = [] if not assignment_ids: course: Course = Course.by_id(course_id) check_resource_exists(course, "Course", course_id) grouped_assignments = natsorted( course.get_submitted_assignments_grouped(), key=lambda r: (r.AssignmentGroup.name if r.AssignmentGroup is not None else None, r.Assignment.name)) assignments = [a.Assignment.encode_json() for a in grouped_assignments] groups = [ a.AssignmentGroup.encode_json() if a.AssignmentGroup is not None else None for a in grouped_assignments ] else: for assignment_id in assignment_ids.split(","): if not assignment_id.isdigit(): errors.append(f"Unknown Assignment ID: {assignment_id!r}") assignment_id = int(assignment_id) # With Course Role Information assignment = Assignment.by_id(assignment_id) check_resource_exists(assignment, "Assignment", assignment_id) assignments.append(assignment.encode_json()) return ajax_success( dict(assignments=assignments, errors=errors, groups=groups))
def submissions_user(course_id, owner_id): ''' List all the users in the course ''' course_id = int(course_id) course = Course.by_id(course_id) check_resource_exists(course, "Course", course_id) user, user_id = get_user() if course_id is not None: is_grader = user.is_grader(course_id) else: is_grader = False is_owner = user_id == maybe_int(owner_id) if not is_grader and not is_owner: return "You are not an instructor or the owner of the assignment!" owner = User.by_id(maybe_int(owner_id)) assignments = natsorted(course.get_submitted_assignments(), key=lambda r: r.name) all_subs = Submission.by_student(owner_id, course_id) all_subs = {s[0].assignment_id: s for s in all_subs} submissions = [all_subs.get(assignment.id, (None, None, assignment)) for assignment in assignments] return render_template('courses/submissions_user.html', course_id=course_id, assignments=assignments, submissions=submissions, owner=owner, is_instructor=is_grader)
def fork_group(lti=lti): ''' Adds a group to a course''' # Get arguments assignment_group_id = int(request.values.get('assignment_group_id')) assignment_group = AssignmentGroup.by_id(assignment_group_id) is_embedded = ('embed' == request.values.get('menu', "select")) # Verify exists check_resource_exists(assignment_group, "Assignment Group", assignment_group_id) # Verify permissions require_course_instructor(g.user, assignment_group.course_id) # Perform action new_assignment_group = AssignmentGroup.new( owner_id=g.user.id, course_id=assignment_group.course_id, name=assignment_group.name) new_assignment_group.forked_id = assignment_group_id new_assignment_group.forked_version = assignment_group.version # Result select_url = get_select_menu_link(new_assignment_group.id, new_assignment_group.name, is_embedded, True) return jsonify(success=True, id=new_assignment_group.id, name=new_assignment_group.name, select=select_url)
def load_assignment(lti=lti): # Get arguments assignment_id = int(request.values.get('assignment_id')) assignment = Assignment.by_id(assignment_id) student_id = maybe_int(request.values.get('user_id')) course_id = get_course_id(True) user, user_id = get_user() force_download = maybe_bool(request.values.get('force_download', "false")) # Verify exists check_resource_exists(assignment, "Assignment", assignment_id) # Verify permissions if user_id != student_id and not user.is_grader(course_id): return ajax_failure( "Only graders can see submissions for other people.") if course_id is None: editor_information = assignment.for_read_only_editor(student_id) else: editor_information = assignment.for_editor(student_id, course_id) browser_info = json.dumps({ 'platform': request.user_agent.platform, 'browser': request.user_agent.browser, 'version': request.user_agent.version, 'language': request.user_agent.language, 'user_agent': request.user_agent.string }) # Log the event if user is not None: if user_id != student_id: make_log_entry(assignment_id, assignment.version, course_id, user_id, 'X-Submission.Get', message=str(student_id)) else: make_log_entry(assignment_id, assignment.version, course_id, user_id, 'Session.Start', message=browser_info) # Verify passcode, if necessary if assignment.passcode_fails(request.values.get('passcode')): return ajax_failure("Passcode {!r} rejected".format( request.values.get("passcode"))) if force_download: student_filename = User.by_id(student_id).get_filename("") filename = assignment.get_filename( "") + "_" + student_filename + '_submission.json' return Response(json.dumps(editor_information), mimetype='application/json', headers={ 'Content-Disposition': 'attachment;filename={}'.format(filename) }) else: return ajax_success(editor_information)
def get_one(self, review_id): user, user_id = get_user() review = Review.by_id(review_id) check_resource_exists(review, "Review", review_id) submission = Submission.by_id(review.submission_id) check_resource_exists(submission, "Submission", review.submission_id) if submission.user_id != user_id: require_course_grader(user, submission.course_id) return ajax_success(dict(review=review.encode_json()))
def remove_assignment(lti=None): assignment_id = int(request.values.get('assignment_id')) assignment = Assignment.by_id(assignment_id) # Verify exists check_resource_exists(assignment, "Assignment", assignment_id) # Verify permissions require_course_instructor(g.user, assignment.course_id) Assignment.remove(assignment.id) return jsonify(success=True)
def delete(self, review_id): user, user_id = get_user() review = Review.by_id(review_id) check_resource_exists(review, "Review", review_id) submission = Submission.by_id(review.submission_id) check_resource_exists(submission, "Submission", review.submission_id) require_course_grader(user, submission.course_id) review.delete() return ajax_success(dict(success=True))
def get_groups_submissions(group_id, user_id, course_id): group = AssignmentGroup.by_id(group_id) check_resource_exists(group, "AssignmentGroup", group_id) assignments = group.get_assignments() submissions = [ assignment.load(user_id, course_id=course_id) for assignment in assignments ] return group, assignments, submissions
def remove_course(course_id): course_id = int(request.values.get('course_id')) course = Course.by_id(course_id) # Verify exists check_resource_exists(course, "Course", course_id) # Verify permissions require_course_instructor(g.user, course_id) # Perform action Course.remove(course_id) flash('Course removed') return redirect(url_for('courses.index'))
def rename_course(): course_id = int(request.values.get('course_id')) course = Course.by_id(course_id) # Verify exists check_resource_exists(course, "Course", course_id) # Verify permissions require_course_instructor(g.user, course_id) # Perform action new_name = request.values.get('name') Course.rename(course_id, new_name) return jsonify(success=True)
def get_submission_image(lti=lti): submission_id = int(request.values.get('submission_id')) relative_image_path = 'uploads/submission_blocks/{}.png'.format(submission_id) submission = Submission.query.get(submission_id) user, user_id = get_user() # Check exists check_resource_exists(submission, "Submission", submission_id) # Check permissions if submission.user_id != user_id: require_course_grader(user, submission.course_id) # Do action return app.send_static_file(relative_image_path)
def move_course(lti=None): assignment_id = int(request.values.get('assignment_id')) new_course_id = int(request.values.get('new_course_id')) assignment = Assignment.by_id(int(assignment_id)) # Verify exists check_resource_exists(assignment, "Assignment", assignment_id) # Verify permissions require_course_instructor(g.user, assignment.course_id) require_course_instructor(g.user, new_course_id) # Perform action assignment.move_course(new_course_id) return jsonify(success=True)
def update_grading_status(lti, lti_exception=None): submission_id = maybe_int(request.values.get("submission_id")) # TODO: Pretty sure multiple assignments are broken for grading assignment_group_id = maybe_int(request.values.get('assignment_group_id')) new_grading_status = request.values.get("new_grading_status") user, user_id = get_user() submission = Submission.by_id(submission_id) # Check resource exists check_resource_exists(submission, "Submission", submission_id) # Verify permissions if not user.is_grader(submission.course_id): return ajax_failure( "This is not your submission and you are not a grader in its course." ) submission.update_grading_status(new_grading_status) if submission.grading_status != GradingStatuses.FULLY_GRADED: return ajax_success({'new_status': new_grading_status}) # Do action if assignment_group_id is None: assignment_group_id = submission.assignment_group_id error = "Generic LTI Failure - perhaps not logged into LTI session?" try: success, score = lti_post_grade(lti, submission, None, assignment_group_id, submission.user_id, submission.course_id) except LTIPostMessageException as e: success = False error = str(e) if success: make_log_entry(submission.assignment_id, submission.assignment_version, submission.course_id, user_id, "X-Submission.LMS", "answer.py", message=str(score)) return ajax_success({"submitted": True, "new_status": "FullyGraded"}) else: submission.update_grading_status(GradingStatuses.FAILED) make_log_entry(submission.assignment_id, submission.assignment_version, submission.course_id, user_id, "X-Submission.LMS.Failure", "answer.py", message=error) return ajax_failure({ "submitted": False, "message": error, "new_status": "Failed" })
def get_all(self): submission_id = maybe_int(request.values.get('submission_id')) user, user_id = get_user() if submission_id is None: reviews = Review.get_generic_reviews() else: submission = Submission.by_id(submission_id) check_resource_exists(submission, "Submission", submission_id) reviews = Review.get_for_submission(submission_id) if submission.user_id != user_id: require_course_grader(user, submission.course_id) return ajax_success( dict(reviews=[review.encode_json() for review in reviews]))
def put(self, review_id): user, user_id = get_user() review = Review.by_id(review_id) check_resource_exists(review, "Review", review_id) submission = Submission.by_id(review.submission_id) check_resource_exists(submission, "Submission", review.submission_id) require_course_grader(user, submission.course_id) review_data = request.json.copy() del review_data['id'] fix_nullables(review_data) review_data['author_id'] = user_id edited_review = review.edit(review_data) return ajax_success(dict(review=edited_review.encode_json()))
def update_submission_status(): submission_id = maybe_int(request.values.get("submission_id")) new_submission_status = request.values.get("new_submission_status") user, user_id = get_user() submission = Submission.by_id(submission_id) # Check resource exists check_resource_exists(submission, "Submission", submission_id) # Verify permissions if not user.is_grader(submission.course_id): return ajax_failure( "You are not a grader in this submission's course.") submission.update_submission_status(new_submission_status) return ajax_success({'new_status': new_submission_status})
def get_image(): submission_id = int(request.values.get('submission_id')) directory = request.values.get('directory') relative_image_path = 'uploads/{}/{}.png'.format(directory, submission_id) submission = Submission.query.get(submission_id) user, user_id = get_user() # Check exists check_resource_exists(submission, "Submission", submission_id) # Check permissions if submission.user_id != user_id and not user.is_grader(submission.course_id): return ajax_failure("This is not your submission and you are not a grader in its course.") # Do action return app.send_static_file(relative_image_path)
def remove_group(lti=lti): ''' Removes a group from a course''' assignment_group_id = int(request.values.get('assignment_group_id')) assignment_group = AssignmentGroup.by_id(assignment_group_id) # Verify exists check_resource_exists(assignment_group, "Assignment Group", assignment_group_id) # Verify permissions require_course_instructor(g.user, assignment_group.course_id) # Perform action AssignmentGroup.remove(assignment_group.id) # Result return jsonify(success=True)
def post(self): user, user_id = get_user() submission_id = maybe_int(request.values.get('submission_id')) submission = Submission.by_id(submission_id) check_resource_exists(submission, "Submission", submission_id) require_course_grader(user, submission.course_id) review_data = request.values.copy() del review_data['id'] review_data['author_id'] = user_id review_data['submission_version'] = submission.version review_data['assignment_version'] = submission.assignment_version fix_nullables(review_data) new_review = Review.new(review_data) return ajax_success(dict(review=new_review.encode_json()))
def save_instructor_file(course_id, user, filename): assignment_id = request.values.get("assignment_id") code = request.values.get("code") assignment = Assignment.query.get(assignment_id) # Verify exists check_resource_exists(assignment, "Assignment", assignment_id) # Verify permissions if assignment.owner_id != user.id: require_course_grader(user, assignment.course_id) # Perform update assignment.save_file(filename, code) make_log_entry(assignment_id, assignment.version, course_id, user.id, "X-Instructor.File.Edit", filename, message=code) return ajax_success({})
def get_assignments(): assignment_ids = request.values.get('assignment_ids', "") course_id = get_course_id() user, user_id = get_user() # TODO: verify that they have the permissions to see this file assignments = [] for assignment_id in assignment_ids.split(","): if not assignment_id.isdigit(): return ajax_failure(f"Unknown Assignment ID: {assignment_id!r}") assignment_id = int(assignment_id) # With Course Role Information assignment = Assignment.by_id(assignment_id) check_resource_exists(assignment, "Assignment", assignment_id) assignments.append(assignment.encode_json()) return ajax_success(dict(assignments=assignments))
def edit_settings(lti=lti): # Get arguments assignment_group_id = int(request.values.get('assignment_group_id')) assignment_group = AssignmentGroup.by_id(assignment_group_id) ip_ranges = request.values.get('ip_ranges') passcode = request.values.get('passcode') # Verify exists check_resource_exists(assignment_group, "Assignment Group", assignment_group_id) # Verify permissions require_course_instructor(g.user, assignment_group.course_id) # Perform action if request.method == 'POST': if ip_ranges is not None: for assignment in assignment_group.get_assignments(): assignment.edit(dict(ip_ranges=ip_ranges)) assignment.update_setting("passcode", passcode) return redirect(request.url) # Result else: assignments = assignment_group.get_assignments() passcode = assignments[0].get_setting("passcode", "") existing_ip_ranges = [ assignment.ip_ranges for assignment in assignments ] merged_duplicates = set(existing_ip_ranges) warning = "" if len(merged_duplicates) == 1: ip_ranges = merged_duplicates.pop() elif merged_duplicates: ip_ranges = existing_ip_ranges[0] warning = "This assignment has multiple IP ranges: <pre>{}</pre>".format( "\n".join(existing_ip_ranges)) return ''' <!doctype html> <title>Edit Assignment Group Settings</title> <h1>Edit Assignment Group Settings</h1> <p>Assignment: {group_name}</p> <p>{warning}</p> <form action="" method=post> <p>IP Ranges: <input type=text name=ip_ranges value="{ip_ranges}"><br> Passcode: <input type=text name=passcode value="{passcode}"><br> <input type=submit value=Change> </form> '''.format(group_name=assignment_group.name, ip_ranges=ip_ranges if ip_ranges else "", passcode=passcode if passcode else "", warning=warning)
def export(): assignment_id = int(request.values.get('assignment_id')) assignment = Assignment.by_id(assignment_id) course_id = get_course_id(True) user, user_id = get_user() # Verify exists check_resource_exists(assignment, "Assignment", assignment_id) # Verify permissions bundle = export_bundle(assignments=[assignment]) filename = assignment.get_filename() return Response(json.dumps(bundle), mimetype='application/json', headers={ 'Content-Disposition': 'attachment;filename={}'.format(filename) })