Commit 97c14810 authored by Barbora Kompišová's avatar Barbora Kompišová
Browse files

roles API consolidation

project API consolidation

course API consolidation

user API consolidation
parent f5b64c93
Loading
Loading
Loading
Loading
+9 −7
Original line number Diff line number Diff line
@@ -20,14 +20,14 @@ class CourseResource(Resource):
        log.info(f"GET to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Course with identifier {cid} not found.")
            abort(404, message=f"Could not find course {cid}.")
        return course_schema.dump(course)

    def delete(self, cid):
        log.info(f"DELETE to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Course with identifier {cid} not found.")
            abort(404, message=f"Could not find course {cid}.")
        delete_entity(course)
        return '', 204

@@ -35,7 +35,7 @@ class CourseResource(Resource):
        log.info(f"PUT to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Course with identifier {cid} not found.")
            abort(404, message=f"Could not find course {cid}.")

        json_data = request.get_json()
        if not json_data:
@@ -50,7 +50,7 @@ class CourseResource(Resource):
        course.codename = data['codename']
        # relationships are written elsewhere
        write_entity(course)
        log.debug(f"Course update successful. Course={course}")
        log.debug(f"Updated course {cid} to {course}.")
        return '', 204


@@ -64,7 +64,8 @@ class CourseList(Resource):
        log.info(f"POST to {request.url}")
        json_data = request.get_json()
        if not json_data:
            abort(400, message='No data provided for course update.')
            abort(400, message='No data provided for course creation.')

        try:
            data = course_schema.load(json_data)
        except ValidationError as err:
@@ -73,6 +74,7 @@ class CourseList(Resource):
        name = data[0]['name']
        codename = data[0]['codename']
        new_course = Course(name=name, codename=codename)

        write_entity(new_course)
        log.debug(f"Created course={new_course}")
        return course_schema.dump(new_course)[0], 201
@@ -82,13 +84,13 @@ class CourseNotesToken(Resource):
    def get(self, cid):
        course = find_course(cid)
        if not course:
            abort(404, message=f"Course with identifier {cid} not found.")
            abort(404, message=f"Could not find course {cid}.")
        return course.notes_access_token

    def put(self, cid):
        course = find_course(cid)
        if not course:
            abort(404, message=f"Course with identifier {cid} not found.")
            abort(404, message=f"Could not find course {cid}.")

        json_data = request.get_json()
        if not json_data:
+39 −31
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ from flask import Blueprint, request
from flask_restful import Api, Resource, abort
from marshmallow import ValidationError
from flask_jwt_extended import JWTManager, jwt_required, create_access_token, get_jwt_identity
from datetime import timedelta

from portal.rest import ProjectSchema, ProjectConfigSchema, SubmissionSchema,\
    delete_entity, write_entity, find_course, find_project, find_user
@@ -26,20 +27,22 @@ class ProjectResource(Resource):
        log.info(f"GET to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not find project {pid}: course with identifier {cid} not found.")
        role = find_project(course, pid)
        if not role:
            abort(404, message=f"Could not find project {pid} in course {cid}: course not found.")
        project = find_project(course, pid)
        if not project:
            abort(404, message=f"Could not find project {pid} in course {cid}: project not found.")
        return project_schema.dump(role)

        return project_schema.dump(project)

    def delete(self, cid, pid):
        log.info(f"DELETE to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not delete project {pid}: course with identifier {cid} not found.")
            abort(404, message=f"Could not delete project {pid} in course {cid}: course not found.")
        project = find_project(course, pid)
        if not project:
            abort(404, message=f"Could not delete project {pid} in course {cid}: project not found.")

        delete_entity(project)
        return '', 204

@@ -47,7 +50,7 @@ class ProjectResource(Resource):
        log.info(f"PUT to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not update project {pid}: course with identifier {cid} not found.")
            abort(404, message=f"Could not update project {pid} in course {cid}: course not found.")
        project = find_project(course, pid)
        if not project:
            abort(404, message=f"Could not update project {pid} in course {cid}: project not found.")
