Ejemplo n.º 1
0
def create_new_assignment(course_id: int) -> JSONResponse[models.Assignment]:
    """Create a new course for the given assignment.

    .. :quickref: Course; Create a new assignment in a course.

    :param int course_id: The course to create an assignment in.

    :<json str name: The name of the new assignment.

    :returns: The newly created assignment.
    """
    with get_from_map_transaction(get_json_dict_from_request()) as [get, _]:
        name = get('name', str)

    course = helpers.get_or_404(
        models.Course,
        course_id,
        also_error=lambda c: c.virtual,
    )

    assig = models.Assignment(
        name=name,
        course=course,
        is_lti=False,
    )
    auth.AssignmentPermissions(assig).ensure_may_add()
    db.session.add(assig)
    db.session.commit()

    return jsonify(assig)
Ejemplo n.º 2
0
def add_course() -> JSONResponse[models.Course]:
    """Add a new :class:`.models.Course`.

    .. :quickref: Course; Add a new course.

    :returns: A response containing the JSON serialization of the new course

    :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN)
    :raises PermissionException: If the user can not create courses.
                                 (INCORRECT_PERMISSION)
    :raises APIException: If the parameter "name" is not in the request.
        (MISSING_REQUIRED_PARAM)
    """
    content = get_json_dict_from_request()
    ensure_keys_in_dict(content, [('name', str)])
    name = t.cast(str, content['name'])

    new_course = models.Course.create_and_add(name)
    db.session.commit()

    role = models.CourseRole.get_initial_course_role(new_course)
    current_user.courses[new_course.id] = role
    db.session.commit()

    return jsonify(new_course)
Ejemplo n.º 3
0
def register_user_in_course(
        course_id: int,
        link_id: uuid.UUID) -> JSONResponse[t.Mapping[str, str]]:
    """Register as a new user, and directly enroll in a course.

    .. :quickref: Course; Register as a new user, and enroll in a course.

    :param course_id: The id of the course to which the registration link is
        connected.
    :param link_id: The id of the registration link.
    :>json access_token: The access token that the created user can use to
        login.
    """
    link = _get_non_expired_link(course_id, link_id)

    if not link.allow_register:
        raise PermissionException(
            'You are not allowed to register using this link',
            'This link does not support registration',
            APICodes.INCORRECT_PERMISSION, 403)

    with get_from_map_transaction(get_json_dict_from_request()) as [get, _]:
        username = get('username', str)
        password = get('password', str)
        email = get('email', str)
        name = get('name', str)

    user = models.User.register_new_user(username=username,
                                         password=password,
                                         email=email,
                                         name=name)
    user.courses[link.course_id] = link.course_role
    db.session.commit()

    return jsonify({'access_token': user.make_access_token()})
Ejemplo n.º 4
0
def create_or_edit_registration_link(
        course_id: int) -> JSONResponse[models.CourseRegistrationLink]:
    """Create or edit a registration link.

    .. :quickref: Course; Create or edit a registration link for a course.

    :param course_id: The id of the course in which this link should enroll
        users.
    :>json id: The id of the link to edit, omit to create a new link.
    :>json role_id: The id of the role that users should get when registering
        with this link.
    :>json expiration_date: The date this link should stop working, this date
        should be in ISO8061 format without any timezone information, as it
        will be interpret as a UTC date.
    :returns: The created or edited link.
    """
    course = helpers.get_or_404(models.Course,
                                course_id,
                                also_error=lambda c: c.virtual)
    auth.ensure_permission(CPerm.can_edit_course_users, course_id)

    with get_from_map_transaction(
            get_json_dict_from_request()) as [get, opt_get]:
        expiration_date = get('expiration_date', str)
        role_id = get('role_id', int)
        link_id = opt_get('id', str, default=None)

    if link_id is None:
        link = models.CourseRegistrationLink(course=course)
        db.session.add(link)
    else:
        link = helpers.filter_single_or_404(
            models.CourseRegistrationLink,
            models.CourseRegistrationLink.id == uuid.UUID(link_id),
            also_error=lambda l: l.course_id != course.id)

    link.course_role = helpers.get_or_404(
        models.CourseRole,
        role_id,
        also_error=lambda r: r.course_id != course.id)
    link.expiration_date = parsers.parse_datetime(expiration_date)
    if link.expiration_date < helpers.get_request_start_time():
        helpers.add_warning('The link has already expired.',
                            APIWarnings.ALREADY_EXPIRED)

    if link.course_role.has_permission(CPerm.can_edit_course_roles):
        helpers.add_warning(
            ('Users that register with this link will have the permission'
             ' to give themselves more permissions.'),
            APIWarnings.DANGEROUS_ROLE)

    db.session.commit()
    return jsonify(link)
