diff --git a/primed/cdsa/adapters.py b/primed/cdsa/adapters.py index b32edc11..a4767906 100644 --- a/primed/cdsa/adapters.py +++ b/primed/cdsa/adapters.py @@ -2,7 +2,7 @@ from anvil_consortium_manager.forms import WorkspaceForm from anvil_consortium_manager.models import Workspace -from primed.miscellaneous_workspaces.tables import DataPrepWorkspaceTable +from primed.miscellaneous_workspaces.tables import DataPrepWorkspaceUserTable from . import forms, models, tables @@ -27,7 +27,7 @@ def get_extra_detail_context_data(self, workspace, request): associated_data_prep = Workspace.objects.filter( dataprepworkspace__target_workspace=workspace ) - extra_context["associated_data_prep_workspaces"] = DataPrepWorkspaceTable( + extra_context["associated_data_prep_workspaces"] = DataPrepWorkspaceUserTable( associated_data_prep ) extra_context["data_prep_active"] = associated_data_prep.filter( diff --git a/primed/cdsa/tables.py b/primed/cdsa/tables.py index 4fd93ce1..f39d6988 100644 --- a/primed/cdsa/tables.py +++ b/primed/cdsa/tables.py @@ -299,11 +299,10 @@ def render_date_shared(self, record): return "—" -class CDSAWorkspaceStaffTable(tables.Table): +class CDSAWorkspaceUserTable(tables.Table): """A table for the CDSAWorkspace model.""" name = tables.Column(linkify=True) - billing_project = tables.Column(linkify=True) cdsaworkspace__data_use_permission__abbreviation = tables.Column( verbose_name="DUO permission", linkify=lambda record: record.cdsaworkspace.data_use_permission.get_absolute_url(), @@ -329,7 +328,6 @@ class Meta: model = Workspace fields = ( "name", - "billing_project", "cdsaworkspace__study", "cdsaworkspace__data_use_permission__abbreviation", "cdsaworkspace__data_use_modifiers", @@ -351,27 +349,10 @@ def render_cdsaworkspace__requires_study_review(self, record): return mark_safe(f'') -class CDSAWorkspaceUserTable(tables.Table): +class CDSAWorkspaceStaffTable(CDSAWorkspaceUserTable): """A table for the CDSAWorkspace model.""" - name = tables.Column(linkify=True) - billing_project = tables.Column() - cdsaworkspace__data_use_permission__abbreviation = tables.Column( - verbose_name="DUO permission", - ) - cdsaworkspace__study = tables.Column() - cdsaworkspace__data_use_modifiers = tables.ManyToManyColumn( - transform=lambda x: x.abbreviation, - verbose_name="DUO modifiers", - ) - cdsaworkspace__requires_study_review = BooleanIconColumn( - verbose_name="Study review required?", - orderable=False, - true_icon="dash-circle-fill", - true_color="#ffc107", - ) - cdsaworkspace__gsr_restricted = BooleanIconColumn(orderable=False) - is_shared = WorkspaceSharedWithConsortiumColumn() + billing_project = tables.Column(linkify=True) class Meta: model = Workspace @@ -385,15 +366,3 @@ class Meta: "cdsaworkspace__gsr_restricted", ) order_by = ("name",) - - def render_cdsaworkspace__requires_study_review(self, record): - try: - if record.cdsaworkspace.get_primary_cdsa().requires_study_review: - icon = "dash-circle-fill" - color = "#ffc107" - else: - return "" - except models.DataAffiliateAgreement.DoesNotExist: - icon = "question-circle-fill" - color = "red" - return mark_safe(f'') diff --git a/primed/cdsa/tests/test_views.py b/primed/cdsa/tests/test_views.py index beb8e4e8..ab203d0a 100644 --- a/primed/cdsa/tests/test_views.py +++ b/primed/cdsa/tests/test_views.py @@ -30,7 +30,7 @@ from freezegun import freeze_time from primed.duo.tests.factories import DataUseModifierFactory, DataUsePermissionFactory -from primed.miscellaneous_workspaces.tables import DataPrepWorkspaceTable +from primed.miscellaneous_workspaces.tables import DataPrepWorkspaceUserTable from primed.miscellaneous_workspaces.tests.factories import DataPrepWorkspaceFactory from primed.primed_anvil.tests.factories import ( AvailableDataFactory, @@ -7436,6 +7436,29 @@ def test_render_duo_modifiers(self): self.assertContains(response, modifiers[0].abbreviation) self.assertContains(response, modifiers[1].abbreviation) + def test_associated_data_prep_view_user(self): + """View users do not see the associated data prep section""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add( + Permission.objects.get( + codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME + ) + ) + + obj = factories.CDSAWorkspaceFactory.create() + DataPrepWorkspaceFactory.create(target_workspace=obj.workspace) + self.client.force_login(user) + response = self.client.get(obj.get_absolute_url()) + self.assertNotContains(response, "Associated data prep workspaces") + + def test_associated_data_prep_staff_view_user(self): + """Staff view users do see the associated data prep section.""" + obj = factories.CDSAWorkspaceFactory.create() + DataPrepWorkspaceFactory.create(target_workspace=obj.workspace) + self.client.force_login(self.user) + response = self.client.get(obj.get_absolute_url()) + self.assertContains(response, "Associated data prep workspaces") + def test_associated_data_prep_workspaces_context_exists(self): obj = factories.CDSAWorkspaceFactory.create() self.client.force_login(self.user) @@ -7443,7 +7466,7 @@ def test_associated_data_prep_workspaces_context_exists(self): self.assertIn("associated_data_prep_workspaces", response.context_data) self.assertIsInstance( response.context_data["associated_data_prep_workspaces"], - DataPrepWorkspaceTable, + DataPrepWorkspaceUserTable, ) def test_only_show_one_associated_data_prep_workspace(self): diff --git a/primed/collaborative_analysis/tables.py b/primed/collaborative_analysis/tables.py index a17c5cb1..dfaeddb7 100644 --- a/primed/collaborative_analysis/tables.py +++ b/primed/collaborative_analysis/tables.py @@ -2,12 +2,10 @@ from anvil_consortium_manager.models import Workspace -class CollaborativeAnalysisWorkspaceStaffTable(tables.Table): +class CollaborativeAnalysisWorkspaceUserTable(tables.Table): """Class to render a table of Workspace objects with CollaborativeAnalysisWorkspace data.""" name = tables.columns.Column(linkify=True) - billing_project = tables.Column(linkify=True) - collaborativeanalysisworkspace__custodian = tables.Column(linkify=True) number_source_workspaces = tables.columns.Column( accessor="pk", verbose_name="Number of source workspaces", @@ -18,7 +16,6 @@ class Meta: model = Workspace fields = ( "name", - "billing_project", "collaborativeanalysisworkspace__custodian", "number_source_workspaces", ) @@ -29,16 +26,12 @@ def render_number_source_workspaces(self, record): return record.collaborativeanalysisworkspace.source_workspaces.count() -class CollaborativeAnalysisWorkspaceUserTable(tables.Table): +class CollaborativeAnalysisWorkspaceStaffTable(CollaborativeAnalysisWorkspaceUserTable): """Class to render a table of Workspace objects with CollaborativeAnalysisWorkspace data.""" name = tables.columns.Column(linkify=True) - billing_project = tables.Column() - number_source_workspaces = tables.columns.Column( - accessor="pk", - verbose_name="Number of source workspaces", - orderable=False, - ) + billing_project = tables.Column(linkify=True) + collaborativeanalysisworkspace__custodian = tables.Column(linkify=True) class Meta: model = Workspace @@ -49,7 +42,3 @@ class Meta: "number_source_workspaces", ) order_by = ("name",) - - def render_number_source_workspaces(self, record): - """Render the number of source workspaces.""" - return record.collaborativeanalysisworkspace.source_workspaces.count() diff --git a/primed/dbgap/adapters.py b/primed/dbgap/adapters.py index 626a3c10..a2ed57a9 100644 --- a/primed/dbgap/adapters.py +++ b/primed/dbgap/adapters.py @@ -2,7 +2,7 @@ from anvil_consortium_manager.forms import WorkspaceForm from anvil_consortium_manager.models import Workspace -from primed.miscellaneous_workspaces.tables import DataPrepWorkspaceTable +from primed.miscellaneous_workspaces.tables import DataPrepWorkspaceUserTable from . import forms, models, tables @@ -25,7 +25,7 @@ def get_extra_detail_context_data(self, workspace, request): associated_data_prep = Workspace.objects.filter( dataprepworkspace__target_workspace=workspace ) - extra_context["associated_data_prep_workspaces"] = DataPrepWorkspaceTable( + extra_context["associated_data_prep_workspaces"] = DataPrepWorkspaceUserTable( associated_data_prep ) extra_context["data_prep_active"] = associated_data_prep.filter( diff --git a/primed/dbgap/tables.py b/primed/dbgap/tables.py index 08443706..fa22c5b1 100644 --- a/primed/dbgap/tables.py +++ b/primed/dbgap/tables.py @@ -74,11 +74,10 @@ def render_dbgap_phs(self, value): return "phs{0:06d}".format(value) -class dbGaPWorkspaceStaffTable(tables.Table): +class dbGaPWorkspaceUserTable(tables.Table): """Class to render a table of Workspace objects with dbGaPWorkspace workspace data.""" name = tables.columns.Column(linkify=True) - billing_project = tables.Column(linkify=True) dbgap_accession = dbGaPAccessionColumn( accessor="dbgapworkspace__get_dbgap_accession", dbgap_link_accessor="dbgapworkspace__get_dbgap_link", @@ -91,11 +90,6 @@ class dbGaPWorkspaceStaffTable(tables.Table): dbgapworkspace__dbgap_consent_abbreviation = tables.columns.Column( verbose_name="Consent" ) - number_approved_dars = tables.columns.Column( - accessor="pk", - verbose_name="Approved DARs", - orderable=False, - ) dbgapworkspace__gsr_restricted = BooleanIconColumn( orderable=False, true_icon="dash-circle-fill", true_color="#ffc107" ) @@ -105,43 +99,23 @@ class Meta: model = Workspace fields = ( "name", - "billing_project", "dbgap_accession", "dbgapworkspace__dbgap_consent_abbreviation", - "number_approved_dars", "dbgapworkspace__gsr_restricted", "is_shared", ) order_by = ("name",) - def render_number_approved_dars(self, record): - n = ( - record.dbgapworkspace.get_data_access_requests(most_recent=True) - .filter(dbgap_current_status=models.dbGaPDataAccessRequest.APPROVED) - .count() - ) - return n - -class dbGaPWorkspaceUserTable(tables.Table): +class dbGaPWorkspaceStaffTable(dbGaPWorkspaceUserTable): """Class to render a table of Workspace objects with dbGaPWorkspace workspace data.""" - name = tables.columns.Column(linkify=True) - billing_project = tables.Column() - dbgap_accession = dbGaPAccessionColumn( - accessor="dbgapworkspace__get_dbgap_accession", - dbgap_link_accessor="dbgapworkspace__get_dbgap_link", - order_by=( - "dbgapworkspace__dbgap_study_accession__dbgap_phs", - "dbgapworkspace__dbgap_version", - "dbgapworkspace__dbgap_participant_set", - ), - ) - dbgapworkspace__dbgap_consent_abbreviation = tables.columns.Column( - verbose_name="Consent" + billing_project = tables.Column(linkify=True) + number_approved_dars = tables.columns.Column( + accessor="pk", + verbose_name="Approved DARs", + orderable=False, ) - dbgapworkspace__gsr_restricted = BooleanIconColumn(orderable=False) - is_shared = WorkspaceSharedWithConsortiumColumn() class Meta: model = Workspace @@ -150,11 +124,20 @@ class Meta: "billing_project", "dbgap_accession", "dbgapworkspace__dbgap_consent_abbreviation", + "number_approved_dars", "dbgapworkspace__gsr_restricted", "is_shared", ) order_by = ("name",) + def render_number_approved_dars(self, record): + n = ( + record.dbgapworkspace.get_data_access_requests(most_recent=True) + .filter(dbgap_current_status=models.dbGaPDataAccessRequest.APPROVED) + .count() + ) + return n + class dbGaPApplicationTable(tables.Table): """Class to render a table of dbGaPApplication objects.""" diff --git a/primed/dbgap/tests/test_views.py b/primed/dbgap/tests/test_views.py index c33fb131..a2ebf614 100644 --- a/primed/dbgap/tests/test_views.py +++ b/primed/dbgap/tests/test_views.py @@ -32,7 +32,7 @@ from freezegun import freeze_time from primed.duo.tests.factories import DataUseModifierFactory, DataUsePermissionFactory -from primed.miscellaneous_workspaces.tables import DataPrepWorkspaceTable +from primed.miscellaneous_workspaces.tables import DataPrepWorkspaceUserTable from primed.miscellaneous_workspaces.tests.factories import DataPrepWorkspaceFactory from primed.primed_anvil.tests.factories import ( # DataUseModifierFactory,; DataUsePermissionFactory, StudyFactory, @@ -953,6 +953,29 @@ def test_links_audit_access_view_permission(self): ), ) + def test_associated_data_prep_view_user(self): + """View users do not see the associated data prep section""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add( + Permission.objects.get( + codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME + ) + ) + + obj = factories.dbGaPWorkspaceFactory.create() + DataPrepWorkspaceFactory.create(target_workspace=obj.workspace) + self.client.force_login(user) + response = self.client.get(obj.get_absolute_url()) + self.assertNotContains(response, "Associated data prep workspaces") + + def test_associated_data_prep_staff_view_user(self): + """Staff view users do see the associated data prep section.""" + obj = factories.dbGaPWorkspaceFactory.create() + DataPrepWorkspaceFactory.create(target_workspace=obj.workspace) + self.client.force_login(self.user) + response = self.client.get(obj.get_absolute_url()) + self.assertContains(response, "Associated data prep workspaces") + def test_associated_data_prep_workspaces_context_exists(self): obj = factories.dbGaPWorkspaceFactory.create() self.client.force_login(self.user) @@ -960,7 +983,7 @@ def test_associated_data_prep_workspaces_context_exists(self): self.assertIn("associated_data_prep_workspaces", response.context_data) self.assertIsInstance( response.context_data["associated_data_prep_workspaces"], - DataPrepWorkspaceTable, + DataPrepWorkspaceUserTable, ) def test_only_show_one_associated_data_prep_workspace(self): diff --git a/primed/duo/tests/test_views.py b/primed/duo/tests/test_views.py index 96f2a5af..e22ca408 100644 --- a/primed/duo/tests/test_views.py +++ b/primed/duo/tests/test_views.py @@ -23,7 +23,7 @@ def setUp(self): self.user = UserFactory.create(username="test", password="test") self.user.user_permissions.add( Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME + codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME ) ) @@ -180,7 +180,7 @@ def setUp(self): self.user = UserFactory.create(username="test", password="test") self.user.user_permissions.add( Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME + codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME ) ) diff --git a/primed/duo/views.py b/primed/duo/views.py index f27c1e25..308cc3e1 100644 --- a/primed/duo/views.py +++ b/primed/duo/views.py @@ -1,14 +1,11 @@ -from anvil_consortium_manager.auth import ( - AnVILConsortiumManagerStaffViewRequired, - AnVILConsortiumManagerViewRequired, -) +from anvil_consortium_manager.auth import AnVILConsortiumManagerViewRequired from django.http import Http404 from django.views.generic import DetailView, ListView from . import models -class DataUsePermissionList(AnVILConsortiumManagerStaffViewRequired, ListView): +class DataUsePermissionList(AnVILConsortiumManagerViewRequired, ListView): model = models.DataUsePermission @@ -41,7 +38,7 @@ def get_context_data(self, **kwargs): return context -class DataUseModifierList(AnVILConsortiumManagerStaffViewRequired, ListView): +class DataUseModifierList(AnVILConsortiumManagerViewRequired, ListView): model = models.DataUseModifier diff --git a/primed/miscellaneous_workspaces/adapters.py b/primed/miscellaneous_workspaces/adapters.py index 24262017..17bdfb9b 100644 --- a/primed/miscellaneous_workspaces/adapters.py +++ b/primed/miscellaneous_workspaces/adapters.py @@ -93,8 +93,8 @@ class DataPrepWorkspaceAdapter(BaseWorkspaceAdapter): type = "data_prep" name = "Data prep workspace" description = "Workspaces used to prepare data for sharing or update data that is already shared" - list_table_class_staff_view = tables.DataPrepWorkspaceTable - list_table_class_view = tables.DataPrepWorkspaceTable + list_table_class_staff_view = tables.DataPrepWorkspaceStaffTable + list_table_class_view = tables.DataPrepWorkspaceUserTable workspace_form_class = WorkspaceForm workspace_data_model = models.DataPrepWorkspace workspace_data_form_class = forms.DataPrepWorkspaceForm diff --git a/primed/miscellaneous_workspaces/tables.py b/primed/miscellaneous_workspaces/tables.py index fd1132df..014f746c 100644 --- a/primed/miscellaneous_workspaces/tables.py +++ b/primed/miscellaneous_workspaces/tables.py @@ -9,30 +9,29 @@ ) -class OpenAccessWorkspaceStaffTable(tables.Table): +class OpenAccessWorkspaceUserTable(tables.Table): """Class to render a table of Workspace objects with OpenAccessWorkspace workspace data.""" name = tables.columns.Column(linkify=True) - billing_project = tables.Column(linkify=True) is_shared = WorkspaceSharedWithConsortiumColumn() + openaccessworkspace__studies = tables.ManyToManyColumn( + linkify_item=True, + ) class Meta: model = Workspace fields = ( "name", - "billing_project", "openaccessworkspace__studies", "is_shared", ) order_by = ("name",) -class OpenAccessWorkspaceUserTable(tables.Table): +class OpenAccessWorkspaceStaffTable(OpenAccessWorkspaceUserTable): """Class to render a table of Workspace objects with OpenAccessWorkspace workspace data.""" - name = tables.columns.Column(linkify=True) - billing_project = tables.Column() - is_shared = WorkspaceSharedWithConsortiumColumn() + billing_project = tables.Column(linkify=True) class Meta: model = Workspace @@ -45,11 +44,10 @@ class Meta: order_by = ("name",) -class DataPrepWorkspaceTable(tables.Table): +class DataPrepWorkspaceUserTable(tables.Table): """Class to render a table of Workspace objects with DataPrepWorkspace workspace data.""" name = tables.columns.Column(linkify=True) - # TODO: Figure out why this is not showing up dataprepworkspace__target_workspace__name = tables.columns.Column( linkify=True, verbose_name="Target workspace" ) @@ -65,3 +63,19 @@ class Meta: "dataprepworkspace__is_active", ) order_by = ("name",) + + +class DataPrepWorkspaceStaffTable(DataPrepWorkspaceUserTable): + """Class to render a table of Workspace objects with DataPrepWorkspace workspace data.""" + + billing_project = tables.columns.Column(linkify=True) + + class Meta: + model = Workspace + fields = ( + "name", + "billing_project", + "dataprepworkspace__target_workspace__name", + "dataprepworkspace__is_active", + ) + order_by = ("name",) diff --git a/primed/miscellaneous_workspaces/tests/test_tables.py b/primed/miscellaneous_workspaces/tests/test_tables.py index de536164..d6252a70 100644 --- a/primed/miscellaneous_workspaces/tests/test_tables.py +++ b/primed/miscellaneous_workspaces/tests/test_tables.py @@ -49,11 +49,32 @@ def test_row_count_with_two_objects(self): self.assertEqual(len(table.rows), 2) -class DataPrepWorkspaceTableTest(TestCase): - """Tests for the DataPrepWorkspaceTable table.""" +class DataPrepWorkspaceUserTableTest(TestCase): + """Tests for the DataPrepWorkspaceUserTable table.""" model_factory = factories.DataPrepWorkspaceFactory - table_class = tables.DataPrepWorkspaceTable + table_class = tables.DataPrepWorkspaceUserTable + + def test_row_count_with_no_objects(self): + table = self.table_class(Workspace.objects.filter(workspace_type="data_prep")) + self.assertEqual(len(table.rows), 0) + + def test_row_count_with_one_object(self): + self.model_factory.create() + table = self.table_class(Workspace.objects.filter(workspace_type="data_prep")) + self.assertEqual(len(table.rows), 1) + + def test_row_count_with_two_objects(self): + self.model_factory.create_batch(2) + table = self.table_class(Workspace.objects.filter(workspace_type="data_prep")) + self.assertEqual(len(table.rows), 2) + + +class DataPrepWorkspaceStaffTableTest(TestCase): + """Tests for the DataPrepWorkspaceUserTable table.""" + + model_factory = factories.DataPrepWorkspaceFactory + table_class = tables.DataPrepWorkspaceStaffTable def test_row_count_with_no_objects(self): table = self.table_class(Workspace.objects.filter(workspace_type="data_prep")) diff --git a/primed/primed_anvil/helpers.py b/primed/primed_anvil/helpers.py index 88460087..39193ade 100644 --- a/primed/primed_anvil/helpers.py +++ b/primed/primed_anvil/helpers.py @@ -2,6 +2,7 @@ from anvil_consortium_manager.models import WorkspaceGroupSharing from django.db.models import Exists, F, OuterRef, Value +from primed.cdsa.models import CDSAWorkspace from primed.dbgap.models import dbGaPWorkspace from primed.miscellaneous_workspaces.models import OpenAccessWorkspace @@ -34,7 +35,7 @@ def get_summary_table_data(): "access_mechanism", # Rename columns to have the same names. workspace_name=F("workspace__name"), - study=F("dbgap_study_accession__studies__short_name"), + study_name=F("dbgap_study_accession__studies__short_name"), data=F("available_data__name"), ) df_dbgap = pd.DataFrame.from_dict(dbgap) @@ -48,11 +49,25 @@ def get_summary_table_data(): "access_mechanism", # Rename columns to have the same names. workspace_name=F("workspace__name"), - study=F("studies__short_name"), + study_name=F("studies__short_name"), data=F("available_data__name"), ) df_open = pd.DataFrame.from_dict(open) + # Query for CDSAWorkspaces. + cdsa = CDSAWorkspace.objects.annotate( + access_mechanism=Value("CDSA"), + is_shared=Exists(shared), + ).values( + "is_shared", + "access_mechanism", + # Rename columns to have the same names. + workspace_name=F("workspace__name"), + study_name=F("study__short_name"), + data=F("available_data__name"), + ) + df_cdsa = pd.DataFrame.from_dict(cdsa) + # This union may not work with MySQL < 10.3: # https://code.djangoproject.com/ticket/31445 # qs = dbgap.union(open) @@ -65,20 +80,20 @@ def get_summary_table_data(): # df = pd.DataFrame.from_dict(qs) # Instead combine in pandas. - df = pd.concat([df_dbgap, df_open]) + df = pd.concat([df_cdsa, df_dbgap, df_open]) # If there are no workspaces, return an empty list. if df.empty: return [] # Sort by specific columns - df = df.sort_values(by=["study", "access_mechanism"]) + df = df.sort_values(by=["study_name", "access_mechanism"]) # Concatenate multiple studies into a single comma-delimited string. df = ( df.groupby( ["workspace_name", "data", "is_shared", "access_mechanism"], dropna=False, - )["study"] + )["study_name"] .apply(lambda x: ", ".join(x)) .reset_index() .drop("workspace_name", axis=1) @@ -90,7 +105,7 @@ def get_summary_table_data(): data = ( pd.pivot_table( df, - index=["study", "is_shared", "access_mechanism"], + index=["study_name", "is_shared", "access_mechanism"], columns=["data"], # set this to len to count the number of workspaces instead of returning a boolean value. aggfunc=lambda x: len(x) > 0, @@ -100,6 +115,7 @@ def get_summary_table_data(): ) .rename_axis(columns=None) .reset_index() + .rename(columns={"study_name": "study", "B": "c"}) ) # Remove the dummy "no_data" column if it exists. if "no_data" in data: diff --git a/primed/primed_anvil/tables.py b/primed/primed_anvil/tables.py index 30048579..24534216 100644 --- a/primed/primed_anvil/tables.py +++ b/primed/primed_anvil/tables.py @@ -59,42 +59,38 @@ def _get_bool_value(self, record, value, bound_column): return is_shared -class DefaultWorkspaceStaffTable(tables.Table): +class DefaultWorkspaceUserTable(tables.Table): """Class to use for default workspace tables in PRIMED.""" name = tables.Column(linkify=True, verbose_name="Workspace") - billing_project = tables.Column(linkify=True) - number_groups = tables.Column( - verbose_name="Number of groups shared with", - empty_values=(), - orderable=False, - accessor="workspacegroupsharing_set__count", - ) is_shared = WorkspaceSharedWithConsortiumColumn() class Meta: model = Workspace fields = ( "name", - "billing_project", - "number_groups", "is_shared", ) order_by = ("name",) -class DefaultWorkspaceUserTable(tables.Table): +class DefaultWorkspaceStaffTable(DefaultWorkspaceUserTable): """Class to use for default workspace tables in PRIMED.""" - name = tables.Column(linkify=True, verbose_name="Workspace") - billing_project = tables.Column() - is_shared = WorkspaceSharedWithConsortiumColumn() + billing_project = tables.Column(linkify=True) + number_groups = tables.Column( + verbose_name="Number of groups shared with", + empty_values=(), + orderable=False, + accessor="workspacegroupsharing_set__count", + ) class Meta: model = Workspace fields = ( "name", "billing_project", + "number_groups", "is_shared", ) order_by = ("name",) diff --git a/primed/primed_anvil/tests/test_helpers.py b/primed/primed_anvil/tests/test_helpers.py index d62b5507..78a20b57 100644 --- a/primed/primed_anvil/tests/test_helpers.py +++ b/primed/primed_anvil/tests/test_helpers.py @@ -3,6 +3,7 @@ from anvil_consortium_manager.tests.factories import WorkspaceGroupSharingFactory from django.test import TestCase +from primed.cdsa.tests.factories import CDSAWorkspaceFactory from primed.dbgap.tests.factories import dbGaPWorkspaceFactory from primed.miscellaneous_workspaces.tests.factories import OpenAccessWorkspaceFactory from primed.primed_anvil.tests.factories import AvailableDataFactory, StudyFactory @@ -44,8 +45,8 @@ def test_one_dbgap_workspace_one_study_not_shared_no_available_data(self): def test_one_open_access_workspace_one_study_not_shared_no_available_data(self): AvailableDataFactory.create(name="Foo") study = StudyFactory.create(short_name="TEST") - open_access_workspace = OpenAccessWorkspaceFactory.create() - open_access_workspace.studies.add(study) + workspace = OpenAccessWorkspaceFactory.create() + workspace.studies.add(study) res = helpers.get_summary_table_data() self.assertEqual(len(res), 1) self.assertEqual(len(res[0]), 4) @@ -59,13 +60,11 @@ def test_one_open_access_workspace_one_study_not_shared_no_available_data(self): self.assertIn("Foo", res[0]) self.assertEqual(res[0]["Foo"], False) - def test_one_workspace_one_study_not_shared_with_one_available_data(self): + def test_one_dbgap_workspace_one_study_not_shared_with_one_available_data(self): available_data = AvailableDataFactory.create(name="Foo") study = StudyFactory.create(short_name="TEST") - dbgap_workspace = dbGaPWorkspaceFactory.create( - dbgap_study_accession__studies=[study] - ) - dbgap_workspace.available_data.add(available_data) + workspace = dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) + workspace.available_data.add(available_data) res = helpers.get_summary_table_data() self.assertEqual(len(res), 1) self.assertEqual(len(res[0]), 4) @@ -79,15 +78,13 @@ def test_one_workspace_one_study_not_shared_with_one_available_data(self): self.assertIn("Foo", res[0]) self.assertEqual(res[0]["Foo"], True) - def test_one_workspace_one_study_not_shared_with_two_available_data(self): + def test_one_dbgap_workspace_one_study_not_shared_with_two_available_data(self): available_data_1 = AvailableDataFactory.create(name="Foo") available_data_2 = AvailableDataFactory.create(name="Bar") study = StudyFactory.create(short_name="TEST") - dbgap_workspace = dbGaPWorkspaceFactory.create( - dbgap_study_accession__studies=[study] - ) - dbgap_workspace.available_data.add(available_data_1) - dbgap_workspace.available_data.add(available_data_2) + workspace = dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) + workspace.available_data.add(available_data_1) + workspace.available_data.add(available_data_2) res = helpers.get_summary_table_data() self.assertEqual(len(res), 1) self.assertEqual(len(res[0]), 5) @@ -101,7 +98,7 @@ def test_one_workspace_one_study_not_shared_with_two_available_data(self): self.assertIn("Foo", res[0]) self.assertEqual(res[0]["Foo"], True) - def test_one_workspace_two_studies_not_shared_no_available_data(self): + def test_one_dbgap_workspace_two_studies_not_shared_no_available_data(self): AvailableDataFactory.create(name="Foo") study_1 = StudyFactory.create(short_name="TEST") study_2 = StudyFactory.create(short_name="Other") @@ -119,14 +116,12 @@ def test_one_workspace_two_studies_not_shared_no_available_data(self): self.assertIn("Foo", res[0]) self.assertEqual(res[0]["Foo"], False) - def test_one_workspace_one_study_shared_no_available_data(self): + def test_one_dbgap_workspace_one_study_shared_no_available_data(self): AvailableDataFactory.create(name="Foo") study = StudyFactory.create(short_name="TEST") - dbgap_workspace = dbGaPWorkspaceFactory.create( - dbgap_study_accession__studies=[study] - ) + workspace = dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) WorkspaceGroupSharingFactory.create( - workspace=dbgap_workspace.workspace, group__name="PRIMED_ALL" + workspace=workspace.workspace, group__name="PRIMED_ALL" ) res = helpers.get_summary_table_data() self.assertEqual(len(res), 1) @@ -141,7 +136,7 @@ def test_one_workspace_one_study_shared_no_available_data(self): self.assertIn("Foo", res[0]) self.assertEqual(res[0]["Foo"], False) - def test_two_workspaces_one_study(self): + def test_two_dbgap_workspaces_one_study(self): AvailableDataFactory.create(name="Foo") study = StudyFactory.create(short_name="TEST") dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) @@ -159,21 +154,21 @@ def test_two_workspaces_one_study(self): self.assertIn("Foo", res[0]) self.assertEqual(res[0]["Foo"], False) - def test_two_workspaces_one_study_one_shared(self): + def test_two_dbgap_workspaces_one_study_one_shared(self): available_data_1 = AvailableDataFactory.create(name="Foo") available_data_2 = AvailableDataFactory.create(name="Bar") study = StudyFactory.create(short_name="TEST") - dbgap_workspace_1 = dbGaPWorkspaceFactory.create( + workspace_1 = dbGaPWorkspaceFactory.create( dbgap_study_accession__studies=[study] ) - dbgap_workspace_1.available_data.add(available_data_1) + workspace_1.available_data.add(available_data_1) WorkspaceGroupSharingFactory.create( - workspace=dbgap_workspace_1.workspace, group__name="PRIMED_ALL" + workspace=workspace_1.workspace, group__name="PRIMED_ALL" ) - dbgap_workspace_2 = dbGaPWorkspaceFactory.create( + workspace_2 = dbGaPWorkspaceFactory.create( dbgap_study_accession__studies=[study] ) - dbgap_workspace_2.available_data.add(available_data_2) + workspace_2.available_data.add(available_data_2) res = helpers.get_summary_table_data() self.assertEqual(len(res), 2) self.assertIn( @@ -197,7 +192,7 @@ def test_two_workspaces_one_study_one_shared(self): res, ) - def test_two_workspaces_multiple_studies(self): + def test_two_dbgap_workspaces_multiple_studies(self): AvailableDataFactory.create(name="Foo") study_1 = StudyFactory.create(short_name="TEST") study_2 = StudyFactory.create(short_name="Other") @@ -229,8 +224,8 @@ def test_one_dbgap_workspace_one_open_access_workspace_different_studies(self): study_1 = StudyFactory.create(short_name="TEST") dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study_1]) study_2 = StudyFactory.create(short_name="Other") - open_access_workspace = OpenAccessWorkspaceFactory.create() - open_access_workspace.studies.add(study_2) + workspace = OpenAccessWorkspaceFactory.create() + workspace.studies.add(study_2) res = helpers.get_summary_table_data() self.assertEqual(len(res), 2) self.assertIn( @@ -256,8 +251,8 @@ def test_one_dbgap_workspace_one_open_access_workspace_same_study(self): AvailableDataFactory.create(name="Foo") study = StudyFactory.create(short_name="TEST") dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) - open_access_workspace = OpenAccessWorkspaceFactory.create() - open_access_workspace.studies.add(study) + workspace = OpenAccessWorkspaceFactory.create() + workspace.studies.add(study) res = helpers.get_summary_table_data() self.assertEqual(len(res), 2) self.assertIn( @@ -284,12 +279,10 @@ def test_one_dbgap_workspace_one_open_access_workspace_different_available_data( ): available_data_1 = AvailableDataFactory.create(name="Foo") study = StudyFactory.create(short_name="TEST") - dbgap_workspace = dbGaPWorkspaceFactory.create( - dbgap_study_accession__studies=[study] - ) - dbgap_workspace.available_data.add(available_data_1) - open_access_workspace = OpenAccessWorkspaceFactory.create() - open_access_workspace.studies.add(study) + workspace = dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) + workspace.available_data.add(available_data_1) + workspace = OpenAccessWorkspaceFactory.create() + workspace.studies.add(study) res = helpers.get_summary_table_data() self.assertEqual(len(res), 2) self.assertIn( @@ -310,3 +303,244 @@ def test_one_dbgap_workspace_one_open_access_workspace_different_available_data( }, res, ) + + def test_one_cdsa_workspace_not_shared_no_available_data(self): + AvailableDataFactory.create(name="Foo") + study = StudyFactory.create(short_name="TEST") + CDSAWorkspaceFactory.create(study=study) + res = helpers.get_summary_table_data() + self.assertEqual(len(res), 1) + self.assertEqual(len(res[0]), 4) + self.assertIn("study", res[0]) + self.assertEqual(res[0]["study"], "TEST") + self.assertIn("access_mechanism", res[0]) + self.assertEqual(res[0]["access_mechanism"], "CDSA") + self.assertIn("is_shared", res[0]) + self.assertEqual(res[0]["is_shared"], False) + # Available data columns. + self.assertIn("Foo", res[0]) + self.assertEqual(res[0]["Foo"], False) + + def test_one_cdsa_workspace_not_shared_with_one_available_data(self): + available_data = AvailableDataFactory.create(name="Foo") + study = StudyFactory.create(short_name="TEST") + workspace = CDSAWorkspaceFactory.create(study=study) + workspace.available_data.add(available_data) + res = helpers.get_summary_table_data() + self.assertEqual(len(res), 1) + self.assertEqual(len(res[0]), 4) + self.assertIn("study", res[0]) + self.assertEqual(res[0]["study"], "TEST") + self.assertIn("access_mechanism", res[0]) + self.assertEqual(res[0]["access_mechanism"], "CDSA") + self.assertIn("is_shared", res[0]) + self.assertEqual(res[0]["is_shared"], False) + # Available data columns. + self.assertIn("Foo", res[0]) + self.assertEqual(res[0]["Foo"], True) + + def test_one_cdsa_workspace_not_shared_with_two_available_data(self): + available_data_1 = AvailableDataFactory.create(name="Foo") + available_data_2 = AvailableDataFactory.create(name="Bar") + study = StudyFactory.create(short_name="TEST") + workspace = CDSAWorkspaceFactory.create( + study=study, + ) + workspace.available_data.add(available_data_1) + workspace.available_data.add(available_data_2) + res = helpers.get_summary_table_data() + self.assertEqual(len(res), 1) + self.assertEqual(len(res[0]), 5) + self.assertIn("study", res[0]) + self.assertEqual(res[0]["study"], "TEST") + self.assertIn("access_mechanism", res[0]) + self.assertEqual(res[0]["access_mechanism"], "CDSA") + self.assertIn("is_shared", res[0]) + self.assertEqual(res[0]["is_shared"], False) + # Available data columns. + self.assertIn("Foo", res[0]) + self.assertEqual(res[0]["Foo"], True) + + def test_one_cdsa_workspace_one_study_shared_no_available_data(self): + AvailableDataFactory.create(name="Foo") + study = StudyFactory.create(short_name="TEST") + workspace = CDSAWorkspaceFactory.create(study=study) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group__name="PRIMED_ALL" + ) + res = helpers.get_summary_table_data() + self.assertEqual(len(res), 1) + self.assertEqual(len(res[0]), 4) + self.assertIn("study", res[0]) + self.assertEqual(res[0]["study"], "TEST") + self.assertIn("access_mechanism", res[0]) + self.assertEqual(res[0]["access_mechanism"], "CDSA") + self.assertIn("is_shared", res[0]) + self.assertEqual(res[0]["is_shared"], True) + # Available data columns. + self.assertIn("Foo", res[0]) + self.assertEqual(res[0]["Foo"], False) + + def test_two_cdsa_workspaces_one_study(self): + AvailableDataFactory.create(name="Foo") + study = StudyFactory.create(short_name="TEST") + CDSAWorkspaceFactory.create(study=study) + CDSAWorkspaceFactory.create(study=study) + res = helpers.get_summary_table_data() + self.assertEqual(len(res), 1) + self.assertEqual(len(res[0]), 4) + self.assertIn("study", res[0]) + self.assertEqual(res[0]["study"], "TEST") + self.assertIn("access_mechanism", res[0]) + self.assertEqual(res[0]["access_mechanism"], "CDSA") + self.assertIn("is_shared", res[0]) + self.assertEqual(res[0]["is_shared"], False) + # Available data columns. + self.assertIn("Foo", res[0]) + self.assertEqual(res[0]["Foo"], False) + + def test_two_cdsa_workspaces_one_study_one_shared(self): + available_data_1 = AvailableDataFactory.create(name="Foo") + available_data_2 = AvailableDataFactory.create(name="Bar") + study = StudyFactory.create(short_name="TEST") + workspace_1 = CDSAWorkspaceFactory.create(study=study) + workspace_1.available_data.add(available_data_1) + WorkspaceGroupSharingFactory.create( + workspace=workspace_1.workspace, group__name="PRIMED_ALL" + ) + workspace_2 = CDSAWorkspaceFactory.create(study=study) + workspace_2.available_data.add(available_data_2) + res = helpers.get_summary_table_data() + self.assertEqual(len(res), 2) + self.assertIn( + { + "study": "TEST", + "is_shared": True, + "access_mechanism": "CDSA", + "Foo": True, + "Bar": False, + }, + res, + ) + self.assertIn( + { + "study": "TEST", + "is_shared": False, + "access_mechanism": "CDSA", + "Foo": False, + "Bar": True, + }, + res, + ) + + def test_two_cdsa_workspaces(self): + AvailableDataFactory.create(name="Foo") + study_1 = StudyFactory.create(short_name="TEST") + study_2 = StudyFactory.create(short_name="Other") + CDSAWorkspaceFactory.create(study=study_1) + CDSAWorkspaceFactory.create(study=study_2) + res = helpers.get_summary_table_data() + self.assertEqual(len(res), 2) + self.assertIn( + { + "study": "Other", + "is_shared": False, + "access_mechanism": "CDSA", + "Foo": False, + }, + res, + ) + self.assertIn( + { + "study": "TEST", + "is_shared": False, + "access_mechanism": "CDSA", + "Foo": False, + }, + res, + ) + + def test_one_cdsa_workspace_one_open_access_workspace_different_studies(self): + AvailableDataFactory.create(name="Foo") + study_1 = StudyFactory.create(short_name="TEST") + CDSAWorkspaceFactory.create(study=study_1) + study_2 = StudyFactory.create(short_name="Other") + workspace = OpenAccessWorkspaceFactory.create() + workspace.studies.add(study_2) + res = helpers.get_summary_table_data() + self.assertEqual(len(res), 2) + self.assertIn( + { + "study": "TEST", + "is_shared": False, + "access_mechanism": "CDSA", + "Foo": False, + }, + res, + ) + self.assertIn( + { + "study": "Other", + "is_shared": False, + "access_mechanism": "Open access", + "Foo": False, + }, + res, + ) + + def test_one_cdsa_workspace_one_open_access_workspace_same_study(self): + AvailableDataFactory.create(name="Foo") + study = StudyFactory.create(short_name="TEST") + CDSAWorkspaceFactory.create(study=study) + workspace = OpenAccessWorkspaceFactory.create() + workspace.studies.add(study) + res = helpers.get_summary_table_data() + self.assertEqual(len(res), 2) + self.assertIn( + { + "study": "TEST", + "is_shared": False, + "access_mechanism": "CDSA", + "Foo": False, + }, + res, + ) + self.assertIn( + { + "study": "TEST", + "is_shared": False, + "access_mechanism": "Open access", + "Foo": False, + }, + res, + ) + + def test_one_cdsa_workspace_one_open_access_workspace_different_available_data( + self, + ): + available_data_1 = AvailableDataFactory.create(name="Foo") + study = StudyFactory.create(short_name="TEST") + workspace = CDSAWorkspaceFactory.create(study=study) + workspace.available_data.add(available_data_1) + workspace = OpenAccessWorkspaceFactory.create() + workspace.studies.add(study) + res = helpers.get_summary_table_data() + self.assertEqual(len(res), 2) + self.assertIn( + { + "study": "TEST", + "is_shared": False, + "access_mechanism": "CDSA", + "Foo": True, + }, + res, + ) + self.assertIn( + { + "study": "TEST", + "is_shared": False, + "access_mechanism": "Open access", + "Foo": False, + }, + res, + ) diff --git a/primed/primed_anvil/tests/test_views.py b/primed/primed_anvil/tests/test_views.py index 9e1b079f..9a95ea11 100644 --- a/primed/primed_anvil/tests/test_views.py +++ b/primed/primed_anvil/tests/test_views.py @@ -1239,3 +1239,31 @@ def test_table_rows(self): response = self.client.get(self.get_url()) self.assertIn("summary_table", response.context_data) self.assertEqual(len(response.context_data["summary_table"].rows), 2) + + def test_includes_open_access_workspaces(self): + """Open access workspaces are included in the table.""" + study = StudyFactory.create() + open_workspace = OpenAccessWorkspaceFactory.create() + open_workspace.studies.add(study) + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("summary_table", response.context_data) + self.assertEqual(len(response.context_data["summary_table"].rows), 1) + + def test_includes_dbgap_workspaces(self): + """dbGaP workspaces are included in the table.""" + # One open access workspace with one study, with one available data type. + # One dbGaP workspae with two studies. + dbGaPWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("summary_table", response.context_data) + self.assertEqual(len(response.context_data["summary_table"].rows), 1) + + def test_includes_cdsa_workspaces(self): + """CDSA workspaces are included in the table.""" + CDSAWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("summary_table", response.context_data) + self.assertEqual(len(response.context_data["summary_table"].rows), 1) diff --git a/primed/templates/snippets/data_prep_workspace_table.html b/primed/templates/snippets/data_prep_workspace_table.html index cb9c9b66..32d04cff 100644 --- a/primed/templates/snippets/data_prep_workspace_table.html +++ b/primed/templates/snippets/data_prep_workspace_table.html @@ -1,5 +1,7 @@ {% load render_table from django_tables2 %} +{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %} +
@@ -18,3 +20,5 @@

+ +{% endif %} diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index d5c4d978..f2991d16 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -233,7 +233,7 @@ types-pytz==2024.1.0.20240203 # via django-stubs types-pyyaml==6.0.12.12 # via django-stubs -types-requests==2.31.0.20240311 +types-requests==2.31.0.20240406 # via -r requirements/dev-requirements.in typing-extensions==4.8.0 # via @@ -257,7 +257,7 @@ virtualenv==20.25.1 # via pre-commit wcwidth==0.2.13 # via prompt-toolkit -werkzeug==3.0.1 +werkzeug==3.0.2 # via -r requirements/dev-requirements.in zipp==3.17.0 # via diff --git a/requirements/requirements.txt b/requirements/requirements.txt index fe11664b..58c39805 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -89,9 +89,9 @@ django-login-required-middleware==0.9.0 # via -r requirements/requirements.in django-maintenance-mode==0.21.1 # via -r requirements/requirements.in -django-model-utils==4.4.0 +django-model-utils==4.5.0 # via -r requirements/requirements.in -django-picklefield==3.1 +django-picklefield==3.2 # via # -r requirements/requirements.in # django-constance @@ -101,7 +101,7 @@ django-simple-history==3.5.0 # django-anvil-consortium-manager django-tables2==2.7.0 # via django-anvil-consortium-manager -django-tree-queries==0.16.1 +django-tree-queries==0.18.0 # via -r requirements/requirements.in fastobo==0.12.3 # via pronto @@ -206,7 +206,7 @@ sqlparse==0.4.4 # via # -r requirements/requirements.in # django -tablib==3.6.0 +tablib==3.6.1 # via -r requirements/requirements.in tenacity==8.2.1 # via