Skip to content

Commit

Permalink
Merge pull request #415 from UW-GAC/feature/sensitive-data
Browse files Browse the repository at this point in the history
Add sensitive data tracking to dbGaP and CDSA workspaces
  • Loading branch information
amstilp authored Jan 30, 2024
2 parents 5ad2fc3 + d34881c commit a448fa8
Show file tree
Hide file tree
Showing 18 changed files with 218 additions and 65 deletions.
8 changes: 8 additions & 0 deletions primed/cdsa/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ class Meta:
class CDSAWorkspaceForm(Bootstrap5MediaFormMixin, forms.ModelForm):
"""Form for `CDSAWorkspace` objects."""

is_sensitive = forms.TypedChoiceField(
coerce=lambda x: x == "True",
choices=((True, "Primary"), (False, "Component")),
widget=forms.RadioSelect,
label="Sensitive data?",
)

class Meta:
model = models.CDSAWorkspace
fields = (
Expand All @@ -107,6 +114,7 @@ class Meta:
"data_use_modifiers",
"disease_term",
"data_use_limitations",
"is_sensitive",
"acknowledgments",
"available_data",
"disease_term",
Expand Down
33 changes: 33 additions & 0 deletions primed/cdsa/migrations/0012_cdsaworkspace_is_sensitive_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 4.2.8 on 2024-01-30 00:48

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("cdsa", "0011_signedagreement_add_replaced_status"),
]

operations = [
migrations.AddField(
model_name="cdsaworkspace",
name="is_sensitive",
field=models.BooleanField(
default=False,
help_text="Indicator of whether this workspace contains sensitive data.",
verbose_name="Sensitive?",
),
preserve_default=False,
),
migrations.AddField(
model_name="historicalcdsaworkspace",
name="is_sensitive",
field=models.BooleanField(
default=False,
help_text="Indicator of whether this workspace contains sensitive data.",
verbose_name="Sensitive?",
),
preserve_default=False,
),
]
4 changes: 4 additions & 0 deletions primed/cdsa/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@ class CDSAWorkspace(
help_text="Data available in this accession.",
blank=True,
)
is_sensitive = models.BooleanField(
verbose_name="Sensitive?",
help_text="Indicator of whether this workspace contains sensitive data.",
)

class Meta:
verbose_name = " CDSA workspace"
Expand Down
4 changes: 4 additions & 0 deletions primed/cdsa/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ class CDSAWorkspaceStaffTable(tables.Table):
verbose_name="DUO modifiers",
linkify_item=True,
)
cdsaworkspace__is_sensitive = BooleanIconColumn(orderable=False)
is_shared = WorkspaceSharedWithConsortiumColumn()

class Meta:
Expand All @@ -320,6 +321,7 @@ class Meta:
"cdsaworkspace__study",
"cdsaworkspace__data_use_permission__abbreviation",
"cdsaworkspace__data_use_modifiers",
"cdsaworkspace__is_sensitive",
)
order_by = ("name",)

Expand All @@ -337,6 +339,7 @@ class CDSAWorkspaceUserTable(tables.Table):
transform=lambda x: x.abbreviation,
verbose_name="DUO modifiers",
)
cdsaworkspace__is_sensitive = BooleanIconColumn(orderable=False)
is_shared = WorkspaceSharedWithConsortiumColumn()

class Meta:
Expand All @@ -347,5 +350,6 @@ class Meta:
"cdsaworkspace__study",
"cdsaworkspace__data_use_permission__abbreviation",
"cdsaworkspace__data_use_modifiers",
"cdsaworkspace__is_sensitive",
)
order_by = ("name",)
1 change: 1 addition & 0 deletions primed/cdsa/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class CDSAWorkspaceFactory(DjangoModelFactory):
requested_by = SubFactory(UserFactory)
data_use_permission = SubFactory(DataUsePermissionFactory)
workspace = SubFactory(WorkspaceFactory, workspace_type="cdsa")
is_sensitive = Faker("boolean")

