def delete_task(): data = json.loads(request.data) task_id = data['taskId'] db.connect() submissions = db.select_columns('submissions', ['name'], ['task'], [task_id]) if submissions: db.close() return error('Cannot delete this task!' + '<br>Students have already made submissions') task_path = db.select_columns('task_attachments', ['path'], ['task'], [task_id]) if task_path: file_upload = FileUpload(filename=task_path[0][0]) file_upload.remove_file() db.delete_rows('tasks', ['id'], [task_id]) db.delete_rows('task_attachments', ['task'], [task_id]) db.delete_rows('task_criteria', ['task'], [task_id]) db.delete_rows('allowed_files', ['task'], [task_id]) db.delete_rows('submission_types', ['task'], [task_id]) db.close() return jsonify({'status': 'ok', "message": "Task deleted"})
def delete_material(): data = json.loads(request.data) material_id = data['materialId'] db.connect() material_path = db.select_columns('material_attachments', ['path'], ['material'], [material_id]) if material_path: file_upload = FileUpload(filename=material_path[0][0]) file_upload.remove_file() db.delete_rows('materials', ['id'], [material_id]) db.delete_rows('material_attachments', ['material'], [material_id]) db.close() return jsonify({'status': 'ok', "message": "Material deleted"})
def build_student_submission(student_id, task_id): res = db.select_columns( 'submissions', ['name', 'path', 'text', 'date_modified', 'status'], ['student', 'task'], [student_id, task_id]) # Account for no submission and a text based submission (no path) if not res: return {} submission = { 'name': res[0][0], 'date_modified': timestamp_to_string(res[0][3], True), 'status': { 'id': res[0][4] }, 'file': None, 'text': None } task = build_task(task_id) if 'file' in task['sub_method']['name']: submission['file'] = FileUpload(filename=res[0][1]) else: submission['text'] = res[0][2] res = db.select_columns('request_statuses', ['name'], ['id'], [submission['status']['id']]) submission['status']['name'] = res[0][0] if 'approval' in task['mark_method']['name']: submission['mark_method'] = 'approval' else: submission['mark_method'] = 'mark' return submission
def allowed_file_access(filename): ''' Check if a file access should be permitted ''' if 'user' not in session: raise KeyError('Not logged in') if not is_at_least_role(UserRole.STUDENT): # public users shouldn't have access to any file uploads return False if is_at_least_role(UserRole.STAFF): # allow staff to have access to anything return True # students should only have access to files they have submitted # or files in tasks within courses they are part of # or material files within courses they are part of # as long as the task and/or material is marked as visible try: name = FileUpload(filename=filename).get_name() except LookupError as e: if config.DEBUG: print(f'Request file: {e}') abort(404) db.connect() submitted_file = db.select_columns('submissions', ['path'], ['student', 'path'], [session['id'], name]) task_files = queries.get_allowed_task_attachments(session['id']) materials = queries.get_allowed_material_attachments(session['id']) db.close() if submitted_file or (task_files and name in task_files) or \ (materials and name in materials): return True else: return False
def view_submission(): student_id = request.args.get('submissions', None, type=int) if student_id is None: abort(400) db.connect() student_info = db.select_columns('users', ['name', 'email'], ['id'], [student_id]) if not len(student_info): db.close() abort(404) # get tasks for this student tasks = [] student_tasks = queries.get_student_submissions(student_id) for task in student_tasks: submit_date_text = timestamp_to_string(task[4], True) file_url = None if task[3]: file_url = FileUpload(filename=task[3]).get_url() status = get_sub_status(student_id, task[0]) if 'approval' in task[2]: tasks.append((task[1], submit_date_text, status, file_url, task[0], student_id)) else: criteria = db.select_columns('task_criteria', ['id', 'max_mark'], ['task'], [task[0]]) staff_mark = 0 total_max_mark = 0 for c in criteria: total_max_mark += c[1] mark = db.select_columns('marks', ['mark'], ['criteria', 'student', 'marker'], [c[0], student_id, session['id']]) if len(mark) != 0: staff_mark += mark[0][0] else: staff_mark = -1 if staff_mark <= 0: staff_mark = '- ' tasks.append((task[1], submit_date_text, str(staff_mark) + '/' + str(total_max_mark), file_url, task[0], student_id)) db.close() zid = student_info[0][1].split('@')[0] heading = f'Submissions - {student_info[0][0]} ({zid})' return render_template('submission_staff.html', heading=heading, title=heading, submissions=tasks)
def upload_enroll_file(): try: enroll_file = FileUpload(req=request) except KeyError: return error('Could not find a file to upload') if enroll_file.get_extention() != '.csv': return error('File type must be in .csv format') if enroll_file.get_size() > config.MAX_FILE_SIZE: return error( f'File exceeds the maximum size of {config.MAX_FILE_SIZE} MB') enroll_file.commit() db.connect() error_string = update_from_file(enroll_file.get_path(), session['current_co'], 'student') db.close() enroll_file.remove_file() if error_string != "": return error(error_string) return jsonify({'status': 'ok'})
def build_task(task_id): 'Assumes you already have a database connection open' res = db.select_columns('tasks', [ 'deadline', 'marking_method', 'visible', 'course_offering', 'word_limit', 'name', 'description', 'submission_method', 'word_limit', 'size_limit' ], ['id'], [task_id]) if not res: return None task = { 'id': task_id, 'deadline': res[0][0], 'pretty_deadline': timestamp_to_string(res[0][0], True), 'mark_method': { 'id': res[0][1] }, 'sub_method': { 'id': res[0][7] }, 'visible': res[0][2], 'offering': res[0][3], 'word_limit': res[0][4], 'name': res[0][5], 'description': res[0][6], 'text_limit': res[0][8], 'file_limit': res[0][9], 'attachment': None, 'accepted_files': queries.get_tasks_accepted_files(task_id) } res = queries.get_general_task_info(task_id) task['course_name'] = res[0][0] res = db.select_columns('marking_methods', ['name'], ['id'], [task['mark_method']['id']]) task['mark_method']['name'] = res[0][0] res = db.select_columns('submission_methods', ['name'], ['id'], [task['sub_method']['id']]) task['sub_method']['name'] = res[0][0] res = db.select_columns('task_attachments', ['path'], ['task'], [task_id]) if res: task['attachment'] = FileUpload(filename=res[0][0]) return task
def manage_course_offerings(): data = {} if request.method == 'POST': data = json.loads(request.data) if 'table' in data: if data['table'] == 'update_account_types': db.connect() if not re.match(config.EMAIL_FORMAT, data['email']): db.close() return error(f"""Invalid email address<br> {config.EMAIL_FORMAT_ERROR}""") update_account_type(data['email'], data['name'], data['account_type'], session['current_co']) db.close() if data['table'] == 'materials': db.connect() db.update_rows('materials', [data['type']], ['visible'], ['id'], [data['id']]) db.close() if data['table'] == 'tasks': db.connect() db.update_rows('tasks', [data['type']], ['visible'], ['id'], [data['id']]) db.close() return jsonify({'status': 'ok'}) co = 1 if 'current_co' in session: co = session['current_co'] else: session['current_co'] = co # maybe default to whatever course is in the current session db.connect() course_offerings = queries.get_course_offering_details() co_map, courses, sessions = split_co(course_offerings) materials_query = db.select_columns('materials', ['id', 'name', 'visible'], ['course_offering'], [co]) materials = [] for material in materials_query: attachments = [] attachments_query = db.select_columns('material_attachments', ['path'], ['material'], [material[0]]) for x in attachments_query: attachments.append(FileUpload(filename=x[0])) materials.append((material[0], material[1], attachments, material[2])) tasks = [] task_query = db.select_columns('tasks', ['id', 'name', 'deadline', 'visible'], ['course_offering'], [co]) task_ids = [] task_query.sort(key=lambda x: x[2]) for task in task_query: attachments = [] attachments_query = db.select_columns('task_attachments', ['path'], ['task'], [task[0]]) for x in attachments_query: attachments.append(FileUpload(filename=x[0])) print_date = timestamp_to_string(task[2]) tasks.append((task[0], task[1], print_date, attachments, task[3])) task_ids.append(task[0]) enrollments = [] enrollments_query = queries.get_student_enrollments(co) for student in enrollments_query: zid = student[2].split('@')[0] if student[3] is not None: enrollments.append( (student[1], zid, student[2], student[3], student[0])) else: enrollments.append( (student[1], zid, student[2], 'No topic', student[0])) # for material file upload file_types = db.select_columns('file_types', ['name']) file_types = list(map(lambda x: x[0], file_types)) allowed_file_types = ','.join(file_types) account_types = get_all_account_types() accepted_account_types = [('student', account_types['student']), ('admin', account_types['course_admin'])] sessions_list = sessions[co_map[co]['course']] enrollments.sort(key=lambda x: zid_sort(x[1])) db.close() return render_template('manage_courses.html', title='Manage Courses', heading='Manage Courses', materials=materials, tasks=tasks, enrollments=enrollments, courses=courses, sessions=sessions_list, co_map=co_map, default_co=co, max_file_size=config.MAX_FILE_SIZE, accepted_files=allowed_file_types, task_ids=task_ids)
def upload_material(): try: fields = [ 'file-label', 'file-name', 'course-offering', 'old-material-id', 'delete-old-file' ] file_label, file_name, course_offering, old_material_id, \ delete_old_file = \ get_fields(request.form, fields, optional=['word-limit', 'file-name'], ints=['course-offering']) except ValueError as e: return e.args[0] try: old_material_id = int(old_material_id) except ValueError as e: old_material_id = None # check if no file when there should be one if file_name == '' and \ (delete_old_file == 'true' or old_material_id is None): return error('File is required!') db.connect() # check if course offering is valid res = db.select_columns('course_offerings', ['id'], ['id'], [course_offering]) if not len(res): db.close() return error('Cannot attach material to unknown course offering') # check if material with same label exists in course res = db.select_columns('materials', ['id'], ['name', 'course_offering'], [file_label, course_offering]) if len(res) and old_material_id != res[0][0]: db.close() return error('An item with that label already exists in this course') # otherwise, we can insert the material into the course if len(file_name): try: sent_file = FileUpload(req=request) except KeyError: db.close() return error('Could not find a file to upload') res = db.select_columns('file_types', ['name']) file_types = list(map(lambda x: x[0], res)) if sent_file.get_extention() not in file_types: db.close() accept_files = ', '.join(file_types) return error(f'Accepted file types are: {accept_files}') if sent_file.get_size() > config.MAX_FILE_SIZE: sent_file.remove_file() db.close() return error( f'File exceeds the maximum size of {config.MAX_FILE_SIZE} MB') sent_file.commit() if delete_old_file == 'true': old = db.select_columns('material_attachments', ['path'], ['material'], [old_material_id]) if old: db.delete_rows('material_attachments', ['material'], [old_material_id]) try: prev_submission = FileUpload(filename=old[0][0]) prev_submission.remove_file() except LookupError: # If the file doesn't exists don't worry as we are deleting # the attachment anyway pass if old_material_id is not None: # update existing material entries db.update_rows('materials', [file_label], ['name'], ['id'], [old_material_id]) db.update_rows('materials', [file_label], ['name'], ['id'], [old_material_id]) if delete_old_file == 'true': db.insert_single( 'material_attachments', [old_material_id, sent_file.get_name()], ['material', 'path']) else: # add material and file path to db db.insert_single('materials', [course_offering, file_label, 0], ['course_offering', 'name', 'visible']) res = db.select_columns('materials', ['id'], ['name', 'course_offering'], [file_label, course_offering]) db.insert_single('material_attachments', [res[0][0], sent_file.get_name()], ['material', 'path']) db.close() return jsonify({'status': 'ok'})
def create(): course_id = request.args.get('course_offering_id', None, type=int) if request.method == 'GET': if course_id is None: abort(400) db.connect() res = db.select_columns('course_offerings', ['id'], ['id'], [course_id]) if not len(res): db.close() abort(404) file_types = db.select_columns('file_types', ['name']) file_types = list(map(lambda x: x[0], file_types)) allowed_file_types = ','.join(file_types) heading = 'Create Task' default_fields = { 'task-name': '', 'deadline': '', 'task-description': '', 'submission-type': 'text', 'word-limit': '', 'maximum-file-size': '', 'accepted-file-type': '', 'marking-method': 'accept', 'criteria': [], 'task_attachments': [] } # if updating old task then load old task data old_task_id = request.args.get('update', None, type=int) if old_task_id is not None: res = queries.get_past_task_data(old_task_id) if res is not None: res = res[0] heading = 'Edit Task' # basic task details default_fields['task-name'] = res[0] time_format = '%d/%m/%Y %H:%M' due_date = datetime.fromtimestamp(res[1]) default_fields['deadline'] = due_date.strftime(time_format) default_fields['task-description'] = res[2] attachments = db.select_columns('task_attachments', ['path'], ['task'], [old_task_id]) for r in attachments: file = [FileUpload(filename=r[0])] default_fields['task_attachments'] = file # submission method specific if res[3] == 'text submission': default_fields['word-limit'] = res[4] else: default_fields['submission-type'] = 'file' default_fields['maximum-file-size'] = int(res[5]) default_fields['accepted-file-type'] = res[6] # marking method specifics if res[7] == 'requires mark': default_fields['marking-method'] = 'criteria' crit = db.select_columns('task_criteria', ['name, max_mark'], ['task'], [old_task_id]) if crit is not None: default_fields['criteria'] = crit db.close() if default_fields['maximum-file-size'] == '': default_fields['maximum-file-size'] = 5 if default_fields['accepted-file-type'] == '': default_fields['accepted-file-type'] = '.pdf' return render_template('create_task.html', heading=heading, title=heading, file_types=file_types, course_id=course_id, max_file_size=config.MAX_FILE_SIZE, max_word_limit=config.MAX_WORD_LIMIT, accepted_file_types=allowed_file_types, old_task_id=old_task_id, default_fields=default_fields) try: fields = [ 'task-name', 'deadline', 'task-description', 'submission-type', 'word-limit', 'maximum-file-size', 'accepted-file-type', 'marking-method', 'num-criteria', 'course-id', 'file-name', 'old_task_id', 'delete_old_attachment' ] task_name, deadline, task_description, submission_type, \ word_limit, max_file_size, accepted_ftype, marking_method, \ num_criteria, course_id, file_name, old_task_id, \ delete_old_attachment = \ get_fields(request.form, fields, optional=['word-limit', 'file-name'], ints=['maximum-file-size', 'num-criteria', 'word-limit', 'course-id', 'delete_old_attachment']) except ValueError as e: return e.args[0] try: old_task_id = int(old_task_id) except ValueError as e: old_task_id = None try: deadline = datetime.strptime(deadline, '%d/%m/%Y %H:%M').timestamp() except ValueError: return error('Invalid date format for deadline!') if submission_type == 'file': max_size = config.MAX_FILE_SIZE if not (1 <= max_file_size <= max_size): return error( f'Maximum file size must be between 1 and {max_size}!') elif submission_type == 'text': try: word_limit = get_fields(request.form, ['word-limit'], ints=['word-limit'])[0] except ValueError as e: return e.args[0] max_word_limit = config.MAX_WORD_LIMIT if not (1 <= word_limit <= max_word_limit): return error(f'Word limit must be between 1 and {max_word_limit}!') else: return error('Unknown submission type!') if marking_method == 'criteria': if num_criteria < 1: return error('At least one marking criterion is required!') else: criteria = [f'criteria-{i}' for i in range(1, num_criteria + 1)] marks = [f'maximum-mark-{i}' for i in range(1, num_criteria + 1)] try: criteria = get_fields(request.form, criteria) marks = get_fields(request.form, marks, ints=marks) except ValueError as e: return e.args[0] if sum([mark for mark in marks]) != 100: return error('Marks must add to 100!') elif marking_method != 'accept': return error('Unknown marking method!') db.connect() res = db.select_columns('course_offerings', ['id'], ['id'], [course_id]) if not len(res): db.close() return error('Cannot create task for unknown course!') res = db.select_columns('tasks', ['id', 'name'], ['name', 'course_offering'], [task_name, course_id]) if len(res) and res[0][0] != old_task_id: db.close() return error('A task with that name already exists in this course!') # retrieve some foreign keys for insertion res = db.select_columns('file_types', ['id'], ['name'], [accepted_ftype]) if not len(res): db.close() return error('Invalid or unsupported file type!') file_type_id = res[0][0] # upload file if present sent_file = None if len(file_name): try: sent_file = FileUpload(req=request) except KeyError: db.close() return error('Could not find a file to upload') res = db.select_columns('file_types', ['name']) file_types = list(map(lambda x: x[0], res)) if sent_file.get_extention() not in file_types: db.close() accept_files = ', '.join(file_types) return error(f'Accepted file types are: {accept_files}') if sent_file.get_size() > config.MAX_FILE_SIZE: sent_file.remove_file() db.close() return error( f'File exceeds the maximum size of {config.MAX_FILE_SIZE} MB') sent_file.commit() if (len(file_name) and old_task_id is not None) or delete_old_attachment: old = db.select_columns('task_attachments', ['path'], ['task'], [old_task_id]) if old: db.delete_rows('task_attachments', ['task'], [old_task_id]) try: prev_submission = FileUpload(filename=old[0][0]) prev_submission.remove_file() except LookupError: # If the file doesn't exists don't worry as we are deleting # the attachment anyway pass res = db.select_columns('submission_methods', ['id'], ['name'], ['{} submission'.format(submission_type)]) submission_method_id = res[0][0] marking_method = 'approval' if marking_method == 'accept' else 'mark' res = db.select_columns('marking_methods', ['id'], ['name'], ['requires {}'.format(marking_method)]) mark_method_id = res[0][0] # commit task if old_task_id is not None: # update an existing task db.update_rows('tasks', [ task_name, course_id, deadline, task_description, max_file_size, submission_method_id, mark_method_id, word_limit ], [ 'name', 'course_offering', 'deadline', 'description', 'size_limit', 'submission_method', 'marking_method', 'word_limit' ], ['id'], [old_task_id]) else: # add a new task` db.insert_single('tasks', [ task_name, course_id, deadline, task_description, max_file_size, 0, submission_method_id, mark_method_id, word_limit ], [ 'name', 'course_offering', 'deadline', 'description', 'size_limit', 'visible', 'submission_method', 'marking_method', 'word_limit' ]) res = db.select_columns('tasks', ['id'], ['name', 'course_offering'], [task_name, course_id]) task_id = res[0][0] if sent_file: db.insert_single('task_attachments', [task_id, sent_file.get_name()], ['task', 'path']) # delete old entries in other tables if old_task_id is not None: db.delete_rows('submission_types', ['task'], [old_task_id]) res = db.select_columns('task_criteria', ['id'], ['task'], [old_task_id]) for r in res: db.delete_rows('marks', ['criteria'], [r[0]]) db.delete_rows('task_criteria', ['task'], [old_task_id]) # commit accepted file type db.insert_single('submission_types', [file_type_id, task_id], ['file_type', 'task']) # commit marking criteria marking_criteria = [] if marking_method == 'approval': marking_criteria.append( ('task_criteria', [task_id, 'Approval', 100], ['task', 'name', 'max_mark'])) else: for i in range(len(criteria)): marking_criteria.append( ('task_criteria', [task_id, criteria[i], marks[i]], ['task', 'name', 'max_mark'])) db.insert_multiple(marking_criteria) db.close() return jsonify({'status': 'ok'})
def submit_file_task(): task_id = request.form.get('task', -1) db.connect() res = db.select_columns('tasks', [ 'deadline', 'marking_method', 'visible', 'course_offering', 'size_limit' ], ['id'], [task_id]) if not res: db.close() return error("Task not found") task = build_task(task_id) res = db.select_columns('enrollments', ['user'], ['user', 'course_offering'], [session['id'], task['offering']]) if not res: db.close() return error("User not enrolled in task's course") if not request.form.get('certify', 'false') == 'true': db.close() return error("You must certify this is all your own work") if datetime.now().timestamp() >= task['deadline']: db.close() return error("Submissions closed!<br>You can no longer submit") if task['mark_method']['name'] == 'requires approval': res = db.select_columns('request_statuses', ['id'], ['name'], ['pending']) elif task['mark_method']['name'] == 'requires mark': res = db.select_columns('request_statuses', ['id'], ['name'], ['pending mark']) pending_status_id = res[0][0] try: sent_file = FileUpload(req=request) except KeyError: db.close() return error("You must supply a file for submission") if sent_file.get_extention() not in task['accepted_files']: db.close() accept_files = ', '.join([f[1:] for f in task['accepted_files']]) return error(f"File must be formatted as {accept_files}") if sent_file.get_size() > task['file_limit']: sent_file.remove_file() db.close() return error( f'File exceeds the maximum size of {task["file_limit"]} MB') sent_file.commit() res = db.select_columns('submissions', ['path'], ['student', 'task'], [session['id'], task['id']]) if res: db.delete_rows('submissions', ['student', 'task'], [session['id'], task['id']]) # If the file doesn't exists don't worry as we are deleting # the submission anyway try: prev_submission = FileUpload(filename=res[0][0]) prev_submission.remove_file() except LookupError: pass db.insert_single('submissions', [ session['id'], task['id'], sent_file.get_original_name(), str(sent_file.get_name()), datetime.now().timestamp(), pending_status_id ], ['student', 'task', 'name', 'path', 'date_modified', 'status']) db.close() return jsonify({'status': 'ok'})
def student_dashboard(): db.connect() all_materials = queries.get_user_materials(session['id']) cur_materials = [] for material in all_materials: # now = datetime.now().timestamp() # if now < material[2] or now > material[3]: # if we wanted to split current, previous or # future we would do the above line attachments = [] attachments_query = db.select_columns( 'material_attachments', ['path'], ['material'], [material[0]] ) for x in attachments_query: attachments.append(FileUpload(filename=x[0])) cur_materials.append((material[1], attachments, material[4])) assessor = -1 supervisor = -1 markers = queries.get_user_ass_sup(session['id']) if len(markers) > 0: assessor = markers[0][0] supervisor = markers[0][1] tasks = [] my_tasks = queries.get_user_tasks(session['id']) for task in my_tasks: status = get_sub_status(session['id'], task[0]) if 'approval' in task[3]: tasks.append(( task[2], task[1], status, '-', '-', task[0], task[4] )) else: criteria = db.select_columns( 'task_criteria', ['id', 'max_mark'], ['task'], [task[0]] ) total_max_mark = 0 supervisor_mark = 0 assessor_mark = 0 for c in criteria: total_max_mark += c[1] if assessor is not None and assessor != -1: mark = db.select_columns( 'marks', ['mark'], ['criteria', 'student', 'marker'], [c[0], session['id'], assessor] ) if len(mark) != 0: assessor_mark += mark[0][0] else: assessor_mark = -1 if supervisor is not None and supervisor != -1: mark = db.select_columns( 'marks', ['mark'], ['criteria', 'student', 'marker'], [c[0], session['id'], supervisor] ) if len(mark) != 0: supervisor_mark += mark[0][0] else: supervisor_mark = -1 if supervisor_mark <= 0: supervisor_mark = '-' if assessor_mark <= 0: assessor_mark = '-' tasks.append(( task[2], task[1], status, supervisor_mark, assessor_mark, task[0], task[4] )) # get info about selected topic and assigned supervisor/assessor try: sup = db.select_columns('users', ['name', 'email'], ['id'], [supervisor])[0] has_sup = True except IndexError: sup = 'Not assigned' # should not happen anyway has_sup = False try: assess = db.select_columns('users', ['name', 'email'], ['id'], [assessor])[0] has_assess = True except IndexError: assess = 'Not assigned' has_assess = False try: topic = queries.get_student_topic(session['id'])[0][0] has_topic = True except IndexError: topic = 'You have not selected a topic yet.' has_topic = False topic_info = { 'has_topic': has_topic, 'topic': topic, 'supervisor': sup, 'assessor': assess, 'has_supervisor': has_sup, 'has_assessor': has_assess } tasks.sort(key=lambda x: (x[0], x[6])) db.close() return render_template('home_student.html', heading='My Dashboard', title='My Dashboard', materials=cur_materials, tasks=tasks, topic_info=topic_info)