@@ -63,7 +66,7 @@ class ProjectResource(Resource):

        project.name = data['name']
        write_entity(project)
        log.debug(f"Project update successful. Project={project}")
        log.debug(f"Updated project {pid} to {project}.")
        return '', 204


@@ -72,7 +75,7 @@ class ProjectsList(Resource):
        log.info(f"GET to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not list projects: course with identifier {cid} not found.")
            abort(404, message=f"Could not list projects in course {cid}: course not found.")

        return projects_schema.dump(course.projects)

@@ -80,10 +83,11 @@ class ProjectsList(Resource):
        log.info(f"POST to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not create project: Course with identifier {cid} not found.")
            abort(404, message=f"Could not create project in course {cid}: course not found.")
        json_data = request.get_json()
        if not json_data:
            abort(400, message='No data provided for project creation.')

        try:
            data = project_schema.load(json_data)[0]
        except ValidationError as err:
@@ -101,20 +105,21 @@ class ProjectConfigResource(Resource):
        log.info(f"GET to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not get project configuration: course with identifier {cid} not found.")
            abort(404, message=f"Could not get configuration of project {pid} in course {cid}: course not found.")
        project = find_project(course, pid)
        if not project:
            abort(404, message=f"Could not get project configuration: project {pid} not found in course {cid}")
            abort(404, message=f"Could not get configuration of project {pid} in course {cid}: project not found")

        return config_schema.dump(project.config)

    def put(self, cid, pid):
        log.info(f"PUT to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not update project configuration: course with identifier {cid} not found.")
            abort(404, message=f"Could not update configuration of project {pid} in course {cid}: course not found.")
        project = find_project(course, pid)
        if not project:
            abort(404, message=f"Could not update project configuration: project {pid} not found in course {cid}.")
            abort(404, message=f"Could not update configuration of project {pid} in course {cid}: project not found.")

        json_data = request.get_json()
        if not json_data:
@@ -126,7 +131,7 @@ class ProjectConfigResource(Resource):
            return abort(422, errors=err.messages)  # TODO: flash, process errors

        project.set_config(data)  # TODO: check - missing **? in role permissions too
        log.debug(f"Updated configuration for project {pid} in course {cid}.")
        log.debug(f"Updated configuration for project {pid} in course {cid} to {data}.")
        return '', 204


@@ -136,11 +141,11 @@ class ProjectSubmissions(Resource):
        log.info(f"GET to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not list project submissions: course with identifier {cid} not found.")
            abort(404, message=f"Could not list submissions of project {pid} in course {cid}: course not found.")
        project = find_project(course, pid)
        if not project:
            abort(404, message=f"Could not list project submissions: project {pid} not found in course {cid}.")

            abort(404, message=f"Could not list submissions of project {pid} in course {cid}: project not found.")
        # TODO: user=<user_selector>` - filters submissions by user
        return submissions_schema.dump(project.submissions)

    @jwt_required
@@ -153,19 +158,23 @@ class ProjectSubmissions(Resource):

        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not update project configuration: course with identifier {cid} not found.")
            abort(404, message=f"Could not create a new submission for user {user.username} in project {pid} "
                               f"in course {cid}: course not found.")
        project = find_project(course, pid)
        if not project:
            abort(404, message=f"Could not update project configuration: project {pid} not found in course {cid}.")
            abort(404, message=f"Could not create a new submission for user {user.username} in project {pid} "
                               f"in course {cid}: project not found.")

        # check if a new submission can be created by this user in this project
        if not can_create_submission(user, project):
        # TODO: configure conflict_timeout in project
        if not can_create_submission(user, project, conflict_timeout=timedelta(minutes=30)):
            # maybe use 409, 403
            abort(429, message=f"New submission denied: you have an unfinished submission for this project.")
            abort(429, message=f"New submission for user {user.username} denied: a submission for this user "
                               f"is already being processed.")

        json_data = request.get_json()
        if not json_data:
            abort(400, message='No data provided for project configuration update.')
            abort(400, message='No data provided for submission creation.')
        try:
            data = config_schema.load(json_data)[0]
        except ValidationError as err:
