Verified Commit 14caef34 authored by Peter Stanko's avatar Peter Stanko
Browse files

Facades done

parent cf43b7da
......@@ -13,7 +13,7 @@ from management.data.data_test import init_test_data
from portal import logger
from portal.database.models import Course, Role, Secret, Submission, SubmissionState, User
from portal.service.find import FindService
from portal.service.rest import RestService
from portal.service.services_collection import ServicesCollection
from portal.service.users import UserService
from portal.tools import time
......@@ -30,7 +30,7 @@ class DataManagement(object):
self.app = app
self.db = db
self.creator = shared.DataFactory(self.db)
self.rest = RestService()
self.rest = ServicesCollection()
@property
def find(self):
......
from portal.database import Submission, SubmissionState
from portal.service.rest import RestService
from portal.service.services_collection import ServicesCollection
from portal.tools import time
class AsyncManager:
def __init__(self):
self._rest = RestService()
self._rest = ServicesCollection()
@property
def rest(self) -> RestService:
def rest(self) -> ServicesCollection:
return self._rest
def abort_submissions_sched_exceeded(self, limit):
......
......@@ -28,13 +28,13 @@ def abort_non_proc_submissions(**kwargs):
@celery_app.task
def delete_cancelled_submissions():
from portal.service.rest import RestService
subm_service = RestService().submissions
from portal.service.services_collection import ServicesCollection
subm_service = ServicesCollection().submissions
subm_service.delete_cancelled_submissions()
@celery_app.task
def archive_submissions_in_arch_project():
from portal.service.rest import RestService
subm_service = RestService().submissions
from portal.service.services_collection import ServicesCollection
subm_service = ServicesCollection().submissions
subm_service.archive_submissions()
......@@ -17,8 +17,8 @@ class SubmissionProcessor:
def __init__(self, submission: Submission, params: dict = None):
self._submission = submission
self._params = params
from portal.service.rest import RestService
self._rest = RestService()
from portal.service.services_collection import ServicesCollection
self._rest = ServicesCollection()
@property
def submission(self) -> Submission:
......
from .courses_facade import CoursesFacade
from .groups_facade import GroupsFacade
from .projects_facade import ProjectsFacade
from .reviews_facade import ReviewsFacade
from .roles_facade import RolesFacade
from .users_facade import UsersFacade
from .secrets_facade import SecretsFacade
from .submissions_facade import SubmissionsFacade
from .users_facade import UsersFacade
from .workers_facade import WorkersFacade
class SecretsFacade(object):
pass
......@@ -8,7 +8,7 @@ from portal.service import errors
from portal.service.auth import AuthService
from portal.service.find import FindService
from portal.service.general import GeneralService
from portal.service.permissions import PermissionsService
from portal.service.services_collection import ServicesCollection
log = logging.getLogger(__name__)
......@@ -21,11 +21,13 @@ class GeneralFacade:
entity_name: Entity name - for logging purposes
"""
service_klass = main_service or GeneralService
self._service = service_klass
self._permissions = PermissionsService()
self._auth = AuthService()
self._find = FindService()
self._service = service_klass()
self._entity_name = entity_name
self._services = ServicesCollection()
@property
def services(self) -> ServicesCollection:
return self._services
@property
def client(self) -> models.Client:
......@@ -47,15 +49,20 @@ class GeneralFacade:
@property
def permissions(self):
return self._permissions
return self.services.permissions
@property
def auth(self) -> AuthService:
return self._auth
return self.services.auth
@property
def find(self) -> FindService:
return self._find
return self.services.find
@property
def storage(self):
from portal import storage
return storage
def _require_params(self, dictionary, *params):
for param in params:
......
import logging
from typing import List
from portal.database import Course, Group, Project, User
from portal.facade.general_facade import GeneralCRUDFacade
from portal.service.groups import GroupService
......@@ -10,3 +10,39 @@ log = logging.getLogger(__name__)
class GroupsFacade(GeneralCRUDFacade):
def __init__(self):
super().__init__(GroupService, 'Group')
def find_users_by_role(self, group: Group, role_id: str):
return self._service(group).find_users_by_role(role_id=role_id)
def update_membership(self, group: Group, **data):
log.info(f"[UPDATE] Group membership {group.log_name} "
f" by {self.client_name}: {data}")
return self._service(group).update_membership(**data)
def add_user(self, group: Group, user: User):
log.info(f"[ADD] User to group {group.log_name} "
f"by {self.client_name}: {user.log_name}")
return self._service(group).add_user(user)
def remove_user(self, group: Group, user: User):
log.info(f"[REMOVE] User from group {group.log_name} "
f"by {self.client_name}: {user.log_name}")
return self._service(group).remove_user(user)
def find_projects(self, group: Group):
return self._service(group).find_projects()
def add_project(self, group: Group, project: Project):
log.info(f"[ADD] Project {project.log_name} to group"
f" {group.log_name} by {self.client_name}")
return self._service(group).add_project(project)
def remove_project(self, group: Group, project: Project):
log.info(f"[REMOVE] Project {project.log_name} from group"
f" {group.log_name} by {self.client_name}")
return self._service(group).remove_project(project)
def import_groups(self, target_course: Course, **data):
log.info(f"[IMPORT] Group to course {target_course.log_name}"
f" by {self.client_name}: {data}")
return self._service.import_group(course=target_course, data=data)
import logging
from portal.database import Project
from portal.database.models import Client, Course
from portal.facade.general_facade import GeneralCRUDFacade
from portal.service.projects import ProjectService
log = logging.getLogger(__name__)
class ProjectsFacade(GeneralCRUDFacade):
def __init__(self):
super().__init__(ProjectService, 'Project')
def update_project_config(self, project: Project, **params):
"""Updates the project configuration for the project
Args:
project: Project Instance
**params:
"""
log.info(f"[UPDATE] Configuration for project {project.log_name}"
f" by {self.client_name}: {params}.")
result = self._service(project).update_project_config(params)
return result
def update_project_test_files(self, project: Project):
"""Update the project test files
Downloads the new version from the gitlab
Args:
project: project instance
"""
# TODO invoke the async task
result = self._service(project).update_project_test_files()
return result
def find_project_submissions(self, project: Project, user_id: str):
"""Finds all project submissions. If a user id is specified, returns only submissions created
by that user.
Args:
project(Project): Project
user_id(str): User id (optional)
Returns(List[Submission]): A list of all submissions in a project if no user_id is specified
Otherwise a list of submissions created in the project by the specified user.
"""
result = self._service(project).find_project_submissions(user_id)
return result
def check_submission_create(self, project: Project, client: Client):
"""Check whether client can create a submission
Args:
project: project instance
client: client instance
"""
client = client or self.client
result = self._service(project).check_submission_create(client)
return result
def get_test_files(self, project: Project):
"""Gets the test files for the project
Args:
project: project instance
"""
log.debug(f"Getting test files for the project: {project.log_name}"
f" by {self.client.log_name}")
service = self.services.storage(project=project)
storage_entity = service.get_test_files_entity_from_storage()
path = self._request.args.get('path')
return service.send_file_or_zip(storage_entity=storage_entity, path=path)
def copy_project(self, project: Project, target_course: Course):
"""Copies the project
Args:
project(Project): Project instance
target_course(Course): Course instance
"""
log.info(f"[COPY] Project from {project.log_name} to {target_course.log_name} by"
f"{self.client_name}")
result = self._service(project).copy_project(target_course)
return result
import logging
from portal.facade.general_facade import GeneralCRUDFacade
from portal.service.reviews import ReviewService
log = logging.getLogger(__name__)
class ReviewsFacade(GeneralCRUDFacade):
def __init__(self):
super().__init__(ReviewService, 'Review')
def create(self, submission, **data):
if not submission.review:
self._service(submission=submission).create()
params = dict(items=data['review_items'], author=self.client)
result = self._service(submission=submission).create_review_items(**params)
return result
import logging
from portal.database import Course, Role
from portal.database.models import Client
from portal.facade.general_facade import GeneralCRUDFacade
from portal.service.roles import RoleService
......@@ -9,3 +11,65 @@ log = logging.getLogger(__name__)
class RolesFacade(GeneralCRUDFacade):
def __init__(self):
super().__init__(RoleService, 'Role')
def update_permissions(self, role: Role, **params):
"""Updates role permissions
Args:
role: Role instance
params: Permissions dictionary
Returns(RolePermissions): Role Permissions instance
"""
log.info(f"[UPDATE] Permissions for role {role.log_name} "
f"by {self.client_name}: {params}.")
result = self._service(role).update_permissions(params)
return result
def update_clients_membership(self, role: Role, **data):
"""Updates client membership in the role
Args:
role: Role instance
data(dict): Data provided to update role membership
Returns(Role): Updated role
"""
log.info(f"[UPDATE] Membership for role {role.log_name} "
f"by {self.client_name}: {data}.")
result = self._service(role).update_clients_membership(data)
return result
def add_client(self, role: Role, client: Client):
"""Adds single client to the role
Args:
role: Role instance
client(Client): Client instance
Returns:
"""
log.info(f"[REMOVE] Client {client.log_name} to role {role.log_name} by "
f"{self.client.log_name}.")
result = self._service(role).add_client(client)
return result
def remove_client(self, role: Role, client: Client):
"""Removes single client from the role
Args:
role: Role instance
client(Client): Client instance
Returns(Role): Updated role
"""
log.info(f"[REMOVE] Client {client.log_name} from role {role.log_name} by "
f"{self.client.log_name}.")
result = self._service(role).remove_client(client)
return result
def copy_role(self, source: Role, target_course: Course, **params):
"""Copies role to the target course
Args:
source: Source role
target_course(Course): Target Course resource
Returns(Role): Copied role
"""
log.info(f"[COPY] Role {source.log_name} to course {target_course} by "
f"{self.client_name}: {params}")
return self._service(source).copy_role(target_course, params)
import logging
from typing import List
from portal.database import Submission, SubmissionState, User
from portal.facade.general_facade import GeneralCRUDFacade
from portal.logger import SUBMIT
from portal.service import errors
from portal.service.submissions import SubmissionsService
from portal.service.users import UserService
log = logging.getLogger(__name__)
......@@ -11,3 +12,98 @@ log = logging.getLogger(__name__)
class SubmissionsFacade(GeneralCRUDFacade):
def __init__(self):
super().__init__(SubmissionsService, 'Submission')
def create(self, project, user, **data):
from portal.tools import request_helpers
SUBMIT.info(f'[SUBMIT] Submission for project: "{project.log_name}", '
f'user: "{user.log_name}", created: "{self.client_name}", '
f'params: {data}, UA: {self._request.user_agent} '
f'({request_helpers.get_ip()})')
user = user or self.client
return super(SubmissionsFacade, self).create(user=user, project=project, **data)
def get_source_files(self, submission: Submission):
service = self.services.storage(submission=submission)
path = self._request.args.get('path')
return service.send_file_or_zip(path=path)
def get_result_files(self, submission: Submission):
service = self.services.storage(submission)
storage_entity = self.storage.results.get(submission.id)
path = self._request.args.get('path')
return service.send_file_or_zip(path=path, storage_entity=storage_entity)
def get_test_files(self, submission: Submission):
service = self.services.storage(submission)
path = self._request.args.get('path')
storage_entity = service.get_test_files_entity_from_storage()
return service.send_file_or_zip(storage_entity=storage_entity, path=path)
def result_files_tree(self, submission: Submission):
storage_entity = self.storage.results.get(submission.id)
service = self.services.storage(submission)
return service.send_files_tree(storage_entity)
def test_files_tree(self, submission: Submission):
service = self.services.storage(project=submission.project)
storage_entity = service.get_test_files_entity_from_storage()
return service.send_files_tree(storage_entity)
def update_submission_state(self, submission: Submission, **data):
log.info(f"[UPDATE] Submission state {submission.log_name} "
f"by {self.client_name}: {data}")
new_state = data.get('state')
self.normal_user_can_only_cancel(new_state)
return self._service(submission).update_submission_state(**data)
def normal_user_can_only_cancel(self, new_state):
if isinstance(self.client_name, User) and \
new_state != SubmissionState.CANCELLED:
raise errors.ForbiddenError(self.client,
note=f"User {self.client.log_name} cannot update "
f"state to other than CANCELLED.")
def get_stats(self, submission: Submission):
return self._service(submission).get_statsI()
def send_sources_tree(self, submission: Submission):
service = self.services.storage(submission)
return service.send_files_tree()
def upload_results_to_storage(self, submission: Submission):
log.info(f"[UPLOAD] Uploading results to storage for "
f"{submission.log_name} by {self.client_name}")
file = self._request.files['file']
return self._service(submission).upload_results_to_storage(file)
def copy_submission(self, source_submission: Submission, **params):
"""Copies a submission. Used at resubmitting
Args:
source_submission: Source submission instance
note(str): Note for the submission. Typically contains a reason for the resubmit.
Returns(Submission): Copied submission
"""
log.info(f"[COPY] Submission {source_submission.log_name} to "
f"same project {source_submission.project.log_name} by {self.client_name}")
return self._service(source_submission).copy_submission(**params)
def resend_submission(self, submission: Submission):
""" Resends the submission to the worker
"""
# TODO: ASYNC TASK
log.info(f"[SUBMIT] Resending submission task for {submission.log_name}")
return self._service(submission).resend_submission()
def cancel_submission(self, submission):
log.info(f"[CANCEL] Cancelling the submission {submission.log_name} "
f"by {self.client_name}")
return self._service(submission).cancel_submission()
def find_all(self, *args, **kwargs):
role_ids = self._request.args.get('roles')
group_ids = self._request.args.get('groups')
return super(SubmissionsFacade, self).find_all(*args, **kwargs,
role_ids=role_ids, group_ids=group_ids)
import logging
from typing import List
from portal.database import Group, Review, Role, Submission, User, Project
from portal.database import Group, Project, Review, Role, Submission, User
from portal.facade.general_facade import GeneralCRUDFacade
from portal.service.users import UserService
......@@ -12,6 +12,11 @@ class UsersFacade(GeneralCRUDFacade):
def __init__(self):
super().__init__(UserService, 'User')
def create(self, **data):
new_user = super(UsersFacade, self).create(**data)
self.services.emails.notify_user(new_user, 'user_created', context=data)
return new_user
def update(self, user: User, **data):
"""Update the user params, but it is required to check whether the user is admin
if the user is not admin, the is_admin param will be removed
......@@ -26,7 +31,7 @@ class UsersFacade(GeneralCRUDFacade):
log.info(f"[UPDATE] User {user.log_name} by {self.client_name}: {data}")
if not self.client.is_admin:
params.pop('is_admin', None)
updated = self._service(user).update(params)
updated = self._service(user).update(**params)
return updated
def update_password(self, user: User, **data):
......@@ -46,6 +51,8 @@ class UsersFacade(GeneralCRUDFacade):
self._require_params(data, 'old_password')
self._service.check_old_password()
result = self._service(user).update_password(data)
self.services.emails.notify_user(user, 'user_passwd_update',
context=dict(username=user.username))
return result
def find_users(self, ids: List[str]) -> List[User]:
......
import logging
from portal.database import Worker
from portal.facade.general_facade import GeneralCRUDFacade
from portal.service.workers import WorkerService
......@@ -9,3 +10,17 @@ log = logging.getLogger(__name__)
class WorkersFacade(GeneralCRUDFacade):
def __init__(self):
super().__init__(WorkerService, 'Worker')
def get_worker_status(self, worker: Worker):
"""Gets current worker status
Args:
worker: Worker instance
"""
return self.services.workers(worker).worker_client.status()
def list_images_available(self, worker: Worker):
"""List all images available on the worker
Args:
worker: Worker instance
"""
return self.services.workers(worker).list_images()
......@@ -47,7 +47,7 @@ class ClientSecretsController(CustomResource):
@clients_namespace.response(200, 'List client secrets')
def get(self, cid: str):
self.permissions.require.sysadmin_or_self(cid)
client = self.rest.find.client(cid)
client = self.find.client(cid)
log.debug(f"[REST] Get client secrets {client.log_name} by {self.client.log_name}")
return SCHEMAS.dump('secrets', client.secrets)
......@@ -60,7 +60,7 @@ class ClientSecretsController(CustomResource):
client = self.find.client(cid)
log.info(f"[REST] Create new secret by {self.client.log_name} "
f"for {client.log_name}: {data} ")
new_secret, value = self.rest.secrets(client=client).create(**data)
new_secret, value = self.facades.secrets.create(client=client, **data)
return {'id': new_secret.id, 'value': value}, 201
......@@ -72,22 +72,22 @@ class ClientSecretController(CustomResource):
@jwt_required
@clients_namespace.response(200, 'Client secret detail')
def get(self, cid: str, sid: str):
self.rest.permissions.require.sysadmin_or_self(cid)
client = self.rest.find.client(cid)
self.permissions.require.sysadmin_or_self(cid)
client = self.find.client(cid)
log.debug(f"[REST] Get secret for {client.log_name} by {self.client.log_name}: {sid}")
secret = self.rest.find.secret(client, sid)
secret = self.find.secret(client, sid)
return SCHEMAS.dump('secret', secret)
@jwt_required
@access_log
@clients_namespace.response(204, 'Client secret deleted')
def delete(self, cid: str, sid: str):
self.rest.permissions.require.sysadmin_or_self(cid)
self.permissions.require.sysadmin_or_self(cid)
client = self.find.client(cid)
secret = self.find.secret(client, sid)
log.info(f"[REST] Delete a secret by {self.client.log_name}: {secret.log_name} ")
self.rest.secrets(secret).delete()
self.facades.secrets.delete(secret)
return '', 204
@jwt_required
......@@ -95,12 +95,12 @@ class ClientSecretController(CustomResource):
@clients_namespace.response(204, 'Client secret updated')
@clients_namespace.response(403, 'Not allowed to update client secret')
def put(self, cid: str, sid: str):
self.rest.permissions.require.sysadmin_or_self(cid)
self.permissions.require.sysadmin_or_self(cid)
data = rest_helpers.parse_request_data(action='update', resource='secret', partial=True)
client = self.find.client(cid)
secret = self.find.secret(client, sid)