1
atst/domain/common/__init__.py
Normal file
1
atst/domain/common/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .query import Query
|
37
atst/domain/common/query.py
Normal file
37
atst/domain/common/query.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from sqlalchemy.exc import DataError
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
from atst.domain.exceptions import NotFoundError
|
||||
from atst.database import db
|
||||
|
||||
|
||||
class Query(object):
|
||||
|
||||
model = None
|
||||
|
||||
@property
|
||||
def resource_name(cls):
|
||||
return cls.model.__class__.lower()
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
# pylint: disable=E1102
|
||||
return cls.model(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def get(cls, id_):
|
||||
try:
|
||||
resource = db.session.query(cls.model).filter_by(id=id_).one()
|
||||
return resource
|
||||
except (NoResultFound, DataError):
|
||||
raise NotFoundError(cls.resource_name)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
return db.session.query(cls.model).all()
|
||||
|
||||
@classmethod
|
||||
def add_and_commit(cls, resource):
|
||||
db.session.add(resource)
|
||||
db.session.commit()
|
||||
return resource
|
1
atst/domain/requests/__init__.py
Normal file
1
atst/domain/requests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .requests import Requests, create_revision_from_request_body
|
71
atst/domain/requests/query.py
Normal file
71
atst/domain/requests/query.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from sqlalchemy import exists, and_, exc, text
|
||||
|
||||
from atst.database import db
|
||||
from atst.domain.common import Query
|
||||
from atst.models.request import Request
|
||||
|
||||
|
||||
class RequestsQuery(Query):
|
||||
model = Request
|
||||
|
||||
@classmethod
|
||||
def exists(cls, request_id, creator):
|
||||
try:
|
||||
return db.session.query(
|
||||
exists().where(
|
||||
and_(Request.id == request_id, Request.creator == creator)
|
||||
)
|
||||
).scalar()
|
||||
|
||||
except exc.DataError:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def get_many(cls, creator=None):
|
||||
filters = []
|
||||
if creator:
|
||||
filters.append(Request.creator == creator)
|
||||
|
||||
requests = (
|
||||
db.session.query(Request)
|
||||
.filter(*filters)
|
||||
.order_by(Request.time_created.desc())
|
||||
.all()
|
||||
)
|
||||
return requests
|
||||
|
||||
@classmethod
|
||||
def get_with_lock(cls, request_id):
|
||||
try:
|
||||
# Query for request matching id, acquiring a row-level write lock.
|
||||
# https://www.postgresql.org/docs/10/static/sql-select.html#SQL-FOR-UPDATE-SHARE
|
||||
return (
|
||||
db.session.query(Request)
|
||||
.filter_by(id=request_id)
|
||||
.with_for_update(of=Request)
|
||||
.one()
|
||||
)
|
||||
|
||||
except NoResultFound:
|
||||
raise NotFoundError("requests")
|
||||
|
||||
@classmethod
|
||||
def status_count(cls, status, creator=None):
|
||||
bindings = {"status": status.name}
|
||||
raw = """
|
||||
SELECT count(requests_with_status.id)
|
||||
FROM (
|
||||
SELECT DISTINCT ON (rse.request_id) r.*, rse.new_status as status
|
||||
FROM request_status_events rse JOIN requests r ON r.id = rse.request_id
|
||||
ORDER BY rse.request_id, rse.sequence DESC
|
||||
) as requests_with_status
|
||||
WHERE requests_with_status.status = :status
|
||||
"""
|
||||
|
||||
if creator:
|
||||
raw += " AND requests_with_status.user_id = :user_id"
|
||||
bindings["user_id"] = creator.id
|
||||
|
||||
results = db.session.execute(text(raw), bindings).fetchone()
|
||||
(count,) = results
|
||||
return count
|
@@ -1,22 +1,18 @@
|
||||
from enum import Enum
|
||||
from sqlalchemy import exists, and_, exc
|
||||
from sqlalchemy.sql import text
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
from werkzeug.datastructures import FileStorage
|
||||
import dateutil
|
||||
|
||||
from atst.database import db
|
||||
from atst.domain.authz import Authorization
|
||||
from atst.domain.task_orders import TaskOrders
|
||||
from atst.domain.workspaces import Workspaces
|
||||
from atst.models.request import Request
|
||||
from atst.models.request_revision import RequestRevision
|
||||
from atst.models.request_status_event import RequestStatusEvent, RequestStatus
|
||||
from atst.models.request_review import RequestReview
|
||||
from atst.models.request_internal_comment import RequestInternalComment
|
||||
from atst.utils import deep_merge
|
||||
|
||||
from .exceptions import NotFoundError, UnauthorizedError
|
||||
from atst.domain.exceptions import UnauthorizedError
|
||||
|
||||
from .query import RequestsQuery
|
||||
|
||||
|
||||
def create_revision_from_request_body(body):
|
||||
@@ -38,38 +34,19 @@ class Requests(object):
|
||||
@classmethod
|
||||
def create(cls, creator, body):
|
||||
revision = create_revision_from_request_body(body)
|
||||
request = Request(creator=creator, revisions=[revision])
|
||||
request = RequestsQuery.create(creator=creator, revisions=[revision])
|
||||
request = Requests.set_status(request, RequestStatus.STARTED)
|
||||
|
||||
db.session.add(request)
|
||||
db.session.commit()
|
||||
request = RequestsQuery.add_and_commit(request)
|
||||
|
||||
return request
|
||||
|
||||
@classmethod
|
||||
def exists(cls, request_id, creator):
|
||||
try:
|
||||
return db.session.query(
|
||||
exists().where(
|
||||
and_(Request.id == request_id, Request.creator == creator)
|
||||
)
|
||||
).scalar()
|
||||
|
||||
except exc.DataError:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _get(cls, user, request_id):
|
||||
try:
|
||||
request = db.session.query(Request).filter_by(id=request_id).one()
|
||||
except (NoResultFound, exc.DataError):
|
||||
raise NotFoundError("request")
|
||||
|
||||
return request
|
||||
return RequestsQuery.exists(request_id, creator)
|
||||
|
||||
@classmethod
|
||||
def get(cls, user, request_id):
|
||||
request = Requests._get(user, request_id)
|
||||
request = RequestsQuery.get(request_id)
|
||||
|
||||
if not Authorization.can_view_request(user, request):
|
||||
raise UnauthorizedError(user, "get request")
|
||||
@@ -78,7 +55,7 @@ class Requests(object):
|
||||
|
||||
@classmethod
|
||||
def get_for_approval(cls, user, request_id):
|
||||
request = Requests._get(user, request_id)
|
||||
request = RequestsQuery.get(request_id)
|
||||
|
||||
Authorization.check_can_approve_request(user)
|
||||
|
||||
@@ -86,17 +63,7 @@ class Requests(object):
|
||||
|
||||
@classmethod
|
||||
def get_many(cls, creator=None):
|
||||
filters = []
|
||||
if creator:
|
||||
filters.append(Request.creator == creator)
|
||||
|
||||
requests = (
|
||||
db.session.query(Request)
|
||||
.filter(*filters)
|
||||
.order_by(Request.time_created.desc())
|
||||
.all()
|
||||
)
|
||||
return requests
|
||||
return RequestsQuery.get_many(creator)
|
||||
|
||||
@classmethod
|
||||
def submit(cls, request):
|
||||
@@ -109,52 +76,33 @@ class Requests(object):
|
||||
new_status = RequestStatus.PENDING_CCPO_ACCEPTANCE
|
||||
|
||||
request = Requests.set_status(request, new_status)
|
||||
|
||||
db.session.add(request)
|
||||
db.session.commit()
|
||||
request = RequestsQuery.add_and_commit(request)
|
||||
|
||||
return request
|
||||
|
||||
@classmethod
|
||||
def update(cls, request_id, request_delta):
|
||||
request = Requests._get_with_lock(request_id)
|
||||
request = RequestsQuery.get_with_lock(request_id)
|
||||
|
||||
new_body = deep_merge(request_delta, request.body)
|
||||
revision = create_revision_from_request_body(new_body)
|
||||
request.revisions.append(revision)
|
||||
|
||||
db.session.add(request)
|
||||
db.session.commit()
|
||||
request = RequestsQuery.add_and_commit(request)
|
||||
|
||||
return request
|
||||
|
||||
@classmethod
|
||||
def _get_with_lock(cls, request_id):
|
||||
try:
|
||||
# Query for request matching id, acquiring a row-level write lock.
|
||||
# https://www.postgresql.org/docs/10/static/sql-select.html#SQL-FOR-UPDATE-SHARE
|
||||
return (
|
||||
db.session.query(Request)
|
||||
.filter_by(id=request_id)
|
||||
.with_for_update(of=Request)
|
||||
.one()
|
||||
)
|
||||
|
||||
except NoResultFound:
|
||||
raise NotFoundError()
|
||||
|
||||
@classmethod
|
||||
def approve_and_create_workspace(cls, request):
|
||||
approved_request = Requests.set_status(request, RequestStatus.APPROVED)
|
||||
workspace = Workspaces.create(approved_request)
|
||||
|
||||
db.session.add(approved_request)
|
||||
db.session.commit()
|
||||
RequestsQuery.add_and_commit(approved_request)
|
||||
|
||||
return workspace
|
||||
|
||||
@classmethod
|
||||
def set_status(cls, request: Request, status: RequestStatus):
|
||||
def set_status(cls, request, status: RequestStatus):
|
||||
status_event = RequestStatusEvent(
|
||||
new_status=status, revision=request.latest_revision
|
||||
)
|
||||
@@ -205,26 +153,7 @@ class Requests(object):
|
||||
|
||||
@classmethod
|
||||
def status_count(cls, status, creator=None):
|
||||
if isinstance(status, Enum):
|
||||
status = status.name
|
||||
bindings = {"status": status}
|
||||
raw = """
|
||||
SELECT count(requests_with_status.id)
|
||||
FROM (
|
||||
SELECT DISTINCT ON (rse.request_id) r.*, rse.new_status as status
|
||||
FROM request_status_events rse JOIN requests r ON r.id = rse.request_id
|
||||
ORDER BY rse.request_id, rse.sequence DESC
|
||||
) as requests_with_status
|
||||
WHERE requests_with_status.status = :status
|
||||
"""
|
||||
|
||||
if creator:
|
||||
raw += " AND requests_with_status.user_id = :user_id"
|
||||
bindings["user_id"] = creator.id
|
||||
|
||||
results = db.session.execute(text(raw), bindings).fetchone()
|
||||
(count,) = results
|
||||
return count
|
||||
return RequestsQuery.status_count(status, creator)
|
||||
|
||||
@classmethod
|
||||
def in_progress_count(cls):
|
||||
@@ -251,7 +180,7 @@ WHERE requests_with_status.status = :status
|
||||
|
||||
@classmethod
|
||||
def update_financial_verification(cls, request_id, financial_data):
|
||||
request = Requests._get_with_lock(request_id)
|
||||
request = RequestsQuery.get_with_lock(request_id)
|
||||
|
||||
request_data = financial_data.copy()
|
||||
task_order_data = {
|
||||
@@ -283,20 +212,14 @@ WHERE requests_with_status.status = :status
|
||||
|
||||
@classmethod
|
||||
def submit_financial_verification(cls, request):
|
||||
Requests.set_status(request, RequestStatus.PENDING_CCPO_APPROVAL)
|
||||
|
||||
db.session.add(request)
|
||||
db.session.commit()
|
||||
|
||||
request = Requests.set_status(request, RequestStatus.PENDING_CCPO_APPROVAL)
|
||||
request = RequestsQuery.add_and_commit(request)
|
||||
return request
|
||||
|
||||
@classmethod
|
||||
def _add_review(cls, user, request, review_data):
|
||||
request.latest_status.review = RequestReview(reviewer=user, **review_data)
|
||||
|
||||
db.session.add(request)
|
||||
db.session.commit()
|
||||
|
||||
request = RequestsQuery.add_and_commit(request)
|
||||
return request
|
||||
|
||||
@classmethod
|
||||
@@ -320,9 +243,6 @@ WHERE requests_with_status.status = :status
|
||||
@classmethod
|
||||
def update_internal_comments(cls, user, request, comment_text):
|
||||
Authorization.check_can_approve_request(user)
|
||||
|
||||
request.internal_comments = RequestInternalComment(text=comment_text, user=user)
|
||||
db.session.add(request)
|
||||
db.session.commit()
|
||||
|
||||
request = RequestsQuery.add_and_commit(request)
|
||||
return request
|
33
atst/domain/workspaces/query.py
Normal file
33
atst/domain/workspaces/query.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
from atst.database import db
|
||||
from atst.domain.common import Query
|
||||
from atst.domain.exceptions import NotFoundError
|
||||
from atst.models.workspace import Workspace
|
||||
from atst.models.workspace_role import WorkspaceRole
|
||||
|
||||
|
||||
class WorkspacesQuery(Query):
|
||||
model = Workspace
|
||||
|
||||
@classmethod
|
||||
def get_by_request(cls, request):
|
||||
try:
|
||||
workspace = db.session.query(Workspace).filter_by(request=request).one()
|
||||
except NoResultFound:
|
||||
raise NotFoundError("workspace")
|
||||
|
||||
return workspace
|
||||
|
||||
@classmethod
|
||||
def get_for_user(cls, user):
|
||||
return (
|
||||
db.session.query(Workspace)
|
||||
.join(WorkspaceRole)
|
||||
.filter(WorkspaceRole.user == user)
|
||||
.all()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create_workspace_role(cls, user, role, workspace):
|
||||
return WorkspaceRole(user=user, role=role, workspace=workspace)
|
@@ -1,14 +1,10 @@
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
from atst.database import db
|
||||
from atst.models.workspace import Workspace
|
||||
from atst.models.workspace_role import WorkspaceRole
|
||||
from atst.domain.exceptions import NotFoundError
|
||||
from atst.domain.roles import Roles
|
||||
from atst.domain.authz import Authorization
|
||||
from atst.models.permissions import Permissions
|
||||
from atst.domain.users import Users
|
||||
from atst.domain.workspace_users import WorkspaceUsers
|
||||
|
||||
from .query import WorkspacesQuery
|
||||
from .scopes import ScopedWorkspace
|
||||
|
||||
|
||||
@@ -16,17 +12,14 @@ class Workspaces(object):
|
||||
@classmethod
|
||||
def create(cls, request, name=None):
|
||||
name = name or request.id
|
||||
workspace = Workspace(request=request, name=name)
|
||||
workspace = WorkspacesQuery.create(request=request, name=name)
|
||||
Workspaces._create_workspace_role(request.creator, workspace, "owner")
|
||||
|
||||
db.session.add(workspace)
|
||||
db.session.commit()
|
||||
|
||||
WorkspacesQuery.add_and_commit(workspace)
|
||||
return workspace
|
||||
|
||||
@classmethod
|
||||
def get(cls, user, workspace_id):
|
||||
workspace = Workspaces._get(workspace_id)
|
||||
workspace = WorkspacesQuery.get(workspace_id)
|
||||
Authorization.check_workspace_permission(
|
||||
user, workspace, Permissions.VIEW_WORKSPACE, "get workspace"
|
||||
)
|
||||
@@ -35,7 +28,7 @@ class Workspaces(object):
|
||||
|
||||
@classmethod
|
||||
def get_for_update(cls, user, workspace_id):
|
||||
workspace = Workspaces._get(workspace_id)
|
||||
workspace = WorkspacesQuery.get(workspace_id)
|
||||
Authorization.check_workspace_permission(
|
||||
user, workspace, Permissions.ADD_APPLICATION_IN_WORKSPACE, "add project"
|
||||
)
|
||||
@@ -44,16 +37,11 @@ class Workspaces(object):
|
||||
|
||||
@classmethod
|
||||
def get_by_request(cls, request):
|
||||
try:
|
||||
workspace = db.session.query(Workspace).filter_by(request=request).one()
|
||||
except NoResultFound:
|
||||
raise NotFoundError("workspace")
|
||||
|
||||
return workspace
|
||||
return WorkspacesQuery.get_by_request(request)
|
||||
|
||||
@classmethod
|
||||
def get_with_members(cls, user, workspace_id):
|
||||
workspace = Workspaces._get(workspace_id)
|
||||
workspace = WorkspacesQuery.get(workspace_id)
|
||||
Authorization.check_workspace_permission(
|
||||
user,
|
||||
workspace,
|
||||
@@ -63,27 +51,12 @@ class Workspaces(object):
|
||||
|
||||
return workspace
|
||||
|
||||
@classmethod
|
||||
def get_many(cls, user):
|
||||
workspaces = (
|
||||
db.session.query(Workspace)
|
||||
.join(WorkspaceRole)
|
||||
.filter(WorkspaceRole.user == user)
|
||||
.all()
|
||||
)
|
||||
return workspaces
|
||||
|
||||
@classmethod
|
||||
def for_user(cls, user):
|
||||
if Authorization.has_atat_permission(user, Permissions.VIEW_WORKSPACE):
|
||||
workspaces = db.session.query(Workspace).all()
|
||||
workspaces = WorkspacesQuery.get_all()
|
||||
else:
|
||||
workspaces = (
|
||||
db.session.query(Workspace)
|
||||
.join(WorkspaceRole)
|
||||
.filter(WorkspaceRole.user == user)
|
||||
.all()
|
||||
)
|
||||
workspaces = WorkspacesQuery.get_for_user(user)
|
||||
return workspaces
|
||||
|
||||
@classmethod
|
||||
@@ -122,15 +95,6 @@ class Workspaces(object):
|
||||
@classmethod
|
||||
def _create_workspace_role(cls, user, workspace, role_name):
|
||||
role = Roles.get(role_name)
|
||||
workspace_role = WorkspaceRole(user=user, role=role, workspace=workspace)
|
||||
db.session.add(workspace_role)
|
||||
workspace_role = WorkspacesQuery.create_workspace_role(user, role, workspace)
|
||||
WorkspacesQuery.add_and_commit(workspace_role)
|
||||
return workspace_role
|
||||
|
||||
@classmethod
|
||||
def _get(cls, workspace_id):
|
||||
try:
|
||||
workspace = db.session.query(Workspace).filter_by(id=workspace_id).one()
|
||||
except NoResultFound:
|
||||
raise NotFoundError("workspace")
|
||||
|
||||
return workspace
|
||||
|
Reference in New Issue
Block a user