@@ -178,21 +187,20 @@ class ProjectSubmissions(Resource):
                                    review=data['review'])
        write_entity(new_submission)
        # download relevant files into Storage
        # TODO
        # TODO storage

        return submission_schema.dump(new_submission)


class ProjectTestFiles(Resource):
    def get(self, cid, pid):
        # Gets test files (feature)
        return "Not implemented."


projects_api.add_resource(ProjectResource, '/<string:pid>')
projects_api.add_resource(ProjectsList, '/')
projects_api.add_resource(ProjectConfigResource, '/<string:pid>/config')
projects_api.add_resource(ProjectSubmissions, '/<string:pid>/submissions')
'''
### Project test files
 * `[GET]     /courses/{cid/projects/{id}/files` - Gets test files (feature)

### Project submissions
 * `[GET]     /courses/{cid}/projects/{id}/submissions` - Read all submissions for project
    * `user=<user_selector>` - filters submissions by user
 * `[POST]     /courses/{cid}/projects/{id}/submissions` - Create submission'''
projects_api.add_resource(ProjectTestFiles, '/<string:pid>/files')
+45 −28
Original line number Diff line number Diff line
@@ -22,20 +22,20 @@ class RoleResource(Resource):
        log.info(f"GET to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not find role: course with identifier {cid} not found.")
            abort(404, message=f"Could not find role {rid} in course {cid}: course not found.")
        role = find_role(course, rid)
        if not role:
            abort(404, message=f"Could not find role {rid} in course {cid}.")
            abort(404, message=f"Could not find role {rid} in course {cid}: role not found.")
        return role_schema.dump(role)

    def delete(self, cid, rid):
        log.info(f"DELETE to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not delete role: course with identifier {cid} not found.")
            abort(404, message=f"Could not delete role {rid} in course {cid}: course not found.")
        role = find_role(course, rid)
        if not role:
            abort(404, message=f"Could not delete role {rid} in course {cid}.")
            abort(404, message=f"Could not delete role {rid} in course {cid}: role not found.")
        delete_entity(role)
        return '', 204

@@ -43,10 +43,10 @@ class RoleResource(Resource):
        log.info(f"PUT to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not update role: course with identifier {cid} not found.")
            abort(404, message=f"Could not update role {rid} in course {cid}: course not found.")
        role = find_role(course, rid)
        if not role:
            abort(404, message=f"Could not update role {rid} in course {cid}.")
            abort(404, message=f"Could not update role {rid} in course {cid}: role not found.")

        json_data = request.get_json()
        if not json_data:
@@ -61,7 +61,7 @@ class RoleResource(Resource):
        role.name = data['name']
        # updating course?
        write_entity(role)
        log.debug(f"Role update successful. Role={role}")
        log.debug(f"Updated role {rid} to {role}.")
        return '', 204


@@ -70,14 +70,14 @@ class RoleList(Resource):
        log.info(f"GET to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not list roles: course with identifier {cid} not found.")
            abort(404, message=f"Could not list roles in course {cid}: course not found.")
        return roles_schema.dump(course.roles)

    def post(self, cid):
        log.info(f"POST to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not create role: Course with identifier {cid} not found.")
            abort(404, message=f"Could not create role in course {cid}: course not found.")
        json_data = request.get_json()
        if not json_data:
            abort(400, message='No data provided for role creation.')
@@ -90,7 +90,7 @@ class RoleList(Resource):
        new_role = Role(course=course, name=data['name'])
        new_role.description = data.get('description') or ""
        write_entity(new_role)
        log.debug(f"Created role={new_role} for course {course.codename}")
        log.debug(f"Created role={new_role} for course {cid}")
        return role_schema.dump(new_role)[0], 201


