def assignment(name): assign = get_assignment(name) user_ids = assign.active_user_ids(current_user.id) fs = assign.final_submission(user_ids) revision = assign.revision(user_ids) scores = assign.scores(user_ids) group = Group.lookup(current_user, assign) can_invite = assign.max_group_size > 1 and assign.active can_remove = group and group.has_status(current_user, 'active') if group: can_invite = len(group.members) < assign.max_group_size has_extension = Extension.get_extension(current_user, assign) data = { 'course': assign.course, 'assignment': assign, 'backups': assign.backups(user_ids).limit(5).all(), 'subms': assign.submissions(user_ids).limit(5).all(), 'final_submission': fs, 'flagged': fs and fs.flagged, 'group': group, 'revision': revision, 'scores': scores, 'can_invite': can_invite, 'can_remove': can_remove, 'has_extension': has_extension, 'csrf_form': CSRFForm() } return render_template('student/assignment/index.html', **data)
def test_extension_group(self): self.set_offset(1) # Allow assignment to be active Group.invite(self.user1, self.user2, self.assignment) Group.invite(self.user1, self.user3, self.assignment) group = Group.lookup(self.user1, self.assignment) group.accept(self.user2) self.set_offset(-2) # Lock assignment ext = self._make_ext(self.assignment, self.user1) self.assertEqual(ext, Extension.get_extension(self.user1, self.assignment)) self.assertEqual(ext, Extension.get_extension(self.user2, self.assignment)) # User 3 has not accepted yet so does not get an extension self.assertFalse(Extension.get_extension(self.user3, self.assignment)) # If user 2 leaves, they no longer have access to the extension self.set_offset(1) # Allow assignment to be active to remove the user. group.remove(self.user1, self.user2) self.assertFalse(Extension.get_extension(self.user2, self.assignment))
def test_extension_group(self): self.set_offset(1) # Allow assignment to be active Group.invite(self.user1, self.user2, self.assignment) Group.invite(self.user1, self.user3, self.assignment) group = Group.lookup(self.user1, self.assignment) group.accept(self.user2) self.set_offset(-1) # Lock assignment ext = self._make_ext(self.assignment, self.user1) self.assertEqual(ext, Extension.get_extension(self.user1, self.assignment)) self.assertEqual(ext, Extension.get_extension(self.user2, self.assignment)) # User 3 has not accepted yet so does not get an extension self.assertFalse(Extension.get_extension(self.user3, self.assignment)) # If user 2 leaves, they no longer have access to the extension group.remove(self.user1, self.user2) self.assertFalse(Extension.get_extension(self.user2, self.assignment))
def _make_ext(self, assignment, user, custom_time=None): if not custom_time: custom_time = dt.datetime.utcnow() ext = Extension(assignment=assignment, user=user, custom_submission_time=custom_time, expires=dt.datetime.utcnow() + dt.timedelta(days=1), staff=self.staff1) db.session.add(ext) db.session.commit() return ext
def get_submission_time(backup, assignment): """ Returns the "time" the backup was submitted. If an extension exists and it hasn't expired, use its ``custom_submission_time`` instead of the backup's. If the extension's ``custom_submission_time`` is None, assume it's right before the assignment's due date. """ extension = Extension.get_extension(backup.submitter, assignment, backup.created) if extension: return extension.custom_submission_time or assignment.due_date return backup.submission_time
def test_edit_extension(self): ext = self._make_ext(self.assignment, self.user1) self.login(self.staff1.email) expires = (dt.datetime.utcnow() + dt.timedelta(days=3)).strftime(constants.ISO_DATETIME_FMT) custom_submission_time = dt.datetime.utcnow().strftime(constants.ISO_DATETIME_FMT) message = 'Sickness' data = { 'assignment_id': self.assignment.id, 'email': self.user1.email, 'expires': expires, 'reason': message, 'submission_time': 'other', 'custom_submission_time': custom_submission_time } self.assert200(self.client.post('/admin/course/{}/extensions/{}'.format(self.course.id, utils.encode_id(ext.id)), data=data, follow_redirects=True)) extension = Extension.get_extension(self.user1, self.assignment, time=dt.datetime.utcnow()) self.assertEqual(extension.staff_id, self.staff1.id) self.assertEqual(extension.assignment_id, self.assignment.id) self.assertEqual(extension.message, message) self.assertEqual(utils.local_time_obj(extension.expires, self.course).replace(tzinfo=None), dt.datetime.strptime(expires, '%Y-%m-%d %H:%M:%S')) self.assertEqual(utils.local_time_obj(extension.custom_submission_time, self.course).replace(tzinfo=None), dt.datetime.strptime(custom_submission_time, '%Y-%m-%d %H:%M:%S'))
def test_extension_expiry(self): ext = self._make_ext(self.assignment, self.user1) self.assertEqual(ext, Extension.get_extension(self.user1, self.assignment)) ext.expires = dt.datetime.utcnow() - dt.timedelta(days=1) self.assertFalse(Extension.get_extension(self.user1, self.assignment))
def test_extension_permissions(self): ext = self._make_ext(self.assignment, self.user1) self.assertFalse(Extension.can(ext, self.user1, 'delete')) self.assertTrue(Extension.can(ext, self.staff1, 'delete'))
def test_extension_basic(self): ext = self._make_ext(self.assignment, self.user1) self.assertEqual(ext, Extension.get_extension(self.user1, self.assignment)) self.assertFalse(Extension.get_extension(self.user2, self.assignment))
def _make_ext(self, assignment, user, custom_time=None): custom_time = custom_time or dt.datetime.utcnow() return Extension.create(assignment=assignment, user=user, custom_submission_time=custom_time, expires=dt.datetime.utcnow() + dt.timedelta(days=1), staff=self.staff1)
def test_extension_expiry(self): ext = self._make_ext(self.assignment, self.user1) self.assertEquals(ext, Extension.get_extension(self.user1, self.assignment)) ext.expires = dt.datetime.utcnow() - dt.timedelta(days=1) self.assertFalse(Extension.get_extension(self.user1, self.assignment))
def test_extension_permissions(self): ext = self._make_ext(self.assignment, self.user1) self.assertFalse(Extension.can(ext, self.user1, 'delete')) self.assertTrue(Extension.can(ext, self.staff1, 'delete'))
def test_extension_basic(self): ext = self._make_ext(self.assignment, self.user1) self.assertEquals(ext, Extension.get_extension(self.user1, self.assignment)) self.assertFalse(Extension.get_extension(self.user2, self.assignment))
def submit_assignment(name): # TODO: Unify student & staff upload. assign = get_assignment(name) group = Group.lookup(current_user, assign) user_ids = assign.active_user_ids(current_user.id) fs = assign.final_submission(user_ids) if not assign.uploads_enabled: flash("This assignment cannot be submitted online", 'warning') return redirect(url_for('.assignment', name=assign.name)) extension = None # No need for an extension if not assign.active: extension = Extension.get_extension(current_user, assign) if not extension: flash("It's too late to submit this assignment", 'warning') return redirect(url_for('.assignment', name=assign.name)) if request.method == "POST": backup = Backup.create( submitter=current_user, assignment=assign, submit=True, ) assignment = backup.assignment if extension: backup.custom_submission_time = extension.custom_submission_time templates = assignment.files or [] files = {} def extract_file_index(file_ind): """ Get the index of of file objects. Used because request.files.getlist() does not handle uniquely indexed lists. >>> extract_file_index('file[12']) 12 """ brace_loc = file_ind.find('[') index_str = file_ind[brace_loc+1:-1] return int(index_str) # A list of one element lists sorted_uploads = sorted(list(request.files.items()), key=lambda x: extract_file_index(x[0])) uploads = [v[1] for v in sorted_uploads] full_path_names = list(request.form.listvalues())[0] template_files = assign.files or [] file_names = [os.path.split(f)[1] for f in full_path_names] missing = [t for t in template_files if t not in file_names] if missing: return jsonify({ 'error': ('Missing files: {}. The following files are required: {}' .format(', '.join(missing), ', '.join(template_files))) }), 400 backup_folder_postfix = time.time() for full_path, upload in zip(full_path_names, uploads): data = upload.read() if len(data) > MAX_UPLOAD_FILE_SIZE: # file is too large (over 25 MB) return jsonify({ 'error': ('{} is larger than the maximum file size of {} MB' .format(full_path, MAX_UPLOAD_FILE_SIZE/1024/1024)) }), 400 try: files[full_path] = str(data, 'utf-8') except UnicodeDecodeError: upload.stream.seek(0) # We've already read data, so reset before uploading dest_folder = "uploads/{}/{}/{}/".format(assign.name, current_user.id, backup_folder_postfix) bin_file = ExternalFile.upload(upload.stream, current_user.id, full_path, staff_file=False, prefix=dest_folder, course_id=assign.course.id, backup=backup, assignment_id=assign.id) db.session.add(bin_file) message = Message(kind='file_contents', contents=files) backup.messages.append(message) db.session.add(backup) db.session.commit() # Send to continuous autograder if assign.autograding_key and assign.continuous_autograding: try: submit_continuous(backup) except ValueError as e: flash('Did not send to autograder: {}'.format(e), 'warning') return jsonify({ 'backup': backup.hashid, 'url': url_for('.code', name=assign.name, submit=backup.submit, bid=backup.id) }) return render_template('student/assignment/submit.html', assignment=assign, group=group, course=assign.course)
def _make_ext(self, assignment, user, custom_time=None): custom_time = custom_time or dt.datetime.utcnow() return Extension.create(assignment=assignment, user=user, custom_submission_time=custom_time, expires=dt.datetime.utcnow() + dt.timedelta(days=1), staff=self.staff1)