From e46e5968ba6287f3859af562e83f4d5f76c69418 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Mon, 24 Apr 2023 21:32:39 +0200 Subject: [PATCH 01/18] Added upgrade step --- .../patient/profiles/default/metadata.xml | 10 +--- src/senaite/patient/upgrade/v01_04_000.py | 49 ++++++++++++++++++- src/senaite/patient/upgrade/v01_04_000.zcml | 9 ++++ 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/senaite/patient/profiles/default/metadata.xml b/src/senaite/patient/profiles/default/metadata.xml index f6ab94c..e2f505a 100644 --- a/src/senaite/patient/profiles/default/metadata.xml +++ b/src/senaite/patient/profiles/default/metadata.xml @@ -1,14 +1,6 @@ - - 1407 - - + 1408 profile-senaite.lims:default diff --git a/src/senaite/patient/upgrade/v01_04_000.py b/src/senaite/patient/upgrade/v01_04_000.py index f5bf2ab..52ad0ad 100644 --- a/src/senaite/patient/upgrade/v01_04_000.py +++ b/src/senaite/patient/upgrade/v01_04_000.py @@ -19,19 +19,22 @@ # Some rights reserved, see README and LICENSE. import transaction - from bika.lims import api from senaite.core.catalog import SAMPLE_CATALOG from senaite.core.upgrade import upgradestep from senaite.core.upgrade.utils import UpgradeUtils from senaite.patient import logger from senaite.patient.api import patient_search +from senaite.patient.catalog import PATIENT_CATALOG from senaite.patient.config import PRODUCT_NAME from senaite.patient.setuphandlers import setup_catalogs version = "1.4.0" profile = "profile-{0}:default".format(PRODUCT_NAME) +PATIENT_WORKFLOW = "senaite_patient_workflow" +PATIENT_FOLDER_WORKFLOW = "senaite_patient_folder_workflow" + @upgradestep(PRODUCT_NAME, version) def upgrade(tool): @@ -214,3 +217,47 @@ def allow_patients_in_clients(tool): setup.runImportStepFromProfile(profile, "plone.app.registry") logger.info("Allow patients in clients [DONE]") + + +def update_patient_workflows(tool): + """Update patient workflows and security settings + """ + logger.info("Update patient workflows ...") + + # import workflows + portal = tool.aq_inner.aq_parent + setup = portal.portal_setup + setup.runImportStepFromProfile(profile, "workflow") + wf_tool = api.get_tool("portal_workflow") + + # get patient folder + workflow + patientsfolder = portal.patients + patients_workflow = wf_tool.getWorkflowById(PATIENT_FOLDER_WORKFLOW) + + # update rolemappings + object security for patients folder + patients_workflow.updateRoleMappingsFor(patientsfolder) + patientsfolder.reindexObject(idxs=["allowedRolesAndUsers"]) + + # fetch patients + workflow + patients = api.search({"portal_type": "Patient"}, PATIENT_CATALOG) + total = len(patients) + patient_workflow = wf_tool.getWorkflowById(PATIENT_WORKFLOW) + + for num, patient in enumerate(patients): + obj = api.get_object(patient) + logger.info("Processing patient %s/%s: %s" + % (num+1, total, obj.Title())) + + # update rolemappings + object security for patient + patient_workflow.updateRoleMappingsFor(obj) + obj.reindexObject(idxs=["allowedRolesAndUsers"]) + + if num and num % 10 == 0: + logger.info("Commiting patient %s/%s" % (num+1, total)) + transaction.commit() + logger.info("Commit done") + + # Flush the object from memory + obj._p_deactivate() + + logger.info("Update patient workflows [DONE]") diff --git a/src/senaite/patient/upgrade/v01_04_000.zcml b/src/senaite/patient/upgrade/v01_04_000.zcml index fbc04a7..8fafff5 100644 --- a/src/senaite/patient/upgrade/v01_04_000.zcml +++ b/src/senaite/patient/upgrade/v01_04_000.zcml @@ -2,6 +2,15 @@ xmlns="http://namespaces.zope.org/zope" xmlns:genericsetup="http://namespaces.zope.org/genericsetup"> + + + Date: Mon, 24 Apr 2023 21:46:39 +0200 Subject: [PATCH 02/18] Added custom view permission --- src/senaite/patient/permissions.py | 1 + src/senaite/patient/permissions.zcml | 1 + src/senaite/patient/profiles/default/rolemap.xml | 7 +++++++ 3 files changed, 9 insertions(+) diff --git a/src/senaite/patient/permissions.py b/src/senaite/patient/permissions.py index 3aa66d9..4357947 100644 --- a/src/senaite/patient/permissions.py +++ b/src/senaite/patient/permissions.py @@ -19,6 +19,7 @@ # Some rights reserved, see README and LICENSE. ManagePatients = "senaite.patient: Manage Patients" +ViewPatients = "senaite.patient: View Patients" AddPatientFolder = "senaite.patient: Add PatientFolder" AddPatient = "senaite.patient: Add Patient" diff --git a/src/senaite/patient/permissions.zcml b/src/senaite/patient/permissions.zcml index 8f7f1bc..694b53e 100644 --- a/src/senaite/patient/permissions.zcml +++ b/src/senaite/patient/permissions.zcml @@ -3,6 +3,7 @@ i18n_domain="senaite.patient"> + diff --git a/src/senaite/patient/profiles/default/rolemap.xml b/src/senaite/patient/profiles/default/rolemap.xml index 32385dc..7c09b34 100644 --- a/src/senaite/patient/profiles/default/rolemap.xml +++ b/src/senaite/patient/profiles/default/rolemap.xml @@ -9,6 +9,13 @@ + + + + + + + From 4087fc2baa9890ba50708520a7ef4b5d1420d407 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Mon, 24 Apr 2023 21:55:51 +0200 Subject: [PATCH 03/18] Import rolemap and typeinfo --- src/senaite/patient/upgrade/v01_04_000.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/senaite/patient/upgrade/v01_04_000.py b/src/senaite/patient/upgrade/v01_04_000.py index 52ad0ad..f8d4686 100644 --- a/src/senaite/patient/upgrade/v01_04_000.py +++ b/src/senaite/patient/upgrade/v01_04_000.py @@ -224,14 +224,16 @@ def update_patient_workflows(tool): """ logger.info("Update patient workflows ...") - # import workflows + # import rolemap, workflow and typeinfo portal = tool.aq_inner.aq_parent setup = portal.portal_setup + setup.runImportStepFromProfile(profile, "rolemap") setup.runImportStepFromProfile(profile, "workflow") - wf_tool = api.get_tool("portal_workflow") + setup.runImportStepFromProfile(profile, "typeinfo") # get patient folder + workflow patientsfolder = portal.patients + wf_tool = api.get_tool("portal_workflow") patients_workflow = wf_tool.getWorkflowById(PATIENT_FOLDER_WORKFLOW) # update rolemappings + object security for patients folder From 493972439a05553791f5a314e40d64e01952b3e9 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Mon, 24 Apr 2023 21:56:20 +0200 Subject: [PATCH 04/18] View permissions --- src/senaite/patient/browser/configure.zcml | 14 +++++++++----- .../profiles/default/types/PatientFolder.xml | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/senaite/patient/browser/configure.zcml b/src/senaite/patient/browser/configure.zcml index 4b83e83..92e402b 100644 --- a/src/senaite/patient/browser/configure.zcml +++ b/src/senaite/patient/browser/configure.zcml @@ -16,7 +16,8 @@ name="view" for="senaite.patient.content.patientfolder.IPatientFolder" class=".patientfolder.PatientFolderView" - permission="zope2.View" + permission="senaite.patient.permissions.ViewPatients" + layer="senaite.patient.interfaces.ISenaitePatientLayer" /> @@ -24,14 +25,16 @@ name="patient-controlpanel" for="Products.CMFPlone.interfaces.IPloneSiteRoot" class=".controlpanel.PatientControlPanelView" - permission="senaite.core.permissions.ManageBika" - layer="senaite.patient.interfaces.ISenaitePatientLayer" /> + permission="senaite.patient.permissions.ManagePatients" + layer="senaite.patient.interfaces.ISenaitePatientLayer" + /> + name="senaite.patient.static" + /> + layer="senaite.patient.interfaces.ISenaitePatientLayer" + /> diff --git a/src/senaite/patient/profiles/default/types/PatientFolder.xml b/src/senaite/patient/profiles/default/types/PatientFolder.xml index 945c38c..cd2560e 100644 --- a/src/senaite/patient/profiles/default/types/PatientFolder.xml +++ b/src/senaite/patient/profiles/default/types/PatientFolder.xml @@ -82,7 +82,7 @@ link_target="" url_expr="string:${object_url}" visible="True"> - + From ec69c31ead9c6bcc0668d524f51c61e47adda200 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Mon, 24 Apr 2023 22:00:59 +0200 Subject: [PATCH 05/18] Updated permissions for patient folder --- .../definition.xml | 45 ++++--------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/src/senaite/patient/profiles/default/workflows/senaite_patient_folder_workflow/definition.xml b/src/senaite/patient/profiles/default/workflows/senaite_patient_folder_workflow/definition.xml index b3456ad..10f914c 100644 --- a/src/senaite/patient/profiles/default/workflows/senaite_patient_folder_workflow/definition.xml +++ b/src/senaite/patient/profiles/default/workflows/senaite_patient_folder_workflow/definition.xml @@ -18,67 +18,40 @@ + + + + + - - Analyst - ClientGuest LabClerk LabManager - Preserver - RegulatoryInspector - Sampler - SamplingCoordinator - Manager - Owner - Site Administrator - - - Analyst - ClientGuest LabClerk LabManager - Preserver - RegulatoryInspector - Sampler - SamplingCoordinator - Manager - Site Administrator - Client LabClerk LabManager - Manager - LabClerk LabManager - Manager - Owner - - Analyst - Client - ClientGuest LabClerk LabManager - Preserver - RegulatoryInspector - Sampler - SamplingCoordinator - Manager - Owner - Site Administrator + + + + From 0d88058f848debf2e7795ac31b184a48da6effd6 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Mon, 24 Apr 2023 22:20:48 +0200 Subject: [PATCH 06/18] Patient Workflow --- .../patient/profiles/default/rolemap.xml | 11 ++- .../senaite_patient_workflow/definition.xml | 68 ++++++++----------- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/src/senaite/patient/profiles/default/rolemap.xml b/src/senaite/patient/profiles/default/rolemap.xml index 7c09b34..21f4ebc 100644 --- a/src/senaite/patient/profiles/default/rolemap.xml +++ b/src/senaite/patient/profiles/default/rolemap.xml @@ -14,6 +14,8 @@ + + @@ -24,17 +26,24 @@ + + - + + + + + + - Delete objects - Modify portal content - View - - - - Access contents information - - - List folder contents - - + + senaite.patient: Field: Edit MRN senaite.patient: Field: Edit ID senaite.patient: Field: Edit Fullname @@ -37,31 +26,25 @@ - - - - - - - + + + + + + LabClerk + LabManager + Manager + Owner + - - Analyst ClientGuest LabClerk LabManager - Preserver - RegulatoryInspector - Sampler - SamplingCoordinator - Manager Owner - Site Administrator - @@ -69,21 +52,27 @@ - + + - + - + + - - - - + + + - - + + ClientGuest + LabClerk + LabManager + Manager + Owner + @@ -91,8 +80,9 @@ - + + From a13f9d12b66a8ba26f7c3b9362b02c2d8a83d0e2 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Mon, 24 Apr 2023 22:20:58 +0200 Subject: [PATCH 07/18] Add permission for patients --- src/senaite/patient/browser/patientfolder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/senaite/patient/browser/patientfolder.py b/src/senaite/patient/browser/patientfolder.py index 8d68483..f679490 100644 --- a/src/senaite/patient/browser/patientfolder.py +++ b/src/senaite/patient/browser/patientfolder.py @@ -57,7 +57,7 @@ def __init__(self, context, request): self.context_actions = { _("Add"): { "url": "++add++Patient", - "permission": "Add portal content", + "permission": "senaite.patient: Add Patient", "icon": "++resource++bika.lims.images/add.png"} } From d9065374bae71e75492e53d80f37a40226a09e52 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Tue, 25 Apr 2023 10:03:28 +0200 Subject: [PATCH 08/18] Fixed test --- src/senaite/patient/tests/doctests/PatientWorkflow.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/senaite/patient/tests/doctests/PatientWorkflow.rst b/src/senaite/patient/tests/doctests/PatientWorkflow.rst index d278a50..7b592ac 100644 --- a/src/senaite/patient/tests/doctests/PatientWorkflow.rst +++ b/src/senaite/patient/tests/doctests/PatientWorkflow.rst @@ -88,7 +88,7 @@ Global add permission: >>> from senaite.patient.permissions import AddPatient >>> get_roles_for_permission(AddPatient, patients) - ['LabClerk', 'LabManager', 'Manager'] + ['LabClerk', 'LabManager', 'Manager', 'Owner'] Patient Permissions From 2a2886f445732c2b1e1a264159a9965eef6f44a1 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Tue, 25 Apr 2023 12:40:17 +0200 Subject: [PATCH 09/18] Patient folder workflow fixtures --- src/senaite/patient/browser/configure.zcml | 2 +- src/senaite/patient/browser/patientfolder.py | 3 +- src/senaite/patient/configure.zcml | 1 + src/senaite/patient/permissions.py | 10 +-- src/senaite/patient/permissions.zcml | 5 +- .../patient/profiles/default/rolemap.xml | 9 --- .../profiles/default/types/PatientFolder.xml | 2 +- .../definition.xml | 35 ++++++++--- .../tests/doctests/PatientWorkflow.rst | 62 ++++++++++++++++--- 9 files changed, 90 insertions(+), 39 deletions(-) diff --git a/src/senaite/patient/browser/configure.zcml b/src/senaite/patient/browser/configure.zcml index 92e402b..323a88e 100644 --- a/src/senaite/patient/browser/configure.zcml +++ b/src/senaite/patient/browser/configure.zcml @@ -16,7 +16,7 @@ name="view" for="senaite.patient.content.patientfolder.IPatientFolder" class=".patientfolder.PatientFolderView" - permission="senaite.patient.permissions.ViewPatients" + permission="zope2.View" layer="senaite.patient.interfaces.ISenaitePatientLayer" /> diff --git a/src/senaite/patient/browser/patientfolder.py b/src/senaite/patient/browser/patientfolder.py index f679490..23a5604 100644 --- a/src/senaite/patient/browser/patientfolder.py +++ b/src/senaite/patient/browser/patientfolder.py @@ -30,6 +30,7 @@ from senaite.patient.api import to_identifier_type_name from senaite.patient.api import tuplify_identifiers from senaite.patient.catalog import PATIENT_CATALOG +from senaite.patient.permissions import AddPatient class PatientFolderView(ListingView): @@ -57,7 +58,7 @@ def __init__(self, context, request): self.context_actions = { _("Add"): { "url": "++add++Patient", - "permission": "senaite.patient: Add Patient", + "permission": AddPatient, "icon": "++resource++bika.lims.images/add.png"} } diff --git a/src/senaite/patient/configure.zcml b/src/senaite/patient/configure.zcml index d4ef395..0958080 100644 --- a/src/senaite/patient/configure.zcml +++ b/src/senaite/patient/configure.zcml @@ -85,6 +85,7 @@ description="Run various configuration actions" handler=".setuphandlers.setup_handler"> + diff --git a/src/senaite/patient/permissions.py b/src/senaite/patient/permissions.py index 4357947..ec8e40b 100644 --- a/src/senaite/patient/permissions.py +++ b/src/senaite/patient/permissions.py @@ -18,11 +18,13 @@ # Copyright 2020-2022 by it's authors. # Some rights reserved, see README and LICENSE. -ManagePatients = "senaite.patient: Manage Patients" -ViewPatients = "senaite.patient: View Patients" - -AddPatientFolder = "senaite.patient: Add PatientFolder" +# Add permission for our custom content types. +# Slso see `initialize` function in `__init__.py` AddPatient = "senaite.patient: Add Patient" +AddPatientFolder = "senaite.patient: Add PatientFolder" + +# Permission that governs the control panel view +ManagePatients = "senaite.patient: Manage Patients" # Transition permissions TransitionActivate = "senaite.patient: Transition: Activate" diff --git a/src/senaite/patient/permissions.zcml b/src/senaite/patient/permissions.zcml index 694b53e..8e05d71 100644 --- a/src/senaite/patient/permissions.zcml +++ b/src/senaite/patient/permissions.zcml @@ -3,11 +3,10 @@ i18n_domain="senaite.patient"> - - - + + diff --git a/src/senaite/patient/profiles/default/rolemap.xml b/src/senaite/patient/profiles/default/rolemap.xml index 21f4ebc..0723c78 100644 --- a/src/senaite/patient/profiles/default/rolemap.xml +++ b/src/senaite/patient/profiles/default/rolemap.xml @@ -9,15 +9,6 @@ - - - - - - - - - diff --git a/src/senaite/patient/profiles/default/types/PatientFolder.xml b/src/senaite/patient/profiles/default/types/PatientFolder.xml index cd2560e..945c38c 100644 --- a/src/senaite/patient/profiles/default/types/PatientFolder.xml +++ b/src/senaite/patient/profiles/default/types/PatientFolder.xml @@ -82,7 +82,7 @@ link_target="" url_expr="string:${object_url}" visible="True"> - + diff --git a/src/senaite/patient/profiles/default/workflows/senaite_patient_folder_workflow/definition.xml b/src/senaite/patient/profiles/default/workflows/senaite_patient_folder_workflow/definition.xml index 10f914c..5906145 100644 --- a/src/senaite/patient/profiles/default/workflows/senaite_patient_folder_workflow/definition.xml +++ b/src/senaite/patient/profiles/default/workflows/senaite_patient_folder_workflow/definition.xml @@ -8,50 +8,65 @@ manager_bypass="False" i18n:domain="senaite.patient"> - + Add portal content Access contents information Delete objects List folder contents Modify portal content View + + senaite.patient: Add Patient + - + + - + + LabClerk LabManager Manager - + + + + + LabClerk LabManager Manager - + + + + + LabClerk LabManager Manager + - LabManager Manager + LabClerk LabManager Manager - - - - + diff --git a/src/senaite/patient/tests/doctests/PatientWorkflow.rst b/src/senaite/patient/tests/doctests/PatientWorkflow.rst index 7b592ac..4d4a802 100644 --- a/src/senaite/patient/tests/doctests/PatientWorkflow.rst +++ b/src/senaite/patient/tests/doctests/PatientWorkflow.rst @@ -11,17 +11,23 @@ Test Setup Needed Imports: >>> from AccessControl.PermissionRole import rolesForPermissionOn + >>> from DateTime import DateTime + >>> from Products.CMFCore.permissions import AccessContentsInformation + >>> from Products.CMFCore.permissions import AddPortalContent + >>> from Products.CMFCore.permissions import DeleteObjects + >>> from Products.CMFCore.permissions import ListFolderContents + >>> from Products.CMFCore.permissions import ModifyPortalContent + >>> from Products.CMFCore.permissions import View >>> from bika.lims import api + >>> from bika.lims.api.security import get_roles_for_permission >>> from bika.lims.utils.analysisrequest import create_analysisrequest >>> from bika.lims.utils.analysisrequest import create_partition >>> from bika.lims.workflow import doActionFor as do_action_for - >>> from bika.lims.workflow import isTransitionAllowed >>> from bika.lims.workflow import getAllowedTransitions - >>> from DateTime import DateTime - >>> from plone.app.testing import setRoles + >>> from bika.lims.workflow import isTransitionAllowed >>> from plone.app.testing import TEST_USER_ID >>> from plone.app.testing import TEST_USER_PASSWORD - >>> from bika.lims.api.security import get_roles_for_permission + >>> from plone.app.testing import setRoles Functional Helpers: @@ -67,8 +73,8 @@ We need to create some basic objects for the test: >>> MS = api.create(setup.bika_analysisservices, "AnalysisService", title="Malaria Species", Keyword="MS", Price="10", Category=category.UID(), Accredited=True) -Patient Folder Permissions -.......................... +Patient Folder Workflow +....................... Get the mapped workflow and status of the patient folder: @@ -80,20 +86,56 @@ Get the mapped workflow and status of the patient folder: >>> api.get_workflow_status_of(patients) 'active' -Global add permission: + +Patient Folder Permissions +.......................... + +The creation of a patients folder is governed with the custom `AddPatientFolder` permission. +Also see the `initialize` function in `__init__.py`. >>> from senaite.patient.permissions import AddPatientFolder >>> get_roles_for_permission(AddPatientFolder, portal) ['Manager'] - >>> from senaite.patient.permissions import AddPatient - >>> get_roles_for_permission(AddPatient, patients) - ['LabClerk', 'LabManager', 'Manager', 'Owner'] +The `View` permission governs who is allowed to see the patients folder and if +it is displayed in the side navigation or not: + + >>> get_roles_for_permission(View, patients) + ['LabClerk', 'LabManager', 'Manager'] + +The `DeleteObjects` permission governs if it is allowed to delete *any kind of +objects* from this folder: + + >>> get_roles_for_permission(DeleteObjects, patients) + [] + +The `AccessContentsInformation` permission governs if the basic access to the +folder, without necessarily viewing it: + + >>> get_roles_for_permission(AccessContentsInformation, patients) + ['LabClerk', 'LabManager', 'Manager'] + +The `ListFolderContents` permission governs whether you can get a listing of the patients: + + >>> get_roles_for_permission(ListFolderContents, patients) + ['LabClerk', 'LabManager', 'Manager'] + +The `ModifyPortalContent` permission governs whether it is allowed to change e.g. the Title of the folder: + + >>> get_roles_for_permission(ModifyPortalContent, patients) + ['Manager'] Patient Permissions ................... +The creation of a patients is governed with the custom `AddPatient` permission. +Also see the `initialize` function in `__init__.py`. + + >>> from senaite.patient.permissions import AddPatient + >>> get_roles_for_permission(AddPatient, patients) + ['LabClerk', 'LabManager', 'Manager'] + Create a new patient: >>> patient = api.create(patients, "Patient", mrn="1", fullname="Clark Kent") From b60fa1bc7114f252cef4e19e9b8cbdce7be96a56 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Tue, 25 Apr 2023 12:40:36 +0200 Subject: [PATCH 10/18] Added test browser to test base --- src/senaite/patient/tests/base.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/senaite/patient/tests/base.py b/src/senaite/patient/tests/base.py index dc56d04..4e4b589 100644 --- a/src/senaite/patient/tests/base.py +++ b/src/senaite/patient/tests/base.py @@ -22,11 +22,14 @@ import unittest2 as unittest from plone.app.testing import PLONE_FIXTURE from plone.app.testing import TEST_USER_ID +from plone.app.testing import TEST_USER_NAME +from plone.app.testing import TEST_USER_PASSWORD from plone.app.testing import FunctionalTesting from plone.app.testing import PloneSandboxLayer from plone.app.testing import applyProfile from plone.app.testing import setRoles from plone.testing import zope +from plone.testing.z2 import Browser class SimpleTestLayer(PloneSandboxLayer): @@ -38,11 +41,11 @@ def setUpZope(self, app, configurationContext): super(SimpleTestLayer, self).setUpZope(app, configurationContext) import bika.lims - import senaite.lims - import senaite.core import senaite.app.listing - import senaite.impress import senaite.app.spotlight + import senaite.core + import senaite.impress + import senaite.lims import senaite.patient # Load ZCML @@ -92,6 +95,23 @@ def setUp(self): self.request["ACTUAL_URL"] = self.portal.absolute_url() setRoles(self.portal, TEST_USER_ID, ["LabManager", "Manager"]) + def getBrowser(self, + username=TEST_USER_NAME, + password=TEST_USER_PASSWORD, + loggedIn=True): + + # Instantiate and return a testbrowser for convenience + browser = Browser(self.portal) + browser.addHeader("Accept-Language", "en-US") + browser.handleErrors = False + if loggedIn: + browser.open(self.portal.absolute_url()) + browser.getControl("Login Name").value = username + browser.getControl("Password").value = password + browser.getControl("Log in").click() + self.assertTrue("You are now logged in" in browser.contents) + return browser + class FunctionalTestCase(unittest.TestCase): layer = SIMPLE_TESTING From 035da49130883f5725c435880d2a0da828880c55 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Tue, 25 Apr 2023 13:11:16 +0200 Subject: [PATCH 11/18] Fixed permission mappings in patient workflow --- .../definition.xml | 11 ++- .../senaite_patient_workflow/definition.xml | 93 ++++++++++++++++++- 2 files changed, 97 insertions(+), 7 deletions(-) diff --git a/src/senaite/patient/profiles/default/workflows/senaite_patient_folder_workflow/definition.xml b/src/senaite/patient/profiles/default/workflows/senaite_patient_folder_workflow/definition.xml index 5906145..59f4361 100644 --- a/src/senaite/patient/profiles/default/workflows/senaite_patient_folder_workflow/definition.xml +++ b/src/senaite/patient/profiles/default/workflows/senaite_patient_folder_workflow/definition.xml @@ -36,9 +36,12 @@ LabManager Manager - - + + + LabClerk + LabManager + Manager @@ -46,7 +49,7 @@ LabManager Manager - + diff --git a/src/senaite/patient/profiles/default/workflows/senaite_patient_workflow/definition.xml b/src/senaite/patient/profiles/default/workflows/senaite_patient_workflow/definition.xml index 7d7e376..640b40d 100644 --- a/src/senaite/patient/profiles/default/workflows/senaite_patient_workflow/definition.xml +++ b/src/senaite/patient/profiles/default/workflows/senaite_patient_workflow/definition.xml @@ -8,9 +8,13 @@ manager_bypass="False" i18n:domain="senaite.patient"> + + Add portal content + Access contents information + Delete objects + List folder contents Modify portal content View - senaite.patient: Field: Edit MRN senaite.patient: Field: Edit ID @@ -19,10 +23,10 @@ senaite.patient: Field: Edit Gender senaite.patient: Field: Edit Date of Birth senaite.patient: Field: Edit Address - senaite.patient: Transition: Activate senaite.patient: Transition: Deactivate + @@ -32,12 +36,54 @@ + + + ClientGuest + LabClerk + LabManager + Manager + Owner + + + + LabClerk + LabManager + Manager + Owner + + + + + + + ClientGuest + LabClerk + LabManager + Manager + Owner + + LabClerk LabManager Manager Owner + ClientGuest LabClerk @@ -45,6 +91,8 @@ Manager Owner + + @@ -65,7 +113,44 @@ - + + + ClientGuest + LabClerk + LabManager + Manager + Owner + + + + + + + + + + ClientGuest + LabClerk + LabManager + Manager + Owner + + + + + ClientGuest LabClerk @@ -73,6 +158,8 @@ Manager Owner + + From aa46ecd22dab1e42793b51ebeb288d489046f82d Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Tue, 25 Apr 2023 14:06:08 +0200 Subject: [PATCH 12/18] Workflow doctest updated --- .../tests/doctests/PatientWorkflow.rst | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/senaite/patient/tests/doctests/PatientWorkflow.rst b/src/senaite/patient/tests/doctests/PatientWorkflow.rst index 4d4a802..c515fe3 100644 --- a/src/senaite/patient/tests/doctests/PatientWorkflow.rst +++ b/src/senaite/patient/tests/doctests/PatientWorkflow.rst @@ -155,6 +155,52 @@ Allowed transitions: >>> getAllowedTransitions(patient) ['deactivate'] + +Default permissions in **active** state: + +The following roles can `Access contents information` of patients, e.g. to see +the results in the reference widget: + + >>> get_roles_for_permission(AccessContentsInformation, patient) + ['ClientGuest', 'LabClerk', 'LabManager', 'Manager', 'Owner'] + +The `AddPortalContent` permission governs wether it is allowed to add contents +inside a patient. + +Although it is not used currently, we use the default permissions including the +`Owner` for client-local patients: + + >>> get_roles_for_permission(AddPortalContent, patient) + ['LabClerk', 'LabManager', 'Manager', 'Owner'] + +The `DeleteObjects` permission governs wether it is allowed to removed contents +inside a patient. We (almost) never allow this: + + >>> get_roles_for_permission(DeleteObjects, patient) + [] + +The `ListFolderContents` permission governs wether it is allowed list contents +inside patients. + +Although it is not used currently, we use the default roles including the +`Owner` for client-local and `ClientGuest` for shared patients: + + >>> get_roles_for_permission(ListFolderContents, patient) + ['ClientGuest', 'LabClerk', 'LabManager', 'Manager', 'Owner'] + +The `ModifyPortalContent` permission governs wether it is allowed to edit a patient. +Note that we do not allow this for `ClientGuest` role, because we do not want that +shared patients can be edited from basically client contacts: + + >>> get_roles_for_permission(ModifyPortalContent, patient) + ['LabClerk', 'LabManager', 'Manager', 'Owner'] + +The `View` permission governs if the patient can be viewed: + + >>> get_roles_for_permission(View, patient) + ['ClientGuest', 'LabClerk', 'LabManager', 'Manager', 'Owner'] + + Field permission in **active** state: >>> from senaite.patient.permissions import FieldEditMRN @@ -187,6 +233,40 @@ Deactivating the patient >>> api.get_workflow_status_of(patient) 'inactive' + +Default permissions in **inactive** state: + +Accessing the patient is possible for the same roles: + + >>> get_roles_for_permission(AccessContentsInformation, patient) + ['ClientGuest', 'LabClerk', 'LabManager', 'Manager', 'Owner'] + +It should be no longer possible to add contents to a deactivated patient: + + >>> get_roles_for_permission(AddPortalContent, patient) + [] + +Deleting contents is not allowed: + + >>> get_roles_for_permission(DeleteObjects, patient) + [] + +Inactive clients should be still listed for the same roles: + + >>> get_roles_for_permission(ListFolderContents, patient) + ['ClientGuest', 'LabClerk', 'LabManager', 'Manager', 'Owner'] + +No modifications are allowed for inactive patients: + + >>> get_roles_for_permission(ModifyPortalContent, patient) + [] + +Viewing an inactive client is still possible for the same roles + + >>> get_roles_for_permission(View, patient) + ['ClientGuest', 'LabClerk', 'LabManager', 'Manager', 'Owner'] + + Field permission in **inactive** state: >>> from senaite.patient.permissions import FieldEditMRN From 57ba72c26085705e37a44d3c0a3e708ea66a7c77 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Tue, 25 Apr 2023 14:39:02 +0200 Subject: [PATCH 13/18] Create patient in client if set --- src/senaite/patient/api.py | 16 +++++++++++++--- .../patient/subscribers/analysisrequest.py | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/senaite/patient/api.py b/src/senaite/patient/api.py index 474c87d..79eb00b 100644 --- a/src/senaite/patient/api.py +++ b/src/senaite/patient/api.py @@ -22,6 +22,7 @@ from datetime import datetime from bika.lims import api +from bika.lims.utils import get_client from bika.lims.utils import tmpID from dateutil.relativedelta import relativedelta from senaite.patient.config import PATIENT_CATALOG @@ -155,12 +156,21 @@ def create_temporary_patient(): return patient -def store_temporary_patient(patient): +def store_temporary_patient(context, patient): """Store temporary patient to the patients folder + + :param context: The current context + :param patient: A temporary patient object """ - portal = api.get_portal() - container = portal.patients + if is_patient_allowed_in_client(): + # create the patient inside the client + container = get_client(context) + else: + portal = api.get_portal() + container = portal.patients + # Notify `ObjectCreateEvent` to generate a UID first notify(ObjectCreatedEvent(patient)) + # set the patient in the container container._setObject(patient.id, patient) patient = container.get(patient.getId()) return patient diff --git a/src/senaite/patient/subscribers/analysisrequest.py b/src/senaite/patient/subscribers/analysisrequest.py index f3593c0..80c0966 100644 --- a/src/senaite/patient/subscribers/analysisrequest.py +++ b/src/senaite/patient/subscribers/analysisrequest.py @@ -98,7 +98,7 @@ def update_patient(instance): try: patient = patient_api.create_temporary_patient() patient_api.update_patient(patient, **values) - patient_api.store_temporary_patient(patient) + patient_api.store_temporary_patient(instance, patient) except ValueError as exc: logger.error("%s" % exc) logger.error("Failed to create patient for values: %r" % values) From cad5ef8b5a40be2589ba5317790e497d6d148818 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Tue, 25 Apr 2023 14:47:04 +0200 Subject: [PATCH 14/18] Changelog updated --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 950890e..6f09bc1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,7 @@ Changelog 1.4.0 (unreleased) ------------------ +- #69 Fix Patient Workflows and Permissions - #68 Allow client local patients - #66 Fix widget view mode - #65 Fix cannot create partitions from samples with Patient assigned From 8ba1ffbbef63e525dc96b7339e70478ff64b8e08 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Wed, 26 Apr 2023 10:54:54 +0200 Subject: [PATCH 15/18] Skip patient creation if user has no permission --- src/senaite/patient/api.py | 31 +++++++++++++------ .../temporaryidentifierwidget.pt | 2 -- .../patient/subscribers/analysisrequest.py | 30 ++++++++++++++++-- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/senaite/patient/api.py b/src/senaite/patient/api.py index 79eb00b..62dc683 100644 --- a/src/senaite/patient/api.py +++ b/src/senaite/patient/api.py @@ -22,10 +22,10 @@ from datetime import datetime from bika.lims import api -from bika.lims.utils import get_client from bika.lims.utils import tmpID from dateutil.relativedelta import relativedelta from senaite.patient.config import PATIENT_CATALOG +from senaite.patient.permissions import AddPatient from zope.component import getUtility from zope.component.interfaces import IFactory from zope.event import notify @@ -156,18 +156,12 @@ def create_temporary_patient(): return patient -def store_temporary_patient(context, patient): +def store_temporary_patient(container, patient): """Store temporary patient to the patients folder - :param context: The current context + :param container: The container where the patient should be stored :param patient: A temporary patient object """ - if is_patient_allowed_in_client(): - # create the patient inside the client - container = get_client(context) - else: - portal = api.get_portal() - container = portal.patients # Notify `ObjectCreateEvent` to generate a UID first notify(ObjectCreatedEvent(patient)) # set the patient in the container @@ -343,3 +337,22 @@ def is_patient_allowed_in_client(): allowed = api.get_registry_record( "senaite.patient.allow_patients_in_clients", False) return allowed + + +def get_patient_folder(): + """Returns the global patient folder + + :returns: global patients folder + """ + portal = api.get_portal() + return portal.patients + + +def is_patient_creation_allowed(container): + """Check if the security context allows to add a new patient + + :param container: The container to check the permission + :returns: True if it is allowed to create a patient in the container, + otherwise False + """ + return api.security.check_permission(AddPatient, container) diff --git a/src/senaite/patient/skins/templates/senaite_patient_widgets/temporaryidentifierwidget.pt b/src/senaite/patient/skins/templates/senaite_patient_widgets/temporaryidentifierwidget.pt index 947c01b..54341ad 100644 --- a/src/senaite/patient/skins/templates/senaite_patient_widgets/temporaryidentifierwidget.pt +++ b/src/senaite/patient/skins/templates/senaite_patient_widgets/temporaryidentifierwidget.pt @@ -31,8 +31,6 @@
setting MRN to temporary".format( + api.user.get_user_id(), api.get_path(container))) + # make the MRN temporary + # XXX: Refactor logic from Widget -> Field/DataManager + + mrn_field = instance.getField("MedicalRecordNumber") + mrn = dict(mrn_field.get(instance)) + mrn["temporary"] = True + mrn_field.set(instance, mrn) + message = _("You are not allowed to add a patient in {} folder. " + "Medical Record Number set to Temporary." + .format(api.get_title(container))) + instance.plone_utils.addPortalMessage(message, "error") + return None + + logger.info("Creating new Patient in '{}' with MRN: '{}'" + .format(api.get_path(container), mrn)) values = get_patient_fields(instance) try: patient = patient_api.create_temporary_patient() patient_api.update_patient(patient, **values) - patient_api.store_temporary_patient(instance, patient) + patient_api.store_temporary_patient(container, patient) except ValueError as exc: logger.error("%s" % exc) logger.error("Failed to create patient for values: %r" % values) From ea57fa1f5a67156fc5fcd1264f22f07d8034c081 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Wed, 26 Apr 2023 10:57:49 +0200 Subject: [PATCH 16/18] Comment and whitespace only --- src/senaite/patient/subscribers/analysisrequest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/senaite/patient/subscribers/analysisrequest.py b/src/senaite/patient/subscribers/analysisrequest.py index cdaf602..7b15998 100644 --- a/src/senaite/patient/subscribers/analysisrequest.py +++ b/src/senaite/patient/subscribers/analysisrequest.py @@ -93,6 +93,7 @@ def update_patient(instance): if mrn is None: return patient = patient_api.get_patient_by_mrn(mrn, include_inactive=True) + # Create a new patient if patient is None: if patient_api.is_patient_allowed_in_client(): # create the patient in the client @@ -107,7 +108,6 @@ def update_patient(instance): api.user.get_user_id(), api.get_path(container))) # make the MRN temporary # XXX: Refactor logic from Widget -> Field/DataManager - mrn_field = instance.getField("MedicalRecordNumber") mrn = dict(mrn_field.get(instance)) mrn["temporary"] = True From 679f9a861b740be80c20467c5dffea7f0b89cc4c Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Sun, 7 May 2023 07:22:07 +0200 Subject: [PATCH 17/18] Use client folder if client local patients are allowed --- src/senaite/patient/subscribers/analysisrequest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/senaite/patient/subscribers/analysisrequest.py b/src/senaite/patient/subscribers/analysisrequest.py index 7b15998..11e69d5 100644 --- a/src/senaite/patient/subscribers/analysisrequest.py +++ b/src/senaite/patient/subscribers/analysisrequest.py @@ -97,7 +97,7 @@ def update_patient(instance): if patient is None: if patient_api.is_patient_allowed_in_client(): # create the patient in the client - container = patient_api.get_patient_folder() + container = instance.getClient() else: # create the patient in the global patients folder container = patient_api.get_patient_folder() From 2dae54066d9ff5b6632313b2f40893f0cf5bc5bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Sun, 7 May 2023 14:00:47 +0200 Subject: [PATCH 18/18] Added upgradestep decorator for 1408 --- src/senaite/patient/upgrade/v01_04_000.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/senaite/patient/upgrade/v01_04_000.py b/src/senaite/patient/upgrade/v01_04_000.py index f8d4686..b52c6dd 100644 --- a/src/senaite/patient/upgrade/v01_04_000.py +++ b/src/senaite/patient/upgrade/v01_04_000.py @@ -219,6 +219,7 @@ def allow_patients_in_clients(tool): logger.info("Allow patients in clients [DONE]") +@upgradestep(PRODUCT_NAME, version) def update_patient_workflows(tool): """Update patient workflows and security settings """