def watch(): assignment_list = request.values.get('assignments', '') assignments = [int(aid) for aid in assignment_list.split(',') if len(aid) > 0] course_id = request.values.get('course_id', g.course.id if 'course' in g else None) if course_id == None or course_id == "": return ajax_failure("No Course ID given!") if g.user is None or not g.user.is_instructor(int(course_id)): return ajax_failure("You are not an instructor in this assignments' course.") update = request.values.get('update', 'false') == "true" if update: data = [] for aid in assignments: submissions = Submission.by_assignment(aid, int(course_id)) completions = sum([int(sua[0].correct) for sua in submissions]) workings = Submission.get_latest(aid, int(course_id)) histories = [process_history([h['time'] for h in sua[0].get_history()]) for sua in submissions] touches = [int(sua[0].version) for sua in submissions] feedbacks = [l[0] for l in Log.calculate_feedbacks(aid, course_id)] data.append({'id': aid, 'Completions': completions, 'Workings': workings, 'Time': histories, 'Touches': touches, 'Feedbacks': feedbacks}) return jsonify(success=True, data=data) else: assignments = [Assignment.by_id(aid) for aid in assignments] return render_template('blockpy/watch.html', course_id=course_id, assignments=assignments, assignment_list=assignment_list)
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 save_file(lti=lti): filename = request.values.get("filename") course_id = get_course_id() user, user_id = get_user() if course_id is None: return ajax_failure("Course ID was not made available") if filename in Submission.STUDENT_FILENAMES: return save_student_file(filename, course_id, user) if filename in Assignment.INSTRUCTOR_FILENAMES: return save_instructor_file(course_id, user, filename) return ajax_failure("Unknown filename: " + str(filename))
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 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 view_submissions(course_id, user_id, assignment_group_id): embed = maybe_bool(request.values.get('embed')) viewer, viewer_id = get_user() group, assignments, submissions = get_groups_submissions( assignment_group_id, user_id, course_id) # Check permissions for submission in submissions: if not submission: return ajax_failure( "No submission for the given course, user, and group.") elif submission.user_id != viewer_id: require_course_grader(viewer, submission.course_id) # Do action points_total, points_possible = calculate_submissions_score( assignments, submissions) score = round(points_total / points_possible, 2) # TODO: Handle tags is_grader = viewer.is_grader(course_id) tags = [] if is_grader: tags = [tag.encode_json() for tag in AssignmentTag.get_all()] return render_template("reports/group.html", embed=embed, points_total=points_total, points_possible=points_possible, score=score, tags=tags, is_grader=is_grader, group=list(zip(assignments, submissions)), user_id=user_id, course_id=course_id)
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 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 update_submission(lti, lti_exception=None): # Get parameters submission_id = maybe_int(request.values.get("submission_id")) lis_result_sourcedid = request.values.get('lis_result_sourcedid') assignment_group_id = maybe_int(request.values.get('assignment_group_id')) score = float(request.values.get('score', '0')) correct = maybe_bool(request.values.get("correct")) # TODO: Only send image if the assignment settings starts as Block or Split image = request.values.get('image', "") hidden_override = maybe_bool(request.values.get('hidden_override')) force_update = maybe_bool(request.values.get('force_update')) 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 was_changed = submission.update_submission(score, correct) if assignment_group_id is None: assignment_group_id = submission.assignment_group_id # TODO: Document that we currently only pass back grade if it changed # TODO: If failure on previous submission grading, then retry if was_changed or force_update: submission.save_block_image(image) error = "Generic LTI Failure - perhaps not logged into LTI session?" try: success, score = lti_post_grade(lti, submission, lis_result_sourcedid, 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, course_id, user_id, "X-Submission.LMS", "answer.py", message=str(score)) else: submission.update_grading_status(GradingStatuses.FAILED) make_log_entry(submission.assignment_id, submission.assignment_version, course_id, user_id, "X-Submission.LMS.Failure", "answer.py", message=error) return ajax_failure({"submitted": False, "changed": was_changed, "message": error}) return ajax_success({"submitted": was_changed or force_update, "changed": was_changed})
def browse_submissions(): assignment_id = request.values.get('assignment_id', None) if assignment_id is None: return ajax_failure("No Assignment ID given!") assignment_id = int(assignment_id) course_id = request.values.get('course_id', g.course.id if 'course' in g else None) if course_id == None or course_id == "": return ajax_failure("No Course ID given!") if g.user is None or not g.user.is_instructor(int(course_id)): return ajax_failure("You are not an instructor in this assignments' course.") submissions = Submission.by_assignment(assignment_id, int(course_id)) for submission, user, assignment in submissions: submission.highlighted_code = highlight_python_code(submission.code) submission.history = process_history([h['time'] for h in reversed(submission.get_history())]) return render_template('blockpy/browse_submissions.html', course_id=course_id, assignment_id=assignment_id, submissions=submissions, ip=request.remote_addr)
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 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 users(): user_ids = request.values.get('user_ids', "") course_id = get_course_id() user, user_id = get_user() if course_id is None: return ajax_failure("You are not in a course context.") is_grader = user.is_grader(course_id) if not is_grader and user_ids != str(user_id): return ajax_failure("You do not have permissions to see those users.") users = [] errors = [] # If blank, then get all the available users if not user_ids: course = Course.by_id(course_id) check_resource_exists(course, "Course", course_id) user_roles = course.get_users() user_data = {} for role, user in user_roles: if user not in user_data: user_data[user] = user.encode_json() user_data[user]['roles'] = [] user_data[user]['roles'].append(role.encode_json()) users.extend(user_data.values()) # Otherwise, get the subset suggested else: for user_id in user_ids.split(","): if not user_id.isdigit(): errors.append(f"Unknown User ID: {user_id!r}") continue user_id = int(user_id) # With Course Role Information user = User.by_id(user_id) check_resource_exists(user, "User", user_id) user_data = user.encode_json() user_data['roles'] = [ r.encode_json() for r in user.get_course_roles(course_id) ] users.append(user_data) return ajax_success(dict(users=users, errors=errors))
def load_assignment_give_feedback(): ''' Very random function necessary for syncing with JN - we need to expose the 'on_run' field from assignments in public courses. TODO: Do this for public courses only, not just private ones :return: ''' assignment_id = request.values.get('assignment_id', None) if assignment_id is None: return ajax_failure("No Assignment ID given!") assignment = Assignment.by_id(assignment_id) return jsonify(success=True, give_feedback=assignment.on_run)
def mass_close_assignment(): assignment_id = maybe_int(request.values.get("assignment_id")) course_id = maybe_int(request.values.get("course_id")) new_submission_status = request.values.get("new_submission_status") user, user_id = get_user() submissions = Submission.by_assignment(assignment_id=assignment_id, course_id=course_id) # Verify permissions if not user.is_grader(course_id): return ajax_failure("You are not a grader in this course.") # Do action for submission in submissions: submission.update_submission_status(new_submission_status) return ajax_success({'new_status': new_submission_status})
def users(): user_ids = request.values.get('user_ids', "") course_id = get_course_id() user, user_id = get_user() if course_id is None: return ajax_failure("You are not in a course context.") is_grader = user.is_grader(course_id) if not is_grader and user_ids != str(user_id): return ajax_failure("You do not have permissions to see those users.") users = [] for user_id in user_ids.split(","): if not user_id.isdigit(): return ajax_failure(f"Unknown User ID: {user_id!r}") user_id = int(user_id) # With Course Role Information user = User.by_id(user_id) check_resource_exists(user, "User", user_id) user_data = user.encode_json() user_data['roles'] = [ r.encode_json() for r in user.get_course_roles(course_id) ] users.append(user_data) return ajax_success(dict(users=users))
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 save_image(): # Get parameters submission_id = maybe_int(request.values.get("submission_id")) directory = request.values.get('directory') image = request.values.get('image') 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.save_image(directory, image) make_log_entry(submission.assignment_id, submission.assignment_version, course_id, user_id, "X-Image.Save", directory) return ajax_success({"success": success})
def load_history(): # Get parameters course_id = maybe_int(request.values.get('course_id')) assignment_id = maybe_int(request.values.get('assignment_id')) student_id = maybe_int(request.values.get('user_id')) page_limit = maybe_int(request.values.get('page_limit')) page_offset = maybe_int(request.values.get('page_offset')) user, user_id = get_user() # Verify user can see the submission if user_id != student_id and not user.is_grader(course_id): return ajax_failure("Only graders can see logs for other people.") history = list( reversed( Log.get_history(course_id, assignment_id, student_id, page_offset=page_offset, page_limit=page_limit))) return ajax_success({"history": history})
def load_submission(lti=lti): submission_id = int(request.args.get('submission_id')) embed = maybe_bool(request.values.get('embed')) course_id = get_course_id(True) user, user_id = get_user() submission = Submission.by_id(submission_id) read_only = maybe_bool(request.values.get('read_only', "true")) # Check that the resource exists check_resource_exists(submission, "Submission", submission_id) # If it is this user's submission, redirect to load the assignment if submission.user_id == user_id: if course_id is None: course_id = submission.course_id return redirect( url_for('blockpy.load', assignment_id=submission.assignment.id, course_id=course_id)) # Check that it is public or you are a grader elif user.is_grader(submission.course_id): role = 'grader' elif not submission.assignment.public: # TODO: Handle this more gracefully return ajax_failure( "Cannot view submission. This is not a public submission, and you do not own the submission, and you are " "not an instructor in its course.") else: role = 'anonymous' # Get the assignment assignment_data = submission.assignment.for_editor(submission.user_id, submission.course_id) return load_editor( lti, { "user": user, "user_id": user_id, "embed": embed, "read_only": read_only, "current_submission_id": submission_id, "course_id": course_id, "role": role, "assignment_group_id": None, "assignment_data": assignment_data })
def load_history(): # Get parameters course_id = maybe_int(request.values.get('course_id')) assignment_id = (request.values.get('assignment_id')) student_id = (request.values.get('user_id')) page_limit = maybe_int(request.values.get('page_limit')) page_offset = maybe_int(request.values.get('page_offset')) with_submission = maybe_bool(request.values.get('with_submission')) user, user_id = get_user() # Verify user can see the submission if str(user_id) != str(student_id) and not user.is_grader(course_id): return ajax_failure("Only graders can see logs for other people.") history = Log.get_history(course_id, assignment_id, student_id, page_offset=page_offset, page_limit=page_limit) history = list(reversed(history)) submissions = [] if with_submission: submissions = Submission.get_submissions(course_id, assignment_id, student_id) return ajax_success({"history": history, "submissions": submissions})
def browse_history(): # Get parameters course_id = maybe_int(request.values.get('course_id')) assignment_id = maybe_int(request.values.get('assignment_id')) student_id = maybe_int(request.values.get('user_id')) page_offset = maybe_int(request.values.get('page_offset', 0)) embed = maybe_bool(request.values.get('embed')) user, user_id = get_user() # Get resources assignment = Assignment.by_id(assignment_id) student = User.by_id(student_id) course = Course.by_id(course_id) # Verify user can see the submission if user_id != student_id and not user.is_grader(course_id): return ajax_failure("Only graders can see logs for other people.") history = Log.get_history(course_id, assignment_id, student_id, page_offset, HISTORY_PAGE_LIMIT) return render_template('blockpy/browse_history.html', assignment=assignment, student=student, course=course, history=history, embed=embed)
def load_submission(lti=lti): submission_id = int(request.args.get('submission_id')) embed = maybe_bool(request.values.get('embed')) user = g.get('user', None) user_id = user.id if user else None course_id = maybe_int(request.args.get('course_id', None)) if course_id is None: course_id = int(g.course.id) if g.course else None submission = Submission.query.get(submission_id) # Check that the resource exists check_resource_exists(submission, "Submission", submission_id) # If it is this user's submission, redirect to load the assignment if submission.user_id == user_id: return redirect( url_for( 'blockpy.load', assignment_id=submission.assignment.id, )) # Check that it is public or you are a grader elif user.is_grader(submission.course_id): pass elif not submission.assignment.public: # TODO: Handle this more gracefully return ajax_failure( "Cannot view submission. This is not a public submission, and you do not own the submission, and you are " "not an instructor in its course.") # Get the assignment assignment_data = submission.assignment.for_editor(submission.user_id, submission.course_id) return load_editor( lti, { "user": user, "user_id": user_id, "embed": embed, "course_id": course_id, "role": 'anonymous', "assignment_group_id": None, "assignment_data": assignment_data })