Ejemplo n.º 5
0
def patch_course_snippet(course_id: int, snippet_id: int) -> EmptyResponse:
    """Modify the :class:`.models.CourseSnippet` with the given id.

    .. :quickref: CourseSnippet; Change a snippets key and value.

    :param int snippet_id: The id of the snippet to change.
    :returns: An empty response with return code 204.

    :<json str key: The new key of the snippet.
    :<json str value: The new value of the snippet.

    :raises APIException: If the parameters "key" and/or "value" were not in
        the request. (MISSING_REQUIRED_PARAM)
    :raises APIException: If the snippet does not belong to the current user.
        (INCORRECT_PERMISSION)
    :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN)
    :raises PermissionException: If the user can not use snippets.
        (INCORRECT_PERMISSION)
    :raises APIException: If another snippet with the same key already exists.
        (OBJECT_ALREADY_EXISTS)
    """
    auth.ensure_permission(CPerm.can_manage_course_snippets, course_id)
    content = get_json_dict_from_request()

    ensure_keys_in_dict(content, [('key', str), ('value', str)])
    key = t.cast(str, content['key'])
    value = t.cast(str, content['value'])

    course = helpers.get_or_404(models.Course, course_id)
    snip = helpers.get_or_404(
        models.CourseSnippet,
        snippet_id,
        also_error=lambda snip: snip.course_id != course.id)

    other = models.CourseSnippet.query.filter_by(
        course=course,
        key=key,
    ).first()
    if other is not None and other.id != snippet_id:
        raise APIException(
            'A snippet with the same key already exists.',
            'A snippet with key "{}" already exists for course "{}"'.format(
                key, course_id),
            APICodes.OBJECT_ALREADY_EXISTS,
            400,
        )

    snip.key = key
    snip.value = value
    db.session.commit()

    return make_empty_response()
Ejemplo n.º 6
0
def update_role(course_id: int, role_id: int) -> EmptyResponse:
    """Update the :class:`.models.Permission` of a given
    :class:`.models.CourseRole` in the given :class:`.models.Course`.

    .. :quickref: Course; Update a permission for a certain role.

    :param int course_id: The id of the course.
    :param int role_id: The id of the course role.
    :returns: An empty response with return code 204.

    :<json str permission: The name of the permission to change.
    :<json bool value: The value to set the permission to (``True`` means the
        specified role has the specified permission).

    :raises APIException: If the value or permission parameter are not in the
                          request. (MISSING_REQUIRED_PARAM)
    :raises APIException: If the role with the given id does not exist or the
                          permission with the given name does not exist.
                          (OBJECT_NOT_FOUND)
    :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN)
    :raises PermissionException: If the user can not manage the course with the
                                 given id. (INCORRECT_PERMISSION)
    """
    content = get_json_dict_from_request()

    auth.ensure_permission(CPerm.can_edit_course_roles, course_id)

    ensure_keys_in_dict(content, [('value', bool), ('permission', str)])
    value = t.cast(bool, content['value'])
    permission_name = t.cast(str, content['permission'])
    permission = CPerm.get_by_name(permission_name)

    role = helpers.filter_single_or_404(
        models.CourseRole,
        models.CourseRole.course_id == course_id,
        models.CourseRole.id == role_id,
        also_error=lambda r: r.hidden,
    )

    if (current_user.courses[course_id].id == role.id
            and permission == CPerm.can_edit_course_roles):
        raise APIException(
            'You cannot remove this permission from your own role',
            ('The current user is in role {} which'
             ' cannot remove "can_edit_course_roles"').format(role.id),
            APICodes.INCORRECT_PERMISSION, 403)

    role.set_permission(permission, value)

    db.session.commit()

    return make_empty_response()
Ejemplo n.º 7
0
def add_role(course_id: int) -> EmptyResponse:
    """Add a new :class:`.models.CourseRole` to the given
    :class:`.models.Course`.

    .. :quickref: Course; Add a new course role to a course.

    :param int course_id: The id of the course
    :returns: An empty response with return code 204.

    :<json str name: The name of the new course role.

    :raises APIException: If the name parameter was not in the request.
                          (MISSING_REQUIRED_PARAM)
    :raises APIException: If the course with the given id was not found.
                          (OBJECT_NOT_FOUND)
    :raises APIException: If the course already has a role with the submitted
                          name. (INVALID_PARAM)
    :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN)
    :raises PermissionException: If the user can not manage the course with the
                                 given id. (INCORRECT_PERMISSION)
    """
    auth.ensure_permission(CPerm.can_edit_course_roles, course_id)

    content = get_json_dict_from_request()

    ensure_keys_in_dict(content, [('name', str)])
    name = t.cast(str, content['name'])

    course = helpers.get_or_404(
        models.Course,
        course_id,
        also_error=lambda c: c.virtual,
    )

    if models.CourseRole.query.filter_by(
            name=name, course_id=course_id).first() is not None:
        raise APIException(
            'This course already has a role with this name',
            'The course "{}" already has a role named "{}"'.format(
                course_id, name), APICodes.INVALID_PARAM, 400)

    role = models.CourseRole(name=name, course=course, hidden=False)
    db.session.add(role)
    db.session.commit()

    return make_empty_response()
