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 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 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 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 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 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 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 destroy(self, request, *args, **kwargs): """Delete an entry and the node it belongs to. Arguments: request -- request data pk -- entry ID Returns: On failure: not found -- when the course does not exist 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 was deleted """ pk, = utils.required_typed_params(kwargs, (int, 'pk')) entry = Entry.objects.get(pk=pk) journal = entry.node.journal assignment = journal.assignment if journal.user == request.user: request.user.check_permission( 'can_have_journal', assignment, 'You are not allowed to delete entries.') if entry.is_graded(): return response.forbidden( 'You are not allowed to delete graded entries.') if entry.is_locked(): return response.forbidden( 'You are not allowed to delete locked entries.') if assignment.is_locked(): return response.forbidden( 'You are not allowed to delete entries in a locked assignment.' ) elif not request.user.is_superuser: return response.forbidden( 'You are not allowed to delete someone else\'s entry.') if journal.needs_lti_link(): return response.forbidden(journal.outdated_link_warning_msg) if entry.node.type != Node.ENTRYDEADLINE: entry.node.delete() entry.delete() return response.success(description='Successfully deleted entry.')
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 GDPR(self, request, pk): """Get a zip file of all the userdata. Arguments: request -- request data pk -- user ID to download the files from Returns On failure: unauthorized -- when the user is not logged in 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 user = User.objects.get(pk=pk) # Check the right permissions to get this users data, either be the user of the data or be an admin. if not (request.user.is_superuser or request.user.id == pk): return response.forbidden( 'You are not allowed to view this user\'s data.') profile = UserSerializer(user).data journals = Journal.objects.filter(user=pk) journal_dict = {} for journal in journals: # Select the nodes of this journal but only the ones with entries. entry_ids = Node.objects.filter(journal=journal).exclude( entry__isnull=True).values_list('entry', flat=True) entries = Entry.objects.filter(id__in=entry_ids) # Serialize all entries and put them into the entries dictionary with the assignment name key. journal_dict.update({ journal.assignment.name: EntrySerializer(entries, context={ 'user': request.user, 'comments': True }, many=True).data }) archive_path, archive_name = file_handling.compress_all_user_data( user, { 'profile': profile, 'journals': journal_dict }) return response.file(archive_path, archive_name)
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.assignment) req_data = request.data published, = utils.optional_params(request.data, 'published') if published: request.user.check_permission('can_publish_grades', journal.assignment) req_data.pop('published', None) return self.publish(request, journal) bonus_points, = utils.optional_typed_params(request.data, (float, 'bonus_points')) if bonus_points is not None: request.user.check_permission('can_grade', journal.assignment) req_data.pop('bonus_points', None) journal.bonus_points = bonus_points journal.save() lti_grade.replace_result(journal) return response.success( {'journal': JournalSerializer(journal).data}) if not request.user.is_superuser: return response.forbidden( 'You are not allowed to edit this journal.') return self.admin_update(request, journal)
def get_lti_params_from_jwt(request, jwt_params): """Handle the controlflow for course/assignment create, connect and select. Returns the data needed for the correct entry place. """ 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_lti_roles(lti_params) # convert LTI param for True to python True lti_params['custom_assignment_publish'] = lti_params.get( 'custom_assignment_publish', 'false') == 'true' # If the course is already created, update that course, else return new course variables course = lti.update_lti_course_if_exists(lti_params, user, role) if course is None: return get_new_course_response(lti_params, role) # If the assignment is already created, update that assignment, else return new assignment variables assignment = lti.update_lti_assignment_if_exists(lti_params) if assignment is None: return get_new_assignment_response(lti_params, course, role) # Select a journal if user.has_permission('can_have_journal', assignment): journal = lti.select_create_journal(lti_params, user, assignment) return response.success( payload={ 'params': { 'state': get_finish_state(user, assignment, lti_params), 'cID': course.pk, 'aID': assignment.pk, 'jID': journal.pk if user. has_permission('can_have_journal', assignment) else None, } })
def list(self, request): """Get all users. Arguments: request -- request data Returns: On failure: unauthorized -- when the user is not logged in On succes: success -- with the course data """ if not request.user.is_superuser: return response.forbidden('Only administrators are allowed to request all user data.') serializer = UserSerializer(User.objects.all(), many=True) return response.success({'users': 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 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 retrieve(self, request, pk): """Get the preferences 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 exist On success: success -- with the preferences data """ pk = int(pk) if not (request.user.pk == pk or request.user.is_superuser): return response.forbidden('You are not allowed to view this users preferences.') preferences = Preferences.objects.get(user=pk) serializer = PreferencesSerializer(preferences) return response.success({'preferences': serializer.data})
def partial_update(self, request, *args, **kwargs): """Update an existing user. Arguments: request -- request data jwt_params -- jwt params to get the lti information from user_id -- id of the user user_image -- user image roles -- role of the user 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 exists bad request -- when the data is invalid On success: success -- with the updated user """ pk, = utils.required_typed_params(kwargs, (int, 'pk')) if pk == 0: pk = request.user.pk if not (request.user.pk == pk or request.user.is_superuser): return response.forbidden() user = User.objects.get(pk=pk) lti_id, user_email, user_full_name, user_image, is_teacher = get_lti_params( request, 'user_id', 'custom_user_email', 'custom_user_full_name', 'custom_user_image') if user_image is not None: user.profile_picture = user_image if user_email: if User.objects.filter(email=user_email).exclude( pk=user.pk).exists(): return response.bad_request( '{} is taken by another account. Link to that account or contact support.' .format(user_email)) user.email = user_email user.verified_email = True if user_full_name is not None: user.full_name = user_full_name if is_teacher: user.is_teacher = is_teacher if lti_id is not None: if User.objects.filter(lti_id=lti_id).exists(): return response.bad_request( 'User with this lti id already exists.') elif (bool(lti_id) and not bool(user_email) and user_full_name == settings.LTI_TEST_STUDENT_FULL_NAME or user.is_test_student): return response.forbidden( 'You are not allowed to link a test account to an existing account.' ) user.lti_id = lti_id user.save() if user.lti_id is not None: pp, = utils.optional_params(request.data, 'profile_picture') data = {'profile_picture': pp if pp else user.profile_picture} else: data = request.data serializer = OwnUserSerializer(user, data=data, partial=True) if not serializer.is_valid(): return response.bad_request() serializer.save() return response.success({'user': serializer.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 })
def partial_update(self, request, *args, **kwargs): """Update an existing user. Arguments: request -- request data jwt_params -- jwt params to get the lti information from user_id -- id of the user user_image -- user image roles -- role of the user 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 exists bad request -- when the data is invalid On success: success -- with the updated user """ pk, = utils.required_typed_params(kwargs, (int, 'pk')) if pk == 0: pk = request.user.pk if not (request.user.pk == pk or request.user.is_superuser): return response.forbidden() user = User.objects.get(pk=pk) lti_id, user_email, user_full_name, user_image, is_teacher = get_lti_params( request, 'user_id', 'custom_user_email', 'custom_user_full_name', 'custom_user_image') if user_image is not None: user.profile_picture = user_image if user_email is not None: user.email = user_email user.verified_email = True if user_full_name is not None: user.first_name, user.last_name = lti.split_fullname(user_full_name) if is_teacher: user.is_teacher = is_teacher if lti_id is not None: if User.objects.filter(lti_id=lti_id).exists(): return response.bad_request('User with this lti id already exists.') user.lti_id = lti_id user.save() if user.lti_id is not None: gn, cn, pp = utils.optional_params( request.data, 'grade_notifications', 'comment_notifications', 'profile_picture') data = { 'grade_notifications': gn if gn else user.grade_notifications, 'comment_notifications': cn if cn else user.comment_notifications, 'profile_picture': pp if pp else user.profile_picture } else: data = request.data serializer = OwnUserSerializer(user, data=data, partial=True) if not serializer.is_valid(): return response.bad_request() serializer.save() return response.success({'user': serializer.data})