Skip to content

Commit

Permalink
Merge pull request #506 from UW-GAC/feature/cdsa-data-affiliate-addit…
Browse files Browse the repository at this point in the history
…ional-limitations

Track additional limitations associated with Data Affiliate CDSAs
  • Loading branch information
amstilp authored Mar 14, 2024
2 parents f42b6a6 + 704c525 commit 6e5806f
Show file tree
Hide file tree
Showing 15 changed files with 415 additions and 32 deletions.
12 changes: 9 additions & 3 deletions add_cdsa_example_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@
ManagedGroupFactory,
)
from django.conf import settings
from django.core.management import call_command

from primed.cdsa.tests import factories
from primed.duo.tests.factories import DataUseModifierFactory, DataUsePermissionFactory
from primed.duo.models import DataUseModifier, DataUsePermission
from primed.primed_anvil.models import Study, StudySite
from primed.primed_anvil.tests.factories import StudyFactory, StudySiteFactory
from primed.users.models import User
from primed.users.tests.factories import UserFactory

# Load duos
call_command("load_duo")

# Create major versions
major_version = factories.AgreementMajorVersionFactory.create(version=1)

Expand All @@ -28,8 +32,8 @@
)

# Create a couple signed CDSAs.
dup = DataUsePermissionFactory.create(abbreviation="GRU")
dum = DataUseModifierFactory.create(abbreviation="NPU")
dup = DataUsePermission.objects.get(abbreviation="GRU")
dum = DataUseModifier.objects.get(abbreviation="NPU")

# create the CDSA auth group
cdsa_group = ManagedGroupFactory.create(name=settings.ANVIL_CDSA_GROUP_NAME)
Expand Down Expand Up @@ -113,6 +117,7 @@
signed_agreement__signing_institution="UW",
study=Study.objects.get(short_name="MESA"),
signed_agreement__version=v10,
additional_limitations="This data can only be used for testing the app.",
)
GroupGroupMembershipFactory.create(
parent_group=cdsa_group, child_group=cdsa_1006.signed_agreement.anvil_access_group
Expand Down Expand Up @@ -219,5 +224,6 @@
workspace__name="DEMO_PRIMED_CDSA_MESA_2",
study=Study.objects.get(short_name="MESA"),
data_use_permission=dup,
additional_limitations="Additional limitations for workspace.",
)
cdsa_workspace_2.data_use_modifiers.add(dum)
11 changes: 11 additions & 0 deletions primed/cdsa/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,14 @@ class CDSAWorkspaceAdapter(BaseWorkspaceAdapter):
workspace_data_model = models.CDSAWorkspace
workspace_data_form_class = forms.CDSAWorkspaceForm
workspace_detail_template_name = "cdsa/cdsaworkspace_detail.html"

def get_extra_detail_context_data(self, workspace, request):
# Get the primary CDSA for this study, assuming it exists.
extra_context = {}
# Data use limitations from CDSA
try:
extra_context["primary_cdsa"] = workspace.cdsaworkspace.get_primary_cdsa()
except models.DataAffiliateAgreement.DoesNotExist:
extra_context["primary_cdsa"] = None