Ejemplo n.º 8
0
def create_course_snippet(
        course_id: int) -> JSONResponse[models.CourseSnippet]:
    """Add or modify a :class:`.models.CourseSnippet` by key.

    .. :quickref: CourseSnippet; Add or modify a course snippet.

    :returns: A response containing the JSON serialized snippet and return
              code 201.
    :<json str value: The new value of the snippet.
    :<json str key: The key of the new or existing snippet.

    :raises APIException: If the parameters "key", "value", and/or "course_id"
        were not in the request. (MISSING_REQUIRED_PARAM)
    :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN)
    :raises PermissionException: If the user can not use snippets
        (INCORRECT_PERMISSION)
    """
    auth.ensure_permission(CPerm.can_manage_course_snippets, course_id)
    content = get_json_dict_from_request()
    ensure_keys_in_dict(content, [('value', str), ('key', str)])
    key = t.cast(str, content['key'])
    value = t.cast(str, content['value'])

    course = helpers.get_or_404(models.Course, course_id)
    snippet = models.CourseSnippet.query.filter_by(
        course=course,
        key=key,
    ).first()

    if snippet is None:
        snippet = models.CourseSnippet(
            course=course,
            key=key,
            value=value,
        )
        db.session.add(snippet)
    else:
        snippet.value = value

    db.session.commit()

    return jsonify(snippet, status_code=201)
Ejemplo n.º 9
0
def create_new_assignment(course_id: int) -> JSONResponse[models.Assignment]:
    """Create a new course for the given assignment.

    .. :quickref: Course; Create a new assignment in a course.

    :param int course_id: The course to create an assignment in.

    :<json str name: The name of the new assignment.

    :returns: The newly created assignment.

    :raises PermissionException: If the current user does not have the
        ``can_create_assignment`` permission (INCORRECT_PERMISSION).
    """
    auth.ensure_permission(CPerm.can_create_assignment, course_id)

    content = get_json_dict_from_request()
    ensure_keys_in_dict(content, [('name', str)])
    name = t.cast(str, content['name'])

    course = helpers.get_or_404(
        models.Course,
        course_id,
        also_error=lambda c: c.virtual,
    )

    if course.lti_provider is not None:
        lms = course.lti_provider.lms_name
        raise APIException(f'You cannot add assignments to a {lms} course',
                           f'The course "{course_id}" is a LTI course',
                           APICodes.INVALID_STATE, 400)

    assig = models.Assignment(
        name=name,
        course=course,
        is_lti=False,
    )
    db.session.add(assig)
    db.session.commit()

    return jsonify(assig)
Ejemplo n.º 10
0
def register_user_in_course(
        course_id: int,
        link_id: uuid.UUID) -> JSONResponse[t.Mapping[str, str]]:
    """Register as a new user, and directly enroll in a course.

    .. :quickref: Course; Register as a new user, and enroll in a course.

    :param course_id: The id of the course to which the registration link is
        connected.
    :param link_id: The id of the registration link.
    :>json access_token: The access token that the created user can use to
        login.
    """
    link = helpers.get_or_404(models.CourseRegistrationLink,
                              link_id,
                              also_error=lambda l: l.course_id != course_id)
    if link.expiration_date < helpers.get_request_start_time():
        raise APIException('This registration link has expired.',
                           f'The registration link {link.id} has expired',
                           APICodes.OBJECT_EXPIRED, 409)

    with get_from_map_transaction(get_json_dict_from_request()) as [get, _]:
        username = get('username', str)
        password = get('password', str)
        email = get('email', str)
        name = get('name', str)

    user = models.User.register_new_user(username=username,
                                         password=password,
                                         email=email,
                                         name=name)
    user.courses[link.course_id] = link.course_role
    db.session.commit()

    token: str = flask_jwt.create_access_token(
        identity=user.id,
        fresh=True,
    )
    return jsonify({'access_token': token})
