def flag(name, bid): assign = get_assignment(name) user_ids = assign.active_user_ids(current_user.id) flag = 'flag' in request.form next_url = request.form['next'] backup = models.Backup.query.get(bid) if not Backup.can(backup, current_user, "view"): abort(404) if not assign.active: flash('It is too late to change what submission is graded.', 'warning') elif flag: result = assign.flag(bid, user_ids) flash('Flagged submission {}. '.format(result.hashid) + 'This submission will be used for grading', 'success') else: result = assign.unflag(bid, user_ids) flash('Removed flag from {}. '.format(result.hashid) + 'The most recent submission will be used for grading.', 'success') if is_safe_redirect_url(request, next_url): return redirect(next_url) else: flash("Not a valid redirect", "danger") abort(400)
def code(name, submit, bid): assign = get_assignment(name) backup = Backup.query.get(bid) if not (backup and Backup.can(backup, current_user, "view")): abort(404) if backup.submit != submit: return redirect(url_for('.code', name=name, submit=backup.submit, bid=bid)) diff_type = request.args.get('diff') if diff_type not in (None, 'short', 'full'): return redirect(url_for('.code', name=name, submit=submit, bid=bid)) if not assign.files and diff_type: return abort(404) # sort comments by (filename, line) comments = collections.defaultdict(list) for comment in backup.comments: comments[(comment.filename, comment.line)].append(comment) # highlight files and add comments files = highlight.diff_files(assign.files, backup.files(), diff_type) for filename, source_file in files.items(): for line in source_file.lines: line.comments = comments[(filename, line.line_after)] return render_template('student/assignment/code.html', course=assign.course, assignment=assign, backup=backup, files=files, diff_type=diff_type)
def flag(name, bid): assign = get_assignment(name) user_ids = assign.active_user_ids(current_user.id) flag = 'flag' in request.form next_url = request.form['next'] backup = models.Backup.query.get(bid) if not Backup.can(backup, current_user, "view"): abort(404) if not assign.active: flash('It is too late to change what submission is graded.', 'warning') elif flag: result = assign.flag(bid, user_ids) flash('Flagged submission {}. '.format(result.hashid) + 'This submission will be used for grading', 'success') else: result = assign.unflag(bid, user_ids) flash('Removed flag from {}. '.format(result.hashid) + 'The most recent submission will be used for grading.', 'success') if is_safe_redirect_url(request, next_url): return redirect(next_url) else: flash("Not a valid redirect", "danger") abort(400)
def download(name, submit, bid, file): backup = Backup.query.get(bid) if not (backup and Backup.can(backup, current_user, "view")): abort(404) if backup.submit != submit: return redirect(url_for('.download', name=name, submit=backup.submit, bid=bid, file=file)) try: contents = backup.files()[file] except KeyError: abort(404) response = make_response(contents) inline = 'raw' in request.args content_disposition = "inline" if inline else "attachment" response.headers["Content-Disposition"] = ("{0}; filename={1!s}" .format(content_disposition, file)) response.headers["Content-Security-Policy"] = "default-src 'none';" response.headers["X-Content-Type-Options"] = "nosniff" if file.endswith('.ipynb') and not inline: # Prevent safari from adding a .txt extension to files response.headers["Content-Type"] = "application/octet-stream; charset=UTF-8" else: response.headers["Content-Type"] = "text/plain; charset=UTF-8" return response
def edit_backup(bid): courses, current_course = get_courses() backup = Backup.query.options(db.joinedload('assignment')).get(bid) if not backup: abort(404) if not Backup.can(backup, current_user, 'grade'): flash("You do not have permission to score this assignment.", "warning") abort(401) form = forms.SubmissionTimeForm() if form.validate_on_submit(): backup.custom_submission_time = form.get_submission_time( backup.assignment) db.session.commit() flash('Submission time saved', 'success') return redirect(url_for('.edit_backup', bid=bid)) else: form.set_submission_time(backup) return render_template( 'staff/grading/edit.html', courses=courses, current_course=current_course, backup=backup, student=backup.submitter, form=form, )
def download(name, submit, bid, file): backup = Backup.query.get(bid) if not (backup and Backup.can(backup, current_user, "view")): abort(404) if backup.submit != submit: return redirect( url_for('.download', name=name, submit=backup.submit, bid=bid, file=file)) try: contents = backup.files()[file] except KeyError: abort(404) response = make_response(contents) content_disposition = "inline" if 'raw' in request.args else "attachment" response.headers["Content-Disposition"] = ("{0}; filename={1!s}".format( content_disposition, file)) response.headers["Content-Security-Policy"] = "default-src 'none';" response.headers["X-Content-Type-Options"] = "nosniff" response.headers["Content-Type"] = "text/plain; charset=UTF-8" return response
def composition(bid): backup = Backup.query.get(bid) if not (backup and Backup.can(backup, current_user, "grade")): abort(404) form = forms.CompositionScoreForm() existing = Score.query.filter_by(backup=backup, kind="composition").first() if existing: form.kind.data = "composition" form.message.data = existing.message form.score.data = existing.score return grading_view(backup, form=form)
def composition(bid): backup = Backup.query.get(bid) if not (backup and Backup.can(backup, current_user, "grade")): abort(404) form = forms.CompositionScoreForm() existing = Score.query.filter_by(backup=backup, kind="composition").first() if existing: form.kind.data = "composition" form.message.data = existing.message form.score.data = existing.score return grading_view(backup, form=form)
def download(name, submit, bid, file): backup = Backup.query.get(bid) if not (backup and Backup.can(backup, current_user, "view")): abort(404) if backup.submit != submit: return redirect(url_for('.download', name=name, submit=backup.submit, bid=bid, file=file)) try: contents = backup.files()[file] except KeyError: abort(404) response = make_response(contents) response.headers["Content-Disposition"] = "attachment; filename={0!s}".format(file) return response
def grading(bid): backup = Backup.query.get(bid) if not (backup and Backup.can(backup, current_user, "grade")): abort(404) form = forms.GradeForm() existing = Score.query.filter_by(backup=backup).first() if existing and existing.kind in GRADE_TAGS: form = forms.GradeForm(kind=existing.kind) form.kind.data = existing.kind form.message.data = existing.message form.score.data = existing.score return grading_view(backup, form=form)
def grading(bid): backup = Backup.query.get(bid) if not (backup and Backup.can(backup, current_user, "grade")): abort(404) form = forms.GradeForm() existing = [s for s in backup.scores if not s.archived] first_score = existing[0] if existing else None if first_score and first_score.kind in GRADE_TAGS: form = forms.GradeForm(kind=first_score.kind) form.kind.data = first_score.kind form.message.data = first_score.message form.score.data = first_score.score return grading_view(backup, form=form)
def autograde_backup(bid): backup = Backup.query.options(db.joinedload('assignment')).get(bid) if not backup: abort(404) if not Backup.can(backup, current_user, 'grade'): flash("You do not have permission to score this assignment.", "warning") abort(401) form = forms.CSRFForm() if form.validate_on_submit(): try: autograder.autograde_backup(backup) flash('Submitted to the autograder', 'success') except ValueError as e: flash(str(e), 'error') return redirect(url_for('.grading', bid=bid))
def download(name, submit, bid, file): backup = Backup.query.get(bid) if not (backup and Backup.can(backup, current_user, "view")): abort(404) if backup.submit != submit: return redirect( url_for('.download', name=name, submit=backup.submit, bid=bid, file=file)) try: contents = backup.files()[file] except KeyError: abort(404) response = make_response(contents) response.headers[ "Content-Disposition"] = "attachment; filename={0!s}".format(file) return response
def download(name, submit, bid, file): backup = Backup.query.get(bid) if not (backup and Backup.can(backup, current_user, "view")): abort(404) if backup.submit != submit: return redirect(url_for('.download', name=name, submit=backup.submit, bid=bid, file=file)) try: contents = backup.files()[file] except KeyError: abort(404) response = make_response(contents) content_disposition = "inline" if 'raw' in request.args else "attachment" response.headers["Content-Disposition"] = ("{0}; filename={1!s}" .format(content_disposition, file)) response.headers["Content-Security-Policy"] = "default-src 'none';" response.headers["X-Content-Type-Options"] = "nosniff" response.headers["Content-Type"] = "text/plain; charset=UTF-8" return response
def grade(bid): """ Used as a form submission endpoint. """ backup = Backup.query.options(db.joinedload('assignment')).get(bid) if not backup: abort(404) if not Backup.can(backup, current_user, 'grade'): flash("You do not have permission to score this assignment.", "warning") abort(401) form = forms.GradeForm() score_kind = form.kind.data.strip().lower() is_composition = (score_kind == "composition") # TODO: Form should include redirect url instead of guessing based off tag if is_composition: form = forms.CompositionScoreForm() if not form.validate_on_submit(): return grading_view(backup, form=form) score = Score(backup=backup, grader=current_user, assignment_id=backup.assignment_id) form.populate_obj(score) db.session.add(score) db.session.commit() # Archive old scores of the same kind score.archive_duplicates() next_page = None flash_msg = "Added a {0} {1} score.".format(score.score, score_kind) # Find GradingTasks applicable to this score tasks = backup.grading_tasks for task in tasks: task.score = score cache.delete_memoized(User.num_grading_tasks, task.grader) db.session.commit() if len(tasks) == 1: # Go to next task for the current task queue if possible. task = tasks[0] next_task = task.get_next_task() next_route = '.composition' if is_composition else '.grading' # Handle case when the task is on the users queue if next_task: flash_msg += (" There are {0} tasks left. Here's the next submission:" .format(task.remaining)) next_page = url_for(next_route, bid=next_task.backup_id) else: flash_msg += " All done with grading for {}".format(backup.assignment.name) next_page = url_for('.grading_tasks') else: # TODO: Send task id or redirect_url in the grading form # For now, default to grading tasks next_page = url_for('.grading_tasks') flash(flash_msg, 'success') if not next_page: next_page = url_for('.assignment_queues', aid=backup.assignment_id, cid=backup.assignment.course_id) return redirect(next_page)
def grade(bid): """ Used as a form submission endpoint. """ backup = Backup.query.options(db.joinedload('assignment')).get(bid) if not backup: abort(404) if not Backup.can(backup, current_user, 'grade'): flash("You do not have permission to score this assignment.", "warning") abort(401) form = forms.GradeForm() score_kind = form.kind.data.strip().lower() is_composition = (score_kind == "composition") # TODO: Form should include redirect url instead of guessing based off tag if is_composition: form = forms.CompositionScoreForm() if not form.validate_on_submit(): return grading_view(backup, form=form) score = Score(backup=backup, grader=current_user, assignment_id=backup.assignment_id) form.populate_obj(score) db.session.add(score) db.session.commit() # Archive old scores of the same kind score.archive_duplicates() next_page = None flash_msg = "Added a {0} {1} score.".format(score.score, score_kind) # Find GradingTasks applicable to this score tasks = backup.grading_tasks for task in tasks: task.score = score cache.delete_memoized(User.num_grading_tasks, task.grader) db.session.commit() if len(tasks) == 1: # Go to next task for the current task queue if possible. task = tasks[0] next_task = task.get_next_task() next_route = '.composition' if is_composition else '.grading' # Handle case when the task is on the users queue if next_task: flash_msg += (" There are {0} tasks left. Here's the next submission:" .format(task.remaining)) next_page = url_for(next_route, bid=next_task.backup_id) else: flash_msg += " All done with grading for {}".format(backup.assignment.name) next_page = url_for('.grading_tasks') else: # TODO: Send task id or redirect_url in the grading form # For now, default to grading tasks next_page = url_for('.grading_tasks') flash(flash_msg, 'success') if not next_page: next_page = url_for('.assignment_queues', aid=backup.assignment_id, cid=backup.assignment.course_id) return redirect(next_page)