@post_generation
def authorization_domains(self, create, extracted, **kwargs):
Expand Down
30 changes: 30 additions & 0 deletions primed/cdsa/tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ def test_valid(self):
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertTrue(form.is_valid())
Expand All @@ -527,6 +528,7 @@ def test_valid_with_one_data_use_modifier(self):
"data_use_modifier": DataUseModifier.objects.all(),
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertTrue(form.is_valid())
Expand All @@ -541,6 +543,7 @@ def test_valid_with_two_data_use_modifiers(self):
"data_use_modifier": DataUseModifier.objects.all(),
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertTrue(form.is_valid())
Expand All @@ -554,6 +557,7 @@ def test_invalid_missing_workspace(self):
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertFalse(form.is_valid())
Expand All @@ -571,6 +575,7 @@ def test_invalid_missing_study(self):
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertFalse(form.is_valid())
Expand All @@ -588,6 +593,7 @@ def test_invalid_missing_requested_by(self):
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertFalse(form.is_valid())
Expand All @@ -605,6 +611,7 @@ def test_invalid_missing_data_use_permission(self):
# "data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertFalse(form.is_valid())
Expand All @@ -622,6 +629,7 @@ def test_invalid_missing_data_use_limitations(self):
"data_use_permission": self.duo_permission,
# "data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertFalse(form.is_valid())
Expand All @@ -639,6 +647,7 @@ def test_invalid_missing_acknowledgments(self):
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
# "acknowledgments": "test acknowledgmnts",
"is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertFalse(form.is_valid())
Expand All @@ -647,6 +656,24 @@ def test_invalid_missing_acknowledgments(self):
self.assertEqual(len(form.errors["acknowledgments"]), 1)
self.assertIn("required", form.errors["acknowledgments"][0])

def test_invalid_missing_is_sensitive(self):
"""Form is valid? when missing is_sensitive."""
form_data = {
"workspace": self.workspace,
"study": self.study,
"requested_by": self.requester,
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
# "is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertFalse(form.is_valid())
self.assertEqual(len(form.errors), 1)
self.assertIn("is_sensitive", form.errors)
self.assertEqual(len(form.errors["is_sensitive"]), 1)
self.assertIn("required", form.errors["is_sensitive"][0])

def test_invalid_duplicate_workspace(self):
"""Form is invalid with a duplicated workspace."""
cdsa_workspace = factories.CDSAWorkspaceFactory.create()
Expand All @@ -657,6 +684,7 @@ def test_invalid_duplicate_workspace(self):
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertFalse(form.is_valid())
Expand All @@ -676,6 +704,7 @@ def test_valid_one_available_data(self):
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"available_data": [available_data],
"is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertTrue(form.is_valid())
Expand All @@ -691,6 +720,7 @@ def test_valid_two_available_data(self):
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"available_data": available_data,
"is_sensitive": False,
}
form = self.form_class(data=form_data)
self.assertTrue(form.is_valid())
19 changes: 1 addition & 18 deletions primed/cdsa/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,7 @@ def test_model_saving(self):
acknowledgments="test acknowledgments",
requested_by=requester,
workspace=workspace,
is_sensitive=False,
)
instance.save()
self.assertIsInstance(instance, models.CDSAWorkspace)
Expand All @@ -629,24 +630,6 @@ def test_str_method(self):
instance = factories.CDSAWorkspaceFactory.create()
self.assertIsInstance(instance.__str__(), str)

def test_can_add_data_use_permission(self):
"""Saving a model with data_use_permission set is valid."""
workspace = WorkspaceFactory.create()
study = StudyFactory.create()
requester = UserFactory.create()
data_use_permission = DataUsePermissionFactory.create()
instance = models.CDSAWorkspace(
workspace=workspace,
study=study,
data_use_limitations="test limitations",
acknowledgments="test acknowledgments",
requested_by=requester,
data_use_permission=data_use_permission,
)
instance.save()
self.assertIsInstance(instance, models.CDSAWorkspace)
self.assertEqual(instance.data_use_permission, data_use_permission)

