def get_new_course_response(lti_params, role): """Generate course information for the teacher to setup the course with. This only works when the lti user is a teacher of that course. """ if 'Teacher' not in role: return response.success({ 'params': { 'state': LTI_STATES.LACKING_PERMISSION_TO_SETUP_COURSE.value, 'lti_cName': lti_params['custom_course_name'], 'lti_aName': lti_params['custom_assignment_title'], } }) try: return response.success({ 'params': { 'state': LTI_STATES.NEW_COURSE.value, 'lti_cName': lti_params['custom_course_name'], 'lti_abbr': lti_params.get('context_label', ''), 'lti_cID': lti_params['custom_course_id'], 'lti_course_start': lti_params['custom_course_start'], 'lti_aName': lti_params['custom_assignment_title'], 'lti_aID': lti_params['custom_assignment_id'], 'lti_aUnlock': lti_params['custom_assignment_unlock'], 'lti_aDue': lti_params['custom_assignment_due'], 'lti_aLock': lti_params['custom_assignment_lock'], 'lti_points_possible': lti_params['custom_assignment_points'], 'lti_aPublished': lti_params['custom_assignment_publish'], } }) except KeyError as err: raise VLEMissingRequiredKey(err.args[0])
def destroy(self, request, *args, **kwargs): """Delete an existing assignment from a course. Arguments: request -- request data course_id -- the course ID of course this assignment belongs to pk -- assignment ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the assignment or course does not exist unauthorized -- when the user is not logged in forbidden -- when the user cannot delete the assignment On success: success -- with a message that the course was deleted """ assignment_id, = utils.required_typed_params(kwargs, (int, 'pk')) course_id, = utils.required_typed_params(request.query_params, (int, 'course_id')) assignment = Assignment.objects.get(pk=assignment_id) course = Course.objects.get(pk=course_id) request.user.check_permission('can_delete_assignment', course) assignment.courses.remove(course) assignment.save() # If the assignment is only connected to one course, delete it completely if assignment.courses.count() == 0: assignment.delete() return response.success(description='Successfully deleted the assignment.') else: return response.success(description='Successfully removed the assignment from {}.'.format(str(course)))
def get_new_assignment_response(lti_params, course, role): """Generate assignment information for the teacher to setup the assignment with. This only works when the lti user is a teacher of that assignment. """ if 'Teacher' not in role: return response.success({ 'params': { 'state': LTI_STATES.LACKING_PERMISSION_TO_SETUP_ASSIGNMENT.value, 'lti_cName': lti_params['custom_course_name'], 'lti_aName': lti_params['custom_assignment_title'], } }) try: return response.success({ 'params': { 'state': LTI_STATES.NEW_ASSIGN.value, 'cID': course.pk, 'lti_aName': lti_params['custom_assignment_title'], 'lti_aID': lti_params['custom_assignment_id'], 'lti_aUnlock': lti_params['custom_assignment_unlock'], 'lti_aDue': lti_params['custom_assignment_due'], 'lti_aLock': lti_params['custom_assignment_lock'], 'lti_points_possible': lti_params['custom_assignment_points'], 'lti_aPublished': lti_params['custom_assignment_publish'], } }) except KeyError as err: raise VLEMissingRequiredKey(err.args[0])
def retrieve(self, request, pk=0): """Get the permissions of a user connected to a course / assignment. Arguments: request -- the request that was sent course_id -- course ID assignment_id -- assignment ID pk -- user ID (0 = logged in user) Returns: On failure: unauthorized -- when the user is not logged in not found -- when the course is not found forbidden -- when the user is not in the course On success: success -- with a list of the permissions """ if int(pk) == 0: pk = request.user.id user = User.objects.get(pk=pk) # Return course permissions if course_id is set try: course_id, = utils.required_typed_params(request.query_params, (int, 'course_id')) course = Course.objects.get(pk=course_id) request.user.check_participation(course) if user != request.user: # TODO: P Is this the right permission request.user.check_permission('can_edit_course_roles', course) return response.success({ 'role': permissions.serialize_course_permissions(request.user, course) }) # Return assignment permissions if assignment_id is set except (VLEMissingRequiredKey, VLEParamWrongType): assignment_id, = utils.required_typed_params( request.query_params, (int, 'assignment_id')) assignment = Assignment.objects.get(pk=assignment_id) request.user.check_can_view(assignment) if user != request.user: # TODO: P Add a permission for this request.user.check_permission('can_view_all_journals', course) return response.success({ 'role': permissions.serialize_assignment_permissions( request.user, assignment) })
def destroy(self, request, *args, **kwargs): """Delete an existing assignment from a course. Arguments: request -- request data course_id -- the course ID of course this assignment belongs to pk -- assignment ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the assignment or course does not exist unauthorized -- when the user is not logged in forbidden -- when the user cannot delete the assignment On success: success -- with a message that the course was deleted """ assignment_id, = utils.required_typed_params(kwargs, (int, 'pk')) course_id, = utils.required_typed_params(request.query_params, (int, 'course_id')) assignment = Assignment.objects.get(pk=assignment_id) course = Course.objects.get(pk=course_id) request.user.check_permission('can_delete_assignment', course) intersecting_assignment_lti_id = assignment.get_course_lti_id(course) if intersecting_assignment_lti_id: if assignment.active_lti_id == intersecting_assignment_lti_id and len( assignment.lti_id_set) > 1: return response.bad_request( 'This assignment cannot be removed from this course, since it is' + ' currently configured for grade passback to the LMS') course.assignment_lti_id_set.remove(intersecting_assignment_lti_id) assignment.lti_id_set.remove(intersecting_assignment_lti_id) course.save() assignment.courses.remove(course) assignment.save() if assignment.active_lti_id is not None and assignment.active_lti_id in course.assignment_lti_id_set: course.assignment_lti_id_set.remove(assignment.active_lti_id) course.save() # If the assignment is only connected to one course, delete it completely if assignment.courses.count() == 0: assignment.delete() return response.success( description='Successfully deleted the assignment.') else: return response.success( description='Successfully removed the assignment from {}.'. format(str(course)))
def unenrolled(self, request): """Get all users that are not in the given course. Arguments: request -- request data course_id -- course ID unenrolled_query -- query that needs to match with the unenrolled users Returns: On failure: unauthorized -- when the user is not logged in not found -- when the course does not exist forbidden -- when the user is not in the course forbidden -- when the user is unauthorized to view its participants On success: success -- list of all the users and their role """ course_id, unenrolled_query = utils.required_params( request.query_params, 'course_id', 'unenrolled_query') if ' ' in unenrolled_query: first_name = unenrolled_query.split(' ', 1)[0] last_name = unenrolled_query.split(' ', 1)[1] course = Course.objects.get(pk=course_id) request.user.check_permission('can_add_course_users', course) ids_in_course = course.participation_set.all().values('user__id') users = User.objects.all().exclude(id__in=ids_in_course) if len(unenrolled_query) < 5: user = users.filter(username=unenrolled_query) if user: return response.success( {'participants': UserSerializer(user, many=True).data}) else: return response.success({'participants': []}) found_users = users.filter( Q(username__contains=unenrolled_query) | Q(first_name__contains=unenrolled_query) | Q(last_name__contains=unenrolled_query)) if ' ' in unenrolled_query: found_users = found_users | users.filter( first_name__contains=first_name, last_name__contains=last_name) return response.success( {'participants': UserSerializer(found_users, many=True).data})
def linkable(self, request): """Get linkable courses. Gets all courses that the current user either participates in or is allowed to edit the course details of. A user can then link this course to Canvas. Arguments: request -- request data Returns: On failure: unauthorized -- when the user is not logged in not found -- when the course does not exist forbidden -- when the user is not in the course On success: success -- with a message that the course was deleted """ if not (request.user.is_teacher or request.user.is_superuser): return response.forbidden( "You are not allowed to get linkable courses.") unlinked_courses = Course.objects.filter( participation__user=request.user.id, participation__role__can_edit_course_details=True) serializer = serialize.CourseSerializer(unlinked_courses, many=True) data = serializer.data for i, course in enumerate(data): data[i]['lti_couples'] = len( Lti_ids.objects.filter(course=course['id'])) return response.success({'courses': data})
def copyable(self, request, pk): """Get all assignments that a user can copy a format from, except for the current assignment. Arguments: pk -- assignment ID Returns a list of tuples consisting of courses and copyable assignments.""" courses = Course.objects.filter( participation__user=request.user.id, participation__role__can_edit_assignment=True) copyable = [] for course in courses: assignments = Assignment.objects.filter(courses=course).exclude( pk=pk) if assignments: copyable.append({ 'course': CourseSerializer(course).data, 'assignments': AssignmentDetailsSerializer(assignments, context={ 'user': request.user }, many=True).data }) return response.success({'data': copyable})
def destroy(self, request, *args, **kwargs): """Delete an existing course group. Arguments: request -- request data group_name -- name of the course pk -- course ID Returns: On failure: not found -- when the course does not exists unauthorized -- when the user is not logged in forbidden -- when the user is not in the course On success: success -- with a message that the course group was deleted """ course_id, = utils.required_typed_params(kwargs, (int, 'pk')) name, = utils.required_typed_params(request.query_params, (str, 'group_name')) course = Course.objects.get(pk=course_id) request.user.check_permission('can_delete_course_user_group', course) group = Group.objects.get(name=name, course=course) group.delete() return response.success(description='Successfully deleted course group.')
def list(self, request): """Get the assignments from a course for the user. Arguments: request -- request data course_id -- course ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the course does not exist forbidden -- when the user is not part of the course On succes: success -- with the assignment data """ try: course_id, = utils.optional_typed_params(request.query_params, (int, 'course_id')) course = Course.objects.get(pk=course_id) request.user.check_participation(course) courses = [course] except VLEParamWrongType: course = None courses = request.user.participations.all() query = Assignment.objects.filter(courses__in=courses).distinct() viewable = [assignment for assignment in query if request.user.can_view(assignment)] serializer = AssignmentSerializer(viewable, many=True, context={'user': request.user, 'course': course}) data = serializer.data for i, assignment in enumerate(data): data[i]['lti_couples'] = len(Lti_ids.objects.filter(assignment=assignment['id'])) return response.success({'assignments': data})
def destroy(self, request, *args, **kwargs): """Delete an existing comment from an entry. Arguments: request -- request data pk -- comment ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the comment or author does not exist forbidden -- when the user cannot delete the assignment On success: success -- with a message that the comment was deleted """ comment_id, = utils.required_typed_params(kwargs, (int, 'pk')) comment = Comment.objects.get(pk=comment_id) journal = comment.entry.node.journal request.user.check_can_view(journal) if not (request.user.is_superuser or request.user.id == comment.author.id): return response.forbidden(description='You are not allowed to delete this comment.') comment.delete() return response.success(description='Successfully deleted comment.')
def send_feedback(request): """Send an email with feedback to the developers. Arguments: request -- the request that was sent. topic -- the topic of the feedback. type -- the type of feedback. feedback -- the actual feedback. browser -- the browser of the user who sends the feedback. files -- potential files as attachments. Returns: On failure: bad request -- when required keys are missing or file sizes too big. unauthorized -- when the user is not logged in. On success: success -- with a description. """ request.user.check_verified_email() utils.required_params(request.POST, 'topic', 'feedback', 'ftype', 'user_agent') files = request.FILES.getlist('files') validators.validate_email_files(files) email_handling.send_email_feedback(request.user, files, **request.POST) return response.success( description='Feedback was successfully received, thank you!')
def partial_update(self, request, *args, **kwargs): """Update an existing course group. Arguments: request -- request data name -- group name pk -- group ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the course does not exists forbidden -- when the user is not in the course unauthorized -- when the user is unauthorized to edit the course bad_request -- when there is invalid data in the request On success: success -- with the new course data """ name, = utils.required_params(request.data, 'name') group_id, = utils.required_typed_params(kwargs, (int, 'pk')) group = Group.objects.get(pk=group_id) course = group.course request.user.check_permission('can_edit_course_user_group', course) if not name: return response.bad_request('Group name is not allowed to be empty.') serializer = GroupSerializer(group, data={'name': name}, partial=True) if not serializer.is_valid(): return response.bad_request() serializer.save() return response.success({'group': serializer.data})
def list(self, request): """Get the groups from a course for the user. Arguments: request -- request data course_id -- course ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the course does not exists forbidden -- when the user is not part of the course On success: success -- with the group data """ course_id, = utils.required_typed_params(request.query_params, (int, 'course_id')) course = Course.objects.get(pk=course_id) check_can_view_groups(request.user, course) queryset = Group.objects.filter(course=course) serializer = GroupSerializer(queryset, many=True, context={'user': request.user, 'course': course}) return response.success({'groups': serializer.data})
def names(request, course_id, assignment_id, journal_id): """Get names of course, assignment, journal. Arguments: request -- the request that was sent course_id -- optionally the course id assignment_id -- optionally the assignment id journal_id -- optionally the journal id Returns a json string containing the names of the set fields. course_id populates 'course', assignment_id populates 'assignment', tID populates 'template' and journal_id populates 'journal' with the users' name. """ result = {} if course_id: course = Course.objects.get(pk=course_id) request.user.check_participation(course) result['course'] = course.name if assignment_id: assignment = Assignment.objects.get(pk=assignment_id) request.user.check_can_view(assignment) result['assignment'] = assignment.name if journal_id: journal = Journal.objects.get(pk=journal_id) request.user.check_can_view(journal) result[ 'journal'] = journal.user.first_name + " " + journal.user.last_name return response.success({'names': result})
def forgot_password(request): """Handles a forgot password request. Arguments: username -- User claimed username. email -- User claimed email. token -- Django stateless token, invalidated after password change or after a set time (by default three days). Generates a recovery token if a matching user can be found by either the prodived username or email. """ username, email = utils.optional_params(request.data, 'username', 'email') # We are retrieving the username based on either the username or email try: user = User.objects.get(username=username) email = 'your recovery address' except User.DoesNotExist: if email is None or email == '': return response.not_found('Invalid email address provided.') user = User.objects.get(email=email) if not user.email: return response.bad_request( description='The provided account has no known email address.') send_password_recovery_link.delay(user.pk) return response.success( description='An email was sent to {}, please check your inbox for further instructions.'.format(email))
def send_feedback(request): """Send an email with feedback to the developers. Arguments: request -- the request that was sent. topic -- the topic of the feedback. type -- the type of feedback. feedback -- the actual feedback. browser -- the browser of the user who sends the feedback. files -- potential files as attachments, currently only one file is processed. Returns: On failure: bad request -- when required keys are missing or file sizes too big. unauthorized -- when the user is not logged in. On success: success -- with a description. """ request.user.check_verified_email() topic, ftype, feedback, user_agent, url = \ utils.required_params(request.data, 'topic', 'ftype', 'feedback', 'user_agent', 'url') if request.FILES: files = request.FILES.getlist('files') validators.validate_email_files(files) if request.user.feedback_file: request.user.feedback_file.delete() request.user.feedback_file = files[0] request.user.save() send_email_feedback.delay( request.user.pk, topic, ftype, feedback, user_agent, url, file_content_type=files[0].content_type) else: send_email_feedback.delay(request.user.pk, topic, ftype, feedback, user_agent, url) return response.success(description='Thank you for contacting support, we\'ll get back to you as soon as possible!')
def retrieve(self, request, pk): """Get the user data of the requested user. Arguments: request -- request data pk -- user ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the user doesn't exists On success: success -- with the user data """ if int(pk) == 0: pk = request.user.id user = User.objects.get(pk=pk) if request.user == user or request.user.is_superuser: serializer = OwnUserSerializer(user, many=False) elif permissions.is_user_supervisor_of(request.user, user): serializer = UserSerializer(user, many=False) else: return response.forbidden('You are not allowed to view this users information.') return response.success({'user': serializer.data})
def password(self, request): """Change the password of a user. Arguments: request -- request data new_password -- new password of the user old_password -- current password of the user Returns On failure: unauthorized -- when the user is not logged in bad request -- when the password is invalid On success: success -- with a success description """ new_password, old_password = utils.required_params(request.data, 'new_password', 'old_password') if not request.user.check_password(old_password): return response.bad_request('Wrong password.') if validators.validate_password(new_password): return response.bad_request(validators.validate_password(new_password)) request.user.set_password(new_password) request.user.save() return response.success(description='Successfully changed the password.')
def partial_update(self, request, *args, **kwargs): """Update an existing course. Arguments: request -- request data data -- the new data for the course pk -- course ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the course does not exist forbidden -- when the user is not in the course unauthorized -- when the user is unauthorized to edit the course bad_request -- when there is invalid data in the request On success: success -- with the new course data """ course_id, = utils.required_typed_params(kwargs, (int, 'pk')) course = Course.objects.get(pk=course_id) request.user.check_permission('can_edit_course_details', course) data = request.data if 'lti_id' in data: factory.make_lti_ids(lti_id=data['lti_id'], for_model=Lti_ids.COURSE, course=course) serializer = self.serializer_class(course, data=data, partial=True) if not serializer.is_valid(): response.bad_request() serializer.save() return response.success({'course': serializer.data})
def partial_update(self, request, *args, **kwargs): """Update the preferences of a user. Arguments: request -- request data pk -- user ID Returns: On failure: unauthorized -- when the user is not logged in forbidden -- when the user is not superuser or pk is not the same as the logged in user not found -- when the user doesnt exist bad request -- when the data is invalid On success: success -- with the updated preferences """ pk, = utils.required_typed_params(kwargs, (int, 'pk')) if not (request.user.pk == pk or request.user.is_superuser): return response.forbidden('You are not allowed to change this users preferences.') preferences = Preferences.objects.get(user=pk) serializer = PreferencesSerializer(preferences, data=request.data, partial=True) if not serializer.is_valid(): return response.bad_request('Invalid preference data provided.') serializer.save() return response.success({'preferences': serializer.data})
def list(self, request): """Get the comments belonging to an entry. Arguments: request -- request data entry_id -- entry ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the course does not exist forbidden -- when its not their own journal, or the user is not allowed to grade that journal On succes: success -- with a list of the comments belonging to the entry """ entry_id, = utils.required_params(request.query_params, "entry_id") entry = Entry.objects.get(pk=entry_id) journal = entry.node.journal assignment = journal.assignment request.user.check_can_view(journal) if request.user.has_permission('can_grade', assignment): comments = Comment.objects.filter(entry=entry) else: comments = Comment.objects.filter(entry=entry, published=True) serializer = CommentSerializer(comments, many=True) return response.success({'comments': serializer.data})
def recover_password(request): """Handles a reset password request. Arguments: username -- User claimed username. recovery_token -- Django stateless token, invalidated after password change or after a set time (by default three days). new_password -- The new user desired password. Updates password if the recovery_token is valid. """ username, recovery_token, new_password = utils.required_params( request.data, 'username', 'recovery_token', 'new_password') user = User.objects.get(username=username) recovery_token, = utils.required_params(request.data, 'recovery_token') token_generator = PasswordResetTokenGenerator() if not token_generator.check_token(user, recovery_token): return response.bad_request('Invalid recovery token.') validators.validate_password(new_password) user.set_password(new_password) user.save() return response.success( description='Successfully changed the password, you can now log in.')
def destroy(self, request, pk): """Delete course role. Arguments: request -- request data name -- role name pk -- course ID Returns: On failure: unauthorized -- when the user is not logged in forbidden -- when the user is not in the course forbidden -- when the user is unauthorized to edit its roles On success: success -- newly created course """ name, = utils.required_typed_params(request.query_params, (str, 'name')) course = Course.objects.get(pk=pk) # Users can only delete course roles with can_edit_course_roles request.user.check_permission('can_edit_course_roles', course) if name in ['Student', 'TA', 'Teacher']: return response.bad_request( 'Default roles "Student", "TA" and "Teacher" cannot be deleted.' ) Role.objects.get(name=name, course=pk).delete() return response.success( description='Successfully deleted role from course.')
def upcoming(self, request): """Get upcoming deadlines for the requested user. Arguments: request -- request data course_id -- course ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the course does not exist On success: success -- upcoming assignments """ try: course_id, = utils.required_typed_params(request.query_params, (int, 'course_id')) course = Course.objects.get(pk=course_id) courses = [course] except (VLEMissingRequiredKey, VLEParamWrongType): course = None courses = request.user.participations.all() now = timezone.now() query = Assignment.objects.filter( Q(lock_date__gt=now) | Q(lock_date=None), courses__in=courses ).distinct() viewable = [assignment for assignment in query if request.user.can_view(assignment)] upcoming = AssignmentSerializer(viewable, context={'user': request.user, 'course': course}, many=True).data return response.success({'upcoming': upcoming})
def partial_update(self, request, *args, **kwargs): """Update instance details. Arguments: request -- request data data -- the new data for the journal Returns: On failure: unauthorized -- when the user is not logged in forbidden -- User not allowed to edit instance bad_request -- when there is invalid data in the request On success: success -- with the new instance details """ if not request.user.is_superuser: return response.forbidden( 'You are not allowed to edit instance details.') try: instance = Instance.objects.get(pk=1) except Instance.DoesNotExist: instance = factory.make_instance() req_data = request.data serializer = InstanceSerializer(instance, data=req_data, partial=True) if not serializer.is_valid(): response.bad_request() serializer.save() return response.success({'instance': serializer.data})
def list(self, request): """Get course roles. Arguments: request -- request data course_id -- course ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the course does not exist forbidden -- when the user is not in the course forbidden -- when the user is unauthorized to edit its roles On success: success -- list of all the roles in the course """ course_id, = utils.required_typed_params(request.query_params, (int, 'course_id')) course = Course.objects.get(pk=course_id) # TODO: P Is this the right permission request.user.check_permission('can_edit_course_roles', course) roles = Role.objects.filter(course=course) serializer = RoleSerializer(roles, many=True) return response.success({'roles': serializer.data})
def retrieve(self, request, pk=None): """Retrieve a comment. Arguments: request -- request data pk -- assignment ID Returns: On failure: unauthorized -- when the user is not logged in not_found -- could not find the course with the given id forbidden -- not allowed to retrieve assignments in this course On success: succes -- with the comment data """ comment = Comment.objects.get(pk=pk) journal = comment.entry.node.journal request.user.check_can_view(journal) serializer = CommentSerializer(comment) return response.success({'comment': serializer.data})
def list(self, request): """Get all users and their roles for a given course. Arguments: request -- request data course_id -- course ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the course does not exist forbidden -- when the user is not in the course forbidden -- when the user is unauthorized to view its participants On success: success -- list of all the users and their role """ course_id, = utils.required_params(request.query_params, "course_id") course = Course.objects.get(pk=course_id) request.user.check_permission('can_view_course_users', course) users = UserSerializer(course.users, context={ 'course': course }, many=True).data return response.success({'participants': users})
def list(self, request): """Get the student submitted journals of one assignment from a course. Arguments: request -- request data course_id -- course ID assignment_id -- assignment ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the assignment does not exist forbidden -- when the user has no permission to view the journals of the assignment On succes: success -- with journals and stats about the journals """ assignment_id, course_id = utils.required_typed_params( request.query_params, (int, 'assignment_id'), (int, 'course_id')) assignment = Assignment.objects.get(pk=assignment_id) course = Course.objects.get(pk=course_id) request.user.check_permission('can_view_all_journals', assignment) request.user.check_can_view(assignment) users = course.participation_set.filter( role__can_have_journal=True).values('user') queryset = assignment.journal_set.filter(user__in=users) journals = JournalSerializer(queryset, many=True).data return response.success({'journals': journals})