Beispiel #1
0
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.')
Beispiel #2
0
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!')
Beispiel #3
0
    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})
Beispiel #4
0
    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})
Beispiel #5
0
    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})
Beispiel #6
0
    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})
Beispiel #7
0
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!')
Beispiel #8
0
    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.')
Beispiel #9
0
    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})
Beispiel #10
0
    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})
Beispiel #11
0
 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
Beispiel #12
0
    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})
Beispiel #13
0
    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))
Beispiel #14
0
    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')
Beispiel #15
0
    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})
Beispiel #16
0
    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.')
Beispiel #17
0
    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})
Beispiel #18
0
    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.')
Beispiel #19
0
    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})
Beispiel #20
0
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
Beispiel #21
0
    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.')
Beispiel #22
0
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)
Beispiel #23
0
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
Beispiel #24
0
    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})
Beispiel #25
0
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.')
Beispiel #26
0
    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})
Beispiel #27
0
    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)
Beispiel #28
0
    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})
Beispiel #29
0
    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
        })
Beispiel #30
0
    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
        })