Loading docs/prirucka.adoc +0 −5 Original line number Diff line number Diff line Loading @@ -364,11 +364,6 @@ sú maximálnou hodnotou povolení jeho rolí. * Komponentom sa prístupové práva nekontrolujú. === Zoznam povolení * archive_projects: ** povoľuje archiváciu projektov v kurze ** typicky vhodná pre vlastníka kurzu ** zatiaľ nevyužitá hodnota (archivácia projektov nie je implementovaná) * create_submissions: ** povoľuje vytváranie odovzdaní pod vlastnou identitou s ohľadom na časové obmedzenia projektov (submissions allowed from-to) ** typické povolenie pre študenta Loading management/data/shared.py +0 −2 Original line number Diff line number Diff line Loading @@ -44,7 +44,6 @@ PERM_TEACHER = dict( handle_notes_access_token=False, write_groups=True, write_projects=True, archive_projects=True, create_submissions_other=True, create_submissions=True, resubmit_submissions=True, Loading @@ -68,7 +67,6 @@ PERM_OWNER = dict( write_roles=True, write_groups=True, write_projects=True, archive_projects=True, resubmit_submissions=True, evaluate_submissions=True, read_submissions_all=True, Loading portal/database/models.py +12 −2 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ from flask_sqlalchemy import BaseQuery from sqlalchemy import event from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.sql.base import ImmutableColumnCollection from werkzeug.security import check_password_hash, generate_password_hash from portal import db Loading Loading @@ -93,7 +94,7 @@ class Client(db.Model): Returns: Query for the permissions """ from . import queries return queries.client_permissions_for_course(self, course) return queries.client_roles_in_course(self, course) def get_permissions_for_course(self, course: 'Course') -> List['Role']: """Gets list of permissions for the course Loading Loading @@ -698,6 +699,15 @@ class Role(db.Model, EntityBase, NamedMixin): 'course.id', ondelete='cascade'), nullable=False) course = db.relationship("Course", back_populates="roles", uselist=False) PERMISSIONS = ('create_submissions', 'create_submissions_other', 'view_course_limited', 'view_course_full', 'update_course', 'handle_notes_access_token', 'write_roles', 'write_groups', 'write_projects', 'resubmit_submissions', 'evaluate_submissions', 'read_submissions_all', 'read_submissions_groups', 'read_submissions_own', 'read_reviews_all', 'read_reviews_groups', 'read_reviews_own', 'write_reviews_all', 'write_reviews_group', 'write_reviews_own') # all default to False, explicit setting via role.set_permissions is # required view_course_limited = db.Column(db.Boolean, default=False, nullable=False) Loading @@ -709,7 +719,6 @@ class Role(db.Model, EntityBase, NamedMixin): write_roles = db.Column(db.Boolean, default=False, nullable=False) write_groups = db.Column(db.Boolean, default=False, nullable=False) write_projects = db.Column(db.Boolean, default=False, nullable=False) archive_projects = db.Column(db.Boolean, default=False, nullable=False) create_submissions = db.Column(db.Boolean, default=False, nullable=False) create_submissions_other = db.Column(db.Boolean, default=False, nullable=False) Loading Loading @@ -876,6 +885,7 @@ class Submission(db.Model, EntityBase): points = db.Column(db.Numeric(precision=10, scale=4), default=0.0) result = db.Column(db.String(length=10), default='none') course = association_proxy('project', 'course') course_id = association_proxy('project', 'course_id') ALLOWED_TRANSITIONS = { SubmissionState.CANCELLED: [SubmissionState.READY, SubmissionState.CREATED, Loading portal/database/queries.py +82 −5 Original line number Diff line number Diff line import datetime import logging from flask_sqlalchemy import BaseQuery from sqlalchemy import func from portal import db from portal.database.models import Client, ClientType, Course, Group, Project, Role, Submission, \ SubmissionState, User, Worker log = logging.getLogger(__name__) def _get_class_based_on_client_type(client_type): klass = Client Loading @@ -13,11 +18,6 @@ def _get_class_based_on_client_type(client_type): return klass def client_permissions_for_course(client, course: Course = None) -> BaseQuery: return Role.query.filter_by(course=course) \ .join(Role.clients).filter(Client.id == client.id) def client_courses(client) -> BaseQuery: return Course.query.join(Course.roles).join(Role.clients).filter(Client.id == client.id) Loading @@ -43,6 +43,7 @@ def user_projects(user: User, course: Course = None) -> BaseQuery: def user_projects_by_course(user, course: 'Course') -> BaseQuery: # TODO: To query groups = user_groups_in_course(user, course=course).all() pids = [] for group in groups: Loading Loading @@ -139,3 +140,79 @@ def cancelled_submissions_for_deletion(time_period: datetime.timedelta) -> BaseQ return Submission.query.filter( Submission.state.in_(SubmissionState.CANCELLED, SubmissionState.ABORTED) & Submission.created_at < delete_from) def effective_permissions_for_course(client: Client, course: Course) -> BaseQuery: def _m(name): return func.max(getattr(Role, name)).label(name) props = [_m(prop) for prop in Role.PERMISSIONS] result = db.session.query(*props).filter(Role.course == course).join( Role.clients).filter(Client.id == client.id) return result def effective_permissions_for_client(client: Client) -> BaseQuery: def _m(name): return func.max(getattr(Role, name)).label(name) props = [_m(prop) for prop in Role.PERMISSIONS] query = db.session.query(Role.course_id, *props)\ .filter(Role.clients.contains(client))\ .group_by(Role.course_id) return query def list_submissions_for_user(client: User, user_ids=None, course_id=None, role_ids=None, group_ids=None, project_ids=None, states=None, archived=False) -> BaseQuery: query: BaseQuery = Submission.query if user_ids: user_filter = (User.id.in_(user_ids)) | (User.codename.in_(user_ids)) query = query.join(Submission.user).filter(user_filter) if not archived: query = query.filter(Submission.state != SubmissionState.ARCHIVED) if states: query = query.filter(Submission.state.in_(states)) if course_id: query = _filter_by_course_groups_roles(query, course_id, group_ids, project_ids, role_ids) if not client.is_admin: query = _filter_by_client(query, client) log.debug(f"[QUERY] find submissions: {query}") return query def _filter_by_course_groups_roles(query, course_id, group_ids, project_ids, role_ids): course_filter = (Course.id == course_id) | (Course.codename == course_id) query = query.join(Submission.project).join(Project.course).filter(course_filter) if project_ids: project_filter = (Project.id.in_(project_ids) | (Project.codename.in_(project_ids))) query = query.filter(project_filter) if role_ids: role_filter = (Role.id.in_(role_ids) | (Role.codename.in_(role_ids))) query = query.join(Course.roles).filter(role_filter) if group_ids: group_filter = (Group.id.in_(group_ids) | (Group.codename.in_(group_ids))) query = query.join(Course.groups).filter(group_filter) return query def _filter_by_client(query: BaseQuery, client: User) -> BaseQuery: eff_client = effective_permissions_for_client(client).subquery() check_course_id = eff_client.c.course_id == Project.course_id from sqlalchemy import true check_read_all = (check_course_id & (eff_client.c.read_submissions_all == true())) check_read_own = (check_course_id & (eff_client.c.read_submissions_own == true()) & (Submission.user_id == client.id)) query = query.join(Submission.project)\ .join(eff_client, eff_client.c.course_id == Project.course_id)\ .filter(check_read_all | check_read_own) return query portal/facade/submissions_facade.py +9 −3 Original line number Diff line number Diff line Loading @@ -103,7 +103,13 @@ class SubmissionsFacade(GeneralCRUDFacade): 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 = self._request.args.getlist('roles') group_ids = self._request.args.getlist('groups') project_ids = self._request.args.getlist('projects') states = self._request.args.getlist('states') user_ids = self._request.args.getlist('users') course = self._request.args.get('course') return super(SubmissionsFacade, self).find_all(*args, **kwargs, client=self.client, course_id=course, states=states, user_ids=user_ids, project_ids=project_ids, role_ids=role_ids, group_ids=group_ids) Loading
docs/prirucka.adoc +0 −5 Original line number Diff line number Diff line Loading @@ -364,11 +364,6 @@ sú maximálnou hodnotou povolení jeho rolí. * Komponentom sa prístupové práva nekontrolujú. === Zoznam povolení * archive_projects: ** povoľuje archiváciu projektov v kurze ** typicky vhodná pre vlastníka kurzu ** zatiaľ nevyužitá hodnota (archivácia projektov nie je implementovaná) * create_submissions: ** povoľuje vytváranie odovzdaní pod vlastnou identitou s ohľadom na časové obmedzenia projektov (submissions allowed from-to) ** typické povolenie pre študenta Loading
management/data/shared.py +0 −2 Original line number Diff line number Diff line Loading @@ -44,7 +44,6 @@ PERM_TEACHER = dict( handle_notes_access_token=False, write_groups=True, write_projects=True, archive_projects=True, create_submissions_other=True, create_submissions=True, resubmit_submissions=True, Loading @@ -68,7 +67,6 @@ PERM_OWNER = dict( write_roles=True, write_groups=True, write_projects=True, archive_projects=True, resubmit_submissions=True, evaluate_submissions=True, read_submissions_all=True, Loading
portal/database/models.py +12 −2 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ from flask_sqlalchemy import BaseQuery from sqlalchemy import event from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.sql.base import ImmutableColumnCollection from werkzeug.security import check_password_hash, generate_password_hash from portal import db Loading Loading @@ -93,7 +94,7 @@ class Client(db.Model): Returns: Query for the permissions """ from . import queries return queries.client_permissions_for_course(self, course) return queries.client_roles_in_course(self, course) def get_permissions_for_course(self, course: 'Course') -> List['Role']: """Gets list of permissions for the course Loading Loading @@ -698,6 +699,15 @@ class Role(db.Model, EntityBase, NamedMixin): 'course.id', ondelete='cascade'), nullable=False) course = db.relationship("Course", back_populates="roles", uselist=False) PERMISSIONS = ('create_submissions', 'create_submissions_other', 'view_course_limited', 'view_course_full', 'update_course', 'handle_notes_access_token', 'write_roles', 'write_groups', 'write_projects', 'resubmit_submissions', 'evaluate_submissions', 'read_submissions_all', 'read_submissions_groups', 'read_submissions_own', 'read_reviews_all', 'read_reviews_groups', 'read_reviews_own', 'write_reviews_all', 'write_reviews_group', 'write_reviews_own') # all default to False, explicit setting via role.set_permissions is # required view_course_limited = db.Column(db.Boolean, default=False, nullable=False) Loading @@ -709,7 +719,6 @@ class Role(db.Model, EntityBase, NamedMixin): write_roles = db.Column(db.Boolean, default=False, nullable=False) write_groups = db.Column(db.Boolean, default=False, nullable=False) write_projects = db.Column(db.Boolean, default=False, nullable=False) archive_projects = db.Column(db.Boolean, default=False, nullable=False) create_submissions = db.Column(db.Boolean, default=False, nullable=False) create_submissions_other = db.Column(db.Boolean, default=False, nullable=False) Loading Loading @@ -876,6 +885,7 @@ class Submission(db.Model, EntityBase): points = db.Column(db.Numeric(precision=10, scale=4), default=0.0) result = db.Column(db.String(length=10), default='none') course = association_proxy('project', 'course') course_id = association_proxy('project', 'course_id') ALLOWED_TRANSITIONS = { SubmissionState.CANCELLED: [SubmissionState.READY, SubmissionState.CREATED, Loading
portal/database/queries.py +82 −5 Original line number Diff line number Diff line import datetime import logging from flask_sqlalchemy import BaseQuery from sqlalchemy import func from portal import db from portal.database.models import Client, ClientType, Course, Group, Project, Role, Submission, \ SubmissionState, User, Worker log = logging.getLogger(__name__) def _get_class_based_on_client_type(client_type): klass = Client Loading @@ -13,11 +18,6 @@ def _get_class_based_on_client_type(client_type): return klass def client_permissions_for_course(client, course: Course = None) -> BaseQuery: return Role.query.filter_by(course=course) \ .join(Role.clients).filter(Client.id == client.id) def client_courses(client) -> BaseQuery: return Course.query.join(Course.roles).join(Role.clients).filter(Client.id == client.id) Loading @@ -43,6 +43,7 @@ def user_projects(user: User, course: Course = None) -> BaseQuery: def user_projects_by_course(user, course: 'Course') -> BaseQuery: # TODO: To query groups = user_groups_in_course(user, course=course).all() pids = [] for group in groups: Loading Loading @@ -139,3 +140,79 @@ def cancelled_submissions_for_deletion(time_period: datetime.timedelta) -> BaseQ return Submission.query.filter( Submission.state.in_(SubmissionState.CANCELLED, SubmissionState.ABORTED) & Submission.created_at < delete_from) def effective_permissions_for_course(client: Client, course: Course) -> BaseQuery: def _m(name): return func.max(getattr(Role, name)).label(name) props = [_m(prop) for prop in Role.PERMISSIONS] result = db.session.query(*props).filter(Role.course == course).join( Role.clients).filter(Client.id == client.id) return result def effective_permissions_for_client(client: Client) -> BaseQuery: def _m(name): return func.max(getattr(Role, name)).label(name) props = [_m(prop) for prop in Role.PERMISSIONS] query = db.session.query(Role.course_id, *props)\ .filter(Role.clients.contains(client))\ .group_by(Role.course_id) return query def list_submissions_for_user(client: User, user_ids=None, course_id=None, role_ids=None, group_ids=None, project_ids=None, states=None, archived=False) -> BaseQuery: query: BaseQuery = Submission.query if user_ids: user_filter = (User.id.in_(user_ids)) | (User.codename.in_(user_ids)) query = query.join(Submission.user).filter(user_filter) if not archived: query = query.filter(Submission.state != SubmissionState.ARCHIVED) if states: query = query.filter(Submission.state.in_(states)) if course_id: query = _filter_by_course_groups_roles(query, course_id, group_ids, project_ids, role_ids) if not client.is_admin: query = _filter_by_client(query, client) log.debug(f"[QUERY] find submissions: {query}") return query def _filter_by_course_groups_roles(query, course_id, group_ids, project_ids, role_ids): course_filter = (Course.id == course_id) | (Course.codename == course_id) query = query.join(Submission.project).join(Project.course).filter(course_filter) if project_ids: project_filter = (Project.id.in_(project_ids) | (Project.codename.in_(project_ids))) query = query.filter(project_filter) if role_ids: role_filter = (Role.id.in_(role_ids) | (Role.codename.in_(role_ids))) query = query.join(Course.roles).filter(role_filter) if group_ids: group_filter = (Group.id.in_(group_ids) | (Group.codename.in_(group_ids))) query = query.join(Course.groups).filter(group_filter) return query def _filter_by_client(query: BaseQuery, client: User) -> BaseQuery: eff_client = effective_permissions_for_client(client).subquery() check_course_id = eff_client.c.course_id == Project.course_id from sqlalchemy import true check_read_all = (check_course_id & (eff_client.c.read_submissions_all == true())) check_read_own = (check_course_id & (eff_client.c.read_submissions_own == true()) & (Submission.user_id == client.id)) query = query.join(Submission.project)\ .join(eff_client, eff_client.c.course_id == Project.course_id)\ .filter(check_read_all | check_read_own) return query
portal/facade/submissions_facade.py +9 −3 Original line number Diff line number Diff line Loading @@ -103,7 +103,13 @@ class SubmissionsFacade(GeneralCRUDFacade): 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 = self._request.args.getlist('roles') group_ids = self._request.args.getlist('groups') project_ids = self._request.args.getlist('projects') states = self._request.args.getlist('states') user_ids = self._request.args.getlist('users') course = self._request.args.get('course') return super(SubmissionsFacade, self).find_all(*args, **kwargs, client=self.client, course_id=course, states=states, user_ids=user_ids, project_ids=project_ids, role_ids=role_ids, group_ids=group_ids)