extend soft delete functionality to app and env roles
This commit is contained in:
parent
dac764ab82
commit
0bde431a70
@ -1,8 +1,8 @@
|
|||||||
"""add soft delete to applications and environments
|
"""add soft delete to application and environment resources and roles
|
||||||
|
|
||||||
Revision ID: fd0cf917f682
|
Revision ID: 014e4bceb947
|
||||||
Revises: 32438a35cfb5
|
Revises: 32438a35cfb5
|
||||||
Create Date: 2019-04-09 06:16:15.445951
|
Create Date: 2019-04-10 09:40:37.688157
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
@ -11,7 +11,7 @@ from sqlalchemy.sql import expression
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = 'fd0cf917f682'
|
revision = '014e4bceb947'
|
||||||
down_revision = '32438a35cfb5'
|
down_revision = '32438a35cfb5'
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
@ -19,13 +19,17 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.add_column('applications', sa.Column('deleted', sa.Boolean(), nullable=False, server_default=expression.false()))
|
op.add_column('application_roles', sa.Column('deleted', sa.Boolean(), server_default=expression.false(), nullable=False))
|
||||||
op.add_column('environments', sa.Column('deleted', sa.Boolean(), nullable=False, server_default=expression.false()))
|
op.add_column('applications', sa.Column('deleted', sa.Boolean(), server_default=expression.false(), nullable=False))
|
||||||
|
op.add_column('environment_roles', sa.Column('deleted', sa.Boolean(), server_default=expression.false(), nullable=False))
|
||||||
|
op.add_column('environments', sa.Column('deleted', sa.Boolean(), server_default=expression.false(), nullable=False))
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.drop_column('environments', 'deleted')
|
op.drop_column('environments', 'deleted')
|
||||||
|
op.drop_column('environment_roles', 'deleted')
|
||||||
op.drop_column('applications', 'deleted')
|
op.drop_column('applications', 'deleted')
|
||||||
|
op.drop_column('application_roles', 'deleted')
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
@ -75,5 +75,9 @@ class Applications(object):
|
|||||||
|
|
||||||
application.deleted = True
|
application.deleted = True
|
||||||
|
|
||||||
|
for role in application.roles:
|
||||||
|
role.deleted = True
|
||||||
|
db.session.add(role)
|
||||||
|
|
||||||
db.session.add(application)
|
db.session.add(application)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -102,8 +102,12 @@ class Environments(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def delete(cls, environment, commit=False):
|
def delete(cls, environment, commit=False):
|
||||||
environment.deleted = True
|
environment.deleted = True
|
||||||
|
|
||||||
db.session.add(environment)
|
db.session.add(environment)
|
||||||
|
|
||||||
|
for role in environment.roles:
|
||||||
|
role.deleted = True
|
||||||
|
db.session.add(role)
|
||||||
|
|
||||||
if commit:
|
if commit:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from sqlalchemy import Column, ForeignKey, String, Boolean
|
from sqlalchemy import Column, ForeignKey, String
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from atst.models import Base
|
from atst.models import Base
|
||||||
@ -6,7 +6,9 @@ from atst.models.types import Id
|
|||||||
from atst.models import mixins
|
from atst.models import mixins
|
||||||
|
|
||||||
|
|
||||||
class Application(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
|
class Application(
|
||||||
|
Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin
|
||||||
|
):
|
||||||
__tablename__ = "applications"
|
__tablename__ = "applications"
|
||||||
|
|
||||||
id = Id()
|
id = Id()
|
||||||
@ -22,8 +24,6 @@ class Application(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
|
|||||||
)
|
)
|
||||||
roles = relationship("ApplicationRole")
|
roles = relationship("ApplicationRole")
|
||||||
|
|
||||||
deleted = Column(Boolean, default=False)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def users(self):
|
def users(self):
|
||||||
return set(role.user for role in self.roles)
|
return set(role.user for role in self.roles)
|
||||||
@ -43,9 +43,4 @@ class Application(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def history(self):
|
def history(self):
|
||||||
previous_state = self.get_changes()
|
return self.get_changes()
|
||||||
change_set = {}
|
|
||||||
if "deleted" in previous_state:
|
|
||||||
change_set["deleted"] = previous_state["deleted"]
|
|
||||||
|
|
||||||
return change_set
|
|
||||||
|
@ -26,7 +26,11 @@ application_roles_permission_sets = Table(
|
|||||||
|
|
||||||
|
|
||||||
class ApplicationRole(
|
class ApplicationRole(
|
||||||
Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.PermissionsMixin
|
Base,
|
||||||
|
mixins.TimestampsMixin,
|
||||||
|
mixins.AuditableMixin,
|
||||||
|
mixins.PermissionsMixin,
|
||||||
|
mixins.DeletableMixin,
|
||||||
):
|
):
|
||||||
__tablename__ = "application_roles"
|
__tablename__ = "application_roles"
|
||||||
|
|
||||||
@ -51,6 +55,10 @@ class ApplicationRole(
|
|||||||
self.application.name, self.user_id, self.id, self.permissions
|
self.application.name, self.user_id, self.id, self.permissions
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def history(self):
|
||||||
|
return self.get_changes()
|
||||||
|
|
||||||
|
|
||||||
Index(
|
Index(
|
||||||
"application_role_user_application",
|
"application_role_user_application",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from sqlalchemy import Column, ForeignKey, String, Boolean
|
from sqlalchemy import Column, ForeignKey, String
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from atst.models import Base
|
from atst.models import Base
|
||||||
@ -6,7 +6,9 @@ from atst.models.types import Id
|
|||||||
from atst.models import mixins
|
from atst.models import mixins
|
||||||
|
|
||||||
|
|
||||||
class Environment(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
|
class Environment(
|
||||||
|
Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin
|
||||||
|
):
|
||||||
__tablename__ = "environments"
|
__tablename__ = "environments"
|
||||||
|
|
||||||
id = Id()
|
id = Id()
|
||||||
@ -17,8 +19,6 @@ class Environment(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
|
|||||||
|
|
||||||
cloud_id = Column(String)
|
cloud_id = Column(String)
|
||||||
|
|
||||||
deleted = Column(Boolean, default=False)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def users(self):
|
def users(self):
|
||||||
return [r.user for r in self.roles]
|
return [r.user for r in self.roles]
|
||||||
@ -49,9 +49,4 @@ class Environment(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def history(self):
|
def history(self):
|
||||||
previous_state = self.get_changes()
|
return self.get_changes()
|
||||||
change_set = {}
|
|
||||||
if "deleted" in previous_state:
|
|
||||||
change_set["deleted"] = previous_state["deleted"]
|
|
||||||
|
|
||||||
return change_set
|
|
||||||
|
@ -13,7 +13,9 @@ class CSPRole(Enum):
|
|||||||
TECHNICAL_READ = "Technical Read-only"
|
TECHNICAL_READ = "Technical Read-only"
|
||||||
|
|
||||||
|
|
||||||
class EnvironmentRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
|
class EnvironmentRole(
|
||||||
|
Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin
|
||||||
|
):
|
||||||
__tablename__ = "environment_roles"
|
__tablename__ = "environment_roles"
|
||||||
|
|
||||||
id = types.Id()
|
id = types.Id()
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
from .timestamps import TimestampsMixin
|
from .timestamps import TimestampsMixin
|
||||||
from .auditable import AuditableMixin
|
from .auditable import AuditableMixin
|
||||||
from .permissions import PermissionsMixin
|
from .permissions import PermissionsMixin
|
||||||
|
from .deletable import DeletableMixin
|
||||||
|
6
atst/models/mixins/deletable.py
Normal file
6
atst/models/mixins/deletable.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from sqlalchemy import Column, Boolean
|
||||||
|
from sqlalchemy.sql import expression
|
||||||
|
|
||||||
|
|
||||||
|
class DeletableMixin(object):
|
||||||
|
deleted = Column(Boolean, nullable=False, server_default=expression.false())
|
@ -25,7 +25,11 @@ class User(
|
|||||||
permission_sets = relationship("PermissionSet", secondary=users_permission_sets)
|
permission_sets = relationship("PermissionSet", secondary=users_permission_sets)
|
||||||
|
|
||||||
portfolio_roles = relationship("PortfolioRole", backref="user")
|
portfolio_roles = relationship("PortfolioRole", backref="user")
|
||||||
application_roles = relationship("ApplicationRole", backref="user")
|
application_roles = relationship(
|
||||||
|
"ApplicationRole",
|
||||||
|
backref="user",
|
||||||
|
primaryjoin="and_(ApplicationRole.user_id==User.id, ApplicationRole.deleted==False)",
|
||||||
|
)
|
||||||
|
|
||||||
email = Column(String, unique=True)
|
email = Column(String, unique=True)
|
||||||
dod_id = Column(String, unique=True, nullable=False)
|
dod_id = Column(String, unique=True, nullable=False)
|
||||||
|
@ -12,3 +12,4 @@ data:
|
|||||||
RQ_QUEUES: atat-test
|
RQ_QUEUES: atat-test
|
||||||
CRL_STORAGE_PROVIDER: CLOUDFILES
|
CRL_STORAGE_PROVIDER: CLOUDFILES
|
||||||
LOG_JSON: "true"
|
LOG_JSON: "true"
|
||||||
|
DEBUG: "false"
|
||||||
|
@ -5,6 +5,7 @@ from atst.domain.exceptions import NotFoundError
|
|||||||
|
|
||||||
from tests.factories import (
|
from tests.factories import (
|
||||||
ApplicationFactory,
|
ApplicationFactory,
|
||||||
|
ApplicationRoleFactory,
|
||||||
UserFactory,
|
UserFactory,
|
||||||
PortfolioFactory,
|
PortfolioFactory,
|
||||||
EnvironmentFactory,
|
EnvironmentFactory,
|
||||||
@ -71,17 +72,20 @@ def test_get_excludes_deleted():
|
|||||||
|
|
||||||
def test_delete_application(session):
|
def test_delete_application(session):
|
||||||
app = ApplicationFactory.create()
|
app = ApplicationFactory.create()
|
||||||
|
app_role = ApplicationRoleFactory.create(user=UserFactory.create(), application=app)
|
||||||
env1 = EnvironmentFactory.create(application=app)
|
env1 = EnvironmentFactory.create(application=app)
|
||||||
env2 = EnvironmentFactory.create(application=app)
|
env2 = EnvironmentFactory.create(application=app)
|
||||||
assert not app.deleted
|
assert not app.deleted
|
||||||
assert not env1.deleted
|
assert not env1.deleted
|
||||||
assert not env2.deleted
|
assert not env2.deleted
|
||||||
|
assert not app_role.deleted
|
||||||
|
|
||||||
Applications.delete(app)
|
Applications.delete(app)
|
||||||
|
|
||||||
assert app.deleted
|
assert app.deleted
|
||||||
assert env1.deleted
|
assert env1.deleted
|
||||||
assert env2.deleted
|
assert env2.deleted
|
||||||
|
assert app_role.deleted
|
||||||
|
|
||||||
# changes are flushed
|
# changes are flushed
|
||||||
assert not session.dirty
|
assert not session.dirty
|
||||||
|
@ -10,6 +10,7 @@ from tests.factories import (
|
|||||||
UserFactory,
|
UserFactory,
|
||||||
PortfolioFactory,
|
PortfolioFactory,
|
||||||
EnvironmentFactory,
|
EnvironmentFactory,
|
||||||
|
EnvironmentRoleFactory,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -206,13 +207,17 @@ def test_get_excludes_deleted():
|
|||||||
|
|
||||||
def test_delete_environment(session):
|
def test_delete_environment(session):
|
||||||
env = EnvironmentFactory.create(application=ApplicationFactory.create())
|
env = EnvironmentFactory.create(application=ApplicationFactory.create())
|
||||||
|
env_role = EnvironmentRoleFactory.create(user=UserFactory.create(), environment=env)
|
||||||
assert not env.deleted
|
assert not env.deleted
|
||||||
|
assert not env_role.deleted
|
||||||
Environments.delete(env)
|
Environments.delete(env)
|
||||||
assert env.deleted
|
assert env.deleted
|
||||||
|
assert env_role.deleted
|
||||||
# did not flush
|
# did not flush
|
||||||
assert env in session.dirty
|
assert session.dirty
|
||||||
|
|
||||||
Environments.delete(env, commit=True)
|
Environments.delete(env, commit=True)
|
||||||
assert env.deleted
|
assert env.deleted
|
||||||
|
assert env_role.deleted
|
||||||
# flushed the change
|
# flushed the change
|
||||||
assert not session.dirty
|
assert not session.dirty
|
||||||
|
@ -25,8 +25,6 @@ def test_add_user_to_environment():
|
|||||||
|
|
||||||
|
|
||||||
def test_audit_event_for_environment_deletion(session):
|
def test_audit_event_for_environment_deletion(session):
|
||||||
EnvironmentFactory._meta.sqlalchemy_session_persistence = "flush"
|
|
||||||
|
|
||||||
env = EnvironmentFactory.create(application=ApplicationFactory.create())
|
env = EnvironmentFactory.create(application=ApplicationFactory.create())
|
||||||
env.deleted = True
|
env.deleted = True
|
||||||
session.add(env)
|
session.add(env)
|
||||||
@ -38,4 +36,6 @@ def test_audit_event_for_environment_deletion(session):
|
|||||||
.one()
|
.one()
|
||||||
)
|
)
|
||||||
assert update_event.changed_state.get("deleted")
|
assert update_event.changed_state.get("deleted")
|
||||||
assert update_event.changed_state["deleted"] == [False, True]
|
before, after = update_event.changed_state["deleted"]
|
||||||
|
assert not before
|
||||||
|
assert after
|
||||||
|
@ -3,7 +3,7 @@ from sqlalchemy.exc import InternalError
|
|||||||
|
|
||||||
from atst.models.user import User
|
from atst.models.user import User
|
||||||
|
|
||||||
from tests.factories import UserFactory
|
from tests.factories import UserFactory, ApplicationFactory, ApplicationRoleFactory
|
||||||
|
|
||||||
|
|
||||||
def test_profile_complete_with_all_info():
|
def test_profile_complete_with_all_info():
|
||||||
@ -24,3 +24,16 @@ def test_cannot_update_dod_id(session):
|
|||||||
session.add(user)
|
session.add(user)
|
||||||
with pytest.raises(InternalError):
|
with pytest.raises(InternalError):
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def test_deleted_application_roles_are_ignored(session):
|
||||||
|
user = UserFactory.create()
|
||||||
|
app = ApplicationFactory.create()
|
||||||
|
app_role = ApplicationRoleFactory.create(user=user, application=app)
|
||||||
|
assert len(user.application_roles) == 1
|
||||||
|
|
||||||
|
app_role.deleted = True
|
||||||
|
session.add(app_role)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
assert len(user.application_roles) == 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user