return extra_context
3 changes: 2 additions & 1 deletion primed/cdsa/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class Meta:
fields = (
"signed_agreement",
"study",
"additional_limitations",
)
widgets = {
"study": autocomplete.ModelSelect2(
Expand Down Expand Up @@ -114,7 +115,7 @@ class Meta:
"data_use_permission",
"data_use_modifiers",
"disease_term",
"data_use_limitations",
"additional_limitations",
"gsr_restricted",
"acknowledgments",
"available_data",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.2.10 on 2024-03-12 21:38

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("cdsa", "0014_gsr_restricted_help_and_verbose"),
]

operations = [
migrations.AddField(
model_name="dataaffiliateagreement",
name="additional_limitations",
field=models.TextField(
blank=True,
help_text="Additional limitations on data use as specified in the signed CDSA.",
),
),
migrations.AddField(
model_name="historicaldataaffiliateagreement",
name="additional_limitations",
field=models.TextField(
blank=True,
help_text="Additional limitations on data use as specified in the signed CDSA.",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.10 on 2024-03-14 18:35

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("cdsa", "0015_dataaffiliateagreement_additional_limitations_and_more"),
]

operations = [
migrations.RenameField(
model_name="cdsaworkspace",
old_name="data_use_limitations",
new_name="additional_limitations",
),
migrations.RenameField(
model_name="historicalcdsaworkspace",
old_name="data_use_limitations",
new_name="additional_limitations",
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 4.2.10 on 2024-03-14 18:36

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
(
"cdsa",
"0016_rename_data_use_limitations_cdsaworkspace_additional_limitations_and_more",
),
]

operations = [
migrations.AlterField(
model_name="cdsaworkspace",
name="additional_limitations",
field=models.TextField(
blank=True,
help_text="Additional data use limitations that cannot be captured by DUO.",
),
),
migrations.AlterField(
model_name="historicalcdsaworkspace",
name="additional_limitations",
field=models.TextField(
blank=True,
help_text="Additional data use limitations that cannot be captured by DUO.",
),
),
]
29 changes: 27 additions & 2 deletions primed/cdsa/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,13 +277,28 @@ class DataAffiliateAgreement(TimeStampedModel, AgreementTypeModel, models.Model)
help_text="Study that this agreement is associated with.",
)
anvil_upload_group = models.ForeignKey(ManagedGroup, on_delete=models.PROTECT)
additional_limitations = models.TextField(
blank=True,
help_text="Additional limitations on data use as specified in the signed CDSA.",
)

def get_absolute_url(self):
return reverse(
"cdsa:signed_agreements:data_affiliates:detail",
kwargs={"cc_id": self.signed_agreement.cc_id},
)

def clean(self):
super().clean()
if (
self.additional_limitations
and hasattr(self, "signed_agreement")
and not self.signed_agreement.is_primary
):
raise ValidationError(
"Additional limitations are only allowed for primary agreements."
)

def get_agreement_group(self):
return self.study

Expand Down Expand Up @@ -318,8 +333,9 @@ class CDSAWorkspace(
on_delete=models.PROTECT,
help_text="The study associated with data in this workspace.",
)
data_use_limitations = models.TextField(
help_text="""The full data use limitations for this workspace."""
additional_limitations = models.TextField(
help_text="""Additional data use limitations that cannot be captured by DUO.""",
blank=True,
)
acknowledgments = models.TextField(
help_text="Acknowledgments associated with data in this workspace."
Expand All @@ -337,3 +353,12 @@ class CDSAWorkspace(
class Meta:
verbose_name = " CDSA workspace"
verbose_name_plural = " CDSA workspaces"

def get_primary_cdsa(self):
"""Return the primary, valid CDSA associated with this workspace."""
cdsa = DataAffiliateAgreement.objects.get(
study=self.study,
signed_agreement__is_primary=True,
signed_agreement__status=SignedAgreement.StatusChoices.ACTIVE,
)
return cdsa
1 change: 0 additions & 1 deletion primed/cdsa/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ class Meta:
class CDSAWorkspaceFactory(DjangoModelFactory):

study = SubFactory(StudyFactory)
data_use_limitations = Faker("paragraph")
acknowledgments = Faker("paragraph")
requested_by = SubFactory(UserFactory)
data_use_permission = SubFactory(DataUsePermissionFactory)
Expand Down
53 changes: 34 additions & 19 deletions primed/cdsa/tests/test_forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Tests for the `cdsa` app."""

from anvil_consortium_manager.tests.factories import WorkspaceFactory
from django.core.exceptions import NON_FIELD_ERRORS
from django.test import TestCase

from primed.duo.models import DataUseModifier
Expand Down Expand Up @@ -416,6 +417,35 @@ def test_invalid_signed_agreement_wrong_type(self):
self.assertEqual(len(form.errors["signed_agreement"]), 1)
self.assertIn("expected type", form.errors["signed_agreement"][0])

def test_valid_primary_with_additional_limitations(self):
"""Form is valid with necessary input."""
signed_agreement = factories.SignedAgreementFactory.create(
type=models.SignedAgreement.DATA_AFFILIATE, is_primary=True
)
form_data = {
"signed_agreement": signed_agreement,
"study": self.study,
"additional_limitations": "test limitations",
}
form = self.form_class(data=form_data)
self.assertTrue(form.is_valid())

def test_invalid_component_with_additional_limitations(self):
"""Form is valid with necessary input."""
signed_agreement = factories.SignedAgreementFactory.create(
type=models.SignedAgreement.DATA_AFFILIATE, is_primary=False
)
form_data = {
"signed_agreement": signed_agreement,
"study": self.study,
"additional_limitations": "test limitations",
}
form = self.form_class(data=form_data)
self.assertFalse(form.is_valid())
self.assertIn(NON_FIELD_ERRORS, form.errors)
self.assertEqual(len(form.errors[NON_FIELD_ERRORS]), 1)
self.assertIn("only allowed for primary", form.errors[NON_FIELD_ERRORS][0])


class NonDataAffiliateAgreementFormTest(TestCase):
"""Tests for the NonDataAffiliateAgreementForm class."""
Expand Down Expand Up @@ -511,7 +541,6 @@ def test_valid(self):
"study": self.study,
"requested_by": self.requester,
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"gsr_restricted": False,
}
Expand All @@ -526,7 +555,6 @@ def test_valid_with_one_data_use_modifier(self):
"requested_by": self.requester,
"data_use_permission": self.duo_permission,
"data_use_modifier": DataUseModifier.objects.all(),
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"gsr_restricted": False,
}
Expand All @@ -541,7 +569,6 @@ def test_valid_with_two_data_use_modifiers(self):
"requested_by": self.requester,
"data_use_permission": self.duo_permission,
"data_use_modifier": DataUseModifier.objects.all(),
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"gsr_restricted": False,
}
Expand All @@ -555,7 +582,6 @@ def test_invalid_missing_workspace(self):
"study": self.study,
"requested_by": self.requester,
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"gsr_restricted": False,
}
Expand All @@ -573,7 +599,6 @@ def test_invalid_missing_study(self):
# "study": self.study,
"requested_by": self.requester,
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"gsr_restricted": False,
}
Expand All @@ -591,7 +616,6 @@ def test_invalid_missing_requested_by(self):
"study": self.study,
# "requested_by": self.requester,
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"gsr_restricted": False,
}
Expand All @@ -609,7 +633,6 @@ def test_invalid_missing_data_use_permission(self):
"study": self.study,
"requested_by": self.requester,
# "data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"gsr_restricted": False,
}
Expand All @@ -620,23 +643,20 @@ def test_invalid_missing_data_use_permission(self):
self.assertEqual(len(form.errors["data_use_permission"]), 1)
self.assertIn("required", form.errors["data_use_permission"][0])

def test_invalid_missing_data_use_limitations(self):
def test_valid_additional_limitations(self):
"""Form is invalid when missing data_use_limitations."""
form_data = {
"workspace": self.workspace,
"study": self.study,
"requested_by": self.requester,
"data_use_permission": self.duo_permission,
# "data_use_limitations": "test limitations",
"additional_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"gsr_restricted": False,
}
form = self.form_class(data=form_data)
self.assertFalse(form.is_valid())
self.assertEqual(len(form.errors), 1)
self.assertIn("data_use_limitations", form.errors)
self.assertEqual(len(form.errors["data_use_limitations"]), 1)
self.assertIn("required", form.errors["data_use_limitations"][0])
self.assertTrue(form.is_valid())
self.assertEqual(form.instance.additional_limitations, "test limitations")

def test_invalid_missing_acknowledgments(self):
"""Form is invalid when missing acknowledgments."""
Expand All @@ -645,7 +665,6 @@ def test_invalid_missing_acknowledgments(self):
"study": self.study,
"requested_by": self.requester,
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
# "acknowledgments": "test acknowledgmnts",
"gsr_restricted": False,
}
Expand All @@ -663,7 +682,6 @@ def test_invalid_missing_gsr_restricted(self):
"study": self.study,
"requested_by": self.requester,
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
# "gsr_restricted": False,
}
Expand All @@ -682,7 +700,6 @@ def test_invalid_duplicate_workspace(self):
"study": self.study,
"requested_by": self.requester,
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"gsr_restricted": False,
}
Expand All @@ -701,7 +718,6 @@ def test_valid_one_available_data(self):
"study": self.study,
"requested_by": self.requester,
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"available_data": [available_data],
"gsr_restricted": False,
Expand All @@ -717,7 +733,6 @@ def test_valid_two_available_data(self):
"study": self.study,
"requested_by": self.requester,
"data_use_permission": self.duo_permission,
"data_use_limitations": "test limitations",
"acknowledgments": "test acknowledgmnts",
"available_data": available_data,
"gsr_restricted": False,
Expand Down
Loading

0 comments on commit 6e5806f

Please sign in to comment.