def assignment_stats(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not Assignment.can(assign, current_user, 'edit'): flash('Insufficient permissions', 'error') return abort(401) stats = Assignment.assignment_stats(assign.id) submissions = [d for d in stats.pop('raw_data')] pie_chart = pygal.Pie(half_pie=True, disable_xml_declaration=True, style=CleanStyle, inner_radius=.5, legend_at_bottom=True) pie_chart.title = 'Students submission status' pie_chart.add('Students with Submissions', stats['students_with_subm']) pie_chart.add('Students with Backups', stats['students_with_backup']) pie_chart.add('Not Started', stats['students_no_backup']) return render_template('staff/course/assignment/assignment.stats.html', assignment=assign, subm_chart=pie_chart, courses=courses, stats=stats, submissions=submissions, current_course=current_course)
def assignment_single_queue(cid, aid, uid): courses, current_course = get_courses(cid) assignment = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not Assignment.can(assignment, current_user, 'grade'): flash('Insufficient permissions', 'error') return abort(401) assigned_grader = User.get_by_id(uid) if not Assignment.can(assignment, assigned_grader, 'grade'): return abort(404) page = request.args.get('page', 1, type=int) tasks_query = GradingTask.query.filter_by(assignment=assignment, grader_id=uid) queue = (tasks_query.options(db.joinedload('assignment')) .order_by(GradingTask.score_id.asc()) .order_by(GradingTask.created.asc()) .paginate(page=page, per_page=20)) remaining = tasks_query.filter_by(score_id=None).count() percent_left = (1-(remaining/max(1, queue.total))) * 100 return render_template('staff/grading/queue.html', courses=courses, current_course=current_course, assignment=assignment, grader=assigned_grader, queue=queue, remaining=remaining, percent_left=percent_left)
def staff_group_remove(cid, email, aid): assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): return abort(404) student = User.lookup(email) if not student: abort(404) result_page = url_for('.student_assignment_detail', cid=cid, email=email, aid=aid) form = forms.CSRFForm() if form.validate_on_submit(): target = User.lookup(request.form['target']) if not target: flash('{} does not exist'.format(request.form['target']), 'error') return redirect(result_page) try: Group.force_remove(current_user, student, target, assign) except BadRequest as e: flash("Error: {}".format(str(e.description)), 'error') return redirect(result_page)
def autograde(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): flash('Cannot access assignment', 'error') return abort(404) auth_token = get_token_if_valid() form = forms.AutogradeForm() if form.validate_on_submit(): if hasattr(form, 'token') and form.token.data: token = form.token.data else: token = auth_token autopromotion = form.autopromote.data try: autograde_assignment(assign, form.autograder_id.data, token, autopromotion=autopromotion) flash('Submitted to the autograder', 'success') except ValueError as e: flash(str(e), 'error') if not form.token.data and auth_token: form.token.data = auth_token[0] if not form.autograder_id.data and assign.autograding_key: form.autograder_id.data = assign.autograding_key return render_template('staff/grading/autograde.html', current_course=current_course, assignment=assign, form=form)
def gen_assignment(course): if gen_bool(0.5): display_name = random.choice( ['Hog', 'Hog Contest', 'Maps', 'Ants', 'Scheme']) else: display_name = '{0} {1}'.format( random.choice(['Homework', 'Lab', 'Quiz']), str(random.randrange(15)).zfill(2)) name = course.offering + '/' + display_name.lower().replace(' ', '') last_night = (datetime.datetime.utcnow().replace( hour=0, minute=0, second=0, microsecond=0) - datetime.timedelta(seconds=1)) last_night = ( pytz.timezone("America/Los_Angeles").localize(last_night).astimezone( pytz.utc)) due_date = last_night + datetime.timedelta( days=random.randrange(-100, 100)) lock_date = due_date + datetime.timedelta(days=1) support_online = gen_bool(0.5) return Assignment( name=name, course_id=course.id, display_name=display_name, due_date=due_date, lock_date=lock_date, max_group_size=weighted_choice([(1, 20), (2, 70), (3, 10)]), revisions_allowed=gen_bool(0.3), uploads_enabled=support_online, files={'fizzbuzz.py': original_file} if support_online else None)
def start_moss_job(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): flash('Cannot access assignment', 'error') return abort(404) form = forms.MossSubmissionForm() if form.validate_on_submit(): job = jobs.enqueue_job( moss.submit_to_moss, description='Moss Upload for {}'.format(assign.display_name), course_id=cid, user_id=current_user.id, assignment_id=assign.id, moss_id=form.moss_userid.data, file_regex=form.file_regex.data or '*', language=form.language.data) return redirect(url_for('.course_job', cid=cid, job_id=job.id)) else: return render_template( 'staff/jobs/moss.html', courses=courses, current_course=current_course, assignment=assign, form=form, )
def setup_course(self): """Creates: * A course (self.course) * An assignment (self.assignment) in that course * 5 users (self.user1, self.user2, etc.) enrolled as students """ self.course = Course(offering='cal/cs61a/sp16', institution='UC Berkeley', display_name='CS 61A') self.assignment = Assignment(name='cal/cs61a/sp16/proj1', course=self.course, display_name='Hog', due_date=datetime.datetime.now(), lock_date=datetime.datetime.now() + datetime.timedelta(days=1), max_group_size=4) db.session.add(self.assignment) def make_student(n): user = User(email='student{0}@aol.com'.format(n)) participant = Enrollment(user=user, course=self.course) db.session.add(participant) return user self.user1 = make_student(1) self.user2 = make_student(2) self.user3 = make_student(3) self.user4 = make_student(4) self.user5 = make_student(5) db.session.commit()
def assignment_queues(cid, aid): courses, current_course = get_courses(cid) assignment = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not Assignment.can(assignment, current_user, 'grade'): flash('Insufficient permissions', 'error') return abort(401) queues = GradingTask.get_staff_tasks(assignment.id) incomplete = [q.grader.email for q in queues if q.completed != q.total] complete = [q.grader.email for q in queues if q.completed == q.total] mailto_link = "mailto://{0}?subject={1}&body={2}&cc={3}".format( current_user.email, "{0} grading queue is not finished".format(assignment.display_name), "Queue Link: {0}".format(url_for('admin.grading_tasks', _external=True)), ','.join(incomplete) ) remaining = len(incomplete) percent_left = (1-(remaining/max(1, len(queues)))) * 100 if current_user.email in incomplete: flash("Hmm... You aren't finished with your queue.", 'info') elif current_user.email in complete: flash("Nice! You are all done with your queue", 'success') else: flash("You don't have a queue for this assignment", 'info') return render_template('staff/grading/overview.html', courses=courses, current_course=current_course, assignment=assignment, queues=queues, incomplete=incomplete, mailto=mailto_link, remaining=remaining, percent_left=percent_left)
def staff_flag_backup(cid, email, aid): assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): return abort(404) result_page = url_for('.student_assignment_detail', cid=cid, email=email, aid=aid) student = User.lookup(email) if not student: abort(404) user_ids = assign.active_user_ids(student.id) bid = request.form.get('bid') form = forms.CSRFForm() if form.validate_on_submit(): backup = Backup.query.filter_by(id=utils.decode_id(bid), assignment=assign).one_or_none() if not backup: flash('{} does not exist'.format(bid, 'error')) return redirect(result_page) if not backup.flagged: result = assign.flag(backup.id, user_ids) flash('Flagged backup {} for grading'.format(bid), 'success') else: result = assign.unflag(backup.id, user_ids) flash('Removed grading flag on {}'.format(bid), 'success') return redirect(result_page)
def staff_group_add(cid, email, aid): assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): flash('Cannot access assignment', 'error') return abort(404) form = forms.StaffAddGroupFrom() result_page = url_for('.student_assignment_detail', cid=cid, email=email, aid=aid) student = User.lookup(email) if not student: return abort(404) if form.validate_on_submit(): target = User.lookup(form.email.data) if not target or not target.is_enrolled(cid): flash("This user is not enrolled", 'warning') return redirect(result_page) try: Group.force_add(current_user, student, target, assign) except BadRequest as e: flash("Error: {}".format(str(e.description)), 'error') return redirect(result_page) return redirect(result_page)
def templates(cid, aid): courses, current_course = get_courses(cid) assignment = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not Assignment.can(assignment, current_user, 'edit'): flash('Insufficient permissions', 'error') return abort(401) form = forms.AssignmentTemplateForm() if assignment.course != current_course: return abort(401) if form.validate_on_submit(): files = request.files.getlist("template_files") if files: templates = {} for template in files: templates[template.filename] = str(template.read(), 'latin1') assignment.files = templates cache.delete_memoized(Assignment.name_to_assign_info) db.session.commit() flash("Templates Uploaded", "success") # TODO: Use same student facing code rendering/highlighting return render_template('staff/course/assignment/assignment.template.html', assignment=assignment, form=form, courses=courses, current_course=current_course)
def templates(cid, aid): courses, current_course = get_courses(cid) assignment = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not Assignment.can(assignment, current_user, 'edit'): flash('Insufficient permissions', 'error') return abort(401) form = forms.AssignmentTemplateForm() if assignment.course != current_course: return abort(401) if form.validate_on_submit(): files = request.files.getlist("template_files") if files: templates = {} for template in files: templates[template.filename] = str(template.read(), 'latin1') assignment.files = templates cache.delete_memoized(Assignment.name_to_assign_info) db.session.commit() flash("Templates Uploaded", "success") # TODO: Use same student facing code rendering/highlighting return render_template('staff/course/assignment.template.html', assignment=assignment, form=form, courses=courses, current_course=current_course)
def start_github_search(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): flash('Cannot access assignment', 'error') return abort(404) form = forms.GithubSearchRecentForm() if form.validate_on_submit(): job = jobs.enqueue_job( github_search.search_similar_repos, description='Github Search for {}'.format(assign.display_name), course_id=cid, user_id=current_user.id, assignment_id=assign.id, keyword=form.keyword.data, template_name=form.template_name.data, access_token=form.access_token.data, weeks_past=form.weeks_past.data, language=form.language.data, issue_title=form.issue_title.data, issue_body=form.issue_body.data) return redirect(url_for('.course_job', cid=cid, job_id=job.id)) else: return render_template( 'staff/jobs/github_search.html', courses=courses, current_course=current_course, assignment=assign, form=form, )
def get_assignment(name): """Get an assignment with the given name. If the user is not enrolled, flash a warning message. """ assignment = Assignment.by_name(name) if not assignment: abort(404) check_enrollment(assignment.course) return assignment
def assignment_stats(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not Assignment.can(assign, current_user, 'edit'): flash('Insufficient permissions', 'error') return abort(401) stats = Assignment.assignment_stats(assign.id, detailed=True) pie_chart = pygal.Pie(half_pie=True, disable_xml_declaration=True, style=CleanStyle, inner_radius=.5, legend_at_bottom=True) pie_chart.title = 'Students submission status' pie_chart.add('Students with Submissions', stats['students_submitted']) pie_chart.add('Not Submitted', stats['students_nosubmit']) return render_template('staff/course/assignment.stats.html', assignment=assign, subm_chart=pie_chart, courses=courses, stats=stats, current_course=current_course)
def new_assignment(cid): courses, current_course = get_courses(cid) if not Assignment.can(None, current_user, 'create'): flash('Insufficient permissions', 'error') return abort(401) form = forms.AssignmentForm(course=current_course) if form.validate_on_submit(): model = Assignment(course_id=cid, creator_id=current_user.id) form.populate_obj(model) db.session.add(model) db.session.commit() cache.delete_memoized(Assignment.name_to_assign_info) flash("Assignment created successfully.", "success") if form.visible.data: return redirect(url_for(".templates", cid=cid, aid=model.id)) return redirect(url_for(".course_assignments", cid=cid)) return render_template('staff/course/assignment/assignment.new.html', form=form, courses=courses, current_course=current_course)
def test_invite_individual(self): individual_assignment = Assignment(name='cal/cs61a/sp16/lab00', course=self.course, display_name='Lab 0', due_date=datetime.datetime.now(), lock_date=datetime.datetime.now() + datetime.timedelta(days=1), max_group_size=1) db.session.add(individual_assignment) self.assertRaises(BadRequest, Group.invite, self.user1, self.user2, individual_assignment)
def student_assignment_detail(cid, email, aid): courses, current_course = get_courses(cid) page = request.args.get('page', 1, type=int) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): flash('Cannot access assignment', 'error') return abort(404) student = User.lookup(email) if not student.is_enrolled(cid): flash("This user is not enrolled", 'warning') assignment_stats = assign.user_status(student, staff_view=True) user_ids = assign.active_user_ids(student.id) latest = assignment_stats.final_subm or assign.backups(user_ids).first() stats = { 'num_backups': assign.backups(user_ids).count(), 'num_submissions': assign.submissions(user_ids).count(), 'current_q': None, 'attempts': None, 'latest': latest, 'analytics': latest and latest.analytics() } backups = (Backup.query.options( db.joinedload('scores'), db.joinedload('submitter')).filter( Backup.submitter_id.in_(user_ids), Backup.assignment_id == assign.id).order_by( Backup.flagged.desc(), Backup.submit.desc(), Backup.created.desc())) paginate = backups.paginate(page=page, per_page=15) if stats['analytics']: stats['current_q'] = stats['analytics'].get('question') stats['attempts'] = (stats['analytics'].get('history', {}).get('all_attempts')) return render_template('staff/student/assignment.html', courses=courses, current_course=current_course, student=student, assignment=assign, add_member_form=forms.StaffAddGroupFrom(), paginate=paginate, csrf_form=forms.CSRFForm(), stats=stats, assign_status=assignment_stats)
def student_assignment_detail(cid, email, aid): courses, current_course = get_courses(cid) page = request.args.get('page', 1, type=int) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): flash('Cannot access assignment', 'error') return abort(404) student = User.lookup(email) if not student.is_enrolled(cid): flash("This user is not enrolled", 'warning') assignment_stats = assign.user_status(student) user_ids = assign.active_user_ids(student.id) latest = assignment_stats.final_subm or assign.backups(user_ids).first() stats = { 'num_backups': assign.backups(user_ids).count(), 'num_submissions': assign.submissions(user_ids).count(), 'current_q': None, 'attempts': None, 'latest': latest, 'analytics': latest and latest.analytics() } backups = (Backup.query.options(db.joinedload('scores'), db.joinedload('submitter')) .filter(Backup.submitter_id.in_(user_ids), Backup.assignment_id == assign.id) .order_by(Backup.flagged.desc(), Backup.submit.desc(), Backup.created.desc())) paginate = backups.paginate(page=page, per_page=15) if stats['analytics']: stats['current_q'] = stats['analytics'].get('question') stats['attempts'] = (stats['analytics'].get('history', {}) .get('all_attempts')) return render_template('staff/student/assignment.html', courses=courses, current_course=current_course, student=student, assignment=assign, add_member_form=forms.StaffAddGroupFrom(), paginate=paginate, csrf_form=forms.CSRFForm(), upload_form=forms.UploadSubmissionForm(), stats=stats, assign_status=assignment_stats)
def assignment(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign: return abort(404) if not Assignment.can(assign, current_user, 'edit'): flash('Insufficient permissions', 'error') return abort(401) form = forms.AssignmentUpdateForm(obj=assign, course=current_course) stats = Assignment.assignment_stats(assign.id) if form.validate_on_submit(): # populate_obj converts back to UTC form.populate_obj(assign) assign.creator_id = current_user.id cache.delete_memoized(Assignment.name_to_assign_info) db.session.commit() flash("Assignment edited successfully.", "success") return render_template('staff/course/assignment.html', assignment=assign, form=form, courses=courses, stats=stats, current_course=current_course)
def assignment(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign: return abort(404) if not Assignment.can(assign, current_user, 'edit'): flash('Insufficient permissions', 'error') return abort(401) form = forms.AssignmentUpdateForm(obj=assign, course=current_course) stats = Assignment.assignment_stats(assign.id) if form.validate_on_submit(): # populate_obj converts back to UTC form.populate_obj(assign) assign.creator_id = current_user.id cache.delete_memoized(Assignment.name_to_assign_info) db.session.commit() flash("Assignment edited successfully.", "success") return render_template('staff/course/assignment/assignment.html', assignment=assign, form=form, courses=courses, stats=stats, current_course=current_course)
def autograde(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): flash('Cannot access assignment', 'error') return abort(404) form = forms.CSRFForm() if form.validate_on_submit(): try: autograder.autograde_assignment(assign) flash('Submitted to the autograder', 'success') except ValueError as e: flash(str(e), 'error') return redirect(url_for('.assignment', cid=cid, aid=aid))
def staff_submit_backup(cid, email, aid): assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): return abort(404) result_page = url_for('.student_assignment_detail', cid=cid, email=email, aid=aid) student = User.lookup(email) if not student: abort(404) user_ids = assign.active_user_ids(student.id) # TODO: DRY - Unify with student upload code - should just be a function form = forms.UploadSubmissionForm() if form.validate_on_submit(): files = request.files.getlist("upload_files") if files: templates = assign.files messages = {'file_contents': {}} for upload in files: data = upload.read() if len(data) > 2097152: # File is too large (over 2 MB) flash(("{} is over the maximum file size limit of 2MB" .format(upload.filename)), 'danger') return redirect(result_page) messages['file_contents'][upload.filename] = str(data, 'latin1') if templates: missing = [] for template in templates: if template not in messages['file_contents']: missing.append(template) if missing: flash(("Missing files: {}. The following files are required: {}" .format(', '.join(missing), ', '.join([t for t in templates])) ), 'danger') return redirect(result_page) # use student, not current_user backup = ok_api.make_backup(student, assign.id, messages, True) if form.flag_submission.data: assign.flag(backup.id, user_ids) if assign.autograding_key: try: submit_continous(backup) except ValueError as e: flash('Did not send to autograder: {}'.format(e), 'warning') flash("Uploaded submission (ID: {})".format(backup.hashid), 'success') return redirect(result_page)
def assign_grading(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): flash('Cannot access assignment', 'error') return abort(404) form = forms.CreateTaskForm() course_staff = sorted(current_course.get_staff(), key=lambda x: x.role) details = lambda e: "{0} - ({1})".format(e.user.email, e.role) form.staff.choices = [(utils.encode_id(e.user_id), details(e)) for e in course_staff] if not form.staff.data: # Select all by default form.staff.default = [u[0] for u in form.staff.choices] form.process() if form.validate_on_submit(): # TODO: Use worker job for this (this is query intensive) selected_users = [] for hash_id in form.staff.data: user = User.get_by_id(utils.decode_id(hash_id)) if user and user.is_enrolled(cid, roles=STAFF_ROLES): selected_users.append(user) # Available backups data = assign.course_submissions() backups = set(b['backup']['id'] for b in data if b['backup']) students = set(b['user']['id'] for b in data if b['backup']) no_submissions = set(b['user']['id'] for b in data if not b['backup']) tasks = GradingTask.create_staff_tasks(backups, selected_users, aid, cid, form.kind.data, form.only_unassigned.data) num_with_submissions = len(students) - len(no_submissions) flash(("Created {0} tasks ({1} students) for {2} staff.".format( len(tasks), num_with_submissions, len(selected_users))), "success") return redirect(url_for('.assignment', cid=cid, aid=aid)) # Return template with options for who has to grade. return render_template('staff/grading/assign_tasks.html', current_course=current_course, assignment=assign, form=form)
def export_scores(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not Assignment.can(assign, current_user, 'export'): flash('Insufficient permissions', 'error') return abort(401) query = (Score.query.options(db.joinedload('backup')).filter_by( assignment=assign, archived=False)) custom_items = ('time', 'is_late', 'email', 'group') items = custom_items + Enrollment.export_items + Score.export_items def generate_csv(): """ Generate csv export of scores for assignment. Num Queries: ~2N queries for N scores. """ # Yield Column Info as first row yield ','.join(items) + '\n' for score in query: csv_file = StringIO() csv_writer = csv.DictWriter(csv_file, fieldnames=items) submitters = score.backup.enrollment_info() group = [s.user.email for s in submitters] time_str = utils.local_time(score.backup.created, current_course) for submitter in submitters: data = { 'email': submitter.user.email, 'time': time_str, 'is_late': score.backup.is_late, 'group': group } data.update(submitter.export) data.update(score.export) csv_writer.writerow(data) yield csv_file.getvalue() file_name = "{0}.csv".format(assign.name.replace('/', '-')) disposition = 'attachment; filename={0}'.format(file_name) # TODO: Remove. For local performance testing. # return render_template('staff/index.html', data=list(generate_csv())) return Response(stream_with_context(generate_csv()), mimetype='text/csv', headers={'Content-Disposition': disposition})
def new_assignment(cid): courses, current_course = get_courses(cid) if not Assignment.can(None, current_user, 'create'): flash('Insufficient permissions', 'error') return abort(401) form = forms.AssignmentForm(course=current_course) if form.validate_on_submit(): model = Assignment(course_id=cid, creator_id=current_user.id) form.populate_obj(model) db.session.add(model) db.session.commit() cache.delete_memoized(Assignment.name_to_assign_info) flash("Assignment created successfully.", "success") return redirect(url_for(".course_assignments", cid=cid)) return render_template('staff/course/assignment.new.html', form=form, courses=courses, current_course=current_course)
def export_assignment(assignment_id, anonymized): """ Generate a zip file of submissions from enrolled students. Final Submission: One submission per student/group Zip Strucutre: cal-cs61a../[email protected]@b.com/abc12d/hog.py Anonymized: Submission without identifying info Zip Strucutre: cal-cs61a../{hash}/hog.py """ logger = jobs.get_job_logger() assignment = Assignment.query.get(assignment_id) requesting_user = jobs.get_current_job().user if not assignment: logger.warning("No assignment found") raise Exception("No Assignment") if not Assignment.can(assignment, requesting_user, "download"): raise Exception("{} does not have enough permission" .format(requesting_user.email)) if anonymized: logger.info("Starting anonymized submission export") else: logger.info("Starting final submission export") course = assignment.course with io.BytesIO() as bio: # Get a handle to the in-memory zip in append mode with zipfile.ZipFile(bio, "w", zipfile.ZIP_DEFLATED, False) as zf: zf.external_attr = 0o655 << 16 export_loop(bio, zf, logger, assignment, anonymized) created_time = local_time(dt.datetime.now(), course, fmt='%m-%d-%I-%M-%p') zip_name = '{}_{}.zip'.format(assignment.name.replace('/', '-'), created_time) bio.seek(0) # Close zf handle to finish writing zipfile logger.info("Uploading...") upload = ExternalFile.upload(bio, user_id=requesting_user.id, name=zip_name, course_id=course.id, prefix='jobs/exports/{}/'.format(course.offering)) logger.info("Saved as: {0}".format(upload.object_name)) msg = "/files/{0}".format(encode_id(upload.id)) return msg
def assign_grading(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): flash('Cannot access assignment', 'error') return abort(404) form = forms.CreateTaskForm() course_staff = sorted(current_course.get_staff(), key=lambda x: x.role) details = lambda e: "{0} - ({1})".format(e.user.email, e.role) form.staff.choices = [(utils.encode_id(e.user_id), details(e)) for e in course_staff] if not form.staff.data: # Select all by default form.staff.default = [u[0] for u in form.staff.choices] form.process() if form.validate_on_submit(): # TODO: Use worker job for this (this is query intensive) selected_users = [] for hash_id in form.staff.data: user = User.get_by_id(utils.decode_id(hash_id)) if user and user.is_enrolled(cid, roles=STAFF_ROLES): selected_users.append(user) # Available backups: students, backups, no_submissions = assign.course_submissions() tasks = GradingTask.create_staff_tasks(backups, selected_users, aid, cid, form.kind.data, form.only_unassigned.data) num_with_submissions = len(students) - len(no_submissions) flash(("Created {0} tasks ({1} students) for {2} staff." .format(len(tasks), num_with_submissions, len(selected_users))), "success") return redirect(url_for('.assignment', cid=cid, aid=aid)) # Return template with options for who has to grade. return render_template('staff/grading/assign_tasks.html', current_course=current_course, assignment=assign, form=form)
def export_scores(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not Assignment.can(assign, current_user, 'export'): flash('Insufficient permissions', 'error') return abort(401) query = (Score.query.options(db.joinedload('backup')) .filter_by(assignment=assign, archived=False)) custom_items = ('time', 'is_late', 'email', 'group') items = custom_items + Enrollment.export_items + Score.export_items def generate_csv(): """ Generate csv export of scores for assignment. Num Queries: ~2N queries for N scores. """ # Yield Column Info as first row yield ','.join(items) + '\n' for score in query: csv_file = StringIO() csv_writer = csv.DictWriter(csv_file, fieldnames=items) submitters = score.backup.enrollment_info() group = [s.user.email for s in submitters] time_str = utils.local_time(score.backup.created, course) for submitter in submitters: data = {'email': submitter.user.email, 'time': time_str, 'is_late': score.backup.is_late, 'group': group} data.update(submitter.export) data.update(score.export) csv_writer.writerow(data) yield csv_file.getvalue() file_name = "{0}.csv".format(assign.name.replace('/', '-')) disposition = 'attachment; filename={0}'.format(file_name) # TODO: Remove. For local performance testing. # return render_template('staff/index.html', data=list(generate_csv())) return Response(stream_with_context(generate_csv()), mimetype='text/csv', headers={'Content-Disposition': disposition})
def assignment_timeline(cid, email, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): flash('Cannot access assignment', 'error') return abort(404) student = User.lookup(email) if not student.is_enrolled(cid): flash("This user is not enrolled", 'warning') stats = assign.user_timeline(student.id) return render_template('staff/student/assignment.timeline.html', courses=courses, current_course=current_course, student=student, assignment=assign, submitters=stats['submitters'], timeline=stats['timeline'])
def publish_scores(cid, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not Assignment.can(assign, current_user, 'publish_scores'): flash('Insufficient permissions', 'error') abort(401) form = forms.PublishScores(obj=assign) if form.validate_on_submit(): assign.published_scores = form.published_scores.data db.session.commit() flash( "Saved published scores for {}".format(assign.display_name), "success", ) return redirect(url_for('.publish_scores', cid=cid, aid=aid)) return render_template('staff/course/assignment/assignment.publish.html', assignment=assign, form=form, courses=courses, current_course=current_course)
def staff_submit_backup(cid, email, aid): courses, current_course = get_courses(cid) assign = Assignment.query.filter_by(id=aid, course_id=cid).one_or_none() if not assign or not Assignment.can(assign, current_user, 'grade'): return abort(404) student = User.lookup(email) if not student: abort(404) user_ids = assign.active_user_ids(student.id) # TODO: DRY - Unify with student upload code - should just be a function form = forms.StaffUploadSubmissionForm() if form.validate_on_submit(): backup = Backup( submitter=student, creator=current_user, assignment=assign, submit=True, custom_submission_time=form.get_submission_time(assign), ) if form.upload_files.upload_backup_files(backup): db.session.add(backup) db.session.commit() if assign.autograding_key: try: autograder.submit_continous(backup) except ValueError as e: flash('Did not send to autograder: {}'.format(e), 'warning') flash('Uploaded submission'.format(backup.hashid), 'success') return redirect(url_for('.grading', bid=backup.id)) return render_template( 'staff/student/submit.html', current_course=current_course, courses=courses, student=student, assignment=assign, upload_form=form, )
def setup_course(self): """Creates: * A course (self.course) * 2 assignments (self.assignment) in that course * 5 users (self.user1, self.user2, etc.) enrolled as students * 2 staff members (self.staff1, self.staff2) as TAs * 1 lab assistant (self.lab_assistant1) as lab assistants * 1 Admin ([email protected]) """ self.admin = User(email='*****@*****.**', is_admin=True) db.session.add(self.admin) db.session.commit() self.course = Course(offering='cal/cs61a/sp16', institution='UC Berkeley', display_name='CS 61A') self.assignment = Assignment( name='cal/cs61a/sp16/proj1', creator_id=self.admin.id, course=self.course, display_name='Hog', due_date=dt.datetime.now(), lock_date=dt.datetime.now() + dt.timedelta(days=1), max_group_size=4, autograding_key='test') # AG responds with a 200 if ID = 'test' db.session.add(self.assignment) self.assignment2 = Assignment( name='cal/cs61a/sp16/proj2', creator_id=self.admin.id, course=self.course, display_name='Maps', due_date=dt.datetime.now() + dt.timedelta(days=2), lock_date=dt.datetime.now() + dt.timedelta(days=3), max_group_size=3) db.session.add(self.assignment2) def make_student(n): user = User(email='student{0}@aol.com'.format(n)) participant = Enrollment(user=user, course=self.course) db.session.add(participant) return user def make_staff(n, role=constants.STAFF_ROLE): user = User(email='staff{0}@bitdiddle.net'.format(n)) participant = Enrollment(user=user, course=self.course, role=role) db.session.add(participant) return user def make_lab_assistant(n, role=constants.LAB_ASSISTANT_ROLE): user = User(email='lab_assistant{0}@labassist.net'.format(n)) participant = Enrollment(user=user, course=self.course, role=role) db.session.add(participant) return user self.user1 = make_student(1) self.user2 = make_student(2) self.user3 = make_student(3) self.user4 = make_student(4) self.user5 = make_student(5) self.staff1 = make_staff(1) self.staff2 = make_staff(2) self.lab_assistant1 = make_lab_assistant(1) db.session.commit()