diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d40e34f5..67c3b105 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,7 @@ Added - REST API list view pagination (#1994) - **Samplesheets** - REST API list view pagination (#1994) + - ``notify_email_irods_request`` user app setting (#1939) Changed ------- diff --git a/samplesheets/plugins.py b/samplesheets/plugins.py index 1c547a04..e11c15e5 100644 --- a/samplesheets/plugins.py +++ b/samplesheets/plugins.py @@ -62,6 +62,8 @@ PROJECT_TYPE_PROJECT = SODAR_CONSTANTS['PROJECT_TYPE_PROJECT'] PROJECT_ACTION_CREATE = SODAR_CONSTANTS['PROJECT_ACTION_CREATE'] PROJECT_ACTION_UPDATE = SODAR_CONSTANTS['PROJECT_ACTION_UPDATE'] +APP_SETTING_SCOPE_PROJECT = SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'] +APP_SETTING_SCOPE_USER = SODAR_CONSTANTS['APP_SETTING_SCOPE_USER'] # Local constants SHEETS_INFO_SETTINGS = [ @@ -113,7 +115,7 @@ class ProjectAppPlugin( #: App settings definition app_settings = { 'allow_editing': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'], + 'scope': APP_SETTING_SCOPE_PROJECT, 'type': 'BOOLEAN', 'label': 'Allow sample sheet editing', 'description': 'Allow editing of project sample sheets by ' @@ -122,14 +124,14 @@ class ProjectAppPlugin( 'default': True, }, 'display_config': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT_USER'], + 'scope': APP_SETTING_SCOPE_USER, 'type': 'JSON', 'label': 'Sample sheet display configuration', 'description': 'User specific JSON configuration for column ' 'display in project sample sheets', }, 'display_config_default': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'], + 'scope': APP_SETTING_SCOPE_PROJECT, 'type': 'JSON', 'label': 'Default sample sheet display configuration', 'description': 'Default JSON configuration for column display in ' @@ -137,14 +139,14 @@ class ProjectAppPlugin( 'user_modifiable': False, }, 'sheet_config': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'], + 'scope': APP_SETTING_SCOPE_PROJECT, 'type': 'JSON', 'label': 'Sample sheet editing configuration', 'description': 'JSON configuration for sample sheet editing', 'user_modifiable': False, }, 'edit_config_min_role': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'], + 'scope': APP_SETTING_SCOPE_PROJECT, 'type': 'STRING', 'options': [ 'superuser', @@ -159,7 +161,7 @@ class ProjectAppPlugin( 'user_modifiable': True, }, 'sheet_sync_enable': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'], + 'scope': APP_SETTING_SCOPE_PROJECT, 'type': 'BOOLEAN', 'default': False, 'label': 'Enable sheet synchronization', @@ -167,7 +169,7 @@ class ProjectAppPlugin( 'user_modifiable': True, }, 'sheet_sync_url': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'], + 'scope': APP_SETTING_SCOPE_PROJECT, 'type': 'STRING', 'label': 'URL for sheet synchronization', 'default': '', @@ -175,7 +177,7 @@ class ProjectAppPlugin( 'user_modifiable': True, }, 'sheet_sync_token': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'], + 'scope': APP_SETTING_SCOPE_PROJECT, 'type': 'STRING', 'label': 'Token for sheet synchronization', 'default': '', @@ -184,7 +186,7 @@ class ProjectAppPlugin( 'user_modifiable': True, }, 'sheet_table_height': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_USER'], + 'scope': APP_SETTING_SCOPE_USER, 'type': 'INTEGER', 'label': 'Sample sheet table height', 'options': [250, 400, 600, 800], @@ -194,7 +196,7 @@ class ProjectAppPlugin( 'user_modifiable': True, }, 'public_access_ticket': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'], + 'scope': APP_SETTING_SCOPE_PROJECT, 'type': 'STRING', 'label': 'iRODS public access ticket', 'default': '', @@ -203,7 +205,7 @@ class ProjectAppPlugin( 'user_modifiable': False, }, 'igv_genome': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'], + 'scope': APP_SETTING_SCOPE_PROJECT, 'type': 'STRING', 'label': 'IGV session genome', 'default': IGV_DEFAULT_GENOME, @@ -213,7 +215,7 @@ class ProjectAppPlugin( 'user_modifiable': True, }, 'igv_omit_bam': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'], + 'scope': APP_SETTING_SCOPE_PROJECT, 'type': 'STRING', 'label': 'BAM and CRAM paths to omit from IGV sessions', 'default': ', '.join(settings.SHEETS_IGV_OMIT_BAM), @@ -224,7 +226,7 @@ class ProjectAppPlugin( 'user_modifiable': True, }, 'igv_omit_vcf': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'], + 'scope': APP_SETTING_SCOPE_PROJECT, 'type': 'STRING', 'label': 'VCF paths to omit from IGV sessions', 'default': ', '.join(settings.SHEETS_IGV_OMIT_VCF), @@ -235,7 +237,7 @@ class ProjectAppPlugin( 'user_modifiable': True, }, 'template_output_dir_display': { - 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_USER'], + 'scope': APP_SETTING_SCOPE_USER, 'type': 'BOOLEAN', 'label': 'Display template output directory field', 'default': False, @@ -243,6 +245,15 @@ class ProjectAppPlugin( 'templates.', 'user_modifiable': True, }, + 'notify_email_irods_request': { + 'scope': APP_SETTING_SCOPE_USER, + 'type': 'BOOLEAN', + 'default': True, + 'label': 'Receive email for iRODS data requests', + 'description': 'Receive email notifications for iRODS data ' + 'request accepting and rejecting', + 'user_modifiable': True, + }, } #: Iconify icon diff --git a/samplesheets/tests/test_views_taskflow.py b/samplesheets/tests/test_views_taskflow.py index 07a82300..b14d7aba 100644 --- a/samplesheets/tests/test_views_taskflow.py +++ b/samplesheets/tests/test_views_taskflow.py @@ -11,6 +11,7 @@ from django.conf import settings from django.contrib import auth from django.contrib.messages import get_messages +from django.core import mail from django.forms.models import model_to_dict from django.test import override_settings from django.urls import reverse @@ -1522,7 +1523,7 @@ def setUp(self): def test_get(self): """Test IrodsDataRequestAcceptView GET""" self.assert_irods_obj(self.obj_path) - self.make_irods_request( + request = self.make_irods_request( project=self.project, action=IRODS_REQUEST_ACTION_DELETE, path=self.obj_path, @@ -1530,17 +1531,15 @@ def test_get(self): user=self.user_contributor, ) self.assertEqual(IrodsDataRequest.objects.count(), 1) - obj = IrodsDataRequest.objects.first() - with self.login(self.user): response = self.client.get( reverse( 'samplesheets:irods_request_accept', - kwargs={'irodsdatarequest': obj.sodar_uuid}, + kwargs={'irodsdatarequest': request.sodar_uuid}, ), {'confirm': True}, ) - self.assertEqual(response.context['request_objects'][0], obj) + self.assertEqual(response.context['request_objects'][0], request) def test_get_coll(self): """Test GET with collection request""" @@ -1577,6 +1576,7 @@ def test_post(self): self._assert_alert_count(EVENT_CREATE, self.user_delegate, 1) self._assert_alert_count(EVENT_ACCEPT, self.user, 0) self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) + mail_count = len(mail.outbox) obj = IrodsDataRequest.objects.first() with self.login(self.user): @@ -1607,6 +1607,10 @@ def test_post(self): self._assert_alert_count(EVENT_ACCEPT, self.user, 0) self._assert_alert_count(EVENT_ACCEPT, self.user_delegate, 0) self.assert_irods_obj(self.obj_path, False) + self.assertEqual(len(mail.outbox), mail_count + 1) + self.assertEqual( + mail.outbox[-1].recipients(), [self.user_contributor.email] + ) def test_post_no_request(self): """Test POST with non-existing delete request""" @@ -1860,6 +1864,36 @@ def test_post_collection(self): self.assertEqual(self.irods.collections.exists(coll_path), False) self.assert_irods_obj(obj_path2, False) + def test_post_disable_email_notify(self): + """Test POST wth disabled email notifications""" + app_settings.set( + APP_NAME, + 'notify_email_irods_request', + False, + user=self.user_contributor, + ) + self.assert_irods_obj(self.obj_path) + request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=self.obj_path, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contributor, + ) + mail_count = len(mail.outbox) + with self.login(self.user): + self.client.post( + reverse( + 'samplesheets:irods_request_accept', + kwargs={'irodsdatarequest': request.sodar_uuid}, + ), + {'confirm': True}, + ) + request.refresh_from_db() + self.assertEqual(request.status, IRODS_REQUEST_STATUS_ACCEPTED) + self.assert_irods_obj(self.obj_path, False) + self.assertEqual(len(mail.outbox), mail_count) # No new mail + class TestIrodsDataRequestAcceptBatchView( IrodsDataRequestMixin, IrodsDataRequestViewTestBase @@ -2154,8 +2188,8 @@ def setUp(self): kwargs={'project': self.project.sodar_uuid}, ) - def test_get_admin(self): - """Test IrodsDataRequestRejectView GET as admin""" + def test_get_superuser(self): + """Test IrodsDataRequestRejectView GET as superuser""" self.assertEqual(IrodsDataRequest.objects.count(), 0) self.assert_irods_obj(self.obj_path) self._assert_alert_count(EVENT_REJECT, self.user, 0) @@ -2168,6 +2202,7 @@ def test_get_admin(self): status=IRODS_REQUEST_STATUS_ACTIVE, user=self.user_contributor, ) + mail_count = len(mail.outbox) with self.login(self.user): response = self.client.get( @@ -2196,10 +2231,13 @@ def test_get_admin(self): self._assert_alert_count(EVENT_REJECT, self.user, 0) self._assert_alert_count(EVENT_REJECT, self.user_delegate, 0) self._assert_alert_count(EVENT_REJECT, self.user_contributor, 1) + self.assertEqual(len(mail.outbox), mail_count + 1) + self.assertEqual( + mail.outbox[-1].recipients(), [self.user_contributor.email] + ) def test_get_owner(self): """Test GET as owner""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) request = self.make_irods_request( project=self.project, action=IRODS_REQUEST_ACTION_DELETE, @@ -2224,7 +2262,6 @@ def test_get_owner(self): def test_get_delegate(self): """Test GET as delegate""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) request = self.make_irods_request( project=self.project, action=IRODS_REQUEST_ACTION_DELETE, @@ -2249,7 +2286,6 @@ def test_get_delegate(self): def test_get_contributor(self): """Test GET as contributor""" - self.assertEqual(IrodsDataRequest.objects.count(), 0) request = self.make_irods_request( project=self.project, action=IRODS_REQUEST_ACTION_DELETE, @@ -2329,6 +2365,35 @@ def test_get_no_request(self): ) self.assertEqual(response.status_code, 404) + def test_get_disable_email_notify(self): + """Test GET with disabled email notifications""" + app_settings.set( + APP_NAME, + 'notify_email_irods_request', + False, + user=self.user_contributor, + ) + self.assert_irods_obj(self.obj_path) + request = self.make_irods_request( + project=self.project, + action=IRODS_REQUEST_ACTION_DELETE, + path=self.obj_path, + status=IRODS_REQUEST_STATUS_ACTIVE, + user=self.user_contributor, + ) + mail_count = len(mail.outbox) + with self.login(self.user): + self.client.get( + reverse( + 'samplesheets:irods_request_reject', + kwargs={'irodsdatarequest': request.sodar_uuid}, + ), + ) + request.refresh_from_db() + self.assertEqual(request.status, IRODS_REQUEST_STATUS_REJECTED) + self.assert_irods_obj(self.obj_path) + self.assertEqual(len(mail.outbox), mail_count) + class TestIrodsDataRequestRejectBatchView( IrodsDataRequestMixin, IrodsDataRequestViewTestBase diff --git a/samplesheets/views.py b/samplesheets/views.py index 3c18a816..3b8ac6ba 100644 --- a/samplesheets/views.py +++ b/samplesheets/views.py @@ -994,6 +994,9 @@ def accept_request( if ( settings.PROJECTROLES_SEND_EMAIL and irods_request.user != request.user + and app_settings.get( + APP_NAME, 'notify_email_irods_request', user=irods_request.user + ) ): subject_body = 'iRODS delete request accepted' message_body = EMAIL_DELETE_REQUEST_ACCEPT.format( @@ -1068,6 +1071,9 @@ def reject_request( if ( settings.PROJECTROLES_SEND_EMAIL and irods_request.user != request.user + and app_settings.get( + APP_NAME, 'notify_email_irods_request', user=irods_request.user + ) ): subject_body = 'iRODS delete request rejected' message_body = EMAIL_DELETE_REQUEST_REJECT.format(