Merge pull request #197 from dod-ccpo/workspaces2
Create workspaces and projects
This commit is contained in:
commit
63d2e222a9
56
Pipfile.lock
generated
56
Pipfile.lock
generated
@ -271,7 +271,7 @@
|
|||||||
"sha256:1d936da41ee06216d89fdc7ead1ee9a5da2811a8787515a976b646e110c3f622",
|
"sha256:1d936da41ee06216d89fdc7ead1ee9a5da2811a8787515a976b646e110c3f622",
|
||||||
"sha256:e4ef42e82b0b493c5849eed98b5ab49d6767caf982127e9a33167f1153b36cc5"
|
"sha256:e4ef42e82b0b493c5849eed98b5ab49d6767caf982127e9a33167f1153b36cc5"
|
||||||
],
|
],
|
||||||
"markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*' and python_version >= '2.7'",
|
"markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'",
|
||||||
"version": "==2018.5"
|
"version": "==2018.5"
|
||||||
},
|
},
|
||||||
"redis": {
|
"redis": {
|
||||||
@ -317,7 +317,7 @@
|
|||||||
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
|
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
|
||||||
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
|
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
|
||||||
],
|
],
|
||||||
"markers": "python_version < '4' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.2.*' and python_version >= '2.6' and python_version != '3.1.*'",
|
"markers": "python_version >= '2.6' and python_version != '3.2.*' and python_version != '3.0.*' and python_version < '4' and python_version != '3.1.*' and python_version != '3.3.*'",
|
||||||
"version": "==1.23"
|
"version": "==1.23"
|
||||||
},
|
},
|
||||||
"webassets": {
|
"webassets": {
|
||||||
@ -350,14 +350,6 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.4.3"
|
"version": "==1.4.3"
|
||||||
},
|
},
|
||||||
"appnope": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0",
|
|
||||||
"sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"
|
|
||||||
],
|
|
||||||
"markers": "sys_platform == 'darwin'",
|
|
||||||
"version": "==0.1.0"
|
|
||||||
},
|
|
||||||
"argh": {
|
"argh": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3",
|
"sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3",
|
||||||
@ -449,6 +441,7 @@
|
|||||||
"sha256:ea7cfd3aeb1544732d08bd9cfba40c5b78e3a91e17b1a0698ab81bfc5554c628",
|
"sha256:ea7cfd3aeb1544732d08bd9cfba40c5b78e3a91e17b1a0698ab81bfc5554c628",
|
||||||
"sha256:f6d67f04abfb2b4bea7afc7fa6c18cf4c523a67956e455668be9ae42bccc21ad"
|
"sha256:f6d67f04abfb2b4bea7afc7fa6c18cf4c523a67956e455668be9ae42bccc21ad"
|
||||||
],
|
],
|
||||||
|
"markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.1.*' and python_version >= '2.7'",
|
||||||
"version": "==0.9.0"
|
"version": "==0.9.0"
|
||||||
},
|
},
|
||||||
"flask": {
|
"flask": {
|
||||||
@ -501,7 +494,7 @@
|
|||||||
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
|
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
|
||||||
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
|
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*'",
|
"markers": "python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.3.*'",
|
||||||
"version": "==4.3.4"
|
"version": "==4.3.4"
|
||||||
},
|
},
|
||||||
"itsdangerous": {
|
"itsdangerous": {
|
||||||
@ -619,7 +612,7 @@
|
|||||||
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
|
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
|
||||||
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
|
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*'",
|
"markers": "python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.3.*'",
|
||||||
"version": "==0.7.1"
|
"version": "==0.7.1"
|
||||||
},
|
},
|
||||||
"prompt-toolkit": {
|
"prompt-toolkit": {
|
||||||
@ -642,7 +635,7 @@
|
|||||||
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
|
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
|
||||||
"sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
|
"sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*'",
|
"markers": "python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.3.*'",
|
||||||
"version": "==1.5.4"
|
"version": "==1.5.4"
|
||||||
},
|
},
|
||||||
"pygments": {
|
"pygments": {
|
||||||
@ -754,43 +747,6 @@
|
|||||||
],
|
],
|
||||||
"version": "==4.3.2"
|
"version": "==4.3.2"
|
||||||
},
|
},
|
||||||
"typed-ast": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0948004fa228ae071054f5208840a1e88747a357ec1101c17217bfe99b299d58",
|
|
||||||
"sha256:10703d3cec8dcd9eef5a630a04056bbc898abc19bac5691612acba7d1325b66d",
|
|
||||||
"sha256:1f6c4bd0bdc0f14246fd41262df7dfc018d65bb05f6e16390b7ea26ca454a291",
|
|
||||||
"sha256:25d8feefe27eb0303b73545416b13d108c6067b846b543738a25ff304824ed9a",
|
|
||||||
"sha256:29464a177d56e4e055b5f7b629935af7f49c196be47528cc94e0a7bf83fbc2b9",
|
|
||||||
"sha256:2e214b72168ea0275efd6c884b114ab42e316de3ffa125b267e732ed2abda892",
|
|
||||||
"sha256:3e0d5e48e3a23e9a4d1a9f698e32a542a4a288c871d33ed8df1b092a40f3a0f9",
|
|
||||||
"sha256:519425deca5c2b2bdac49f77b2c5625781abbaf9a809d727d3a5596b30bb4ded",
|
|
||||||
"sha256:57fe287f0cdd9ceaf69e7b71a2e94a24b5d268b35df251a88fef5cc241bf73aa",
|
|
||||||
"sha256:668d0cec391d9aed1c6a388b0d5b97cd22e6073eaa5fbaa6d2946603b4871efe",
|
|
||||||
"sha256:68ba70684990f59497680ff90d18e756a47bf4863c604098f10de9716b2c0bdd",
|
|
||||||
"sha256:6de012d2b166fe7a4cdf505eee3aaa12192f7ba365beeefaca4ec10e31241a85",
|
|
||||||
"sha256:79b91ebe5a28d349b6d0d323023350133e927b4de5b651a8aa2db69c761420c6",
|
|
||||||
"sha256:8550177fa5d4c1f09b5e5f524411c44633c80ec69b24e0e98906dd761941ca46",
|
|
||||||
"sha256:898f818399cafcdb93cbbe15fc83a33d05f18e29fb498ddc09b0214cdfc7cd51",
|
|
||||||
"sha256:94b091dc0f19291adcb279a108f5d38de2430411068b219f41b343c03b28fb1f",
|
|
||||||
"sha256:a26863198902cda15ab4503991e8cf1ca874219e0118cbf07c126bce7c4db129",
|
|
||||||
"sha256:a8034021801bc0440f2e027c354b4eafd95891b573e12ff0418dec385c76785c",
|
|
||||||
"sha256:bc978ac17468fe868ee589c795d06777f75496b1ed576d308002c8a5756fb9ea",
|
|
||||||
"sha256:c05b41bc1deade9f90ddc5d988fe506208019ebba9f2578c622516fd201f5863",
|
|
||||||
"sha256:c9b060bd1e5a26ab6e8267fd46fc9e02b54eb15fffb16d112d4c7b1c12987559",
|
|
||||||
"sha256:edb04bdd45bfd76c8292c4d9654568efaedf76fe78eb246dde69bdb13b2dad87",
|
|
||||||
"sha256:f19f2a4f547505fe9072e15f6f4ae714af51b5a681a97f187971f50c283193b6"
|
|
||||||
],
|
|
||||||
"markers": "python_version < '3.7' and implementation_name == 'cpython'",
|
|
||||||
"version": "==1.1.0"
|
|
||||||
},
|
|
||||||
"typing": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf",
|
|
||||||
"sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8",
|
|
||||||
"sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2"
|
|
||||||
],
|
|
||||||
"version": "==3.6.4"
|
|
||||||
},
|
|
||||||
"watchdog": {
|
"watchdog": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7e65882adb7746039b6f3876ee174952f8eaaa34491ba34333ddf1fe35de4162"
|
"sha256:7e65882adb7746039b6f3876ee174952f8eaaa34491ba34333ddf1fe35de4162"
|
||||||
|
37
alembic/versions/4be312655ceb_add_workspaces_table.py
Normal file
37
alembic/versions/4be312655ceb_add_workspaces_table.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
"""add workspaces table
|
||||||
|
|
||||||
|
Revision ID: 4be312655ceb
|
||||||
|
Revises: 05d6272bdb43
|
||||||
|
Create Date: 2018-08-16 09:25:19.888549
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '4be312655ceb'
|
||||||
|
down_revision = '05d6272bdb43'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('workspaces',
|
||||||
|
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||||
|
sa.Column('request_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||||
|
sa.Column('task_order_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['request_id'], ['requests.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['task_order_id'], ['task_order.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('name')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('workspaces')
|
||||||
|
# ### end Alembic commands ###
|
30
alembic/versions/a2b499a1dd62_workspace_timestamps.py
Normal file
30
alembic/versions/a2b499a1dd62_workspace_timestamps.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"""workspace timestamps
|
||||||
|
|
||||||
|
Revision ID: a2b499a1dd62
|
||||||
|
Revises: f549c7cee17c
|
||||||
|
Create Date: 2018-08-17 10:43:13.165829
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'a2b499a1dd62'
|
||||||
|
down_revision = 'f549c7cee17c'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('workspaces', sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False))
|
||||||
|
op.add_column('workspaces', sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('workspaces', 'time_updated')
|
||||||
|
op.drop_column('workspaces', 'time_created')
|
||||||
|
# ### end Alembic commands ###
|
47
alembic/versions/f064247f2988_projects_and_environments.py
Normal file
47
alembic/versions/f064247f2988_projects_and_environments.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
"""projects and environments
|
||||||
|
|
||||||
|
Revision ID: f064247f2988
|
||||||
|
Revises: a2b499a1dd62
|
||||||
|
Create Date: 2018-08-17 11:30:53.684954
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'f064247f2988'
|
||||||
|
down_revision = 'a2b499a1dd62'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('projects',
|
||||||
|
sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||||
|
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||||
|
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||||
|
sa.Column('name', sa.String(), nullable=False),
|
||||||
|
sa.Column('description', sa.String(), nullable=False),
|
||||||
|
sa.Column('workspace_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('environments',
|
||||||
|
sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||||
|
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||||
|
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||||
|
sa.Column('name', sa.String(), nullable=False),
|
||||||
|
sa.Column('project_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['project_id'], ['projects.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('environments')
|
||||||
|
op.drop_table('projects')
|
||||||
|
# ### end Alembic commands ###
|
@ -0,0 +1,28 @@
|
|||||||
|
"""add workspace_role workspace_id fk
|
||||||
|
|
||||||
|
Revision ID: f36f130622b9
|
||||||
|
Revises: f064247f2988
|
||||||
|
Create Date: 2018-08-20 10:36:23.920881
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'f36f130622b9'
|
||||||
|
down_revision = 'f064247f2988'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_foreign_key('workspace_role_workspace_id_fk', 'workspace_role', 'workspaces', ['workspace_id'], ['id'])
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint('workspace_role_workspace_id_fk', 'workspace_role', type_='foreignkey')
|
||||||
|
# ### end Alembic commands ###
|
@ -0,0 +1,30 @@
|
|||||||
|
"""remove workspaces task order association
|
||||||
|
|
||||||
|
Revision ID: f549c7cee17c
|
||||||
|
Revises: 4be312655ceb
|
||||||
|
Create Date: 2018-08-16 16:42:48.581510
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'f549c7cee17c'
|
||||||
|
down_revision = '4be312655ceb'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint('workspaces_task_order_id_fkey', 'workspaces', type_='foreignkey')
|
||||||
|
op.drop_column('workspaces', 'task_order_id')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('workspaces', sa.Column('task_order_id', sa.INTEGER(), autoincrement=False))
|
||||||
|
op.create_foreign_key('workspaces_task_order_id_fkey', 'workspaces', 'task_order', ['task_order_id'], ['id'])
|
||||||
|
# ### end Alembic commands ###
|
12
atst/domain/authz.py
Normal file
12
atst/domain/authz.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from atst.domain.workspace_users import WorkspaceUsers
|
||||||
|
|
||||||
|
|
||||||
|
class Authorization(object):
|
||||||
|
@classmethod
|
||||||
|
def has_workspace_permission(cls, user, workspace, permission):
|
||||||
|
workspace_user = WorkspaceUsers.get(workspace.id, user.id)
|
||||||
|
return permission in workspace_user.permissions()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_in_workspace(cls, user, workspace):
|
||||||
|
return user in workspace.users
|
12
atst/domain/environments.py
Normal file
12
atst/domain/environments.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from atst.database import db
|
||||||
|
from atst.models.environment import Environment
|
||||||
|
|
||||||
|
|
||||||
|
class Environments(object):
|
||||||
|
@classmethod
|
||||||
|
def create(cls, project, name):
|
||||||
|
environment = Environment(project=project, name=name)
|
||||||
|
db.session.add(environment)
|
||||||
|
db.session.commit()
|
||||||
|
return environment
|
||||||
|
|
13
atst/domain/projects.py
Normal file
13
atst/domain/projects.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from atst.database import db
|
||||||
|
from atst.models.project import Project
|
||||||
|
|
||||||
|
|
||||||
|
class Projects(object):
|
||||||
|
@classmethod
|
||||||
|
def create(cls, workspace, name, description):
|
||||||
|
project = Project(workspace=workspace, name=name, description=description)
|
||||||
|
|
||||||
|
db.session.add(project)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return project
|
@ -6,6 +6,7 @@ from sqlalchemy.orm.attributes import flag_modified
|
|||||||
|
|
||||||
from atst.models.request import Request
|
from atst.models.request import Request
|
||||||
from atst.models.request_status_event import RequestStatusEvent, RequestStatus
|
from atst.models.request_status_event import RequestStatusEvent, RequestStatus
|
||||||
|
from atst.domain.workspaces import Workspaces
|
||||||
from atst.database import db
|
from atst.database import db
|
||||||
|
|
||||||
from .exceptions import NotFoundError
|
from .exceptions import NotFoundError
|
||||||
@ -114,6 +115,18 @@ class Requests(object):
|
|||||||
db.session.add(request)
|
db.session.add(request)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
return request
|
||||||
|
|
||||||
|
@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()
|
||||||
|
|
||||||
|
return workspace
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_status(cls, request: Request, status: RequestStatus):
|
def set_status(cls, request: Request, status: RequestStatus):
|
||||||
status_event = RequestStatusEvent(new_status=status)
|
status_event = RequestStatusEvent(new_status=status)
|
||||||
|
@ -21,7 +21,8 @@ class WorkspaceUsers(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
workspace_role = (
|
workspace_role = (
|
||||||
WorkspaceRole.query.join(User)
|
db.session.query(WorkspaceRole)
|
||||||
|
.join(User)
|
||||||
.filter(User.id == user_id, WorkspaceRole.workspace_id == workspace_id)
|
.filter(User.id == user_id, WorkspaceRole.workspace_id == workspace_id)
|
||||||
.one()
|
.one()
|
||||||
)
|
)
|
||||||
@ -30,6 +31,29 @@ class WorkspaceUsers(object):
|
|||||||
|
|
||||||
return WorkspaceUser(user, workspace_role)
|
return WorkspaceUser(user, workspace_role)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add(cls, user, workspace_id, role_name):
|
||||||
|
role = Roles.get(role_name)
|
||||||
|
try:
|
||||||
|
existing_workspace_role = (
|
||||||
|
db.session.query(WorkspaceRole)
|
||||||
|
.filter(
|
||||||
|
WorkspaceRole.user == user,
|
||||||
|
WorkspaceRole.workspace_id == workspace_id,
|
||||||
|
)
|
||||||
|
.one()
|
||||||
|
)
|
||||||
|
new_workspace_role = existing_workspace_role
|
||||||
|
new_workspace_role.role = role
|
||||||
|
except NoResultFound:
|
||||||
|
new_workspace_role = WorkspaceRole(
|
||||||
|
user=user, role_id=role.id, workspace_id=workspace_id
|
||||||
|
)
|
||||||
|
|
||||||
|
user.workspace_roles.append(new_workspace_role)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_many(cls, workspace_id, workspace_user_dicts):
|
def add_many(cls, workspace_id, workspace_user_dicts):
|
||||||
workspace_users = []
|
workspace_users = []
|
||||||
|
@ -1,112 +1,71 @@
|
|||||||
|
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, UnauthorizedError
|
||||||
|
from atst.domain.roles import Roles
|
||||||
|
from atst.domain.authz import Authorization
|
||||||
|
from atst.models.permissions import Permissions
|
||||||
|
|
||||||
|
|
||||||
class Workspaces(object):
|
class Workspaces(object):
|
||||||
MOCK_WORKSPACES = [
|
@classmethod
|
||||||
{
|
def create(cls, request, name=None):
|
||||||
"name": "Unclassified IaaS and PaaS for Defense Digital Service (DDS)",
|
name = name or request.id
|
||||||
"id": "5966187a-eff9-44c3-aa15-4de7a65ac7ff",
|
workspace = Workspace(request=request, name=name)
|
||||||
"task_order": {"number": 123456},
|
Workspaces._create_workspace_role(request.creator, workspace, "owner")
|
||||||
"user_count": 23,
|
|
||||||
}
|
db.session.add(workspace)
|
||||||
]
|
db.session.commit()
|
||||||
|
|
||||||
|
return workspace
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, workspace_id):
|
def get(cls, user, workspace_id):
|
||||||
return cls.MOCK_WORKSPACES[0]
|
try:
|
||||||
|
workspace = db.session.query(Workspace).filter_by(id=workspace_id).one()
|
||||||
|
except NoResultFound:
|
||||||
|
raise NotFoundError("workspace")
|
||||||
|
|
||||||
|
if not Authorization.is_in_workspace(user, workspace):
|
||||||
|
raise UnauthorizedError(user, "get workspace")
|
||||||
|
|
||||||
|
return workspace
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_many(cls):
|
def get_for_update(cls, user, workspace_id):
|
||||||
return cls.MOCK_WORKSPACES
|
workspace = Workspaces.get(user, workspace_id)
|
||||||
|
if not Authorization.has_workspace_permission(
|
||||||
class Projects(object):
|
user, workspace, Permissions.ADD_APPLICATION_IN_WORKSPACE
|
||||||
|
):
|
||||||
|
raise UnauthorizedError(user, "add project")
|
||||||
|
return workspace
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, creator_id, body):
|
def get_by_request(cls, request):
|
||||||
pass
|
try:
|
||||||
|
workspace = db.session.query(Workspace).filter_by(request=request).one()
|
||||||
|
except NoResultFound:
|
||||||
|
raise NotFoundError("workspace")
|
||||||
|
|
||||||
|
return workspace
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, project_id):
|
def get_many(cls, user):
|
||||||
pass
|
workspaces = (
|
||||||
|
db.session.query(Workspace)
|
||||||
|
.join(WorkspaceRole)
|
||||||
|
.filter(WorkspaceRole.user == user)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
return workspaces
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_many(cls, workspace_id):
|
def _create_workspace_role(cls, user, workspace, role_name):
|
||||||
return [
|
role = Roles.get(role_name)
|
||||||
{
|
workspace_role = WorkspaceRole(
|
||||||
"id": "187c9bea-9541-45d7-801f-cf8e7a642e93",
|
user=user, role=role, workspace=workspace
|
||||||
"name": "Code.mil",
|
)
|
||||||
"environments": [
|
db.session.add(workspace_role)
|
||||||
{
|
return workspace_role
|
||||||
"id": "b1154fdd-31c9-437f-b580-2e4d757de5cb",
|
|
||||||
"name": "Development",
|
|
||||||
},
|
|
||||||
{"id": "b1e2077a-6a3d-4e7f-a80c-bf1143433adf", "name": "Sandbox"},
|
|
||||||
{
|
|
||||||
"id": "8ea95eea-7cc0-4500-adf7-8a13eaa6b752",
|
|
||||||
"name": "production",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ececfd73-b19d-45aa-9199-a950ba2c7269",
|
|
||||||
"name": "Digital Dojo",
|
|
||||||
"environments": [
|
|
||||||
{
|
|
||||||
"id": "f56167cb-ca3d-4e29-8b60-91052957a118",
|
|
||||||
"name": "Development",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7c18689c-5b77-4b68-8d64-d4d8a830bf47",
|
|
||||||
"name": "production",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def update(cls, request_id, request_delta):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Members(object):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create(cls, creator_id, body):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get(cls, request_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_many(cls, workspace_id):
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"first_name": "Danny",
|
|
||||||
"last_name": "Knight",
|
|
||||||
"email": "dknight@thenavy.mil",
|
|
||||||
"dod_id": "1257892124",
|
|
||||||
"workspace_role": "Developer",
|
|
||||||
"status": "Pending",
|
|
||||||
"num_projects": "4",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"first_name": "Mario",
|
|
||||||
"last_name": "Hudson",
|
|
||||||
"email": "mhudson@thearmy.mil",
|
|
||||||
"dod_id": "4357892125",
|
|
||||||
"workspace_role": "CCPO",
|
|
||||||
"status": "Active",
|
|
||||||
"num_projects": "0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"first_name": "Louise",
|
|
||||||
"last_name": "Greer",
|
|
||||||
"email": "lgreer@theairforce.mil",
|
|
||||||
"dod_id": "7257892125",
|
|
||||||
"workspace_role": "Admin",
|
|
||||||
"status": "Pending",
|
|
||||||
"num_projects": "43",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def update(cls, request_id, request_delta):
|
|
||||||
pass
|
|
||||||
|
9
atst/forms/new_project.py
Normal file
9
atst/forms/new_project.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from flask_wtf import Form
|
||||||
|
from wtforms.fields import StringField, TextAreaField
|
||||||
|
|
||||||
|
|
||||||
|
class NewProjectForm(Form):
|
||||||
|
|
||||||
|
name = StringField(label="Project Name")
|
||||||
|
description = TextAreaField(label="Description")
|
||||||
|
environment_name = StringField(label="Environment Name")
|
@ -10,3 +10,6 @@ from .user import User
|
|||||||
from .workspace_role import WorkspaceRole
|
from .workspace_role import WorkspaceRole
|
||||||
from .pe_number import PENumber
|
from .pe_number import PENumber
|
||||||
from .task_order import TaskOrder
|
from .task_order import TaskOrder
|
||||||
|
from .workspace import Workspace
|
||||||
|
from .project import Project
|
||||||
|
from .environment import Environment
|
||||||
|
16
atst/models/environment.py
Normal file
16
atst/models/environment.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from sqlalchemy import Column, ForeignKey, String
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from atst.models import Base
|
||||||
|
from atst.models.types import Id
|
||||||
|
from atst.models.mixins import TimestampsMixin
|
||||||
|
|
||||||
|
|
||||||
|
class Environment(Base, TimestampsMixin):
|
||||||
|
__tablename__ = "environments"
|
||||||
|
|
||||||
|
id = Id()
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
|
||||||
|
project_id = Column(ForeignKey("projects.id"))
|
||||||
|
project = relationship("Project")
|
7
atst/models/mixins.py
Normal file
7
atst/models/mixins.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from sqlalchemy import Column, func, TIMESTAMP
|
||||||
|
|
||||||
|
|
||||||
|
class TimestampsMixin(object):
|
||||||
|
time_created = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now())
|
||||||
|
time_updated = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now(), onupdate=func.current_timestamp())
|
||||||
|
|
18
atst/models/project.py
Normal file
18
atst/models/project.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from sqlalchemy import Column, ForeignKey, String
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from atst.models import Base
|
||||||
|
from atst.models.types import Id
|
||||||
|
from atst.models.mixins import TimestampsMixin
|
||||||
|
|
||||||
|
|
||||||
|
class Project(Base, TimestampsMixin):
|
||||||
|
__tablename__ = "projects"
|
||||||
|
|
||||||
|
id = Id()
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
description = Column(String, nullable=False)
|
||||||
|
|
||||||
|
workspace_id = Column(ForeignKey("workspaces.id"), nullable=False)
|
||||||
|
workspace = relationship("Workspace")
|
||||||
|
environments = relationship("Environment", back_populates="project")
|
75
atst/models/workspace.py
Normal file
75
atst/models/workspace.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
from sqlalchemy import Column, ForeignKey, String
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from atst.models import Base
|
||||||
|
from atst.models.types import Id
|
||||||
|
from atst.models.mixins import TimestampsMixin
|
||||||
|
|
||||||
|
|
||||||
|
MOCK_MEMBERS = [
|
||||||
|
{
|
||||||
|
"first_name": "Danny",
|
||||||
|
"last_name": "Knight",
|
||||||
|
"email": "dknight@thenavy.mil",
|
||||||
|
"dod_id": "1257892124",
|
||||||
|
"workspace_role": "Developer",
|
||||||
|
"status": "Pending",
|
||||||
|
"num_projects": "4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"first_name": "Mario",
|
||||||
|
"last_name": "Hudson",
|
||||||
|
"email": "mhudson@thearmy.mil",
|
||||||
|
"dod_id": "4357892125",
|
||||||
|
"workspace_role": "CCPO",
|
||||||
|
"status": "Active",
|
||||||
|
"num_projects": "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"first_name": "Louise",
|
||||||
|
"last_name": "Greer",
|
||||||
|
"email": "lgreer@theairforce.mil",
|
||||||
|
"dod_id": "7257892125",
|
||||||
|
"workspace_role": "Admin",
|
||||||
|
"status": "Pending",
|
||||||
|
"num_projects": "43",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Workspace(Base, TimestampsMixin):
|
||||||
|
__tablename__ = "workspaces"
|
||||||
|
|
||||||
|
id = Id()
|
||||||
|
name = Column(String, unique=True)
|
||||||
|
request_id = Column(ForeignKey("requests.id"), nullable=False)
|
||||||
|
request = relationship("Request")
|
||||||
|
projects = relationship("Project", back_populates="workspace")
|
||||||
|
roles = relationship("WorkspaceRole")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def owner(self):
|
||||||
|
return next(
|
||||||
|
(
|
||||||
|
workspace_role.user
|
||||||
|
for workspace_role in self.roles
|
||||||
|
if workspace_role.role.name == "owner"
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def users(self):
|
||||||
|
return set(role.user for role in self.roles)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_count(self):
|
||||||
|
return len(self.users)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def task_order(self):
|
||||||
|
return {"number": "task-order-number"}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def members(self):
|
||||||
|
return MOCK_MEMBERS
|
@ -10,11 +10,14 @@ class WorkspaceRole(Base):
|
|||||||
__tablename__ = "workspace_role"
|
__tablename__ = "workspace_role"
|
||||||
|
|
||||||
id = Id()
|
id = Id()
|
||||||
workspace_id = Column(UUID(as_uuid=True), index=True)
|
workspace_id = Column(UUID(as_uuid=True), ForeignKey("workspaces.id"), index=True)
|
||||||
|
workspace = relationship("Workspace", back_populates="roles")
|
||||||
|
|
||||||
role_id = Column(UUID(as_uuid=True), ForeignKey("roles.id"))
|
role_id = Column(UUID(as_uuid=True), ForeignKey("roles.id"))
|
||||||
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), index=True)
|
|
||||||
role = relationship("Role")
|
role = relationship("Role")
|
||||||
|
|
||||||
|
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), index=True)
|
||||||
|
|
||||||
|
|
||||||
Index(
|
Index(
|
||||||
"workspace_role_user_workspace",
|
"workspace_role_user_workspace",
|
||||||
|
@ -31,17 +31,19 @@ def update_financial_verification(request_id):
|
|||||||
existing_request = Requests.get(request_id)
|
existing_request = Requests.get(request_id)
|
||||||
form = financial_form(post_data)
|
form = financial_form(post_data)
|
||||||
|
|
||||||
rerender_args = dict(request_id=request_id, f=form, extended=http_request.args.get("extended"))
|
rerender_args = dict(
|
||||||
|
request_id=request_id, f=form, extended=http_request.args.get("extended")
|
||||||
|
)
|
||||||
|
|
||||||
if form.validate():
|
if form.validate():
|
||||||
request_data = {"financial_verification": form.data}
|
request_data = {"financial_verification": form.data}
|
||||||
valid = form.perform_extra_validation(
|
valid = form.perform_extra_validation(
|
||||||
existing_request.body.get("financial_verification")
|
existing_request.body.get("financial_verification")
|
||||||
)
|
)
|
||||||
Requests.update(request_id, request_data)
|
updated_request = Requests.update(request_id, request_data)
|
||||||
if valid:
|
if valid:
|
||||||
return redirect(url_for("requests.financial_verification_submitted"))
|
new_workspace = Requests.approve_and_create_workspace(updated_request)
|
||||||
|
return redirect(url_for("workspaces.workspace_projects", workspace_id=new_workspace.id, newWorkspace=True))
|
||||||
else:
|
else:
|
||||||
form.reset()
|
form.reset()
|
||||||
return render_template(
|
return render_template(
|
||||||
|
@ -1,35 +1,76 @@
|
|||||||
from flask import Blueprint, render_template, request as http_request
|
from flask import (
|
||||||
|
Blueprint,
|
||||||
from atst.domain.workspaces import Members, Projects, Workspaces
|
render_template,
|
||||||
|
request as http_request,
|
||||||
|
g,
|
||||||
|
redirect,
|
||||||
|
url_for,
|
||||||
|
)
|
||||||
|
|
||||||
|
from atst.domain.workspaces import Workspaces
|
||||||
|
from atst.domain.projects import Projects
|
||||||
|
from atst.domain.environments import Environments
|
||||||
|
from atst.forms.new_project import NewProjectForm
|
||||||
|
|
||||||
bp = Blueprint("workspaces", __name__)
|
bp = Blueprint("workspaces", __name__)
|
||||||
|
|
||||||
|
|
||||||
@bp.context_processor
|
@bp.context_processor
|
||||||
def workspace():
|
def workspace():
|
||||||
workspace = None
|
workspace = None
|
||||||
if "workspace_id" in http_request.view_args:
|
if "workspace_id" in http_request.view_args:
|
||||||
workspace = Workspaces.get(http_request.view_args["workspace_id"])
|
workspace = Workspaces.get(
|
||||||
return { "workspace": workspace }
|
g.current_user, http_request.view_args["workspace_id"]
|
||||||
|
)
|
||||||
|
return {"workspace": workspace}
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/workspaces")
|
@bp.route("/workspaces")
|
||||||
def workspaces():
|
def workspaces():
|
||||||
return render_template("workspaces.html", page=5, workspaces=Workspaces.get_many())
|
workspaces = Workspaces.get_many(g.current_user)
|
||||||
|
return render_template("workspaces.html", page=5, workspaces=workspaces)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/workspaces/<workspace_id>/projects")
|
@bp.route("/workspaces/<workspace_id>/projects")
|
||||||
def workspace_projects(workspace_id):
|
def workspace_projects(workspace_id):
|
||||||
projects = Projects.get_many(workspace_id)
|
workspace = Workspaces.get(g.current_user, workspace_id)
|
||||||
return render_template("workspace_projects.html", projects=projects)
|
return render_template("workspace_projects.html", workspace=workspace)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/workspaces/<workspace_id>")
|
||||||
|
def show_workspace(workspace_id):
|
||||||
|
return redirect(url_for("workspaces.workspace_projects", workspace_id=workspace_id))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/workspaces/<workspace_id>/members")
|
@bp.route("/workspaces/<workspace_id>/members")
|
||||||
def workspace_members(workspace_id):
|
def workspace_members(workspace_id):
|
||||||
members = Members.get_many(workspace_id)
|
workspace = Workspaces.get(g.current_user, workspace_id)
|
||||||
return render_template("workspace_members.html", members=members)
|
return render_template("workspace_members.html", workspace=workspace)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/workspaces/<workspace_id>/reports")
|
@bp.route("/workspaces/<workspace_id>/reports")
|
||||||
def workspace_reports(workspace_id):
|
def workspace_reports(workspace_id):
|
||||||
return render_template("workspace_reports.html")
|
return render_template("workspace_reports.html", workspace_id=workspace_id)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/workspaces/<workspace_id>/projects/new")
|
||||||
|
def new_project(workspace_id):
|
||||||
|
workspace = Workspaces.get_for_update(g.current_user, workspace_id)
|
||||||
|
form = NewProjectForm()
|
||||||
|
return render_template("workspace_project_new.html", workspace=workspace, form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/workspaces/<workspace_id>/projects", methods=["POST"])
|
||||||
|
def update_project(workspace_id):
|
||||||
|
workspace = Workspaces.get_for_update(g.current_user, workspace_id)
|
||||||
|
form = NewProjectForm(http_request.form)
|
||||||
|
|
||||||
|
if form.validate():
|
||||||
|
project_data = form.data
|
||||||
|
project = Projects.create(
|
||||||
|
workspace, project_data["name"], project_data["description"]
|
||||||
|
)
|
||||||
|
Environments.create(project, project_data["environment_name"])
|
||||||
|
return redirect(
|
||||||
|
url_for("workspaces.workspace_projects", workspace_id=workspace.id)
|
||||||
|
)
|
||||||
|
46
js/components/forms/new_project.js
Normal file
46
js/components/forms/new_project.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import textinput from '../text_input'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'new-project',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
textinput
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
initialData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function () {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
environments = ['']
|
||||||
|
} = this.initialData
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
environments,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted: function () {
|
||||||
|
this.$root.$on('onEnvironmentAdded', this.addEnvironment)
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
addEnvironment: function (event) {
|
||||||
|
this.environments.push('')
|
||||||
|
},
|
||||||
|
|
||||||
|
removeEnvironment: function (index) {
|
||||||
|
if (this.environments.length > 1) {
|
||||||
|
this.environments.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import checkboxinput from './components/checkbox_input'
|
|||||||
import DetailsOfUse from './components/forms/details_of_use'
|
import DetailsOfUse from './components/forms/details_of_use'
|
||||||
import poc from './components/forms/poc'
|
import poc from './components/forms/poc'
|
||||||
import financial from './components/forms/financial'
|
import financial from './components/forms/financial'
|
||||||
|
import NewProject from './components/forms/new_project'
|
||||||
|
|
||||||
Vue.use(VTooltip)
|
Vue.use(VTooltip)
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ const app = new Vue({
|
|||||||
DetailsOfUse,
|
DetailsOfUse,
|
||||||
poc,
|
poc,
|
||||||
financial,
|
financial,
|
||||||
|
NewProject
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
closeModal: function(name) {
|
closeModal: function(name) {
|
||||||
|
@ -8,6 +8,8 @@ sys.path.append(parent_dir)
|
|||||||
from atst.app import make_config, make_app
|
from atst.app import make_config, make_app
|
||||||
from atst.domain.users import Users
|
from atst.domain.users import Users
|
||||||
from atst.domain.requests import Requests
|
from atst.domain.requests import Requests
|
||||||
|
from atst.domain.workspaces import Workspaces
|
||||||
|
from atst.domain.projects import Projects
|
||||||
from atst.domain.exceptions import AlreadyExistsError
|
from atst.domain.exceptions import AlreadyExistsError
|
||||||
from tests.factories import RequestFactory
|
from tests.factories import RequestFactory
|
||||||
from atst.routes.dev import _DEV_USERS as DEV_USERS
|
from atst.routes.dev import _DEV_USERS as DEV_USERS
|
||||||
@ -23,11 +25,20 @@ def seed_db():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
for user in users:
|
for user in users:
|
||||||
|
requests = []
|
||||||
for dollar_value in [1, 200, 3000, 40000, 500000, 1000000]:
|
for dollar_value in [1, 200, 3000, 40000, 500000, 1000000]:
|
||||||
request = Requests.create(
|
request = Requests.create(
|
||||||
user, RequestFactory.build_request_body(user, dollar_value)
|
user, RequestFactory.build_request_body(user, dollar_value)
|
||||||
)
|
)
|
||||||
Requests.submit(request)
|
Requests.submit(request)
|
||||||
|
requests.append(request)
|
||||||
|
|
||||||
|
workspace = Workspaces.create(requests[0], name="{}'s workspace".format(user.first_name))
|
||||||
|
Projects.create(
|
||||||
|
workspace=workspace,
|
||||||
|
name="First Project",
|
||||||
|
description="This is our first project."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -15,11 +15,16 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.icon-tooltip {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin block-list__title {
|
@mixin block-list__title {
|
||||||
@include h4;
|
@include h4;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
line-height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin block-list__footer {
|
@mixin block-list__footer {
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
background: none;
|
background: none;
|
||||||
transition: background-color $hover-transition-time;
|
transition: background-color $hover-transition-time;
|
||||||
border-radius: $gap / 2;
|
border-radius: $gap / 2;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
@include icon-color($color-primary);
|
@include icon-color($color-primary);
|
||||||
|
@ -49,4 +49,8 @@
|
|||||||
&.icon--large {
|
&.icon--large {
|
||||||
@include icon-size(24);
|
@include icon-size(24);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.icon--remove {
|
||||||
|
@include icon-color($color-red);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,13 +63,23 @@
|
|||||||
|
|
||||||
.panel__heading {
|
.panel__heading {
|
||||||
margin: $gap * 2;
|
margin: $gap * 2;
|
||||||
|
|
||||||
@include media($medium-screen) {
|
@include media($medium-screen) {
|
||||||
margin: $gap * 4;
|
margin: $gap * 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-tooltip {
|
||||||
|
margin-left: $gap*2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--grow {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,10 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.usa-input {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.project-list-item__environment__link {
|
.project-list-item__environment__link {
|
||||||
@include icon-link;
|
@include icon-link;
|
||||||
@include icon-link-large;
|
@include icon-link-large;
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
subnav=[
|
subnav=[
|
||||||
{
|
{
|
||||||
"label": "Add New Project",
|
"label": "Add New Project",
|
||||||
"href":"/",
|
"href": url_for('workspaces.new_project', workspace_id=workspace.id),
|
||||||
"active": g.matchesPath('workspaces/projects/new'),
|
"active": g.matchesPath('\/workspaces\/[A-Za-z0-9-]*\/projects'),
|
||||||
"icon": "plus"
|
"icon": "plus"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -88,7 +88,7 @@
|
|||||||
<footer class='block-list__footer'>
|
<footer class='block-list__footer'>
|
||||||
<a href='/' class='icon-link'>
|
<a href='/' class='icon-link'>
|
||||||
{% module Icon('plus') %}
|
{% module Icon('plus') %}
|
||||||
<span>Add another environment</span>
|
<span class="icon-link">Add another environment</span>
|
||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{% block workspace_content %}
|
{% block workspace_content %}
|
||||||
|
|
||||||
{% if not members %}
|
{% if not workspace.members %}
|
||||||
|
|
||||||
{{ EmptyState(
|
{{ EmptyState(
|
||||||
'There are currently no members in this Workspace.',
|
'There are currently no members in this Workspace.',
|
||||||
@ -58,7 +58,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for m in members %}
|
{% for m in workspace.members %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/workspaces/123456/members/789/edit" class="icon-link icon-link--large">{{ m['first_name'] }} {{ m['last_name'] }}</a></td>
|
<td><a href="/workspaces/123456/members/789/edit" class="icon-link icon-link--large">{{ m['first_name'] }} {{ m['last_name'] }}</a></td>
|
||||||
<td class='table-cell--shrink'>{% if m['num_projects'] == '0' %} <span class="label label--info">No Project Access</span> {% endif %}</td>
|
<td class='table-cell--shrink'>{% if m['num_projects'] == '0' %} <span class="label label--info">No Project Access</span> {% endif %}</td>
|
||||||
|
59
templates/workspace_project_new.html
Normal file
59
templates/workspace_project_new.html
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
{% from "components/icon.html" import Icon %}
|
||||||
|
{% from "components/text_input.html" import TextInput %}
|
||||||
|
{% from "components/tooltip.html" import Tooltip %}
|
||||||
|
|
||||||
|
{% extends "base_workspace.html" %}
|
||||||
|
|
||||||
|
{% block workspace_content %}
|
||||||
|
<new-project inline-template v-bind:initial-data='{{ form.data|tojson }}'>
|
||||||
|
<form method="POST" action="{{ url_for('workspaces.update_project', workspace_id=workspace.id) }}" >
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel__heading panel__heading--grow">
|
||||||
|
<h1>Add a new project</h1>
|
||||||
|
{{ Tooltip(
|
||||||
|
"AT-AT allows you to organize your workspace into multiple projects, each of which may have environments.",
|
||||||
|
title="learn more"
|
||||||
|
)}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="panel__content">
|
||||||
|
{{ TextInput(form.name) }}
|
||||||
|
{{ TextInput(form.description, paragraph=True) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block-list project-list-item">
|
||||||
|
<header class="block-list__header">
|
||||||
|
<h2 class="block-list__title">Project Environments</h2>
|
||||||
|
{{ Tooltip(
|
||||||
|
"Each environment created within a project is an enclave of cloud resources that is logically separated from each other for increased security.",
|
||||||
|
title="learn more"
|
||||||
|
)}}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li v-for="(_, i) in environments" class="block-list__item">
|
||||||
|
{{ TextInput(form.environment_name) }}
|
||||||
|
<span class="icon-link icon-link--danger icon-link--vertical" v-on:click="removeEnvironment(i)">{{ Icon('x') }} Remove</span>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="block-list__footer">
|
||||||
|
<a v-on:click="addEnvironment" class="icon-link">Add another environment</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="action-group">
|
||||||
|
<input type="submit" value="Create Project" class="usa-button usa-button-primary">
|
||||||
|
<a href="{{ url_for('workspaces.workspace_projects', workspace_id=workspace.id) }}" class="action-group__action">Cancel</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</new-project>
|
||||||
|
{% endblock %}
|
@ -1,24 +1,34 @@
|
|||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
|
{% from "components/alert.html" import Alert %}
|
||||||
|
|
||||||
{% extends "base_workspace.html" %}
|
{% extends "base_workspace.html" %}
|
||||||
|
|
||||||
{% block workspace_content %}
|
{% block workspace_content %}
|
||||||
|
|
||||||
{% for project in projects %}
|
{% if request.args.get("newWorkspace") %}
|
||||||
|
{{ Alert('Workspace created!',
|
||||||
|
message="\
|
||||||
|
<p>You are now ready to create projects and environments within the JEDI Cloud.</p>
|
||||||
|
",
|
||||||
|
level='success'
|
||||||
|
) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for project in workspace.projects %}
|
||||||
<div class='block-list project-list-item'>
|
<div class='block-list project-list-item'>
|
||||||
<header class='block-list__header'>
|
<header class='block-list__header'>
|
||||||
<h2 class='block-list__title'>{{ project['name'] }} ({{ project['environments']|length }} environments)</h2>
|
<h2 class='block-list__title'>{{ project.name }} ({{ project.environments|length }} environments)</h2>
|
||||||
<a class='icon-link' href=''>
|
<a class='icon-link' href='/workspaces/123456/projects/789/edit'>
|
||||||
{{ Icon('edit') }}
|
{{ Icon('edit') }}
|
||||||
<span>edit</span>
|
<span>edit</span>
|
||||||
</a>
|
</a>
|
||||||
</header>
|
</header>
|
||||||
<ul>
|
<ul>
|
||||||
{% for environment in project['environments'] %}
|
{% for environment in project.environments %}
|
||||||
<li class='block-list__item project-list-item__environment'>
|
<li class='block-list__item project-list-item__environment'>
|
||||||
<a href='/' target='_blank' rel='noopener noreferrer' class='project-list-item__environment__link'>
|
<a href='/' target='_blank' rel='noopener noreferrer' class='project-list-item__environment__link'>
|
||||||
{{ Icon('link') }}
|
{{ Icon('link') }}
|
||||||
<span>{{ environment["name"]}}</span>
|
<span>{{ environment.name }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class='project-list-item__environment__members'>
|
<div class='project-list-item__environment__members'>
|
||||||
|
@ -11,16 +11,16 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for w in workspaces %}
|
{% for workspace in workspaces %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a class='icon-link icon-link--large' href="/workspaces/{{w['task_order']['number']}}/projects">{{ w['name'] }}</a><br>
|
<a class='icon-link icon-link--large' href="/workspaces/{{ workspace.id }}/projects">{{ workspace.name }}</a><br>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
#{{ w['task_order']['number'] }}
|
#{{ workspace.task_order.number }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="label">{{ w['user_count'] }}</span><span class='h6'>Users</span>
|
<span class="label">{{ workspace.user_count }}</span><span class='h6'>Users</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
{% extends "base.html.to" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div class="usa-width-one-whole empty-state">
|
|
||||||
<p>There are currently no JEDI workspaces</p>
|
|
||||||
<a href="" class="usa-button">New Workspace</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{% end %}
|
|
||||||
|
|
@ -1,32 +1,30 @@
|
|||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from atst.domain.workspace_users import WorkspaceUsers
|
from atst.domain.workspace_users import WorkspaceUsers
|
||||||
from atst.domain.users import Users
|
from atst.domain.users import Users
|
||||||
|
from tests.factories import WorkspaceFactory
|
||||||
|
|
||||||
|
|
||||||
def test_can_create_new_workspace_user():
|
def test_can_create_new_workspace_user():
|
||||||
workspace_id = uuid4()
|
workspace = WorkspaceFactory.create()
|
||||||
user = Users.create("developer")
|
new_user = Users.create("developer")
|
||||||
|
|
||||||
workspace_user_dicts = [{"id": user.id, "workspace_role": "owner"}]
|
workspace_user_dicts = [{"id": new_user.id, "workspace_role": "owner"}]
|
||||||
|
workspace_users = WorkspaceUsers.add_many(workspace.id, workspace_user_dicts)
|
||||||
|
|
||||||
workspace_users = WorkspaceUsers.add_many(workspace_id, workspace_user_dicts)
|
assert workspace_users[0].user.id == new_user.id
|
||||||
|
|
||||||
assert workspace_users[0].user.id == user.id
|
|
||||||
assert workspace_users[0].user.atat_role.name == "developer"
|
assert workspace_users[0].user.atat_role.name == "developer"
|
||||||
assert workspace_users[0].workspace_role.role.name == "owner"
|
assert workspace_users[0].workspace_role.role.name == "owner"
|
||||||
|
|
||||||
|
|
||||||
def test_can_update_existing_workspace_user():
|
def test_can_update_existing_workspace_user():
|
||||||
workspace_id = uuid4()
|
workspace = WorkspaceFactory.create()
|
||||||
user = Users.create("developer")
|
new_user = Users.create("developer")
|
||||||
|
|
||||||
WorkspaceUsers.add_many(
|
WorkspaceUsers.add_many(
|
||||||
workspace_id, [{"id": user.id, "workspace_role": "owner"}]
|
workspace.id, [{"id": new_user.id, "workspace_role": "owner"}]
|
||||||
)
|
)
|
||||||
workspace_users = WorkspaceUsers.add_many(
|
workspace_users = WorkspaceUsers.add_many(
|
||||||
workspace_id, [{"id": user.id, "workspace_role": "developer"}]
|
workspace.id, [{"id": new_user.id, "workspace_role": "developer"}]
|
||||||
)
|
)
|
||||||
|
|
||||||
assert workspace_users[0].user.id == user.id
|
assert workspace_users[0].user.id == new_user.id
|
||||||
assert workspace_users[0].workspace_role.role.name == "developer"
|
assert workspace_users[0].workspace_role.role.name == "developer"
|
||||||
|
89
tests/domain/test_workspaces.py
Normal file
89
tests/domain/test_workspaces.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import pytest
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from atst.domain.exceptions import NotFoundError, UnauthorizedError
|
||||||
|
from atst.domain.workspaces import Workspaces
|
||||||
|
from atst.domain.workspace_users import WorkspaceUsers
|
||||||
|
|
||||||
|
from tests.factories import WorkspaceFactory, RequestFactory, UserFactory
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_create_workspace():
|
||||||
|
request = RequestFactory.create()
|
||||||
|
workspace = Workspaces.create(request, name="frugal-whale")
|
||||||
|
assert workspace.name == "frugal-whale"
|
||||||
|
assert workspace.request == request
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_workspace_name_is_request_id():
|
||||||
|
request = RequestFactory.create()
|
||||||
|
workspace = Workspaces.create(request)
|
||||||
|
assert workspace.name == str(request.id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_nonexistent_workspace_raises():
|
||||||
|
with pytest.raises(NotFoundError):
|
||||||
|
Workspaces.get(UserFactory.build(), uuid4())
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_get_workspace_by_request():
|
||||||
|
workspace = WorkspaceFactory.create()
|
||||||
|
found = Workspaces.get_by_request(workspace.request)
|
||||||
|
assert workspace == found
|
||||||
|
|
||||||
|
|
||||||
|
def test_creating_workspace_adds_owner():
|
||||||
|
user = UserFactory.create()
|
||||||
|
request = RequestFactory.create(creator=user)
|
||||||
|
workspace = Workspaces.create(request)
|
||||||
|
assert workspace.roles[0].user == user
|
||||||
|
|
||||||
|
|
||||||
|
def test_workspace_has_timestamps():
|
||||||
|
request = RequestFactory.create()
|
||||||
|
workspace = Workspaces.create(request)
|
||||||
|
assert workspace.time_created == workspace.time_updated
|
||||||
|
|
||||||
|
|
||||||
|
def test_workspaces_get_ensures_user_is_in_workspace():
|
||||||
|
owner = UserFactory.create()
|
||||||
|
outside_user = UserFactory.create()
|
||||||
|
workspace = Workspaces.create(RequestFactory.create(creator=owner))
|
||||||
|
|
||||||
|
workspace_ = Workspaces.get(owner, workspace.id)
|
||||||
|
assert workspace_ == workspace
|
||||||
|
|
||||||
|
with pytest.raises(UnauthorizedError):
|
||||||
|
Workspaces.get(outside_user, workspace.id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_workspaces_get_many_with_no_workspaces():
|
||||||
|
workspaces = Workspaces.get_many(UserFactory.build())
|
||||||
|
assert workspaces == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_workspaces_get_many_returns_a_users_workspaces():
|
||||||
|
user = UserFactory.create()
|
||||||
|
users_workspace = Workspaces.create(RequestFactory.create(creator=user))
|
||||||
|
|
||||||
|
# random workspace
|
||||||
|
Workspaces.create(RequestFactory.create())
|
||||||
|
|
||||||
|
assert Workspaces.get_many(user) == [users_workspace]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_for_update_allows_owner():
|
||||||
|
owner = UserFactory.create()
|
||||||
|
workspace = Workspaces.create(RequestFactory.create(creator=owner))
|
||||||
|
Workspaces.get_for_update(owner, workspace.id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_for_update_blocks_developer():
|
||||||
|
owner = UserFactory.create()
|
||||||
|
developer = UserFactory.create()
|
||||||
|
|
||||||
|
workspace = Workspaces.create(RequestFactory.create(creator=owner))
|
||||||
|
WorkspaceUsers.add(developer, workspace.id, "developer")
|
||||||
|
|
||||||
|
with pytest.raises(UnauthorizedError):
|
||||||
|
Workspaces.get_for_update(developer, workspace.id)
|
@ -10,6 +10,7 @@ from atst.models.pe_number import PENumber
|
|||||||
from atst.models.task_order import TaskOrder
|
from atst.models.task_order import TaskOrder
|
||||||
from atst.models.user import User
|
from atst.models.user import User
|
||||||
from atst.models.role import Role
|
from atst.models.role import Role
|
||||||
|
from atst.models.workspace import Workspace
|
||||||
from atst.models.request_status_event import RequestStatusEvent
|
from atst.models.request_status_event import RequestStatusEvent
|
||||||
from atst.domain.roles import Roles
|
from atst.domain.roles import Roles
|
||||||
|
|
||||||
@ -102,3 +103,12 @@ class PENumberFactory(factory.alchemy.SQLAlchemyModelFactory):
|
|||||||
class TaskOrderFactory(factory.alchemy.SQLAlchemyModelFactory):
|
class TaskOrderFactory(factory.alchemy.SQLAlchemyModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TaskOrder
|
model = TaskOrder
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceFactory(factory.alchemy.SQLAlchemyModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = Workspace
|
||||||
|
|
||||||
|
request = factory.SubFactory(RequestFactory)
|
||||||
|
# name it the same as the request ID by default
|
||||||
|
name = factory.LazyAttribute(lambda w: w.request.id)
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import re
|
|
||||||
import pytest
|
|
||||||
import urllib
|
import urllib
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|
||||||
from atst.eda_client import MockEDAClient
|
from atst.eda_client import MockEDAClient
|
||||||
|
|
||||||
from tests.mocks import MOCK_REQUEST, MOCK_USER
|
from tests.mocks import MOCK_REQUEST, MOCK_USER
|
||||||
from tests.factories import PENumberFactory
|
from tests.factories import PENumberFactory, RequestFactory
|
||||||
|
|
||||||
|
|
||||||
class TestPENumberInForm:
|
class TestPENumberInForm:
|
||||||
@ -39,14 +37,13 @@ class TestPENumberInForm:
|
|||||||
|
|
||||||
def _set_monkeypatches(self, monkeypatch):
|
def _set_monkeypatches(self, monkeypatch):
|
||||||
monkeypatch.setattr("atst.forms.financial.FinancialForm.validate", lambda s: True)
|
monkeypatch.setattr("atst.forms.financial.FinancialForm.validate", lambda s: True)
|
||||||
monkeypatch.setattr("atst.domain.requests.Requests.get", lambda i: MOCK_REQUEST)
|
|
||||||
monkeypatch.setattr("atst.domain.auth.get_current_user", lambda *args: MOCK_USER)
|
monkeypatch.setattr("atst.domain.auth.get_current_user", lambda *args: MOCK_USER)
|
||||||
|
|
||||||
def submit_data(self, client, data, extended=False):
|
def submit_data(self, client, data, extended=False):
|
||||||
url_kwargs = {"request_id": MOCK_REQUEST.id}
|
request = RequestFactory.create(body=MOCK_REQUEST.body)
|
||||||
|
url_kwargs = {"request_id": request.id}
|
||||||
if extended:
|
if extended:
|
||||||
url_kwargs["extended"] = True
|
url_kwargs["extended"] = True
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
url_for("requests.financial_verification", **url_kwargs),
|
url_for("requests.financial_verification", **url_kwargs),
|
||||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||||
@ -72,7 +69,7 @@ class TestPENumberInForm:
|
|||||||
response = self.submit_data(client, data)
|
response = self.submit_data(client, data)
|
||||||
|
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert "/requests/financial_verification_submitted" in response.headers.get("Location")
|
assert "/workspaces" in response.headers.get("Location")
|
||||||
|
|
||||||
def test_submit_request_form_with_new_valid_pe_id(self, monkeypatch, client):
|
def test_submit_request_form_with_new_valid_pe_id(self, monkeypatch, client):
|
||||||
self._set_monkeypatches(monkeypatch)
|
self._set_monkeypatches(monkeypatch)
|
||||||
@ -84,7 +81,7 @@ class TestPENumberInForm:
|
|||||||
response = self.submit_data(client, data)
|
response = self.submit_data(client, data)
|
||||||
|
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert "/requests/financial_verification_submitted" in response.headers.get("Location")
|
assert "/workspaces" in response.headers.get("Location")
|
||||||
|
|
||||||
def test_submit_request_form_with_missing_pe_id(self, monkeypatch, client):
|
def test_submit_request_form_with_missing_pe_id(self, monkeypatch, client):
|
||||||
self._set_monkeypatches(monkeypatch)
|
self._set_monkeypatches(monkeypatch)
|
||||||
@ -132,4 +129,4 @@ class TestPENumberInForm:
|
|||||||
response = self.submit_data(client, data, extended=True)
|
response = self.submit_data(client, data, extended=True)
|
||||||
|
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert "/requests/financial_verification_submitted" in response.headers.get("Location")
|
assert "/workspaces" in response.headers.get("Location")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user