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 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 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 create(self, request): """Create a new course. Arguments: request -- request data name -- name of the course abbreviation -- abbreviation of the course startdate -- (optional) date when the course starts enddate -- (optional) date when the course ends 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 courses On succes: success -- with the course data """ request.user.check_permission('can_add_course') name, abbr = utils.required_params(request.data, 'name', 'abbreviation') startdate, enddate, lti_id = utils.optional_params( request.data, 'startdate', 'enddate', 'lti_id') course = factory.make_course(name, abbr, startdate, enddate, request.user, lti_id) serializer = self.serializer_class(course, many=False) return response.created({'course': 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 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 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 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 create(self, request): """Create a new comment. Arguments: request -- request data entry_id -- entry ID text -- comment text published -- publishment 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: succes -- with the assignment data """ entry_id, text, published = utils.required_params(request.data, "entry_id", "text", "published") entry = Entry.objects.get(pk=entry_id) journal = entry.node.journal assignment = journal.assignment request.user.check_permission('can_comment', assignment) request.user.check_can_view(journal) # By default a comment will be published, only users who can grade can delay publishing. published = published or not request.user.has_permission('can_grade', assignment) comment = factory.make_comment(entry, request.user, text, published) return response.created({'comment': CommentSerializer(comment).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 post(self, request): result = super(LoginView, self).post(request) if result.status_code == 200: username, = utils.required_params(request.data, 'username') User.objects.filter(username=username).update( last_login=timezone.now()) return result
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 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 set_profile_picture(self, request): """Update user profile picture. Arguments: request -- request data file -- a base64 encoded image Returns On failure: unauthorized -- when the user is not logged in bad_request -- when the file is not valid On success: success -- a zip file of all the userdata with all their files """ utils.required_params(request.data, 'file') validators.validate_profile_picture_base64(request.data['file']) request.user.profile_picture = request.data['file'] request.user.save() return response.success(description='Successfully updated profile picture')
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 destroy(self, request, pk): """Remove a user from the course. request -- request data user_id -- user ID pk -- course ID """ user_id, = utils.required_params(request.query_params, 'user_id') user = User.objects.get(pk=user_id) course = Course.objects.get(pk=pk) participation = Participation.objects.get(user=user, course=course) request.user.check_permission('can_delete_course_users', course) participation.delete() return response.success( description='Sucesfully removed user from course.')
def create(self, request): """Create a new assignment. Arguments: request -- request data name -- name of the assignment description -- description of the assignment course_id -- id of the course the assignment belongs to points_possible -- the possible amount of points for the assignment unlock_date -- (optional) date the assignment becomes available on due_date -- (optional) date the assignment is due for lock_date -- (optional) date the assignment becomes unavailable on lti_id -- id labeled link to LTI instance Returns: On failure: unauthorized -- when the user is not logged in not_found -- could not find the course with the given id key_error -- missing keys forbidden -- the user is not allowed to create assignments in this course On success: succes -- with the assignment data """ name, description, course_id = utils.required_params(request.data, "name", "description", "course_id") points_possible, unlock_date, due_date, lock_date, lti_id, is_published = \ utils.optional_params(request.data, "points_possible", "unlock_date", "due_date", "lock_date", "lti_id", "is_published") course = Course.objects.get(pk=course_id) request.user.check_permission('can_add_assignment', course) assignment = factory.make_assignment(name, description, courses=[course], author=request.user, lti_id=lti_id, points_possible=points_possible, unlock_date=unlock_date, due_date=due_date, lock_date=lock_date, is_published=is_published) for user in course.users.all(): factory.make_journal(assignment, user) serializer = AssignmentSerializer(assignment, context={'user': request.user, 'course': course}) return response.created({'assignment': serializer.data})
def partial_update(self, request, pk): """Update user role in a course. Arguments: request -- request data user_id -- user ID role -- name of the role (default: Student) pk -- course ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the perticipation is not found forbidden -- when the user is not connected to the course forbidden -- when the user is not allowed to change the perticipation On success: success -- with the new role name """ user_id, = utils.required_params(request.data, 'user_id') role_name, group_name = utils.optional_params(request.data, 'role', 'group') user = User.objects.get(pk=user_id) course = Course.objects.get(pk=pk) participation = Participation.objects.get(user=user, course=course) request.user.check_permission('can_edit_course_user_group', course) if role_name: request.user.check_permission('can_edit_course_roles', course) participation.role = Role.objects.get(name=role_name, course=course) if group_name: participation.group = Group.objects.get(name=group_name, course=course) elif 'group' in request.data: participation.group = None participation.save() serializer = UserSerializer(participation.user, context={'course': course}) return response.success( {'user': serializer.data}, description='Successfully updated participation.')
def published_state(self, request, *args, **kwargs): """Update the grade publish status for whole assignment. Arguments: request -- the request that was send with published -- new published state pk -- assignment ID Returns a json string if it was successful or not. """ assignment_id, = utils.required_typed_params(kwargs, (int, 'pk')) published, = utils.required_params(request.data, 'published') assignment = Assignment.objects.get(pk=assignment_id) request.user.check_permission('can_publish_grades', assignment) self.publish(request, assignment, published) return response.success(payload={'new_published': published})
def call(obj, function, url, params=None, user=None, password=userfactory.DEFAULT_PASSWORD, status=200, status_when_unauthorized=401, content_type='application/json', access=None): # Set params to an empty dictionary when its None this cant be done in the parameters themself as that can give # unwanted results when calling the function multiple times if params is None: params = dict() params = params.copy() url = format_url(obj, url, params, function) if user is None: if function in [obj.client.get, obj.client.delete]: response = function(url, content_type=content_type) else: if content_type != 'application/json': response = function(url, params) else: response = function(url, json.dumps(params), content_type=content_type) else: # A test student does not login via password if not access: logged_user = login(obj, user, password) access, = utils.required_params(logged_user, 'access') if function in [obj.client.get, obj.client.delete]: response = function(url, content_type=content_type, HTTP_AUTHORIZATION='Bearer ' + access) else: if content_type != 'application/json': response = function(url, params, HTTP_AUTHORIZATION='Bearer ' + access) else: response = function(url, json.dumps(params), content_type=content_type, HTTP_AUTHORIZATION='Bearer ' + access) try: result = response.json() except (AttributeError, ValueError): result = response assert response.status_code == status, \ 'Request status did not match the expected response. Expected {}, but got {}: {}'.format(status, response.status_code, result) return result
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_params(request.data, 'user_id', '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) return response.success( description='Successfully added student to course.')
def check_fields(template, content_list): """Check if the supplied content list is a valid for the given template""" received_ids = [] # Check if all the content is valid for content in content_list: data, field_id = utils.required_params(content, 'data', 'id') if data is not None and data != '': received_ids.append(field_id) try: field = Field.objects.get(pk=field_id, template=template) except Field.DoesNotExist: raise VLEBadRequest('Passed field is not from template.') validators.validate_entry_content(data, field) # Check for missing required fields required_fields = Field.objects.filter(template=template, required=True) for field in required_fields: if field.id not in received_ids: raise VLEMissingRequiredField(field)
def handle_test_student(user, params): """Creates a test user if no user is proved and the params contain a blank email adress.""" if not user \ and 'custom_user_email' in params and params['custom_user_email'] == '' \ and 'custom_user_full_name' in params and params['custom_user_full_name'] == settings.LTI_TEST_STUDENT_FULL_NAME: lti_id, username, full_name, email, course_id = utils.required_params( params, 'user_id', 'custom_username', 'custom_user_full_name', 'custom_user_email', 'custom_course_id') profile_picture = '/unknown-profile.png' if 'custom_user_image' not in params else params[ 'custom_user_image'] is_teacher = settings.ROLES['Teacher'] in lti.roles_to_list(params) return factory.make_user(username, email=email, lti_id=lti_id, profile_picture=profile_picture, is_teacher=is_teacher, full_name=full_name, is_test_student=True) return user
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.can_edit(request.user): return response.forbidden( 'You are not allowed to edit this comment.') comment.last_edited_by = request.user comment.save() text, = utils.required_params(request.data, 'text') serializer = CommentSerializer(comment, data={'text': text}, partial=True) if not serializer.is_valid(): return response.bad_request() serializer.save() return response.success({'comment': serializer.data})
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 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 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 create(self, request): """Create a new entry. Deletes remaining temporary user files if successful. Arguments: request -- the request that was send with journal_id -- the journal id template_id -- the template id to create the entry with node_id -- optional: the node to bind the entry to (only for entrydeadlines) content -- the list of {tag, data} tuples to bind data to a template field. """ journal_id, template_id, content_list = utils.required_params( request.data, "journal_id", "template_id", "content") node_id, = utils.optional_params(request.data, "node_id") journal = Journal.objects.get(pk=journal_id, user=request.user) assignment = journal.assignment template = Template.objects.get(pk=template_id) request.user.check_permission('can_have_journal', assignment) if assignment.is_locked(): return response.forbidden( 'The assignment is locked, entries can no longer be edited/changed.' ) if journal.needs_lti_link(): return response.forbidden(journal.outdated_link_warning_msg) # Check if the template is available if not (node_id or assignment.format.template_set.filter( archived=False, preset_only=False, pk=template.pk).exists()): return response.forbidden('Entry template is not available.') entry_utils.check_fields(template, content_list) # Node specific entry if node_id: node = Node.objects.get(pk=node_id, journal=journal) entry = entry_utils.add_entry_to_node(node, template) # Template specific entry else: entry = factory.make_entry(template) node = factory.make_node(journal, entry) for content in content_list: field_id, = utils.required_typed_params(content, (int, 'id')) data, = utils.required_params(content, 'data') field = Field.objects.get(pk=field_id) created_content = factory.make_content(node.entry, data, field) if field.type in field.FILE_TYPES: # Image, file or PDF user_file = file_handling.get_temp_user_file( request.user, assignment, content['data']) if user_file is None and field.required: node.entry.delete() # If there is a newly created node, delete that as well if not node_id: node.delete() return response.bad_request( 'One of your files was not correctly uploaded, please try again.' ) elif user_file: file_handling.make_permanent_file_content( user_file, created_content, node) # Notify teacher on new entry if (node.journal.sourcedid and node.entry.vle_coupling == Entry.NEED_SUBMISSION): lti_tasks.needs_grading.delay(node.pk) # Delete old user files file_handling.remove_temp_user_files(request.user) return response.created({ 'added': entry_utils.get_node_index(journal, node, request.user), 'nodes': timeline.get_nodes(journal, request.user), 'entry': serialize.EntrySerializer(entry, context={ 'user': request.user }).data })
def partial_update(self, request, *args, **kwargs): """Update an existing entry. Arguments: request -- request data data -- the new data for the course pk -- assignment ID Returns: On failure: unauthorized -- when the user is not logged in not found -- when the entry does not exist forbidden -- User not allowed to edit this entry unauthorized -- when the user is unauthorized to edit the entry bad_request -- when there is invalid data in the request On success: success -- with the new entry data """ content_list, = utils.required_typed_params(request.data, (list, 'content')) entry_id, = utils.required_typed_params(kwargs, (int, 'pk')) entry = Entry.objects.get(pk=entry_id) graded = entry.is_graded() journal = entry.node.journal assignment = journal.assignment if assignment.is_locked(): return response.forbidden( 'The assignment is locked, entries can no longer be edited/changed.' ) request.user.check_permission('can_have_journal', assignment) if not (journal.user == request.user or request.user.is_superuser): return response.forbidden( 'You are not allowed to edit someone else\'s entry.') if graded: return response.bad_request( 'You are not allowed to edit graded entries.') if entry.is_locked(): return response.bad_request( 'You are not allowed to edit locked entries.') if journal.needs_lti_link(): return response.forbidden(journal.outdated_link_warning_msg) # Check for required fields entry_utils.check_fields(entry.template, content_list) # Attempt to edit the entries content. for content in content_list: field_id, = utils.required_typed_params(content, (int, 'id')) data, = utils.required_params(content, 'data') field = Field.objects.get(pk=field_id) old_content = entry.content_set.filter(field=field) if old_content.exists(): old_content = old_content.first() if old_content.field.pk != field_id: return response.bad_request( 'The given content does not match the accompanying field type.' ) if not data: old_content.delete() continue entry_utils.patch_entry_content(request.user, entry, old_content, field, data, assignment) # If there was no content in this field before, create new content with the new data. # This can happen with non-required fields, or when the given data is deleted. else: factory.make_content(entry, data, field) file_handling.remove_temp_user_files(request.user) return response.success({ 'entry': serialize.EntrySerializer(entry, context={ 'user': request.user }).data })