Exemplo n.º 1
0
def get_course_by_id(course_id: int) -> ExtendedJSONResponse[models.Course]:
    """Return course data for a given :class:`.models.Course`.

    .. :quickref: Course; Get data for a given course.

    :param int course_id: The id of the course

    :returns: A response containing the JSON serialized course

    :>json str role: The name of the role the current user has in this
        course.
    :>json ``**rest``: JSON serialization of :py:class:`psef.models.Course`.

    :raises APIException: If there is no course with the given id.
                          (OBJECT_ID_NOT_FOUND)
    :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN)
    """
    course = helpers.get_or_404(models.Course, course_id)
    auth.CoursePermissions(course).ensure_may_see()

    if not helpers.request_arg_true('no_role_name'):
        helpers.add_deprecate_warning(
            'Getting the role of the current user in the requested course is'
            ' deprecated and will be removed in the next major version of'
            ' CodeGrade')
        helpers.jsonify_options.get_options().add_role_to_course = True

    return ExtendedJSONResponse.make(course, use_extended=models.Course)
Exemplo n.º 2
0
def update_course(course_id: int) -> ExtendedJSONResponse[models.Course]:
    """Update the given :class:`.models.Course` with new values.

    .. :quickref: Course; Update course data.

    :param int course_id: The id of the course you want to update.

    :returns: The updated course, in extended format.
    """
    data = rqa.FixedMapping(
        rqa.OptionalArgument(
            'name',
            rqa.SimpleValue.str,
            'The new name of the course',
        ),
        rqa.OptionalArgument(
            'state',
            rqa.EnumValue(models.CourseState),
            """
            The new state of the course, currently you cannot set the state of
            a course to 'deleted'
            """,
        )).from_flask()
    course = helpers.get_or_404(models.Course, course_id)
    checker = auth.CoursePermissions(course)
    checker.ensure_may_see()

    if data.name.is_just:
        if course.is_lti:
            raise APIException(
                'You cannot rename LTI courses',
                ('LTI courses get their name from the LMS, so renaming is'
                 ' not possible'), APICodes.INVALID_PARAM, 400)
        if not data.name.value:
            raise APIException(
                'The name of a course should contain at least one character',
                'A course name cannot be empty', APICodes.INVALID_PARAM, 400)
        checker.ensure_may_edit_info()
        course.name = data.name.value

    if data.state.is_just:
        if data.state.value.is_deleted:
            raise APIException(
                'It is not yet possible to delete a course',
                'Deleting courses in the API is not yet possible',
                APICodes.INVALID_PARAM, 400)
        checker.ensure_may_edit_state()
        course.state = data.state.value

    db.session.commit()

    return ExtendedJSONResponse.make(course, use_extended=models.Course)
Exemplo n.º 3
0
def get_courses() -> ExtendedJSONResponse[t.Sequence[models.Course]]:
    """Return all Course objects the current user is a member of.

    .. :quickref: Course; Get all courses the current user is enrolled in.

    :returns: A response containing the JSON serialized courses

    :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN)
    """
    courses_query = models.Course.update_query_for_extended_jsonify(
        models.Course.query.filter(
            auth.CoursePermissions.ensure_may_see_filter())).order_by(
                sql_expression.case(
                    [(models.Course.state.is_visible, 0),
                     (models.Course.state.is_archived, 1)],
                    else_=2,
                ),
                models.Course.created_at.desc(),
                models.Course.name,
            )

    psef.current_user.load_all_permissions()

    courses = helpers.maybe_apply_sql_slice(courses_query).all()
    assignments = cg_helpers.flatten(c.assignments for c in courses)
    assignment_ids = [a.id for a in assignments]
    with_linter = set(
        assignment_id for assignment_id, in
        models.AssignmentLinter.get_whitespace_linter_query().filter(
            models.AssignmentLinter.assignment_id.in_(assignment_ids)
        ).with_entities(models.AssignmentLinter.assignment_id))
    for assignment in assignments:
        assignment.whitespace_linter_exists = assignment.id in with_linter

    if not helpers.request_arg_true('no_role_name'):
        helpers.add_deprecate_warning(
            'Getting the role of the current user in the requested course is'
            ' deprecated and will be removed in the next major version of'
            ' CodeGrade')
        helpers.jsonify_options.get_options().add_role_to_course = True

    return ExtendedJSONResponse.make_list(courses, use_extended=models.Course)
