def _delete_assignments(ids, delete_class): if delete_class: delete_class = ObjectId(delete_class) # Convert all of the IDs we were given to ObjectIDs in one go ids = [ObjectId(i) for i in ids] logger.info("Deleting assignments %s.", str(ids)) # Query the database for all the assignments we are supposed to delete assignments = list(Assignment.objects(id__in = ids)) # Query the database for all the submissions we are supposed to delete, this # will potentially be an absolutely massive list, so we do not want to # place all the results into a list immediately like we did for the # assignments. submissions = Submission.objects(assignment__in = ids) # Delete assignments directory on the filesystem for i in assignments: try: shutil.rmtree( os.path.join(config["SUBMISSION_DIRECTORY"], str(i.id)) ) except OSError as e: # We don't want to explode if the directory has been deleted for us # but it is weird so log a warning. if e.errno == errno.ENOENT: logger.warning("Assignment directory missing for %s", str(i.id)) else: raise # Actually delete the submissions from the database Submission.objects(assignment__in = ids).delete() # Delete the assignments Assignment.objects(id__in = ids).delete() if delete_class: # Unenroll all students in the class User.objects(classes = delete_class).update( pull__classes = delete_class ) # Delete the class Class.objects(id = delete_class).delete()
def _delete_assignments(ids, delete_class): if delete_class: delete_class = ObjectId(delete_class) # Convert all of the IDs we were given to ObjectIDs in one go ids = [ObjectId(i) for i in ids] logger.debug("Deleting assignments %s.", ids) # Query the database for all the assignments we are supposed to delete assignments = list(Assignment.objects(id__in = ids)) # Query the database for all the submissions we are supposed to delete, this # will potentially be an absolutely massive list, so we do not want to # place all the results into a list immediately like we did for the # assignments. submissions = Submission.objects(assignment__in = ids) # Go through all of the submissions and delete the files from the # filesystem. We will tell the database to delete all of the submissions # in one go afterwards. for i in submissions: # Delete the submission on the filesystem. try: shutil.rmtree( os.path.join(config["SUBMISSION_DIRECTORY"], str(i.id)) ) except OSError as e: logger.warn( "Failed to delete submission with id %s: %s", str(i.id), str(e) ) # Actually delete the submissions from the database Submission.objects(assignment__in = ids).delete() # Delete the assignments Assignment.objects(id__in = ids).delete() if delete_class: # Unenroll all students in the class User.objects(classes = delete_class).update( pull__classes = delete_class ) # Delete the class Class.objects(id = delete_class).delete()
def _delete_assignments(ids, delete_class): if delete_class: delete_class = ObjectId(delete_class) # Convert all of the IDs we were given to ObjectIDs in one go ids = [ObjectId(i) for i in ids] logger.debug("Deleting assignments %s.", ids) # Query the database for all the assignments we are supposed to delete assignments = list(Assignment.objects(id__in=ids)) # Query the database for all the submissions we are supposed to delete, this # will potentially be an absolutely massive list, so we do not want to # place all the results into a list immediately like we did for the # assignments. submissions = Submission.objects(assignment__in=ids) # Go through all of the submissions and delete the files from the # filesystem. We will tell the database to delete all of the submissions # in one go afterwards. for i in submissions: # Delete the submission on the filesystem. try: shutil.rmtree( os.path.join(config["SUBMISSION_DIRECTORY"], str(i.id))) except OSError as e: logger.warn("Failed to delete submission with id %s: %s", str(i.id), str(e)) # Actually delete the submissions from the database Submission.objects(assignment__in=ids).delete() # Delete the assignments Assignment.objects(id__in=ids).delete() if delete_class: # Unenroll all students in the class User.objects(classes=delete_class).update(pull__classes=delete_class) # Delete the class Class.objects(id=delete_class).delete()
def _create_assignment_csv(csv_id, requester, assignment): csv_id = ObjectId(csv_id) csv_file = temp_directory = "" # Find any expired archives and remove them deleted_files = [] for i in CSV.objects(expires__lt=datetime.datetime.today()): deleted_files.append(i.file_location) if i.file_location: try: os.remove(i.file_location) except OSError as e: logger.warning("Could not remove expired csv file at %s: %s.", i.file_location, str(e)) i.delete() if deleted_files: logger.info("Deleted csv files %s.", str(deleted_files)) # This is the CSV object that will be added to the database new_csv = CSV(id=csv_id, requester=requester) temp_directory = csv_file = None try: assn = Assignment.objects.get(id=ObjectId(assignment)) # Grab all student users for this class. users = list( User.objects(account_type="student", classes=assn.for_class)) # Form the query query = { "assignment": ObjectId(assignment), "most_recent": True, "user__in": [i.id for i in users] } # Grab the most recent submissions from each user. submissions = list(Submission.objects(**query)) # Create the actual csv file. csv_file = open(os.path.join(config["CSV_DIRECTORY"], str(csv_id)), "w") for i in submissions: score = "None" if i.test_results: test_result = TestResult.objects.get(id=i.test_results) score = str(test_result.score) print >> csv_file, "%s,%s,%s" % \ (i.user, score, i.timestamp.strftime("%Y-%m-%d-%H-%M-%S")) csv_file.close() new_csv.file_location = os.path.join(config["CSV_DIRECTORY"], str(csv_id)) new_csv.expires = \ datetime.datetime.today() + config["TEACHER_CSV_LIFETIME"] new_csv.save(force_insert=True) except Exception as e: new_csv.file_location = None os.remove(os.path.join(config["CSV_DIRECTORY"], str(csv_id))) new_csv.error_string = str(e) new_csv.save(force_insert=True) raise
def view_assignment(assignment_id): simple_archive_form = SimpleArchiveForm() # Convert the assignment in the URL into an ObjectId try: id = ObjectId(assignment_id) except InvalidId: logger.info("Invalid Assignment ID requested.") abort(404) # Retrieve the assignment try: assignment = Assignment.objects.get(id=id) except Assignment.DoesNotExist: logger.info("Non-extant ID requested.") abort(404) assignment.apply_personal_deadlines(current_user) # Get all of the submissions for this assignment submissions = list( Submission.objects(user=current_user.id, assignment=id).order_by("-most_recent", "-timestamp")) # Get student submissions if being viewed as teacher student_submissions = [] students = [] if current_user.account_type in ["teacher", "teaching_assistant"]: students = list( User.objects(classes=assignment.for_class, account_type="student").order_by("-email")) student_submissions = list( Submission.objects(user__in=[i.email for i in students], assignment=assignment_id, most_recent=True)) test_results = list( TestResult.objects( id__in=[i.test_results for i in submissions if i.test_results])) # Match test results to submissions for i in submissions: for j in test_results: if i.test_results == j.id: i.test_results_obj = j # Current time to be compared to submission test_request_timestamp now = datetime.datetime.now() # Flag to set refresh trigger if user is waiting on test results wait_and_refresh = False # Add the pretty version of each submissions timestamp for i in submissions: i.timestamp_pretty = pretty_time(i.timestamp) i.status = "Submitted" # If the user submitted a test request and there aren't any results if (i.test_request_timestamp and not i.test_results): timedelta = now - i.test_request_timestamp i.show_resubmit = (timedelta > config["STUDENT_RETRY_INTERVAL"]) if not i.show_resubmit: i.status = "Waiting for test results..." else: i.status = "Test request timed out" elif (i.test_results and i.test_results_obj.failed): i.status = "Tests Failed" i.show_resubmit = True elif (i.test_results and not i.test_results_obj.failed): i.status = "Tests Completed" wait_and_refresh = \ any(i.status == "Waiting for test results..." for i in submissions) return render_template( "assignment.html", now=datetime.datetime.today(), create_time_element=create_time_element, assignment=assignment, submissions=submissions, simple_archive_form=simple_archive_form, wait_and_refresh=wait_and_refresh, new_submissions=[ v for k, v in get_flashed_messages(with_categories=True) if k == "new_submission" ], view_as_teacher=(current_user.account_type in ["teacher", "teaching_assistant"]), students=students, student_submissions=student_submissions, enumerate=enumerate)
def _create_assignment_csv(csv_id, requester, assignment): csv_id = ObjectId(csv_id) csv_file = temp_directory = "" # Find any expired archives and remove them deleted_files = [] for i in CSV.objects(expires__lt = datetime.datetime.today()): deleted_files.append(i.file_location) if i.file_location: try: os.remove(i.file_location) except OSError as e: logger.warning( "Could not remove expired csv file at %s: %s.", i.file_location, str(e) ) i.delete() if deleted_files: logger.info("Deleted csv files %s.", str(deleted_files)) # This is the CSV object that will be added to the database new_csv = CSV( id = csv_id, requester = requester ) temp_directory = csv_file = None try: assn = Assignment.objects.get(id = ObjectId(assignment)) # Grab all student users for this class. users = list( User.objects( account_type = "student", classes = assn.for_class ) ) # Form the query query = { "assignment": ObjectId(assignment), "most_recent": True, "user__in": [i.id for i in users] } # Grab the most recent submissions from each user. submissions = list(Submission.objects(**query)) # Create the actual csv file. csv_file = open(os.path.join(config["CSV_DIRECTORY"], str(csv_id)), "w") for i in submissions: score = "None" if i.test_results: test_result = TestResult.objects.get(id = i.test_results) score = str(test_result.score) print >> csv_file, "%s,%s,%s" % \ (i.user, score, i.timestamp.strftime("%Y-%m-%d-%H-%M-%S")) csv_file.close() new_csv.file_location = os.path.join(config["CSV_DIRECTORY"], str(csv_id)) new_csv.expires = \ datetime.datetime.today() + config["TEACHER_CSV_LIFETIME"] new_csv.save(force_insert = True) except Exception as e: new_csv.file_location = None os.remove(os.path.join(config["CSV_DIRECTORY"], str(csv_id))) new_csv.error_string = str(e) new_csv.save(force_insert = True) raise
def view_assignment(assignment_id): simple_archive_form = SimpleArchiveForm() # Convert the assignment in the URL into an ObjectId try: id = ObjectId(assignment_id) except InvalidId: logger.info("Invalid Assignment ID requested.") abort(404) # Retrieve the assignment try: assignment = Assignment.objects.get(id=id) except Assignment.DoesNotExist: logger.info("Non-extant ID requested.") abort(404) assignment.apply_personal_deadlines(current_user) # Get all of the submissions for this assignment submissions = list(Submission.objects(user=current_user.id, assignment=id).order_by("-most_recent", "-timestamp")) # Get student submissions if being viewed as teacher student_submissions = [] students = [] if current_user.account_type in ["teacher", "teaching_assistant"]: students = list(User.objects(classes=assignment.for_class, account_type="student").order_by("-email")) student_submissions = list( Submission.objects(user__in=[i.email for i in students], assignment=assignment_id, most_recent=True) ) test_results = list(TestResult.objects(id__in=[i.test_results for i in submissions if i.test_results])) # Match test results to submissions for i in submissions: for j in test_results: if i.test_results == j.id: i.test_results_obj = j # Current time to be compared to submission test_request_timestamp now = datetime.datetime.now() # Flag to set refresh trigger if user is waiting on test results wait_and_refresh = False # Add the pretty version of each submissions timestamp for i in submissions: i.timestamp_pretty = pretty_time(i.timestamp) i.status = "Submitted" # If the user submitted a test request and there aren't any results if i.test_request_timestamp and not i.test_results: timedelta = now - i.test_request_timestamp i.show_resubmit = timedelta > config["STUDENT_RETRY_INTERVAL"] if not i.show_resubmit: i.status = "Waiting for test results..." else: i.status = "Test request timed out" elif i.test_results and i.test_results_obj.failed: i.status = "Tests Failed" i.show_resubmit = True elif i.test_results and not i.test_results_obj.failed: i.status = "Tests Completed" wait_and_refresh = any(i.status == "Waiting for test results..." for i in submissions) return render_template( "assignment.html", now=datetime.datetime.today(), create_time_element=create_time_element, assignment=assignment, submissions=submissions, simple_archive_form=simple_archive_form, wait_and_refresh=wait_and_refresh, new_submissions=[v for k, v in get_flashed_messages(with_categories=True) if k == "new_submission"], view_as_teacher=(current_user.account_type in ["teacher", "teaching_assistant"]), students=students, student_submissions=student_submissions, enumerate=enumerate, )
def _zip_bulk_submissions(archive_id, requester, assignment, email = ""): archive_id = ObjectId(archive_id) archive_file = temp_directory = "" # Find any expired archives and remove them deleted_files = [] for i in Archive.objects(expires__lt = datetime.datetime.today()): deleted_files.append(i.file_location) if i.file_location: try: os.remove(i.file_location) except OSError as e: logger.warning( "Could not remove expired archive at %s: %s.", i.file_location, str(e) ) i.delete() if deleted_files: logger.info("Deleted archives %s.", str(deleted_files)) # This is the archive object we will eventually add to the database new_archive = Archive( id = archive_id, requester = requester, archive_type = "assignment_package" ) temp_directory = archive_file = None try: # Form the query query = {"assignment": ObjectId(assignment)} # Only mention email in the query if it's not None or the empty # string, otherwise mongo will look for submissions that list the # user as None or the empty string (which should be exactly none of # the submission in the system). if email: query["user"] = email else: # Otherwise, we need to be careful not to get teacher/TA submissions. assn = Assignment.objects.get(id = ObjectId(assignment)) students = User.objects( account_type="student", classes = assn.for_class ) query["user__in"] = [i.id for i in students] # Grab all the submissions submissions = list(Submission.objects(**query)) if not submissions: logger.info("No submissions found matching query.") return # Organize all the submissions by user name, as this will closely # match the structure of the archive we will build. submission_map = {} for i in submissions: if i.user in submission_map: submission_map[i.user].append(i) else: submission_map[i.user] = [i] # Create a temporary directory we will create our archive in. temp_directory = tempfile.mkdtemp() # Create our directory tree. Instead of making new folders for each # submission and copying the user's files over however, we will # create symlinks to save space and time. for user, user_submissions in submission_map.items(): # Create a directory for the user os.makedirs(os.path.join(temp_directory, user)) # Create symlinks for all his submissions. Each symlink is # named after the submission date. for i in user_submissions: time_stamp = i.timestamp.strftime("%Y-%m-%d-%H-%M-%S") symlink_path = \ os.path.join(temp_directory, user, time_stamp) # In the highly unlikely event that two of the same user's # submissions have the same exact time stamp, we'll need to # add a marker to the end of the timestamp. marker = 0 while os.path.exists(symlink_path + ("-%d" % marker if marker > 0 else "")): marker += 1 if marker > 0: symlink_path += "-%d" % marker original_path = i.getFilePath() # Detect if the submission's files are still on the filesystem if os.path.isdir(original_path): # Create a symlink pointing to the actual submission # directory with the name we gnerated os.symlink(original_path, symlink_path) else: # Create an empty text file marking the fact that a # submissions existed but is no longer available. open(symlink_path, "w").close() # Create the actual archive file. # TODO: Create it in galah's /var/ directory file_descriptor, archive_file = tempfile.mkstemp(suffix = ".zip") os.close(file_descriptor) # Run zip and do the actual archiving. Will block until it's finished. zipdir(temp_directory, archive_file) new_archive.file_location = archive_file new_archive.expires = \ datetime.datetime.today() + config["TEACHER_ARCHIVE_LIFETIME"] new_archive.save(force_insert = True) except Exception as e: # If we created a temporary archive file we need to delete it. new_archive.file_location = None if archive_file: os.remove(archive_file) new_archive.error_string = str(e) new_archive.save(force_insert = True) raise finally: if temp_directory: shutil.rmtree(temp_directory)
def _create_gradebook_csv(csv_id, requester, class_id, fill=0): csv_id = ObjectId(csv_id) csv_file = temp_directory = "" # Find any expired archives and remove them deleted_files = [] for i in CSV.objects(expires__lt = datetime.datetime.today()): deleted_files.append(i.file_location) if i.file_location: try: os.remove(i.file_location) except OSError as e: logger.warning( "Could not remove expired csv file at %s: %s.", i.file_location, str(e) ) i.delete() if deleted_files: logger.info("Deleted csv files %s.", str(deleted_files)) # This is the CSV object that will be added to the database new_csv = CSV( id = csv_id, requester = requester ) temp_directory = csv_file = None try: # Create the actual csv file. csv_file = open(os.path.join(config["CSV_DIRECTORY"], str(csv_id)), "w") the_class = Class.objects.get(id = ObjectId(class_id)) # Grab all assignments in this class assns = list( Assignment.objects(for_class = the_class.id) ) print >> csv_file, "%s,%s" % \ ("Username", ",".join('"{0}"'.format(i.name) for i in assns)) # Grab all student users for this class. users = list( User.objects( account_type = "student", classes = the_class.id ) ) assn_ids = [i.id for i in assns] for user in users: # Query for user's most recent submissions in the known assignments query = { "assignment__in": assn_ids, "most_recent": True, "user": user.id } submissions = list(Submission.objects(**query)) # Initialize each assignment score to empty at first. assn_to_score = OrderedDict((i, str(fill)) for i in assn_ids) # Go through submissions, associating scores with assignment for sub in submissions: if sub.test_results: test_result = TestResult.objects.get(id = sub.test_results) if test_result.score is not None: assn_to_score[sub.assignment] = str(test_result.score) # Write gradebook results to csv file. print >> csv_file, "%s,%s" % \ (user.email, ",".join(assn_to_score.values())) csv_file.close() new_csv.file_location = os.path.join(config["CSV_DIRECTORY"], str(csv_id)) new_csv.expires = \ datetime.datetime.today() + config["TEACHER_CSV_LIFETIME"] new_csv.save(force_insert = True) except Exception as e: new_csv.file_location = None os.remove(os.path.join(config["CSV_DIRECTORY"], str(csv_id))) new_csv.error_string = str(e) new_csv.save(force_insert = True) raise