@@ -99,24 +99,24 @@ class RolePermissions(Resource):
        log.info(f"GET to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not get role permissions: course with identifier {cid} not found.")
            abort(404, message=f"Could not list permissions for role {rid} in course {cid}: course not found.")
        role = find_role(course, rid)
        if not role:
            abort(404, message=f"Could not update permissions for role{rid} in course {cid}.")
            abort(404, message=f"Could not list permissions for role {rid} in course {cid}: role not found.")
        return roles_schema.dump(role.permissions)

    def put(self, cid, rid):
        log.info(f"PUT to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not update role: course with identifier {cid} not found.")
            abort(404, message=f"Could not update permissions for role {rid} in course {cid}: course not found.")
        role = find_role(course, rid)
        if not role:
            abort(404, message=f"Could not update role {rid} in course {cid}.")
            abort(404, message=f"Could not update permissions for role {rid} in course {cid}: role not found.")

        json_data = request.get_json()
        if not json_data:
            abort(400, message='No data provided for role update.')
            abort(400, message='No data provided for update of role permissions.')
        try:
            data = role_schema.load(json_data)[0]
        except ValidationError as err:
@@ -124,7 +124,7 @@ class RolePermissions(Resource):
            return abort(422, errors=err.messages)  # TODO: flash, process errors

        role.set_permissions(data)
        log.debug(f"Updated permissions for role {rid} in course {cid}.")
        log.debug(f"Updated permissions for role {rid} in course {cid} to {data}.")
        return '', 204


@@ -133,10 +133,10 @@ class RoleUsersList(Resource):
        log.info(f"GET to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not update role: course with identifier {cid} not found.")
            abort(404, message=f"Could not list users with role {rid} in course {cid}: course not found.")
        role = find_role(course, rid)
        if not role:
            abort(404, message=f"Could not update role {rid} in course {cid}.")
            abort(404, message=f"Could not list users with role {rid} in course {cid}: role not found.")

        return users_schema.dump(role.users)

@@ -144,44 +144,61 @@ class RoleUsersList(Resource):
        log.info(f"PUT to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not update role: course with identifier {cid} not found.")
            abort(404, message=f"Could not update users with role {rid} in course {cid}: course not found.")
        role = find_role(course, rid)
        if not role:
            abort(404, message=f"Could not update role {rid} in course {cid}.")
            abort(404, message=f"Could not update users with role {rid} in course {cid}: role not found")

        json_data = request.get_json()
        try:
            # TODO full entities?, check duplicates
            data = users_schema.load(json_data)[0]  # presumes the json contains full entities; change to ids?
        except ValidationError as err:
            log.info(f"Validation failed on: {err.messages}")
            return abort(422, errors=err.messages)  # TODO: flash, process errors
        role.users = data
        log.info(f"Updated users of role {rid} to {data}")
        log.info(f"Updated users of role {rid} to {data}.")
        return '', 204


class RoleUsers(Resource):
    def put(self, cid, rid, uid):
        log.info(f"PUT to {request.url}")
        pass
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not add user {uid} to role {rid} in course {cid}: course not found.")
        role = find_role(course, rid)
        if not role:
            abort(404, message=f"Could not add user {uid} to role {rid} in course {cid}: role not found.")
        user = find_user(uid)
        if not user:
            abort(404, message=f"Could not add user {uid} to role {rid} in course {cid}: user not found.")

        if user not in role.users:
            role.users.append(user)
            log.info(f"Added user {uid} to role {rid} in course {cid}.")
        else:
            log.info(f"User {uid} is already in role {rid} in course {cid}: no change.")
        return '', 204

    def delete(self, cid, rid, uid):
        log.info(f"DELETE to {request.url}")
        course = find_course(cid)
        if not course:
            abort(404, message=f"Could not delete user {uid} from role: course with identifier {cid} not found.")
            abort(404, message=f"Could not remove user {uid} from role {rid} in course {cid}: course not found.")
        role = find_role(course, rid)
        if not role:
            abort(404, message=f"Could not delete user {uid} from role: role {rid} not found in course {cid}.")
            abort(404, message=f"Could not remove user {uid} from role {rid} in course {cid}: role not found.")
        user = find_user(uid)
        if not user:
            abort(404, message=f"Could not delete user {uid} from role{rid} in course {cid}: user not found.")
            abort(404, message=f"Could not remove user {uid} from role {rid} in course {cid}: user not found.")

        try:
            role.users.remove(user)
        except ValueError as err:  # the user was not in the role
            log.info(f"Did not remove user {user} from role {rid} of course {cid}: user was not in the role. ")

        except ValueError:
            abort(400, message=f"Could not remove user {uid} from role {rid} in course {cid}: "
                               f"role does not contain user.")
        log.info(f"Removed user {uid} from role {rid} in course {cid}.")
        return '', 204