def test_can_add_data_use_modifiers(self):
"""Saving a model with data_use_permission and data_use_modifiers set is valid."""
data_use_permission = DataUsePermissionFactory.create()
Expand Down
3 changes: 3 additions & 0 deletions primed/cdsa/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6188,6 +6188,7 @@ def test_creates_upload_workspace_without_duos(self):
"workspacedata-0-data_use_limitations": "test limitations",
"workspacedata-0-acknowledgments": "test acknowledgments",
"workspacedata-0-requested_by": self.requester.pk,
"workspacedata-0-is_sensitive": False,
},
)
self.assertEqual(response.status_code, 302)
Expand Down Expand Up @@ -6244,6 +6245,7 @@ def test_creates_upload_workspace_with_duo_modifiers(self):
data_use_modifier_2.pk,
],
"workspacedata-0-requested_by": self.requester.pk,
"workspacedata-0-is_sensitive": False,
},
)
self.assertEqual(response.status_code, 302)
Expand Down Expand Up @@ -6289,6 +6291,7 @@ def test_creates_upload_workspace_with_disease_term(self):
"workspacedata-0-data_use_permission": data_use_permission.pk,
"workspacedata-0-disease_term": "foo",
"workspacedata-0-requested_by": self.requester.pk,
"workspacedata-0-is_sensitive": False,
},
)
self.assertEqual(response.status_code, 302)
Expand Down
8 changes: 8 additions & 0 deletions primed/dbgap/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ class Meta:
class dbGaPWorkspaceForm(Bootstrap5MediaFormMixin, forms.ModelForm):
"""Form for a dbGaPWorkspace object."""

is_sensitive = forms.TypedChoiceField(
coerce=lambda x: x == "True",
choices=((True, "Primary"), (False, "Component")),
widget=forms.RadioSelect,
label="Sensitive data?",
)

class Meta:
model = models.dbGaPWorkspace
fields = (
Expand All @@ -39,6 +46,7 @@ class Meta:
"dbgap_consent_abbreviation",
"dbgap_consent_code",
"data_use_limitations",
"is_sensitive",
"acknowledgments",
"data_use_permission",
"disease_term",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 4.2.8 on 2024-01-30 00:48

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("dbgap", "0008_dbgapapplication_anvil_access_group_verbose"),
]

operations = [
migrations.AddField(
model_name="dbgapworkspace",
name="is_sensitive",
field=models.BooleanField(
default=False,
help_text="Indicator of whether this workspace contains sensitive data.",
verbose_name="Sensitive?",
),
preserve_default=False,
),
migrations.AddField(
model_name="historicaldbgapworkspace",
name="is_sensitive",
field=models.BooleanField(
default=False,
help_text="Indicator of whether this workspace contains sensitive data.",
verbose_name="Sensitive?",
),
preserve_default=False,
),
]
4 changes: 4 additions & 0 deletions primed/dbgap/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ class dbGaPWorkspace(
help_text="Data available in this accession.",
blank=True,
)
is_sensitive = models.BooleanField(
verbose_name="Sensitive?",
help_text="Indicator of whether this workspace contains sensitive data.",
)

class Meta:
# Add a white space to prevent autocapitalization fo the "d" in "dbGaP".
Expand Down
9 changes: 8 additions & 1 deletion primed/dbgap/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
from django.template import Context, Template
from django.utils.html import format_html

from primed.primed_anvil.tables import WorkspaceSharedWithConsortiumColumn
from primed.primed_anvil.tables import (
BooleanIconColumn,
WorkspaceSharedWithConsortiumColumn,
)

from . import models

Expand Down Expand Up @@ -93,6 +96,7 @@ class dbGaPWorkspaceStaffTable(tables.Table):
verbose_name="Approved DARs",
orderable=False,
)
dbgapworkspace__is_sensitive = BooleanIconColumn(orderable=False)
is_shared = WorkspaceSharedWithConsortiumColumn()

class Meta:
Expand All @@ -103,6 +107,7 @@ class Meta:
"dbgap_accession",
"dbgapworkspace__dbgap_consent_abbreviation",
"number_approved_dars",
"dbgapworkspace__is_sensitive",
"is_shared",
)
order_by = ("name",)
Expand Down Expand Up @@ -133,6 +138,7 @@ class dbGaPWorkspaceUserTable(tables.Table):
dbgapworkspace__dbgap_consent_abbreviation = tables.columns.Column(
verbose_name="Consent"
)
dbgapworkspace__is_sensitive = BooleanIconColumn(orderable=False)
is_shared = WorkspaceSharedWithConsortiumColumn()

class Meta:
Expand All @@ -142,6 +148,7 @@ class Meta:
"billing_project",
"dbgap_accession",
"dbgapworkspace__dbgap_consent_abbreviation",
"dbgapworkspace__is_sensitive",
"is_shared",
)
order_by = ("name",)
Expand Down
Loading

0 comments on commit a448fa8

Please sign in to comment.