extend soft delete functionality to app and env roles

This commit is contained in:
dandds 2019-04-10 10:42:36 -04:00
parent dac764ab82
commit 0bde431a70
15 changed files with 81 additions and 35 deletions

View File

@ -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
Create Date: 2019-04-09 06:16:15.445951
Create Date: 2019-04-10 09:40:37.688157
"""
from alembic import op
@ -11,7 +11,7 @@ from sqlalchemy.sql import expression
# revision identifiers, used by Alembic.
revision = 'fd0cf917f682'
revision = '014e4bceb947'
down_revision = '32438a35cfb5'
branch_labels = None
depends_on = None
@ -19,13 +19,17 @@ depends_on = None
def upgrade():
# ### 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('environments', 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('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 ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('environments', 'deleted')
op.drop_column('environment_roles', 'deleted')
op.drop_column('applications', 'deleted')
op.drop_column('application_roles', 'deleted')
# ### end Alembic commands ###

View File

@ -75,5 +75,9 @@ class Applications(object):
application.deleted = True
for role in application.roles:
role.deleted = True
db.session.add(role)
db.session.add(application)
db.session.commit()

View File

@ -102,8 +102,12 @@ class Environments(object):
@classmethod
def delete(cls, environment, commit=False):
environment.deleted = True
db.session.add(environment)
for role in environment.roles:
role.deleted = True
db.session.add(role)
if commit:
db.session.commit()

View File

@ -1,4 +1,4 @@
from sqlalchemy import Column, ForeignKey, String, Boolean
from sqlalchemy import Column, ForeignKey, String
from sqlalchemy.orm import relationship
from atst.models import Base
@ -6,7 +6,9 @@ from atst.models.types import Id
from atst.models import mixins
class Application(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
class Application(
Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin
):
__tablename__ = "applications"
id = Id()
@ -22,8 +24,6 @@ class Application(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
)
roles = relationship("ApplicationRole")
deleted = Column(Boolean, default=False)
@property
def users(self):
return set(role.user for role in self.roles)
@ -43,9 +43,4 @@ class Application(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
@property
def history(self):
previous_state = self.get_changes()
change_set = {}
if "deleted" in previous_state:
change_set["deleted"] = previous_state["deleted"]
return change_set
return self.get_changes()

View File

@ -26,7 +26,11 @@ application_roles_permission_sets = Table(
class ApplicationRole(
Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.PermissionsMixin
Base,
mixins.TimestampsMixin,
mixins.AuditableMixin,
mixins.PermissionsMixin,
mixins.DeletableMixin,
):
__tablename__ = "application_roles"
@ -51,6 +55,10 @@ class ApplicationRole(
self.application.name, self.user_id, self.id, self.permissions
)
@property
def history(self):
return self.get_changes()
Index(
"application_role_user_application",

View File

@ -1,4 +1,4 @@
from sqlalchemy import Column, ForeignKey, String, Boolean
from sqlalchemy import Column, ForeignKey, String
from sqlalchemy.orm import relationship
from atst.models import Base
@ -6,7 +6,9 @@ from atst.models.types import Id
from atst.models import mixins
class Environment(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
class Environment(
Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin
):
__tablename__ = "environments"
id = Id()
@ -17,8 +19,6 @@ class Environment(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
cloud_id = Column(String)
deleted = Column(Boolean, default=False)
@property
def users(self):
return [r.user for r in self.roles]
@ -49,9 +49,4 @@ class Environment(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
@property
def history(self):
previous_state = self.get_changes()
change_set = {}
if "deleted" in previous_state:
change_set["deleted"] = previous_state["deleted"]
return change_set
return self.get_changes()

View File

@ -13,7 +13,9 @@ class CSPRole(Enum):
TECHNICAL_READ = "Technical Read-only"
class EnvironmentRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
class EnvironmentRole(
Base, mixins.TimestampsMixin, mixins.AuditableMixin, mixins.DeletableMixin
):
__tablename__ = "environment_roles"
id = types.Id()

View File

@ -1,3 +1,4 @@
from .timestamps import TimestampsMixin
from .auditable import AuditableMixin
from .permissions import PermissionsMixin
from .deletable import DeletableMixin

View 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())

View File

@ -25,7 +25,11 @@ class User(
permission_sets = relationship("PermissionSet", secondary=users_permission_sets)
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)
dod_id = Column(String, unique=True, nullable=False)

View File

@ -12,3 +12,4 @@ data:
RQ_QUEUES: atat-test
CRL_STORAGE_PROVIDER: CLOUDFILES
LOG_JSON: "true"
DEBUG: "false"

View File

@ -5,6 +5,7 @@ from atst.domain.exceptions import NotFoundError
from tests.factories import (
ApplicationFactory,
ApplicationRoleFactory,
UserFactory,
PortfolioFactory,
EnvironmentFactory,
@ -71,17 +72,20 @@ def test_get_excludes_deleted():
def test_delete_application(session):
app = ApplicationFactory.create()
app_role = ApplicationRoleFactory.create(user=UserFactory.create(), application=app)
env1 = EnvironmentFactory.create(application=app)
env2 = EnvironmentFactory.create(application=app)
assert not app.deleted
assert not env1.deleted
assert not env2.deleted
assert not app_role.deleted
Applications.delete(app)
assert app.deleted
assert env1.deleted
assert env2.deleted
assert app_role.deleted
# changes are flushed
assert not session.dirty

View File

@ -10,6 +10,7 @@ from tests.factories import (
UserFactory,
PortfolioFactory,
EnvironmentFactory,
EnvironmentRoleFactory,
)
@ -206,13 +207,17 @@ def test_get_excludes_deleted():
def test_delete_environment(session):
env = EnvironmentFactory.create(application=ApplicationFactory.create())
env_role = EnvironmentRoleFactory.create(user=UserFactory.create(), environment=env)
assert not env.deleted
assert not env_role.deleted
Environments.delete(env)
assert env.deleted
assert env_role.deleted
# did not flush
assert env in session.dirty
assert session.dirty
Environments.delete(env, commit=True)
assert env.deleted
assert env_role.deleted
# flushed the change
assert not session.dirty

View File

@ -25,8 +25,6 @@ def test_add_user_to_environment():
def test_audit_event_for_environment_deletion(session):
EnvironmentFactory._meta.sqlalchemy_session_persistence = "flush"
env = EnvironmentFactory.create(application=ApplicationFactory.create())
env.deleted = True
session.add(env)
@ -38,4 +36,6 @@ def test_audit_event_for_environment_deletion(session):
.one()
)
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

View File

@ -3,7 +3,7 @@ from sqlalchemy.exc import InternalError
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():
@ -24,3 +24,16 @@ def test_cannot_update_dod_id(session):
session.add(user)
with pytest.raises(InternalError):
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