def get(self, course_uuid, assignment_uuid, answer_uuid, answer_comment_uuid): """ Get an answer comment """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) answer_comment = AnswerComment.get_active_by_uuid_or_404( answer_comment_uuid) require( READ, answer_comment, title="Feedback Unavailable", message= "Sorry, your role in this course does not allow you to view this feedback." ) restrict_user = not allow(MANAGE, assignment) restrict_user = not allow(MANAGE, assignment) on_answer_comment_get.send(self, event_name=on_answer_comment_get.name, user=current_user, course_id=course.id, data={ 'assignment_id': assignment.id, 'answer_id': answer.id, 'answer_comment_id': answer_comment.id }) return marshal(answer_comment, dataformat.get_answer_comment(restrict_user))
def output_csv(data, code, headers=None): fieldnames = [ 'username', 'student_number', 'firstname', 'lastname', 'displayname', 'group_name' ] if allow(MANAGE, User) or current_app.config.get( 'EXPOSE_EMAIL_TO_INSTRUCTOR', False): fieldnames.insert(4, 'email') if allow(MANAGE, User) or current_app.config.get( 'EXPOSE_THIRD_PARTY_USERNAMES_TO_INSTRUCTOR', False): if current_app.config.get('CAS_LOGIN_ENABLED'): fieldnames.insert(1, 'cas_username') if current_app.config.get('SAML_LOGIN_ENABLED'): fieldnames.insert(1, 'saml_username') csv_buffer = BytesIO() writer = csv.DictWriter(csv_buffer, fieldnames=fieldnames, extrasaction='ignore') writer.writeheader() if 'objects' in data: writer.writerows(data['objects']) response = make_response(csv_buffer.getvalue(), code) response.headers.extend(headers or {}) response.headers[ 'Content-Disposition'] = 'attachment;filename=classlist.csv' return response
def post(self, user_uuid): user = User.get_by_uuid_or_404(user_uuid) # anyone who passes checking below should be an admin or current user if not allow(MANAGE, user) and not user.id == current_user.id and not \ (allow(EDIT, user) and current_app.config.get('EXPOSE_EMAIL_TO_INSTRUCTOR', False)): abort( 403, title="Notifications Not Updated", message= "Sorry, your system role does not allow you to update notification settings for this user." ) if not user.email: abort( 400, title="Notifications Not Updated", message= "Sorry, you cannot update notification settings since this user does not have an email address in ComPAIR." ) params = update_notification_settings_parser.parse_args() email_notification_method = params.get("email_notification_method") check_valid_email_notification_method(email_notification_method) user.email_notification_method = EmailNotificationMethod( email_notification_method) db.session.commit() on_user_notifications_update.send( self, event_name=on_user_notifications_update.name, user=current_user) return marshal_user_data(user)
def get(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require( READ, course, title="Instructors Unavailable", message= "Instructors can only be seen here by those enrolled in the course. Please double-check your enrollment in this course." ) restrict_user = not allow(MANAGE, course) instructors = User.query \ .with_entities(User, UserCourse) \ .join(UserCourse, UserCourse.user_id == User.id) \ .filter( UserCourse.course_id == course.id, or_( UserCourse.course_role == CourseRole.instructor, UserCourse.course_role == CourseRole.teaching_assistant ) ) \ .order_by(User.lastname, User.firstname) \ .all() users = [] user_course = UserCourse(course_id=course.id) for u in instructors: if allow(READ, user_course): users.append({ 'id': u.User.uuid, 'name': u.User.fullname_sortable, 'group_id': u.UserCourse.group_id, 'role': u.UserCourse.course_role.value }) else: users.append({ 'id': u.User.uuid, 'name': u.User.displayname, 'group_id': u.UserCourse.group_id, 'role': u.UserCourse.course_role.value }) on_classlist_instructor.send(self, event_name=on_classlist_instructor.name, user=current_user, course_id=course.id) return {'objects': users}
def get(self): params = user_course_list_parser.parse_args() # Note, start and end dates are optional so default sort is by start_date (course.start_date or min assignment start date), then name query = Course.query \ .filter_by(active=True) \ .order_by(Course.start_date_order.desc(), Course.name) \ # we want to list user linked courses only, so only check the association table if not allow(MANAGE, Course): query = query.join(UserCourse) \ .filter(and_( UserCourse.user_id == current_user.id, UserCourse.course_role != CourseRole.dropped )) if params['search']: search_terms = params['search'].split() for search_term in search_terms: if search_term != "": search = '%'+search_term+'%' query = query.filter(or_( Course.name.like(search), Course.year.like(search), Course.term.like(search) )) if params['includeSandbox'] != None: query = query.filter( Course.sandbox == params['includeSandbox'] ) if params['period'] != None: now = dateutil.parser.parse(datetime.datetime.utcnow().replace(tzinfo=pytz.utc).isoformat()) if params['period'] == 'upcoming': query = query.filter( Course.start_date > now ) elif params['period'] == 'active': query = query.filter(and_( or_(Course.start_date == None, Course.start_date <= now), or_(Course.end_date == None, Course.end_date >= now), )) elif params['period'] == 'past': query = query.filter( Course.end_date < now ) page = query.paginate(params['page'], params['perPage']) # TODO REMOVE COURSES WHERE COURSE IS UNAVAILABLE? on_user_course_get.send( self, event_name=on_user_course_get.name, user=current_user) return {"objects": marshal(page.items, dataformat.get_course()), "page": page.page, "pages": page.pages, "total": page.total, "per_page": page.per_page}
def get(self, course_uuid, assignment_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require( READ, assignment, title="Help Comments Unavailable", message= "Help comments can be seen only by those enrolled in the course. Please double-check your enrollment in this course." ) restrict_user = not allow(MANAGE, assignment) assignment_comments = AssignmentComment.query \ .filter_by( course_id=course.id, assignment_id=assignment.id, active=True ) \ .order_by(AssignmentComment.created.asc()).all() on_assignment_comment_list_get.send( self, event_name=on_assignment_comment_list_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id}) return { "objects": marshal(assignment_comments, dataformat.get_assignment_comment(restrict_user)) }
def get(self, course_uuid, assignment_uuid, answer_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404( answer_uuid, joinedloads=['file', 'user', 'group', 'score']) require( READ, answer, title="Answer Unavailable", message= "Sorry, your role in this course does not allow you to view this answer." ) restrict_user = not allow(MANAGE, assignment) on_answer_get.send(self, event_name=on_answer_get.name, user=current_user, course_id=course.id, data={ 'assignment_id': assignment.id, 'answer_id': answer.id }) # don't include score/rank unless the user is non-restricted include_score = not restrict_user return marshal( answer, dataformat.get_answer(restrict_user, include_score=include_score))
def get(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require(READ, course, title="Instructors Unavailable", message="Instructors can only be seen here by those enrolled in the course. Please double-check your enrollment in this course.") restrict_user = not allow(MANAGE, course) instructors = User.query \ .with_entities(User, UserCourse) \ .join(UserCourse, UserCourse.user_id == User.id) \ .filter( UserCourse.course_id == course.id, or_( UserCourse.course_role == CourseRole.instructor, UserCourse.course_role == CourseRole.teaching_assistant ) ) \ .order_by(User.lastname, User.firstname) \ .all() users = [] user_course = UserCourse(course_id=course.id) for u in instructors: if allow(READ, user_course): users.append({ 'id': u.User.uuid, 'name': u.User.fullname_sortable, 'group_id': u.UserCourse.group_id, 'role': u.UserCourse.course_role.value }) else: users.append({ 'id': u.User.uuid, 'name': u.User.displayname, 'group_id': u.UserCourse.group_id, 'role': u.UserCourse.course_role.value }) on_classlist_instructor.send( self, event_name=on_classlist_instructor.name, user=current_user, course_id=course.id ) return { 'objects': users }
def get(self): if allow(MANAGE, Course()): courses = Course.query.filter_by(active=True).all() else: courses = [] for user_course in current_user.user_courses: if user_course.course.active and allow( MANAGE, Assignment(course_id=user_course.course_id)): courses.append(user_course.course) course_list = [{'id': c.uuid, 'name': c.name} for c in courses] on_teaching_course_get.send(self, event_name=on_teaching_course_get.name, user=current_user) return {'courses': course_list}
def get(self): params = user_course_list_parser.parse_args() # Note, start and end dates are optional so default sort is by start_date (course.start_date or min assignment start date), then name query = Course.query \ .filter_by(active=True) \ .order_by(Course.start_date_order.desc(), Course.name) \ # we want to list user linked courses only, so only check the association table if not allow(MANAGE, Course): query = query.join(UserCourse) \ .filter(and_( UserCourse.user_id == current_user.id, UserCourse.course_role != CourseRole.dropped )) if params['search']: search_terms = params['search'].split() for search_term in search_terms: if search_term != "": search = '%' + search_term + '%' query = query.filter( or_(Course.name.like(search), Course.year.like(search), Course.term.like(search))) if params['includeSandbox'] != None: query = query.filter(Course.sandbox == params['includeSandbox']) if params['period'] != None: now = dateutil.parser.parse(datetime.datetime.utcnow().replace( tzinfo=pytz.utc).isoformat()) if params['period'] == 'upcoming': query = query.filter(Course.start_date > now) elif params['period'] == 'active': query = query.filter( and_( or_(Course.start_date == None, Course.start_date <= now), or_(Course.end_date == None, Course.end_date >= now), )) elif params['period'] == 'past': query = query.filter(Course.end_date < now) page = query.paginate(params['page'], params['perPage']) # TODO REMOVE COURSES WHERE COURSE IS UNAVAILABLE? on_user_course_get.send(self, event_name=on_user_course_get.name, user=current_user) return { "objects": marshal(page.items, dataformat.get_course()), "page": page.page, "pages": page.pages, "total": page.total, "per_page": page.per_page }
def marshal_user_data(user): if impersonation.is_impersonating() and current_user.id == user.id: # when retrieving the profile of the student being impersonated, # don't include full profile (i.e. no email) return marshal(user, dataformat.get_user(False)) elif allow(MANAGE, user) or current_user.id == user.id: return marshal(user, dataformat.get_full_user()) else: return marshal(user, dataformat.get_user(is_user_access_restricted(user)))
def get(self): if allow(MANAGE, Course()): courses = Course.query.filter_by(active=True).all() else: courses = [] for user_course in current_user.user_courses: if user_course.course.active and allow(MANAGE, Assignment(course_id=user_course.course_id)): courses.append(user_course.course) course_list = [{'id': c.uuid, 'name': c.name} for c in courses] on_teaching_course_get.send( self, event_name=on_teaching_course_get.name, user=current_user ) return {'courses': course_list}
def get(self, course_uuid, assignment_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(READ, assignment) now = datetime.datetime.utcnow() if assignment.answer_start and not allow(MANAGE, assignment) and not (assignment.answer_start <= now): return {"error": "The assignment is unavailable!"}, 403 restrict_user = not allow(MANAGE, assignment) on_assignment_get.send( self, event_name=on_assignment_get.name, user=current_user, course_id=course.id, data={'id': assignment.id}) return marshal(assignment, dataformat.get_assignment(restrict_user))
def get(self, course_uuid, assignment_uuid): """ Get answers submitted to the assignment submitted by current user :param course_uuid: :param assignment_uuid: :return: answers """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require( READ, Answer(user_id=current_user.id), title="Answers Unavailable", message= "Sorry, your role in this course does not allow you to view answers for this assignment." ) restrict_user = not allow(MANAGE, assignment) params = user_answer_list_parser.parse_args() query = Answer.query \ .options(joinedload('comments')) \ .options(joinedload('file')) \ .options(joinedload('user')) \ .options(joinedload('group')) \ .options(joinedload('score')) \ .filter_by( active=True, assignment_id=assignment.id, course_id=course.id, draft=params.get('draft') ) # get group and individual answers for user if applicable group = current_user.get_course_group(course.id) if group: query = query.filter( or_(Answer.user_id == current_user.id, Answer.group_id == group.id)) # get just individual answers for user else: query = query.filter(Answer.user_id == current_user.id) answers = query.all() on_user_answer_get.send(self, event_name=on_user_answer_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id}) return { "objects": marshal(answers, dataformat.get_answer(restrict_user)) }
def get(self, course_uuid, assignment_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(READ, assignment, title="Assignment Unavailable", message="Assignments can be saved only by those enrolled in the course. Please double-check your enrollment in this course.") now = datetime.datetime.utcnow() if assignment.answer_start and not allow(MANAGE, assignment) and not (assignment.answer_start <= now): abort(403, title="Assignment Unavailable", message="This assignment is not yet open to students. Please check back after the start date the instructor has set.") restrict_user = not allow(MANAGE, assignment) on_assignment_get.send( self, event_name=on_assignment_get.name, user=current_user, course_id=course.id, data={'id': assignment.id}) return marshal(assignment, dataformat.get_assignment(restrict_user))
def get(self, user_uuid): user = User.get_by_uuid_or_404(user_uuid) available = allow(EDIT, user) on_user_edit_button_get.send( self, event_name=on_user_edit_button_get.name, user=current_user, data={'user_id': user.id, 'available': available}) return {'available': available}
def get(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require(READ, course) restrict_user = not allow(MANAGE, course) students = User.query \ .with_entities(User, UserCourse.group_name) \ .join(UserCourse, UserCourse.user_id == User.id) \ .filter( UserCourse.course_id == course.id, UserCourse.course_role == CourseRole.student ) \ .all() users = [] user_course = UserCourse(course_id=course.id) for u in students: if allow(READ, user_course): users.append({ 'id': u.User.uuid, 'name': u.User.fullname if u.User.fullname else u.User.displayname, 'group_name': u.group_name }) else: name = u.User.displayname if u.User.id == current_user.id: name += ' (You)' users.append({ 'id': u.User.uuid, 'name': name, 'group_name': u.group_name }) on_classlist_student.send( self, event_name=on_classlist_student.name, user=current_user, course_id=course.id ) return { 'objects': users }
def get(self, course_uuid, assignment_uuid): """ Get answers submitted to the assignment submitted by current user :param course_uuid: :param assignment_uuid: :return: answers """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(READ, Answer(user_id=current_user.id), title="Answers Unavailable", message="Sorry, your role in this course does not allow you to view answers for this assignment.") restrict_user = not allow(MANAGE, assignment) params = user_answer_list_parser.parse_args() query = Answer.query \ .options(joinedload('comments')) \ .options(joinedload('file')) \ .options(joinedload('user')) \ .options(joinedload('group')) \ .options(joinedload('score')) \ .filter_by( active=True, assignment_id=assignment.id, course_id=course.id, draft=params.get('draft') ) # get group and individual answers for user if applicable group = current_user.get_course_group(course.id) if group: query = query.filter(or_( Answer.user_id == current_user.id, Answer.group_id == group.id )) # get just individual answers for user else: query = query.filter(Answer.user_id == current_user.id) answers = query.all() on_user_answer_get.send( self, event_name=on_user_answer_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id}) return {"objects": marshal(answers, dataformat.get_answer(restrict_user))}
def get(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require(READ, UserCourse(course_id=course.id)) restrict_user = not allow(READ, USER_IDENTITY) # expire current_user from the session. When loading classlist from database, if the # user is already in the session, e.g. instructor for the course, the User.user_courses # is not loaded from the query below, but from session. In this case, if user has more # than one course, User.user_courses will return multiple row. Thus violating the # course_role constrain. db.session.expire(current_user) users = User.query \ .join(UserCourse, UserCourse.user_id == User.id) \ .add_columns(UserCourse.course_role, UserCourse.group_name) \ .filter(and_( UserCourse.course_id == course.id, UserCourse.course_role != CourseRole.dropped )) \ .order_by(User.firstname) \ .all() if not restrict_user: user_ids = [_user.id for (_user, _course_role, _group_name) in users] third_party_auths = ThirdPartyUser.query \ .filter(and_( ThirdPartyUser.user_id.in_(user_ids), ThirdPartyUser.third_party_type == ThirdPartyType.cas )) \ .all() class_list = [] for (_user, _course_role, _group_name) in users: _user.course_role = _course_role _user.group_name = _group_name if not restrict_user: third_party_auth = next( (third_party_auth for third_party_auth in third_party_auths if third_party_auth.user_id == _user.id), None ) _user.cas_username = third_party_auth.unique_identifier if third_party_auth else None class_list.append(_user) on_classlist_get.send( self, event_name=on_classlist_get.name, user=current_user, course_id=course.id) return {'objects': marshal(class_list, dataformat.get_users_in_course(restrict_user=restrict_user))}
def post(self, course_uuid, assignment_uuid, answer_uuid): """ Mark an answer as inappropriate or incomplete to instructors :param course_uuid: :param assignment_uuid: :param answer_uuid: :return: marked answer """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(READ, answer, title="Answer Not Flagged", message="Sorry, your role in this course does not allow you to flag answers.") restrict_user = not allow(MANAGE, answer) # anyone can flag an answer, but only the original flagger or someone who can manage # the answer can unflag it if answer.flagged and answer.flagger_user_id != current_user.id and \ not allow(MANAGE, answer): abort(400, title="Answer Not Updated", message="Sorry, your role in this course does not allow you to unflag answers.") params = flag_parser.parse_args() answer.flagged = params['flagged'] answer.flagger_user_id = current_user.id db.session.add(answer) db.session.commit() on_answer_flag.send( self, event_name=on_answer_flag.name, user=current_user, course_id=course.id, assignment_id=assignment.id, answer=answer, data={'answer_id': answer.id, 'flag': answer.flagged}) return marshal(answer, dataformat.get_answer(restrict_user))
def get(self, user_uuid): user = User.get_by_uuid_or_404(user_uuid) available = allow(EDIT, user) on_user_edit_button_get.send(self, event_name=on_user_edit_button_get.name, user=current_user, data={ 'user_id': user.id, 'available': available }) return {'available': available}
def output_csv(data, code, headers=None): fieldnames = ['username', 'student_number', 'firstname', 'lastname', 'displayname', 'group_name'] if allow(MANAGE, User) or current_app.config.get('EXPOSE_EMAIL_TO_INSTRUCTOR', False): fieldnames.insert(4, 'email') if allow(MANAGE, User) or current_app.config.get('EXPOSE_THIRD_PARTY_USERNAMES_TO_INSTRUCTOR', False): if current_app.config.get('CAS_LOGIN_ENABLED'): fieldnames.insert(1, 'cas_username') if current_app.config.get('SAML_LOGIN_ENABLED'): fieldnames.insert(1, 'saml_username') csv_buffer = BytesIO() writer = csv.DictWriter(csv_buffer, fieldnames=fieldnames, extrasaction='ignore') writer.writeheader() if 'objects' in data: writer.writerows(data['objects']) response = make_response(csv_buffer.getvalue(), code) response.headers.extend(headers or {}) response.headers['Content-Disposition'] = 'attachment;filename=classlist.csv' return response
def post(self, course_uuid, assignment_uuid, answer_uuid): """ Mark an answer as inappropriate or incomplete to instructors :param course_uuid: :param assignment_uuid: :param answer_uuid: :return: marked answer """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(READ, answer) restrict_user = not allow(MANAGE, answer) # anyone can flag an answer, but only the original flagger or someone who can manage # the answer can unflag it if answer.flagged and answer.flagger_user_id != current_user.id and \ not allow(MANAGE, answer): return {"error": "You do not have permission to unflag this answer."}, 400 params = flag_parser.parse_args() answer.flagged = params['flagged'] answer.flagger_user_id = current_user.id db.session.add(answer) db.session.commit() on_answer_flag.send( self, event_name=on_answer_flag.name, user=current_user, course_id=course.id, assignment_id=assignment.id, answer=answer, data={'answer_id': answer.id, 'flag': answer.flagged}) return marshal(answer, dataformat.get_answer(restrict_user))
def post(self, user_uuid): user = User.get_by_uuid_or_404(user_uuid) # anyone who passes checking below should be an admin or current user if not allow(MANAGE, user) and not user.id == current_user.id and not \ (allow(EDIT, user) and current_app.config.get('EXPOSE_EMAIL_TO_INSTRUCTOR', False)): abort(403, title="Notifications Not Updated", message="Sorry, your system role does not allow you to update notification settings for this user.") if not user.email: abort(400, title="Notifications Not Updated", message="Sorry, you cannot update notification settings since this user does not have an email address in ComPAIR.") params = update_notification_settings_parser.parse_args() email_notification_method = params.get("email_notification_method") check_valid_email_notification_method(email_notification_method) user.email_notification_method = EmailNotificationMethod(email_notification_method) db.session.commit() on_user_notifications_update.send( self, event_name=on_user_notifications_update.name, user=current_user) return marshal_user_data(user)
def get(self, course_uuid, assignment_uuid, answer_uuid, answer_comment_uuid): """ Get an answer comment """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) answer_comment = AnswerComment.get_active_by_uuid_or_404(answer_comment_uuid) require(READ, answer_comment, title="Feedback Unavailable", message="Sorry, your role in this course does not allow you to view this feedback.") restrict_user = not allow(MANAGE, assignment) restrict_user = not allow(MANAGE, assignment) on_answer_comment_get.send( self, event_name=on_answer_comment_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id, 'answer_id': answer.id, 'answer_comment_id': answer_comment.id}) return marshal(answer_comment, dataformat.get_answer_comment(restrict_user))
def get(self): restrict_user = not allow(READ, User) params = user_list_parser.parse_args() query = User.query if params['search']: search = '%{}%'.format(params['search']) query = query.filter(or_(User.firstname.like(search), User.lastname.like(search))) page = query.paginate(params['page'], params['perPage']) on_user_list_get.send( self, event_name=on_user_list_get.name, user=current_user) return {"objects": marshal(page.items, dataformat.get_user(restrict_user)), "page": page.page, "pages": page.pages, "total": page.total, "per_page": page.per_page}
def get(self, course_uuid, assignment_uuid, answer_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404( answer_uuid, joinedloads=['file', 'user', 'scores'] ) require(READ, answer) restrict_user = not allow(MANAGE, assignment) on_answer_get.send( self, event_name=on_answer_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id, 'answer_id': answer.id}) return marshal(answer, dataformat.get_answer(restrict_user))
def get(self, course_uuid, assignment_uuid): """ Get answers submitted to the assignment submitted by current user :param course_uuid: :param assignment_uuid: :return: answers """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(READ, Answer(user_id=current_user.id)) restrict_user = not allow(MANAGE, assignment) params = user_answer_list_parser.parse_args() query = Answer.query \ .options(joinedload('comments')) \ .options(joinedload('file')) \ .options(joinedload('user')) \ .options(joinedload('scores')) \ .filter_by( active=True, assignment_id=assignment.id, course_id=course.id, user_id=current_user.id, draft=params.get('draft') ) if params.get('unsaved'): query = query.filter(Answer.modified == Answer.created) answers = query.all() on_user_answer_get.send( self, event_name=on_user_answer_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id}) return {"objects": marshal( answers, dataformat.get_answer(restrict_user))}
def get(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require( READ, course, title="Group Unavailable", message= "Groups can be seen those enrolled in the course. Please double-check your enrollment in this course." ) user_course = UserCourse.query \ .filter(and_( UserCourse.course_id == course.id, UserCourse.user_id == current_user.id, UserCourse.course_role != CourseRole.dropped )) \ .one_or_none() if not user_course: # return none for admins who aren't enrolled in the course if allow(MANAGE, course): return None abort( 400, title="Group Unavailable", message= "You are not currently enrolled in the course. Please double-check your enrollment in this course." ) group = user_course.group on_group_user_get.send(current_app._get_current_object(), event_name=on_group_user_get.name, user=current_user, course_id=course.id) if group: return marshal(group, dataformat.get_group()) else: return None
def get(self, course_uuid, assignment_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(READ, assignment) restrict_user = not allow(MANAGE, assignment) assignment_comments = AssignmentComment.query \ .filter_by( course_id=course.id, assignment_id=assignment.id, active=True ) \ .order_by(AssignmentComment.created.asc()).all() on_assignment_comment_list_get.send( self, event_name=on_assignment_comment_list_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id}) return {"objects": marshal(assignment_comments, dataformat.get_assignment_comment(restrict_user))}
def get(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require(READ, course, title="Assignments Unavailable", message="Assignments can be seen only by those enrolled in the course. Please double-check your enrollment in this course.") assignment = Assignment(course_id=course.id) restrict_user = not allow(MANAGE, assignment) # Get all assignments for this course, order by answer_start date, created date base_query = Assignment.query \ .options(joinedload("assignment_criteria").joinedload("criterion")) \ .options(undefer_group('counts')) \ .filter( Assignment.course_id == course.id, Assignment.active == True ) \ .order_by(desc(Assignment.answer_start), desc(Assignment.created)) if restrict_user: now = datetime.datetime.utcnow() assignments = base_query \ .filter(or_( Assignment.answer_start.is_(None), now >= Assignment.answer_start ))\ .all() else: assignments = base_query.all() on_assignment_list_get.send( self, event_name=on_assignment_list_get.name, user=current_user, course_id=course.id) return { "objects": marshal(assignments, dataformat.get_assignment(restrict_user)) }
def get(self): params = user_course_list_parser.parse_args() # Note, start and end dates are optional so default sort is by start_date (course.start_date or min assignment start date), then name query = Course.query \ .filter_by(active=True) \ .order_by(Course.start_date_order.desc(), Course.name) \ # we want to list user linked courses only, so only check the association table if not allow(MANAGE, Course): query = query.join(UserCourse) \ .filter(and_( UserCourse.user_id == current_user.id, UserCourse.course_role != CourseRole.dropped )) if params['search']: search_terms = params['search'].split() for search_term in search_terms: if search_term != "": search = '%{}%'.format(search_term) query = query.filter(or_( Course.name.like(search), Course.year.like(search), Course.term.like(search) )) page = query.paginate(params['page'], params['perPage']) # TODO REMOVE COURSES WHERE COURSE IS UNAVAILABLE? on_user_course_get.send( self, event_name=on_user_course_get.name, user=current_user) return {"objects": marshal(page.items, dataformat.get_course()), "page": page.page, "pages": page.pages, "total": page.total, "per_page": page.per_page}
def get(self): restrict_user = not allow(READ, User) params = user_list_parser.parse_args() query = User.query if params['search']: # match each word of search for word in params['search'].strip().split(' '): if word != '': search = '%' + word + '%' query = query.filter( or_(User.firstname.like(search), User.lastname.like(search), User.displayname.like(search))) if params['orderBy']: if params['reverse']: query = query.order_by(desc(params['orderBy'])) else: query = query.order_by(asc(params['orderBy'])) query = query.order_by(User.lastname.asc(), User.firstname.asc()) page = query.paginate(params['page'], params['perPage']) on_user_list_get.send(self, event_name=on_user_list_get.name, user=current_user) return { "objects": marshal(page.items, dataformat.get_user(restrict_user)), "page": page.page, "pages": page.pages, "total": page.total, "per_page": page.per_page }
def get(self, course_uuid, assignment_uuid, answer_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404( answer_uuid, joinedloads=['file', 'user', 'group', 'score'] ) require(READ, answer, title="Answer Unavailable", message="Sorry, your role in this course does not allow you to view this answer.") restrict_user = not allow(MANAGE, assignment) on_answer_get.send( self, event_name=on_answer_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id, 'answer_id': answer.id}) # don't include score/rank unless the user is non-restricted include_score = not restrict_user return marshal(answer, dataformat.get_answer(restrict_user, include_score=include_score))
def post(self, course_uuid, assignment_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) if not assignment.answer_grace and not allow(MANAGE, assignment): abort( 403, title="Answer Not Submitted", message= "Sorry, the answer deadline has passed. No answers can be submitted after the deadline unless the instructor submits the answer for you." ) require( CREATE, Answer(course_id=course.id), title="Answer Not Submitted", message= "Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course." ) restrict_user = not allow(MANAGE, assignment) answer = Answer(assignment_id=assignment.id) params = new_answer_parser.parse_args() answer.content = params.get("content") answer.draft = params.get("draft") file_uuid = params.get('file_id') attachment = None if file_uuid: attachment = File.get_by_uuid_or_404(file_uuid) answer.file_id = attachment.id else: answer.file_id = None # non-drafts must have content if not answer.draft and not answer.content and not file_uuid: abort( 400, title="Answer Not Submitted", message= "Please provide content in the text editor or upload a file and try submitting again." ) user_uuid = params.get("user_id") group_uuid = params.get("group_id") # we allow instructor and TA to submit multiple answers for other users in the class if user_uuid and not allow(MANAGE, Answer(course_id=course.id)): abort( 400, title="Answer Not Submitted", message= "Only instructors and teaching assistants can submit an answer on behalf of another." ) if group_uuid and not assignment.enable_group_answers: abort(400, title="Answer Not Submitted", message="Group answers are not allowed for this assignment.") if group_uuid and not allow(MANAGE, Answer(course_id=course.id)): abort( 400, title="Answer Not Submitted", message= "Only instructors and teaching assistants can submit an answer on behalf of a group." ) if group_uuid and user_uuid: abort( 400, title="Answer Not Submitted", message= "You cannot submit an answer for a user and a group at the same time." ) user = User.get_by_uuid_or_404( user_uuid) if user_uuid else current_user group = Group.get_active_by_uuid_or_404( group_uuid) if group_uuid else None if restrict_user and assignment.enable_group_answers and not group: group = current_user.get_course_group(course.id) if group == None: abort( 400, title="Answer Not Submitted", message= "You are currently not in any group for this course. Please contact your instructor to be added to a group." ) check_for_existing_answers = False if group and assignment.enable_group_answers: if group.course_id != course.id: abort( 400, title="Answer Not Submitted", message= "Group answers can be submitted to courses they belong in." ) answer.user_id = None answer.group_id = group.id answer.comparable = True check_for_existing_answers = True else: answer.user_id = user.id answer.group_id = None course_role = User.get_user_course_role(answer.user_id, course.id) # only system admin can add answers for themselves to a class without being enrolled in it # required for managing comparison examples as system admin if (not course_role or course_role == CourseRole.dropped ) and current_user.system_role != SystemRole.sys_admin: abort( 400, title="Answer Not Submitted", message= "Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course." ) if course_role == CourseRole.student and assignment.enable_group_answers: abort( 400, title="Answer Not Submitted", message= "Students can only submit group answers for this assignment." ) # we allow instructor and TA to submit multiple answers for their own, # but not for student. Each student can only have one answer. if course_role and course_role == CourseRole.student: check_for_existing_answers = True answer.comparable = True else: # instructor / TA / Sys Admin can mark the answer as non-comparable, unless the answer is for a student answer.comparable = params.get("comparable") if check_for_existing_answers: # check for answers with user_id or group_id prev_answers = Answer.query \ .filter_by( assignment_id=assignment.id, user_id=answer.user_id, group_id=answer.group_id, active=True ) \ .all() # check if there is a previous answer submitted for the student non_draft_answers = [ prev_answer for prev_answer in prev_answers if not prev_answer.draft ] if len(non_draft_answers) > 0: abort( 400, title="Answer Not Submitted", message= "An answer has already been submitted for this assignment by you or on your behalf." ) # check if there is a previous draft answer submitted for the student (soft-delete if present) draft_answers = [ prev_answer for prev_answer in prev_answers if prev_answer.draft ] for draft_answer in draft_answers: draft_answer.active = False # set submission date if answer is being submitted for the first time if not answer.draft and not answer.submission_date: answer.submission_date = datetime.datetime.utcnow() answer.update_attempt(params.get('attempt_uuid'), params.get('attempt_started', None), params.get('attempt_ended', None)) db.session.add(answer) db.session.commit() on_answer_create.send(self, event_name=on_answer_create.name, user=current_user, course_id=course.id, answer=answer, data=marshal( answer, dataformat.get_answer(restrict_user))) if attachment: on_attach_file.send(self, event_name=on_attach_file.name, user=current_user, course_id=course.id, file=attachment, data={ 'answer_id': answer.id, 'file_id': attachment.id }) # update course & assignment grade for user if answer is fully submitted if not answer.draft: if answer.user: assignment.calculate_grade(answer.user) course.calculate_grade(answer.user) elif answer.group: assignment.calculate_group_grade(answer.group) course.calculate_group_grade(answer.group) return marshal(answer, dataformat.get_answer(restrict_user))
def get(self): params = user_course_status_list_parser.parse_args() course_uuids = params['ids'].split(',') if params['ids'] == '' or len(course_uuids) == 0: abort(400, title="Course Status Unavailable", message="Please select a course from the list of courses to see that course's status.") query = Course.query \ .filter(and_( Course.uuid.in_(course_uuids), Course.active == True, )) \ .add_columns(UserCourse.course_role, UserCourse.group_id) \ if not allow(MANAGE, Course): query = query.join(UserCourse, and_( UserCourse.user_id == current_user.id, UserCourse.course_id == Course.id, UserCourse.course_role != CourseRole.dropped )) else: query = query.outerjoin(UserCourse, and_( UserCourse.user_id == current_user.id, UserCourse.course_id == Course.id )) results = query.all() if len(course_uuids) != len(results): abort(400, title="Course Status Unavailable", message="Sorry, you are not enrolled in one or more of the selected users' courses yet. Course status is not available until your are enrolled in the course.") statuses = {} for course, course_role, group_id in results: incomplete_assignment_ids = set() if not allow(MANAGE, Course) and course_role == CourseRole.student: answer_period_assignments = [assignment for assignment in course.assignments if assignment.active and assignment.answer_period] compare_period_assignments = [assignment for assignment in course.assignments if assignment.active and assignment.compare_period] if len(answer_period_assignments) > 0: answer_period_assignment_ids = [assignment.id for assignment in answer_period_assignments] answers = Answer.query \ .filter(and_( or_( and_(Answer.group_id == group_id, Answer.group_id != None), Answer.user_id == current_user.id, ), Answer.assignment_id.in_(answer_period_assignment_ids), Answer.active == True, Answer.practice == False, Answer.draft == False )) for assignment in answer_period_assignments: answer = next( (answer for answer in answers if answer.assignment_id == assignment.id), None ) if answer is None: incomplete_assignment_ids.add(assignment.id) if len(compare_period_assignments) > 0: compare_period_assignment_ids = [assignment.id for assignment in compare_period_assignments] comparisons = Comparison.query \ .filter(and_( Comparison.user_id == current_user.id, Comparison.assignment_id.in_(compare_period_assignment_ids), Comparison.completed == True )) self_evaluations = AnswerComment.query \ .join("answer") \ .with_entities( Answer.assignment_id, func.count(Answer.assignment_id).label('self_evaluation_count') ) \ .filter(and_( or_( and_(Answer.group_id == group_id, Answer.group_id != None), Answer.user_id == current_user.id, ), AnswerComment.active == True, AnswerComment.comment_type == AnswerCommentType.self_evaluation, AnswerComment.draft == False, Answer.active == True, Answer.practice == False, Answer.draft == False, Answer.assignment_id.in_(compare_period_assignment_ids) )) \ .group_by(Answer.assignment_id) \ .all() for assignment in compare_period_assignments: assignment_comparisons = [comparison for comparison in comparisons if comparison.assignment_id == assignment.id] if len(assignment_comparisons) < assignment.total_comparisons_required: incomplete_assignment_ids.add(assignment.id) if assignment.enable_self_evaluation: self_evaluation_count = next( (result.self_evaluation_count for result in self_evaluations if result.assignment_id == assignment.id), 0 ) if self_evaluation_count == 0: incomplete_assignment_ids.add(assignment.id) statuses[course.uuid] = { 'incomplete_assignments': len(incomplete_assignment_ids) } on_user_course_status_get.send( self, event_name=on_user_course_status_get.name, user=current_user, data=statuses) return {"statuses": statuses}
def get(self, course_uuid, assignment_uuid): """ Get (or create if needed) a comparison set for assignment. """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require( READ, assignment, title="Comparisons Unavailable", message= "Assignments and their comparisons can only be seen here by those enrolled in the course. Please double-check your enrollment in this course." ) require( CREATE, Comparison, title="Comparisons Unavailable", message= "Comparisons can only be seen here by those enrolled in the course. Please double-check your enrollment in this course." ) restrict_user = not allow(MANAGE, assignment) comparison_count = assignment.completed_comparison_count_for_user( current_user.id) if not assignment.compare_grace: abort( 403, title="Comparisons Unavailable", message= "Sorry, the comparison deadline has passed. No comparisons can be done after the deadline." ) elif not restrict_user and not assignment.educators_can_compare: abort( 403, title="Comparisons Unavailable", message= "Only students can currently compare answers for this assignment. To change these settings to include instructors and teaching assistants, edit the assignment." ) elif restrict_user and comparison_count >= assignment.total_comparisons_required: abort( 400, title="Comparisons Completed", message= "More comparisons aren't available, since you've finished your comparisons for this assignment. Good job!" ) # check if user has a comparison they have not completed yet new_pair = False comparison = Comparison.query \ .options(joinedload('comparison_criteria')) \ .filter_by( assignment_id=assignment.id, user_id=current_user.id, completed=False ) \ .first() if comparison: on_comparison_get.send( self, event_name=on_comparison_get.name, user=current_user, course_id=course.id, data=marshal(comparison, dataformat.get_comparison(restrict_user))) else: # if there isn't an incomplete comparison, assign a new one try: comparison = Comparison.create_new_comparison( assignment.id, current_user.id, skip_comparison_examples=allow(MANAGE, assignment)) new_pair = True on_comparison_create.send( self, event_name=on_comparison_create.name, user=current_user, course_id=course.id, data=marshal(comparison, dataformat.get_comparison(restrict_user))) except InsufficientObjectsForPairException: abort( 400, title="Comparisons Unavailable", message= "Not enough answers are available for you to do comparisons right now. Please check back later for more answers." ) except UserComparedAllObjectsException: abort( 400, title="Comparisons Unavailable", message= "You have compared all the currently available answer pairs. Please check back later for more answers." ) except UnknownPairGeneratorException: abort( 500, title="Comparisons Unavailable", message= "Generating scored pairs failed, this really shouldn't happen." ) # get evaluation comments for answers by current user answer_comments = AnswerComment.query \ .join("answer") \ .filter(and_( # both draft and completed comments are allowed AnswerComment.active == True, AnswerComment.comment_type == AnswerCommentType.evaluation, Answer.id.in_([comparison.answer1_id, comparison.answer2_id]), AnswerComment.user_id == current_user.id )) \ .order_by(AnswerComment.draft, AnswerComment.created) \ .all() comparison.answer1_feedback = [ c for c in answer_comments if c.answer_id == comparison.answer1_id ] comparison.answer2_feedback = [ c for c in answer_comments if c.answer_id == comparison.answer2_id ] return { 'comparison': marshal( comparison, dataformat.get_comparison(restrict_user, include_answer_author=False, include_score=False, with_feedback=True)), 'new_pair': new_pair, 'current': comparison_count + 1 }
def get(self, course_uuid, assignment_uuid): """ Return a list of answers for a assignment based on search criteria. The list of the answers are paginated. If there is any answers from instructor or TA, their answers will be on top of the list. :param course_uuid: course uuid :param assignment_uuid: assignment uuid :return: list of answers """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(READ, assignment, title="Answers Unavailable", message="Answers are visible only to those enrolled in the course. Please double-check your enrollment in this course.") restrict_user = not allow(MANAGE, assignment) params = answer_list_parser.parse_args() if restrict_user and not assignment.after_comparing: # only the answer from student himself/herself should be returned params['author'] = current_user.uuid # this query could be further optimized by reduction the selected columns query = Answer.query \ .options(joinedload('file')) \ .options(joinedload('user')) \ .options(joinedload('score')) \ .options(undefer_group('counts')) \ .join(UserCourse, and_( Answer.user_id == UserCourse.user_id, UserCourse.course_id == course.id )) \ .add_columns( UserCourse.course_role.__eq__(CourseRole.instructor).label("instructor_role"), UserCourse.course_role.__eq__(CourseRole.teaching_assistant).label("ta_role") ) \ .filter(and_( Answer.assignment_id == assignment.id, Answer.active == True, Answer.practice == False, Answer.draft == False, UserCourse.course_role != CourseRole.dropped )) \ .order_by(desc('instructor_role'), desc('ta_role')) if params['author']: user = User.get_by_uuid_or_404(params['author']) query = query.filter(Answer.user_id == user.id) elif params['group']: query = query.filter(UserCourse.group_name == params['group']) if params['ids']: query = query.filter(Answer.uuid.in_(params['ids'].split(','))) if params['top']: query = query.filter(Answer.top_answer == True) if params['orderBy'] == 'score': query = query.join(AnswerScore) \ .order_by(AnswerScore.score.desc(), Answer.created.desc()) # limit answers up to rank if rank_display_limit is set and current_user is restricted (student) if assignment.rank_display_limit and restrict_user: score_for_rank = AnswerScore.get_score_for_rank(assignment.id, assignment.rank_display_limit) # display answers with score >= score_for_rank if score_for_rank != None: # will get all answer with a score greater than or equal to the score for a given rank # the '- 0.00001' fixes floating point precision problems query = query.filter(AnswerScore.score >= score_for_rank - 0.00001) else: query = query.order_by(Answer.created.desc()) page = query.paginate(params['page'], params['perPage'], error_out=False) # remove label entities from results page.items = [answer for (answer, instructor_role, ta_role) in page.items] on_answer_list_get.send( self, event_name=on_answer_list_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id}) return {"objects": marshal(page.items, dataformat.get_answer(restrict_user)), "page": page.page, "pages": page.pages, "total": page.total, "per_page": page.per_page}
def get(self, **kwargs): """ **Example request**: .. sourcecode:: http GET /api/answer/123/comments HTTP/1.1 Host: example.com Accept: application/json .. sourcecode:: http GET /api/answer_comments?ids=1,2,3&self_evaluation=only HTTP/1.1 Host: example.com Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Vary: Accept Content-Type: application/json [{ 'id': 1 'content': 'comment text', 'created': '', 'user_id': 1, 'user_displayname': 'John', 'user_fullname': 'John Smith', 'fullname_sortable': 'Smith, John', 'user_avatar': '12k3jjh24k32jhjksaf', 'comment_type': 'self_evaluation', 'course_id': 1, }] :query string ids: a comma separated comment uuids to query :query string answer_ids: a comma separated answer uuids for answer filter :query string assignment_id: filter the answer comments with a assignment uuid :query string user_ids: a comma separated user uuids that own the comments :query string self_evaluation: indicate whether the result should include self-evaluation comments or self-evaluation only. Possible values: true, false or only. Default true. :query string evaluation: indicate whether the result should include evaluation comments or evaluation only. Possible values: true, false or only. Default true. :query string draft: indicate whether the result should include drafts for current user or not. Possible values: true, false or only. Default false. :reqheader Accept: the response content type depends on :mailheader:`Accept` header :resheader Content-Type: this depends on :mailheader:`Accept` header of request :statuscode 200: no error :statuscode 404: answers don't exist """ params = answer_comment_list_parser.parse_args() answer_uuids = [] if 'answer_uuid' in kwargs: answer_uuids.append(kwargs['answer_uuid']) elif 'answer_ids' in params and params['answer_ids']: answer_uuids.extend(params['answer_ids'].split(',')) if not answer_uuids and not params['ids'] and not params[ 'assignment_id'] and not params['user_ids']: abort( 404, title="Replies Unavailable", message= "There was a problem getting the replies for this answer. Please try again." ) conditions = [] answers = Answer.query \ .filter( Answer.active == True, Answer.draft == False, Answer.uuid.in_(answer_uuids) ) \ .all() if answer_uuids else [] if answer_uuids and not answers: # non-existing answer ids. abort( 404, title="Replies Unavailable", message= "There was a problem getting the replies for this answer. Please try again." ) # build query condition for each answer for answer in answers: clauses = [AnswerComment.answer_id == answer.id] # student can only see the comments for themselves or public ones. # since the owner of the answer can access all comments. We only filter # on non-owners if current_user.get_course_role(answer.course_id) == CourseRole.student \ and answer.user_id != current_user.id: # public comments or comments owned by current user clauses.append( or_(AnswerComment.comment_type == AnswerCommentType.public, AnswerComment.user_id == current_user.id)) conditions.append(and_(*clauses)) query = AnswerComment.query. \ filter(AnswerComment.active==True) .\ filter(or_(*conditions)) if params['ids']: query = query.filter( AnswerComment.uuid.in_(params['ids'].split(','))) if params['self_evaluation'] == 'false': # do not include self-evaluation query = query.filter( AnswerComment.comment_type != AnswerCommentType.self_evaluation ) elif params['self_evaluation'] == 'only': # only self_evaluation query = query.filter(AnswerComment.comment_type == AnswerCommentType.self_evaluation) if params['evaluation'] == 'false': # do not include evalulation comments query = query.filter( AnswerComment.comment_type != AnswerCommentType.evaluation) elif params['evaluation'] == 'only': # only evaluation query = query.filter( AnswerComment.comment_type == AnswerCommentType.evaluation) if params['draft'] == 'true': # with draft (current_user) query = query.filter( or_( AnswerComment.draft == False, and_(AnswerComment.draft == True, AnswerComment.user_id == current_user.id))) elif params['draft'] == 'only': # only draft (current_user) query = query.filter( and_(AnswerComment.draft == True, AnswerComment.user_id == current_user.id)) else: # do not include draft. Default query = query.filter(AnswerComment.draft == False) if params['assignment_id']: query = query.join(AnswerComment.answer). \ filter_by(assignment_uuid=params['assignment_id']) if params['user_ids']: user_ids = params['user_ids'].split(',') query = query \ .join(User, AnswerComment.user_id == User.id) \ .filter(User.uuid.in_(user_ids)) answer_comments = query.order_by(AnswerComment.created.desc()).all() # checking the permission for answer_comment in answer_comments: require( READ, answer_comment.answer, title="Replies Unavailable", message= "Sorry, your role in this course does not allow you to view replies for this answer." ) on_answer_comment_list_get.send( self, event_name=on_answer_comment_list_get.name, user=current_user, data={ 'answer_ids': ','.join([str(answer.id) for answer in answers]) }) return marshal( answer_comments, dataformat.get_answer_comment(not allow(READ, USER_IDENTITY)))
def get(self, course_uuid, assignment_uuid): """ Return a list of answers for a assignment based on search criteria. The list of the answers are paginated. If there is any answers from instructor or TA, their answers will be on top of the list (unless they are comparable). :param course_uuid: course uuid :param assignment_uuid: assignment uuid :return: list of answers """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require( READ, assignment, title="Answers Unavailable", message= "Answers are visible only to those enrolled in the course. Please double-check your enrollment in this course." ) restrict_user = not allow(MANAGE, assignment) params = answer_list_parser.parse_args() # if assingment has no rank display limit set, restricted users can't force to retreive by rank if params[ 'orderBy'] == 'score' and restrict_user and not assignment.rank_display_limit: abort( 400, title="Answers Unavailable", message= "Sorry, you cannot cannot see answers by rank for this assignment." ) if restrict_user and not assignment.after_comparing: # only the answer from student himself/herself should be returned params['author'] = current_user.uuid # this query could be further optimized by reduction the selected columns query = Answer.query \ .options(joinedload('file')) \ .options(joinedload('user')) \ .options(joinedload('group')) \ .options(joinedload('score')) \ .options(undefer_group('counts')) \ .outerjoin(UserCourse, and_( Answer.user_id == UserCourse.user_id, UserCourse.course_id == course.id )) \ .add_columns( and_( UserCourse != None, UserCourse.course_role.__eq__(CourseRole.instructor), not Answer.comparable ).label("instructor_role"), and_( UserCourse != None, UserCourse.course_role.__eq__(CourseRole.teaching_assistant), not Answer.comparable ).label("ta_role") ) \ .filter(and_( Answer.assignment_id == assignment.id, Answer.active == True, Answer.practice == False, Answer.draft == False, or_( and_(UserCourse.course_role != CourseRole.dropped, Answer.user_id != None), Answer.group_id != None ) )) \ .order_by(desc('instructor_role'), desc('ta_role')) if params['author']: user = User.get_by_uuid_or_404(params['author']) group = user.get_course_group(course.id) if group: query = query.filter( or_(Answer.user_id == user.id, Answer.group_id == group.id)) else: query = query.filter(Answer.user_id == user.id) elif params['group']: group = Group.get_active_by_uuid_or_404(params['group']) query = query.filter( or_(UserCourse.group_id == group.id, Answer.group_id == group.id)) if params['ids']: query = query.filter(Answer.uuid.in_(params['ids'].split(','))) if params['top']: query = query.filter(Answer.top_answer == True) if params['orderBy'] == 'score': # use outer join to include comparable answers that are not yet compared (for non-restricted users) query = query.outerjoin(AnswerScore) \ .filter(Answer.comparable == True) \ .order_by(AnswerScore.score.desc(), Answer.submission_date.desc(), Answer.created.desc()) if restrict_user: # when orderd by rank, students won't see answers that are not compared (i.e. no score/rank) query = query.filter(AnswerScore.score.isnot(None)) # limit answers up to rank if rank_display_limit is set and current_user is restricted (student) if assignment.rank_display_limit and restrict_user: score_for_rank = AnswerScore.get_score_for_rank( assignment.id, assignment.rank_display_limit) # display answers with score >= score_for_rank if score_for_rank != None: # will get all answers with a score greater than or equal to the score for a given rank # the '- 0.00001' fixes floating point precision problems query = query.filter( AnswerScore.score >= score_for_rank - 0.00001) else: # when ordered by date, non-comparable answers should be on top of the list query = query.order_by(Answer.comparable, Answer.submission_date.desc(), Answer.created.desc()) page = query.paginate(params['page'], params['perPage'], error_out=False) # remove label entities from results page.items = [ answer for (answer, instructor_role, ta_role) in page.items ] on_answer_list_get.send(self, event_name=on_answer_list_get.name, user=current_user, course_id=course.id, data={'assignment_id': assignment.id}) # only include score/rank info if: # - requesters are non-restricted users (i.e. instructors / TAs); or, # - retrieving answers ordered by score/rank include_score = (not restrict_user) or \ (params['orderBy'] == 'score' and assignment.rank_display_limit) return { "objects": marshal( page.items, dataformat.get_answer(restrict_user, include_score=include_score)), "page": page.page, "pages": page.pages, "total": page.total, "per_page": page.per_page }
def post(self, course_uuid, assignment_uuid, answer_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) if not assignment.answer_grace and not allow(MANAGE, assignment): abort(403, title="Answer Not Submitted", message="Sorry, the answer deadline has passed. No answers can be submitted after the deadline unless the instructor submits the answer for you.") answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(EDIT, answer, title="Answer Not Saved", message="Sorry, your role in this course does not allow you to save this answer.") restrict_user = not allow(MANAGE, assignment) if current_app.config.get('DEMO_INSTALLATION', False): from data.fixtures import DemoDataFixture if assignment.id in DemoDataFixture.DEFAULT_ASSIGNMENT_IDS and answer.user_id in DemoDataFixture.DEFAULT_STUDENT_IDS: abort(400, title="Answer Not Saved", message="Sorry, you cannot edit the default student demo answers.") params = existing_answer_parser.parse_args() # make sure the answer id in the url and the id matches if params['id'] != answer_uuid: abort(400, title="Answer Not Saved", message="The answer's ID does not match the URL, which is required in order to save the answer.") # modify answer according to new values, preserve original values if values not passed answer.content = params.get("content") user_uuid = params.get("user_id") # we allow instructor and TA to submit multiple answers for other users in the class if user_uuid and user_uuid != answer.user_uuid: if not allow(MANAGE, answer) or not answer.draft: abort(400, title="Answer Not Saved", message="Only instructors and teaching assistants can update an answer on behalf of another.") user = User.get_by_uuid_or_404(user_uuid) answer.user_id = user.id user_course = UserCourse.query \ .filter_by( course_id=course.id, user_id=answer.user_id ) \ .one_or_none() if user_course.course_role.value not in [CourseRole.instructor.value, CourseRole.teaching_assistant.value]: # check if there is a previous answer submitted for the student prev_answer = Answer.query \ .filter(Answer.id != answer.id) \ .filter_by( assignment_id=assignment.id, user_id=answer.user_id, active=True ) \ .first() if prev_answer: abort(400, title="Answer Not Saved", message="An answer has already been submitted for this assignment by you or on your behalf.") # can only change draft status while a draft if answer.draft: answer.draft = params.get("draft") uploaded = params.get('uploadFile') file_uuid = params.get('file_id') if file_uuid: attachment = File.get_by_uuid_or_404(file_uuid) answer.file_id = attachment.id else: answer.file_id = None # non-drafts must have content if not answer.draft and not answer.content and not file_uuid: abort(400, title="Answer Not Submitted", message="Please provide content in the text editor or upload a file and try submitting again.") db.session.add(answer) db.session.commit() on_answer_modified.send( self, event_name=on_answer_modified.name, user=current_user, course_id=course.id, answer=answer, assignment=assignment, data=get_model_changes(answer)) # update course & assignment grade for user if answer is fully submitted if not answer.draft: assignment.calculate_grade(answer.user) course.calculate_grade(answer.user) return marshal(answer, dataformat.get_answer(restrict_user))
def post(self, course_uuid, assignment_uuid, answer_uuid): """ Create comment for an answer """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) require(CREATE, AnswerComment(course_id=course.id), title="Feedback Not Saved", message="Sorry, your role in this course does not allow you to save feedback for this answer.") restrict_user = not allow(MANAGE, assignment) restrict_user = not allow(MANAGE, assignment) answer_comment = AnswerComment(answer_id=answer.id) params = new_answer_comment_parser.parse_args() answer_comment.draft = params.get('draft') answer_comment.content = params.get("content") # require content not empty if not a draft if not answer_comment.content and not answer_comment.draft: abort(400, title="Feedback Not Saved", message="Please provide content in the text editor and try saving again.") if params.get('user_id') and current_user.system_role == SystemRole.sys_admin: user = User.get_by_uuid_or_404(params.get('user_id')) answer_comment.user_id = user.id else: answer_comment.user_id = current_user.id comment_types = [ AnswerCommentType.public.value, AnswerCommentType.private.value, AnswerCommentType.evaluation.value, AnswerCommentType.self_evaluation.value ] comment_type = params.get("comment_type") if comment_type not in comment_types: abort(400, title="Feedback Not Saved", message="This feedback type is not recognized. Please contact support for assistance.") answer_comment.comment_type = AnswerCommentType(comment_type) if answer_comment.comment_type == AnswerCommentType.self_evaluation and not assignment.self_eval_grace and not allow(MANAGE, assignment): abort(403, title="Self-Evaluation Not Saved", message="Sorry, the self-evaluation deadline has passed and therefore cannot be submitted.") answer_comment.update_attempt( params.get('attempt_uuid'), params.get('attempt_started', None), params.get('attempt_ended', None) ) db.session.add(answer_comment) db.session.commit() # update course & assignment grade for user if self-evaluation is completed if not answer_comment.draft and answer_comment.comment_type == AnswerCommentType.self_evaluation: assignment.calculate_grade(answer_comment.user) course.calculate_grade(answer_comment.user) on_answer_comment_create.send( self, event_name=on_answer_comment_create.name, user=current_user, course_id=course.id, answer_comment=answer_comment, evaluation_number=params.get("evaluation_number"), data=marshal(answer_comment, dataformat.get_answer_comment(restrict_user))) return marshal(answer_comment, dataformat.get_answer_comment(restrict_user))
def get(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require( READ, UserCourse(course_id=course.id), title="Class List Unavailable", message= "Sorry, your role in this course does not allow you to view the class list." ) restrict_user = not allow(READ, USER_IDENTITY) # expire current_user from the session. When loading classlist from database, if the # user is already in the session, e.g. instructor for the course, the User.user_courses # is not loaded from the query below, but from session. In this case, if user has more # than one course, User.user_courses will return multiple row. Thus violating the # course_role constrain. db.session.expire(current_user) users = User.query \ .with_entities(User, UserCourse) \ .options(joinedload(UserCourse.group)) \ .join(UserCourse, and_( UserCourse.user_id == User.id, UserCourse.course_id == course.id )) \ .filter( UserCourse.course_role != CourseRole.dropped ) \ .order_by(User.lastname, User.firstname) \ .all() if not restrict_user: user_ids = [_user.id for (_user, _user_course) in users] third_party_auths = ThirdPartyUser.query \ .filter(and_( ThirdPartyUser.user_id.in_(user_ids), or_( ThirdPartyUser.third_party_type == ThirdPartyType.cas, ThirdPartyUser.third_party_type == ThirdPartyType.saml, ) )) \ .all() class_list = [] for (_user, _user_course) in users: _user.course_role = _user_course.course_role _user.group = _user_course.group _user.group_uuid = _user.group.uuid if _user.group else None _user.group_name = _user.group.name if _user.group else None if not restrict_user: cas_auth = next( (auth for auth in third_party_auths if auth.user_id == _user.id and auth.third_party_type == ThirdPartyType.cas), None) _user.cas_username = cas_auth.unique_identifier if cas_auth else None saml_auth = next( (auth for auth in third_party_auths if auth.user_id == _user.id and auth.third_party_type == ThirdPartyType.saml), None) _user.saml_username = saml_auth.unique_identifier if saml_auth else None class_list.append(_user) on_classlist_get.send(self, event_name=on_classlist_get.name, user=current_user, course_id=course.id) if allow(MANAGE, User): return { 'objects': marshal(class_list, dataformat.get_full_users_in_course()) } else: return { 'objects': marshal( class_list, dataformat.get_users_in_course( restrict_user=restrict_user)) }
def get(self, course_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) require(READ, course, title="Assignment Status Unavailable", message="Assignment status can be seen only by those enrolled in the course. Please double-check your enrollment in this course.") group = current_user.get_course_group(course.id) group_id = group.id if group else None assignments = course.assignments \ .filter_by(active=True) \ .all() assignment_ids = [assignment.id for assignment in assignments] answer_counts = Answer.query \ .with_entities( Answer.assignment_id, func.count(Answer.assignment_id).label('answer_count') ) \ .filter_by( comparable=True, active=True, practice=False, draft=False ) \ .filter(or_( Answer.user_id == current_user.id, and_(Answer.group_id == group_id, Answer.group_answer == True) )) \ .filter(Answer.assignment_id.in_(assignment_ids)) \ .group_by(Answer.assignment_id) \ .all() feedback_counts = AnswerComment.query \ .join("answer") \ .with_entities( Answer.assignment_id, func.count(Answer.assignment_id).label('feedback_count') ) \ .filter(and_( AnswerComment.active == True, AnswerComment.draft == False, Answer.active == True, Answer.practice == False, Answer.draft == False, Answer.assignment_id.in_(assignment_ids) )) \ .filter(or_( Answer.user_id == current_user.id, and_(Answer.group_id == group_id, Answer.group_id != None) )) \ .group_by(Answer.assignment_id) \ .all() # get self evaluation status for assignments with self evaluations enabled self_evaluations = AnswerComment.query \ .join("answer") \ .with_entities( Answer.assignment_id, func.count(Answer.assignment_id).label('self_evaluation_count') ) \ .filter(and_( AnswerComment.user_id == current_user.id, AnswerComment.active == True, AnswerComment.comment_type == AnswerCommentType.self_evaluation, AnswerComment.draft == False, Answer.active == True, Answer.practice == False, Answer.draft == False, Answer.assignment_id.in_(assignment_ids) )) \ .group_by(Answer.assignment_id) \ .all() self_evaluation_drafts = AnswerComment.query \ .join("answer") \ .with_entities( Answer.assignment_id, func.count(Answer.assignment_id).label('self_evaluation_count') ) \ .filter(and_( AnswerComment.user_id == current_user.id, AnswerComment.active == True, AnswerComment.comment_type == AnswerCommentType.self_evaluation, AnswerComment.draft == True, Answer.active == True, Answer.practice == False, Answer.draft == False, Answer.assignment_id.in_(assignment_ids) )) \ .group_by(Answer.assignment_id) \ .all() query = Answer.query \ .options(load_only('id', 'assignment_id', 'uuid')) \ .filter_by( active=True, practice=False, draft=True ) \ .filter(or_( and_(Answer.group_id == group_id, Answer.group_id != None), Answer.user_id == current_user.id )) \ .filter(Answer.assignment_id.in_(assignment_ids)) drafts = query.all() statuses = {} for assignment in assignments: answer_count = next( (result.answer_count for result in answer_counts if result.assignment_id == assignment.id), 0 ) feedback_count = next( (result.feedback_count for result in feedback_counts if result.assignment_id == assignment.id), 0 ) assignment_drafts = [draft for draft in drafts if draft.assignment_id == assignment.id] comparison_count = assignment.completed_comparison_count_for_user(current_user.id) comparison_draft_count = assignment.draft_comparison_count_for_user(current_user.id) other_comparable_answers = assignment.comparable_answer_count - answer_count # students can only begin comparing when there there are enough answers submitted that they can do # comparisons without seeing the same answer more than once comparison_available = other_comparable_answers >= assignment.number_of_comparisons * 2 # instructors and tas can compare as long as there are new possible comparisons if allow(EDIT, assignment): comparison_available = comparison_count < other_comparable_answers * (other_comparable_answers - 1) / 2 statuses[assignment.uuid] = { 'answers': { 'answered': answer_count > 0, 'feedback': feedback_count, 'count': answer_count, 'has_draft': len(assignment_drafts) > 0, 'draft_ids': [draft.uuid for draft in assignment_drafts] }, 'comparisons': { 'available': comparison_available, 'count': comparison_count, 'left': max(0, assignment.total_comparisons_required - comparison_count), 'has_draft': comparison_draft_count > 0 } } if assignment.enable_self_evaluation: self_evaluation_count = next( (result.self_evaluation_count for result in self_evaluations if result.assignment_id == assignment.id), 0 ) self_evaluation_draft_count = next( (result.self_evaluation_count for result in self_evaluation_drafts if result.assignment_id == assignment.id), 0 ) statuses[assignment.uuid]['comparisons']['self_evaluation_completed'] = self_evaluation_count > 0 statuses[assignment.uuid]['comparisons']['self_evaluation_draft'] = self_evaluation_draft_count > 0 on_assignment_list_get_status.send( self, event_name=on_assignment_list_get_status.name, user=current_user, course_id=course.id, data=statuses) return {"statuses": statuses}
def post(self, course_uuid, assignment_uuid): """ Stores comparison set into the database. """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(READ, assignment, title="Comparison Not Saved", message="Comparisons can only be saved by those enrolled in the course. Please double-check your enrollment in this course.") require(EDIT, Comparison, title="Comparison Not Saved", message="Comparisons can only be saved by those enrolled in the course. Please double-check your enrollment in this course.") restrict_user = not allow(MANAGE, assignment) if not assignment.compare_grace: abort(403, title="Comparison Not Saved", message="Sorry, the comparison deadline has passed. No comparisons can be done after the deadline.") elif not restrict_user and not assignment.educators_can_compare: abort(403, title="Comparison Not Saved", message="Only students can save answer comparisons for this assignment. To change these settings to include instructors and teaching assistants, edit the assignment.") comparison = Comparison.query \ .options(joinedload('comparison_criteria')) \ .filter_by( assignment_id=assignment.id, user_id=current_user.id, completed=False ) \ .first() params = update_comparison_parser.parse_args() completed = True # check if there are any comparisons to update if not comparison: abort(400, title="Comparison Not Saved", message="There are no comparisons open for evaluation.") is_comparison_example = comparison.comparison_example_id != None # check if number of comparison criteria submitted matches number of comparison criteria needed if len(comparison.comparison_criteria) != len(params['comparison_criteria']): abort(400, title="Comparison Not Saved", message="Please double-check that all criteria were evaluated and try saving again.") if params.get('draft'): completed = False # check if each comparison has a criterion Id and a winner id for comparison_criterion_update in params['comparison_criteria']: # ensure criterion param is present if 'criterion_id' not in comparison_criterion_update: abort(400, title="Comparison Not Saved", message="Sorry, the assignment is missing criteria. Please reload the page and try again.") # set default values for cotnent and winner comparison_criterion_update.setdefault('content', None) winner = comparison_criterion_update.setdefault('winner', None) # if winner isn't set for any comparison criterion, then the comparison isn't complete yet if winner == None: completed = False # check that we're using criteria that were assigned to the course and that we didn't # get duplicate criteria in comparison criteria known_criterion = False for comparison_criterion in comparison.comparison_criteria: if comparison_criterion_update['criterion_id'] == comparison_criterion.criterion_uuid: known_criterion = True # check that the winner id matches one of the answer pairs if winner not in [None, WinningAnswer.answer1.value, WinningAnswer.answer2.value]: abort(400, title="Comparison Not Saved", message="Please select an answer from the two answers provided for each criterion and try saving again.") break if not known_criterion: abort(400, title="Comparison Not Saved", message="You are attempting to submit a comparison of an unknown criterion. Please remove the unknown criterion and try again.") # update comparison criterion comparison.completed = completed comparison.winner = None assignment_criteria = assignment.assignment_criteria answer1_weight = 0 answer2_weight = 0 for comparison_criterion in comparison.comparison_criteria: for comparison_criterion_update in params['comparison_criteria']: if comparison_criterion_update['criterion_id'] != comparison_criterion.criterion_uuid: continue winner = WinningAnswer(comparison_criterion_update['winner']) if comparison_criterion_update['winner'] != None else None comparison_criterion.winner = winner comparison_criterion.content = comparison_criterion_update['content'] if completed: weight = next(( assignment_criterion.weight for assignment_criterion in assignment_criteria \ if assignment_criterion.criterion_uuid == comparison_criterion.criterion_uuid ), 0) if winner == WinningAnswer.answer1: answer1_weight += weight elif winner == WinningAnswer.answer2: answer2_weight += weight if completed: if answer1_weight > answer2_weight: comparison.winner = WinningAnswer.answer1 elif answer1_weight < answer2_weight: comparison.winner = WinningAnswer.answer2 else: comparison.winner = WinningAnswer.draw else: # ensure that the comparison is 'touched' when saving a draft comparison.modified = datetime.datetime.utcnow() comparison.update_attempt( params.get('attempt_uuid'), params.get('attempt_started', None), params.get('attempt_ended', None) ) db.session.commit() # update answer scores if completed and not is_comparison_example: current_app.logger.debug("Doing scoring") Comparison.update_scores_1vs1(comparison) #Comparison.calculate_scores(assignment.id) # update course & assignment grade for user if comparison is completed if completed: assignment.calculate_grade(current_user) course.calculate_grade(current_user) on_comparison_update.send( self, event_name=on_comparison_update.name, user=current_user, course_id=course.id, assignment=assignment, comparison=comparison, is_comparison_example=is_comparison_example, data=marshal(comparison, dataformat.get_comparison(restrict_user))) return {'comparison': marshal(comparison, dataformat.get_comparison(restrict_user, include_answer_author=False, include_score=False))}
def get(self, course_uuid, assignment_uuid): """ Get (or create if needed) a comparison set for assignment. """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(READ, assignment, title="Comparisons Unavailable", message="Assignments and their comparisons can only be seen here by those enrolled in the course. Please double-check your enrollment in this course.") require(CREATE, Comparison, title="Comparisons Unavailable", message="Comparisons can only be seen here by those enrolled in the course. Please double-check your enrollment in this course.") restrict_user = not allow(MANAGE, assignment) comparison_count = assignment.completed_comparison_count_for_user(current_user.id) if not assignment.compare_grace: abort(403, title="Comparisons Unavailable", message="Sorry, the comparison deadline has passed. No comparisons can be done after the deadline.") elif not restrict_user and not assignment.educators_can_compare: abort(403, title="Comparisons Unavailable", message="Only students can currently compare answers for this assignment. To change these settings to include instructors and teaching assistants, edit the assignment.") elif restrict_user and comparison_count >= assignment.total_comparisons_required: abort(400, title="Comparisons Completed", message="More comparisons aren't available, since you've finished your comparisons for this assignment. Good job!") # check if user has a comparison they have not completed yet comparison = Comparison.query \ .options(joinedload('comparison_criteria')) \ .filter_by( assignment_id=assignment.id, user_id=current_user.id, completed=False ) \ .first() if comparison: on_comparison_get.send( self, event_name=on_comparison_get.name, user=current_user, course_id=course.id, data=marshal(comparison, dataformat.get_comparison(restrict_user))) else: # if there isn't an incomplete comparison, assign a new one try: comparison = Comparison.create_new_comparison(assignment.id, current_user.id, skip_comparison_examples=allow(MANAGE, assignment)) on_comparison_create.send( self, event_name=on_comparison_create.name, user=current_user, course_id=course.id, data=marshal(comparison, dataformat.get_comparison(restrict_user))) except InsufficientObjectsForPairException: abort(400, title="Comparisons Unavailable", message="Not enough answers are available for you to do comparisons right now. Please check back later for more answers.") except UserComparedAllObjectsException: abort(400, title="Comparisons Unavailable", message="You have compared all the currently available answer pairs. Please check back later for more answers.") except UnknownPairGeneratorException: abort(500, title="Comparisons Unavailable", message="Generating scored pairs failed, this really shouldn't happen.") # get evaluation comments for answers by current user answer_comments = AnswerComment.query \ .join("answer") \ .filter(and_( # both draft and completed comments are allowed AnswerComment.active == True, AnswerComment.comment_type == AnswerCommentType.evaluation, Answer.id.in_([comparison.answer1_id, comparison.answer2_id]), AnswerComment.user_id == current_user.id )) \ .order_by(AnswerComment.draft, AnswerComment.created) \ .all() comparison.answer1_feedback = [c for c in answer_comments if c.answer_id == comparison.answer1_id] comparison.answer2_feedback = [c for c in answer_comments if c.answer_id == comparison.answer2_id] return { 'comparison': marshal(comparison, dataformat.get_comparison(restrict_user, include_answer_author=False, include_score=False, with_feedback=True)), 'current': comparison_count+1 }
def post(self, course_uuid, assignment_uuid): """ Stores comparison set into the database. """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(READ, assignment) require(CREATE, Comparison) restrict_user = not allow(MANAGE, assignment) if not assignment.compare_grace: return {'error': comparison_deadline_message}, 403 elif not restrict_user and not assignment.educators_can_compare: return {'error': educators_can_not_compare_message}, 403 comparisons = Comparison.query \ .filter_by( assignment_id=assignment.id, user_id=current_user.id, completed=False ) \ .all() params = update_comparison_parser.parse_args() completed = True # check if there are any comparisons to update if len(comparisons) == 0: return {"error": "There are no comparisons open for evaluation."}, 400 is_comparison_example = comparisons[0].comparison_example_id != None # check if number of comparisons submitted matches number of comparisons needed if len(comparisons) != len(params['comparisons']): return {"error": "Not all criteria were evaluated."}, 400 # check if each comparison has a criterion Id and a winner id for comparison_to_update in params['comparisons']: # check if saving a draft if 'draft' in comparison_to_update and comparison_to_update['draft']: completed = False # ensure criterion param is present if 'criterion_id' not in comparison_to_update: return {"error": "Missing criterion_id in evaluation."}, 400 # set default values for cotnent and winner comparison_to_update.setdefault('content', None) winner_uuid = comparison_to_update.setdefault('winner_id', None) # if winner isn't set for any comparisons, then the comparison set isn't complete yet if winner_uuid == None: completed = False # check that we're using criteria that were assigned to the course and that we didn't # get duplicate criteria in comparisons known_criterion = False for comparison in comparisons: if comparison_to_update['criterion_id'] == comparison.criterion_uuid: known_criterion = True # check that the winner id matches one of the answer pairs if winner_uuid not in [comparison.answer1_uuid, comparison.answer2_uuid, None]: return {"error": "Selected answer does not match the available answers in comparison."}, 400 break if not known_criterion: return {"error": "Unknown criterion submitted!"}, 400 # update comparisons for comparison in comparisons: comparison.completed = completed for comparison_to_update in params['comparisons']: if comparison_to_update['criterion_id'] != comparison.criterion_uuid: continue if comparison_to_update['winner_id'] == comparison.answer1_uuid: comparison.winner_id = comparison.answer1_id elif comparison_to_update['winner_id'] == comparison.answer2_uuid: comparison.winner_id = comparison.answer2_id else: comparison.winner_id = None comparison.content = comparison_to_update['content'] db.session.commit() # update answer scores if completed and not is_comparison_example: current_app.logger.debug("Doing scoring") Comparison.update_scores_1vs1(comparisons) #Comparison.calculate_scores(assignment.id) # update course & assignment grade for user if comparison is completed if completed: assignment.calculate_grade(current_user) course.calculate_grade(current_user) on_comparison_update.send( self, event_name=on_comparison_update.name, user=current_user, course_id=course.id, assignment=assignment, comparisons=comparisons, is_comparison_example=is_comparison_example, data=marshal(comparisons, dataformat.get_comparison(restrict_user))) return {'objects': marshal(comparisons, dataformat.get_comparison(restrict_user))}
def get(self, course_uuid, assignment_uuid): """ Get (or create if needed) a comparison set for assignment. """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require(READ, assignment) require(CREATE, Comparison) restrict_user = not allow(MANAGE, assignment) if not assignment.compare_grace: return {'error': comparison_deadline_message}, 403 elif not restrict_user and not assignment.educators_can_compare: return {'error': educators_can_not_compare_message}, 403 # check if user has comparisons they have not completed yet comparisons = Comparison.query \ .filter_by( assignment_id=assignment.id, user_id=current_user.id, completed=False ) \ .all() new_pair = False if len(comparisons) > 0: on_comparison_get.send( self, event_name=on_comparison_get.name, user=current_user, course_id=course.id, data=marshal(comparisons, dataformat.get_comparison(restrict_user))) else: # if there aren't incomplete comparisons, assign a new one try: comparisons = Comparison.create_new_comparison_set(assignment.id, current_user.id, skip_comparison_examples=allow(MANAGE, assignment)) new_pair = True on_comparison_create.send( self, event_name=on_comparison_create.name, user=current_user, course_id=course.id, data=marshal(comparisons, dataformat.get_comparison(restrict_user))) except InsufficientObjectsForPairException: return {"error": "Not enough answers are available for a comparison."}, 400 except UserComparedAllObjectsException: return {"error": "You have compared all the currently available answers."}, 400 except UnknownPairGeneratorException: return {"error": "Generating scored pairs failed, this really shouldn't happen."}, 500 comparison_count = assignment.completed_comparison_count_for_user(current_user.id) return { 'objects': marshal(comparisons, dataformat.get_comparison(restrict_user)), 'new_pair': new_pair, 'current': comparison_count+1 }
def post(self, course_uuid, assignment_uuid): """ Stores comparison set into the database. """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) require( READ, assignment, title="Comparison Not Saved", message= "Comparisons can only be saved by those enrolled in the course. Please double-check your enrollment in this course." ) require( EDIT, Comparison, title="Comparison Not Saved", message= "Comparisons can only be saved by those enrolled in the course. Please double-check your enrollment in this course." ) restrict_user = not allow(MANAGE, assignment) if not assignment.compare_grace: abort( 403, title="Comparison Not Saved", message= "Sorry, the comparison deadline has passed. No comparisons can be done after the deadline." ) elif not restrict_user and not assignment.educators_can_compare: abort( 403, title="Comparison Not Saved", message= "Only students can save answer comparisons for this assignment. To change these settings to include instructors and teaching assistants, edit the assignment." ) comparison = Comparison.query \ .options(joinedload('comparison_criteria')) \ .filter_by( assignment_id=assignment.id, user_id=current_user.id, completed=False ) \ .first() params = update_comparison_parser.parse_args() completed = True # check if there are any comparisons to update if not comparison: abort(400, title="Comparison Not Saved", message="There are no comparisons open for evaluation.") is_comparison_example = comparison.comparison_example_id != None # check if number of comparison criteria submitted matches number of comparison criteria needed if len(comparison.comparison_criteria) != len( params['comparison_criteria']): abort( 400, title="Comparison Not Saved", message= "Please double-check that all criteria were evaluated and try saving again." ) if params.get('draft'): completed = False # check if each comparison has a criterion Id and a winner id for comparison_criterion_update in params['comparison_criteria']: # ensure criterion param is present if 'criterion_id' not in comparison_criterion_update: abort( 400, title="Comparison Not Saved", message= "Sorry, the assignment is missing criteria. Please reload the page and try again." ) # set default values for cotnent and winner comparison_criterion_update.setdefault('content', None) winner = comparison_criterion_update.setdefault('winner', None) # if winner isn't set for any comparison criterion, then the comparison isn't complete yet if winner == None: completed = False # check that we're using criteria that were assigned to the course and that we didn't # get duplicate criteria in comparison criteria known_criterion = False for comparison_criterion in comparison.comparison_criteria: if comparison_criterion_update[ 'criterion_id'] == comparison_criterion.criterion_uuid: known_criterion = True # check that the winner id matches one of the answer pairs if winner not in [ None, WinningAnswer.answer1.value, WinningAnswer.answer2.value ]: abort( 400, title="Comparison Not Saved", message= "Please select an answer from the two answers provided for each criterion and try saving again." ) break if not known_criterion: abort( 400, title="Comparison Not Saved", message= "You are attempting to submit a comparison of an unknown criterion. Please remove the unknown criterion and try again." ) # update comparison criterion comparison.completed = completed comparison.winner = None assignment_criteria = assignment.assignment_criteria answer1_weight = 0 answer2_weight = 0 for comparison_criterion in comparison.comparison_criteria: for comparison_criterion_update in params['comparison_criteria']: if comparison_criterion_update[ 'criterion_id'] != comparison_criterion.criterion_uuid: continue winner = WinningAnswer( comparison_criterion_update['winner'] ) if comparison_criterion_update['winner'] != None else None comparison_criterion.winner = winner comparison_criterion.content = comparison_criterion_update[ 'content'] if completed: weight = next(( assignment_criterion.weight for assignment_criterion in assignment_criteria \ if assignment_criterion.criterion_uuid == comparison_criterion.criterion_uuid ), 0) if winner == WinningAnswer.answer1: answer1_weight += weight elif winner == WinningAnswer.answer2: answer2_weight += weight if completed: if answer1_weight > answer2_weight: comparison.winner = WinningAnswer.answer1 elif answer1_weight < answer2_weight: comparison.winner = WinningAnswer.answer2 else: comparison.winner = WinningAnswer.draw else: # ensure that the comparison is 'touched' when saving a draft comparison.modified = datetime.datetime.utcnow() db.session.commit() # update answer scores if completed and not is_comparison_example: current_app.logger.debug("Doing scoring") Comparison.update_scores_1vs1(comparison) #Comparison.calculate_scores(assignment.id) # update course & assignment grade for user if comparison is completed if completed: assignment.calculate_grade(current_user) course.calculate_grade(current_user) on_comparison_update.send( self, event_name=on_comparison_update.name, user=current_user, course_id=course.id, assignment=assignment, comparison=comparison, is_comparison_example=is_comparison_example, data=marshal(comparison, dataformat.get_comparison(restrict_user))) return { 'comparison': marshal( comparison, dataformat.get_comparison(restrict_user, include_answer_author=False, include_score=False)) }
def post(self, course_uuid, assignment_uuid, answer_uuid, answer_comment_uuid): """ Update an answer comment """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) answer = Answer.get_active_by_uuid_or_404(answer_uuid) answer_comment = AnswerComment.get_active_by_uuid_or_404(answer_comment_uuid) require(EDIT, answer_comment, title="Feedback Not Saved", message="Sorry, your role in this course does not allow you to save feedback for this answer.") restrict_user = not allow(MANAGE, assignment) restrict_user = not allow(MANAGE, assignment) was_draft = answer_comment.draft params = existing_answer_comment_parser.parse_args() # make sure the answer comment id in the url and the id matches if params['id'] != answer_comment_uuid: abort(400, title="Feedback Not Saved", message="The feedback's ID does not match the URL, which is required in order to save the feedback.") # modify answer comment according to new values, preserve original values if values not passed answer_comment.content = params.get("content") comment_types = [ AnswerCommentType.public.value, AnswerCommentType.private.value, AnswerCommentType.evaluation.value, AnswerCommentType.self_evaluation.value ] eval_comment_types = [ AnswerCommentType.evaluation.value, AnswerCommentType.self_evaluation.value ] comment_type = params.get("comment_type", AnswerCommentType.private.value) if comment_type not in comment_types: abort(400, title="Feedback Not Saved", message="This feedback type is not recognized. Please contact support for assistance.") # do not allow changing a self-eval into a comment or vise-versa if (answer_comment.comment_type.value in eval_comment_types or comment_type in eval_comment_types) and answer_comment.comment_type.value != comment_type: abort(400, title="Feedback Not Saved", message="Feedback type cannot be changed. Please contact support for assistance.") answer_comment.comment_type = AnswerCommentType(comment_type) if answer_comment.comment_type == AnswerCommentType.self_evaluation and not assignment.self_eval_grace and not allow(MANAGE, assignment): abort(403, title="Self-Evaluation Not Saved", message="Sorry, the self-evaluation deadline has passed and therefore cannot be submitted.") # only update draft param if currently a draft if answer_comment.draft: answer_comment.draft = params.get('draft', answer_comment.draft) # require content not empty if not a draft if not answer_comment.content and not answer_comment.draft: abort(400, title="Feedback Not Saved", message="Please provide content in the text editor and try saving again.") answer_comment.update_attempt( params.get('attempt_uuid'), params.get('attempt_started', None), params.get('attempt_ended', None) ) model_changes = get_model_changes(answer_comment) db.session.add(answer_comment) db.session.commit() on_answer_comment_modified.send( self, event_name=on_answer_comment_modified.name, user=current_user, course_id=course.id, answer_comment=answer_comment, evaluation_number=params.get("evaluation_number"), was_draft=was_draft, data=model_changes) # update course & assignment grade for user if self-evaluation is completed if not answer_comment.draft and answer_comment.comment_type == AnswerCommentType.self_evaluation: assignment.calculate_grade(answer_comment.user) course.calculate_grade(answer_comment.user) return marshal(answer_comment, dataformat.get_answer_comment(restrict_user))
def post(self, course_uuid, assignment_uuid, answer_uuid): course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) if not assignment.answer_grace and not allow(MANAGE, assignment): abort( 403, title="Answer Not Submitted", message= "Sorry, the answer deadline has passed. No answers can be submitted after the deadline unless the instructor submits the answer for you." ) answer = Answer.get_active_by_uuid_or_404(answer_uuid) old_file = answer.file require( EDIT, answer, title="Answer Not Saved", message= "Sorry, your role in this course does not allow you to save this answer." ) restrict_user = not allow(MANAGE, assignment) if current_app.config.get('DEMO_INSTALLATION', False): from data.fixtures import DemoDataFixture if assignment.id in DemoDataFixture.DEFAULT_ASSIGNMENT_IDS and answer.user_id in DemoDataFixture.DEFAULT_STUDENT_IDS: abort( 400, title="Answer Not Saved", message= "Sorry, you cannot edit the default student demo answers.") params = existing_answer_parser.parse_args() # make sure the answer id in the url and the id matches if params['id'] != answer_uuid: abort( 400, title="Answer Not Submitted", message= "The answer's ID does not match the URL, which is required in order to save the answer." ) # modify answer according to new values, preserve original values if values not passed answer.content = params.get("content") user_uuid = params.get("user_id") group_uuid = params.get("group_id") # we allow instructor and TA to submit multiple answers for other users in the class if user_uuid and user_uuid != answer.user_uuid and not allow( MANAGE, answer): abort( 400, title="Answer Not Submitted", message= "Only instructors and teaching assistants can submit an answer on behalf of another." ) if group_uuid and not assignment.enable_group_answers: abort(400, title="Answer Not Submitted", message="Group answers are not allowed for this assignment.") if group_uuid and group_uuid != answer.group_uuid and not allow( MANAGE, answer): abort( 400, title="Answer Not Submitted", message= "Only instructors and teaching assistants can submit an answer on behalf of a group." ) if group_uuid and user_uuid: abort( 400, title="Answer Not Submitted", message= "You cannot submit an answer for a user and a group at the same time." ) user = User.get_by_uuid_or_404(user_uuid) if user_uuid else answer.user group = Group.get_active_by_uuid_or_404( group_uuid) if group_uuid else answer.group check_for_existing_answers = False if group and assignment.enable_group_answers: if group.course_id != course.id: abort( 400, title="Answer Not Submitted", message= "Group answers can be submitted to courses they belong in." ) answer.user_id = None answer.group_id = group.id answer.comparable = True check_for_existing_answers = True else: answer.user_id = user.id answer.group_id = None course_role = User.get_user_course_role(answer.user_id, course.id) # only system admin can add answers for themselves to a class without being enrolled in it # required for managing comparison examples as system admin if (not course_role or course_role == CourseRole.dropped ) and current_user.system_role != SystemRole.sys_admin: abort( 400, title="Answer Not Submitted", message= "Answers can be submitted only by those enrolled in the course. Please double-check your enrollment in this course." ) if course_role == CourseRole.student and assignment.enable_group_answers: abort( 400, title="Answer Not Submitted", message= "Students can only submit group answers for this assignment." ) # we allow instructor and TA to submit multiple answers for their own, # but not for student. Each student can only have one answer. if course_role and course_role == CourseRole.student: check_for_existing_answers = True answer.comparable = True else: # instructor / TA / Sys Admin can mark the answer as non-comparable, unless the answer is for a student answer.comparable = params.get("comparable") if check_for_existing_answers: # check for answers with user_id or group_id prev_answers = Answer.query \ .filter_by( assignment_id=assignment.id, user_id=answer.user_id, group_id=answer.group_id, active=True ) \ .filter(Answer.id != answer.id) \ .all() # check if there is a previous answer submitted for the student non_draft_answers = [ prev_answer for prev_answer in prev_answers if not prev_answer.draft ] if len(non_draft_answers) > 0: abort( 400, title="Answer Not Submitted", message= "An answer has already been submitted for this assignment by you or on your behalf." ) # check if there is a previous draft answer submitted for the student (soft-delete if present) draft_answers = [ prev_answer for prev_answer in prev_answers if prev_answer.draft ] for draft_answer in draft_answers: draft_answer.active = False # can only change draft status while a draft if answer.draft: answer.draft = params.get("draft") file_uuid = params.get('file_id') attachment = None if file_uuid: attachment = File.get_by_uuid_or_404(file_uuid) answer.file_id = attachment.id else: answer.file_id = None # non-drafts must have content if not answer.draft and not answer.content and not file_uuid: abort( 400, title="Answer Not Submitted", message= "Please provide content in the text editor or upload a file and try submitting again." ) # set submission date if answer is being submitted for the first time if not answer.draft and not answer.submission_date: answer.submission_date = datetime.datetime.utcnow() answer.update_attempt(params.get('attempt_uuid'), params.get('attempt_started', None), params.get('attempt_ended', None)) model_changes = get_model_changes(answer) db.session.add(answer) db.session.commit() on_answer_modified.send(self, event_name=on_answer_modified.name, user=current_user, course_id=course.id, answer=answer, data=model_changes) if old_file and (not attachment or old_file.id != attachment.id): on_detach_file.send(self, event_name=on_detach_file.name, user=current_user, course_id=course.id, file=old_file, answer=answer, data={ 'answer_id': answer.id, 'file_id': old_file.id }) if attachment and (not old_file or old_file.id != attachment.id): on_attach_file.send(self, event_name=on_attach_file.name, user=current_user, course_id=course.id, file=attachment, data={ 'answer_id': answer.id, 'file_id': attachment.id }) # update course & assignment grade for user if answer is fully submitted if not answer.draft: if answer.user: assignment.calculate_grade(answer.user) course.calculate_grade(answer.user) elif answer.group: assignment.calculate_group_grade(answer.group) course.calculate_group_grade(answer.group) return marshal(answer, dataformat.get_answer(restrict_user))
def get(self, course_uuid, assignment_uuid, **kwargs): """ :query string ids: a comma separated comment uuids to query :query string answer_ids: a comma separated answer uuids for answer filter :query string assignment_id: filter the answer comments with a assignment uuid :query string user_ids: a comma separated user uuids that own the comments :query string self_evaluation: indicate whether the result should include self-evaluation comments or self-evaluation only. Possible values: true, false or only. Default true. :query string evaluation: indicate whether the result should include evaluation comments or evaluation only. Possible values: true, false or only. Default true. :query string draft: indicate whether the result should include drafts for current user or not. Possible values: true, false or only. Default false. :reqheader Accept: the response content type depends on :mailheader:`Accept` header :resheader Content-Type: this depends on :mailheader:`Accept` header of request :statuscode 200: no error :statuscode 404: answers don't exist """ course = Course.get_active_by_uuid_or_404(course_uuid) assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid) restrict_user = not allow(MANAGE, assignment) params = answer_comment_list_parser.parse_args() answer_uuids = [] if 'answer_uuid' in kwargs: answer_uuids.append(kwargs['answer_uuid']) elif 'answer_ids' in params and params['answer_ids']: answer_uuids.extend(params['answer_ids'].split(',')) if not answer_uuids and not params['ids'] and not params['assignment_id'] and not params['user_ids']: abort(404, title="Feedback Unavailable", message="There was a problem getting the feedback for this answer. Please try again.") conditions = [] answers = Answer.query \ .filter( Answer.assignment_id == assignment.id, Answer.active == True, Answer.draft == False, Answer.uuid.in_(answer_uuids) ) \ .all() if answer_uuids else [] if answer_uuids and not answers: # non-existing answer ids. abort(404, title="Feedback Unavailable", message="There was a problem getting the feedback for this answer. Please try again.") group = current_user.get_course_group(course.id) course_role = current_user.get_course_role(course.id) # build query condition for each answer for answer in answers: clauses = [AnswerComment.answer_id == answer.id] # student can only see the comments for themselves or public ones. # since the owner of the answer can access all comments. We only filter # on non-owners answer_owner = answer.user_id == current_user.id or (group and group.id == answer.group_id) if course_role == CourseRole.student and not answer_owner: # public comments or comments owned by current user clauses.append(or_( AnswerComment.comment_type == AnswerCommentType.public, AnswerComment.user_id == current_user.id )) conditions.append(and_(*clauses)) query = AnswerComment.query \ .filter( AnswerComment.assignment_id == assignment.id, AnswerComment.active==True, or_(*conditions) ) if params['ids']: query = query.filter(AnswerComment.uuid.in_(params['ids'].split(','))) if params['self_evaluation'] == 'false': # do not include self-evaluation query = query.filter(AnswerComment.comment_type != AnswerCommentType.self_evaluation) elif params['self_evaluation'] == 'only': # only self_evaluation query = query.filter(AnswerComment.comment_type == AnswerCommentType.self_evaluation) if params['evaluation'] == 'false': # do not include evalulation comments query = query.filter(AnswerComment.comment_type != AnswerCommentType.evaluation) elif params['evaluation'] == 'only': # only evaluation query = query.filter(AnswerComment.comment_type == AnswerCommentType.evaluation) if params['draft'] == 'true': # with draft (current_user) query = query.filter(or_( AnswerComment.draft == False, and_( AnswerComment.draft == True, AnswerComment.user_id == current_user.id ) )) elif params['draft'] == 'only': # only draft (current_user) query = query.filter(and_( AnswerComment.draft == True, AnswerComment.user_id == current_user.id )) else: # do not include draft. Default query = query.filter(AnswerComment.draft == False) if params['user_ids']: user_ids = params['user_ids'].split(',') query = query \ .join(User, AnswerComment.user_id == User.id) \ .filter(User.uuid.in_(user_ids)) answer_comments = query.order_by(AnswerComment.created.desc()).all() # checking the permission for answer_comment in answer_comments: require(READ, answer_comment.answer, title="Feedback Unavailable", message="Sorry, your role in this course does not allow you to view feedback for this answer.") on_answer_comment_list_get.send( self, event_name=on_answer_comment_list_get.name, user=current_user, data={'answer_ids': ','.join([str(answer.id) for answer in answers])}) return marshal(answer_comments, dataformat.get_answer_comment(restrict_user))
def post(self, user_uuid): user = User.get_by_uuid_or_404(user_uuid) if is_user_access_restricted(user): abort( 403, title="User Not Saved", message="Sorry, your role does not allow you to save this user." ) params = existing_user_parser.parse_args() # make sure the user id in the url and the id matches if params['id'] != user_uuid: abort( 400, title="User Not Saved", message= "The user's ID does not match the URL, which is required in order to save the user." ) # only update username if user uses compair login method if user.uses_compair_login: username = params.get("username") if username == None: abort( 400, title="User Not Saved", message= "A username is required. Please enter a username and try saving again." ) username_exists = User.query.filter_by(username=username).first() if username_exists and username_exists.id != user.id: abort( 409, title="User Not Saved", message= "Sorry, this username already exists and usernames must be unique in ComPAIR. Please enter another username and try saving again." ) user.username = username elif allow(MANAGE, user): #admins can optionally set username for users without a username username = params.get("username") if username: username_exists = User.query.filter_by( username=username).first() if username_exists and username_exists.id != user.id: abort( 409, title="User Not Saved", message= "Sorry, this username already exists and usernames must be unique in ComPAIR. Please enter another username and try saving again." ) user.username = username else: user.username = None if allow(MANAGE, user): system_role = params.get("system_role", user.system_role.value) check_valid_system_role(system_role) user.system_role = SystemRole(system_role) if allow(MANAGE, user) or user.id == current_user.id or current_app.config.get( 'EXPOSE_EMAIL_TO_INSTRUCTOR', False): if current_user.system_role != SystemRole.student or current_app.config.get( 'ALLOW_STUDENT_CHANGE_EMAIL'): user.email = params.get("email", user.email) email_notification_method = params.get("email_notification_method") check_valid_email_notification_method(email_notification_method) user.email_notification_method = EmailNotificationMethod( email_notification_method) elif params.get("email") or params.get("email_notification_method"): abort( 400, title="User Not Saved", message= "your role does not allow you to change email settings for this user." ) if current_user.system_role != SystemRole.student or current_app.config.get( 'ALLOW_STUDENT_CHANGE_STUDENT_NUMBER'): # only students should have student numbers if user.system_role == SystemRole.student: student_number = params.get("student_number", user.student_number) student_number_exists = User.query.filter_by( student_number=student_number).first() if student_number is not None and student_number_exists and student_number_exists.id != user.id: abort( 409, title="User Not Saved", message= "Sorry, this student number already exists and student numbers must be unique in ComPAIR. Please enter another number and try saving again." ) else: user.student_number = student_number else: user.student_number = None if current_user.system_role != SystemRole.student or current_app.config.get( 'ALLOW_STUDENT_CHANGE_NAME'): user.firstname = params.get("firstname", user.firstname) user.lastname = params.get("lastname", user.lastname) if current_user.system_role != SystemRole.student or current_app.config.get( 'ALLOW_STUDENT_CHANGE_DISPLAY_NAME'): user.displayname = params.get("displayname", user.displayname) model_changes = get_model_changes(user) try: db.session.commit() on_user_modified.send(self, event_name=on_user_modified.name, user=current_user, data={ 'id': user.id, 'changes': model_changes }) except exc.IntegrityError: db.session.rollback() abort( 409, title="User Not Saved", message= "Sorry, this ID already exists and IDs must be unique in ComPAIR. Please try addding another user." ) return marshal_user_data(user)
def get(self): params = user_course_status_list_parser.parse_args() course_uuids = params['ids'].split(',') if params['ids'] == '' or len(course_uuids) == 0: abort( 400, title="Course Status Unavailable", message= "Please select a course from the list of courses to see that course's status." ) query = Course.query \ .filter(and_( Course.uuid.in_(course_uuids), Course.active == True, )) \ .add_columns(UserCourse.course_role, UserCourse.group_id) \ if not allow(MANAGE, Course): query = query.join( UserCourse, and_(UserCourse.user_id == current_user.id, UserCourse.course_id == Course.id, UserCourse.course_role != CourseRole.dropped)) else: query = query.outerjoin( UserCourse, and_(UserCourse.user_id == current_user.id, UserCourse.course_id == Course.id)) results = query.all() if len(course_uuids) != len(results): abort( 400, title="Course Status Unavailable", message= "Sorry, you are not enrolled in one or more of the selected users' courses yet. Course status is not available until your are enrolled in the course." ) statuses = {} for course, course_role, group_id in results: incomplete_assignment_ids = set() if not allow(MANAGE, Course) and course_role == CourseRole.student: answer_period_assignments = [ assignment for assignment in course.assignments if assignment.active and assignment.answer_period ] compare_period_assignments = [ assignment for assignment in course.assignments if assignment.active and assignment.compare_period ] if len(answer_period_assignments) > 0: answer_period_assignment_ids = [ assignment.id for assignment in answer_period_assignments ] answers = Answer.query \ .filter(and_( or_( and_(Answer.group_id == group_id, Answer.group_id != None), Answer.user_id == current_user.id, ), Answer.assignment_id.in_(answer_period_assignment_ids), Answer.active == True, Answer.practice == False, Answer.draft == False )) for assignment in answer_period_assignments: answer = next( (answer for answer in answers if answer.assignment_id == assignment.id), None) if answer is None: incomplete_assignment_ids.add(assignment.id) if len(compare_period_assignments) > 0: compare_period_assignment_ids = [ assignment.id for assignment in compare_period_assignments ] comparisons = Comparison.query \ .filter(and_( Comparison.user_id == current_user.id, Comparison.assignment_id.in_(compare_period_assignment_ids), Comparison.completed == True )) self_evaluations = AnswerComment.query \ .join("answer") \ .with_entities( Answer.assignment_id, func.count(Answer.assignment_id).label('self_evaluation_count') ) \ .filter(and_( or_( and_(Answer.group_id == group_id, Answer.group_id != None), Answer.user_id == current_user.id, ), AnswerComment.active == True, AnswerComment.comment_type == AnswerCommentType.self_evaluation, AnswerComment.draft == False, Answer.active == True, Answer.practice == False, Answer.draft == False, Answer.assignment_id.in_(compare_period_assignment_ids) )) \ .group_by(Answer.assignment_id) \ .all() for assignment in compare_period_assignments: assignment_comparisons = [ comparison for comparison in comparisons if comparison.assignment_id == assignment.id ] if len(assignment_comparisons ) < assignment.total_comparisons_required: incomplete_assignment_ids.add(assignment.id) if assignment.enable_self_evaluation: self_evaluation_count = next( (result.self_evaluation_count for result in self_evaluations if result.assignment_id == assignment.id), 0) if self_evaluation_count == 0: incomplete_assignment_ids.add(assignment.id) statuses[course.uuid] = { 'incomplete_assignments': len(incomplete_assignment_ids) } on_user_course_status_get.send( self, event_name=on_user_course_status_get.name, user=current_user, data=statuses) return {"statuses": statuses}