Exemplo n.º 4
0
def get_register_link(
        course_id: int, link_id: uuid.UUID
) -> ExtendedJSONResponse[models.CourseRegistrationLink]:
    """Get a registration link.

    .. :quickref: Course; Get the data in a registration link.

    :param course_id: The id of the course to which the registration link is
        connected.
    :param link_id: The id of the registration link.

    :returns: The specified registration link.

    .. note::

        This route can be used without logging in, i.e. you don't have to be
        enrolled in the course to use this route. This route will not work for
        expired registration links.
    """
    link = _get_non_expired_link(course_id, link_id)

    return ExtendedJSONResponse.make(
        link, use_extended=models.CourseRegistrationLink)
Exemplo n.º 5
0
def add_course() -> ExtendedJSONResponse[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)
    """
    with helpers.get_from_request_transaction() as [get, _]:
        name = get('name', str)

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

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

    return ExtendedJSONResponse.make(new_course, use_extended=models.Course)
Exemplo n.º 6
0
def get_user_submissions(
    course_id: int, user_id: int
) -> ExtendedJSONResponse[t.Mapping[int, t.Sequence[models.Work]]]:
    """Get all :class:`.models.Work`s by the given :class:`.models.User` in the
    given :class:`.models.Course`.

    .. :quickref: Course; Get submissions by user in a course.

    :qparam boolean latest_only: Only get the latest submission of a
        user. Please use this option if at all possible, as students have a
        tendency to submit many attempts and that can make this route quite
        slow.

    :param int course_id: The id of the course
    :param int user_id: The id of the user
    :returns: A response containing the JSON serialized submissions.

    :raises NotFoundException: If the course does not exist.
        (OBJECT_ID_NOT_FOUND)
    :raises NotFoundException: If the user does not exist.
        (OBJECT_ID_NOT_FOUND)
    :raises APIException: If the given user is not member of the course.
        (INVALID_PARAM)
    :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN)
    :raises PermissionException: If the given user is not the logged in user
        and the logged in user does not have the permission to see others work.
        (INCORRECT_PERMISSION)
    """
    course = helpers.get_or_404(models.Course, course_id)
    auth.ensure_permission(CPerm.can_see_assignments, course.id)
    assignments = course.get_all_visible_assignments()

    user = helpers.get_or_404(models.User, user_id)
    if any(not u.is_enrolled(course_id) for u in user.get_contained_users()):
        raise APIException(
            'User is not enrolled in this course',
            f'User {user_id} not enrolled in course {course_id}',
            APICodes.INVALID_PARAM, 400)
    elif not user.contains_user(current_user):
        auth.ensure_permission(CPerm.can_see_others_work, course.id)

    latest_only = helpers.request_arg_true('latest_only')

    def get_subs(query: models.MyQuery[models.Work]) -> t.List[models.Work]:
        return models.Work.update_query_for_extended_jsonify(
            models.Work.limit_to_user_submissions(query, user), ).all()

    if latest_only:
        subs = {}
        for assignment in assignments:
            subs[assignment.id] = get_subs(
                assignment.get_all_latest_submissions(), )
    else:
        query = models.Work.query.filter(
            models.Work.assignment_id.in_([a.id for a in assignments]),
            # Use _deleted because we already know the assignment exists.
            ~models.Work._deleted,  # pylint: disable=protected-access
        ).order_by(models.Work.created_at.asc())
        subs = {assig.id: [] for assig in assignments}
        for sub in get_subs(query):
            subs[sub.assignment_id].append(sub)

    return ExtendedJSONResponse.make(
        subs,
        use_extended=models.Work,
    )