diff --git a/src/euphorie/client/browser/consultancy.py b/src/euphorie/client/browser/consultancy.py index 826be50aa..20513a09c 100644 --- a/src/euphorie/client/browser/consultancy.py +++ b/src/euphorie/client/browser/consultancy.py @@ -255,7 +255,13 @@ def handle_POST(self): ), ) self.sqlsession.add(event) - self.context.session.consultancy.status = "validated" + if not self.context.session.is_locked: + locking_view = api.content.get_view( + name="locking_view", + context=self.context, + request=self.request, + ) + locking_view.create_event("lock_set") self.notify_admins() self.redirect() diff --git a/src/euphorie/client/browser/locking.py b/src/euphorie/client/browser/locking.py index 355325820..0dbb07834 100644 --- a/src/euphorie/client/browser/locking.py +++ b/src/euphorie/client/browser/locking.py @@ -34,13 +34,6 @@ def is_locked(self): """Return whether the session is locked.""" return self.context.session.is_locked - def is_validated(self): - """Return whether the session is validated.""" - consultancy = self.context.session.consultancy - if consultancy and consultancy.status == "validated": - return True - return self.context.session.consultancy - def show_actions(self): """Return whether we should show the actions in the menu.""" if self.is_locked: diff --git a/src/euphorie/client/browser/templates/consultancy.pt b/src/euphorie/client/browser/templates/consultancy.pt index b5f325e54..fd1fc743a 100644 --- a/src/euphorie/client/browser/templates/consultancy.pt +++ b/src/euphorie/client/browser/templates/consultancy.pt @@ -17,8 +17,7 @@ id="content-pane" tal:define=" consultant context/session/consultancy/account|nothing; - status context/session/consultancy/status|nothing; - is_validated python:status == 'validated'; + is_validated context/session/is_validated|nothing; current_user webhelpers/get_current_account; role_consultant python: consultant and current_user == consultant; " diff --git a/src/euphorie/client/browser/webhelpers.py b/src/euphorie/client/browser/webhelpers.py index 1bc5fe67a..1d163578f 100644 --- a/src/euphorie/client/browser/webhelpers.py +++ b/src/euphorie/client/browser/webhelpers.py @@ -1064,9 +1064,6 @@ def can_lock_session(self): @property @memoize def can_unlock_session(self): - session = self.traversed_session.session - if session.is_validated: - return False return self.can_lock_session @property diff --git a/src/euphorie/client/model.py b/src/euphorie/client/model.py index 1de62c265..284d6f56f 100644 --- a/src/euphorie/client/model.py +++ b/src/euphorie/client/model.py @@ -449,11 +449,6 @@ class Consultancy(BaseObject): back_populates="consultancy", ) - status = schema.Column( - types.Unicode(255), - default="pending", - ) - @implementer(IUser) class Account(BaseObject): @@ -829,14 +824,12 @@ def is_validated(self): .order_by(SessionEvent.time.desc()) ) # TODO: There should be something to remove the validation at a certain point + # see https://github.com/syslabcom/scrum/issues/1160 return bool(query.count()) @property def is_locked(self): """Check if the session is locked.""" - if self.is_validated: - return True - event = self.last_locking_event if not event: return False diff --git a/src/euphorie/client/tests/test_consultancy.py b/src/euphorie/client/tests/test_consultancy.py index a62ed0f6f..a63eabb49 100644 --- a/src/euphorie/client/tests/test_consultancy.py +++ b/src/euphorie/client/tests/test_consultancy.py @@ -13,6 +13,7 @@ from euphorie.content.tests.utils import BASIC_SURVEY from euphorie.testing import EuphorieIntegrationTestCase from plone import api +from time import sleep from z3c.saconfig import Session from zope.interface import alsoProvides @@ -158,6 +159,12 @@ def test_validate_permission_denied_to_other_member(self): account=self.consultant, session=self.traversed_session.session, ) + event = SessionEvent( + account_id=self.consultant.id, + session_id=self.traversed_session.session.id, + action="validation_requested", + ) + self.session.add(event) with api.env.adopt_user(user=other_member): with self._get_view( "panel-validate-risk-assessment", @@ -215,6 +222,12 @@ def test_validate_risk_assessment(self): account=self.consultant, session=self.traversed_session.session, ) + event = SessionEvent( + account_id=self.consultant.id, + session_id=self.traversed_session.session.id, + action="validation_requested", + ) + self.session.add(event) self.session.flush() with api.env.adopt_user(user=self.consultant): with self._get_view( @@ -231,7 +244,7 @@ def test_validate_risk_assessment(self): self.traversed_session.session.consultancy.account, self.consultant, ) - self.assertEqual(self.traversed_session.session.consultancy.status, "validated") + self.assertTrue(self.traversed_session.session.is_validated) with api.env.adopt_user(user=self.owner): with self._get_view( "consultancy", @@ -251,8 +264,7 @@ def test_validate_risk_assessment(self): timedelta(seconds=5), ) - # TODO check that assessment is locked - # self.assertTrue(self.traversed_session.session.locked) + self.assertTrue(self.traversed_session.session.is_locked) self.assertEqual(len(mail_fixture.storage), 2) recipients = { @@ -264,12 +276,81 @@ def test_validate_risk_assessment(self): {"valerie@labyrinth.social", "jessica@labyrinth.social"}, ) + # We need to wait at least one second because the datetime + # is stored with that accuracy + sleep(1) + + # if locking is enabled... + api.portal.set_registry_record("euphorie.use_locking_feature", True) + # ... then admin/owner can unlock + with api.env.adopt_user(user=self.owner): + with self._get_view( + "locking_view", + self.traversed_session, + self.traversed_session.session, + ) as view: + view.unset_lock() + + self.session.flush() + self.assertTrue(self.traversed_session.session.is_validated) + self.assertFalse(self.traversed_session.session.is_locked) + + def test_validate_locked_risk_assessment(self): + self.traversed_session.session.consultancy = Consultancy( + account=self.consultant, + session=self.traversed_session.session, + ) + event = SessionEvent( + account_id=self.consultant.id, + session_id=self.traversed_session.session.id, + action="validation_requested", + ) + self.session.add(event) + + api.portal.set_registry_record("euphorie.use_locking_feature", True) + with api.env.adopt_user(user=self.owner): + with self._get_view( + "locking_view", + self.traversed_session, + self.traversed_session.session, + ) as view: + view.set_lock() + original_lock_event = view.last_locking_event + + # Some time passes before the consultant validates the assessment + sleep(3) + + with api.env.adopt_user(user=self.consultant): + with self._get_view( + "panel-validate-risk-assessment", + self.traversed_session, + self.traversed_session.session, + ) as view: + view.request.method = "POST" + view.request.form = {"approved": "1"} + view() + + # consultant stays set + self.assertEqual( + self.traversed_session.session.consultancy.account, + self.consultant, + ) + self.assertTrue(self.traversed_session.session.is_validated) + self.assertTrue(self.traversed_session.session.is_locked) + with self._get_view( + "locking_view", + self.traversed_session, + self.traversed_session.session, + ) as view: + lock_event = view.last_locking_event + self.assertEqual(lock_event.account, self.owner) + self.assertEqual(lock_event.time, original_lock_event.time) + def test_delete_consultant(self): now = datetime.utcnow().replace(tzinfo=timezone.utc) self.traversed_session.session.consultancy = Consultancy( account=self.consultant, session=self.traversed_session.session, - status="validated", ) event = SessionEvent( account_id=self.consultant.id, @@ -289,7 +370,7 @@ def test_delete_consultant(self): self.session.delete(self.consultant) self.session.flush() - self.assertEqual(self.traversed_session.session.consultancy.status, "validated") + self.assertTrue(self.traversed_session.session.is_validated) with api.env.adopt_user(user=self.owner): with self._get_view( "consultancy", diff --git a/src/euphorie/deployment/upgrade/alembic/versions/20230531153726_drop_consultancy_status_column.py b/src/euphorie/deployment/upgrade/alembic/versions/20230531153726_drop_consultancy_status_column.py new file mode 100644 index 000000000..b5df5b56a --- /dev/null +++ b/src/euphorie/deployment/upgrade/alembic/versions/20230531153726_drop_consultancy_status_column.py @@ -0,0 +1,33 @@ +"""Drop consultancy status column + +Revision ID: 20230531153726 +Revises: 20230515143459 +Create Date: 2023-05-31 15:41:06.990714 + +""" +from alembic import op +from euphorie.deployment.upgrade.utils import has_column + +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "20230531153726" +down_revision = "20230515143459" +branch_labels = None +depends_on = None + + +def upgrade(): + if has_column("consultancy", "status"): + op.drop_column("consultancy", "status") + + +def downgrade(): + if not has_column("consultancy", "status"): + op.add_column( + "consultancy", + sa.Column( + "status", sa.VARCHAR(length=255), autoincrement=False, nullable=True + ), + ) diff --git a/src/euphorie/upgrade/deployment/v1/20230531153726_drop_consultancy_status_column/__init__.py b/src/euphorie/upgrade/deployment/v1/20230531153726_drop_consultancy_status_column/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/euphorie/upgrade/deployment/v1/20230531153726_drop_consultancy_status_column/upgrade.py b/src/euphorie/upgrade/deployment/v1/20230531153726_drop_consultancy_status_column/upgrade.py new file mode 100644 index 000000000..bb174aa2c --- /dev/null +++ b/src/euphorie/upgrade/deployment/v1/20230531153726_drop_consultancy_status_column/upgrade.py @@ -0,0 +1,9 @@ +from euphorie.deployment.upgrade.utils import alembic_upgrade_to +from ftw.upgrade import UpgradeStep + + +class DropConsultancyStatusColumn(UpgradeStep): + """Drop consultancy status column.""" + + def __call__(self): + alembic_upgrade_to(self.target_version)