Ejemplo n.º 11
0
def create_group_set(course_id: int) -> JSONResponse[models.GroupSet]:
    """Create or update a :class:`.models.GroupSet` in the given course id.

    .. :quickref: Course; Create a new group set in the course.

    :>json int minimum_size: The minimum size attribute that the group set
        should have.
    :>json int maximum_size: The maximum size attribute that the group set
        should have.
    :>json int id: The id of the group to update.
    :param course_id: The id of the course in which the group set should be
        created or updated. The course id of a group set cannot change.
    :returns: The created or updated group.
    """
    auth.ensure_permission(CPerm.can_edit_group_set, course_id)
    course = helpers.get_or_404(models.Course, course_id)

    content = get_json_dict_from_request()
    ensure_keys_in_dict(content, [
        ('minimum_size', int),
        ('maximum_size', int),
    ])
    min_size = t.cast(int, content['minimum_size'])
    max_size = t.cast(int, content['maximum_size'])

    if 'id' in content:
        ensure_keys_in_dict(content, [('id', int)])
        group_set_id = t.cast(int, content['id'])
        group_set = helpers.get_or_404(
            models.GroupSet,
            group_set_id,
        )
        if group_set.course_id != course.id:
            raise APIException(
                'You cannot change the course id of a group set',
                (f'The group set {group_set.id} is '
                 f'not connected to course {course.id}'),
                APICodes.INVALID_PARAM, 400)
    else:
        group_set = models.GroupSet(course_id=course.id)
        models.db.session.add(group_set)

    if min_size <= 0:
        raise APIException('Minimum size should be larger than 0',
                           f'Minimum size "{min_size}" is <= than 0',
                           APICodes.INVALID_PARAM, 400)
    elif max_size < min_size:
        raise APIException('Maximum size is smaller than minimum size',
                           (f'Maximum size "{max_size}" is smaller '
                            f'than minimum size "{min_size}"'),
                           APICodes.INVALID_PARAM, 400)
    elif group_set.largest_group_size > max_size:
        raise APIException('There are groups larger than the new maximum size',
                           f'Some groups have more than {max_size} members',
                           APICodes.INVALID_PARAM, 400)
    elif group_set.smallest_group_size < min_size:
        raise APIException(
            'There are groups smaller than the new minimum size',
            f'Some groups have less than {min_size} members',
            APICodes.INVALID_PARAM, 400)
    group_set.minimum_size = min_size
    group_set.maximum_size = max_size

    models.db.session.commit()

    return jsonify(group_set)
Ejemplo n.º 12
0
def set_course_permission_user(
        course_id: int) -> t.Union[EmptyResponse, JSONResponse[_UserCourse]]:
    """Set the :class:`.models.CourseRole` of a :class:`.models.User` in the
    given :class:`.models.Course`.

    .. :quickref: Course; Change the course role for a user.

    :param int course_id: The id of the course
    :returns: If the user_id parameter is set in the request the response will
              be empty with return code 204. Otherwise the response will
              contain the JSON serialized user and course role with return code
              201

    :raises APIException: If the parameter role_id or not at least one of
                          user_id and user_email are in the request.
                          (MISSING_REQUIRED_PARAM)
    :raises APIException: If no role with the given role_id or no user
                          with the supplied parameters exists.
                          (OBJECT_ID_NOT_FOUND)
    :raises APIException: If the user was selected by email and the user is
                          already in the course. (INVALID_PARAM)
    :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN)
    :raises PermissionException: If the user can not manage the course with the
                                 given id. (INCORRECT_PERMISSION)

    .. todo::
        This function should probability be splitted.
    """
    auth.ensure_permission(CPerm.can_edit_course_users, course_id)

    content = get_json_dict_from_request()
    ensure_keys_in_dict(content, [('role_id', int)])
    role_id = t.cast(int, content['role_id'])

    role = helpers.filter_single_or_404(
        models.CourseRole, models.CourseRole.id == role_id,
        models.CourseRole.course_id == course_id)

    res: t.Union[EmptyResponse, JSONResponse[_UserCourse]]

    if 'user_id' in content:
        with get_from_map_transaction(content) as [get, _]:
            user_id = get('user_id', int)

        user = helpers.get_or_404(models.User, user_id)

        if user.id == current_user.id:
            raise APIException(
                'You cannot change your own role',
                'The user requested and the current user are the same',
                APICodes.INCORRECT_PERMISSION, 403)

        res = make_empty_response()
    elif 'username' in content:
        with get_from_map_transaction(content) as [get, _]:
            username = get('username', str)

        user = helpers.filter_single_or_404(models.User,
                                            models.User.username == username)

        if course_id in user.courses:
            raise APIException(
                'The specified user is already in this course',
                'The user {} is in course {}'.format(user.id, course_id),
                APICodes.INVALID_PARAM, 400)

        res = jsonify({
            'User': user,
            'CourseRole': role,
        }, status_code=201)
    else:
        raise APIException(
            'None of the keys "user_id" or "role_id" were found',
            ('The given content ({})'
             ' does  not contain "user_id" or "user_email"').format(content),
            APICodes.MISSING_REQUIRED_PARAM, 400)

    if user.is_test_student:
        raise APIException('You cannot change the role of a test student',
                           f'The user {user.id} is a test student',
                           APICodes.INVALID_PARAM, 400)

    user.courses[role.course_id] = role
    db.session.commit()
    return res