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 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 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 partial_update(self, request, *args, **kwargs): """Update an existing assignment. Arguments: request -- request data data -- the new data for the assignment pk -- assignment ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the assignment does not exist forbidden -- User not allowed to edit this assignment unauthorized -- when the user is unauthorized to edit the assignment bad_request -- when there is invalid data in the request On success: success -- with the new assignment data """ # Get data pk, = utils.required_typed_params(kwargs, (int, 'pk')) assignment = Assignment.objects.get(pk=pk) published, = utils.optional_params(request.data, 'published') # Remove data that must not be changed by the serializer req_data = request.data req_data.pop('published', None) if not (request.user.is_superuser or request.user == assignment.author): req_data.pop('author', None) response_data = {} # Publish is possible and asked for if published: request.user.check_permission('can_publish_grades', assignment) self.publish(request, assignment) response_data['published'] = published # Update assignment data is possible and asked for if req_data: if 'lti_id' in req_data: factory.make_lti_ids(lti_id=req_data['lti_id'], for_model=Lti_ids.ASSIGNMENT, assignment=assignment) # If a entry has been submitted to one of the journals of the journal it cannot be unpublished if assignment.is_published and 'is_published' in req_data and not req_data['is_published'] and \ Entry.objects.filter(node__journal__assignment=assignment).exists(): return response.bad_request( 'You are not allowed to unpublish an assignment that already has submissions.') serializer = AssignmentSerializer(assignment, data=req_data, context={'user': request.user}, partial=True) if not serializer.is_valid(): response.bad_request() serializer.save() response_data['assignment'] = serializer.data return response.success(response_data)
def upload(self, request): """Upload a user file. Checks available space for the user and max file size. If the file is intended for an entry, checks if the user can edit the entry. At the time of creation, the UserFile is uploaded but not attached to an entry yet. This UserFile is treated as temporary untill the actual entry is created and the node and content are updated. Arguments: request -- request data file -- filelike data assignment_id -- assignment ID content_id -- content ID, should be null when creating a NEW entry. Returns On failure: unauthorized -- when the user is not logged in bad_request -- when the file, assignment was not found or the validation failed. On success: success -- name of the file. """ assignment_id, content_id = utils.required_params( request.POST, 'assignment_id', 'content_id') assignment = Assignment.objects.get(pk=assignment_id) request.user.check_can_view(assignment) if not (request.FILES and 'file' in request.FILES): return response.bad_request( 'No accompanying file found in the request.') validators.validate_user_file(request.FILES['file'], request.user) if content_id == 'null': factory.make_user_file(request.FILES['file'], request.user, assignment) else: try: content = Content.objects.get( pk=int(content_id), entry__node__journal__user=request.user) except Content.DoesNotExist: return response.bad_request( 'Content with id {:s} was not found.'.format(content_id)) request.user.check_can_edit(content.entry) factory.make_user_file(request.FILES['file'], request.user, assignment, content=content) return response.success(description='Successfully uploaded {:s}.'. format(request.FILES['file'].name))
def create(self, request): """Create course role. Arguments: request -- request data course_id -- course ID name -- role name permissions -- permissions to change (default everything is false) 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 -- newly created course """ course_id, name, permissions = utils.required_params( request.data, 'course_id', 'name', 'permissions') course = Course.objects.get(pk=course_id) # TODO: P Is this the right permission request.user.check_permission('can_edit_course_roles', course) try: role = factory.make_role_default_no_perms(name, course, **permissions) except Exception: return response.bad_request() serializer = RoleSerializer(role, many=False) return response.created({'role': 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 create(self, request): """Create a new course group. Arguments: request -- the request that was send with name -- name of the course group course_id -- course ID of the course lti_id -- (optional) lti_id to link the course to Returns: On failure: unauthorized -- when the user is not logged in forbidden -- when the user has no permission to create new groups On success, with the course group. """ name, course_id = utils.required_params(request.data, "name", "course_id") lti_id, = utils.optional_params(request.data, 'lti_id') course = Course.objects.get(pk=course_id) request.user.check_permission('can_add_course_user_group', course) if lti_id and Group.objects.filter(lti_id=lti_id, course=course).exists(): return response.bad_request('Course group with the desired name already exists.') course_group = factory.make_course_group(name, course, lti_id) serializer = GroupSerializer(course_group, many=False) return response.created({'group': 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 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 download(self, request, pk): """Get a user file by name if it exists. Arguments: request -- the request that was sent file_name -- filename to download pk -- user ID Returns On failure: unauthorized -- when the user is not logged in bad_request -- when the file was not found forbidden -- when its not a superuser nor their own data On success: success -- a zip file of all the userdata with all their files """ if int(pk) == 0: pk = request.user.id file_name, entry_id, node_id, content_id = utils.required_typed_params( request.query_params, (str, 'file_name'), (int, 'entry_id'), (int, 'node_id'), (int, 'content_id')) try: user_file = UserFile.objects.get(author=pk, file_name=file_name, entry=entry_id, node=node_id, content=content_id) if user_file.author != request.user: request.user.check_permission('can_view_all_journals', user_file.assignment) except (UserFile.DoesNotExist, ValueError): return response.bad_request(file_name + ' was not found.') return response.file(user_file)
def destroy(self, request, pk): """Delete a user. Arguments: request -- request data pk -- user ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the user does not exist On success: success -- deleted message """ if not request.user.is_superuser: return response.forbidden('You are not allowed to delete a user.') if int(pk) == 0: pk = request.user.id user = User.objects.get(pk=pk) if len(User.objects.filter(is_superuser=True)) == 1: return response.bad_request('There is only 1 superuser left and therefore cannot be deleted') user.delete() return response.deleted(description='Sucesfully deleted user.')
def process_exception(self, request, exception): # Generic exception if isinstance(exception, VLEBadRequest): return response.bad_request(str(exception)) # Django exceptions elif isinstance(exception, ObjectDoesNotExist): return response.not_found('{0} does not exist.'.format( str(exception).split()[0])) elif isinstance(exception, ValidationError): return response.validation_error(exception) # Variable exceptions elif isinstance(exception, VLEMissingRequiredKey): return response.key_error(*exception.keys) elif isinstance(exception, VLEMissingRequiredField): return response.bad_request(str(exception)) elif isinstance(exception, VLEParamWrongType): return response.value_error(str(exception)) # Permission exceptions elif isinstance(exception, VLEParticipationError): return response.forbidden(str(exception)) elif isinstance(exception, VLEPermissionError): return response.forbidden(str(exception)) elif isinstance(exception, VLEUnverifiedEmailError): return response.forbidden(str(exception)) # Programming exceptions elif isinstance(exception, VLEProgrammingError): return response.internal_server_error(str(exception)) elif isinstance(exception, SMTPAuthenticationError): return response.internal_server_error( 'Mailserver is not configured correctly, please contact a server admin.' ) # LTI exceptions elif isinstance(exception, jwt.exceptions.ExpiredSignatureError): return response.forbidden( 'The LTI instance link has expired, 15 minutes have passed. Please try again.' ) elif isinstance(exception, jwt.exceptions.InvalidSignatureError): return response.unauthorized( 'Invalid LTI parameters given. Please retry from your LTI instance or notify a server admin.' )
def admin_update(self, request, journal): req_data = request.data if 'published' in req_data: del req_data['published'] serializer = JournalSerializer(journal, data=req_data, partial=True) if not serializer.is_valid(): return response.bad_request() serializer.save() return response.success({'journal': serializer.data})
def partial_update(self, request, *args, **kwargs): """Update an existing journal. Arguments: request -- request data data -- the new data for the journal pk -- journal ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the journal does not exist forbidden -- User not allowed to edit this journal unauthorized -- when the user is unauthorized to edit the journal bad_request -- when there is invalid data in the request On success: success -- with the new journal data """ pk, = utils.required_typed_params(kwargs, (int, 'pk')) journal = Journal.objects.get(pk=pk) request.user.check_can_view(journal) published, = utils.optional_params(request.data, 'published') if published: request.user.check_permission('can_publish_grades', journal.assignment) return self.publish(request, journal) if not request.user.is_superuser: return response.forbidden( 'You are not allowed to edit this journal.') req_data = request.data if 'published' in req_data: del req_data['published'] serializer = JournalSerializer(journal, data=req_data, partial=True) if not serializer.is_valid(): response.bad_request() serializer.save() return response.success({'journal': serializer.data})
def request_email_verification(request): """Request an email with a verifcation link for the users email address.""" if request.user.verified_email: return response.bad_request( description='Email address already verified.') email_handling.send_email_verification_link(request.user) return response.success( description= 'An email was sent to {}, please follow the email for instructions.'. format(request.user.email))
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 partial_update(self, request, pk): """Updates course roles. Arguments: request -- request data roles -- each role of the course that needs to be updated 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 forbidden -- when the user is unauthorized to edit its roles bad_request -- if On success: success -- list of all the roles in the course """ course = Course.objects.get(pk=pk) request.user.check_permission('can_edit_course_roles', course) roles_response = [] roles, = utils.required_params(request.data, 'roles') for new_role in roles: try: role = Role.objects.get(name=new_role['name'], course=course) except Role.DoesNotExist: role = factory.make_role_default_no_perms( new_role['name'], course) serializer = RoleSerializer(role, data=new_role, partial=True) if not serializer.is_valid(): response.bad_request() serializer.save() roles_response.append(serializer.data) return response.success({'roles': roles_response})
def update_lti_groups(request, jwt_params): user = request.user lti_params = decode_lti_params(jwt_params) if user != User.objects.get(lti_id=lti_params['user_id']): return response.forbidden( "The user specified that should be logged in according to the request is not the logged in user." ) role = lti.roles_to_list(lti_params) course = lti.update_lti_course_if_exists(lti_params, user, role) if course: return response.success() else: return response.bad_request('Course not found')
def partial_update(self, request, *args, **kwargs): """Update an existing comment. Arguments: request -- request data text -- comment text pk -- comment ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the comment does not exist forbidden -- when the user is not allowed to comment unauthorized -- when the user is unauthorized to edit the assignment On success: success -- with the updated comment """ comment_id, = utils.required_typed_params(kwargs, (int, 'pk')) comment = Comment.objects.get(pk=comment_id) journal = comment.entry.node.journal assignment = journal.assignment request.user.check_permission('can_comment', assignment) request.user.check_can_view(journal) if not (comment.author.id == request.user.id or request.user.is_superuser): return response.forbidden('You are not allowed to edit this comment.') text, = utils.required_params(request.data, 'text') serializer = CommentSerializer( comment, data={'text': text}, partial=True) if not serializer.is_valid(): response.bad_request() serializer.save() return response.success({'comment': serializer.data})
def create(self, request): """Set a new grade for an entry. Arguments: request -- request data entry_id -- entry ID grade -- grade published -- published state Returns: On failure: unauthorized -- when the user is not logged in key_error -- missing keys not_found -- could not find the entry, author or assignment On success: success -- with the assignment data """ entry_id, grade, published = utils.required_typed_params( request.data, (int, 'entry_id'), (float, 'grade'), (bool, 'published')) entry = Entry.objects.get(pk=entry_id) journal = entry.node.journal assignment = journal.assignment request.user.check_permission('can_grade', assignment) if published: request.user.check_permission('can_publish_grades', assignment) if grade is not None and grade < 0: return response.bad_request( 'Grade must be greater than or equal to zero.') grade = factory.make_grade(entry, request.user.pk, grade, published) if published: Comment.objects.filter(entry=entry).update(published=True) # TODO: Is the lti flag ever used? Else move replace_result to celery return response.created({ 'entry': EntrySerializer(entry, context={ 'user': request.user }).data, 'lti': lti_grade.replace_result(journal) })
def create(self, request): """Add a user to a course. Arguments: request -- request data user_id -- user ID course_id -- course ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when course or user is not found forbidden -- when the logged in user is not connected to the course bad request -- when the new user is already connected to the course not found -- when the role doesnt exist On success: success -- success message """ user_id, course_id = utils.required_typed_params( request.data, (int, 'user_id'), (int, 'course_id')) role_name = 'Student' user = User.objects.get(pk=user_id) course = Course.objects.get(pk=course_id) request.user.check_permission('can_add_course_users', course) if user.is_participant(course): return response.bad_request( 'User already participates in the course.') role = Role.objects.get(name=role_name, course=course) factory.make_participation(user, course, role) assignments = course.assignment_set.all() for assignment in assignments: if not Journal.objects.filter(assignment=assignment, user=user).exists(): factory.make_journal(assignment, user) serializer = UserSerializer(user, context={'course': course}) return response.created( {'participant': serializer.data}, description='Successfully added student to course.')
def request_email_verification(request): """Request an email with a verifcation link for the user's email address.""" if request.user.verified_email: return response.success(description='Email address already verified.') email, = utils.optional_params(request.data, 'email') if not email and not request.user.email: return response.bad_request(description='Please provide an email address.') if email and request.user.email != email: request.user.email = email request.user.verified_email = False request.user.save() send_email_verification_link.delay(request.user.pk) return response.success( description='An email was sent to {}, please check your inbox for further \ instructions.'.format(request.user.email))
def verify_email(request): """Handles an email verification request. Arguments: token -- User claimed email verification token. Updates the email verification status. """ if request.user.verified_email: return response.success(description='Email address already verified.') token, = utils.required_params(request.data, 'token') token_generator = PasswordResetTokenGenerator() if not token_generator.check_token(request.user, token): return response.bad_request( description='Invalid email recovery token.') request.user.verified_email = True request.user.save() return response.success( description='Successfully verified your email address.')
def publish(self, request, journal): grading.publish_all_journal_grades(journal, request.user) if journal.sourcedid: payload = lti_grade.replace_result(journal) if payload and 'code_mayor' in payload and payload[ 'code_mayor'] == 'success': return response.success({ 'lti_info': payload, 'journal': JournalSerializer(journal).data }) else: return response.bad_request({ 'lti_info': payload, 'journal': JournalSerializer(journal).data }) else: return response.success( {'journal': JournalSerializer(journal).data})
def partial_update(self, request, *args, **kwargs): """Update an existing assignment. Arguments: request -- request data data -- the new data for the assignment pk -- assignment ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the assignment does not exist forbidden -- User not allowed to edit this assignment unauthorized -- when the user is unauthorized to edit the assignment bad_request -- when there is invalid data in the request On success: success -- with the new assignment data """ pk, = utils.required_typed_params(kwargs, (int, 'pk')) assignment = Assignment.objects.get(pk=pk) request.user.check_permission('can_edit_assignment', assignment) response_data = {} # Remove data that must not be changed by the serializer req_data = request.data if not (request.user.is_superuser or request.user == assignment.author): req_data.pop('author', None) # Check if the assignment can be unpublished is_published, = utils.optional_params(request.data, 'is_published') if not assignment.can_unpublish() and is_published is False: return response.bad_request( 'You cannot unpublish an assignment that already has submissions.' ) active_lti_course, = utils.optional_typed_params( request.data, (int, 'active_lti_course')) if active_lti_course is not None: course = Course.objects.get(pk=active_lti_course) active_lti_id = assignment.get_course_lti_id(course) if active_lti_id: assignment.active_lti_id = active_lti_id assignment.save() # Rename lti id key for serializer if 'lti_id' in req_data: course_id, = utils.required_params(request.data, 'course_id') course = Course.objects.get(pk=course_id) request.user.check_permission('can_add_assignment', course) if course in assignment.courses.all(): return response.bad_request( 'Assignment already used in course.') course.set_assignment_lti_id_set(req_data['lti_id']) course.save() assignment.courses.add(course) assignment.save() for user in User.objects.filter( participation__course=course).exclude( journal__assignment=assignment): factory.make_journal(assignment, user) req_data['active_lti_id'] = req_data.pop('lti_id') # Update the other data serializer = AssignmentSerializer(assignment, data=req_data, context={'user': request.user}, partial=True) if not serializer.is_valid(): return response.bad_request() serializer.save() response_data['assignment'] = serializer.data return response.success(response_data)
def partial_update(self, request, pk): """Update an existing journal format. Arguments: request -- request data templates -- the list of templates to bind to the format presets -- the list of presets to bind to the format unused_templates -- the list of templates that are bound to the template deck, but are not used in presets nor the entry templates. removed_presets -- presets to be removed removed_templates -- templates to be removed pk -- assignment ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the assignment does not exist forbidden -- User not allowed to edit this assignment unauthorized -- when the user is unauthorized to edit the assignment bad_request -- when there is invalid data in the request On success: success -- with the new assignment data """ assignment_id = pk assignment_details, templates, presets, unused_templates, removed_presets, removed_templates \ = utils.required_params(request.data, "assignment_details", "templates", "presets", "unused_templates", "removed_presets", "removed_templates") assignment = Assignment.objects.get(pk=assignment_id) format = assignment.format # If a entry has been submitted to one of the journals of the journal it cannot be unpublished if assignment.is_published and 'is_published' in assignment_details and not assignment_details['is_published'] \ and Entry.objects.filter(node__journal__assignment=assignment).exists(): return response.bad_request('You are not allowed to unpublish an assignment that already has submissions.') request.user.check_permission('can_edit_assignment', assignment) serializer = AssignmentSerializer(assignment, data=assignment_details, context={'user': request.user}, partial=True) if not serializer.is_valid(): return response.bad_request('Invalid data.') serializer.save() format.save() template_map = {} utils.update_presets(assignment, presets, template_map) utils.update_templates(format.available_templates, templates, template_map) utils.update_templates(format.unused_templates, unused_templates, template_map) # Swap templates from lists if they occur in the other: # If a template was previously unused, but is now used, swap it to available templates, and vice versa. utils.swap_templates(format.available_templates, unused_templates, format.unused_templates) utils.swap_templates(format.unused_templates, templates, format.available_templates) utils.delete_presets(format.presetnode_set, removed_presets) utils.delete_templates(format.available_templates, removed_templates) utils.delete_templates(format.unused_templates, removed_templates) serializer = FormatSerializer(format) assignment_details = AssignmentDetailsSerializer(assignment) return response.success({'format': serializer.data, 'assignment_details': assignment_details.data})
def add_bonus_points(self, request, *args, **kwargs): """Give students bonus points though file submission. This will scan over the included file, and give all the users the bonus points supplied. Format: [username1], [bonus_points1] [username2], [bonus_points2] Arguments: request file -- list of all the bonus points pk -- assignment ID """ assignment_id, = utils.required_typed_params(kwargs, (int, 'pk')) assignment = Assignment.objects.get(pk=assignment_id) request.user.check_permission('can_grade', assignment) if not (request.FILES and 'file' in request.FILES): return response.bad_request( 'No accompanying file found in the request.') validators.validate_user_file(request.FILES['file'], request.user) bonuses = dict() incorrect_format_lines = dict() unknown_users = dict() duplicates = dict() non_participants = dict() for line_nr, line in enumerate(request.FILES['file'], 1): try: decoded_line = line.decode() # Ignore empty lines. if not decoded_line.strip(): continue username, bonus = decoded_line[:-1].split(',')[:2] bonus = float(bonus) user = User.objects.get(username=str(username)) journal = Journal.objects.get(assignment=assignment, user=user) if journal in bonuses: duplicates[line_nr] = line.decode().split(',')[0] else: bonuses[journal] = bonus except UnicodeDecodeError: return response.bad_request( {'general': 'Not a valid csv file.'}) except ValueError: incorrect_format_lines[line_nr] = line.decode() except User.DoesNotExist: unknown_users[line_nr] = line.decode().split(',')[0] except Journal.DoesNotExist: non_participants[line_nr] = line.decode().split(',')[0] if unknown_users or incorrect_format_lines or duplicates: errors = dict() if incorrect_format_lines: errors['incorrect_format_lines'] = incorrect_format_lines if duplicates: errors['duplicates'] = duplicates if unknown_users: errors['unknown_users'] = unknown_users if non_participants: errors['non_participants'] = non_participants return response.bad_request(errors) for j, b in bonuses.items(): j.bonus_points = b j.save() lti_grade.replace_result(journal) return response.success()