+13 −14
Original line number Diff line number Diff line
@@ -2,11 +2,11 @@ from flask import Blueprint, request
from flask_restful import Api, Resource, abort
from marshmallow import ValidationError

from portal.rest import UserSchema, delete_entity, write_entity
from portal.rest import UserSchema, delete_entity, write_entity, find_user
from portal.database.models import User
from portal.tools.logging import log

users = Blueprint('users', __name__, url_prefix='/users/')
users = Blueprint('users', __name__, url_prefix='/users')
users_api = Api(users)

user_schema = UserSchema()
@@ -16,21 +16,20 @@ users_schema = UserSchema(many=True, only=('id', 'username', 'uco', 'email'))
class UserResource(Resource):
    def get(self, uid):
        log.info(f"GET to {request.url}")
        user = User.query.filter_by(id=uid).first()
        user = find_user(uid)
        if not user:
            abort(404, message=f"User with id={uid} doesn't exist.")
            abort(404, message=f"Could not find user {uid}.")
        return user_schema.dump(user)

    def put(self, uid):
        log.info(f"PUT to {request.url}")
        user = User.query.filter_by(id=uid).first()
        user = find_user(uid)
        if not user:
            log.debug(f"Did not find course with id={uid}. Aborting request processing.")
            abort(404, message="User with id={} not found.".format(uid))
            abort(404, message=f"Could not find user {uid}.")

        json_data = request.get_json()
        if not json_data:
            abort(400, message='No data provided for course update.')
            abort(400, message='No data provided for user update.')
        try:
            data = user_schema.load(json_data)
        except ValidationError as err:
@@ -44,14 +43,14 @@ class UserResource(Resource):
        user.is_admin = data['is_admin']
        # TODO all; care for password
        write_entity(user)
        log.debug(f"User update successful. User={user}")
        log.debug(f"Updated user {uid} to {user}.")
        return '', 204

    def delete(self, uid):
        log.info(f"DELETE to {request.url}")
        user = User.query.filter_by(id=uid).first()
        user = find_user(uid)
        if not user:
            abort(404, message=f"User with id={uid} doesn't exist.")
            abort(404, message=f"Could not find user {uid}.")
        delete_entity(user)
        return '', 204

@@ -66,7 +65,7 @@ class UserList(Resource):
        log.info(f"POST to {request.url}")
        json_data = request.get_json()
        if not json_data:
            abort(400, message='No data provided for user update.')
            abort(400, message='No data provided for user creation.')
        try:
            data = user_schema.load(json_data)[0]
        except ValidationError as err:
@@ -76,10 +75,10 @@ class UserList(Resource):
        admin = data.get('is_admin') or False
        new_user = User(uco=data['uco'], email=data['email'], username=data['username'], is_admin=admin)
        write_entity(new_user)
        log.debug(f"Created User={User}")
        log.debug(f"Created user={User}")
        return user_schema.dump(new_user)[0], 201
    

users_api.add_resource(UserResource, '/<string:id>')
users_api.add_resource(UserResource, '/<string:uid>')
users_api.add_resource(UserList, '/')