From 7574ae877b24fd6eece988582b30f8c8366c930b Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Thu, 7 Dec 2023 12:32:04 -0800 Subject: [PATCH 01/60] Initial work to support drupal user and user data auditing - Add drupal node ID to study sites, admin view - Update user adapters so code can be shared for user data updates - Add management command to sync data from drupal - Add user audit file and test_audit coverage for file More to do for tests, audit results display --- .env.dist | 4 + config/settings/base.py | 3 + primed/primed_anvil/admin.py | 15 +- .../0006_studysite_drupal_node_id.py | 18 ++ primed/primed_anvil/models.py | 10 +- primed/users/adapters.py | 33 ++- primed/users/audit.py | 262 ++++++++++++++++++ primed/users/management/__init__.py | 0 primed/users/management/commands/__init__.py | 0 .../management/commands/sync-drupal-data.py | 25 ++ primed/users/tests/test_audit.py | 186 +++++++++++++ 11 files changed, 528 insertions(+), 28 deletions(-) create mode 100644 primed/primed_anvil/migrations/0006_studysite_drupal_node_id.py create mode 100644 primed/users/audit.py create mode 100644 primed/users/management/__init__.py create mode 100644 primed/users/management/commands/__init__.py create mode 100644 primed/users/management/commands/sync-drupal-data.py create mode 100644 primed/users/tests/test_audit.py diff --git a/.env.dist b/.env.dist index 518ad98a..28c2dafa 100644 --- a/.env.dist +++ b/.env.dist @@ -23,3 +23,7 @@ DJANGO_EMAIL_PORT= DJANGO_EMAIL_HOST_USER= DJANGO_EMAIL_HOST_PASSWORD= DJANGO_EMAIL_USE_TLS= + +# drupal api +DRUPAL_API_CLIENT_ID= +DRUPAL_API_CLIENT_SECRET= diff --git a/config/settings/base.py b/config/settings/base.py index eb563344..d7d54957 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -386,3 +386,6 @@ # Specify the subject for AnVIL account verification emails. ANVIL_ACCOUNT_LINK_EMAIL_SUBJECT = "Verify your AnVIL account email" ANVIL_ACCOUNT_VERIFY_NOTIFICATION_EMAIL = "primedconsortium@uw.edu" + +DRUPAL_API_CLIENT_ID = env("DRUPAL_API_CLIENT_ID", default="") +DRUPAL_API_CLIENT_SECRET = env("DRUPAL_API_CLIENT_SECRET", default="") diff --git a/primed/primed_anvil/admin.py b/primed/primed_anvil/admin.py index ce6d19ee..a4265dad 100644 --- a/primed/primed_anvil/admin.py +++ b/primed/primed_anvil/admin.py @@ -26,18 +26,9 @@ class StudyAdmin(SimpleHistoryAdmin): class StudySiteAdmin(admin.ModelAdmin): """Admin class for the `Study` model.""" - list_display = ( - "short_name", - "full_name", - ) - search_fields = ( - "short_name", - "full_name", - ) - sortable_by = ( - "short_name", - "full_name", - ) + list_display = ("short_name", "full_name", "drupal_node_id") + search_fields = ("short_name", "full_name", "drupal_node_id") + sortable_by = ("short_name", "full_name", "drupal_node_id") @admin.register(models.AvailableData) diff --git a/primed/primed_anvil/migrations/0006_studysite_drupal_node_id.py b/primed/primed_anvil/migrations/0006_studysite_drupal_node_id.py new file mode 100644 index 00000000..91ee9bd1 --- /dev/null +++ b/primed/primed_anvil/migrations/0006_studysite_drupal_node_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.19 on 2023-12-06 16:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('primed_anvil', '0005_availabledata'), + ] + + operations = [ + migrations.AddField( + model_name='studysite', + name='drupal_node_id', + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/primed/primed_anvil/models.py b/primed/primed_anvil/models.py index 528d85ca..2277c9b8 100644 --- a/primed/primed_anvil/models.py +++ b/primed/primed_anvil/models.py @@ -14,6 +14,7 @@ class Study(TimeStampedModel, models.Model): full_name = models.CharField( max_length=255, help_text="The full name for this Study." ) + history = HistoricalRecords() class Meta: @@ -32,13 +33,16 @@ def get_absolute_url(self): class StudySite(TimeStampedModel, models.Model): - """A model to track Research Centers.""" + """A model to track Study Sites.""" short_name = models.CharField(max_length=15, unique=True) - """The short name of the Research Center.""" + """The short name of the Study Sites.""" full_name = models.CharField(max_length=255) - """The full name of the Research Center.""" + """The full name of the Study Sites.""" + + drupal_node_id = models.IntegerField(blank=True, null=True) + """Reference node ID for entity in drupal""" def __str__(self): """String method. diff --git a/primed/users/adapters.py b/primed/users/adapters.py index b0f6d6a3..24e6d91e 100644 --- a/primed/users/adapters.py +++ b/primed/users/adapters.py @@ -23,7 +23,7 @@ class SocialAccountAdapter(DefaultSocialAccountAdapter): def is_open_for_signup(self, request: HttpRequest, sociallogin: Any): return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) - def update_user_info(self, user, extra_data: Dict): + def update_user_info(self, user, extra_data: Dict, apply_update=True): drupal_username = extra_data.get("preferred_username") drupal_email = extra_data.get("email") first_name = extra_data.get("first_name") @@ -52,13 +52,15 @@ def update_user_info(self, user, extra_data: Dict): user.email = drupal_email user_changed = True - if user_changed is True: + if user_changed is True and apply_update is True: user.save() + return user_changed - def update_user_study_sites(self, user, extra_data: Dict): + def update_user_study_sites(self, user, extra_data: Dict, apply_update=True): # Get list of research centers in domain table research_center_or_site = extra_data.get("study_site_or_center") + user_sites_updated = False if research_center_or_site: if not isinstance(research_center_or_site, list): raise ImproperlyConfigured( @@ -79,19 +81,24 @@ def update_user_study_sites(self, user, extra_data: Dict): continue else: if not user.study_sites.filter(pk=rc.pk): - user.study_sites.add(rc) - logger.info( - f"[SocialAccountAdatpter:update_user_study_sites] adding user " - f"study_sites user: {user} rc: {rc}" - ) + user_sites_updated = True + if apply_update is True: + user.study_sites.add(rc) + logger.info( + f"[SocialAccountAdatpter:update_user_study_sites] adding user " + f"study_sites user: {user} rc: {rc}" + ) for existing_rc in user.study_sites.all(): if existing_rc.short_name not in research_center_or_site: - user.study_sites.remove(existing_rc) - logger.info( - "[SocialAccountAdatpter:update_user_study_sites] " - f"removing study_site {existing_rc} for user {user}" - ) + user_sites_updated = True + if apply_update: + user.study_sites.remove(existing_rc) + logger.info( + "[SocialAccountAdatpter:update_user_study_sites] " + f"removing study_site {existing_rc} for user {user}" + ) + return user_sites_updated def update_user_groups(self, user, extra_data: Dict): managed_scope_status = extra_data.get("managed_scope_status") diff --git a/primed/users/audit.py b/primed/users/audit.py new file mode 100644 index 00000000..6198313c --- /dev/null +++ b/primed/users/audit.py @@ -0,0 +1,262 @@ +import jsonapi_requests +from allauth.socialaccount.models import SocialAccount +from django.conf import settings +from django.contrib.auth import get_user_model +from django.core.exceptions import ObjectDoesNotExist +from oauthlib.oauth2 import BackendApplicationClient +from requests_oauthlib import OAuth2, OAuth2Session + +from primed.drupal_oauth_provider.provider import CustomProvider +from primed.primed_anvil.models import StudySite +from primed.users.adapters import SocialAccountAdapter + + +class UserDataAuditResults: + def __init__(self, results): + self.results = results + + ISSUE_RESULT_TYPE = "issue" + NEW_RESULT_TYPE = "new" + UPDATE_RESULT_TYPE = "update" + + def encountered_issues(self): + for row in self.results: + if row["result_type"] == self.ISSUE_RESULT_TYPE: + return True + return False + + def count_new_rows(self): + new_count = 0 + for row in self.results: + if row["result_type"] == self.NEW_RESULT_TYPE: + new_count += 1 + return new_count + + def count_update_rows(self): + update_count = 0 + for row in self.results: + if row["result_type"] == self.UPDATE_RESULT_TYPE: + update_count += 1 + return update_count + + +def get_drupal_json_api(): + + json_api_client_id = settings.DRUPAL_API_CLIENT_ID + json_api_client_secret = settings.DRUPAL_API_CLIENT_SECRET + + token_url = f"{settings.DRUPAL_SITE_URL}/oauth/token" + client = BackendApplicationClient(client_id=json_api_client_id) + oauth = OAuth2Session(client=client) + token = oauth.fetch_token( + token_url=token_url, + client_id=json_api_client_id, + client_secret=json_api_client_secret, + ) + + drupal_api = jsonapi_requests.Api.config( + { + "API_ROOT": f"{settings.DRUPAL_SITE_URL}/jsonapi", + "AUTH": OAuth2(client=client, client_id=json_api_client_id, token=token), + "VALIDATE_SSL": True, + } + ) + return drupal_api + + +def drupal_data_study_site_audit(should_update=False): + json_api = get_drupal_json_api() + study_sites = get_study_sites(json_api) + status = audit_drupal_study_sites( + study_sites=study_sites, should_update=should_update + ) + # audit_drupal_users(study_sites=study_sites, should_update=should_update) + return status + + +def drupal_data_user_audit(should_update=False): + json_api = get_drupal_json_api() + study_sites = get_study_sites(json_api=json_api) + status = audit_drupal_users( + study_sites=study_sites, should_update=should_update, json_api=json_api + ) + return status + + +def audit_drupal_users(study_sites, json_api, should_update=False): + + issues = [] + + user_endpoint_url = "user/user" + drupal_uids = set() + + drupal_adapter = SocialAccountAdapter() + max_users = 3 + user_count = 0 + while user_endpoint_url is not None: + print(f"GETTING {user_endpoint_url}") + users_endpoint = json_api.endpoint(user_endpoint_url) + users_endpoint_response = users_endpoint.get() + + # If there are more, there will be a 'next' link + next_user_endpoint = users_endpoint_response.content.links.get("next") + if next_user_endpoint: + user_endpoint_url = next_user_endpoint["href"] + else: + user_endpoint_url = None + + for user in users_endpoint_response.data: + drupal_uid = user.attributes.get("drupal_internal__uid") + drupal_username = user.attributes.get("name") + drupal_email = user.attributes.get("mail") + drupal_firstname = user.attributes.get("field_given_first_name_s_") + drupal_lastname = user.attributes.get("field_examples_family_last_name_") + drupal_study_sites_rel = user.relationships.get( + "field_study_site_or_center" + ) + drupal_user_study_site_shortnames = [] + if drupal_study_sites_rel: + for dss in drupal_study_sites_rel.data: + study_site_uuid = dss.id + study_site_info = study_sites[study_site_uuid] + + drupal_user_study_site_shortnames.append( + study_site_info["short_name"] + ) + else: + print(f"No study sites for user {user.attributes['display_name']}") + + # no uid is blocked or anonymous + if not drupal_uid: + print( + f"Skipping blocked or anonymous user {user.attributes['display_name']} {user}" + ) + # FIXME DEACTIVATE if exists in our system + continue + + try: + sa = SocialAccount.objects.get( + uid=user.attributes["drupal_internal__uid"], + provider=CustomProvider.id, + ) + except ObjectDoesNotExist: + print( + f"NO SA found for user {user.attributes['drupal_internal__uid']} {user}" + ) + drupal_user = get_user_model()() + drupal_user.username = drupal_username + drupal_user.email = drupal_email + drupal_user.save() + sa = SocialAccount.objects.create( + user=drupal_user, + uid=user.attributes["drupal_internal__uid"], + provider=CustomProvider.id, + ) + else: + print(f"Found {sa} for {user}") + user_changed = drupal_adapter.update_user_info( + user=sa.user, + extra_data={ + "preferred_username": drupal_username, + "first_name": drupal_firstname, + "last_name": drupal_lastname, + "email": drupal_email, + }, + apply_update=should_update, + ) + if user_changed: + pass + user_sites_changed = drupal_adapter.update_user_study_sites( + user=sa.user, + extra_data={ + "study_site_or_center": drupal_user_study_site_shortnames + }, + ) + if user_sites_changed: + pass + + drupal_uids.add(sa.user.id) + user_count += 1 + if user_count > max_users: + break + if user_count > max_users: + break + + # find active drupal users that we did not account before + # unaudited_drupal_accounts = SocialAccount.objects.filter( + # provider=CustomProvider.id, user__is_active=True + # ).exclude(uid__in=drupal_uids) + return issues + + +def get_study_sites(json_api): + study_sites_endpoint = json_api.endpoint("node/study_site_or_center") + study_sites_response = study_sites_endpoint.get() + study_sites_info = dict() + + for ss in study_sites_response.data: + short_name = ss.attributes["title"] + full_name = ss.attributes["field_long_name"] + node_id = ss.attributes["drupal_internal__nid"] + + study_sites_info[ss.id] = { + "node_id": node_id, + "short_name": short_name, + "full_name": full_name, + } + return study_sites_info + + +def audit_drupal_study_sites(study_sites, should_update=False): + + valid_nodes = set() + results = [] + + for study_site_info in study_sites.values(): + + short_name = study_site_info["short_name"] + full_name = study_site_info["full_name"] + node_id = study_site_info["node_id"] + valid_nodes.add(node_id) + + try: + study_site = StudySite.objects.get(drupal_node_id=node_id) + except ObjectDoesNotExist: + if should_update is True: + study_site = StudySite.objects.create( + drupal_node_id=node_id, short_name=short_name, full_name=full_name + ) + results.append( + { + "result_type": "new", + "data_type": "study_site", + "data": study_site_info, + } + ) + else: + if study_site.full_name != full_name or study_site.short_name != short_name: + study_site.full_name = full_name + study_site.short_name = short_name + if should_update is True: + study_site.save() + results.append( + { + "result_type": "update", + "data_type": "study_site", + "data": study_site_info, + } + ) + + invalid_study_sites = StudySite.objects.exclude(drupal_node_id__in=valid_nodes) + + for iss in invalid_study_sites: + results.append( + { + "result_type": "issue", + "issue_type": "invalid_site", + "data_type": "study_site", + "data": iss, + } + ) + + return UserDataAuditResults(results) diff --git a/primed/users/management/__init__.py b/primed/users/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/primed/users/management/commands/__init__.py b/primed/users/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/primed/users/management/commands/sync-drupal-data.py b/primed/users/management/commands/sync-drupal-data.py new file mode 100644 index 00000000..af25f5a5 --- /dev/null +++ b/primed/users/management/commands/sync-drupal-data.py @@ -0,0 +1,25 @@ +import logging + +from django.core.management.base import BaseCommand + +from primed.users.audit import drupal_data_user_audit + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = "Sync drupal user and domain data" + + def add_arguments(self, parser): + parser.add_argument( + "--update", + action="store_true", + dest="update", + default=False, + ) + + def handle(self, *args, **options): + should_update = options.get("update") + + status = drupal_data_user_audit(should_update=should_update) + print(f"Issues {status}") diff --git a/primed/users/tests/test_audit.py b/primed/users/tests/test_audit.py new file mode 100644 index 00000000..b8a8a4dd --- /dev/null +++ b/primed/users/tests/test_audit.py @@ -0,0 +1,186 @@ +import json +import time + +import responses +from django.conf import settings +from django.test import TestCase +from marshmallow_jsonapi import Schema, fields + +from primed.users.audit import ( + audit_drupal_study_sites, + get_drupal_json_api, + get_study_sites, +) +from primed.users.models import StudySite + + +class StudySiteSchema(Schema): + id = fields.Str(dump_only=True) + title = fields.Str() + field_long_name = fields.Str() + drupal_internal__nid = fields.Str() + # document_meta = fields.DocumentMeta() + + class Meta: + type_ = "node--study_site_or_center" + + +class UserSchema(Schema): + drupal_internal__uid = fields.Str() + name = fields.Str() + mail = fields.Str() + field_given_first_name_s_ = fields.Str() + field_examples_family_last_name_ = fields.Str() + field_study_site_or_center = fields.Relationship(schema="StudySiteSchema") + + class Meta: + type_ = "users" + + +# def debug_requests_on(): +# """Switches on logging of the requests module.""" +# HTTPConnection.debuglevel = 1 + +# logging.basicConfig() +# logging.getLogger().setLevel(logging.DEBUG) +# requests_log = logging.getLogger("requests.packages.urllib3") +# requests_log.setLevel(logging.DEBUG) +# requests_log.propagate = True + + +TEST_STUDY_SITE_DATA = [ + { + "id": "1", + "drupal_internal__nid": "1", + "title": "SS1", + "field_long_name": "S S 1", + # "document_meta": {"page": {"offset": 10}}, + }, + { + "id": "2", + "drupal_internal__nid": "2", + "title": "SS2", + "field_long_name": "S S 2", + # "document_meta": {"page": {"offset": 10}}, + }, +] + + +class TestStudySiteAudit(TestCase): + """General tests of the user audit""" + + def setUp(self): + # debug_requests_on() + super().setUp() + fake_time = time.time() + self.token = { + "token_type": "Bearer", + "access_token": "asdfoiw37850234lkjsdfsdfTEST", + "refresh_token": "sldvafkjw34509s8dfsdfTEST", + "expires_in": 3600, + "expires_at": fake_time + 3600, + } + + def add_fake_study_sites_response(self): + url_path = f"{settings.DRUPAL_SITE_URL}/jsonapi/node/study_site_or_center/" + responses.get( + url=url_path, + body=json.dumps(StudySiteSchema(many=True).dump(TEST_STUDY_SITE_DATA)), + ) + + def get_fake_json_api(self): + token_url = f"{settings.DRUPAL_SITE_URL}/oauth/token" + responses.post(url=token_url, body=json.dumps(self.token)) + return get_drupal_json_api() + + @responses.activate + def test_get_json_api(self): + json_api = self.get_fake_json_api() + # print(f"JSONAPI: {json_api.requests.config.AUTH._client.token}") + assert ( + json_api.requests.config.AUTH._client.token["access_token"] + == self.token["access_token"] + ) + + @responses.activate + def test_get_study_sites(self): + json_api = self.get_fake_json_api() + self.add_fake_study_sites_response() + study_sites = get_study_sites(json_api=json_api) + + for test_study_site in TEST_STUDY_SITE_DATA: + + assert ( + test_study_site["field_long_name"] + == study_sites[test_study_site["drupal_internal__nid"]]["full_name"] + ) + assert ( + test_study_site["title"] + == study_sites[test_study_site["drupal_internal__nid"]]["short_name"] + ) + assert ( + test_study_site["drupal_internal__nid"] + == study_sites[test_study_site["drupal_internal__nid"]]["node_id"] + ) + + @responses.activate + def test_audit_study_sites_no_update(self): + json_api = self.get_fake_json_api() + self.add_fake_study_sites_response() + study_sites = get_study_sites(json_api=json_api) + audit_results = audit_drupal_study_sites( + study_sites=study_sites, should_update=False + ) + assert audit_results.encountered_issues() is False + assert StudySite.objects.all().count() == 0 + + @responses.activate + def test_audit_study_sites_with_new_sites(self): + json_api = self.get_fake_json_api() + self.add_fake_study_sites_response() + study_sites = get_study_sites(json_api=json_api) + audit_results = audit_drupal_study_sites( + study_sites=study_sites, should_update=True + ) + assert audit_results.encountered_issues() is False + assert audit_results.count_new_rows() == 2 + assert StudySite.objects.all().count() == 2 + assert StudySite.objects.filter( + short_name=TEST_STUDY_SITE_DATA[0]["title"] + ).exists() + + @responses.activate + def test_audit_study_sites_with_site_update(self): + StudySite.objects.create( + drupal_node_id=TEST_STUDY_SITE_DATA[0]["drupal_internal__nid"], + short_name=TEST_STUDY_SITE_DATA[0]["title"], + full_name="WrongTitle", + ) + json_api = self.get_fake_json_api() + self.add_fake_study_sites_response() + study_sites = get_study_sites(json_api=json_api) + audit_results = audit_drupal_study_sites( + study_sites=study_sites, should_update=True + ) + assert audit_results.encountered_issues() is False + assert audit_results.count_new_rows() == 1 + assert audit_results.count_update_rows() == 1 + assert StudySite.objects.all().count() == 2 + first_test_ss = StudySite.objects.get( + short_name=TEST_STUDY_SITE_DATA[0]["title"] + ) + # did we update the long name + assert first_test_ss.full_name == TEST_STUDY_SITE_DATA[0]["field_long_name"] + + @responses.activate + def test_audit_study_sites_with_extra_site(self): + StudySite.objects.create( + drupal_node_id=99, short_name="ExtraSite", full_name="ExtraSiteLong" + ) + json_api = self.get_fake_json_api() + self.add_fake_study_sites_response() + study_sites = get_study_sites(json_api=json_api) + audit_results = audit_drupal_study_sites( + study_sites=study_sites, should_update=True + ) + assert audit_results.encountered_issues() is True From ff2fd462b6d51642a803fb6a1fad545703a55a79 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Fri, 8 Dec 2023 12:02:08 -0800 Subject: [PATCH 02/60] Update dependencies needed for json api interactions and mocking --- requirements/requirements.in | 3 +++ requirements/requirements.txt | 8 +++++++- requirements/test-requirements.in | 3 +++ requirements/test-requirements.txt | 5 +++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/requirements/requirements.in b/requirements/requirements.in index 26cbc527..f1d3a324 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -44,6 +44,9 @@ requests # For json schema validation. jsonschema +# For interacting with drupal json api +jsonapi-requests + # For tree structures django-tree-queries diff --git a/requirements/requirements.txt b/requirements/requirements.txt index eadb904b..53e5d183 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -100,10 +100,13 @@ idna==3.3 # via requests importlib-metadata==7.0.0 # via build +importlib-resources==6.1.1 importlib-resources==6.1.1 # via # jsonschema # jsonschema-specifications +jsonapi-requests==0.7.0 + # via -r requirements/requirements.in jsonschema==4.21.1 # via -r requirements/requirements.in jsonschema-specifications==2023.12.1 @@ -174,6 +177,7 @@ requests==2.31.0 # -r requirements/requirements.in # django-allauth # django-anvil-consortium-manager + # jsonapi-requests # requests-oauthlib requests-oauthlib==1.3.1 # via django-allauth @@ -194,7 +198,9 @@ sqlparse==0.4.4 tablib==3.5.0 # via -r requirements/requirements.in tenacity==8.2.1 - # via plotly + # via + # jsonapi-requests + # plotly tomli==2.0.1 # via # build diff --git a/requirements/test-requirements.in b/requirements/test-requirements.in index df760727..0c212980 100644 --- a/requirements/test-requirements.in +++ b/requirements/test-requirements.in @@ -18,3 +18,6 @@ freezegun # https://github.com/spulec/freezegun django-coverage-plugin # https://github.com/nedbat/django_coverage_plugin # Test coverage. coverage +# Mock json api data +marshmallow-jsonapi + diff --git a/requirements/test-requirements.txt b/requirements/test-requirements.txt index 368064f8..f6ac1f1b 100644 --- a/requirements/test-requirements.txt +++ b/requirements/test-requirements.txt @@ -34,9 +34,14 @@ idna==3.3 # requests iniconfig==1.1.1 # via pytest +marshmallow==3.20.1 + # via marshmallow-jsonapi +marshmallow-jsonapi==0.24.0 + # via -r requirements/test-requirements.in packaging==21.3 # via # -c requirements/requirements.txt + # marshmallow # pytest # pytest-sugar pluggy==1.3.0 From d88a6763d361de30f93f817b27ec69205ce13923 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Fri, 12 Jan 2024 10:25:35 -0800 Subject: [PATCH 03/60] Add jsonapi relative path to settings and env --- .env.dist | 1 + config/settings/base.py | 1 + primed/users/tests/test_audit.py | 42 ++++++++++++++++++++++++++++++- requirements/test-requirements.in | 1 - 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/.env.dist b/.env.dist index 28c2dafa..b9e98292 100644 --- a/.env.dist +++ b/.env.dist @@ -27,3 +27,4 @@ DJANGO_EMAIL_USE_TLS= # drupal api DRUPAL_API_CLIENT_ID= DRUPAL_API_CLIENT_SECRET= +DRUPAL_API_REL_PATH= diff --git a/config/settings/base.py b/config/settings/base.py index d7d54957..561a4913 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -389,3 +389,4 @@ DRUPAL_API_CLIENT_ID = env("DRUPAL_API_CLIENT_ID", default="") DRUPAL_API_CLIENT_SECRET = env("DRUPAL_API_CLIENT_SECRET", default="") +DRUPAL_API_REL_PATH = env("DRUPAL_API_REL_PATH", default="") diff --git a/primed/users/tests/test_audit.py b/primed/users/tests/test_audit.py index b8a8a4dd..69f4fd34 100644 --- a/primed/users/tests/test_audit.py +++ b/primed/users/tests/test_audit.py @@ -10,6 +10,8 @@ audit_drupal_study_sites, get_drupal_json_api, get_study_sites, + drupal_data_study_site_audit, + drupal_data_user_audit, ) from primed.users.models import StudySite @@ -26,6 +28,8 @@ class Meta: class UserSchema(Schema): + id = fields.Str(dump_only=True) + display_name = fields.Str() drupal_internal__uid = fields.Str() name = fields.Str() mail = fields.Str() @@ -65,6 +69,19 @@ class Meta: }, ] +TEST_USER_DATA = [ + { + "id": "usr1", + "display_name": "dnusr1", + "drupal_internal__uid": "usr1", + "name": "testuser1", + "mail": "testuser1@test.com", + "field_given_first_name_s_": "test1", + "field_examples_family_last_name_": "user1", + "field_study_site_or_center": [TEST_STUDY_SITE_DATA[0]], + } +] + class TestStudySiteAudit(TestCase): """General tests of the user audit""" @@ -88,9 +105,19 @@ def add_fake_study_sites_response(self): body=json.dumps(StudySiteSchema(many=True).dump(TEST_STUDY_SITE_DATA)), ) - def get_fake_json_api(self): + def add_fake_users_response(self): + url_path = f"{settings.DRUPAL_SITE_URL}/jsonapi/user/user/" + responses.get( + url=url_path, + body=json.dumps(UserSchema(many=True).dump(TEST_USER_DATA)), + ) + + def add_fake_token_response(self): token_url = f"{settings.DRUPAL_SITE_URL}/oauth/token" responses.post(url=token_url, body=json.dumps(self.token)) + + def get_fake_json_api(self): + self.add_fake_token_response() return get_drupal_json_api() @responses.activate @@ -134,6 +161,12 @@ def test_audit_study_sites_no_update(self): assert audit_results.encountered_issues() is False assert StudySite.objects.all().count() == 0 + @responses.activate + def test_full_site_audit(self): + self.add_fake_token_response() + self.add_fake_study_sites_response() + results = drupal_data_study_site_audit() + @responses.activate def test_audit_study_sites_with_new_sites(self): json_api = self.get_fake_json_api() @@ -184,3 +217,10 @@ def test_audit_study_sites_with_extra_site(self): study_sites=study_sites, should_update=True ) assert audit_results.encountered_issues() is True + + @responses.activate + def test_full_user_audit(self): + self.add_fake_token_response() + self.add_fake_study_sites_response() + self.add_fake_users_response() + results = drupal_data_user_audit() diff --git a/requirements/test-requirements.in b/requirements/test-requirements.in index 0c212980..c5e95951 100644 --- a/requirements/test-requirements.in +++ b/requirements/test-requirements.in @@ -20,4 +20,3 @@ django-coverage-plugin # https://github.com/nedbat/django_coverage_plugin coverage # Mock json api data marshmallow-jsonapi - From c82c5df786233c7554386f70c0cfd6b2d5ef8cb7 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Fri, 12 Jan 2024 10:26:29 -0800 Subject: [PATCH 04/60] Iterative refining of the audit results and user data audit tests --- primed/users/audit.py | 231 +++++++++++------- .../management/commands/sync-drupal-data.py | 9 +- primed/users/tests/test_audit.py | 34 ++- 3 files changed, 166 insertions(+), 108 deletions(-) diff --git a/primed/users/audit.py b/primed/users/audit.py index 6198313c..262630fc 100644 --- a/primed/users/audit.py +++ b/primed/users/audit.py @@ -11,33 +11,101 @@ from primed.users.adapters import SocialAccountAdapter -class UserDataAuditResults: - def __init__(self, results): - self.results = results +class AuditResults: + def __init__(self): + self.results = [] + self.data_type = None + + # Data from api was not able to be handled + RESULT_TYPE_ISSUE = "issue" + # A new record was created during audit + RESULT_TYPE_NEW = "new" + # An existing record was updated + RESULT_TYPE_UPDATE = "update" + # A record was removed or deactivated + RESULT_TYPE_REMOVAL = "removed" + + def add_new(self, data): + self.results.append( + { + "data_type": self.data_type, + "result_type": self.RESULT_TYPE_NEW, + "data": data, + } + ) + + def add_update(self, data): + self.results.append( + { + "data_type": self.data_type, + "result_type": self.RESULT_TYPE_UPDATE, + "data": data, + } + ) - ISSUE_RESULT_TYPE = "issue" - NEW_RESULT_TYPE = "new" - UPDATE_RESULT_TYPE = "update" + def add_issue(self, data): + self.results.append( + { + "data_type": self.data_type, + "result_type": self.RESULT_TYPE_ISSUE, + "data": data, + } + ) - def encountered_issues(self): + def add_removal(self, data): + self.results.append( + { + "data_type": self.data_type, + "result_type": self.RESULT_TYPE_REMOVAL, + "data": data, + } + ) + + def rows_by_result_type(self, result_type): + found = [] for row in self.results: - if row["result_type"] == self.ISSUE_RESULT_TYPE: - return True - return False + if row["result_type"] == result_type: + found.append(row) + return found + + def row_count_by_result_type(self, result_type): + return len(self.rows_by_result_type(result_type)) def count_new_rows(self): - new_count = 0 - for row in self.results: - if row["result_type"] == self.NEW_RESULT_TYPE: - new_count += 1 - return new_count + return self.row_count_by_result_type(self.RESULT_TYPE_NEW) def count_update_rows(self): - update_count = 0 - for row in self.results: - if row["result_type"] == self.UPDATE_RESULT_TYPE: - update_count += 1 - return update_count + return self.row_count_by_result_type(self.RESULT_TYPE_UPDATE) + + def count_removal_rows(self): + return self.row_count_by_result_type(self.RESULT_TYPE_REMOVAL) + + def count_issue_rows(self): + return self.row_count_by_result_type(self.RESULT_TYPE_ISSUE) + + def encountered_issues(self): + return self.count_issue_rows() > 0 + + def __str__(self) -> str: + return ( + f"Audit for {self.data_type} " + f"Issues: {self.count_issue_rows()} " + f"New: {self.count_new_rows()} " + f"Updates: {self.count_update_rows()} " + f"Removals: {self.count_removal_rows()}" + ) + + +class UserAuditResults(AuditResults): + def __init__(self): + super().__init__() + self.data_type = "user" + + +class SiteAuditResults(AuditResults): + def __init__(self): + super().__init__() + self.data_type = "site" def get_drupal_json_api(): @@ -48,6 +116,8 @@ def get_drupal_json_api(): token_url = f"{settings.DRUPAL_SITE_URL}/oauth/token" client = BackendApplicationClient(client_id=json_api_client_id) oauth = OAuth2Session(client=client) + api_root = f"{settings.DRUPAL_SITE_URL}/{settings.DRUPAL_API_REL_PATH}" + token = oauth.fetch_token( token_url=token_url, client_id=json_api_client_id, @@ -56,7 +126,7 @@ def get_drupal_json_api(): drupal_api = jsonapi_requests.Api.config( { - "API_ROOT": f"{settings.DRUPAL_SITE_URL}/jsonapi", + "API_ROOT": api_root, "AUTH": OAuth2(client=client, client_id=json_api_client_id, token=token), "VALIDATE_SSL": True, } @@ -64,37 +134,37 @@ def get_drupal_json_api(): return drupal_api -def drupal_data_study_site_audit(should_update=False): +def drupal_data_study_site_audit(apply_changes=False): json_api = get_drupal_json_api() study_sites = get_study_sites(json_api) status = audit_drupal_study_sites( - study_sites=study_sites, should_update=should_update + study_sites=study_sites, apply_changes=apply_changes ) - # audit_drupal_users(study_sites=study_sites, should_update=should_update) + return status -def drupal_data_user_audit(should_update=False): +def drupal_data_user_audit(apply_changes=False): json_api = get_drupal_json_api() study_sites = get_study_sites(json_api=json_api) status = audit_drupal_users( - study_sites=study_sites, should_update=should_update, json_api=json_api + study_sites=study_sites, apply_changes=apply_changes, json_api=json_api ) return status -def audit_drupal_users(study_sites, json_api, should_update=False): +def audit_drupal_users(study_sites, json_api, apply_changes=False): - issues = [] + audit_results = UserAuditResults() user_endpoint_url = "user/user" drupal_uids = set() drupal_adapter = SocialAccountAdapter() - max_users = 3 + user_count = 0 while user_endpoint_url is not None: - print(f"GETTING {user_endpoint_url}") + users_endpoint = json_api.endpoint(user_endpoint_url) users_endpoint_response = users_endpoint.get() @@ -111,6 +181,9 @@ def audit_drupal_users(study_sites, json_api, should_update=False): drupal_email = user.attributes.get("mail") drupal_firstname = user.attributes.get("field_given_first_name_s_") drupal_lastname = user.attributes.get("field_examples_family_last_name_") + drupal_full_name = " ".join( + part for part in (drupal_firstname, drupal_lastname) if part + ) drupal_study_sites_rel = user.relationships.get( "field_study_site_or_center" ) @@ -123,15 +196,10 @@ def audit_drupal_users(study_sites, json_api, should_update=False): drupal_user_study_site_shortnames.append( study_site_info["short_name"] ) - else: - print(f"No study sites for user {user.attributes['display_name']}") - + is_new_user = False # no uid is blocked or anonymous if not drupal_uid: - print( - f"Skipping blocked or anonymous user {user.attributes['display_name']} {user}" - ) - # FIXME DEACTIVATE if exists in our system + # FIXME - deactivate if not anonymous and present locally continue try: @@ -140,53 +208,45 @@ def audit_drupal_users(study_sites, json_api, should_update=False): provider=CustomProvider.id, ) except ObjectDoesNotExist: - print( - f"NO SA found for user {user.attributes['drupal_internal__uid']} {user}" - ) drupal_user = get_user_model()() drupal_user.username = drupal_username + drupal_user.name = drupal_full_name drupal_user.email = drupal_email drupal_user.save() + is_new_user = True sa = SocialAccount.objects.create( user=drupal_user, uid=user.attributes["drupal_internal__uid"], provider=CustomProvider.id, ) - else: - print(f"Found {sa} for {user}") - user_changed = drupal_adapter.update_user_info( - user=sa.user, - extra_data={ - "preferred_username": drupal_username, - "first_name": drupal_firstname, - "last_name": drupal_lastname, - "email": drupal_email, - }, - apply_update=should_update, - ) - if user_changed: - pass - user_sites_changed = drupal_adapter.update_user_study_sites( - user=sa.user, - extra_data={ - "study_site_or_center": drupal_user_study_site_shortnames - }, - ) - if user_sites_changed: - pass + audit_results.add_new(data=user) + + user_changed = drupal_adapter.update_user_info( + user=sa.user, + extra_data={ + "preferred_username": drupal_username, + "first_name": drupal_firstname, + "last_name": drupal_lastname, + "email": drupal_email, + }, + apply_update=apply_changes, + ) + + user_sites_changed = drupal_adapter.update_user_study_sites( + user=sa.user, + extra_data={"study_site_or_center": drupal_user_study_site_shortnames}, + ) + if user_changed or user_sites_changed and not is_new_user: + audit_results.add_update(data=user) - drupal_uids.add(sa.user.id) + drupal_uids.add(sa.user.id) user_count += 1 - if user_count > max_users: - break - if user_count > max_users: - break # find active drupal users that we did not account before # unaudited_drupal_accounts = SocialAccount.objects.filter( # provider=CustomProvider.id, user__is_active=True # ).exclude(uid__in=drupal_uids) - return issues + return audit_results def get_study_sites(json_api): @@ -207,10 +267,10 @@ def get_study_sites(json_api): return study_sites_info -def audit_drupal_study_sites(study_sites, should_update=False): +def audit_drupal_study_sites(study_sites, apply_changes=False): valid_nodes = set() - results = [] + audit_results = SiteAuditResults() for study_site_info in study_sites.values(): @@ -222,41 +282,22 @@ def audit_drupal_study_sites(study_sites, should_update=False): try: study_site = StudySite.objects.get(drupal_node_id=node_id) except ObjectDoesNotExist: - if should_update is True: + if apply_changes is True: study_site = StudySite.objects.create( drupal_node_id=node_id, short_name=short_name, full_name=full_name ) - results.append( - { - "result_type": "new", - "data_type": "study_site", - "data": study_site_info, - } - ) + audit_results.add_new(data=study_site_info) else: if study_site.full_name != full_name or study_site.short_name != short_name: study_site.full_name = full_name study_site.short_name = short_name - if should_update is True: + if apply_changes is True: study_site.save() - results.append( - { - "result_type": "update", - "data_type": "study_site", - "data": study_site_info, - } - ) + audit_results.add_update(data=study_site_info) invalid_study_sites = StudySite.objects.exclude(drupal_node_id__in=valid_nodes) for iss in invalid_study_sites: - results.append( - { - "result_type": "issue", - "issue_type": "invalid_site", - "data_type": "study_site", - "data": iss, - } - ) + audit_results.add_issue(data=iss) - return UserDataAuditResults(results) + return audit_results diff --git a/primed/users/management/commands/sync-drupal-data.py b/primed/users/management/commands/sync-drupal-data.py index af25f5a5..b409775d 100644 --- a/primed/users/management/commands/sync-drupal-data.py +++ b/primed/users/management/commands/sync-drupal-data.py @@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand -from primed.users.audit import drupal_data_user_audit +from primed.users.audit import drupal_data_study_site_audit, drupal_data_user_audit logger = logging.getLogger(__name__) @@ -21,5 +21,8 @@ def add_arguments(self, parser): def handle(self, *args, **options): should_update = options.get("update") - status = drupal_data_user_audit(should_update=should_update) - print(f"Issues {status}") + user_audit_results = drupal_data_user_audit(apply_changes=should_update) + print(f"User Audit Results {user_audit_results}") + + site_audit_results = drupal_data_study_site_audit(apply_changes=should_update) + print(f"Site Audit Results {site_audit_results}") diff --git a/primed/users/tests/test_audit.py b/primed/users/tests/test_audit.py index 69f4fd34..a5146b33 100644 --- a/primed/users/tests/test_audit.py +++ b/primed/users/tests/test_audit.py @@ -3,15 +3,16 @@ import responses from django.conf import settings +from django.contrib.auth import get_user_model from django.test import TestCase from marshmallow_jsonapi import Schema, fields from primed.users.audit import ( audit_drupal_study_sites, - get_drupal_json_api, - get_study_sites, drupal_data_study_site_audit, drupal_data_user_audit, + get_drupal_json_api, + get_study_sites, ) from primed.users.models import StudySite @@ -78,12 +79,13 @@ class Meta: "mail": "testuser1@test.com", "field_given_first_name_s_": "test1", "field_examples_family_last_name_": "user1", + "full_name": "test1 user1", "field_study_site_or_center": [TEST_STUDY_SITE_DATA[0]], } ] -class TestStudySiteAudit(TestCase): +class TestUserDataAudit(TestCase): """General tests of the user audit""" def setUp(self): @@ -99,14 +101,16 @@ def setUp(self): } def add_fake_study_sites_response(self): - url_path = f"{settings.DRUPAL_SITE_URL}/jsonapi/node/study_site_or_center/" + url_path = f"{settings.DRUPAL_SITE_URL}/{settings.DRUPAL_API_REL_PATH}/node/study_site_or_center/" responses.get( url=url_path, body=json.dumps(StudySiteSchema(many=True).dump(TEST_STUDY_SITE_DATA)), ) def add_fake_users_response(self): - url_path = f"{settings.DRUPAL_SITE_URL}/jsonapi/user/user/" + url_path = ( + f"{settings.DRUPAL_SITE_URL}/{settings.DRUPAL_API_REL_PATH}/user/user/" + ) responses.get( url=url_path, body=json.dumps(UserSchema(many=True).dump(TEST_USER_DATA)), @@ -123,7 +127,6 @@ def get_fake_json_api(self): @responses.activate def test_get_json_api(self): json_api = self.get_fake_json_api() - # print(f"JSONAPI: {json_api.requests.config.AUTH._client.token}") assert ( json_api.requests.config.AUTH._client.token["access_token"] == self.token["access_token"] @@ -156,7 +159,7 @@ def test_audit_study_sites_no_update(self): self.add_fake_study_sites_response() study_sites = get_study_sites(json_api=json_api) audit_results = audit_drupal_study_sites( - study_sites=study_sites, should_update=False + study_sites=study_sites, apply_changes=False ) assert audit_results.encountered_issues() is False assert StudySite.objects.all().count() == 0 @@ -166,6 +169,7 @@ def test_full_site_audit(self): self.add_fake_token_response() self.add_fake_study_sites_response() results = drupal_data_study_site_audit() + assert results.encountered_issues() is False @responses.activate def test_audit_study_sites_with_new_sites(self): @@ -173,7 +177,7 @@ def test_audit_study_sites_with_new_sites(self): self.add_fake_study_sites_response() study_sites = get_study_sites(json_api=json_api) audit_results = audit_drupal_study_sites( - study_sites=study_sites, should_update=True + study_sites=study_sites, apply_changes=True ) assert audit_results.encountered_issues() is False assert audit_results.count_new_rows() == 2 @@ -193,7 +197,7 @@ def test_audit_study_sites_with_site_update(self): self.add_fake_study_sites_response() study_sites = get_study_sites(json_api=json_api) audit_results = audit_drupal_study_sites( - study_sites=study_sites, should_update=True + study_sites=study_sites, apply_changes=True ) assert audit_results.encountered_issues() is False assert audit_results.count_new_rows() == 1 @@ -214,7 +218,7 @@ def test_audit_study_sites_with_extra_site(self): self.add_fake_study_sites_response() study_sites = get_study_sites(json_api=json_api) audit_results = audit_drupal_study_sites( - study_sites=study_sites, should_update=True + study_sites=study_sites, apply_changes=True ) assert audit_results.encountered_issues() is True @@ -224,3 +228,13 @@ def test_full_user_audit(self): self.add_fake_study_sites_response() self.add_fake_users_response() results = drupal_data_user_audit() + assert results.encountered_issues() is False + assert results.count_new_rows() == 1 + assert results.count_update_rows() == 0 + assert results.count_removal_rows() == 0 + + users = get_user_model().objects.all() + assert users.count() == 1 + assert users.first().name == TEST_USER_DATA[0]["full_name"] + assert users.first().email == TEST_USER_DATA[0]["mail"] + assert users.first().username == TEST_USER_DATA[0]["name"] From be1eb4152edd061a9a3ffb8710f747ef12b17525 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Thu, 25 Jan 2024 09:54:45 -0800 Subject: [PATCH 05/60] Rework test audit structure to support relationships users->sites. --- config/settings/base.py | 2 +- primed/users/audit.py | 7 ++ primed/users/tests/test_audit.py | 136 +++++++++++++++++++++---------- 3 files changed, 101 insertions(+), 44 deletions(-) diff --git a/config/settings/base.py b/config/settings/base.py index 561a4913..27898fdc 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -389,4 +389,4 @@ DRUPAL_API_CLIENT_ID = env("DRUPAL_API_CLIENT_ID", default="") DRUPAL_API_CLIENT_SECRET = env("DRUPAL_API_CLIENT_SECRET", default="") -DRUPAL_API_REL_PATH = env("DRUPAL_API_REL_PATH", default="") +DRUPAL_API_REL_PATH = env("DRUPAL_API_REL_PATH", default="mockapi") diff --git a/primed/users/audit.py b/primed/users/audit.py index 262630fc..c83accf3 100644 --- a/primed/users/audit.py +++ b/primed/users/audit.py @@ -1,3 +1,5 @@ +import logging + import jsonapi_requests from allauth.socialaccount.models import SocialAccount from django.conf import settings @@ -10,6 +12,8 @@ from primed.primed_anvil.models import StudySite from primed.users.adapters import SocialAccountAdapter +logger = logging.getLogger(__name__) + class AuditResults: def __init__(self): @@ -231,6 +235,9 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): }, apply_update=apply_changes, ) + logger.info( + f"for user {user} ss_short_names {drupal_user_study_site_shortnames}" + ) user_sites_changed = drupal_adapter.update_user_study_sites( user=sa.user, diff --git a/primed/users/tests/test_audit.py b/primed/users/tests/test_audit.py index a5146b33..3b3df53b 100644 --- a/primed/users/tests/test_audit.py +++ b/primed/users/tests/test_audit.py @@ -17,6 +17,36 @@ from primed.users.models import StudySite +class StudySiteMockObject: + def __init__(self, id, title, field_long_name, drupal_internal__nid) -> None: + self.id = id + self.title = title + self.field_long_name = field_long_name + self.drupal_internal__nid = drupal_internal__nid + + +class UserMockObject: + def __init__( + self, + id, + display_name, + drupal_internal__uid, + name, + mail, + field_given_first_name_s_, + field_examples_family_last_name_, + field_study_site_or_center, + ) -> None: + self.id = id + self.display_name = display_name + self.drupal_internal__uid = drupal_internal__uid + self.name = name + self.mail = mail + self.field_given_first_name_s_ = field_given_first_name_s_ + self.field_examples_family_last_name_ = field_examples_family_last_name_ + self.field_study_site_or_center = field_study_site_or_center + + class StudySiteSchema(Schema): id = fields.Str(dump_only=True) title = fields.Str() @@ -36,7 +66,9 @@ class UserSchema(Schema): mail = fields.Str() field_given_first_name_s_ = fields.Str() field_examples_family_last_name_ = fields.Str() - field_study_site_or_center = fields.Relationship(schema="StudySiteSchema") + field_study_site_or_center = fields.Relationship( + many=True, schema="StudySiteSchema", type_="node--study_site_or_center" + ) class Meta: type_ = "users" @@ -54,34 +86,39 @@ class Meta: TEST_STUDY_SITE_DATA = [ - { - "id": "1", - "drupal_internal__nid": "1", - "title": "SS1", - "field_long_name": "S S 1", - # "document_meta": {"page": {"offset": 10}}, - }, - { - "id": "2", - "drupal_internal__nid": "2", - "title": "SS2", - "field_long_name": "S S 2", - # "document_meta": {"page": {"offset": 10}}, - }, + StudySiteMockObject( + **{ + "id": "1", + "drupal_internal__nid": "1", + "title": "SS1", + "field_long_name": "S S 1", + # "document_meta": {"page": {"offset": 10}}, + } + ), + StudySiteMockObject( + **{ + "id": "2", + "drupal_internal__nid": "2", + "title": "SS2", + "field_long_name": "S S 2", + # "document_meta": {"page": {"offset": 10}}, + } + ), ] TEST_USER_DATA = [ - { - "id": "usr1", - "display_name": "dnusr1", - "drupal_internal__uid": "usr1", - "name": "testuser1", - "mail": "testuser1@test.com", - "field_given_first_name_s_": "test1", - "field_examples_family_last_name_": "user1", - "full_name": "test1 user1", - "field_study_site_or_center": [TEST_STUDY_SITE_DATA[0]], - } + UserMockObject( + **{ + "id": "usr1", + "display_name": "dnusr1", + "drupal_internal__uid": "usr1", + "name": "testuser1", + "mail": "testuser1@test.com", + "field_given_first_name_s_": "test1", + "field_examples_family_last_name_": "user1", + "field_study_site_or_center": [], + } + ) ] @@ -111,9 +148,14 @@ def add_fake_users_response(self): url_path = ( f"{settings.DRUPAL_SITE_URL}/{settings.DRUPAL_API_REL_PATH}/user/user/" ) + TEST_USER_DATA[0].field_study_site_or_center = [TEST_STUDY_SITE_DATA[0]] + user_data = UserSchema( + include_data=("field_study_site_or_center",), many=True + ).dump(TEST_USER_DATA) + print(f"USER DATA: {user_data}") responses.get( url=url_path, - body=json.dumps(UserSchema(many=True).dump(TEST_USER_DATA)), + body=json.dumps(user_data), ) def add_fake_token_response(self): @@ -141,16 +183,16 @@ def test_get_study_sites(self): for test_study_site in TEST_STUDY_SITE_DATA: assert ( - test_study_site["field_long_name"] - == study_sites[test_study_site["drupal_internal__nid"]]["full_name"] + test_study_site.field_long_name + == study_sites[test_study_site.drupal_internal__nid]["full_name"] ) assert ( - test_study_site["title"] - == study_sites[test_study_site["drupal_internal__nid"]]["short_name"] + test_study_site.title + == study_sites[test_study_site.drupal_internal__nid]["short_name"] ) assert ( - test_study_site["drupal_internal__nid"] - == study_sites[test_study_site["drupal_internal__nid"]]["node_id"] + test_study_site.drupal_internal__nid + == study_sites[test_study_site.drupal_internal__nid]["node_id"] ) @responses.activate @@ -183,14 +225,14 @@ def test_audit_study_sites_with_new_sites(self): assert audit_results.count_new_rows() == 2 assert StudySite.objects.all().count() == 2 assert StudySite.objects.filter( - short_name=TEST_STUDY_SITE_DATA[0]["title"] + short_name=TEST_STUDY_SITE_DATA[0].title ).exists() @responses.activate def test_audit_study_sites_with_site_update(self): StudySite.objects.create( - drupal_node_id=TEST_STUDY_SITE_DATA[0]["drupal_internal__nid"], - short_name=TEST_STUDY_SITE_DATA[0]["title"], + drupal_node_id=TEST_STUDY_SITE_DATA[0].drupal_internal__nid, + short_name=TEST_STUDY_SITE_DATA[0].title, full_name="WrongTitle", ) json_api = self.get_fake_json_api() @@ -203,11 +245,9 @@ def test_audit_study_sites_with_site_update(self): assert audit_results.count_new_rows() == 1 assert audit_results.count_update_rows() == 1 assert StudySite.objects.all().count() == 2 - first_test_ss = StudySite.objects.get( - short_name=TEST_STUDY_SITE_DATA[0]["title"] - ) + first_test_ss = StudySite.objects.get(short_name=TEST_STUDY_SITE_DATA[0].title) # did we update the long name - assert first_test_ss.full_name == TEST_STUDY_SITE_DATA[0]["field_long_name"] + assert first_test_ss.full_name == TEST_STUDY_SITE_DATA[0].field_long_name @responses.activate def test_audit_study_sites_with_extra_site(self): @@ -227,6 +267,11 @@ def test_full_user_audit(self): self.add_fake_token_response() self.add_fake_study_sites_response() self.add_fake_users_response() + StudySite.objects.create( + drupal_node_id=TEST_STUDY_SITE_DATA[0].drupal_internal__nid, + short_name=TEST_STUDY_SITE_DATA[0].title, + full_name=TEST_STUDY_SITE_DATA[0].field_long_name, + ) results = drupal_data_user_audit() assert results.encountered_issues() is False assert results.count_new_rows() == 1 @@ -235,6 +280,11 @@ def test_full_user_audit(self): users = get_user_model().objects.all() assert users.count() == 1 - assert users.first().name == TEST_USER_DATA[0]["full_name"] - assert users.first().email == TEST_USER_DATA[0]["mail"] - assert users.first().username == TEST_USER_DATA[0]["name"] + + assert users.first().email == TEST_USER_DATA[0].mail + assert users.first().username == TEST_USER_DATA[0].name + assert users.first().study_sites.count() == 1 + assert ( + users.first().study_sites.first().short_name + == TEST_STUDY_SITE_DATA[0].title + ) From 7e8711a786d9ff397f15efd66af30448721e3208 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Thu, 25 Jan 2024 11:35:10 -0800 Subject: [PATCH 06/60] Fix issue where we were not correctly capturing all drupal uids audited --- primed/users/audit.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/primed/users/audit.py b/primed/users/audit.py index c83accf3..141b69d0 100644 --- a/primed/users/audit.py +++ b/primed/users/audit.py @@ -47,12 +47,13 @@ def add_update(self, data): } ) - def add_issue(self, data): + def add_issue(self, data, issue_type): self.results.append( { "data_type": self.data_type, "result_type": self.RESULT_TYPE_ISSUE, "data": data, + "issue_type": issue_type, } ) @@ -201,11 +202,12 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): study_site_info["short_name"] ) is_new_user = False + # no uid is blocked or anonymous if not drupal_uid: - # FIXME - deactivate if not anonymous and present locally + # potential blocked user, but will no longer have a drupal uid + # so we cover these below continue - try: sa = SocialAccount.objects.get( uid=user.attributes["drupal_internal__uid"], @@ -235,9 +237,6 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): }, apply_update=apply_changes, ) - logger.info( - f"for user {user} ss_short_names {drupal_user_study_site_shortnames}" - ) user_sites_changed = drupal_adapter.update_user_study_sites( user=sa.user, @@ -246,13 +245,19 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): if user_changed or user_sites_changed and not is_new_user: audit_results.add_update(data=user) - drupal_uids.add(sa.user.id) + drupal_uids.add(drupal_uid) user_count += 1 # find active drupal users that we did not account before - # unaudited_drupal_accounts = SocialAccount.objects.filter( - # provider=CustomProvider.id, user__is_active=True - # ).exclude(uid__in=drupal_uids) + # these may include blocked users + + unaudited_drupal_accounts = SocialAccount.objects.filter( + provider=CustomProvider.id, user__is_active=True + ).exclude(uid__in=drupal_uids) + + for uda in unaudited_drupal_accounts: + audit_results.add_issue(data=uda, issue_type="Local account not in drupal") + return audit_results @@ -305,6 +310,6 @@ def audit_drupal_study_sites(study_sites, apply_changes=False): invalid_study_sites = StudySite.objects.exclude(drupal_node_id__in=valid_nodes) for iss in invalid_study_sites: - audit_results.add_issue(data=iss) + audit_results.add_issue(data=iss, issue_type="Local site not in drupal") return audit_results From 98512df06bccdf921a9149afa564256f8b9a1dd3 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Thu, 25 Jan 2024 11:48:08 -0800 Subject: [PATCH 07/60] Make sure we apply no updates unless we are in update mode --- primed/users/audit.py | 48 ++++++++++--------- .../management/commands/sync-drupal-data.py | 18 ++++++- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/primed/users/audit.py b/primed/users/audit.py index 141b69d0..201c9c50 100644 --- a/primed/users/audit.py +++ b/primed/users/audit.py @@ -208,6 +208,7 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): # potential blocked user, but will no longer have a drupal uid # so we cover these below continue + sa = None try: sa = SocialAccount.objects.get( uid=user.attributes["drupal_internal__uid"], @@ -218,30 +219,33 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): drupal_user.username = drupal_username drupal_user.name = drupal_full_name drupal_user.email = drupal_email - drupal_user.save() + if apply_changes is True: + drupal_user.save() is_new_user = True - sa = SocialAccount.objects.create( - user=drupal_user, - uid=user.attributes["drupal_internal__uid"], - provider=CustomProvider.id, - ) + if apply_changes is True: + sa = SocialAccount.objects.create( + user=drupal_user, + uid=user.attributes["drupal_internal__uid"], + provider=CustomProvider.id, + ) audit_results.add_new(data=user) - - user_changed = drupal_adapter.update_user_info( - user=sa.user, - extra_data={ - "preferred_username": drupal_username, - "first_name": drupal_firstname, - "last_name": drupal_lastname, - "email": drupal_email, - }, - apply_update=apply_changes, - ) - - user_sites_changed = drupal_adapter.update_user_study_sites( - user=sa.user, - extra_data={"study_site_or_center": drupal_user_study_site_shortnames}, - ) + if sa: + user_changed = drupal_adapter.update_user_info( + user=sa.user, + extra_data={ + "preferred_username": drupal_username, + "first_name": drupal_firstname, + "last_name": drupal_lastname, + "email": drupal_email, + }, + apply_update=apply_changes, + ) + if sa: + user_sites_changed = drupal_adapter.update_user_study_sites( + user=sa.user, + extra_data={"study_site_or_center": drupal_user_study_site_shortnames}, + apply_update=apply_changes + ) if user_changed or user_sites_changed and not is_new_user: audit_results.add_update(data=user) diff --git a/primed/users/management/commands/sync-drupal-data.py b/primed/users/management/commands/sync-drupal-data.py index b409775d..4af3a6b4 100644 --- a/primed/users/management/commands/sync-drupal-data.py +++ b/primed/users/management/commands/sync-drupal-data.py @@ -2,7 +2,11 @@ from django.core.management.base import BaseCommand -from primed.users.audit import drupal_data_study_site_audit, drupal_data_user_audit +from primed.users.audit import ( + AuditResults, + drupal_data_study_site_audit, + drupal_data_user_audit, +) logger = logging.getLogger(__name__) @@ -23,6 +27,18 @@ def handle(self, *args, **options): user_audit_results = drupal_data_user_audit(apply_changes=should_update) print(f"User Audit Results {user_audit_results}") + if user_audit_results.encountered_issues(): + print( + user_audit_results.rows_by_result_type( + result_type=AuditResults.RESULT_TYPE_ISSUE + ) + ) site_audit_results = drupal_data_study_site_audit(apply_changes=should_update) print(f"Site Audit Results {site_audit_results}") + if site_audit_results.encountered_issues(): + print( + site_audit_results.rows_by_result_type( + result_type=AuditResults.RESULT_TYPE_ISSUE + ) + ) From 7f6ef98d18a85eb52e58ba885bc5cae60624355c Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Thu, 25 Jan 2024 11:49:09 -0800 Subject: [PATCH 08/60] log whether we are in update mode --- primed/users/audit.py | 6 ++++-- primed/users/management/commands/sync-drupal-data.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/primed/users/audit.py b/primed/users/audit.py index 201c9c50..1d8e27a9 100644 --- a/primed/users/audit.py +++ b/primed/users/audit.py @@ -243,8 +243,10 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): if sa: user_sites_changed = drupal_adapter.update_user_study_sites( user=sa.user, - extra_data={"study_site_or_center": drupal_user_study_site_shortnames}, - apply_update=apply_changes + extra_data={ + "study_site_or_center": drupal_user_study_site_shortnames + }, + apply_update=apply_changes, ) if user_changed or user_sites_changed and not is_new_user: audit_results.add_update(data=user) diff --git a/primed/users/management/commands/sync-drupal-data.py b/primed/users/management/commands/sync-drupal-data.py index 4af3a6b4..8d97930e 100644 --- a/primed/users/management/commands/sync-drupal-data.py +++ b/primed/users/management/commands/sync-drupal-data.py @@ -26,7 +26,7 @@ def handle(self, *args, **options): should_update = options.get("update") user_audit_results = drupal_data_user_audit(apply_changes=should_update) - print(f"User Audit Results {user_audit_results}") + print(f"User Audit (Update: {should_update}) Results {user_audit_results}") if user_audit_results.encountered_issues(): print( user_audit_results.rows_by_result_type( @@ -35,7 +35,7 @@ def handle(self, *args, **options): ) site_audit_results = drupal_data_study_site_audit(apply_changes=should_update) - print(f"Site Audit Results {site_audit_results}") + print(f"Site Audit (Update: {should_update}) Results {site_audit_results}") if site_audit_results.encountered_issues(): print( site_audit_results.rows_by_result_type( From d4d82daaf69ceb8bd4c2d467e1e0351159e1e19a Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Fri, 26 Jan 2024 09:24:27 -0800 Subject: [PATCH 09/60] Add gitleaks:allow to test secrets --- primed/users/tests/test_audit.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/primed/users/tests/test_audit.py b/primed/users/tests/test_audit.py index 3b3df53b..0f3b7c85 100644 --- a/primed/users/tests/test_audit.py +++ b/primed/users/tests/test_audit.py @@ -131,8 +131,8 @@ def setUp(self): fake_time = time.time() self.token = { "token_type": "Bearer", - "access_token": "asdfoiw37850234lkjsdfsdfTEST", - "refresh_token": "sldvafkjw34509s8dfsdfTEST", + "access_token": "asdfoiw37850234lkjsdfsdfTEST", # gitleaks:allow + "refresh_token": "sldvafkjw34509s8dfsdfTEST", # gitleaks:allow "expires_in": 3600, "expires_at": fake_time + 3600, } @@ -272,7 +272,7 @@ def test_full_user_audit(self): short_name=TEST_STUDY_SITE_DATA[0].title, full_name=TEST_STUDY_SITE_DATA[0].field_long_name, ) - results = drupal_data_user_audit() + results = drupal_data_user_audit(apply_changes=True) assert results.encountered_issues() is False assert results.count_new_rows() == 1 assert results.count_update_rows() == 0 @@ -288,3 +288,6 @@ def test_full_user_audit(self): users.first().study_sites.first().short_name == TEST_STUDY_SITE_DATA[0].title ) + + # test user removal + # test user change From 8af23068ffc217b7356da7091ced09b4621c309f Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Fri, 2 Feb 2024 11:38:16 -0800 Subject: [PATCH 10/60] More clearly capture what is updated during audit. Add flag based support for deactiving missing and blocked drupal users --- config/settings/base.py | 3 + primed/users/audit.py | 95 ++++++++++++++------- primed/users/tests/test_audit.py | 137 ++++++++++++++++++++++++++++++- 3 files changed, 201 insertions(+), 34 deletions(-) diff --git a/config/settings/base.py b/config/settings/base.py index 27898fdc..89e433f3 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -390,3 +390,6 @@ DRUPAL_API_CLIENT_ID = env("DRUPAL_API_CLIENT_ID", default="") DRUPAL_API_CLIENT_SECRET = env("DRUPAL_API_CLIENT_SECRET", default="") DRUPAL_API_REL_PATH = env("DRUPAL_API_REL_PATH", default="mockapi") +DRUPAL_DATA_AUDIT_DEACTIVATE_USERS = env( + "DRUPAL_DATA_AUDIT_DEACTIVATE_USERS", default=False +) diff --git a/primed/users/audit.py b/primed/users/audit.py index 1d8e27a9..3c4f3a92 100644 --- a/primed/users/audit.py +++ b/primed/users/audit.py @@ -10,7 +10,6 @@ from primed.drupal_oauth_provider.provider import CustomProvider from primed.primed_anvil.models import StudySite -from primed.users.adapters import SocialAccountAdapter logger = logging.getLogger(__name__) @@ -38,12 +37,13 @@ def add_new(self, data): } ) - def add_update(self, data): + def add_update(self, data, updates): self.results.append( { "data_type": self.data_type, "result_type": self.RESULT_TYPE_UPDATE, "data": data, + "updates": updates, } ) @@ -145,7 +145,6 @@ def drupal_data_study_site_audit(apply_changes=False): status = audit_drupal_study_sites( study_sites=study_sites, apply_changes=apply_changes ) - return status @@ -165,8 +164,6 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): user_endpoint_url = "user/user" drupal_uids = set() - drupal_adapter = SocialAccountAdapter() - user_count = 0 while user_endpoint_url is not None: @@ -201,8 +198,9 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): drupal_user_study_site_shortnames.append( study_site_info["short_name"] ) - is_new_user = False - + new_user_sites = StudySite.objects.filter( + short_name__in=drupal_user_study_site_shortnames + ) # no uid is blocked or anonymous if not drupal_uid: # potential blocked user, but will no longer have a drupal uid @@ -221,7 +219,7 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): drupal_user.email = drupal_email if apply_changes is True: drupal_user.save() - is_new_user = True + drupal_user.study_sites.set(new_user_sites) if apply_changes is True: sa = SocialAccount.objects.create( user=drupal_user, @@ -229,32 +227,50 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): provider=CustomProvider.id, ) audit_results.add_new(data=user) + if sa: - user_changed = drupal_adapter.update_user_info( - user=sa.user, - extra_data={ - "preferred_username": drupal_username, - "first_name": drupal_firstname, - "last_name": drupal_lastname, - "email": drupal_email, - }, - apply_update=apply_changes, - ) - if sa: - user_sites_changed = drupal_adapter.update_user_study_sites( - user=sa.user, - extra_data={ - "study_site_or_center": drupal_user_study_site_shortnames - }, - apply_update=apply_changes, + user_updates = {} + if sa.user.name != drupal_full_name: + user_updates.update( + {"name": {"old": sa.user.name, "new": drupal_full_name}} + ) + sa.user.name = drupal_full_name + if sa.user.username != drupal_username: + user_updates.update( + {"username": {"old": sa.user.username, "new": drupal_username}} + ) + sa.user.username = drupal_username + if sa.user.email != drupal_email: + user_updates.update( + {"email": {"old": sa.user.email, "new": drupal_email}} + ) + sa.user.email = drupal_email + + prev_user_sites = set( + sa.user.study_sites.all().values_list("short_name", flat=True) ) - if user_changed or user_sites_changed and not is_new_user: - audit_results.add_update(data=user) + if prev_user_sites != set(drupal_user_study_site_shortnames): + user_updates.update( + { + "sites": { + "old": prev_user_sites, + "new": drupal_user_study_site_shortnames, + } + } + ) + + sa.user.study_sites.set(new_user_sites) + + if user_updates: + if apply_changes is True: + sa.user.save() + audit_results.add_update(data=user, updates=user_updates) drupal_uids.add(drupal_uid) user_count += 1 - # find active drupal users that we did not account before + # find active django accounts that are drupal based + # users that we did not get from drupal # these may include blocked users unaudited_drupal_accounts = SocialAccount.objects.filter( @@ -262,7 +278,10 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): ).exclude(uid__in=drupal_uids) for uda in unaudited_drupal_accounts: - audit_results.add_issue(data=uda, issue_type="Local account not in drupal") + if settings.DRUPAL_DATA_AUDIT_DEACTIVATE_USERS is True: + uda.user.is_active = False + uda.user.save() + audit_results.add_removal(data=uda) return audit_results @@ -306,12 +325,26 @@ def audit_drupal_study_sites(study_sites, apply_changes=False): ) audit_results.add_new(data=study_site_info) else: - if study_site.full_name != full_name or study_site.short_name != short_name: + study_site_updates = {} + + if study_site.full_name != full_name: + study_site_updates.update( + {"full_name": {"old": study_site.full_name, "new": full_name}} + ) study_site.full_name = full_name + + if study_site.short_name != short_name: + study_site_updates.update( + {"short_name": {"old": study_site.short_name, "new": short_name}} + ) study_site.short_name = short_name + + if study_site_updates: if apply_changes is True: study_site.save() - audit_results.add_update(data=study_site_info) + audit_results.add_update( + data=study_site_info, updates=study_site_updates + ) invalid_study_sites = StudySite.objects.exclude(drupal_node_id__in=valid_nodes) diff --git a/primed/users/tests/test_audit.py b/primed/users/tests/test_audit.py index 0f3b7c85..c5a85738 100644 --- a/primed/users/tests/test_audit.py +++ b/primed/users/tests/test_audit.py @@ -2,11 +2,13 @@ import time import responses +from allauth.socialaccount.models import SocialAccount from django.conf import settings from django.contrib.auth import get_user_model from django.test import TestCase from marshmallow_jsonapi import Schema, fields +from primed.drupal_oauth_provider.provider import CustomProvider from primed.users.audit import ( audit_drupal_study_sites, drupal_data_study_site_audit, @@ -118,7 +120,20 @@ class Meta: "field_examples_family_last_name_": "user1", "field_study_site_or_center": [], } - ) + ), + # second mock object is deactivated user (no drupal uid) + UserMockObject( + **{ + "id": "usr1", + "display_name": "dnusr2", + "drupal_internal__uid": "", + "name": "testuser2", + "mail": "testuser2@test.com", + "field_given_first_name_s_": "test2", + "field_examples_family_last_name_": "user2", + "field_study_site_or_center": [], + } + ), ] @@ -232,7 +247,7 @@ def test_audit_study_sites_with_new_sites(self): def test_audit_study_sites_with_site_update(self): StudySite.objects.create( drupal_node_id=TEST_STUDY_SITE_DATA[0].drupal_internal__nid, - short_name=TEST_STUDY_SITE_DATA[0].title, + short_name="WrongShortName", full_name="WrongTitle", ) json_api = self.get_fake_json_api() @@ -248,6 +263,7 @@ def test_audit_study_sites_with_site_update(self): first_test_ss = StudySite.objects.get(short_name=TEST_STUDY_SITE_DATA[0].title) # did we update the long name assert first_test_ss.full_name == TEST_STUDY_SITE_DATA[0].field_long_name + assert first_test_ss.short_name == TEST_STUDY_SITE_DATA[0].title @responses.activate def test_audit_study_sites_with_extra_site(self): @@ -273,6 +289,7 @@ def test_full_user_audit(self): full_name=TEST_STUDY_SITE_DATA[0].field_long_name, ) results = drupal_data_user_audit(apply_changes=True) + print(f"RESULTS: {results} -- {results.results}") assert results.encountered_issues() is False assert results.count_new_rows() == 1 assert results.count_update_rows() == 0 @@ -289,5 +306,119 @@ def test_full_user_audit(self): == TEST_STUDY_SITE_DATA[0].title ) + @responses.activate + def test_full_user_audit_check_only(self): + self.add_fake_token_response() + self.add_fake_study_sites_response() + self.add_fake_users_response() + StudySite.objects.create( + drupal_node_id=TEST_STUDY_SITE_DATA[0].drupal_internal__nid, + short_name=TEST_STUDY_SITE_DATA[0].title, + full_name=TEST_STUDY_SITE_DATA[0].field_long_name, + ) + results = drupal_data_user_audit(apply_changes=False) + + assert results.encountered_issues() is False + assert results.count_new_rows() == 1 + assert results.count_update_rows() == 0 + assert results.count_removal_rows() == 0 + + # verify we did not actually create a user + users = get_user_model().objects.all() + assert users.count() == 0 + + @responses.activate + def test_user_audit_change_user(self): + self.add_fake_token_response() + self.add_fake_study_sites_response() + self.add_fake_users_response() + StudySite.objects.create( + drupal_node_id=TEST_STUDY_SITE_DATA[0].drupal_internal__nid, + short_name=TEST_STUDY_SITE_DATA[0].title, + full_name=TEST_STUDY_SITE_DATA[0].field_long_name, + ) + drupal_fullname = "{} {}".format( + TEST_USER_DATA[0].field_given_first_name_s_, + TEST_USER_DATA[0].field_examples_family_last_name_, + ) + drupal_username = TEST_USER_DATA[0].name + drupal_email = TEST_USER_DATA[0].mail + new_user = get_user_model().objects.create( + username=drupal_username + "UPDATE", + email=drupal_email + "UPDATE", + name=drupal_fullname + "UPDATE", + ) + SocialAccount.objects.create( + user=new_user, + uid=TEST_USER_DATA[0].drupal_internal__uid, + provider=CustomProvider.id, + ) + results = drupal_data_user_audit(apply_changes=True) + new_user.refresh_from_db() + + assert new_user.name == drupal_fullname + assert results.encountered_issues() is False + assert results.count_new_rows() == 0 + assert results.count_update_rows() == 1 + assert results.count_removal_rows() == 0 + + # test user removal + @responses.activate + def test_user_audit_remove_user_only_inform(self): + self.add_fake_token_response() + self.add_fake_study_sites_response() + self.add_fake_users_response() + StudySite.objects.create( + drupal_node_id=TEST_STUDY_SITE_DATA[0].drupal_internal__nid, + short_name=TEST_STUDY_SITE_DATA[0].title, + full_name=TEST_STUDY_SITE_DATA[0].field_long_name, + ) + + new_user = get_user_model().objects.create( + username="username2", email="useremail2", name="user fullname2" + ) + SocialAccount.objects.create( + user=new_user, + uid=999, + provider=CustomProvider.id, + ) + results = drupal_data_user_audit(apply_changes=True) + + new_user.refresh_from_db() + assert new_user.is_active is True + assert results.encountered_issues() is False + assert results.count_new_rows() == 1 + assert results.count_update_rows() == 0 + assert results.count_removal_rows() == 1 + + # test user removal + @responses.activate + def test_user_audit_remove_user(self): + self.add_fake_token_response() + self.add_fake_study_sites_response() + self.add_fake_users_response() + StudySite.objects.create( + drupal_node_id=TEST_STUDY_SITE_DATA[0].drupal_internal__nid, + short_name=TEST_STUDY_SITE_DATA[0].title, + full_name=TEST_STUDY_SITE_DATA[0].field_long_name, + ) + + new_user = get_user_model().objects.create( + username="username2", email="useremail2", name="user fullname2" + ) + SocialAccount.objects.create( + user=new_user, + uid=999, + provider=CustomProvider.id, + ) + with self.settings(DRUPAL_DATA_AUDIT_DEACTIVATE_USERS=True): + results = drupal_data_user_audit(apply_changes=True) + + new_user.refresh_from_db() + assert new_user.is_active is False + assert results.encountered_issues() is False + assert results.count_new_rows() == 1 + assert results.count_update_rows() == 0 + assert results.count_removal_rows() == 1 + # test user removal - # test user change From 11d106bb052a8f9f9e5c144c55ad1093eb987ee7 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Fri, 8 Mar 2024 10:28:34 -0800 Subject: [PATCH 11/60] Add more coverage, more clear handling or not handling of issues. Rudimentary reporting to stdout --- primed/users/audit.py | 66 ++++++++++++++-- .../management/commands/sync-drupal-data.py | 78 ++++++++++++++----- primed/users/tests/test_audit.py | 55 ++++++++++++- 3 files changed, 169 insertions(+), 30 deletions(-) diff --git a/primed/users/audit.py b/primed/users/audit.py index 3c4f3a92..34b5e007 100644 --- a/primed/users/audit.py +++ b/primed/users/audit.py @@ -28,6 +28,8 @@ def __init__(self): # A record was removed or deactivated RESULT_TYPE_REMOVAL = "removed" + ISSUE_TYPE_USER_INACTIVE = "user_inactive" + def add_new(self, data): self.results.append( { @@ -100,6 +102,55 @@ def __str__(self) -> str: f"Removals: {self.count_removal_rows()}" ) + def display_result(self, result): + result_string = "" + result_type = result["result_type"] + result_string += f"{result['result_type']} {self.data_type}" + if self.data_type == "user": + if isinstance(result["data"], SocialAccount): + result_string += "\tUSERNAME: {}\tUID: {}".format( + result["data"].user.username, result["data"].uid + ) + else: + result_string += "\tUSERNAME: {}\tUID: {}".format( + result["data"].attributes["display_name"], + result["data"].attributes["drupal_internal__uid"], + ) + elif self.data_type == "site": + if isinstance(result["data"], StudySite): + result_string += "\tShortName: {}FullName: {}\tNodeID: {}".format( + result["data"].short_name, + result["data"].full_name, + result["data"].drupal_node_id, + ) + else: + result_string += "\tShortName: {}FullName: {}\tNodeID: {}".format( + result["data"]["short_name"], + result["data"]["full_name"], + result["data"]["node_id"], + ) + if result_type == self.RESULT_TYPE_UPDATE: + result_string += "\t{result.get('updates')}" + if result_type == self.RESULT_TYPE_ISSUE: + result_string += f"\tIssue Type: {result.get('issue_type')}" + return result_string + + def detailed_issues(self): + result_string = "" + for row in self.rows_by_result_type(result_type=self.RESULT_TYPE_ISSUE): + result_string += self.display_result(row) + result_string += "\n" + + return result_string + + def detailed_results(self): + result_string = "" + for row in self.results: + result_string += self.display_result(row) + result_string += "\n" + + return result_string + class UserAuditResults(AuditResults): def __init__(self): @@ -171,11 +222,10 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): users_endpoint_response = users_endpoint.get() # If there are more, there will be a 'next' link - next_user_endpoint = users_endpoint_response.content.links.get("next") - if next_user_endpoint: - user_endpoint_url = next_user_endpoint["href"] - else: - user_endpoint_url = None + + user_endpoint_url = users_endpoint_response.content.links.get("next", {}).get( + "href" + ) for user in users_endpoint_response.data: drupal_uid = user.attributes.get("drupal_internal__uid") @@ -281,7 +331,11 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): if settings.DRUPAL_DATA_AUDIT_DEACTIVATE_USERS is True: uda.user.is_active = False uda.user.save() - audit_results.add_removal(data=uda) + audit_results.add_removal(data=uda) + else: + audit_results.add_issue( + data=uda, issue_type=AuditResults.ISSUE_TYPE_USER_INACTIVE + ) return audit_results diff --git a/primed/users/management/commands/sync-drupal-data.py b/primed/users/management/commands/sync-drupal-data.py index 8d97930e..ead59479 100644 --- a/primed/users/management/commands/sync-drupal-data.py +++ b/primed/users/management/commands/sync-drupal-data.py @@ -2,17 +2,16 @@ from django.core.management.base import BaseCommand -from primed.users.audit import ( - AuditResults, - drupal_data_study_site_audit, - drupal_data_user_audit, -) +from primed.users.audit import drupal_data_study_site_audit, drupal_data_user_audit logger = logging.getLogger(__name__) class Command(BaseCommand): help = "Sync drupal user and domain data" + NOTIFY_NONE = "none" + NOTIFY_ALL = "all" + NOTIFY_ISSUES = "issues" def add_arguments(self, parser): parser.add_argument( @@ -20,25 +19,64 @@ def add_arguments(self, parser): action="store_true", dest="update", default=False, + help="Make updates to sync local data with remote. If not set, will just report.", + ) + parser.add_argument( + "--verbose", + action="store_true", + dest="verbose", + default=False, + help="Log verbosely", + ) + parser.add_argument( + "--notify", + dest="notify", + choices=[self.NOTIFY_NONE, self.NOTIFY_ALL, self.NOTIFY_ISSUES], + default=self.NOTIFY_ALL, + help=f"Notification level: (default: {self.NOTIFY_ALL})", ) def handle(self, *args, **options): - should_update = options.get("update") + apply_changes = options.get("update") + be_verbose = options.get("verbose") + notify_type = options.get("notify") - user_audit_results = drupal_data_user_audit(apply_changes=should_update) - print(f"User Audit (Update: {should_update}) Results {user_audit_results}") - if user_audit_results.encountered_issues(): - print( - user_audit_results.rows_by_result_type( - result_type=AuditResults.RESULT_TYPE_ISSUE - ) + site_audit_results = drupal_data_study_site_audit(apply_changes=apply_changes) + logger.info( + f"Site Audit (Update: {apply_changes}) Results summary: {site_audit_results}" + ) + detailed_site_audit_results = site_audit_results.detailed_results() + + user_audit_results = drupal_data_user_audit(apply_changes=apply_changes) + logger.info( + f"User Audit (Update: {apply_changes}) Results summary: {user_audit_results}" + ) + detailed_user_audit_results = user_audit_results.detailed_results() + + if be_verbose: + logger.debug( + f"User Audit Results:\n{user_audit_results.detailed_results()}" ) + logger.debug( + f"Study Site Audit Results:\n{site_audit_results.detailed_results()}" + ) + + notification_content = "" + if user_audit_results.encountered_issues(): + notification_content += "Encountered user audit issues:\n" + notification_content += user_audit_results.detailed_issues() + else: + notification_content += "No user audit issues.\n" - site_audit_results = drupal_data_study_site_audit(apply_changes=should_update) - print(f"Site Audit (Update: {should_update}) Results {site_audit_results}") if site_audit_results.encountered_issues(): - print( - site_audit_results.rows_by_result_type( - result_type=AuditResults.RESULT_TYPE_ISSUE - ) - ) + notification_content += "Encountered site audit issues:\n" + notification_content += site_audit_results.detailed_issues() + else: + notification_content += "No site audit issues.\n" + + if notify_type == self.NOTIFY_ALL: + notification_content += detailed_site_audit_results + notification_content += detailed_user_audit_results + notification_content += "sync-drupal-data audit complete\n" + + self.stdout.write(notification_content) diff --git a/primed/users/tests/test_audit.py b/primed/users/tests/test_audit.py index c5a85738..1a821d3a 100644 --- a/primed/users/tests/test_audit.py +++ b/primed/users/tests/test_audit.py @@ -1,10 +1,12 @@ import json import time +from io import StringIO import responses from allauth.socialaccount.models import SocialAccount from django.conf import settings from django.contrib.auth import get_user_model +from django.core.management import call_command from django.test import TestCase from marshmallow_jsonapi import Schema, fields @@ -242,6 +244,7 @@ def test_audit_study_sites_with_new_sites(self): assert StudySite.objects.filter( short_name=TEST_STUDY_SITE_DATA[0].title ).exists() + self.assertRegex(audit_results.detailed_results(), "^new site") @responses.activate def test_audit_study_sites_with_site_update(self): @@ -289,7 +292,7 @@ def test_full_user_audit(self): full_name=TEST_STUDY_SITE_DATA[0].field_long_name, ) results = drupal_data_user_audit(apply_changes=True) - print(f"RESULTS: {results} -- {results.results}") + assert results.encountered_issues() is False assert results.count_new_rows() == 1 assert results.count_update_rows() == 0 @@ -305,6 +308,7 @@ def test_full_user_audit(self): users.first().study_sites.first().short_name == TEST_STUDY_SITE_DATA[0].title ) + self.assertRegex(results.detailed_results(), "^new user") @responses.activate def test_full_user_audit_check_only(self): @@ -361,6 +365,7 @@ def test_user_audit_change_user(self): assert results.count_new_rows() == 0 assert results.count_update_rows() == 1 assert results.count_removal_rows() == 0 + self.assertRegex(results.detailed_results(), "^update user") # test user removal @responses.activate @@ -386,10 +391,17 @@ def test_user_audit_remove_user_only_inform(self): new_user.refresh_from_db() assert new_user.is_active is True - assert results.encountered_issues() is False + assert results.encountered_issues() is True assert results.count_new_rows() == 1 assert results.count_update_rows() == 0 - assert results.count_removal_rows() == 1 + assert results.count_removal_rows() == 0 + assert results.count_issue_rows() == 1 + issue_rows = results.rows_by_result_type(results.RESULT_TYPE_ISSUE) + assert len(issue_rows) == 1 + assert issue_rows[0]["issue_type"] == results.ISSUE_TYPE_USER_INACTIVE + # assert not empty + assert results.detailed_issues() + self.assertRegex(str(results), "Issues: 1") # test user removal @responses.activate @@ -421,4 +433,39 @@ def test_user_audit_remove_user(self): assert results.count_update_rows() == 0 assert results.count_removal_rows() == 1 - # test user removal + @responses.activate + def test_sync_drupal_data_command(self): + self.add_fake_token_response() + self.add_fake_study_sites_response() + self.add_fake_token_response() + self.add_fake_study_sites_response() + self.add_fake_users_response() + out = StringIO() + call_command("sync-drupal-data", stdout=out) + self.assertIn("sync-drupal-data audit complete", out.getvalue()) + + @responses.activate + def test_sync_drupal_data_command_with_issues(self): + + StudySite.objects.create( + drupal_node_id="999999", + short_name=TEST_STUDY_SITE_DATA[0].title, + full_name=TEST_STUDY_SITE_DATA[0].field_long_name, + ) + + new_user = get_user_model().objects.create( + username="username2", email="useremail2", name="user fullname2" + ) + SocialAccount.objects.create( + user=new_user, + uid=999, + provider=CustomProvider.id, + ) + self.add_fake_token_response() + self.add_fake_study_sites_response() + self.add_fake_token_response() + self.add_fake_study_sites_response() + self.add_fake_users_response() + out = StringIO() + call_command("sync-drupal-data", "--verbose", stdout=out) + self.assertIn("sync-drupal-data audit complete", out.getvalue()) From e7d650716326908bf8822a49d34f0280f8dc6291 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Fri, 8 Mar 2024 11:19:49 -0800 Subject: [PATCH 12/60] Add option for capturing and reporting user site removal but not making updates. --- config/settings/base.py | 3 ++ primed/users/audit.py | 32 +++++++++++--- primed/users/tests/test_audit.py | 73 +++++++++++++++++++++++++++++++- 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/config/settings/base.py b/config/settings/base.py index 23524297..5b1b7b96 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -397,3 +397,6 @@ DRUPAL_DATA_AUDIT_DEACTIVATE_USERS = env( "DRUPAL_DATA_AUDIT_DEACTIVATE_USERS", default=False ) +DRUPAL_DATA_AUDIT_REMOVE_USER_SITES = env( + "DRUPAL_DATA_AUDIT_REMOVE_USER_SITES", default=False +) diff --git a/primed/users/audit.py b/primed/users/audit.py index 34b5e007..0c35983b 100644 --- a/primed/users/audit.py +++ b/primed/users/audit.py @@ -29,6 +29,7 @@ def __init__(self): RESULT_TYPE_REMOVAL = "removed" ISSUE_TYPE_USER_INACTIVE = "user_inactive" + ISSUE_TYPE_USER_REMOVED_FROM_SITE = "user_site_removal" def add_new(self, data): self.results.append( @@ -49,13 +50,14 @@ def add_update(self, data, updates): } ) - def add_issue(self, data, issue_type): + def add_issue(self, data, issue_type, issue_extra=None): self.results.append( { "data_type": self.data_type, "result_type": self.RESULT_TYPE_ISSUE, "data": data, "issue_type": issue_type, + "issue_extra": issue_extra, } ) @@ -296,20 +298,36 @@ def audit_drupal_users(study_sites, json_api, apply_changes=False): ) sa.user.email = drupal_email - prev_user_sites = set( + prev_user_site_names = set( sa.user.study_sites.all().values_list("short_name", flat=True) ) - if prev_user_sites != set(drupal_user_study_site_shortnames): + new_user_site_names = set(drupal_user_study_site_shortnames) + if prev_user_site_names != new_user_site_names: user_updates.update( { "sites": { - "old": prev_user_sites, - "new": drupal_user_study_site_shortnames, + "old": prev_user_site_names, + "new": new_user_site_names, } } ) - - sa.user.study_sites.set(new_user_sites) + # do not remove from sites by default + removed_sites = prev_user_site_names.difference(new_user_sites) + new_sites = new_user_site_names.difference(prev_user_site_names) + + if settings.DRUPAL_DATA_AUDIT_REMOVE_USER_SITES is True: + sa.user.study_sites.set(new_user_sites) + else: + if removed_sites: + audit_results.add_issue( + data=user, + issue_type=audit_results.ISSUE_TYPE_USER_REMOVED_FROM_SITE, + issue_extra=f"Removed Sites: {removed_sites}", + ) + if new_sites: + for new_site in new_user_sites: + if new_site.short_name in new_user_site_names: + sa.user.study_sites.add(new_site) if user_updates: if apply_changes is True: diff --git a/primed/users/tests/test_audit.py b/primed/users/tests/test_audit.py index 1a821d3a..7a2a7536 100644 --- a/primed/users/tests/test_audit.py +++ b/primed/users/tests/test_audit.py @@ -126,7 +126,7 @@ class Meta: # second mock object is deactivated user (no drupal uid) UserMockObject( **{ - "id": "usr1", + "id": "usr2", "display_name": "dnusr2", "drupal_internal__uid": "", "name": "testuser2", @@ -331,6 +331,77 @@ def test_full_user_audit_check_only(self): users = get_user_model().objects.all() assert users.count() == 0 + @responses.activate + def test_user_audit_remove_site_inform(self): + self.add_fake_token_response() + self.add_fake_study_sites_response() + self.add_fake_users_response() + ss1 = StudySite.objects.create( + drupal_node_id=TEST_STUDY_SITE_DATA[1].drupal_internal__nid, + short_name=TEST_STUDY_SITE_DATA[1].title, + full_name=TEST_STUDY_SITE_DATA[1].field_long_name, + ) + drupal_fullname = "{} {}".format( + TEST_USER_DATA[0].field_given_first_name_s_, + TEST_USER_DATA[0].field_examples_family_last_name_, + ) + drupal_username = TEST_USER_DATA[0].name + drupal_email = TEST_USER_DATA[0].mail + new_user = get_user_model().objects.create( + username=drupal_username + "UPDATE", + email=drupal_email + "UPDATE", + name=drupal_fullname + "UPDATE", + ) + new_user.study_sites.add(ss1) + SocialAccount.objects.create( + user=new_user, + uid=TEST_USER_DATA[0].drupal_internal__uid, + provider=CustomProvider.id, + ) + results = drupal_data_user_audit(apply_changes=True) + assert results.encountered_issues() is True + issue_rows = results.rows_by_result_type(results.RESULT_TYPE_ISSUE) + assert len(issue_rows) == 1 + assert issue_rows[0]["issue_type"] == results.ISSUE_TYPE_USER_REMOVED_FROM_SITE + new_user.refresh_from_db() + # assert we did not remove the site + assert ss1 in new_user.study_sites.all() + + @responses.activate + def test_user_audit_remove_site_act(self): + self.add_fake_token_response() + self.add_fake_study_sites_response() + self.add_fake_users_response() + ss1 = StudySite.objects.create( + drupal_node_id=TEST_STUDY_SITE_DATA[1].drupal_internal__nid, + short_name=TEST_STUDY_SITE_DATA[1].title, + full_name=TEST_STUDY_SITE_DATA[1].field_long_name, + ) + drupal_fullname = "{} {}".format( + TEST_USER_DATA[0].field_given_first_name_s_, + TEST_USER_DATA[0].field_examples_family_last_name_, + ) + drupal_username = TEST_USER_DATA[0].name + drupal_email = TEST_USER_DATA[0].mail + new_user = get_user_model().objects.create( + username=drupal_username + "UPDATE", + email=drupal_email + "UPDATE", + name=drupal_fullname + "UPDATE", + ) + new_user.study_sites.add(ss1) + SocialAccount.objects.create( + user=new_user, + uid=TEST_USER_DATA[0].drupal_internal__uid, + provider=CustomProvider.id, + ) + with self.settings(DRUPAL_DATA_AUDIT_REMOVE_USER_SITES=True): + results = drupal_data_user_audit(apply_changes=True) + assert results.encountered_issues() is False + + new_user.refresh_from_db() + # assert we did remove the site + assert ss1 not in new_user.study_sites.all() + @responses.activate def test_user_audit_change_user(self): self.add_fake_token_response() From 514425cb6af1683ba08fff5e93095a0162bc74bf Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 22 Mar 2024 16:14:41 -0700 Subject: [PATCH 13/60] Change intended_workspace_type to intended_usage For TemplateWorkspaces, remove the intended_workspace_type field and add an intended_usage field instead. This is helpful because a template workspace can be cloned to create one of a couple workspace types (eg, the genotype template is used for both CDSA and dbGaP). --- ...ended_workspace_type_add_intended_usage.py | 33 ++++++++ primed/miscellaneous_workspaces/models.py | 24 +----- .../tests/factories.py | 15 +--- .../tests/test_models.py | 79 +------------------ 4 files changed, 38 insertions(+), 113 deletions(-) create mode 100644 primed/miscellaneous_workspaces/migrations/0011_alter_templateworkspace_remove_intended_workspace_type_add_intended_usage.py diff --git a/primed/miscellaneous_workspaces/migrations/0011_alter_templateworkspace_remove_intended_workspace_type_add_intended_usage.py b/primed/miscellaneous_workspaces/migrations/0011_alter_templateworkspace_remove_intended_workspace_type_add_intended_usage.py new file mode 100644 index 00000000..f632dc22 --- /dev/null +++ b/primed/miscellaneous_workspaces/migrations/0011_alter_templateworkspace_remove_intended_workspace_type_add_intended_usage.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.11 on 2024-03-22 22:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("miscellaneous_workspaces", "0010_update_workspace_type_field"), + ] + + operations = [ + migrations.RemoveField( + model_name="historicaltemplateworkspace", + name="intended_workspace_type", + ), + migrations.RemoveField( + model_name="templateworkspace", + name="intended_workspace_type", + ), + migrations.AddField( + model_name="historicaltemplateworkspace", + name="intended_usage", + field=models.TextField(default=""), + preserve_default=False, + ), + migrations.AddField( + model_name="templateworkspace", + name="intended_usage", + field=models.TextField(default=""), + preserve_default=False, + ), + ] diff --git a/primed/miscellaneous_workspaces/models.py b/primed/miscellaneous_workspaces/models.py index 210bae3b..d702122d 100644 --- a/primed/miscellaneous_workspaces/models.py +++ b/primed/miscellaneous_workspaces/models.py @@ -1,6 +1,5 @@ """Model definitions for the `miscellaneous_workspaces` app.""" -from anvil_consortium_manager.adapters.workspace import workspace_adapter_registry from anvil_consortium_manager.models import BaseWorkspaceData, Workspace from django.core.exceptions import ValidationError from django.db import models @@ -24,28 +23,7 @@ class ResourceWorkspace(RequesterModel, TimeStampedModel, BaseWorkspaceData): class TemplateWorkspace(TimeStampedModel, BaseWorkspaceData): """A model to track template workspaces.""" - intended_workspace_type = models.CharField(max_length=63) - - def clean(self): - """Custom cleaning checks. - - - Verify that intended_workspace_type is one of the registered types, excluding this type.""" - registered_workspace_types = workspace_adapter_registry.get_registered_names() - if self.intended_workspace_type: - if self.intended_workspace_type not in registered_workspace_types: - raise ValidationError( - { - "intended_workspace_type": "intended_workspace_type must be one of the registered types." - } - ) - # We cannot import the adapter here because it would lead to a circular import, but we don't want - # to create a template workspace for TemplateWorkspaces. So check if the type is not "template". - elif self.intended_workspace_type == "template": - raise ValidationError( - { - "intended_workspace_type": "intended_workspace_type may not be 'template'." - } - ) + intended_usage = models.TextField() class OpenAccessWorkspace(RequesterModel, TimeStampedModel, BaseWorkspaceData): diff --git a/primed/miscellaneous_workspaces/tests/factories.py b/primed/miscellaneous_workspaces/tests/factories.py index c562c36a..a68d9e54 100644 --- a/primed/miscellaneous_workspaces/tests/factories.py +++ b/primed/miscellaneous_workspaces/tests/factories.py @@ -1,8 +1,5 @@ -import random - -from anvil_consortium_manager.adapters.workspace import workspace_adapter_registry from anvil_consortium_manager.tests.factories import WorkspaceFactory -from factory import Faker, SubFactory, lazy_attribute +from factory import Faker, SubFactory from factory.django import DjangoModelFactory from primed.users.tests.factories import UserFactory @@ -53,19 +50,11 @@ class TemplateWorkspaceFactory(DjangoModelFactory): WorkspaceFactory, workspace_type=adapters.TemplateWorkspaceAdapter().get_type(), ) + intended_usage = Faker("sentence") class Meta: model = models.TemplateWorkspace - @lazy_attribute - def intended_workspace_type(self): - """Select a random registered workspace_type other than template.""" - registered_types = list( - workspace_adapter_registry.get_registered_adapters().keys() - ) - registered_types.remove(adapters.TemplateWorkspaceAdapter().get_type()) - return random.choice(registered_types) - class OpenAccessWorkspaceFactory(DjangoModelFactory): """A factory for the OpenAccessWorkspace model.""" diff --git a/primed/miscellaneous_workspaces/tests/test_models.py b/primed/miscellaneous_workspaces/tests/test_models.py index 7b38836c..c8222656 100644 --- a/primed/miscellaneous_workspaces/tests/test_models.py +++ b/primed/miscellaneous_workspaces/tests/test_models.py @@ -7,7 +7,7 @@ from primed.primed_anvil.tests.factories import AvailableDataFactory, StudyFactory from primed.users.tests.factories import UserFactory -from .. import adapters, models +from .. import models from . import factories @@ -79,7 +79,7 @@ class TemplateWorkspaceTest(TestCase): def test_model_saving(self): """Creation using the model constructor and .save() works.""" workspace = WorkspaceFactory.create() - instance = models.TemplateWorkspace(workspace=workspace) + instance = models.TemplateWorkspace(workspace=workspace, intended_usage="Test") instance.save() self.assertIsInstance(instance, models.TemplateWorkspace) @@ -91,81 +91,6 @@ def test_str_method(self): self.assertIsInstance(str(instance), str) self.assertEqual(str(instance), "test-bp/test-ws") - def test_clean_missing_intended_workspace_type_missing(self): - workspace = WorkspaceFactory.create( - billing_project__name="test-bp", name="test-ws" - ) - instance = models.TemplateWorkspace(workspace=workspace) - with self.assertRaises(ValidationError) as e: - instance.full_clean() - self.assertEqual(len(e.exception.message_dict), 1) - self.assertIn("intended_workspace_type", e.exception.message_dict) - self.assertEqual(len(e.exception.message_dict["intended_workspace_type"]), 1) - self.assertIn( - "cannot be blank", e.exception.message_dict["intended_workspace_type"][0] - ) - - def test_clean_intended_workspace_type_blank(self): - workspace = WorkspaceFactory.create( - billing_project__name="test-bp", name="test-ws" - ) - instance = factories.TemplateWorkspaceFactory.build( - workspace=workspace, - intended_workspace_type="", - ) - with self.assertRaises(ValidationError) as e: - instance.full_clean() - self.assertEqual(len(e.exception.message_dict), 1) - self.assertIn("intended_workspace_type", e.exception.message_dict) - self.assertEqual(len(e.exception.message_dict["intended_workspace_type"]), 1) - self.assertIn( - "cannot be blank", e.exception.message_dict["intended_workspace_type"][0] - ) - - def test_clean_intended_workspace_type_with_registered_adapter(self): - """No ValidationError is raised if intended_workspace_type is a registered type.""" - workspace = WorkspaceFactory.create( - billing_project__name="test-bp", name="test-ws" - ) - instance = factories.TemplateWorkspaceFactory.build(workspace=workspace) - instance.full_clean() - - def test_clean_intended_workspace_type_with_unregistered_adapter(self): - """ValidationError is raised if intended_workspace_type is not a registered type.""" - workspace = WorkspaceFactory.create( - billing_project__name="test-bp", name="test-ws" - ) - instance = factories.TemplateWorkspaceFactory.build( - workspace=workspace, intended_workspace_type="foo" - ) - with self.assertRaises(ValidationError) as e: - instance.full_clean() - self.assertEqual(len(e.exception.message_dict), 1) - self.assertIn("intended_workspace_type", e.exception.message_dict) - self.assertEqual(len(e.exception.message_dict["intended_workspace_type"]), 1) - self.assertIn( - "registered types", e.exception.message_dict["intended_workspace_type"][0] - ) - - def test_clean_intended_workspace_type_template(self): - """ValidationError is raised if intended_workspace_type is set to "template".""" - workspace = WorkspaceFactory.create( - billing_project__name="test-bp", name="test-ws" - ) - template_workspace_type = adapters.TemplateWorkspaceAdapter().get_type() - instance = factories.TemplateWorkspaceFactory.build( - workspace=workspace, intended_workspace_type=template_workspace_type - ) - with self.assertRaises(ValidationError) as e: - instance.full_clean() - self.assertEqual(len(e.exception.message_dict), 1) - self.assertIn("intended_workspace_type", e.exception.message_dict) - self.assertEqual(len(e.exception.message_dict["intended_workspace_type"]), 1) - self.assertIn( - template_workspace_type, - e.exception.message_dict["intended_workspace_type"][0], - ) - class OpenAccessWorkspaceTest(TestCase): """Tests for the OpenAccessWorkspace model.""" From a6b6396516bc4f5525cbf3e5aa8797f92474f5ca Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 22 Mar 2024 16:21:26 -0700 Subject: [PATCH 14/60] Update forms for TemplateWorkspace intended_usage --- primed/miscellaneous_workspaces/forms.py | 15 +---- .../tests/test_forms.py | 62 ++++--------------- 2 files changed, 12 insertions(+), 65 deletions(-) diff --git a/primed/miscellaneous_workspaces/forms.py b/primed/miscellaneous_workspaces/forms.py index 6a0825ac..31eb2e46 100644 --- a/primed/miscellaneous_workspaces/forms.py +++ b/primed/miscellaneous_workspaces/forms.py @@ -1,6 +1,5 @@ """Forms for the `workspaces` app.""" -from anvil_consortium_manager.adapters.workspace import workspace_adapter_registry from anvil_consortium_manager.forms import Bootstrap5MediaFormMixin from dal import autocomplete from django import forms @@ -62,23 +61,11 @@ class Meta: class TemplateWorkspaceForm(forms.ModelForm): """Form for a TemplateWorkspace object.""" - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # Set the intended_workspace_type options, excluding "template". - workspace_type_choices = [ - (key, value) - for key, value in workspace_adapter_registry.get_registered_names().items() - if key != "template" - ] - self.fields["intended_workspace_type"] = forms.ChoiceField( - choices=[("", "---------")] + workspace_type_choices - ) - class Meta: model = models.TemplateWorkspace fields = ( "workspace", - "intended_workspace_type", + "intended_usage", ) diff --git a/primed/miscellaneous_workspaces/tests/test_forms.py b/primed/miscellaneous_workspaces/tests/test_forms.py index 40b20431..b6dd125a 100644 --- a/primed/miscellaneous_workspaces/tests/test_forms.py +++ b/primed/miscellaneous_workspaces/tests/test_forms.py @@ -152,7 +152,7 @@ def test_valid(self): """Form is valid with necessary input.""" form_data = { "workspace": self.workspace, - "intended_workspace_type": "resource", + "intended_usage": "Test usage", } form = self.form_class(data=form_data) self.assertTrue(form.is_valid()) @@ -160,7 +160,7 @@ def test_valid(self): def test_invalid_missing_workspace(self): """Form is invalid when missing workspace.""" form_data = { - "intended_workspace_type": "resource", + "intended_usage": "Test usage", } form = self.form_class(data=form_data) self.assertFalse(form.is_valid()) @@ -169,7 +169,7 @@ def test_invalid_missing_workspace(self): self.assertEqual(len(form.errors["workspace"]), 1) self.assertIn("required", form.errors["workspace"][0]) - def test_invalid_missing_intended_workspace_type(self): + def test_invalid_missing_intended_usage(self): """Form is invalid if intended_workspace_type is missing.""" form_data = { "workspace": self.workspace, @@ -177,62 +177,22 @@ def test_invalid_missing_intended_workspace_type(self): form = self.form_class(data=form_data) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertIn("intended_workspace_type", form.errors) - self.assertEqual(len(form.errors["intended_workspace_type"]), 1) - self.assertIn("required", form.errors["intended_workspace_type"][0]) + self.assertIn("intended_usage", form.errors) + self.assertEqual(len(form.errors["intended_usage"]), 1) + self.assertIn("required", form.errors["intended_usage"][0]) - def test_invalid_blank_intended_workspace_type(self): + def test_invalid_blank_intended_usage(self): """Form is invalid if intended_workspace_type is missing.""" form_data = { "workspace": self.workspace, - "intended_workspace_type": "", + "intended_usage": "", } form = self.form_class(data=form_data) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertIn("intended_workspace_type", form.errors) - self.assertEqual(len(form.errors["intended_workspace_type"]), 1) - self.assertIn("required", form.errors["intended_workspace_type"][0]) - - def test_invalid_intended_workspace_type_template(self): - """Form is invalid if intended_workspace_type is "template".""" - form_data = { - "workspace": self.workspace, - "intended_workspace_type": "template", - } - form = self.form_class(data=form_data) - self.assertFalse(form.is_valid()) - self.assertEqual(len(form.errors), 1) - self.assertIn("intended_workspace_type", form.errors) - self.assertEqual(len(form.errors["intended_workspace_type"]), 1) - self.assertIn("template", form.errors["intended_workspace_type"][0]) - - def test_invalid_workspace_type_unregistered_type(self): - """Form is invalid if intended_workspace_type is not a registered type.""" - form_data = { - "workspace": self.workspace, - "intended_workspace_type": "foo", - } - form = self.form_class(data=form_data) - self.assertFalse(form.is_valid()) - self.assertEqual(len(form.errors), 1) - self.assertIn("intended_workspace_type", form.errors) - self.assertEqual(len(form.errors["intended_workspace_type"]), 1) - self.assertIn("valid choice", form.errors["intended_workspace_type"][0]) - - def test_form_all_registered_adapters(self): - """Form is invalid if intended_workspace_type is not a registered type.""" - workspace_types = list(workspace_adapter_registry.get_registered_names().keys()) - for workspace_type in workspace_types: - if workspace_type == "template": - pass - else: - form_data = { - "workspace": self.workspace, - "intended_workspace_type": workspace_type, - } - form = self.form_class(data=form_data) - self.assertTrue(form.is_valid()) + self.assertIn("intended_usage", form.errors) + self.assertEqual(len(form.errors["intended_usage"]), 1) + self.assertIn("required", form.errors["intended_usage"][0]) class OpenAccessWorkspaceFormTest(TestCase): From 4aebaf86696c8c5ade9ada46d72b65259c099dd5 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 22 Mar 2024 16:25:19 -0700 Subject: [PATCH 15/60] Update views for TemplateWorkspace intended_usage change --- primed/miscellaneous_workspaces/tests/test_views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/primed/miscellaneous_workspaces/tests/test_views.py b/primed/miscellaneous_workspaces/tests/test_views.py index ed75975b..6e8d5916 100644 --- a/primed/miscellaneous_workspaces/tests/test_views.py +++ b/primed/miscellaneous_workspaces/tests/test_views.py @@ -734,7 +734,7 @@ def test_creates_workspace(self): "workspacedata-INITIAL_FORMS": 0, "workspacedata-MIN_NUM_FORMS": 1, "workspacedata-MAX_NUM_FORMS": 1, - "workspacedata-0-intended_workspace_type": "resource", + "workspacedata-0-intended_usage": "Test usage", }, ) self.assertEqual(response.status_code, 302) @@ -744,7 +744,7 @@ def test_creates_workspace(self): self.assertEqual(models.TemplateWorkspace.objects.count(), 1) new_workspace_data = models.TemplateWorkspace.objects.latest("pk") self.assertEqual(new_workspace_data.workspace, new_workspace) - self.assertEqual(new_workspace_data.intended_workspace_type, "resource") + self.assertEqual(new_workspace_data.intended_usage, "Test usage") class TemplateWorkspaceImportTest(AnVILAPIMockTestMixin, TestCase): @@ -860,7 +860,7 @@ def test_creates_workspace(self): "workspacedata-INITIAL_FORMS": 0, "workspacedata-MIN_NUM_FORMS": 1, "workspacedata-MAX_NUM_FORMS": 1, - "workspacedata-0-intended_workspace_type": "resource", + "workspacedata-0-intended_usage": "Test usage", }, ) self.assertEqual(response.status_code, 302) @@ -870,7 +870,7 @@ def test_creates_workspace(self): self.assertEqual(models.TemplateWorkspace.objects.count(), 1) new_workspace_data = models.TemplateWorkspace.objects.latest("pk") self.assertEqual(new_workspace_data.workspace, new_workspace) - self.assertEqual(new_workspace_data.intended_workspace_type, "resource") + self.assertEqual(new_workspace_data.intended_usage, "Test usage") class OpenAccessWorkspaceDetailTest(TestCase): From ef0185451897f9ba5eec868a22fd3bcb29185b2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 03:08:35 +0000 Subject: [PATCH 16/60] Bump tablib from 3.5.0 to 3.6.0 Bumps [tablib](https://github.com/jazzband/tablib) from 3.5.0 to 3.6.0. - [Release notes](https://github.com/jazzband/tablib/releases) - [Changelog](https://github.com/jazzband/tablib/blob/master/HISTORY.md) - [Commits](https://github.com/jazzband/tablib/compare/v3.5.0...v3.6.0) --- updated-dependencies: - dependency-name: tablib dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 655a8085..da4315c3 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -202,7 +202,7 @@ sqlparse==0.4.4 # via # -r requirements/requirements.in # django -tablib==3.5.0 +tablib==3.6.0 # via -r requirements/requirements.in tenacity==8.2.3 # via plotly From 268a661a5d629fcecefeee5d59322905980ce517 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 1 Apr 2024 11:43:06 -0700 Subject: [PATCH 17/60] Add MariaDB 10.5 to CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8a8fd23..4570f6cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: # ahead of planned upgrades we can add versions as # needed python-version: [3.8] - mariadb-version: ["10.4"] + mariadb-version: ["10.4", "10.5"] services: mysql: From 673b9e9f00d132bc3b73667be25b0dd6e6de2beb Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Wed, 3 Apr 2024 12:46:10 -0700 Subject: [PATCH 18/60] Update to use generic PRIMEDAudit classes. Add support for inactive anvil accounts --- primed/users/audit.py | 751 ++++++++++-------- .../management/commands/sync-drupal-data.py | 66 +- primed/users/tests/test_audit.py | 213 ++--- 3 files changed, 553 insertions(+), 477 deletions(-) diff --git a/primed/users/audit.py b/primed/users/audit.py index 0c35983b..0004a5ce 100644 --- a/primed/users/audit.py +++ b/primed/users/audit.py @@ -1,361 +1,486 @@ import logging +from dataclasses import dataclass +import django_tables2 as tables import jsonapi_requests from allauth.socialaccount.models import SocialAccount +from anvil_consortium_manager.models import Account from django.conf import settings from django.contrib.auth import get_user_model from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Q +from django_tables2.export import TableExport from oauthlib.oauth2 import BackendApplicationClient from requests_oauthlib import OAuth2, OAuth2Session from primed.drupal_oauth_provider.provider import CustomProvider +from primed.primed_anvil.audit import PRIMEDAudit, PRIMEDAuditResult from primed.primed_anvil.models import StudySite logger = logging.getLogger(__name__) -class AuditResults: - def __init__(self): - self.results = [] - self.data_type = None - - # Data from api was not able to be handled - RESULT_TYPE_ISSUE = "issue" - # A new record was created during audit - RESULT_TYPE_NEW = "new" - # An existing record was updated - RESULT_TYPE_UPDATE = "update" - # A record was removed or deactivated - RESULT_TYPE_REMOVAL = "removed" - - ISSUE_TYPE_USER_INACTIVE = "user_inactive" - ISSUE_TYPE_USER_REMOVED_FROM_SITE = "user_site_removal" - - def add_new(self, data): - self.results.append( - { - "data_type": self.data_type, - "result_type": self.RESULT_TYPE_NEW, - "data": data, - } - ) +class TextTable(object): + def render_to_text(self): + return TableExport(export_format=TableExport.CSV, table=self).export() - def add_update(self, data, updates): - self.results.append( - { - "data_type": self.data_type, - "result_type": self.RESULT_TYPE_UPDATE, - "data": data, - "updates": updates, - } - ) - def add_issue(self, data, issue_type, issue_extra=None): - self.results.append( - { - "data_type": self.data_type, - "result_type": self.RESULT_TYPE_ISSUE, - "data": data, - "issue_type": issue_type, - "issue_extra": issue_extra, - } - ) +class UserAuditResultsTable(tables.Table, TextTable): + """A table to show results from a UserAudit instance.""" - def add_removal(self, data): - self.results.append( - { - "data_type": self.data_type, - "result_type": self.RESULT_TYPE_REMOVAL, - "data": data, - } - ) + result_type = tables.Column() + local_user_id = tables.Column() + local_username = tables.Column() + remote_user_id = tables.Column() + remote_username = tables.Column() + changes = tables.Column() + note = tables.Column() + anvil_groups = tables.Column() - def rows_by_result_type(self, result_type): - found = [] - for row in self.results: - if row["result_type"] == result_type: - found.append(row) - return found - def row_count_by_result_type(self, result_type): - return len(self.rows_by_result_type(result_type)) +@dataclass +class UserAuditResult(PRIMEDAuditResult): + local_user: SocialAccount = None + anvil_account: Account = None + remote_user_data: jsonapi_requests.JsonApiObject = None + note: str = None + changes: dict = None + anvil_groups: list = None - def count_new_rows(self): - return self.row_count_by_result_type(self.RESULT_TYPE_NEW) + def get_table_dictionary(self): + """Return a dictionary that can be used to populate an instance of `SiteAuditResultsTable`.""" - def count_update_rows(self): - return self.row_count_by_result_type(self.RESULT_TYPE_UPDATE) + row = { + "changes": self.changes, + "anvil_groups": self.anvil_groups, + "note": self.note, + "result_type": type(self).__name__, + } + if self.local_user: + row.update( + { + "local_user_id": self.local_user.user.id, + "local_username": self.local_user.user.username, + } + ) + if self.remote_user_data: + row.update( + { + "remote_user_id": self.remote_user_data.attributes.get( + "drupal_internal__uid" + ), + "remote_username": self.remote_user_data.attributes.get("name"), + } + ) + if self.anvil_account: + row.update( + { + "anvil_account": self.anvil_account, + "local_user_id": self.anvil_account.user.id, + } + ) + return row - def count_removal_rows(self): - return self.row_count_by_result_type(self.RESULT_TYPE_REMOVAL) - def count_issue_rows(self): - return self.row_count_by_result_type(self.RESULT_TYPE_ISSUE) +@dataclass +class VerifiedUser(UserAuditResult): + pass - def encountered_issues(self): - return self.count_issue_rows() > 0 - def __str__(self) -> str: - return ( - f"Audit for {self.data_type} " - f"Issues: {self.count_issue_rows()} " - f"New: {self.count_new_rows()} " - f"Updates: {self.count_update_rows()} " - f"Removals: {self.count_removal_rows()}" - ) +@dataclass +class NewUser(UserAuditResult): + pass - def display_result(self, result): - result_string = "" - result_type = result["result_type"] - result_string += f"{result['result_type']} {self.data_type}" - if self.data_type == "user": - if isinstance(result["data"], SocialAccount): - result_string += "\tUSERNAME: {}\tUID: {}".format( - result["data"].user.username, result["data"].uid - ) - else: - result_string += "\tUSERNAME: {}\tUID: {}".format( - result["data"].attributes["display_name"], - result["data"].attributes["drupal_internal__uid"], - ) - elif self.data_type == "site": - if isinstance(result["data"], StudySite): - result_string += "\tShortName: {}FullName: {}\tNodeID: {}".format( - result["data"].short_name, - result["data"].full_name, - result["data"].drupal_node_id, - ) - else: - result_string += "\tShortName: {}FullName: {}\tNodeID: {}".format( - result["data"]["short_name"], - result["data"]["full_name"], - result["data"]["node_id"], - ) - if result_type == self.RESULT_TYPE_UPDATE: - result_string += "\t{result.get('updates')}" - if result_type == self.RESULT_TYPE_ISSUE: - result_string += f"\tIssue Type: {result.get('issue_type')}" - return result_string - def detailed_issues(self): - result_string = "" - for row in self.rows_by_result_type(result_type=self.RESULT_TYPE_ISSUE): - result_string += self.display_result(row) - result_string += "\n" +@dataclass +class RemoveUser(UserAuditResult): + pass - return result_string - def detailed_results(self): - result_string = "" - for row in self.results: - result_string += self.display_result(row) - result_string += "\n" +@dataclass +class InactiveAnvilUser(UserAuditResult): + pass - return result_string +@dataclass +class UpdateUser(UserAuditResult): + pass -class UserAuditResults(AuditResults): - def __init__(self): - super().__init__() - self.data_type = "user" +class UserAudit(PRIMEDAudit): + ISSUE_TYPE_USER_INACTIVE = "User is inactive" + ISSUE_TYPE_USER_REMOVED_FROM_SITE = "User removed from site" + results_table_class = UserAuditResultsTable -class SiteAuditResults(AuditResults): - def __init__(self): + def __init__(self, apply_changes=False): + """Initialize the audit. + + Args: + apply_changes: Whether to make changes to align the audit + """ super().__init__() - self.data_type = "site" + self.apply_changes = apply_changes + + def _run_audit(self): + """Run the audit on local and remote users.""" + user_endpoint_url = "user/user" + drupal_uids = set() + json_api = get_drupal_json_api() + study_sites = get_study_sites(json_api) + + user_count = 0 + while user_endpoint_url is not None: + + users_endpoint = json_api.endpoint(user_endpoint_url) + users_endpoint_response = users_endpoint.get() + + # If there are more, there will be a 'next' link + + user_endpoint_url = users_endpoint_response.content.links.get( + "next", {} + ).get("href") + + for user in users_endpoint_response.data: + drupal_uid = user.attributes.get("drupal_internal__uid") + drupal_username = user.attributes.get("name") + drupal_email = user.attributes.get("mail") + drupal_firstname = user.attributes.get("field_given_first_name_s_") + drupal_lastname = user.attributes.get( + "field_examples_family_last_name_" + ) + drupal_full_name = " ".join( + part for part in (drupal_firstname, drupal_lastname) if part + ) + drupal_study_sites_rel = user.relationships.get( + "field_study_site_or_center" + ) + drupal_user_study_site_shortnames = [] + if drupal_study_sites_rel: + for dss in drupal_study_sites_rel.data: + study_site_uuid = dss.id + study_site_info = study_sites[study_site_uuid] + + drupal_user_study_site_shortnames.append( + study_site_info["short_name"] + ) + new_user_sites = StudySite.objects.filter( + short_name__in=drupal_user_study_site_shortnames + ) + # no uid is blocked or anonymous + if not drupal_uid: + # potential blocked user, but will no longer have a drupal uid + # so we cover these below + continue + sa = None + try: + sa = SocialAccount.objects.get( + uid=user.attributes["drupal_internal__uid"], + provider=CustomProvider.id, + ) + except ObjectDoesNotExist: + drupal_user = get_user_model()() + drupal_user.username = drupal_username + drupal_user.name = drupal_full_name + drupal_user.email = drupal_email + if self.apply_changes is True: + drupal_user.save() + drupal_user.study_sites.set(new_user_sites) + if self.apply_changes is True: + sa = SocialAccount.objects.create( + user=drupal_user, + uid=user.attributes["drupal_internal__uid"], + provider=CustomProvider.id, + ) + self.needs_action.append( + NewUser(local_user=sa, remote_user_data=user) + ) + + if sa: + user_updates = {} + if sa.user.name != drupal_full_name: + user_updates.update( + {"name": {"old": sa.user.name, "new": drupal_full_name}} + ) + sa.user.name = drupal_full_name + if sa.user.username != drupal_username: + user_updates.update( + { + "username": { + "old": sa.user.username, + "new": drupal_username, + } + } + ) + sa.user.username = drupal_username + if sa.user.email != drupal_email: + user_updates.update( + {"email": {"old": sa.user.email, "new": drupal_email}} + ) + sa.user.email = drupal_email + + prev_user_site_names = set( + sa.user.study_sites.all().values_list("short_name", flat=True) + ) + new_user_site_names = set(drupal_user_study_site_shortnames) + if prev_user_site_names != new_user_site_names: + + user_updates.update( + { + "sites": { + "old": prev_user_site_names, + "new": new_user_site_names, + } + } + ) + # do not remove from sites by default + removed_sites = prev_user_site_names.difference( + new_user_site_names + ) + new_sites = new_user_site_names.difference(prev_user_site_names) + + if settings.DRUPAL_DATA_AUDIT_REMOVE_USER_SITES is True: + if self.apply_changes is True: + sa.user.study_sites.set(new_user_sites) + else: + if removed_sites: + self.errors.append( + UpdateUser( + local_user=sa, + remote_user_data=user, + changes=user_updates, + ) + ) + if new_sites: + for new_site in new_user_sites: + if new_site.short_name in new_user_site_names: + if self.apply_changes is True: + sa.user.study_sites.add(new_site) + + if user_updates: + if self.apply_changes is True: + sa.user.save() + + self.needs_action.append( + UpdateUser( + local_user=sa, + remote_user_data=user, + changes=user_updates, + ) + ) + else: + self.verified.append( + VerifiedUser(local_user=sa, remote_user_data=user) + ) + + drupal_uids.add(drupal_uid) + user_count += 1 + + # find active django accounts that are drupal based + # users that we did not get from drupal + # these may include blocked users + + unaudited_drupal_accounts = SocialAccount.objects.filter( + provider=CustomProvider.id, user__is_active=True + ).exclude(uid__in=drupal_uids) + user_ids_to_check = [] + for uda in unaudited_drupal_accounts: + user_ids_to_check.append(uda.user.id) + if settings.DRUPAL_DATA_AUDIT_DEACTIVATE_USERS is True: + uda.user.is_active = False + if self.apply_changes is True: + uda.user.save() + self.needs_action.append(RemoveUser(local_user=uda)) + else: + self.errors.append( + RemoveUser(local_user=uda, note=self.ISSUE_TYPE_USER_INACTIVE) + ) + inactive_anvil_users = Account.objects.filter( + Q(user__is_active=False) | Q(user__id__in=user_ids_to_check), + groupaccountmembership__isnull=False, + ) + for inactive_anvil_user in inactive_anvil_users: + self.errors.append( + InactiveAnvilUser( + anvil_account=inactive_anvil_user, + anvil_groups=list( + inactive_anvil_user.groupaccountmembership_set.all().values_list( + "group__name", flat=True + ) + ), + ) + ) -def get_drupal_json_api(): - json_api_client_id = settings.DRUPAL_API_CLIENT_ID - json_api_client_secret = settings.DRUPAL_API_CLIENT_SECRET +class SiteAuditResultsTable(tables.Table, TextTable): + """A table to show results from a SiteAudit instance.""" - token_url = f"{settings.DRUPAL_SITE_URL}/oauth/token" - client = BackendApplicationClient(client_id=json_api_client_id) - oauth = OAuth2Session(client=client) - api_root = f"{settings.DRUPAL_SITE_URL}/{settings.DRUPAL_API_REL_PATH}" + result_type = tables.Column() + local_site_name = tables.Column() + remote_site_name = tables.Column() + changes = tables.Column() + note = tables.Column() - token = oauth.fetch_token( - token_url=token_url, - client_id=json_api_client_id, - client_secret=json_api_client_secret, - ) + def value_local_site_name(self, value): + return value - drupal_api = jsonapi_requests.Api.config( - { - "API_ROOT": api_root, - "AUTH": OAuth2(client=client, client_id=json_api_client_id, token=token), - "VALIDATE_SSL": True, + +@dataclass +class SiteAuditResult(PRIMEDAuditResult): + local_site: StudySite + remote_site_data: jsonapi_requests.JsonApiObject = None + changes: dict = None + note: str = None + + def get_table_dictionary(self): + """Return a dictionary that can be used to populate an instance of `SiteAuditResultsTable`.""" + row = { + "changes": self.changes, + "note": self.note, + "result_type": type(self).__name__, } - ) - return drupal_api + if self.local_site: + row.update( + { + "local_site_name": self.local_site.short_name, + } + ) + if self.remote_site_data: + row.update( + { + "remote_site_name": self.remote_site_data.get("short_name"), + } + ) + return row -def drupal_data_study_site_audit(apply_changes=False): - json_api = get_drupal_json_api() - study_sites = get_study_sites(json_api) - status = audit_drupal_study_sites( - study_sites=study_sites, apply_changes=apply_changes - ) - return status +@dataclass +class VerifiedSite(SiteAuditResult): + pass -def drupal_data_user_audit(apply_changes=False): - json_api = get_drupal_json_api() - study_sites = get_study_sites(json_api=json_api) - status = audit_drupal_users( - study_sites=study_sites, apply_changes=apply_changes, json_api=json_api - ) - return status +@dataclass +class NewSite(SiteAuditResult): + pass -def audit_drupal_users(study_sites, json_api, apply_changes=False): +@dataclass +class RemoveSite(SiteAuditResult): + pass - audit_results = UserAuditResults() - user_endpoint_url = "user/user" - drupal_uids = set() +@dataclass +class UpdateSite(SiteAuditResult): + changes: dict - user_count = 0 - while user_endpoint_url is not None: - users_endpoint = json_api.endpoint(user_endpoint_url) - users_endpoint_response = users_endpoint.get() +class SiteAudit(PRIMEDAudit): + ISSUE_TYPE_LOCAL_SITE_INVALD = "Local site is invalid" + results_table_class = SiteAuditResultsTable - # If there are more, there will be a 'next' link + def __init__(self, apply_changes=False): + """Initialize the audit. - user_endpoint_url = users_endpoint_response.content.links.get("next", {}).get( - "href" - ) + Args: + apply_changes: Whether to make changes to align the audit + """ + super().__init__() + self.apply_changes = apply_changes + + def _run_audit(self): + """Run the audit on local and remote users.""" + valid_nodes = set() + json_api = get_drupal_json_api() + study_sites = get_study_sites(json_api=json_api) + for study_site_info in study_sites.values(): + + short_name = study_site_info["short_name"] + full_name = study_site_info["full_name"] + node_id = study_site_info["node_id"] + valid_nodes.add(node_id) - for user in users_endpoint_response.data: - drupal_uid = user.attributes.get("drupal_internal__uid") - drupal_username = user.attributes.get("name") - drupal_email = user.attributes.get("mail") - drupal_firstname = user.attributes.get("field_given_first_name_s_") - drupal_lastname = user.attributes.get("field_examples_family_last_name_") - drupal_full_name = " ".join( - part for part in (drupal_firstname, drupal_lastname) if part - ) - drupal_study_sites_rel = user.relationships.get( - "field_study_site_or_center" - ) - drupal_user_study_site_shortnames = [] - if drupal_study_sites_rel: - for dss in drupal_study_sites_rel.data: - study_site_uuid = dss.id - study_site_info = study_sites[study_site_uuid] - - drupal_user_study_site_shortnames.append( - study_site_info["short_name"] - ) - new_user_sites = StudySite.objects.filter( - short_name__in=drupal_user_study_site_shortnames - ) - # no uid is blocked or anonymous - if not drupal_uid: - # potential blocked user, but will no longer have a drupal uid - # so we cover these below - continue - sa = None try: - sa = SocialAccount.objects.get( - uid=user.attributes["drupal_internal__uid"], - provider=CustomProvider.id, - ) + study_site = StudySite.objects.get(drupal_node_id=node_id) except ObjectDoesNotExist: - drupal_user = get_user_model()() - drupal_user.username = drupal_username - drupal_user.name = drupal_full_name - drupal_user.email = drupal_email - if apply_changes is True: - drupal_user.save() - drupal_user.study_sites.set(new_user_sites) - if apply_changes is True: - sa = SocialAccount.objects.create( - user=drupal_user, - uid=user.attributes["drupal_internal__uid"], - provider=CustomProvider.id, + study_site = None + if self.apply_changes is True: + study_site = StudySite.objects.create( + drupal_node_id=node_id, + short_name=short_name, + full_name=full_name, ) - audit_results.add_new(data=user) + self.needs_action.append( + NewSite(remote_site_data=study_site_info, local_site=study_site) + ) + else: + study_site_updates = {} - if sa: - user_updates = {} - if sa.user.name != drupal_full_name: - user_updates.update( - {"name": {"old": sa.user.name, "new": drupal_full_name}} + if study_site.full_name != full_name: + study_site_updates.update( + {"full_name": {"old": study_site.full_name, "new": full_name}} ) - sa.user.name = drupal_full_name - if sa.user.username != drupal_username: - user_updates.update( - {"username": {"old": sa.user.username, "new": drupal_username}} - ) - sa.user.username = drupal_username - if sa.user.email != drupal_email: - user_updates.update( - {"email": {"old": sa.user.email, "new": drupal_email}} - ) - sa.user.email = drupal_email + study_site.full_name = full_name - prev_user_site_names = set( - sa.user.study_sites.all().values_list("short_name", flat=True) - ) - new_user_site_names = set(drupal_user_study_site_shortnames) - if prev_user_site_names != new_user_site_names: - user_updates.update( + if study_site.short_name != short_name: + study_site_updates.update( { - "sites": { - "old": prev_user_site_names, - "new": new_user_site_names, + "short_name": { + "old": study_site.short_name, + "new": short_name, } } ) - # do not remove from sites by default - removed_sites = prev_user_site_names.difference(new_user_sites) - new_sites = new_user_site_names.difference(prev_user_site_names) + study_site.short_name = short_name + + if study_site_updates: + if self.apply_changes is True: + study_site.save() + self.needs_action.append( + UpdateSite( + local_site=study_site, + remote_site_data=study_site_info, + changes=study_site_updates, + ) + ) + else: + self.verified.append( + VerifiedSite( + local_site=study_site, remote_site_data=study_site_info + ) + ) - if settings.DRUPAL_DATA_AUDIT_REMOVE_USER_SITES is True: - sa.user.study_sites.set(new_user_sites) - else: - if removed_sites: - audit_results.add_issue( - data=user, - issue_type=audit_results.ISSUE_TYPE_USER_REMOVED_FROM_SITE, - issue_extra=f"Removed Sites: {removed_sites}", - ) - if new_sites: - for new_site in new_user_sites: - if new_site.short_name in new_user_site_names: - sa.user.study_sites.add(new_site) - - if user_updates: - if apply_changes is True: - sa.user.save() - audit_results.add_update(data=user, updates=user_updates) - - drupal_uids.add(drupal_uid) - user_count += 1 - - # find active django accounts that are drupal based - # users that we did not get from drupal - # these may include blocked users - - unaudited_drupal_accounts = SocialAccount.objects.filter( - provider=CustomProvider.id, user__is_active=True - ).exclude(uid__in=drupal_uids) - - for uda in unaudited_drupal_accounts: - if settings.DRUPAL_DATA_AUDIT_DEACTIVATE_USERS is True: - uda.user.is_active = False - uda.user.save() - audit_results.add_removal(data=uda) - else: - audit_results.add_issue( - data=uda, issue_type=AuditResults.ISSUE_TYPE_USER_INACTIVE + invalid_study_sites = StudySite.objects.exclude(drupal_node_id__in=valid_nodes) + + for iss in invalid_study_sites: + self.errors.append( + RemoveSite(local_site=iss, note=self.ISSUE_TYPE_LOCAL_SITE_INVALD) ) - return audit_results + +def get_drupal_json_api(): + + json_api_client_id = settings.DRUPAL_API_CLIENT_ID + json_api_client_secret = settings.DRUPAL_API_CLIENT_SECRET + + token_url = f"{settings.DRUPAL_SITE_URL}/oauth/token" + client = BackendApplicationClient(client_id=json_api_client_id) + oauth = OAuth2Session(client=client) + api_root = f"{settings.DRUPAL_SITE_URL}/{settings.DRUPAL_API_REL_PATH}" + + token = oauth.fetch_token( + token_url=token_url, + client_id=json_api_client_id, + client_secret=json_api_client_secret, + ) + + drupal_api = jsonapi_requests.Api.config( + { + "API_ROOT": api_root, + "AUTH": OAuth2(client=client, client_id=json_api_client_id, token=token), + "VALIDATE_SSL": True, + } + ) + return drupal_api def get_study_sites(json_api): @@ -374,53 +499,3 @@ def get_study_sites(json_api): "full_name": full_name, } return study_sites_info - - -def audit_drupal_study_sites(study_sites, apply_changes=False): - - valid_nodes = set() - audit_results = SiteAuditResults() - - for study_site_info in study_sites.values(): - - short_name = study_site_info["short_name"] - full_name = study_site_info["full_name"] - node_id = study_site_info["node_id"] - valid_nodes.add(node_id) - - try: - study_site = StudySite.objects.get(drupal_node_id=node_id) - except ObjectDoesNotExist: - if apply_changes is True: - study_site = StudySite.objects.create( - drupal_node_id=node_id, short_name=short_name, full_name=full_name - ) - audit_results.add_new(data=study_site_info) - else: - study_site_updates = {} - - if study_site.full_name != full_name: - study_site_updates.update( - {"full_name": {"old": study_site.full_name, "new": full_name}} - ) - study_site.full_name = full_name - - if study_site.short_name != short_name: - study_site_updates.update( - {"short_name": {"old": study_site.short_name, "new": short_name}} - ) - study_site.short_name = short_name - - if study_site_updates: - if apply_changes is True: - study_site.save() - audit_results.add_update( - data=study_site_info, updates=study_site_updates - ) - - invalid_study_sites = StudySite.objects.exclude(drupal_node_id__in=valid_nodes) - - for iss in invalid_study_sites: - audit_results.add_issue(data=iss, issue_type="Local site not in drupal") - - return audit_results diff --git a/primed/users/management/commands/sync-drupal-data.py b/primed/users/management/commands/sync-drupal-data.py index ead59479..da71d802 100644 --- a/primed/users/management/commands/sync-drupal-data.py +++ b/primed/users/management/commands/sync-drupal-data.py @@ -1,8 +1,9 @@ import logging from django.core.management.base import BaseCommand +from django.utils.timezone import localtime -from primed.users.audit import drupal_data_study_site_audit, drupal_data_user_audit +from primed.users import audit logger = logging.getLogger(__name__) @@ -39,44 +40,39 @@ def add_arguments(self, parser): def handle(self, *args, **options): apply_changes = options.get("update") be_verbose = options.get("verbose") - notify_type = options.get("notify") + # notify_type = options.get("notify") + notification_content = f"Drupal data audit start: Applying Changes: {apply_changes} Start time: {localtime()}\n" + site_audit = audit.SiteAudit(apply_changes=apply_changes) + site_audit.run_audit() - site_audit_results = drupal_data_study_site_audit(apply_changes=apply_changes) - logger.info( - f"Site Audit (Update: {apply_changes}) Results summary: {site_audit_results}" + notification_content += ( + f"SiteAudit summary: status ok: {site_audit.ok()} verified: {len(site_audit.verified)} " + f"needs_changes: {len(site_audit.needs_action)} errors: {len(site_audit.errors)}\n" ) - detailed_site_audit_results = site_audit_results.detailed_results() - - user_audit_results = drupal_data_user_audit(apply_changes=apply_changes) - logger.info( - f"User Audit (Update: {apply_changes}) Results summary: {user_audit_results}" - ) - detailed_user_audit_results = user_audit_results.detailed_results() + if site_audit.needs_action: + notification_content += "Sites that need syncing:\n" + notification_content += site_audit.get_needs_action_table().render_to_text() + if site_audit.errors: + notification_content += "Sites requiring intervention:\n" + notification_content += site_audit.get_errors_table().render_to_text() if be_verbose: - logger.debug( - f"User Audit Results:\n{user_audit_results.detailed_results()}" - ) - logger.debug( - f"Study Site Audit Results:\n{site_audit_results.detailed_results()}" - ) - - notification_content = "" - if user_audit_results.encountered_issues(): - notification_content += "Encountered user audit issues:\n" - notification_content += user_audit_results.detailed_issues() - else: - notification_content += "No user audit issues.\n" + notification_content += site_audit.get_verified_table().render_to_text() - if site_audit_results.encountered_issues(): - notification_content += "Encountered site audit issues:\n" - notification_content += site_audit_results.detailed_issues() - else: - notification_content += "No site audit issues.\n" - - if notify_type == self.NOTIFY_ALL: - notification_content += detailed_site_audit_results - notification_content += detailed_user_audit_results - notification_content += "sync-drupal-data audit complete\n" + user_audit = audit.UserAudit(apply_changes=apply_changes) + user_audit.run_audit() + notification_content += ( + "--------------------------------------\n" + f"UserAudit summary: status ok: {user_audit.ok()} verified: {len(user_audit.verified)} " + f"needs_changes: {len(user_audit.needs_action)} errors: {len(user_audit.errors)}\n" + ) + if user_audit.needs_action: + notification_content += "Users that need syncing:\n" + notification_content += user_audit.get_needs_action_table().render_to_text() + if user_audit.errors: + notification_content += "Users that need intervention:\n" + notification_content += user_audit.get_errors_table().render_to_text() + if be_verbose: + notification_content += user_audit.get_verified_table().render_to_text() self.stdout.write(notification_content) diff --git a/primed/users/tests/test_audit.py b/primed/users/tests/test_audit.py index 7a2a7536..0c189f86 100644 --- a/primed/users/tests/test_audit.py +++ b/primed/users/tests/test_audit.py @@ -4,6 +4,11 @@ import responses from allauth.socialaccount.models import SocialAccount +from anvil_consortium_manager.models import ( + Account, + GroupAccountMembership, + ManagedGroup, +) from django.conf import settings from django.contrib.auth import get_user_model from django.core.management import call_command @@ -11,13 +16,7 @@ from marshmallow_jsonapi import Schema, fields from primed.drupal_oauth_provider.provider import CustomProvider -from primed.users.audit import ( - audit_drupal_study_sites, - drupal_data_study_site_audit, - drupal_data_user_audit, - get_drupal_json_api, - get_study_sites, -) +from primed.users import audit from primed.users.models import StudySite @@ -169,7 +168,7 @@ def add_fake_users_response(self): user_data = UserSchema( include_data=("field_study_site_or_center",), many=True ).dump(TEST_USER_DATA) - print(f"USER DATA: {user_data}") + responses.get( url=url_path, body=json.dumps(user_data), @@ -181,7 +180,7 @@ def add_fake_token_response(self): def get_fake_json_api(self): self.add_fake_token_response() - return get_drupal_json_api() + return audit.get_drupal_json_api() @responses.activate def test_get_json_api(self): @@ -195,7 +194,7 @@ def test_get_json_api(self): def test_get_study_sites(self): json_api = self.get_fake_json_api() self.add_fake_study_sites_response() - study_sites = get_study_sites(json_api=json_api) + study_sites = audit.get_study_sites(json_api=json_api) for test_study_site in TEST_STUDY_SITE_DATA: @@ -214,37 +213,35 @@ def test_get_study_sites(self): @responses.activate def test_audit_study_sites_no_update(self): - json_api = self.get_fake_json_api() - self.add_fake_study_sites_response() - study_sites = get_study_sites(json_api=json_api) - audit_results = audit_drupal_study_sites( - study_sites=study_sites, apply_changes=False - ) - assert audit_results.encountered_issues() is False - assert StudySite.objects.all().count() == 0 - - @responses.activate - def test_full_site_audit(self): - self.add_fake_token_response() + self.get_fake_json_api() self.add_fake_study_sites_response() - results = drupal_data_study_site_audit() - assert results.encountered_issues() is False + site_audit = audit.SiteAudit(apply_changes=False) + site_audit.run_audit() + self.assertFalse(site_audit.ok()) + self.assertEqual(len(site_audit.errors), 0) + self.assertEqual(len(site_audit.needs_action), 2) + self.assertEqual(StudySite.objects.all().count(), 0) @responses.activate def test_audit_study_sites_with_new_sites(self): - json_api = self.get_fake_json_api() + self.get_fake_json_api() self.add_fake_study_sites_response() - study_sites = get_study_sites(json_api=json_api) - audit_results = audit_drupal_study_sites( - study_sites=study_sites, apply_changes=True - ) - assert audit_results.encountered_issues() is False - assert audit_results.count_new_rows() == 2 - assert StudySite.objects.all().count() == 2 - assert StudySite.objects.filter( - short_name=TEST_STUDY_SITE_DATA[0].title - ).exists() - self.assertRegex(audit_results.detailed_results(), "^new site") + site_audit = audit.SiteAudit(apply_changes=True) + site_audit.run_audit() + self.assertFalse(site_audit.ok()) + self.assertEqual(len(site_audit.needs_action), 2) + self.assertEqual(StudySite.objects.all().count(), 2) + + assert ( + StudySite.objects.filter( + short_name__in=[ + TEST_STUDY_SITE_DATA[0].title, + TEST_STUDY_SITE_DATA[1].title, + ] + ).count() + == 2 + ) + assert len(site_audit.get_needs_action_table().rows) == 2 @responses.activate def test_audit_study_sites_with_site_update(self): @@ -253,16 +250,21 @@ def test_audit_study_sites_with_site_update(self): short_name="WrongShortName", full_name="WrongTitle", ) - json_api = self.get_fake_json_api() + StudySite.objects.create( + drupal_node_id=TEST_STUDY_SITE_DATA[1].drupal_internal__nid, + short_name=TEST_STUDY_SITE_DATA[1].title, + full_name=TEST_STUDY_SITE_DATA[1].field_long_name, + ) + self.get_fake_json_api() self.add_fake_study_sites_response() - study_sites = get_study_sites(json_api=json_api) - audit_results = audit_drupal_study_sites( - study_sites=study_sites, apply_changes=True - ) - assert audit_results.encountered_issues() is False - assert audit_results.count_new_rows() == 1 - assert audit_results.count_update_rows() == 1 - assert StudySite.objects.all().count() == 2 + site_audit = audit.SiteAudit(apply_changes=True) + site_audit.run_audit() + self.assertFalse(site_audit.ok()) + self.assertEqual(len(site_audit.needs_action), 1) + self.assertEqual(len(site_audit.verified), 1) + self.assertEqual(len(site_audit.errors), 0) + self.assertEqual(StudySite.objects.all().count(), 2) + first_test_ss = StudySite.objects.get(short_name=TEST_STUDY_SITE_DATA[0].title) # did we update the long name assert first_test_ss.full_name == TEST_STUDY_SITE_DATA[0].field_long_name @@ -273,13 +275,14 @@ def test_audit_study_sites_with_extra_site(self): StudySite.objects.create( drupal_node_id=99, short_name="ExtraSite", full_name="ExtraSiteLong" ) - json_api = self.get_fake_json_api() + self.get_fake_json_api() self.add_fake_study_sites_response() - study_sites = get_study_sites(json_api=json_api) - audit_results = audit_drupal_study_sites( - study_sites=study_sites, apply_changes=True - ) - assert audit_results.encountered_issues() is True + site_audit = audit.SiteAudit(apply_changes=True) + site_audit.run_audit() + self.assertFalse(site_audit.ok()) + self.assertEqual(len(site_audit.errors), 1) + self.assertEqual(StudySite.objects.all().count(), 3) + assert len(site_audit.get_errors_table().rows) == 1 @responses.activate def test_full_user_audit(self): @@ -291,12 +294,11 @@ def test_full_user_audit(self): short_name=TEST_STUDY_SITE_DATA[0].title, full_name=TEST_STUDY_SITE_DATA[0].field_long_name, ) - results = drupal_data_user_audit(apply_changes=True) + user_audit = audit.UserAudit(apply_changes=True) + user_audit.run_audit() - assert results.encountered_issues() is False - assert results.count_new_rows() == 1 - assert results.count_update_rows() == 0 - assert results.count_removal_rows() == 0 + self.assertFalse(user_audit.ok()) + self.assertEqual(len(user_audit.needs_action), 1) users = get_user_model().objects.all() assert users.count() == 1 @@ -308,7 +310,6 @@ def test_full_user_audit(self): users.first().study_sites.first().short_name == TEST_STUDY_SITE_DATA[0].title ) - self.assertRegex(results.detailed_results(), "^new user") @responses.activate def test_full_user_audit_check_only(self): @@ -320,12 +321,10 @@ def test_full_user_audit_check_only(self): short_name=TEST_STUDY_SITE_DATA[0].title, full_name=TEST_STUDY_SITE_DATA[0].field_long_name, ) - results = drupal_data_user_audit(apply_changes=False) - - assert results.encountered_issues() is False - assert results.count_new_rows() == 1 - assert results.count_update_rows() == 0 - assert results.count_removal_rows() == 0 + user_audit = audit.UserAudit(apply_changes=False) + user_audit.run_audit() + self.assertFalse(user_audit.ok()) + self.assertEqual(len(user_audit.needs_action), 1) # verify we did not actually create a user users = get_user_model().objects.all() @@ -358,11 +357,11 @@ def test_user_audit_remove_site_inform(self): uid=TEST_USER_DATA[0].drupal_internal__uid, provider=CustomProvider.id, ) - results = drupal_data_user_audit(apply_changes=True) - assert results.encountered_issues() is True - issue_rows = results.rows_by_result_type(results.RESULT_TYPE_ISSUE) - assert len(issue_rows) == 1 - assert issue_rows[0]["issue_type"] == results.ISSUE_TYPE_USER_REMOVED_FROM_SITE + user_audit = audit.UserAudit(apply_changes=False) + user_audit.run_audit() + self.assertFalse(user_audit.ok()) + self.assertEqual(len(user_audit.errors), 1) + new_user.refresh_from_db() # assert we did not remove the site assert ss1 in new_user.study_sites.all() @@ -395,9 +394,9 @@ def test_user_audit_remove_site_act(self): provider=CustomProvider.id, ) with self.settings(DRUPAL_DATA_AUDIT_REMOVE_USER_SITES=True): - results = drupal_data_user_audit(apply_changes=True) - assert results.encountered_issues() is False - + user_audit = audit.UserAudit(apply_changes=True) + user_audit.run_audit() + self.assertFalse(user_audit.ok()) new_user.refresh_from_db() # assert we did remove the site assert ss1 not in new_user.study_sites.all() @@ -428,15 +427,13 @@ def test_user_audit_change_user(self): uid=TEST_USER_DATA[0].drupal_internal__uid, provider=CustomProvider.id, ) - results = drupal_data_user_audit(apply_changes=True) + user_audit = audit.UserAudit(apply_changes=True) + user_audit.run_audit() + self.assertFalse(user_audit.ok()) new_user.refresh_from_db() - assert new_user.name == drupal_fullname - assert results.encountered_issues() is False - assert results.count_new_rows() == 0 - assert results.count_update_rows() == 1 - assert results.count_removal_rows() == 0 - self.assertRegex(results.detailed_results(), "^update user") + self.assertEqual(new_user.name, drupal_fullname) + self.assertEqual(len(user_audit.needs_action), 1) # test user removal @responses.activate @@ -458,21 +455,12 @@ def test_user_audit_remove_user_only_inform(self): uid=999, provider=CustomProvider.id, ) - results = drupal_data_user_audit(apply_changes=True) + user_audit = audit.UserAudit(apply_changes=True) + user_audit.run_audit() + self.assertFalse(user_audit.ok()) new_user.refresh_from_db() - assert new_user.is_active is True - assert results.encountered_issues() is True - assert results.count_new_rows() == 1 - assert results.count_update_rows() == 0 - assert results.count_removal_rows() == 0 - assert results.count_issue_rows() == 1 - issue_rows = results.rows_by_result_type(results.RESULT_TYPE_ISSUE) - assert len(issue_rows) == 1 - assert issue_rows[0]["issue_type"] == results.ISSUE_TYPE_USER_INACTIVE - # assert not empty - assert results.detailed_issues() - self.assertRegex(str(results), "Issues: 1") + self.assertTrue(new_user.is_active) # test user removal @responses.activate @@ -494,26 +482,44 @@ def test_user_audit_remove_user(self): uid=999, provider=CustomProvider.id, ) - with self.settings(DRUPAL_DATA_AUDIT_DEACTIVATE_USERS=True): - results = drupal_data_user_audit(apply_changes=True) + new_anvil_account = Account.objects.create( + user=new_user, + is_service_account=False, + ) + new_anvil_managed_group = ManagedGroup.objects.create( + name="testgroup", + email="testgroup@testgroup.org", + ) + GroupAccountMembership.objects.create( + group=new_anvil_managed_group, + account=new_anvil_account, + role=GroupAccountMembership.MEMBER, + ) + with self.settings(DRUPAL_DATA_AUDIT_DEACTIVATE_USERS=True): + user_audit = audit.UserAudit(apply_changes=True) + user_audit.run_audit() + self.assertFalse(user_audit.ok()) + self.assertEqual(len(user_audit.errors), 1) + self.assertEqual(user_audit.errors[0].anvil_account, new_anvil_account) + self.assertIn( + "InactiveAnvilUser", user_audit.get_errors_table().render_to_text() + ) + self.assertEqual(len(user_audit.needs_action), 2) new_user.refresh_from_db() - assert new_user.is_active is False - assert results.encountered_issues() is False - assert results.count_new_rows() == 1 - assert results.count_update_rows() == 0 - assert results.count_removal_rows() == 1 + self.assertFalse(new_user.is_active) @responses.activate def test_sync_drupal_data_command(self): - self.add_fake_token_response() - self.add_fake_study_sites_response() self.add_fake_token_response() self.add_fake_study_sites_response() self.add_fake_users_response() out = StringIO() call_command("sync-drupal-data", stdout=out) - self.assertIn("sync-drupal-data audit complete", out.getvalue()) + self.assertIn( + "SiteAudit summary: status ok: False verified: 0 needs_changes: 2", + out.getvalue(), + ) @responses.activate def test_sync_drupal_data_command_with_issues(self): @@ -534,9 +540,8 @@ def test_sync_drupal_data_command_with_issues(self): ) self.add_fake_token_response() self.add_fake_study_sites_response() - self.add_fake_token_response() - self.add_fake_study_sites_response() self.add_fake_users_response() out = StringIO() call_command("sync-drupal-data", "--verbose", stdout=out) - self.assertIn("sync-drupal-data audit complete", out.getvalue()) + self.assertIn("SiteAudit summary: status ok: False", out.getvalue()) + self.assertIn("UserAudit summary: status ok: False", out.getvalue()) From 50e91ad06bbca1bf69a891e77b0d88730068f2e1 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 5 Apr 2024 15:41:32 -0700 Subject: [PATCH 19/60] Consolidate dbGaP workspace user and staff tables Instead of redefining most columns in both tables, subclass the user table to create the staff table, and define or redefine any columns that need to be added or changed. --- primed/dbgap/tables.py | 49 +++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/primed/dbgap/tables.py b/primed/dbgap/tables.py index 08443706..836a1d33 100644 --- a/primed/dbgap/tables.py +++ b/primed/dbgap/tables.py @@ -74,11 +74,11 @@ 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) + billing_project = tables.Column() dbgap_accession = dbGaPAccessionColumn( accessor="dbgapworkspace__get_dbgap_accession", dbgap_link_accessor="dbgapworkspace__get_dbgap_link", @@ -91,11 +91,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" ) @@ -108,40 +103,21 @@ 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 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 +126,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.""" From d5cebeba85b185915418f8a483209f1fd659b750 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 5 Apr 2024 15:58:47 -0700 Subject: [PATCH 20/60] Consolidate CDSA workspace staff and user tables Same as dbGaP consolidation. --- primed/cdsa/tables.py | 37 ++++--------------------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/primed/cdsa/tables.py b/primed/cdsa/tables.py index 4fd93ce1..2def3972 100644 --- a/primed/cdsa/tables.py +++ b/primed/cdsa/tables.py @@ -299,11 +299,11 @@ 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) + billing_project = tables.Column() cdsaworkspace__data_use_permission__abbreviation = tables.Column( verbose_name="DUO permission", linkify=lambda record: record.cdsaworkspace.data_use_permission.get_absolute_url(), @@ -351,27 +351,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 +368,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'') From fcf15af396e4fa38030187e457556ddbb3cdd21a Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 5 Apr 2024 16:01:28 -0700 Subject: [PATCH 21/60] Consolidate Collaborative analysis workspace tables Same as dbGaP consolidation. --- primed/collaborative_analysis/tables.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/primed/collaborative_analysis/tables.py b/primed/collaborative_analysis/tables.py index a17c5cb1..0f6eada0 100644 --- a/primed/collaborative_analysis/tables.py +++ b/primed/collaborative_analysis/tables.py @@ -2,12 +2,11 @@ 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) + billing_project = tables.Column() number_source_workspaces = tables.columns.Column( accessor="pk", verbose_name="Number of source workspaces", @@ -29,16 +28,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 +44,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() From e0c42ef7a120dad0a3961af4df8684444f3d86f6 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 5 Apr 2024 16:05:05 -0700 Subject: [PATCH 22/60] Consolidate open access workspace tables --- primed/miscellaneous_workspaces/tables.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/primed/miscellaneous_workspaces/tables.py b/primed/miscellaneous_workspaces/tables.py index fd1132df..069bfd13 100644 --- a/primed/miscellaneous_workspaces/tables.py +++ b/primed/miscellaneous_workspaces/tables.py @@ -9,12 +9,15 @@ ) -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) + billing_project = tables.Column() is_shared = WorkspaceSharedWithConsortiumColumn() + openaccessworkspace__studies = tables.ManyToManyColumn( + linkify_item=True, + ) class Meta: model = Workspace @@ -27,12 +30,10 @@ class Meta: 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 From 1c73d71bcd79cdac49b05f613cf850a9e3e8a502 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 5 Apr 2024 16:08:15 -0700 Subject: [PATCH 23/60] Consolidate default workspace tables --- primed/primed_anvil/tables.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) 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",) From b77047892fff9a5c2dd21e4b5d3f900c64fe5330 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 5 Apr 2024 16:08:36 -0700 Subject: [PATCH 24/60] Remove billing project from user workspace tables View users likely don't need to see that infomration, and it is available if needed on the workspace detail page. Staff users may want to see it in the tables. --- primed/cdsa/tables.py | 2 -- primed/collaborative_analysis/tables.py | 2 -- primed/dbgap/tables.py | 2 -- primed/miscellaneous_workspaces/tables.py | 2 -- 4 files changed, 8 deletions(-) diff --git a/primed/cdsa/tables.py b/primed/cdsa/tables.py index 2def3972..f39d6988 100644 --- a/primed/cdsa/tables.py +++ b/primed/cdsa/tables.py @@ -303,7 +303,6 @@ class CDSAWorkspaceUserTable(tables.Table): """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", 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", diff --git a/primed/collaborative_analysis/tables.py b/primed/collaborative_analysis/tables.py index 0f6eada0..dfaeddb7 100644 --- a/primed/collaborative_analysis/tables.py +++ b/primed/collaborative_analysis/tables.py @@ -6,7 +6,6 @@ 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() number_source_workspaces = tables.columns.Column( accessor="pk", verbose_name="Number of source workspaces", @@ -17,7 +16,6 @@ class Meta: model = Workspace fields = ( "name", - "billing_project", "collaborativeanalysisworkspace__custodian", "number_source_workspaces", ) diff --git a/primed/dbgap/tables.py b/primed/dbgap/tables.py index 836a1d33..fa22c5b1 100644 --- a/primed/dbgap/tables.py +++ b/primed/dbgap/tables.py @@ -78,7 +78,6 @@ 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() dbgap_accession = dbGaPAccessionColumn( accessor="dbgapworkspace__get_dbgap_accession", dbgap_link_accessor="dbgapworkspace__get_dbgap_link", @@ -100,7 +99,6 @@ class Meta: model = Workspace fields = ( "name", - "billing_project", "dbgap_accession", "dbgapworkspace__dbgap_consent_abbreviation", "dbgapworkspace__gsr_restricted", diff --git a/primed/miscellaneous_workspaces/tables.py b/primed/miscellaneous_workspaces/tables.py index 069bfd13..0e173c3e 100644 --- a/primed/miscellaneous_workspaces/tables.py +++ b/primed/miscellaneous_workspaces/tables.py @@ -13,7 +13,6 @@ 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() is_shared = WorkspaceSharedWithConsortiumColumn() openaccessworkspace__studies = tables.ManyToManyColumn( linkify_item=True, @@ -23,7 +22,6 @@ class Meta: model = Workspace fields = ( "name", - "billing_project", "openaccessworkspace__studies", "is_shared", ) From 6b95992f4c66c8d30b1e2c144db641ec8c2e5c1d Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 5 Apr 2024 16:15:06 -0700 Subject: [PATCH 25/60] Split data prep workspace table into staff and user tables --- primed/cdsa/adapters.py | 4 +-- primed/cdsa/tests/test_views.py | 4 +-- primed/dbgap/adapters.py | 4 +-- primed/dbgap/tests/test_views.py | 4 +-- primed/miscellaneous_workspaces/adapters.py | 4 +-- primed/miscellaneous_workspaces/tables.py | 19 +++++++++++-- .../tests/test_tables.py | 27 ++++++++++++++++--- 7 files changed, 51 insertions(+), 15 deletions(-) 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/tests/test_views.py b/primed/cdsa/tests/test_views.py index beb8e4e8..d22e7a6f 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, @@ -7443,7 +7443,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/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/tests/test_views.py b/primed/dbgap/tests/test_views.py index c33fb131..3c7df87a 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, @@ -960,7 +960,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/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 0e173c3e..014f746c 100644 --- a/primed/miscellaneous_workspaces/tables.py +++ b/primed/miscellaneous_workspaces/tables.py @@ -44,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" ) @@ -64,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")) From 19d49d0b168e58f20c177c89b4b3082f3e63946f Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 5 Apr 2024 16:46:05 -0700 Subject: [PATCH 26/60] Only show associated data prep workspace table to staff users Regular view users do not see the associated data prep workspace tables on the dbGaP and CDSA workspace detail pages. --- primed/cdsa/tests/test_views.py | 23 +++++++++++++++++++ primed/dbgap/tests/test_views.py | 23 +++++++++++++++++++ .../snippets/data_prep_workspace_table.html | 4 ++++ 3 files changed, 50 insertions(+) diff --git a/primed/cdsa/tests/test_views.py b/primed/cdsa/tests/test_views.py index d22e7a6f..ab203d0a 100644 --- a/primed/cdsa/tests/test_views.py +++ b/primed/cdsa/tests/test_views.py @@ -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) diff --git a/primed/dbgap/tests/test_views.py b/primed/dbgap/tests/test_views.py index 3c7df87a..a2ebf614 100644 --- a/primed/dbgap/tests/test_views.py +++ b/primed/dbgap/tests/test_views.py @@ -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) 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 %} From c2071507a29fbda2330b33f15f593614237f30b3 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 5 Apr 2024 16:53:02 -0700 Subject: [PATCH 27/60] Improve tests for data summary view Test that dbGaP workspaces and open access workspaces are included in separate tests. Add dbgap to the names of the tests in the helper function tests. --- primed/primed_anvil/tests/test_helpers.py | 14 +++++++------- primed/primed_anvil/tests/test_views.py | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/primed/primed_anvil/tests/test_helpers.py b/primed/primed_anvil/tests/test_helpers.py index d62b5507..a79aa586 100644 --- a/primed/primed_anvil/tests/test_helpers.py +++ b/primed/primed_anvil/tests/test_helpers.py @@ -59,7 +59,7 @@ 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( @@ -79,7 +79,7 @@ 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") @@ -101,7 +101,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,7 +119,7 @@ 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( @@ -141,7 +141,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,7 +159,7 @@ 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") @@ -197,7 +197,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") diff --git a/primed/primed_anvil/tests/test_views.py b/primed/primed_anvil/tests/test_views.py index 9e1b079f..e2a53555 100644 --- a/primed/primed_anvil/tests/test_views.py +++ b/primed/primed_anvil/tests/test_views.py @@ -1239,3 +1239,25 @@ 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) + open_workspace.available_data.add(self.available_data) + 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. + study = StudyFactory.create() + dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[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) From edafe173d4534256849cea8f30c1679cb0519807 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 5 Apr 2024 17:20:44 -0700 Subject: [PATCH 28/60] Show CDSA workspaces in Data Summary view Add a query for CDSA workspaces to the data summary helper function. Modify tests for the Data Summary view to test each workspace type separately (and just one case for it). Add a test to the Data Summary view for CDSA workspaces. --- primed/primed_anvil/helpers.py | 28 ++- primed/primed_anvil/tests/test_helpers.py | 294 +++++++++++++++++++--- primed/primed_anvil/tests/test_views.py | 12 +- 3 files changed, 295 insertions(+), 39 deletions(-) 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/tests/test_helpers.py b/primed/primed_anvil/tests/test_helpers.py index a79aa586..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) @@ -62,10 +63,8 @@ def test_one_open_access_workspace_one_study_not_shared_no_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) @@ -83,11 +82,9 @@ 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) @@ -122,11 +119,9 @@ def test_one_dbgap_workspace_two_studies_not_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) @@ -163,17 +158,17 @@ 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( @@ -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 e2a53555..9a95ea11 100644 --- a/primed/primed_anvil/tests/test_views.py +++ b/primed/primed_anvil/tests/test_views.py @@ -1245,7 +1245,6 @@ def test_includes_open_access_workspaces(self): study = StudyFactory.create() open_workspace = OpenAccessWorkspaceFactory.create() open_workspace.studies.add(study) - open_workspace.available_data.add(self.available_data) self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("summary_table", response.context_data) @@ -1255,8 +1254,15 @@ 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. - study = StudyFactory.create() - dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) + 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) From 586e0daf713b9e06f6d0aa9ae2856f9959a93b5d Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 5 Apr 2024 17:24:34 -0700 Subject: [PATCH 29/60] Make DUO lists visible to all with View permission Regular view users can see them, not just staff view. --- primed/duo/tests/test_views.py | 4 ++-- primed/duo/views.py | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) 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 From 2bada7d9bd82cb267f5850793e8fd8acbe30f37d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Apr 2024 03:28:47 +0000 Subject: [PATCH 30/60] Bump tablib from 3.6.0 to 3.6.1 Bumps [tablib](https://github.com/jazzband/tablib) from 3.6.0 to 3.6.1. - [Release notes](https://github.com/jazzband/tablib/releases) - [Changelog](https://github.com/jazzband/tablib/blob/master/HISTORY.md) - [Commits](https://github.com/jazzband/tablib/compare/v3.6.0...v3.6.1) --- updated-dependencies: - dependency-name: tablib dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index da4315c3..02e2112b 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -202,7 +202,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.3 # via plotly From 4a01c733a1436761a0a300ed847437394dc89a65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Apr 2024 03:35:11 +0000 Subject: [PATCH 31/60] Bump django-tree-queries from 0.16.1 to 0.18.0 Bumps [django-tree-queries](https://github.com/matthiask/django-tree-queries) from 0.16.1 to 0.18.0. - [Changelog](https://github.com/feincms/django-tree-queries/blob/main/CHANGELOG.rst) - [Commits](https://github.com/matthiask/django-tree-queries/compare/0.16.1...0.18) --- updated-dependencies: - dependency-name: django-tree-queries dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index da4315c3..c3122af6 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -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 From b785ec536c5514eaf8d21930d0d2314793f62e10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Apr 2024 03:36:15 +0000 Subject: [PATCH 32/60] Bump django-model-utils from 4.4.0 to 4.5.0 Bumps [django-model-utils](https://github.com/jazzband/django-model-utils) from 4.4.0 to 4.5.0. - [Release notes](https://github.com/jazzband/django-model-utils/releases) - [Changelog](https://github.com/jazzband/django-model-utils/blob/master/CHANGES.rst) - [Commits](https://github.com/jazzband/django-model-utils/compare/4.4.0...4.5.0) --- updated-dependencies: - dependency-name: django-model-utils dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index da4315c3..7aa17030 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -89,7 +89,7 @@ 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 # via From aa9aab3f58c4bb43087d34be1550dc10c4f91e27 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Apr 2024 03:40:54 +0000 Subject: [PATCH 33/60] Bump werkzeug from 3.0.1 to 3.0.2 Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.0.1 to 3.0.2. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.0.1...3.0.2) --- updated-dependencies: - dependency-name: werkzeug dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index d5c4d978..4101d59c 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -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 From e36defd083cdf63a1f21a579ab1f7776452684b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 Apr 2024 03:41:29 +0000 Subject: [PATCH 34/60] Bump types-requests from 2.31.0.20240311 to 2.31.0.20240406 Bumps [types-requests](https://github.com/python/typeshed) from 2.31.0.20240311 to 2.31.0.20240406. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index d5c4d978..7d2a1d9a 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 From 880922304b5329e3c90af7c9daab8da5aefbebb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:17:44 +0000 Subject: [PATCH 35/60] Bump django-picklefield from 3.1 to 3.2 Bumps [django-picklefield](https://github.com/gintas/django-picklefield) from 3.1 to 3.2. - [Commits](https://github.com/gintas/django-picklefield/compare/v3.1.0...v3.2.0) --- updated-dependencies: - dependency-name: django-picklefield dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 11bc0782..8a0f9bd2 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -91,7 +91,7 @@ django-maintenance-mode==0.21.1 # via -r requirements/requirements.in 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 From fe4550c464b3994d6c0f7c7ae495544863edbc4e Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 10 Apr 2024 14:00:34 -0700 Subject: [PATCH 36/60] Add a helper function to generate pheno inventory json input The primed-phenotype-inventory workflow takes a "map" input, which essentially a json key-value pair. Add a helper function generate this input using data in the app. --- primed/primed_anvil/helpers.py | 100 +++++++- primed/primed_anvil/tests/test_helpers.py | 297 +++++++++++++++++++++- 2 files changed, 393 insertions(+), 4 deletions(-) diff --git a/primed/primed_anvil/helpers.py b/primed/primed_anvil/helpers.py index 39193ade..2544810c 100644 --- a/primed/primed_anvil/helpers.py +++ b/primed/primed_anvil/helpers.py @@ -1,6 +1,9 @@ +from itertools import groupby + import pandas as pd -from anvil_consortium_manager.models import WorkspaceGroupSharing -from django.db.models import Exists, F, OuterRef, Value +from anvil_consortium_manager.models import ManagedGroup, WorkspaceGroupSharing +from django.db.models import CharField, Exists, F, OuterRef, Value +from django.db.models.functions import Concat from primed.cdsa.models import CDSAWorkspace from primed.dbgap.models import dbGaPWorkspace @@ -127,3 +130,96 @@ def get_summary_table_data(): # Convert to a list of dictionaries for passing to the django-tables2 table. data = data.to_dict(orient="records") return data + + +def get_workspaces_for_phenotype_inventory(): + """Get input to the primed-phenotype-inventory workflow. + + This function generates the input for the "workspaces" field of the primed-phenotype-inventory workflow. Only + workspaces that have been shared with the consortium are included. + See dockstore link: https://dockstore.org/workflows/github.com/UW-GAC/primed-inventory-workflows/primed_phenotype_inventory:main?tab=info + + The "workspaces" field has the format: + { + "billing-project-1/workspace-1": "study1, study2", + "billing-project-2/workspace-2": "study3", + ... + } + """ # noqa: E501 + + # primed-all group. We will need this to determine if the workspace is shared with PRIMED_ALL. + primed_all = ManagedGroup.objects.get(name="PRIMED_ALL") + + dbgap_workspaces = ( + dbGaPWorkspace.objects.filter( + # Just those that are shared with PRIMED_ALL. + workspace__workspacegroupsharing__group=primed_all, + ) + .annotate( + workspace_name=Concat( + F("workspace__billing_project__name"), + Value("/"), + F("workspace__name"), + output_field=CharField(), + ), + study_names=F("dbgap_study_accession__studies__short_name"), + ) + .values( + # "workspace", + # "workspace_billing_project", + "workspace_name", + "study_names", + ) + ) + + cdsa_workspaces = ( + CDSAWorkspace.objects.filter( + # Just those that are shared with PRIMED_ALL. + workspace__workspacegroupsharing__group=primed_all, + ) + .annotate( + workspace_name=Concat( + F("workspace__billing_project__name"), + Value("/"), + F("workspace__name"), + output_field=CharField(), + ), + study_names=F("study__short_name"), + ) + .values( + "workspace_name", + "study_names", + ) + ) + + open_access_workspaces = ( + OpenAccessWorkspace.objects.filter( + # Just those that are shared with PRIMED_ALL. + workspace__workspacegroupsharing__group=primed_all, + ) + .annotate( + workspace_name=Concat( + F("workspace__billing_project__name"), + Value("/"), + F("workspace__name"), + output_field=CharField(), + ), + study_names=F("studies__short_name"), + ) + .values( + "workspace_name", + "study_names", + ) + ) + + # Combine all querysets and process into the expected output for the AnVIL workflow. + workspaces = dbgap_workspaces.union(cdsa_workspaces).union(open_access_workspaces) + + json = {} + for key, group in groupby(workspaces, lambda x: x["workspace_name"]): + study_names = [x["study_names"] if x["study_names"] else "" for x in group] + if not study_names: + study_names = "" + json[key] = ", ".join(study_names) + + return json diff --git a/primed/primed_anvil/tests/test_helpers.py b/primed/primed_anvil/tests/test_helpers.py index 78a20b57..a5bf54f1 100644 --- a/primed/primed_anvil/tests/test_helpers.py +++ b/primed/primed_anvil/tests/test_helpers.py @@ -1,10 +1,16 @@ """Tests of helper functions in the `primed_anvil` app.""" -from anvil_consortium_manager.tests.factories import WorkspaceGroupSharingFactory +from anvil_consortium_manager.tests.factories import ( + ManagedGroupFactory, + WorkspaceGroupSharingFactory, +) from django.test import TestCase from primed.cdsa.tests.factories import CDSAWorkspaceFactory -from primed.dbgap.tests.factories import dbGaPWorkspaceFactory +from primed.dbgap.tests.factories import ( + dbGaPStudyAccessionFactory, + dbGaPWorkspaceFactory, +) from primed.miscellaneous_workspaces.tests.factories import OpenAccessWorkspaceFactory from primed.primed_anvil.tests.factories import AvailableDataFactory, StudyFactory @@ -544,3 +550,290 @@ def test_one_cdsa_workspace_one_open_access_workspace_different_available_data( }, res, ) + + +class GetWorkspacesForPhenotypeInventoryTest(TestCase): + """Tests for the helpers.get_workspaces_for_phenotype_inventory method.""" + + def setUp(self): + """Set up the test case.""" + super().setUp() + # Create the PRIMED_ALL group. + self.primed_all_group = ManagedGroupFactory.create(name="PRIMED_ALL") + + def test_no_workspaces(self): + """get_workspaces_for_phenotype_inventory with no workspaces.""" + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(res, {}) + + def test_one_dbgap_workspace_not_shared(self): + """get_workspaces_for_phenotype_inventory with one dbGaP workspace.""" + dbGaPWorkspaceFactory.create() + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(res, {}) + + def test_one_dbgap_workspace_shared_one_study(self): + """get_workspaces_for_phenotype_inventory with one dbGaP workspace.""" + study = StudyFactory.create(short_name="TEST") + workspace = dbGaPWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + dbgap_study_accession__studies=[study], + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 1) + self.assertIn("test-bp/test-ws", res) + self.assertEqual(res["test-bp/test-ws"], "TEST") + + def test_one_dbgap_workspace_shared_two_studies(self): + """get_workspaces_for_phenotype_inventory with one dbGaP workspace.""" + study_1 = StudyFactory.create(short_name="TEST_2") + study_2 = StudyFactory.create(short_name="TEST_1") + study_accession = dbGaPStudyAccessionFactory.create(studies=[study_1, study_2]) + workspace = dbGaPWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + dbgap_study_accession=study_accession, + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 1) + self.assertIn("test-bp/test-ws", res) + self.assertEqual(res["test-bp/test-ws"], "TEST_1, TEST_2") + + def test_two_dbgap_workspaces(self): + """get_workspaces_for_phenotype_inventory with two dbGaP workspaces.""" + study_1 = StudyFactory.create(short_name="TEST 1") + workspace_1 = dbGaPWorkspaceFactory.create( + workspace__billing_project__name="test-bp-1", + workspace__name="test-ws-1", + dbgap_study_accession__studies=[study_1], + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace_1.workspace, group=self.primed_all_group + ) + study_2 = StudyFactory.create(short_name="TEST 2") + workspace_2 = dbGaPWorkspaceFactory.create( + workspace__billing_project__name="test-bp-2", + workspace__name="test-ws-2", + dbgap_study_accession__studies=[study_2], + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace_2.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 2) + self.assertIn("test-bp-1/test-ws-1", res) + self.assertEqual(res["test-bp-1/test-ws-1"], "TEST 1") + self.assertIn("test-bp-2/test-ws-2", res) + self.assertEqual(res["test-bp-2/test-ws-2"], "TEST 2") + + def test_one_cdsa_workspace_not_shared(self): + """get_workspaces_for_phenotype_inventory with one CDSA workspace.""" + CDSAWorkspaceFactory.create() + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(res, {}) + + def test_one_cdsa_workspace_shared_one_study(self): + """get_workspaces_for_phenotype_inventory with one CDSA workspace.""" + study = StudyFactory.create(short_name="TEST") + workspace = CDSAWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + study=study, + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 1) + self.assertIn("test-bp/test-ws", res) + self.assertEqual(res["test-bp/test-ws"], "TEST") + + def test_two_cdsa_workspaces(self): + """get_workspaces_for_phenotype_inventory with two CDSA workspaces.""" + study_1 = StudyFactory.create(short_name="TEST 1") + workspace_1 = CDSAWorkspaceFactory.create( + workspace__billing_project__name="test-bp-1", + workspace__name="test-ws-1", + study=study_1, + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace_1.workspace, group=self.primed_all_group + ) + study_2 = StudyFactory.create(short_name="TEST 2") + workspace_2 = CDSAWorkspaceFactory.create( + workspace__billing_project__name="test-bp-2", + workspace__name="test-ws-2", + study=study_2, + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace_2.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 2) + self.assertIn("test-bp-1/test-ws-1", res) + self.assertEqual(res["test-bp-1/test-ws-1"], "TEST 1") + self.assertIn("test-bp-2/test-ws-2", res) + self.assertEqual(res["test-bp-2/test-ws-2"], "TEST 2") + + def test_one_open_access_workspace_not_shared(self): + """get_workspaces_for_phenotype_inventory with one dbGaP workspace.""" + OpenAccessWorkspaceFactory.create() + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(res, {}) + + def test_one_open_access_workspace_shared_no_study(self): + """get_workspaces_for_phenotype_inventory with one Open access workspace.""" + workspace = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 1) + self.assertIn("test-bp/test-ws", res) + self.assertEqual(res["test-bp/test-ws"], "") + + def test_one_open_access_workspace_shared_one_study(self): + """get_workspaces_for_phenotype_inventory with one Open access workspace.""" + study = StudyFactory.create(short_name="TEST") + workspace = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + ) + workspace.studies.add(study) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 1) + self.assertIn("test-bp/test-ws", res) + self.assertEqual(res["test-bp/test-ws"], "TEST") + + def test_one_open_access_workspace_shared_two_studies(self): + """get_workspaces_for_phenotype_inventory with one Open access workspace.""" + study_1 = StudyFactory.create(short_name="TEST_2") + study_2 = StudyFactory.create(short_name="TEST_1") + workspace = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp", + workspace__name="test-ws", + ) + workspace.studies.add(study_1, study_2) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 1) + self.assertIn("test-bp/test-ws", res) + self.assertEqual(res["test-bp/test-ws"], "TEST_1, TEST_2") + + def test_two_open_access_workspaces(self): + """get_workspaces_for_phenotype_inventory with two Open access workspace.""" + workspace_1 = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp-1", + workspace__name="test-ws-1", + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace_1.workspace, group=self.primed_all_group + ) + study_2 = StudyFactory.create(short_name="TEST 2") + workspace_2 = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp-2", + workspace__name="test-ws-2", + ) + workspace_2.studies.add(study_2) + WorkspaceGroupSharingFactory.create( + workspace=workspace_2.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 2) + self.assertIn("test-bp-1/test-ws-1", res) + self.assertEqual(res["test-bp-1/test-ws-1"], "") + self.assertIn("test-bp-2/test-ws-2", res) + self.assertEqual(res["test-bp-2/test-ws-2"], "TEST 2") + + def test_multiple_workspace_types_same_study(self): + study = StudyFactory.create(short_name="TEST") + # dbgap + workspace = dbGaPWorkspaceFactory.create( + workspace__billing_project__name="test-bp-dbgap", + workspace__name="test-ws-dbgap", + dbgap_study_accession__studies=[study], + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + # CDSA + workspace = CDSAWorkspaceFactory.create( + workspace__billing_project__name="test-bp-cdsa", + workspace__name="test-ws-cdsa", + study=study, + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + # Open access + workspace = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp-open", + workspace__name="test-ws-open", + ) + workspace.studies.add(study) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 3) + self.assertIn("test-bp-dbgap/test-ws-dbgap", res) + self.assertEqual(res["test-bp-dbgap/test-ws-dbgap"], "TEST") + self.assertIn("test-bp-cdsa/test-ws-cdsa", res) + self.assertEqual(res["test-bp-cdsa/test-ws-cdsa"], "TEST") + self.assertIn("test-bp-open/test-ws-open", res) + self.assertEqual(res["test-bp-open/test-ws-open"], "TEST") + + def test_multiple_workspace_types_separate_studies(self): + study_1 = StudyFactory.create(short_name="TEST 1") + # dbgap + workspace = dbGaPWorkspaceFactory.create( + workspace__billing_project__name="test-bp-dbgap", + workspace__name="test-ws-dbgap", + dbgap_study_accession__studies=[study_1], + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + # CDSA + study_2 = StudyFactory.create(short_name="TEST 2") + workspace = CDSAWorkspaceFactory.create( + workspace__billing_project__name="test-bp-cdsa", + workspace__name="test-ws-cdsa", + study=study_2, + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + # Open access + study_3 = StudyFactory.create(short_name="TEST 3") + workspace = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp-open", + workspace__name="test-ws-open", + ) + workspace.studies.add(study_3) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all_group + ) + res = helpers.get_workspaces_for_phenotype_inventory() + self.assertEqual(len(res), 3) + self.assertIn("test-bp-dbgap/test-ws-dbgap", res) + self.assertEqual(res["test-bp-dbgap/test-ws-dbgap"], "TEST 1") + self.assertIn("test-bp-cdsa/test-ws-cdsa", res) + self.assertEqual(res["test-bp-cdsa/test-ws-cdsa"], "TEST 2") + self.assertIn("test-bp-open/test-ws-open", res) + self.assertEqual(res["test-bp-open/test-ws-open"], "TEST 3") From d68aad087dbff8ca2c903a2ade5127ef8dd45782 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 10 Apr 2024 14:57:56 -0700 Subject: [PATCH 37/60] Add a view to show the generated workspaces input This view shows the workspaces input for the primed_phenotype_inventory workflow from helpers.get_workspaces_for_phenotype_inventory(). Add the url path, a view, a template, and tests for the view. --- primed/primed_anvil/tests/test_views.py | 84 ++++++++++++++++++- primed/primed_anvil/urls.py | 12 +++ primed/primed_anvil/views.py | 16 ++++ .../phenotype_inventory_inputs.html | 32 +++++++ 4 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 primed/templates/primed_anvil/phenotype_inventory_inputs.html diff --git a/primed/primed_anvil/tests/test_views.py b/primed/primed_anvil/tests/test_views.py index 9a95ea11..b800b6df 100644 --- a/primed/primed_anvil/tests/test_views.py +++ b/primed/primed_anvil/tests/test_views.py @@ -1,7 +1,11 @@ import json from anvil_consortium_manager import models as acm_models -from anvil_consortium_manager.tests.factories import AccountFactory +from anvil_consortium_manager.tests.factories import ( + AccountFactory, + ManagedGroupFactory, + WorkspaceGroupSharingFactory, +) from anvil_consortium_manager.views import AccountList from constance.test import override_config from django.conf import settings @@ -1267,3 +1271,81 @@ def test_includes_cdsa_workspaces(self): response = self.client.get(self.get_url()) self.assertIn("summary_table", response.context_data) self.assertEqual(len(response.context_data["summary_table"].rows), 1) + + +class PhenotypeInventoryInputsViewTest(TestCase): + """Tests for the PhenotypeInventoryInputsView view.""" + + def setUp(self): + """Set up test class.""" + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get( + codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME + ) + ) + self.primed_all = ManagedGroupFactory.create(name="PRIMED_ALL") + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse("primed_anvil:utilities:phenotype_inventory_inputs", args=args) + + def get_view(self): + """Return the view being tested.""" + return views.PhenotypeInventoryInputsView.as_view() + + def test_view_redirect_not_logged_in(self): + "View redirects to login view when user is not logged in." + # Need a client for redirects. + response = self.client.get(self.get_url()) + self.assertRedirects( + response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() + ) + + def test_status_code_with_authenticated_user(self): + """Redirects to login view when user has no perms.""" + user_no_perms = User.objects.create_user( + username="test-none", password="test-none" + ) + request = self.factory.get(self.get_url()) + request.user = user_no_perms + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_status_code_with_view_user(self): + """Redirects to login view when user has view perm.""" + user = User.objects.create_user(username="test-none", password="test-none") + user.user_permissions.add( + Permission.objects.get( + codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME + ) + ) + request = self.factory.get(self.get_url()) + request.user = user + with self.assertRaises(PermissionDenied): + self.get_view()(request) + + def test_context_workspaces_input_no_workspaces(self): + """workspaces_input exists in the context and is correct with no workspaces.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("workspaces_input", response.context_data) + self.assertEqual(response.context_data["workspaces_input"], "{}") + + def test_context_workspaces_input_one_workspace(self): + """workspaces_input exists in the context and is correct with one shared workspace.""" + workspace = OpenAccessWorkspaceFactory.create( + workspace__billing_project__name="test-bp", workspace__name="test-ws" + ) + WorkspaceGroupSharingFactory.create( + workspace=workspace.workspace, group=self.primed_all + ) + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("workspaces_input", response.context_data) + self.assertEqual( + response.context_data["workspaces_input"], + """{\n "test-bp/test-ws": ""\n}""", + ) diff --git a/primed/primed_anvil/urls.py b/primed/primed_anvil/urls.py index 909ad7fb..81557dcf 100644 --- a/primed/primed_anvil/urls.py +++ b/primed/primed_anvil/urls.py @@ -37,9 +37,21 @@ "summaries", ) +utilities_patterns = ( + [ + path( + "phenotype_inventory_inputs/", + views.PhenotypeInventoryInputsView.as_view(), + name="phenotype_inventory_inputs", + ), + ], + "utilities", +) + urlpatterns = [ path("studies/", include(study_patterns)), path("study_sites/", include(study_site_patterns)), path("available_data/", include(available_data_patterns)), path("summaries/", include(summary_patterns)), + path("utilities/", include(utilities_patterns)), ] diff --git a/primed/primed_anvil/views.py b/primed/primed_anvil/views.py index c89ab3cb..f85e787b 100644 --- a/primed/primed_anvil/views.py +++ b/primed/primed_anvil/views.py @@ -1,3 +1,5 @@ +import json + from anvil_consortium_manager.auth import ( AnVILConsortiumManagerStaffEditRequired, AnVILConsortiumManagerStaffViewRequired, @@ -183,3 +185,17 @@ def get_context_data(self, **kwargs): table_data = helpers.get_summary_table_data() context["summary_table"] = tables.DataSummaryTable(table_data) return context + + +class PhenotypeInventoryInputsView( + AnVILConsortiumManagerStaffViewRequired, TemplateView +): + + template_name = "primed_anvil/phenotype_inventory_inputs.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["workspaces_input"] = json.dumps( + helpers.get_workspaces_for_phenotype_inventory(), indent=2 + ) + return context diff --git a/primed/templates/primed_anvil/phenotype_inventory_inputs.html b/primed/templates/primed_anvil/phenotype_inventory_inputs.html new file mode 100644 index 00000000..157882cf --- /dev/null +++ b/primed/templates/primed_anvil/phenotype_inventory_inputs.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} +{% load render_table from django_tables2 %} + +{% block extra_navbar %} + {% include 'anvil_consortium_manager/navbar.html' %} +{% endblock %} + +{% block content %} + +

Phenotype inventory workflow

+ +

+ This page creates the input for the "workspaces" field in the + PRIMED phenotype inventory workflow. + Copy the text in the box below and paste it into the "workspaces" when running the workflow. +

+ + +
+
+
Input for the workspaces field
+

+ +

{{ workspaces_input }}
+ + +

+
+
+ + +{% endblock content %} From 1d485ca144afdddab18f4f4eead907be90c65f23 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 10 Apr 2024 15:05:15 -0700 Subject: [PATCH 38/60] Add some javascript to copy from a pre tag to the clipboard This isn't ideal but I want the record of it. Taken from this page: https://www.roboleary.net/2022/01/13/copy-code-to-clipboard-blog.html --- primed/static/js/project.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/primed/static/js/project.js b/primed/static/js/project.js index 9c90629e..75662f99 100644 --- a/primed/static/js/project.js +++ b/primed/static/js/project.js @@ -17,3 +17,31 @@ for(var i = 0; i < textInputs.length; i++){ // Console: print the clicked

element textInputs[i].addEventListener("paste", checkPasteLength); } + + +// Button to copy to the clipboard. +const copyButtonLabel = "Copy Code"; + +// use a class selector if available +let blocks = document.querySelectorAll("pre"); + +blocks.forEach((block) => { + // only add button if browser supports Clipboard API + if (navigator.clipboard) { + let button = document.createElement("button"); + + button.innerText = copyButtonLabel; + block.appendChild(button); + + button.addEventListener("click", async () => { + await copyCode(block); + }); + } +}); + +async function copyCode(block) { + let code = block.querySelector("code"); + let text = code.innerText; + + await navigator.clipboard.writeText(text); +} From cc726f920e239fce01650fa67d1e098656d2c92a Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 10 Apr 2024 15:48:11 -0700 Subject: [PATCH 39/60] Change javascript to copy workspaces input on click Move the button to the card subtitle instead of dynamically adding it via the javascript (as the blog example did). Move the javascript into the template inline_javascript block, since this code is only relevant for this template at the moment - there's no need to put it in the project-wide javascript. To make the javascript work, there must be: - an html element with the class "copy-element". This must have an attribute "copy-target" that specifies the id of the element whose text should be copied. - an element with the id specified in the "copy-target" attribute --- primed/static/js/project.js | 28 ------------------- .../phenotype_inventory_inputs.html | 26 +++++++++++++---- 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/primed/static/js/project.js b/primed/static/js/project.js index 75662f99..9c90629e 100644 --- a/primed/static/js/project.js +++ b/primed/static/js/project.js @@ -17,31 +17,3 @@ for(var i = 0; i < textInputs.length; i++){ // Console: print the clicked

element textInputs[i].addEventListener("paste", checkPasteLength); } - - -// Button to copy to the clipboard. -const copyButtonLabel = "Copy Code"; - -// use a class selector if available -let blocks = document.querySelectorAll("pre"); - -blocks.forEach((block) => { - // only add button if browser supports Clipboard API - if (navigator.clipboard) { - let button = document.createElement("button"); - - button.innerText = copyButtonLabel; - block.appendChild(button); - - button.addEventListener("click", async () => { - await copyCode(block); - }); - } -}); - -async function copyCode(block) { - let code = block.querySelector("code"); - let text = code.innerText; - - await navigator.clipboard.writeText(text); -} diff --git a/primed/templates/primed_anvil/phenotype_inventory_inputs.html b/primed/templates/primed_anvil/phenotype_inventory_inputs.html index 157882cf..9cbe01b7 100644 --- a/primed/templates/primed_anvil/phenotype_inventory_inputs.html +++ b/primed/templates/primed_anvil/phenotype_inventory_inputs.html @@ -18,15 +18,31 @@

Phenotype inventory workflow

-
Input for the workspaces field
+
Input for the workspaces field
+

- -

{{ workspaces_input }}
- - +
{{ workspaces_input }}

{% endblock content %} + + +{% block inline_javascript %} + +{% endblock %} From 54a3e27a399859b12e8ec7391830a7536d6b4a0f Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 10 Apr 2024 15:55:08 -0700 Subject: [PATCH 40/60] Typo fixes --- primed/templates/primed_anvil/phenotype_inventory_inputs.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primed/templates/primed_anvil/phenotype_inventory_inputs.html b/primed/templates/primed_anvil/phenotype_inventory_inputs.html index 9cbe01b7..7a479f54 100644 --- a/primed/templates/primed_anvil/phenotype_inventory_inputs.html +++ b/primed/templates/primed_anvil/phenotype_inventory_inputs.html @@ -12,7 +12,7 @@

Phenotype inventory workflow

This page creates the input for the "workspaces" field in the PRIMED phenotype inventory workflow. - Copy the text in the box below and paste it into the "workspaces" when running the workflow. + Copy the text in the box below and paste it into the "workspaces" field when running the workflow on AnVIL.

From dedda2719b60c62291e80af7f599b3c966e41dfe Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Fri, 12 Apr 2024 09:20:05 -0700 Subject: [PATCH 41/60] Add email template and support for emailing results --- .../users/drupal_data_audit_email.html | 47 +++++++++++++ primed/users/audit.py | 6 ++ .../management/commands/sync-drupal-data.py | 66 +++++++++++-------- 3 files changed, 93 insertions(+), 26 deletions(-) create mode 100644 primed/templates/users/drupal_data_audit_email.html diff --git a/primed/templates/users/drupal_data_audit_email.html b/primed/templates/users/drupal_data_audit_email.html new file mode 100644 index 00000000..86bc8749 --- /dev/null +++ b/primed/templates/users/drupal_data_audit_email.html @@ -0,0 +1,47 @@ + +{% load static i18n %} +{% load render_table from django_tables2 %} + + + + Drupal Data Audit Report + + +
+ +{% block content %} + +

Drupal Data Audit - [applying_changes={{ apply_changes }}]

+

User Audit

+ +

Verified Users - {{ user_audit.verified|length }} record(s)

+ +

Needs action - {{user_audit.needs_action|length }} record(s)

+ {% if user_audit.needs_action %} + {% render_table user_audit.get_needs_action_table %} + {% endif %} + +

Errors - {{user_audit.errors|length }} record(s)

+ {% if user_audit.errors %} + {% render_table user_audit.get_errors_table %} + {% endif %} + +

Site Audit

+ +

Verified sites - {{ site_audit.verified|length }} record(s)

+ +

Sites that need action - {{site_audit.needs_action|length }} record(s)

+ {% if site_audit.needs_action %} + {% render_table site_audit.get_needs_action_table %} + {% endif %} + +

Sites with errors - {{site_audit.errors|length }} record(s)

+ {% if site_audit.errors %} + {% render_table site_audit.get_errors_table %} + {% endif %} + + +{% endblock content %} + +
+ diff --git a/primed/users/audit.py b/primed/users/audit.py index 0004a5ce..08ae233f 100644 --- a/primed/users/audit.py +++ b/primed/users/audit.py @@ -37,6 +37,9 @@ class UserAuditResultsTable(tables.Table, TextTable): note = tables.Column() anvil_groups = tables.Column() + class Meta: + orderable = False + @dataclass class UserAuditResult(PRIMEDAuditResult): @@ -324,6 +327,9 @@ class SiteAuditResultsTable(tables.Table, TextTable): def value_local_site_name(self, value): return value + class Meta: + orderable = False + @dataclass class SiteAuditResult(PRIMEDAuditResult): diff --git a/primed/users/management/commands/sync-drupal-data.py b/primed/users/management/commands/sync-drupal-data.py index da71d802..90f082a6 100644 --- a/primed/users/management/commands/sync-drupal-data.py +++ b/primed/users/management/commands/sync-drupal-data.py @@ -1,6 +1,9 @@ import logging +from django.core.mail import send_mail from django.core.management.base import BaseCommand +from django.http import HttpRequest +from django.template.loader import render_to_string from django.utils.timezone import localtime from primed.users import audit @@ -10,9 +13,6 @@ class Command(BaseCommand): help = "Sync drupal user and domain data" - NOTIFY_NONE = "none" - NOTIFY_ALL = "all" - NOTIFY_ISSUES = "issues" def add_arguments(self, parser): parser.add_argument( @@ -22,27 +22,44 @@ def add_arguments(self, parser): default=False, help="Make updates to sync local data with remote. If not set, will just report.", ) + parser.add_argument( - "--verbose", - action="store_true", - dest="verbose", - default=False, - help="Log verbosely", - ) - parser.add_argument( - "--notify", - dest="notify", - choices=[self.NOTIFY_NONE, self.NOTIFY_ALL, self.NOTIFY_ISSUES], - default=self.NOTIFY_ALL, - help=f"Notification level: (default: {self.NOTIFY_ALL})", + "--email", + help="""Email to which to send audit result details that need action or have errors.""", ) + def _send_email(self, user_audit, site_audit): + # Send email if requested and there are problems. + if user_audit.ok() is False or site_audit.ok() is False: + request = HttpRequest() + subject = "SyncDrupalData Report" + html_body = render_to_string( + "users/drupal_data_audit_email.html", + context={ + "user_audit": user_audit, + "site_audit": site_audit, + "request": request, + "apply_changes": self.apply_changes, + }, + ) + send_mail( + subject, + "Drupal data audit problems or changes found. Please see attached report.", + None, + [self.email], + fail_silently=False, + html_message=html_body, + ) + def handle(self, *args, **options): - apply_changes = options.get("update") - be_verbose = options.get("verbose") - # notify_type = options.get("notify") - notification_content = f"Drupal data audit start: Applying Changes: {apply_changes} Start time: {localtime()}\n" - site_audit = audit.SiteAudit(apply_changes=apply_changes) + self.apply_changes = options.get("update") + self.email = options["email"] + + notification_content = ( + f"[sync-drupal-data] start: Applying Changes: {self.apply_changes} " + f"Start time: {localtime()}\n" + ) + site_audit = audit.SiteAudit(apply_changes=self.apply_changes) site_audit.run_audit() notification_content += ( @@ -56,10 +73,7 @@ def handle(self, *args, **options): notification_content += "Sites requiring intervention:\n" notification_content += site_audit.get_errors_table().render_to_text() - if be_verbose: - notification_content += site_audit.get_verified_table().render_to_text() - - user_audit = audit.UserAudit(apply_changes=apply_changes) + user_audit = audit.UserAudit(apply_changes=self.apply_changes) user_audit.run_audit() notification_content += ( "--------------------------------------\n" @@ -72,7 +86,7 @@ def handle(self, *args, **options): if user_audit.errors: notification_content += "Users that need intervention:\n" notification_content += user_audit.get_errors_table().render_to_text() - if be_verbose: - notification_content += user_audit.get_verified_table().render_to_text() self.stdout.write(notification_content) + if self.email: + self._send_email(user_audit, site_audit) From a8bc6ead15862dd5b1ff29ac2e155741ed172e08 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Fri, 12 Apr 2024 10:56:06 -0700 Subject: [PATCH 42/60] Handle drupal inactive, django active. Add threshold for automatic user deactivation. --- .../users/drupal_data_audit_email.html | 14 ++-- primed/users/audit.py | 38 ++++++++--- .../management/commands/sync-drupal-data.py | 19 +++++- primed/users/tests/test_audit.py | 67 ++++++++++++++++++- 4 files changed, 118 insertions(+), 20 deletions(-) diff --git a/primed/templates/users/drupal_data_audit_email.html b/primed/templates/users/drupal_data_audit_email.html index 86bc8749..c957a32f 100644 --- a/primed/templates/users/drupal_data_audit_email.html +++ b/primed/templates/users/drupal_data_audit_email.html @@ -6,7 +6,7 @@ Drupal Data Audit Report - +
{% block content %} @@ -15,31 +15,31 @@

Drupal Data Audit - [applying_changes={{ apply_changes }}]

User Audit

Verified Users - {{ user_audit.verified|length }} record(s)

- +

Needs action - {{user_audit.needs_action|length }} record(s)

{% if user_audit.needs_action %} {% render_table user_audit.get_needs_action_table %} {% endif %} - +

Errors - {{user_audit.errors|length }} record(s)

{% if user_audit.errors %} {% render_table user_audit.get_errors_table %} {% endif %} - +

Site Audit

Verified sites - {{ site_audit.verified|length }} record(s)

- +

Sites that need action - {{site_audit.needs_action|length }} record(s)

{% if site_audit.needs_action %} {% render_table site_audit.get_needs_action_table %} {% endif %} - +

Sites with errors - {{site_audit.errors|length }} record(s)

{% if site_audit.errors %} {% render_table site_audit.get_errors_table %} {% endif %} - + {% endblock content %} diff --git a/primed/users/audit.py b/primed/users/audit.py index 08ae233f..371d7760 100644 --- a/primed/users/audit.py +++ b/primed/users/audit.py @@ -110,12 +110,18 @@ class UpdateUser(UserAuditResult): pass +@dataclass +class OverDeactivateThresholdUser(UserAuditResult): + pass + + class UserAudit(PRIMEDAudit): - ISSUE_TYPE_USER_INACTIVE = "User is inactive" + ISSUE_TYPE_USER_INACTIVE = "User is inactive in drupal" ISSUE_TYPE_USER_REMOVED_FROM_SITE = "User removed from site" + USER_DEACTIVATE_THRESHOLD = 3 results_table_class = UserAuditResultsTable - def __init__(self, apply_changes=False): + def __init__(self, apply_changes=False, ignore_deactivate_threshold=False): """Initialize the audit. Args: @@ -123,6 +129,7 @@ def __init__(self, apply_changes=False): """ super().__init__() self.apply_changes = apply_changes + self.ignore_deactivate_threshold = ignore_deactivate_threshold def _run_audit(self): """Run the audit on local and remote users.""" @@ -221,6 +228,10 @@ def _run_audit(self): ) sa.user.email = drupal_email + if sa.user.is_active is False: + user_updates.update({"is_active": {"old": False, "new": True}}) + sa.user.is_active = True + prev_user_site_names = set( sa.user.study_sites.all().values_list("short_name", flat=True) ) @@ -286,16 +297,25 @@ def _run_audit(self): provider=CustomProvider.id, user__is_active=True ).exclude(uid__in=drupal_uids) user_ids_to_check = [] + count_inactive = unaudited_drupal_accounts.count() + over_threshold = False + if self.ignore_deactivate_threshold is False: + if count_inactive > self.USER_DEACTIVATE_THRESHOLD: + over_threshold = True + for uda in unaudited_drupal_accounts: user_ids_to_check.append(uda.user.id) + handled = False if settings.DRUPAL_DATA_AUDIT_DEACTIVATE_USERS is True: uda.user.is_active = False - if self.apply_changes is True: - uda.user.save() - self.needs_action.append(RemoveUser(local_user=uda)) - else: + if over_threshold is False: + if self.apply_changes is True: + uda.user.save() + handled = True + self.needs_action.append(RemoveUser(local_user=uda)) + if handled is False: self.errors.append( - RemoveUser(local_user=uda, note=self.ISSUE_TYPE_USER_INACTIVE) + RemoveUser(local_user=uda, note=f"Over Threshold {over_threshold}") ) inactive_anvil_users = Account.objects.filter( @@ -381,7 +401,7 @@ class UpdateSite(SiteAuditResult): class SiteAudit(PRIMEDAudit): - ISSUE_TYPE_LOCAL_SITE_INVALD = "Local site is invalid" + ISSUE_TYPE_LOCAL_SITE_INVALID = "Local site is invalid" results_table_class = SiteAuditResultsTable def __init__(self, apply_changes=False): @@ -459,7 +479,7 @@ def _run_audit(self): for iss in invalid_study_sites: self.errors.append( - RemoveSite(local_site=iss, note=self.ISSUE_TYPE_LOCAL_SITE_INVALD) + RemoveSite(local_site=iss, note=self.ISSUE_TYPE_LOCAL_SITE_INVALID) ) diff --git a/primed/users/management/commands/sync-drupal-data.py b/primed/users/management/commands/sync-drupal-data.py index 90f082a6..d58113cc 100644 --- a/primed/users/management/commands/sync-drupal-data.py +++ b/primed/users/management/commands/sync-drupal-data.py @@ -22,6 +22,13 @@ def add_arguments(self, parser): default=False, help="Make updates to sync local data with remote. If not set, will just report.", ) + parser.add_argument( + "--ignore-threshold", + action="store_true", + dest="ignore_threshold", + default=False, + help="Ignore user deactivation threshold", + ) parser.add_argument( "--email", @@ -31,8 +38,10 @@ def add_arguments(self, parser): def _send_email(self, user_audit, site_audit): # Send email if requested and there are problems. if user_audit.ok() is False or site_audit.ok() is False: + # django-tables2 requires request context, so we create an empty one + # if we wanted to linkify any of our data we would need to do more here request = HttpRequest() - subject = "SyncDrupalData Report" + subject = "[command:sync-drupal-data] report" html_body = render_to_string( "users/drupal_data_audit_email.html", context={ @@ -54,10 +63,11 @@ def _send_email(self, user_audit, site_audit): def handle(self, *args, **options): self.apply_changes = options.get("update") self.email = options["email"] + self.ignore_threshold = options["ignore_threshold"] notification_content = ( f"[sync-drupal-data] start: Applying Changes: {self.apply_changes} " - f"Start time: {localtime()}\n" + f"Ignoring Threshold: {self.ignore_threshold} Start time: {localtime()}\n" ) site_audit = audit.SiteAudit(apply_changes=self.apply_changes) site_audit.run_audit() @@ -73,7 +83,10 @@ def handle(self, *args, **options): notification_content += "Sites requiring intervention:\n" notification_content += site_audit.get_errors_table().render_to_text() - user_audit = audit.UserAudit(apply_changes=self.apply_changes) + user_audit = audit.UserAudit( + apply_changes=self.apply_changes, + ignore_deactivate_threshold=self.ignore_threshold, + ) user_audit.run_audit() notification_content += ( "--------------------------------------\n" diff --git a/primed/users/tests/test_audit.py b/primed/users/tests/test_audit.py index 0c189f86..155d114e 100644 --- a/primed/users/tests/test_audit.py +++ b/primed/users/tests/test_audit.py @@ -11,6 +11,7 @@ ) from django.conf import settings from django.contrib.auth import get_user_model +from django.core import mail from django.core.management import call_command from django.test import TestCase from marshmallow_jsonapi import Schema, fields @@ -421,6 +422,7 @@ def test_user_audit_change_user(self): username=drupal_username + "UPDATE", email=drupal_email + "UPDATE", name=drupal_fullname + "UPDATE", + is_active=False, ) SocialAccount.objects.create( user=new_user, @@ -509,6 +511,64 @@ def test_user_audit_remove_user(self): new_user.refresh_from_db() self.assertFalse(new_user.is_active) + # test user removal + @responses.activate + def test_user_audit_remove_user_threshold(self): + self.add_fake_token_response() + self.add_fake_study_sites_response() + self.add_fake_users_response() + StudySite.objects.create( + drupal_node_id=TEST_STUDY_SITE_DATA[0].drupal_internal__nid, + short_name=TEST_STUDY_SITE_DATA[0].title, + full_name=TEST_STUDY_SITE_DATA[0].field_long_name, + ) + + SocialAccount.objects.create( + user=get_user_model().objects.create( + username="username2", email="useremail2", name="user fullname2" + ), + uid=996, + provider=CustomProvider.id, + ) + + SocialAccount.objects.create( + user=get_user_model().objects.create( + username="username3", email="useremail3", name="user fullname3" + ), + uid=997, + provider=CustomProvider.id, + ) + + SocialAccount.objects.create( + user=get_user_model().objects.create( + username="username4", email="useremail4", name="user fullname4" + ), + uid=998, + provider=CustomProvider.id, + ) + SocialAccount.objects.create( + user=get_user_model().objects.create( + username="username5", email="useremail5", name="user fullname5" + ), + uid=999, + provider=CustomProvider.id, + ) + with self.settings(DRUPAL_DATA_AUDIT_DEACTIVATE_USERS=True): + user_audit = audit.UserAudit(apply_changes=False) + user_audit.run_audit() + self.assertFalse(user_audit.ok()) + self.assertEqual(len(user_audit.errors), 4) + self.assertEqual(len(user_audit.needs_action), 1) + self.assertEqual(user_audit.errors[0].note, "Over Threshold True") + # Run again with ignore threshold, should move from error to needs action + user_audit = audit.UserAudit( + apply_changes=False, ignore_deactivate_threshold=True + ) + user_audit.run_audit() + self.assertFalse(user_audit.ok()) + self.assertEqual(len(user_audit.errors), 0) + self.assertEqual(len(user_audit.needs_action), 5) + @responses.activate def test_sync_drupal_data_command(self): self.add_fake_token_response() @@ -542,6 +602,11 @@ def test_sync_drupal_data_command_with_issues(self): self.add_fake_study_sites_response() self.add_fake_users_response() out = StringIO() - call_command("sync-drupal-data", "--verbose", stdout=out) + call_command("sync-drupal-data", "--email=test@example.com", stdout=out) self.assertIn("SiteAudit summary: status ok: False", out.getvalue()) self.assertIn("UserAudit summary: status ok: False", out.getvalue()) + + self.assertEqual(len(mail.outbox), 1) + email = mail.outbox[0] + self.assertEqual(email.to, ["test@example.com"]) + self.assertEqual(email.subject, "[command:sync-drupal-data] report") From 9c44c893f4dcb4c9b85f9d2ed469ca9968d2e089 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Fri, 12 Apr 2024 11:04:22 -0700 Subject: [PATCH 43/60] Fix tenacity version that got changed in merge --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 58c39805..022b97af 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -208,7 +208,7 @@ sqlparse==0.4.4 # django tablib==3.6.1 # via -r requirements/requirements.in -tenacity==8.2.1 +tenacity==8.2.3 # via # jsonapi-requests # plotly From d74b99b027135ee55f4646474e8496cb536acfe2 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Fri, 12 Apr 2024 11:06:08 -0700 Subject: [PATCH 44/60] Remove duplicate requirement caused by merge --- requirements/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 022b97af..1009369c 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -113,7 +113,6 @@ idna==3.3 # via requests importlib-metadata==7.0.0 # via build -importlib-resources==6.1.1 importlib-resources==6.1.1 # via # jsonschema From 3d714de0d21d776b40c32c485a56e225671a4741 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Apr 2024 04:03:02 +0000 Subject: [PATCH 45/60] Bump sqlparse from 0.4.4 to 0.5.0 Bumps [sqlparse](https://github.com/andialbrecht/sqlparse) from 0.4.4 to 0.5.0. - [Changelog](https://github.com/andialbrecht/sqlparse/blob/master/CHANGELOG) - [Commits](https://github.com/andialbrecht/sqlparse/compare/0.4.4...0.5.0) --- updated-dependencies: - dependency-name: sqlparse dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- requirements/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index f2991d16..2bd18fd7 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -205,7 +205,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlparse==0.4.4 +sqlparse==0.5.0 # via # -c requirements/requirements.txt # django diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 1009369c..b6f4014a 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -201,7 +201,7 @@ rsa==4.9 # via google-auth six==1.16.0 # via python-dateutil -sqlparse==0.4.4 +sqlparse==0.5.0 # via # -r requirements/requirements.in # django From 592984795c3c36e7434b10cc688febdd3012ba8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 23:26:19 +0000 Subject: [PATCH 46/60] Bump sqlparse from 0.4.4 to 0.5.0 in /requirements Bumps [sqlparse](https://github.com/andialbrecht/sqlparse) from 0.4.4 to 0.5.0. - [Changelog](https://github.com/andialbrecht/sqlparse/blob/master/CHANGELOG) - [Commits](https://github.com/andialbrecht/sqlparse/compare/0.4.4...0.5.0) --- updated-dependencies: - dependency-name: sqlparse dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 86 +++++++++++++++---------------- requirements/requirements.txt | 62 +++++++++++----------- 2 files changed, 74 insertions(+), 74 deletions(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index f2991d16..c906820f 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -8,7 +8,7 @@ alabaster==0.7.13 # via sphinx asgiref==3.7.2 # via - # -c requirements/requirements.txt + # -c requirements.txt # django astroid==3.0.3 # via pylint @@ -20,25 +20,25 @@ backcall==0.2.0 # via ipython backports-zoneinfo==0.2.1 # via - # -c requirements/requirements.txt + # -c requirements.txt # django black==22.12.0 - # via -r requirements/dev-requirements.in + # via -r dev-requirements.in certifi==2023.11.17 # via - # -c requirements/requirements.txt - # -c requirements/test-requirements.txt + # -c requirements.txt + # -c test-requirements.txt # requests cfgv==3.4.0 # via pre-commit charset-normalizer==3.3.2 # via - # -c requirements/requirements.txt - # -c requirements/test-requirements.txt + # -c requirements.txt + # -c test-requirements.txt # requests click==8.1.3 # via - # -c requirements/requirements.txt + # -c requirements.txt # black colorama==0.4.6 # via sphinx-autobuild @@ -52,14 +52,14 @@ distlib==0.3.8 # via virtualenv django==4.2.11 # via - # -c requirements/requirements.txt + # -c requirements.txt # django-debug-toolbar # django-stubs # django-stubs-ext django-debug-toolbar==4.3.0 - # via -r requirements/dev-requirements.in + # via -r dev-requirements.in django-stubs==4.2.7 - # via -r requirements/dev-requirements.in + # via -r dev-requirements.in django-stubs-ext==4.2.7 # via django-stubs docutils==0.20.1 @@ -70,25 +70,25 @@ filelock==3.13.1 # via virtualenv flake8==7.0.0 # via - # -r requirements/dev-requirements.in + # -r dev-requirements.in # flake8-isort flake8-isort==6.1.1 - # via -r requirements/dev-requirements.in + # via -r dev-requirements.in identify==2.5.34 # via pre-commit idna==3.3 # via - # -c requirements/requirements.txt - # -c requirements/test-requirements.txt + # -c requirements.txt + # -c test-requirements.txt # requests imagesize==1.4.1 # via sphinx importlib-metadata==7.0.0 # via - # -c requirements/requirements.txt + # -c requirements.txt # sphinx ipdb==0.13.13 - # via -r requirements/dev-requirements.in + # via -r dev-requirements.in ipython==8.12.3 # via ipdb isort==5.13.2 @@ -112,7 +112,7 @@ mccabe==0.7.0 # flake8 # pylint mypy==1.9.0 - # via -r requirements/dev-requirements.in + # via -r dev-requirements.in mypy-extensions==1.0.0 # via # black @@ -121,8 +121,8 @@ nodeenv==1.8.0 # via pre-commit packaging==21.3 # via - # -c requirements/requirements.txt - # -c requirements/test-requirements.txt + # -c requirements.txt + # -c test-requirements.txt # sphinx parso==0.8.3 # via jedi @@ -138,7 +138,7 @@ platformdirs==4.1.0 # pylint # virtualenv pre-commit==3.5.0 - # via -r requirements/dev-requirements.in + # via -r dev-requirements.in prompt-toolkit==3.0.43 # via ipython ptyprocess==0.7.0 @@ -158,41 +158,41 @@ pylint==3.0.3 # pylint-django # pylint-plugin-utils pylint-django==2.5.5 - # via -r requirements/dev-requirements.in + # via -r dev-requirements.in pylint-plugin-utils==0.8.2 # via pylint-django pyparsing==3.1.1 # via - # -c requirements/requirements.txt - # -c requirements/test-requirements.txt + # -c requirements.txt + # -c test-requirements.txt # packaging pytz==2023.3.post1 # via - # -c requirements/requirements.txt + # -c requirements.txt # babel pyyaml==6.0.1 # via - # -c requirements/test-requirements.txt + # -c test-requirements.txt # pre-commit requests==2.31.0 # via - # -c requirements/requirements.txt - # -c requirements/test-requirements.txt + # -c requirements.txt + # -c test-requirements.txt # sphinx six==1.16.0 # via - # -c requirements/requirements.txt - # -c requirements/test-requirements.txt + # -c requirements.txt + # -c test-requirements.txt # asttokens # livereload snowballstemmer==2.2.0 # via sphinx sphinx==7.1.2 # via - # -r requirements/dev-requirements.in + # -r dev-requirements.in # sphinx-autobuild sphinx-autobuild==2021.3.14 - # via -r requirements/dev-requirements.in + # via -r dev-requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx sphinxcontrib-devhelp==1.0.2 @@ -205,17 +205,17 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -sqlparse==0.4.4 +sqlparse==0.5.0 # via - # -c requirements/requirements.txt + # -c requirements.txt # django # django-debug-toolbar stack-data==0.6.3 # via ipython tomli==2.0.1 # via - # -c requirements/requirements.txt - # -c requirements/test-requirements.txt + # -c requirements.txt + # -c test-requirements.txt # black # django-stubs # ipdb @@ -234,11 +234,11 @@ types-pytz==2024.1.0.20240203 types-pyyaml==6.0.12.12 # via django-stubs types-requests==2.31.0.20240406 - # via -r requirements/dev-requirements.in + # via -r dev-requirements.in typing-extensions==4.8.0 # via - # -c requirements/requirements.txt - # -c requirements/test-requirements.txt + # -c requirements.txt + # -c test-requirements.txt # asgiref # astroid # black @@ -249,8 +249,8 @@ typing-extensions==4.8.0 # pylint urllib3==2.1.0 # via - # -c requirements/requirements.txt - # -c requirements/test-requirements.txt + # -c requirements.txt + # -c test-requirements.txt # requests # types-requests virtualenv==20.25.1 @@ -258,10 +258,10 @@ virtualenv==20.25.1 wcwidth==0.2.13 # via prompt-toolkit werkzeug==3.0.2 - # via -r requirements/dev-requirements.in + # via -r dev-requirements.in zipp==3.17.0 # via - # -c requirements/requirements.txt + # -c requirements.txt # importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 1009369c..847056f7 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -5,7 +5,7 @@ # pip-compile requirements/requirements.in # argon2-cffi==23.1.0 - # via -r requirements/requirements.in + # via -r requirements.in argon2-cffi-bindings==21.2.0 # via argon2-cffi asgiref==3.7.2 @@ -25,7 +25,7 @@ cachetools==5.3.2 # via google-auth certifi==2023.11.17 # via - # -r requirements/requirements.in + # -r requirements.in # requests cffi==1.16.0 # via @@ -39,7 +39,7 @@ click==8.1.3 # via pip-tools crispy-bootstrap5==2024.2 # via - # -r requirements/requirements.in + # -r requirements.in # django-anvil-consortium-manager cryptography==42.0.5 # via pyjwt @@ -47,7 +47,7 @@ defusedxml==0.7.1 # via python3-openid django==4.2.11 # via - # -r requirements/requirements.in + # -r requirements.in # crispy-bootstrap5 # django-allauth # django-anvil-consortium-manager @@ -61,48 +61,48 @@ django==4.2.11 # django-picklefield # django-tables2 django-allauth==0.54.0 - # via -r requirements/requirements.in + # via -r requirements.in django-anvil-consortium-manager @ git+https://github.com/UW-GAC/django-anvil-consortium-manager.git@v0.22 - # via -r requirements/requirements.in + # via -r requirements.in django-autocomplete-light==3.11.0 # via django-anvil-consortium-manager django-constance==3.1.0 - # via -r requirements/requirements.in + # via -r requirements.in django-crispy-forms==2.1 # via - # -r requirements/requirements.in + # -r requirements.in # crispy-bootstrap5 # django-anvil-consortium-manager django-dbbackup==4.1.0 - # via -r requirements/requirements.in + # via -r requirements.in django-environ==0.10.0 - # via -r requirements/requirements.in + # via -r requirements.in django-extensions==3.2.3 # via - # -r requirements/requirements.in + # -r requirements.in # django-anvil-consortium-manager django-filter==23.5 # via django-anvil-consortium-manager django-htmx==1.17.3 - # via -r requirements/requirements.in + # via -r requirements.in django-login-required-middleware==0.9.0 - # via -r requirements/requirements.in + # via -r requirements.in django-maintenance-mode==0.21.1 - # via -r requirements/requirements.in + # via -r requirements.in django-model-utils==4.5.0 - # via -r requirements/requirements.in + # via -r requirements.in django-picklefield==3.2 # via - # -r requirements/requirements.in + # -r requirements.in # django-constance django-simple-history==3.5.0 # via - # -r requirements/requirements.in + # -r requirements.in # django-anvil-consortium-manager django-tables2==2.7.0 # via django-anvil-consortium-manager django-tree-queries==0.18.0 - # via -r requirements/requirements.in + # via -r requirements.in fastobo==0.12.3 # via pronto fontawesomefree==6.5.1 @@ -118,13 +118,13 @@ importlib-resources==6.1.1 # jsonschema # jsonschema-specifications jsonapi-requests==0.7.0 - # via -r requirements/requirements.in + # via -r requirements.in jsonschema==4.21.1 - # via -r requirements/requirements.in + # via -r requirements.in jsonschema-specifications==2023.12.1 # via jsonschema mysqlclient==2.2.4 - # via -r requirements/requirements.in + # via -r requirements.in networkx==3.1 # via # django-anvil-consortium-manager @@ -135,22 +135,22 @@ numpy==1.24.4 # pandas oauthlib==3.2.2 # via - # -r requirements/requirements.in + # -r requirements.in # requests-oauthlib packaging==21.3 # via # build # plotly pandas==2.0.3 - # via -r requirements/requirements.in + # via -r requirements.in pip-tools==7.4.1 - # via -r requirements/requirements.in + # via -r requirements.in pkgutil-resolve-name==1.3.10 # via jsonschema plotly==5.19.0 # via django-anvil-consortium-manager pronto==2.5.6 - # via -r requirements/requirements.in + # via -r requirements.in pyasn1==0.5.1 # via # pyasn1-modules @@ -186,7 +186,7 @@ referencing==0.33.0 # jsonschema-specifications requests==2.31.0 # via - # -r requirements/requirements.in + # -r requirements.in # django-allauth # django-anvil-consortium-manager # jsonapi-requests @@ -201,12 +201,12 @@ rsa==4.9 # via google-auth six==1.16.0 # via python-dateutil -sqlparse==0.4.4 +sqlparse==0.5.0 # via - # -r requirements/requirements.in + # -r requirements.in # django tablib==3.6.1 - # via -r requirements/requirements.in + # via -r requirements.in tenacity==8.2.3 # via # jsonapi-requests @@ -222,12 +222,12 @@ tzdata==2023.4 # via pandas urllib3==2.1.0 # via - # -r requirements/requirements.in + # -r requirements.in # requests wheel==0.42.0 # via pip-tools whitenoise==6.6.0 - # via -r requirements/requirements.in + # via -r requirements.in zipp==3.17.0 # via # importlib-metadata From a403383bf9de41b6d3e8140c4dd8da7cdd778190 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 22 Apr 2024 10:25:11 -0700 Subject: [PATCH 47/60] Add script to create test data for the phenotype inventoryw orkflow --- add_phenotype_inventory_input_example_data.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 add_phenotype_inventory_input_example_data.py diff --git a/add_phenotype_inventory_input_example_data.py b/add_phenotype_inventory_input_example_data.py new file mode 100644 index 00000000..427c6477 --- /dev/null +++ b/add_phenotype_inventory_input_example_data.py @@ -0,0 +1,50 @@ +# Temporary script to create some test data. +# Run with: python manage.py shell < add_phenotype_inventory_input_example_data.py + +from anvil_consortium_manager.tests.factories import ( + ManagedGroupFactory, + WorkspaceGroupSharingFactory, +) + +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 StudyFactory + +# Create a dbGaP workspace. +fhs = StudyFactory.create(short_name="FHS", full_name="Framingham Heart Study") +workspace_dbgap = dbGaPWorkspaceFactory.create( + dbgap_study_accession__dbgap_phs=7, + dbgap_study_accession__studies=[fhs], + dbgap_version=33, + dbgap_participant_set=12, + dbgap_consent_code=1, + dbgap_consent_abbreviation="HMB", + workspace__name="DBGAP_FHS_v33_p12_HMB", +) + + +# Create a CDSA workspace. +workspace_cdsa = CDSAWorkspaceFactory.create( + study__short_name="MESA", + workspace__name="CDSA_MESA_HMB", +) + +# Create an open access workspace +workspace_open_access = OpenAccessWorkspaceFactory.create( + workspace__name="OPEN_ACCESS_FHS", +) +workspace_open_access.studies.add(fhs) + + +# Share workspaces with PRIMED_ALL +primed_all = ManagedGroupFactory.create(name="PRIMED_ALL") +WorkspaceGroupSharingFactory.create( + workspace=workspace_dbgap.workspace, group=primed_all +) +WorkspaceGroupSharingFactory.create( + workspace=workspace_cdsa.workspace, group=primed_all +) +WorkspaceGroupSharingFactory.create( + workspace=workspace_open_access.workspace, group=primed_all +) From b93ceb57ab250389510a99b066477095a7c30245 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 22 Apr 2024 10:27:56 -0700 Subject: [PATCH 48/60] Add pre-commit.ci badge to README Note: before doing this, I gave pre-commit ci access to this repo starting at this page: https://results.pre-commit.ci/ --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 25a74495..d3a7f7e5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ [![Black code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/UW-GAC/gregor-django/main.svg)](https://results.pre-commit.ci/latest/github/UW-GAC/gregor-django/main) + [![image](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/) [![Built with Cookiecutter Django](https://img.shields.io/badge/built%20with-Cookiecutter%20Django-ff69b4.svg?logo=cookiecutter)](https://github.com/pydanny/cookiecutter-django/) From e00b147ef0371962c5212c8b88744432096f9c40 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 22 Apr 2024 10:41:39 -0700 Subject: [PATCH 49/60] Remove linting job from CI It's now being handled by pre-commit.ci --- .github/workflows/ci.yml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4570f6cf..6ff4177a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,27 +19,6 @@ concurrency: cancel-in-progress: true jobs: - linter: - runs-on: ubuntu-latest - steps: - - - name: Checkout Code Repository - uses: actions/checkout@v4 - - - name: Set up Python 3.9 - uses: actions/setup-python@v5 - with: - python-version: 3.9 - cache: pip - cache-dependency-path: | - requirements/requirements.txt - requirements/test-requirements.txt - - # Run all pre-commit hooks on all the files. - # Getting only staged files can be tricky in case a new PR is opened - # since the action is run on a branch in detached head state - - name: Install and Run Pre-commit - uses: pre-commit/action@v3.0.1 pytest-mariadb: runs-on: ubuntu-latest From e39fd01b7cbe36bc13077f4b736e402474f0d1dd Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 26 Apr 2024 16:07:56 -0700 Subject: [PATCH 50/60] Disable button and change text upon copy --- .../primed_anvil/phenotype_inventory_inputs.html | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/primed/templates/primed_anvil/phenotype_inventory_inputs.html b/primed/templates/primed_anvil/phenotype_inventory_inputs.html index 7a479f54..c043c36a 100644 --- a/primed/templates/primed_anvil/phenotype_inventory_inputs.html +++ b/primed/templates/primed_anvil/phenotype_inventory_inputs.html @@ -36,9 +36,16 @@
+
+ Input for the workspaces field + + + +

{{ workspaces_input }}

From 6feec36fd3b2f4838e2509416e454d3a9f180563 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 26 Apr 2024 16:11:51 -0700 Subject: [PATCH 52/60] Remove right-alignment, which doesn't work well with a wide page --- primed/templates/primed_anvil/phenotype_inventory_inputs.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/primed/templates/primed_anvil/phenotype_inventory_inputs.html b/primed/templates/primed_anvil/phenotype_inventory_inputs.html index f529ac42..7dc372ce 100644 --- a/primed/templates/primed_anvil/phenotype_inventory_inputs.html +++ b/primed/templates/primed_anvil/phenotype_inventory_inputs.html @@ -20,9 +20,7 @@

Phenotype inventory workflow

Input for the workspaces field - - - +

{{ workspaces_input }}
From 895880301646173a652e1a9b61df2f09b681535f Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 29 Apr 2024 14:29:14 -0700 Subject: [PATCH 53/60] Sort names of studies in phenotype inventory workflow helper Mostly for MySQL. --- primed/primed_anvil/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primed/primed_anvil/helpers.py b/primed/primed_anvil/helpers.py index 2544810c..e0f6cacf 100644 --- a/primed/primed_anvil/helpers.py +++ b/primed/primed_anvil/helpers.py @@ -220,6 +220,6 @@ def get_workspaces_for_phenotype_inventory(): study_names = [x["study_names"] if x["study_names"] else "" for x in group] if not study_names: study_names = "" - json[key] = ", ".join(study_names) + json[key] = ", ".join(sorted(study_names)) return json From f0c23278cb04324992aa8dc8116ade858dc5eb03 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 29 Apr 2024 14:31:58 -0700 Subject: [PATCH 54/60] Try upgrading flake8 in pre-commit file --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 842469d1..3b9bd274 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: isort - repo: https://github.com/PyCQA/flake8 - rev: 3.9.2 + rev: 7.0.0 hooks: - id: flake8 args: ['--config=setup.cfg'] From 2f8c5c0620751e7c23ba94d8e5d2dc6f930205ff Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 29 Apr 2024 15:05:15 -0700 Subject: [PATCH 55/60] Add ruff to requirements file --- requirements/dev-requirements.in | 1 + requirements/dev-requirements.txt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/requirements/dev-requirements.in b/requirements/dev-requirements.in index 54932d32..37e02e6e 100644 --- a/requirements/dev-requirements.in +++ b/requirements/dev-requirements.in @@ -22,6 +22,7 @@ flake8-isort # https://github.com/gforcada/flake8-isort black # https://github.com/psf/black pylint-django # https://github.com/PyCQA/pylint-django pre-commit # https://github.com/pre-commit/pre-commit +ruff # https://github.com/astral-sh/ruff # Django # ------------------------------------------------------------------------------ diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index c906820f..133d6577 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -179,6 +179,8 @@ requests==2.31.0 # -c requirements.txt # -c test-requirements.txt # sphinx +ruff==0.4.2 + # via -r requirements/dev-requirements.in six==1.16.0 # via # -c requirements.txt From 34653daf7ccebaa4df3972dec259718f7ba50cc7 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 29 Apr 2024 15:18:47 -0700 Subject: [PATCH 56/60] Run ruff check --fix on all files --- primed/cdsa/tests/test_migrations.py | 4 ---- primed/miscellaneous_workspaces/tests/test_migrations.py | 9 +-------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/primed/cdsa/tests/test_migrations.py b/primed/cdsa/tests/test_migrations.py index d355392c..02351d33 100644 --- a/primed/cdsa/tests/test_migrations.py +++ b/primed/cdsa/tests/test_migrations.py @@ -1,11 +1,7 @@ """Tests for data migrations in the app.""" -from datetime import date -from anvil_consortium_manager.tests.factories import BillingProjectFactory, WorkspaceFactory from django_test_migrations.contrib.unittest_case import MigratorTestCase -import factory -from primed.primed_anvil.tests.factories import StudySiteFactory class AgreementMajorVersionMigrationsTest(MigratorTestCase): diff --git a/primed/miscellaneous_workspaces/tests/test_migrations.py b/primed/miscellaneous_workspaces/tests/test_migrations.py index 51a1afa5..e5c4d2df 100644 --- a/primed/miscellaneous_workspaces/tests/test_migrations.py +++ b/primed/miscellaneous_workspaces/tests/test_migrations.py @@ -1,11 +1,7 @@ """Tests for migrations in the miscellaneous_workspaces app.""" -from anvil_consortium_manager.tests.factories import BillingProjectFactory, WorkspaceFactory from django_test_migrations.contrib.unittest_case import MigratorTestCase -from primed.users.tests.factories import UserFactory -import factory -from . import factories class ExampleToResourceWorkspaceForwardMigrationTest(MigratorTestCase): """Tests for the migrations associated with renaming the ExampleWorkspace to ResourceWorkspace.""" @@ -53,7 +49,7 @@ def prepare(self): def test_workspace_updates(self): """Test updates to the workspace model.""" Workspace = self.new_state.apps.get_model("anvil_consortium_manager", "Workspace") - ResourceWorkspace = self.new_state.apps.get_model("miscellaneous_workspaces", "ResourceWorkspace") + self.new_state.apps.get_model("miscellaneous_workspaces", "ResourceWorkspace") workspace = Workspace.objects.get(pk=self.workspace_1.pk) self.assertEqual(workspace.workspace_type, "resource") workspace.full_clean() @@ -66,7 +62,6 @@ def test_workspace_updates(self): def test_resource_workspace_updates(self): """Test updates to the ResourceWorkspace model.""" - Workspace = self.new_state.apps.get_model("anvil_consortium_manager", "Workspace") ResourceWorkspace = self.new_state.apps.get_model("miscellaneous_workspaces", "ResourceWorkspace") resource_workspace = ResourceWorkspace.objects.get(pk=self.example_workspace_1.pk) resource_workspace.full_clean() @@ -135,7 +130,6 @@ def prepare(self): def test_workspace_updates(self): """Test updates to the workspace model.""" Workspace = self.new_state.apps.get_model("anvil_consortium_manager", "Workspace") - ExampleWorkspace = self.new_state.apps.get_model("miscellaneous_workspaces", "ExampleWorkspace") workspace = Workspace.objects.get(pk=self.workspace_1.pk) self.assertEqual(workspace.workspace_type, "example") workspace.full_clean() @@ -148,7 +142,6 @@ def test_workspace_updates(self): def test_resource_workspace_updates(self): """Test updates to the ResourceWorkspace model.""" - Workspace = self.new_state.apps.get_model("anvil_consortium_manager", "Workspace") ExampleWorkspace = self.new_state.apps.get_model("miscellaneous_workspaces", "ExampleWorkspace") example_workspace = ExampleWorkspace.objects.get(pk=self.resource_workspace_1.pk) example_workspace.full_clean() From 0f2ae44a9e5005ef1b30c5739cf5dedd47d5ed9e Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 29 Apr 2024 15:30:38 -0700 Subject: [PATCH 57/60] Add ruff configuration and rerun ruff check --- .ruff.toml | 20 +++++++++++ ...ter_cdsaworkspace_disease_term_and_more.py | 35 +++++++++++++++++++ primed/cdsa/models.py | 6 ++-- primed/cdsa/tests/test_migrations.py | 1 - ...er_dbgapworkspace_disease_term_and_more.py | 35 +++++++++++++++++++ primed/dbgap/models.py | 18 +++++----- primed/duo/models.py | 2 +- 7 files changed, 103 insertions(+), 14 deletions(-) create mode 100644 .ruff.toml create mode 100644 primed/cdsa/migrations/0023_alter_cdsaworkspace_disease_term_and_more.py create mode 100644 primed/dbgap/migrations/0012_alter_dbgapworkspace_disease_term_and_more.py diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 00000000..78a87d59 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,20 @@ +line-length = 120 +exclude = [ + "**/migrations/**", + "*/static/CACHE/*", + "venv", + "docs", +] + +[lint] +extend-select = [ + "E", + "F", + "W", # pycodestyle warnings + "I", # isort + "DJ", # flake8-django + "E501", # line-too-long +] + +[lint.isort] +known-first-party = ["primed", "config", ] diff --git a/primed/cdsa/migrations/0023_alter_cdsaworkspace_disease_term_and_more.py b/primed/cdsa/migrations/0023_alter_cdsaworkspace_disease_term_and_more.py new file mode 100644 index 00000000..88e103b5 --- /dev/null +++ b/primed/cdsa/migrations/0023_alter_cdsaworkspace_disease_term_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.11 on 2024-04-29 22:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("cdsa", "0022_remove_signedagreement_is_primary"), + ] + + operations = [ + migrations.AlterField( + model_name="cdsaworkspace", + name="disease_term", + field=models.CharField( + blank=True, + default="", + help_text="The disease term if required by data_use_permission.", + max_length=255, + verbose_name="DUO disease term", + ), + ), + migrations.AlterField( + model_name="historicalcdsaworkspace", + name="disease_term", + field=models.CharField( + blank=True, + default="", + help_text="The disease term if required by data_use_permission.", + max_length=255, + verbose_name="DUO disease term", + ), + ), + ] diff --git a/primed/cdsa/models.py b/primed/cdsa/models.py index 4ba3cdec..6c66ed23 100644 --- a/primed/cdsa/models.py +++ b/primed/cdsa/models.py @@ -175,6 +175,9 @@ class SignedAgreement( def __str__(self): return "{}".format(self.cc_id) + def get_absolute_url(self): + return self.get_agreement_type().get_absolute_url() + @property def combined_type(self): combined_type = self.get_type_display() @@ -187,9 +190,6 @@ def combined_type(self): combined_type = combined_type + " component" return combined_type - def get_absolute_url(self): - return self.get_agreement_type().get_absolute_url() - def get_agreement_type(self): if self.type == self.MEMBER: return self.memberagreement diff --git a/primed/cdsa/tests/test_migrations.py b/primed/cdsa/tests/test_migrations.py index 02351d33..7a609516 100644 --- a/primed/cdsa/tests/test_migrations.py +++ b/primed/cdsa/tests/test_migrations.py @@ -3,7 +3,6 @@ from django_test_migrations.contrib.unittest_case import MigratorTestCase - class AgreementMajorVersionMigrationsTest(MigratorTestCase): """Tests for the migrations associated with creating the new AgreementMajorVersion model.""" diff --git a/primed/dbgap/migrations/0012_alter_dbgapworkspace_disease_term_and_more.py b/primed/dbgap/migrations/0012_alter_dbgapworkspace_disease_term_and_more.py new file mode 100644 index 00000000..2ea3c8da --- /dev/null +++ b/primed/dbgap/migrations/0012_alter_dbgapworkspace_disease_term_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.11 on 2024-04-29 22:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("dbgap", "0011_gsr_restricted_help_and_verbose"), + ] + + operations = [ + migrations.AlterField( + model_name="dbgapworkspace", + name="disease_term", + field=models.CharField( + blank=True, + default="", + help_text="The disease term if required by data_use_permission.", + max_length=255, + verbose_name="DUO disease term", + ), + ), + migrations.AlterField( + model_name="historicaldbgapworkspace", + name="disease_term", + field=models.CharField( + blank=True, + default="", + help_text="The disease term if required by data_use_permission.", + max_length=255, + verbose_name="DUO disease term", + ), + ), + ] diff --git a/primed/dbgap/models.py b/primed/dbgap/models.py index d9c1d560..0260a9ce 100644 --- a/primed/dbgap/models.py +++ b/primed/dbgap/models.py @@ -226,6 +226,15 @@ def __str__(self): """String method.""" return "{}".format(self.created) + def get_absolute_url(self): + return reverse( + "dbgap:dbgap_applications:dbgap_data_access_snapshots:detail", + kwargs={ + "dbgap_project_id": self.dbgap_application.dbgap_project_id, + "dbgap_data_access_snapshot_pk": self.pk, + }, + ) + def clean(self): """Perform custom model cleaning. @@ -248,15 +257,6 @@ def clean(self): "Project_id in JSON does not match dbgap_application.dbgap_project_id." ) - def get_absolute_url(self): - return reverse( - "dbgap:dbgap_applications:dbgap_data_access_snapshots:detail", - kwargs={ - "dbgap_project_id": self.dbgap_application.dbgap_project_id, - "dbgap_data_access_snapshot_pk": self.pk, - }, - ) - def create_dars_from_json(self): """Add DARs for this application from the dbGaP json for this project snapshot. diff --git a/primed/duo/models.py b/primed/duo/models.py index 7c6efb25..9b3414e9 100644 --- a/primed/duo/models.py +++ b/primed/duo/models.py @@ -90,7 +90,7 @@ class DataUseOntologyModel(models.Model): verbose_name="DUO disease term", max_length=255, blank=True, - null=True, + default="", help_text="The disease term if required by data_use_permission.", ) From 5f2e9b8cecac416960c14c3e09d809e82f4b7a9a Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 29 Apr 2024 15:38:10 -0700 Subject: [PATCH 58/60] Run ruff format on all files --- add_cdsa_example_data.py | 44 +- add_collaborative_analysis_example_data.py | 32 +- add_dbgap_example_data.py | 28 +- add_phenotype_inventory_input_example_data.py | 12 +- config/settings/base.py | 20 +- config/settings/local.py | 12 +- config/settings/production.py | 19 +- config/urls.py | 12 +- config/wsgi.py | 1 + primed/__init__.py | 7 +- primed/cdsa/adapters.py | 16 +- primed/cdsa/audit/signed_agreement_audit.py | 28 +- primed/cdsa/audit/workspace_audit.py | 27 +- primed/cdsa/forms.py | 4 +- .../cdsa/management/commands/cdsa_records.py | 6 +- .../management/commands/run_cdsa_audit.py | 20 +- primed/cdsa/models.py | 34 +- primed/cdsa/tables.py | 30 +- primed/cdsa/tests/factories.py | 24 +- primed/cdsa/tests/test_audit.py | 172 +-- primed/cdsa/tests/test_commands.py | 82 +- primed/cdsa/tests/test_forms.py | 32 +- primed/cdsa/tests/test_migrations.py | 12 +- primed/cdsa/tests/test_models.py | 140 +- primed/cdsa/tests/test_tables.py | 104 +- primed/cdsa/tests/test_views.py | 1309 +++++------------ primed/cdsa/urls.py | 4 +- primed/cdsa/views.py | 170 +-- primed/collaborative_analysis/adapters.py | 4 +- primed/collaborative_analysis/audit.py | 33 +- .../run_collaborative_analysis_audit.py | 12 +- primed/collaborative_analysis/models.py | 1 - .../collaborative_analysis/tests/factories.py | 8 +- .../tests/test_audit.py | 192 +-- .../tests/test_commands.py | 28 +- .../tests/test_tables.py | 16 +- .../tests/test_views.py | 332 ++--- primed/collaborative_analysis/views.py | 50 +- primed/dbgap/adapters.py | 12 +- primed/dbgap/audit.py | 40 +- primed/dbgap/constants.py | 4 +- primed/dbgap/forms.py | 16 +- .../management/commands/run_dbgap_audit.py | 12 +- primed/dbgap/models.py | 48 +- primed/dbgap/tables.py | 18 +- primed/dbgap/tests/factories.py | 20 +- primed/dbgap/tests/test_audit.py | 72 +- primed/dbgap/tests/test_commands.py | 28 +- primed/dbgap/tests/test_forms.py | 40 +- primed/dbgap/tests/test_models.py | 132 +- primed/dbgap/tests/test_tables.py | 97 +- primed/dbgap/tests/test_views.py | 1245 ++++------------ primed/dbgap/urls.py | 4 +- primed/dbgap/views.py | 156 +- primed/drupal_oauth_provider/provider.py | 5 +- primed/drupal_oauth_provider/views.py | 17 +- primed/duo/management/commands/load_duo.py | 21 +- primed/duo/models.py | 7 +- primed/duo/tests/test_commands.py | 8 +- primed/duo/tests/test_models.py | 60 +- primed/duo/tests/test_views.py | 16 +- primed/duo/views.py | 10 +- primed/miscellaneous_workspaces/adapters.py | 16 +- primed/miscellaneous_workspaces/models.py | 12 +- primed/miscellaneous_workspaces/tables.py | 8 +- .../tests/test_forms.py | 12 +- .../tests/test_models.py | 50 +- .../tests/test_views.py | 240 +-- primed/primed_anvil/adapters.py | 4 +- primed/primed_anvil/audit.py | 12 +- primed/primed_anvil/helpers.py | 4 +- primed/primed_anvil/models.py | 8 +- primed/primed_anvil/tables.py | 18 +- primed/primed_anvil/tests/test_adapters.py | 28 +- primed/primed_anvil/tests/test_audit.py | 2 +- primed/primed_anvil/tests/test_helpers.py | 96 +- primed/primed_anvil/tests/test_models.py | 8 +- primed/primed_anvil/tests/test_tables.py | 5 +- primed/primed_anvil/tests/test_views.py | 321 +--- primed/primed_anvil/views.py | 42 +- primed/users/adapters.py | 16 +- primed/users/admin.py | 1 - primed/users/audit.py | 82 +- primed/users/forms.py | 4 +- primed/users/tests/factories.py | 1 - primed/users/tests/test_adapters.py | 25 +- primed/users/tests/test_audit.py | 75 +- primed/users/tests/test_forms.py | 2 +- primed/users/tests/test_urls.py | 5 +- primed/users/tests/test_views.py | 91 +- primed/users/views.py | 3 - 91 files changed, 1558 insertions(+), 4798 deletions(-) diff --git a/add_cdsa_example_data.py b/add_cdsa_example_data.py index da5c0058..905c3165 100644 --- a/add_cdsa_example_data.py +++ b/add_cdsa_example_data.py @@ -29,12 +29,8 @@ major_version = factories.AgreementMajorVersionFactory.create(version=1) # Create some agreement versions -v10 = factories.AgreementVersionFactory.create( - major_version=major_version, minor_version=0 -) -v11 = factories.AgreementVersionFactory.create( - major_version=major_version, minor_version=1 -) +v10 = factories.AgreementVersionFactory.create(major_version=major_version, minor_version=0) +v11 = factories.AgreementVersionFactory.create(major_version=major_version, minor_version=1) # Create a couple signed CDSAs. dup = DataUsePermission.objects.get(abbreviation="GRU") @@ -57,9 +53,7 @@ signed_agreement__version=v10, study_site=StudySite.objects.get(short_name="CC"), ) -GroupGroupMembershipFactory.create( - parent_group=cdsa_group, child_group=cdsa_1001.signed_agreement.anvil_access_group -) +GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1001.signed_agreement.anvil_access_group) cdsa_1002 = factories.MemberAgreementFactory.create( signed_agreement__cc_id=1002, @@ -70,9 +64,7 @@ signed_agreement__version=v10, study_site=StudySite.objects.get(short_name="CARDINAL"), ) -GroupGroupMembershipFactory.create( - parent_group=cdsa_group, child_group=cdsa_1002.signed_agreement.anvil_access_group -) +GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1002.signed_agreement.anvil_access_group) cdsa_1003 = factories.MemberAgreementFactory.create( signed_agreement__cc_id=1003, @@ -83,9 +75,7 @@ signed_agreement__version=v10, study_site=StudySite.objects.get(short_name="CARDINAL"), ) -GroupGroupMembershipFactory.create( - parent_group=cdsa_group, child_group=cdsa_1003.signed_agreement.anvil_access_group -) +GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1003.signed_agreement.anvil_access_group) cdsa_1004 = factories.MemberAgreementFactory.create( signed_agreement__cc_id=1004, @@ -96,9 +86,7 @@ signed_agreement__version=v11, study_site=StudySite.objects.get(short_name="CARDINAL"), ) -GroupGroupMembershipFactory.create( - parent_group=cdsa_group, child_group=cdsa_1004.signed_agreement.anvil_access_group -) +GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1004.signed_agreement.anvil_access_group) cdsa_1005 = factories.DataAffiliateAgreementFactory.create( signed_agreement__cc_id=1005, @@ -108,9 +96,7 @@ study=Study.objects.get(short_name="Amish"), signed_agreement__version=v10, ) -GroupGroupMembershipFactory.create( - parent_group=cdsa_group, child_group=cdsa_1005.signed_agreement.anvil_access_group -) +GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1005.signed_agreement.anvil_access_group) cdsa_1006 = factories.DataAffiliateAgreementFactory.create( signed_agreement__cc_id=1006, @@ -122,9 +108,7 @@ additional_limitations="This data can only be used for testing the app.", requires_study_review=True, ) -GroupGroupMembershipFactory.create( - parent_group=cdsa_group, child_group=cdsa_1006.signed_agreement.anvil_access_group -) +GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1006.signed_agreement.anvil_access_group) cdsa_1007 = factories.DataAffiliateAgreementFactory.create( signed_agreement__cc_id=1007, @@ -135,9 +119,7 @@ study=Study.objects.get(short_name="MESA"), signed_agreement__version=v10, ) -GroupGroupMembershipFactory.create( - parent_group=cdsa_group, child_group=cdsa_1007.signed_agreement.anvil_access_group -) +GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1007.signed_agreement.anvil_access_group) cdsa_1008 = factories.DataAffiliateAgreementFactory.create( signed_agreement__cc_id=1008, @@ -148,9 +130,7 @@ study=Study.objects.get(short_name="MESA"), signed_agreement__version=v10, ) -GroupGroupMembershipFactory.create( - parent_group=cdsa_group, child_group=cdsa_1008.signed_agreement.anvil_access_group -) +GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1008.signed_agreement.anvil_access_group) cdsa_1009 = factories.NonDataAffiliateAgreementFactory.create( signed_agreement__cc_id=1009, @@ -236,8 +216,6 @@ cdsa_workspace_3 = factories.CDSAWorkspaceFactory.create( workspace__billing_project__name="demo-primed-cdsa", workspace__name="DEMO_PRIMED_CDSA_ARIC_1", - study=Study.objects.create( - short_name="ARIC", full_name="Atherosclerosis Risk in Communities" - ), + study=Study.objects.create(short_name="ARIC", full_name="Atherosclerosis Risk in Communities"), data_use_permission=dup, ) diff --git a/add_collaborative_analysis_example_data.py b/add_collaborative_analysis_example_data.py index 550e9317..39583776 100644 --- a/add_collaborative_analysis_example_data.py +++ b/add_collaborative_analysis_example_data.py @@ -55,27 +55,15 @@ # Add accounts to the auth domains. -account_1 = AccountFactory.create( - user__name="Adrienne", verified=True, email="adrienne@example.com" -) -account_2 = AccountFactory.create( - user__name="Ben", verified=True, email="ben@example.com" -) -account_3 = AccountFactory.create( - user__name="Matt", verified=True, email="matt@example.com" -) -account_4 = AccountFactory.create( - user__name="Stephanie", verified=True, email="stephanie@example.com" -) +account_1 = AccountFactory.create(user__name="Adrienne", verified=True, email="adrienne@example.com") +account_2 = AccountFactory.create(user__name="Ben", verified=True, email="ben@example.com") +account_3 = AccountFactory.create(user__name="Matt", verified=True, email="matt@example.com") +account_4 = AccountFactory.create(user__name="Stephanie", verified=True, email="stephanie@example.com") # Set up collab analysis workspace one # analyst group -GroupAccountMembershipFactory.create( - account=account_1, group=collaborative_analysis_workspace_1.analyst_group -) -GroupAccountMembershipFactory.create( - account=account_2, group=collaborative_analysis_workspace_1.analyst_group -) +GroupAccountMembershipFactory.create(account=account_1, group=collaborative_analysis_workspace_1.analyst_group) +GroupAccountMembershipFactory.create(account=account_2, group=collaborative_analysis_workspace_1.analyst_group) # auth domains GroupAccountMembershipFactory.create( account=account_1, @@ -84,12 +72,8 @@ # Set up collab analysis workspace two # analyst group -GroupAccountMembershipFactory.create( - account=account_3, group=collaborative_analysis_workspace_2.analyst_group -) -GroupAccountMembershipFactory.create( - account=account_4, group=collaborative_analysis_workspace_2.analyst_group -) +GroupAccountMembershipFactory.create(account=account_3, group=collaborative_analysis_workspace_2.analyst_group) +GroupAccountMembershipFactory.create(account=account_4, group=collaborative_analysis_workspace_2.analyst_group) # auth domains GroupAccountMembershipFactory.create( account=account_3, diff --git a/add_dbgap_example_data.py b/add_dbgap_example_data.py index 03b91535..4075ad82 100644 --- a/add_dbgap_example_data.py +++ b/add_dbgap_example_data.py @@ -10,23 +10,13 @@ # Studies fhs = StudyFactory.create(short_name="FHS", full_name="Framingham Heart Study") -mesa = StudyFactory.create( - short_name="MESA", full_name="Multi-Ethnic Study of Atherosclerosis" -) -aric = StudyFactory.create( - short_name="ARIC", full_name="Atherosclerosis Risk in Communities" -) +mesa = StudyFactory.create(short_name="MESA", full_name="Multi-Ethnic Study of Atherosclerosis") +aric = StudyFactory.create(short_name="ARIC", full_name="Atherosclerosis Risk in Communities") # dbGaP study accessions -dbgap_study_accession_fhs = factories.dbGaPStudyAccessionFactory.create( - dbgap_phs=7, studies=[fhs] -) -dbgap_study_accession_mesa = factories.dbGaPStudyAccessionFactory.create( - dbgap_phs=209, studies=[mesa] -) -dbgap_study_accession_aric = factories.dbGaPStudyAccessionFactory.create( - dbgap_phs=280, studies=[aric] -) +dbgap_study_accession_fhs = factories.dbGaPStudyAccessionFactory.create(dbgap_phs=7, studies=[fhs]) +dbgap_study_accession_mesa = factories.dbGaPStudyAccessionFactory.create(dbgap_phs=209, studies=[mesa]) +dbgap_study_accession_aric = factories.dbGaPStudyAccessionFactory.create(dbgap_phs=280, studies=[aric]) # Create some dbGaP workspaces. @@ -78,9 +68,7 @@ dbgap_project_id=33119, ) # Add a snapshot -dar_snapshot_1 = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application_1 -) +dar_snapshot_1 = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application_1) # Add some data access requests. dar_1_1 = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( dbgap_workspace=workspace_fhs_1, @@ -112,9 +100,7 @@ dbgap_project_id=33371, ) # Add a snapshot -dar_snapshot_2 = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application_2 -) +dar_snapshot_2 = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application_2) # Add some data access requests, only for FHS. dar_1_1 = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( dbgap_workspace=workspace_fhs_1, diff --git a/add_phenotype_inventory_input_example_data.py b/add_phenotype_inventory_input_example_data.py index 427c6477..fad9de8d 100644 --- a/add_phenotype_inventory_input_example_data.py +++ b/add_phenotype_inventory_input_example_data.py @@ -39,12 +39,6 @@ # Share workspaces with PRIMED_ALL primed_all = ManagedGroupFactory.create(name="PRIMED_ALL") -WorkspaceGroupSharingFactory.create( - workspace=workspace_dbgap.workspace, group=primed_all -) -WorkspaceGroupSharingFactory.create( - workspace=workspace_cdsa.workspace, group=primed_all -) -WorkspaceGroupSharingFactory.create( - workspace=workspace_open_access.workspace, group=primed_all -) +WorkspaceGroupSharingFactory.create(workspace=workspace_dbgap.workspace, group=primed_all) +WorkspaceGroupSharingFactory.create(workspace=workspace_cdsa.workspace, group=primed_all) +WorkspaceGroupSharingFactory.create(workspace=workspace_open_access.workspace, group=primed_all) diff --git a/config/settings/base.py b/config/settings/base.py index 01092f24..26b16d38 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -1,6 +1,7 @@ """ Base settings to build other settings files upon. """ + from pathlib import Path import environ @@ -135,9 +136,7 @@ ] # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" - }, + {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, @@ -264,12 +263,7 @@ LOGGING = { "version": 1, "disable_existing_loggers": False, - "formatters": { - "verbose": { - "format": "%(levelname)s %(asctime)s %(module)s " - "%(process)d %(thread)d %(message)s" - } - }, + "formatters": {"verbose": {"format": "%(levelname)s %(asctime)s %(module)s " "%(process)d %(thread)d %(message)s"}}, "handlers": { "console": { "level": "DEBUG", @@ -407,9 +401,5 @@ DRUPAL_API_CLIENT_ID = env("DRUPAL_API_CLIENT_ID", default="") DRUPAL_API_CLIENT_SECRET = env("DRUPAL_API_CLIENT_SECRET", default="") DRUPAL_API_REL_PATH = env("DRUPAL_API_REL_PATH", default="mockapi") -DRUPAL_DATA_AUDIT_DEACTIVATE_USERS = env( - "DRUPAL_DATA_AUDIT_DEACTIVATE_USERS", default=False -) -DRUPAL_DATA_AUDIT_REMOVE_USER_SITES = env( - "DRUPAL_DATA_AUDIT_REMOVE_USER_SITES", default=False -) +DRUPAL_DATA_AUDIT_DEACTIVATE_USERS = env("DRUPAL_DATA_AUDIT_DEACTIVATE_USERS", default=False) +DRUPAL_DATA_AUDIT_REMOVE_USER_SITES = env("DRUPAL_DATA_AUDIT_REMOVE_USER_SITES", default=False) diff --git a/config/settings/local.py b/config/settings/local.py index 789b1f6d..fe675bdc 100644 --- a/config/settings/local.py +++ b/config/settings/local.py @@ -26,9 +26,7 @@ # EMAIL # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend -EMAIL_BACKEND = env( - "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend" -) +EMAIL_BACKEND = env("DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend") # WhiteNoise # ------------------------------------------------------------------------------ @@ -73,10 +71,6 @@ # ANVIL_CDSA_GROUP_PREFIX = env( # "ANVIL_CDSA_GROUP_PREFIX", default="DEV_PRIMED_CDSA_ACCESS" # ) -ANVIL_DATA_ACCESS_GROUP_PREFIX = env( - "ANVIL_DATA_ACCESS_GROUP_PREFIX", default="DEV_PRIMED" -) +ANVIL_DATA_ACCESS_GROUP_PREFIX = env("ANVIL_DATA_ACCESS_GROUP_PREFIX", default="DEV_PRIMED") ANVIL_CDSA_GROUP_NAME = env("ANVIL_CDSA_GROUP_NAME", default="DEV_PRIMED_CDSA") -ANVIL_CC_ADMINS_GROUP_NAME = env( - "ANVIL_CC_ADMINS_GROUP_NAME", default="DEV_PRIMED_CC_ADMINS" -) +ANVIL_CC_ADMINS_GROUP_NAME = env("ANVIL_CC_ADMINS_GROUP_NAME", default="DEV_PRIMED_CC_ADMINS") diff --git a/config/settings/production.py b/config/settings/production.py index 311aa3e8..d325df2e 100644 --- a/config/settings/production.py +++ b/config/settings/production.py @@ -57,15 +57,11 @@ # in our apache configuration. Having in both places causes duplicate header SECURE_HSTS_SECONDS = 0 # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains -SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool( - "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True -) +SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool("DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True) # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True) # https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff -SECURE_CONTENT_TYPE_NOSNIFF = env.bool( - "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True -) +SECURE_CONTENT_TYPE_NOSNIFF = env.bool("DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True) # Since we have disabled HSTS above we get a warning when running check --deploy # we are manually silencing this as we have verified apache is enforcing # https://docs.djangoproject.com/en/dev/ref/checks/#security @@ -79,9 +75,7 @@ # EMAIL # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email -DEFAULT_FROM_EMAIL = env( - "DJANGO_DEFAULT_FROM_EMAIL", default="gac-django " -) +DEFAULT_FROM_EMAIL = env("DJANGO_DEFAULT_FROM_EMAIL", default="gac-django ") # https://docs.djangoproject.com/en/dev/ref/settings/#server-email SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL) # https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix @@ -130,12 +124,7 @@ "()": "maintenance_mode.logging.RequireNotMaintenanceMode503", }, }, - "formatters": { - "verbose": { - "format": "%(levelname)s %(asctime)s %(module)s " - "%(process)d %(thread)d %(message)s" - } - }, + "formatters": {"verbose": {"format": "%(levelname)s %(asctime)s %(module)s " "%(process)d %(thread)d %(message)s"}}, "handlers": { "mail_admins": { "level": "ERROR", diff --git a/config/urls.py b/config/urls.py index 139e04ec..9b091d3f 100644 --- a/config/urls.py +++ b/config/urls.py @@ -7,9 +7,7 @@ urlpatterns = [ path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), - path( - "about/", TemplateView.as_view(template_name="pages/about.html"), name="about" - ), + path("about/", TemplateView.as_view(template_name="pages/about.html"), name="about"), # Django Admin, use {% url 'admin:index' %} path(settings.ADMIN_URL, admin.site.urls), # User management @@ -20,17 +18,13 @@ "anvil/", include("anvil_consortium_manager.urls", namespace="anvil_consortium_manager"), ), - path( - "primed_anvil/", include("primed.primed_anvil.urls", namespace="primed_anvil") - ), + path("primed_anvil/", include("primed.primed_anvil.urls", namespace="primed_anvil")), path("dbgap/", include("primed.dbgap.urls", namespace="dbgap")), path("duo/", include("primed.duo.urls", namespace="duo")), path("cdsa/", include("primed.cdsa.urls", namespace="cdsa")), path( "collaborative_analysis/", - include( - "primed.collaborative_analysis.urls", namespace="collaborative_analysis" - ), + include("primed.collaborative_analysis.urls", namespace="collaborative_analysis"), ), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/config/wsgi.py b/config/wsgi.py index b1f3c94e..f2af68ea 100644 --- a/config/wsgi.py +++ b/config/wsgi.py @@ -13,6 +13,7 @@ framework. """ + import os import sys from pathlib import Path diff --git a/primed/__init__.py b/primed/__init__.py index e1d86152..eed836da 100644 --- a/primed/__init__.py +++ b/primed/__init__.py @@ -1,7 +1,2 @@ __version__ = "0.1.0" -__version_info__ = tuple( - [ - int(num) if num.isdigit() else num - for num in __version__.replace("-", ".", 1).split(".") - ] -) +__version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace("-", ".", 1).split(".")]) diff --git a/primed/cdsa/adapters.py b/primed/cdsa/adapters.py index a4767906..78e6f169 100644 --- a/primed/cdsa/adapters.py +++ b/primed/cdsa/adapters.py @@ -12,9 +12,7 @@ class CDSAWorkspaceAdapter(BaseWorkspaceAdapter): type = "cdsa" name = "CDSA workspace" - description = ( - "Workspaces containing data from the Consortium Data Sharing Agreement" - ) + description = "Workspaces containing data from the Consortium Data Sharing Agreement" list_table_class_staff_view = tables.CDSAWorkspaceStaffTable list_table_class_view = tables.CDSAWorkspaceUserTable workspace_form_class = WorkspaceForm @@ -24,15 +22,9 @@ class CDSAWorkspaceAdapter(BaseWorkspaceAdapter): def get_extra_detail_context_data(self, workspace, request): extra_context = {} - associated_data_prep = Workspace.objects.filter( - dataprepworkspace__target_workspace=workspace - ) - extra_context["associated_data_prep_workspaces"] = DataPrepWorkspaceUserTable( - associated_data_prep - ) - extra_context["data_prep_active"] = associated_data_prep.filter( - dataprepworkspace__is_active=True - ).exists() + associated_data_prep = Workspace.objects.filter(dataprepworkspace__target_workspace=workspace) + extra_context["associated_data_prep_workspaces"] = DataPrepWorkspaceUserTable(associated_data_prep) + extra_context["data_prep_active"] = associated_data_prep.filter(dataprepworkspace__is_active=True).exists() # Get the primary CDSA for this study, assuming it exists. try: extra_context["primary_cdsa"] = workspace.cdsaworkspace.get_primary_cdsa() diff --git a/primed/cdsa/audit/signed_agreement_audit.py b/primed/cdsa/audit/signed_agreement_audit.py index 669df722..24c09365 100644 --- a/primed/cdsa/audit/signed_agreement_audit.py +++ b/primed/cdsa/audit/signed_agreement_audit.py @@ -22,9 +22,7 @@ class AccessAuditResult(PRIMEDAuditResult): action: str = None def __post_init__(self): - self.anvil_cdsa_group = ManagedGroup.objects.get( - name=settings.ANVIL_CDSA_GROUP_NAME - ) + self.anvil_cdsa_group = ManagedGroup.objects.get(name=settings.ANVIL_CDSA_GROUP_NAME) def get_action_url(self): """The URL that handles the action needed.""" @@ -97,9 +95,7 @@ class SignedAgreementAccessAuditTable(tables.Table): agreement_type = tables.Column(accessor="signed_agreement__combined_type") agreement_version = tables.Column(accessor="signed_agreement__version") note = tables.Column() - action = tables.TemplateColumn( - template_name="cdsa/snippets/cdsa_signedagreement_audit_action_button.html" - ) + action = tables.TemplateColumn(template_name="cdsa/snippets/cdsa_signedagreement_audit_action_button.html") class Meta: attrs = {"class": "table align-middle"} @@ -132,9 +128,7 @@ def __init__(self, signed_agreement_queryset=None): isinstance(signed_agreement_queryset, QuerySet) and signed_agreement_queryset.model is models.SignedAgreement ): - raise ValueError( - "signed_agreement_queryset must be a queryset of SignedAgreement objects." - ) + raise ValueError("signed_agreement_queryset must be a queryset of SignedAgreement objects.") self.signed_agreement_queryset = signed_agreement_queryset def _audit_primary_agreement(self, signed_agreement): @@ -147,9 +141,7 @@ def _audit_primary_agreement(self, signed_agreement): This audit does *not* check if the AgreementMajorVersion associated with the SignedAgreement is valid. """ in_cdsa_group = signed_agreement.is_in_cdsa_group() - is_active = ( - signed_agreement.status == models.SignedAgreement.StatusChoices.ACTIVE - ) + is_active = signed_agreement.status == models.SignedAgreement.StatusChoices.ACTIVE if is_active: if in_cdsa_group: @@ -189,9 +181,7 @@ def _audit_primary_agreement(self, signed_agreement): # If we made it this far in audit, some other case happened - log it as an error. # Haven't figured out a test for this because it is unexpected. self.errors.append( # pragma: no cover - OtherError( - signed_agreement=signed_agreement, note=self.ERROR_OTHER_CASE - ) # pragma: no cover + OtherError(signed_agreement=signed_agreement, note=self.ERROR_OTHER_CASE) # pragma: no cover ) # pragma: no cover def _audit_component_agreement(self, signed_agreement): @@ -207,9 +197,7 @@ def _audit_component_agreement(self, signed_agreement): SignedAgreement or its component is valid. """ in_cdsa_group = signed_agreement.is_in_cdsa_group() - is_active = ( - signed_agreement.status == models.SignedAgreement.StatusChoices.ACTIVE - ) + is_active = signed_agreement.status == models.SignedAgreement.StatusChoices.ACTIVE # Get the set of potential primary agreements for this component. if hasattr(signed_agreement, "memberagreement"): @@ -303,9 +291,7 @@ def _audit_component_agreement(self, signed_agreement): # If we made it this far in audit, some other case happened - log it as an error. # Haven't figured out a test for this because it is unexpected. self.errors.append( # pragma: no cover - OtherError( - signed_agreement=signed_agreement, note=self.ERROR_OTHER_CASE - ) # pragma: no cover + OtherError(signed_agreement=signed_agreement, note=self.ERROR_OTHER_CASE) # pragma: no cover ) # pragma: no cover def _audit_signed_agreement(self, signed_agreement): diff --git a/primed/cdsa/audit/workspace_audit.py b/primed/cdsa/audit/workspace_audit.py index e81d8287..9c0bff96 100644 --- a/primed/cdsa/audit/workspace_audit.py +++ b/primed/cdsa/audit/workspace_audit.py @@ -22,9 +22,7 @@ class AccessAuditResult(PRIMEDAuditResult): action: str = None def __post_init__(self): - self.anvil_cdsa_group = ManagedGroup.objects.get( - name=settings.ANVIL_CDSA_GROUP_NAME - ) + self.anvil_cdsa_group = ManagedGroup.objects.get(name=settings.ANVIL_CDSA_GROUP_NAME) def get_action_url(self): """The URL that handles the action needed.""" @@ -96,13 +94,9 @@ class WorkspaceAccessAuditTable(tables.Table): workspace = tables.Column(linkify=True) data_affiliate_agreement = tables.Column(linkify=True) - agreement_version = tables.Column( - accessor="data_affiliate_agreement__signed_agreement__version" - ) + agreement_version = tables.Column(accessor="data_affiliate_agreement__signed_agreement__version") note = tables.Column() - action = tables.TemplateColumn( - template_name="cdsa/snippets/cdsa_workspace_audit_action_button.html" - ) + action = tables.TemplateColumn(template_name="cdsa/snippets/cdsa_workspace_audit_action_button.html") class Meta: attrs = {"class": "table align-middle"} @@ -125,9 +119,7 @@ class WorkspaceAccessAudit(PRIMEDAudit): def __init__(self, cdsa_workspace_queryset=None): # Store the CDSA group for auditing membership. - self.anvil_cdsa_group = ManagedGroup.objects.get( - name=settings.ANVIL_CDSA_GROUP_NAME - ) + self.anvil_cdsa_group = ManagedGroup.objects.get(name=settings.ANVIL_CDSA_GROUP_NAME) self.completed = False # Set up lists to hold audit results. self.verified = [] @@ -137,12 +129,9 @@ def __init__(self, cdsa_workspace_queryset=None): if cdsa_workspace_queryset is None: cdsa_workspace_queryset = models.CDSAWorkspace.objects.all() if not ( - isinstance(cdsa_workspace_queryset, QuerySet) - and cdsa_workspace_queryset.model is models.CDSAWorkspace + isinstance(cdsa_workspace_queryset, QuerySet) and cdsa_workspace_queryset.model is models.CDSAWorkspace ): - raise ValueError( - "cdsa_workspace_queryset must be a queryset of CDSAWorkspace objects." - ) + raise ValueError("cdsa_workspace_queryset must be a queryset of CDSAWorkspace objects.") self.cdsa_workspace_queryset = cdsa_workspace_queryset def _audit_workspace(self, workspace): @@ -152,9 +141,7 @@ def _audit_workspace(self, workspace): parent_group=auth_domain, child_group=self.anvil_cdsa_group, ).exists() - primary_qs = models.DataAffiliateAgreement.objects.filter( - study=workspace.study, is_primary=True - ) + primary_qs = models.DataAffiliateAgreement.objects.filter(study=workspace.study, is_primary=True) primary_exists = primary_qs.exists() if primary_exists: diff --git a/primed/cdsa/forms.py b/primed/cdsa/forms.py index 5ec7d116..b529f9da 100644 --- a/primed/cdsa/forms.py +++ b/primed/cdsa/forms.py @@ -22,9 +22,7 @@ class Meta: class SignedAgreementForm(Bootstrap5MediaFormMixin, forms.ModelForm): """Form for a SignedAgreement object.""" - version = forms.ModelChoiceField( - queryset=models.AgreementVersion.objects.filter(major_version__is_valid=True) - ) + version = forms.ModelChoiceField(queryset=models.AgreementVersion.objects.filter(major_version__is_valid=True)) class Meta: model = models.SignedAgreement diff --git a/primed/cdsa/management/commands/cdsa_records.py b/primed/cdsa/management/commands/cdsa_records.py index 78db2704..c6d4c27b 100644 --- a/primed/cdsa/management/commands/cdsa_records.py +++ b/primed/cdsa/management/commands/cdsa_records.py @@ -7,7 +7,6 @@ class Command(BaseCommand): - help = """Management command to generate CDSA records.""" def add_arguments(self, parser): @@ -23,7 +22,6 @@ def _export_table(self, table, filename): f.write(exporter.export()) def handle(self, *args, **options): - # Create directory. outdir = options["outdir"] try: @@ -40,9 +38,7 @@ def handle(self, *args, **options): ) # Studies. - self._export_table( - helpers.get_study_records_table(), os.path.join(outdir, "study_records.tsv") - ) + self._export_table(helpers.get_study_records_table(), os.path.join(outdir, "study_records.tsv")) # CDSA workspaces. self._export_table( diff --git a/primed/cdsa/management/commands/run_cdsa_audit.py b/primed/cdsa/management/commands/run_cdsa_audit.py index 8337f91a..1acd6761 100644 --- a/primed/cdsa/management/commands/run_cdsa_audit.py +++ b/primed/cdsa/management/commands/run_cdsa_audit.py @@ -23,11 +23,7 @@ def _audit_signed_agreements(self): data_access_audit.run_audit() # Construct the url for handling errors. - url = ( - "https://" - + Site.objects.get_current().domain - + reverse("cdsa:audit:signed_agreements:all") - ) + url = "https://" + Site.objects.get_current().domain + reverse("cdsa:audit:signed_agreements:all") self._report_results(data_access_audit, url) self._send_email(data_access_audit, url) @@ -37,11 +33,7 @@ def _audit_workspaces(self): data_access_audit.run_audit() # Construct the url for handling errors. - url = ( - "https://" - + Site.objects.get_current().domain - + reverse("cdsa:audit:workspaces:all") - ) + url = "https://" + Site.objects.get_current().domain + reverse("cdsa:audit:workspaces:all") self._report_results(data_access_audit, url) self._send_email(data_access_audit, url) @@ -55,15 +47,11 @@ def _report_results(self, data_access_audit, resolve_url): # Print results self.stdout.write("* Verified: {}".format(len(data_access_audit.verified))) - self.stdout.write( - "* Needs action: {}".format(len(data_access_audit.needs_action)) - ) + self.stdout.write("* Needs action: {}".format(len(data_access_audit.needs_action))) self.stdout.write("* Errors: {}".format(len(data_access_audit.errors))) if not audit_ok: - self.stdout.write( - self.style.ERROR(f"Please visit {resolve_url} to resolve these issues.") - ) + self.stdout.write(self.style.ERROR(f"Please visit {resolve_url} to resolve these issues.")) def _send_email(self, data_access_audit, url): # Send email if requested and there are problems. diff --git a/primed/cdsa/models.py b/primed/cdsa/models.py index 6c66ed23..e146c426 100644 --- a/primed/cdsa/models.py +++ b/primed/cdsa/models.py @@ -28,9 +28,7 @@ class AgreementMajorVersion(TimeStampedModel, models.Model): validators=[MinValueValidator(1)], unique=True, ) - is_valid = models.BooleanField( - default=True, help_text="Boolean indicator of whether this version is valid." - ) + is_valid = models.BooleanField(default=True, help_text="Boolean indicator of whether this version is valid.") history = HistoricalRecords() @@ -114,9 +112,7 @@ class StatusChoices(models.TextChoices): STATUS = StatusChoices.choices -class SignedAgreement( - TimeStampedModel, SignedAgreementStatusMixin, StatusModel, models.Model -): +class SignedAgreement(TimeStampedModel, SignedAgreementStatusMixin, StatusModel, models.Model): """Model to track verified, signed consortium data sharing agreements.""" MEMBER = "member" @@ -183,10 +179,7 @@ def combined_type(self): combined_type = self.get_type_display() if self.type == self.MEMBER and not self.get_agreement_type().is_primary: combined_type = combined_type + " component" - elif ( - self.type == self.DATA_AFFILIATE - and not self.get_agreement_type().is_primary - ): + elif self.type == self.DATA_AFFILIATE and not self.get_agreement_type().is_primary: combined_type = combined_type + " component" return combined_type @@ -217,9 +210,7 @@ class AgreementTypeModel(models.Model): AGREEMENT_TYPE = None ERROR_TYPE_DOES_NOT_MATCH = "The type of the SignedAgreement does not match the expected type for this model." - signed_agreement = models.OneToOneField( - SignedAgreement, on_delete=models.CASCADE, primary_key=True - ) + signed_agreement = models.OneToOneField(SignedAgreement, on_delete=models.CASCADE, primary_key=True) history = HistoricalRecords(inherit=True) class Meta: @@ -230,10 +221,7 @@ def __str__(self): def clean(self): """Ensure that the SignedAgreement type is correct for the class.""" - if ( - hasattr(self, "signed_agreement") - and self.signed_agreement.type != self.AGREEMENT_TYPE - ): + if hasattr(self, "signed_agreement") and self.signed_agreement.type != self.AGREEMENT_TYPE: raise ValidationError({"signed_agreement": self.ERROR_TYPE_DOES_NOT_MATCH}) def get_agreement_group(self): @@ -323,9 +311,7 @@ class NonDataAffiliateAgreement(TimeStampedModel, AgreementTypeModel, models.Mod AGREEMENT_TYPE = SignedAgreement.NON_DATA_AFFILIATE - affiliation = models.CharField( - max_length=255, help_text="The affiliation of the person signing this CDSA." - ) + affiliation = models.CharField(max_length=255, help_text="The affiliation of the person signing this CDSA.") def get_absolute_url(self): return reverse( @@ -337,9 +323,7 @@ def get_agreement_group(self): return self.affiliation -class CDSAWorkspace( - TimeStampedModel, RequesterModel, DataUseOntologyModel, BaseWorkspaceData -): +class CDSAWorkspace(TimeStampedModel, RequesterModel, DataUseOntologyModel, BaseWorkspaceData): """A model to track additional data about a CDSA workspace.""" # Only one study per workspace. @@ -352,9 +336,7 @@ class CDSAWorkspace( 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." - ) + acknowledgments = models.TextField(help_text="Acknowledgments associated with data in this workspace.") available_data = models.ManyToManyField( AvailableData, help_text="Data available in this accession.", diff --git a/primed/cdsa/tables.py b/primed/cdsa/tables.py index f39d6988..7d4a3550 100644 --- a/primed/cdsa/tables.py +++ b/primed/cdsa/tables.py @@ -17,14 +17,9 @@ class AgreementVersionTable(tables.Table): - major_version = tables.Column(linkify=True) - full_version = tables.Column( - linkify=True, order_by=("major_version", "minor_version") - ) - major_version__is_valid = BooleanIconColumn( - verbose_name="Valid?", show_false_icon=True - ) + full_version = tables.Column(linkify=True, order_by=("major_version", "minor_version")) + major_version__is_valid = BooleanIconColumn(verbose_name="Valid?", show_false_icon=True) class Meta: model = models.AgreementVersion @@ -37,7 +32,6 @@ class Meta: class SignedAgreementTable(tables.Table): - cc_id = tables.Column(linkify=True) representative__name = tables.Column( linkify=lambda record: record.representative.get_absolute_url(), @@ -213,9 +207,7 @@ def render_signing_group(self, record): class StudyRecordsTable(tables.Table): """Table for a list of studies that have signed the CDSA.""" - signed_agreement__representative__name = tables.Column( - verbose_name="Representative" - ) + signed_agreement__representative__name = tables.Column(verbose_name="Representative") # This will only order properly if the order_by value is a column in the table. study__short_name = tables.Column(verbose_name="Study") @@ -235,9 +227,7 @@ class UserAccessRecordsTable(tables.Table): group__signedagreement__representative__name = tables.Column( verbose_name="Signing representatitve", ) - signing_group = tables.Column( - verbose_name="Signing group", accessor="group__signedagreement", orderable=False - ) + signing_group = tables.Column(verbose_name="Signing group", accessor="group__signedagreement", orderable=False) class Meta: model = GroupAccountMembership @@ -267,12 +257,8 @@ class CDSAWorkspaceRecordsTable(tables.Table): workspace__name = tables.Column() workspace__billing_project = tables.Column() study = tables.Column() - data_use_permission__abbreviation = tables.Column( - verbose_name="Data use permission" - ) - data_use_modifiers = tables.ManyToManyColumn( - transform=lambda x: x.abbreviation, verbose_name="Data use modifiers" - ) + data_use_permission__abbreviation = tables.Column(verbose_name="Data use permission") + data_use_modifiers = tables.ManyToManyColumn(transform=lambda x: x.abbreviation, verbose_name="Data use modifiers") workspace__created = tables.columns.Column(verbose_name="Date created") date_shared = tables.columns.Column(accessor="pk", verbose_name="Date shared") @@ -291,9 +277,7 @@ class Meta: def render_date_shared(self, record): try: - wgs = record.workspace.workspacegroupsharing_set.get( - group__name="PRIMED_ALL" - ) + wgs = record.workspace.workspacegroupsharing_set.get(group__name="PRIMED_ALL") return wgs.created except WorkspaceGroupSharing.DoesNotExist: return "—" diff --git a/primed/cdsa/tests/factories.py b/primed/cdsa/tests/factories.py index 6d7473d9..26625f70 100644 --- a/primed/cdsa/tests/factories.py +++ b/primed/cdsa/tests/factories.py @@ -51,9 +51,7 @@ class SignedAgreementFactory(DjangoModelFactory): anvil_access_group = SubFactory( ManagedGroupFactory, name=LazyAttribute( - lambda o: settings.ANVIL_DATA_ACCESS_GROUP_PREFIX - + "_CDSA_ACCESS_" - + str(o.factory_parent.cc_id) + lambda o: settings.ANVIL_DATA_ACCESS_GROUP_PREFIX + "_CDSA_ACCESS_" + str(o.factory_parent.cc_id) ), ) @@ -62,10 +60,7 @@ class Meta: class MemberAgreementFactory(DjangoModelFactory): - - signed_agreement = SubFactory( - SignedAgreementFactory, type=models.SignedAgreement.MEMBER - ) + signed_agreement = SubFactory(SignedAgreementFactory, type=models.SignedAgreement.MEMBER) study_site = SubFactory(StudySiteFactory) is_primary = True @@ -74,10 +69,7 @@ class Meta: class DataAffiliateAgreementFactory(DjangoModelFactory): - - signed_agreement = SubFactory( - SignedAgreementFactory, type=models.SignedAgreement.DATA_AFFILIATE - ) + signed_agreement = SubFactory(SignedAgreementFactory, type=models.SignedAgreement.DATA_AFFILIATE) study = SubFactory(StudyFactory) is_primary = True anvil_upload_group = SubFactory( @@ -94,10 +86,7 @@ class Meta: class NonDataAffiliateAgreementFactory(DjangoModelFactory): - - signed_agreement = SubFactory( - SignedAgreementFactory, type=models.SignedAgreement.NON_DATA_AFFILIATE - ) + signed_agreement = SubFactory(SignedAgreementFactory, type=models.SignedAgreement.NON_DATA_AFFILIATE) affiliation = Faker("company") class Meta: @@ -105,7 +94,6 @@ class Meta: class CDSAWorkspaceFactory(DjangoModelFactory): - study = SubFactory(StudyFactory) acknowledgments = Faker("paragraph") requested_by = SubFactory(UserFactory) @@ -121,9 +109,7 @@ def authorization_domains(self, create, extracted, **kwargs): return # Create an authorization domain. - auth_domain = ManagedGroupFactory.create( - name="auth_{}".format(self.workspace.name) - ) + auth_domain = ManagedGroupFactory.create(name="auth_{}".format(self.workspace.name)) self.workspace.authorization_domains.add(auth_domain) class Meta: diff --git a/primed/cdsa/tests/test_audit.py b/primed/cdsa/tests/test_audit.py index ef5fc61c..0e7fa8d7 100644 --- a/primed/cdsa/tests/test_audit.py +++ b/primed/cdsa/tests/test_audit.py @@ -22,9 +22,7 @@ class SignedAgreementAuditResultTest(TestCase): def setUp(self): super().setUp() - self.cdsa_group = ManagedGroupFactory.create( - name=settings.ANVIL_CDSA_GROUP_NAME - ) + self.cdsa_group = ManagedGroupFactory.create(name=settings.ANVIL_CDSA_GROUP_NAME) def test_verified_access(self): signed_agreement = factories.SignedAgreementFactory.create() @@ -35,9 +33,7 @@ def test_verified_access(self): self.assertIsNone(instance.action) self.assertEqual( instance.get_action_url(), - reverse( - "cdsa:audit:signed_agreements:resolve", args=[signed_agreement.cc_id] - ), + reverse("cdsa:audit:signed_agreements:resolve", args=[signed_agreement.cc_id]), ) def test_verified_no_access(self): @@ -49,9 +45,7 @@ def test_verified_no_access(self): self.assertIsNone(instance.action) self.assertEqual( instance.get_action_url(), - reverse( - "cdsa:audit:signed_agreements:resolve", args=[signed_agreement.cc_id] - ), + reverse("cdsa:audit:signed_agreements:resolve", args=[signed_agreement.cc_id]), ) def test_grant_access(self): @@ -63,9 +57,7 @@ def test_grant_access(self): self.assertEqual(instance.action, "Grant access") self.assertEqual( instance.get_action_url(), - reverse( - "cdsa:audit:signed_agreements:resolve", args=[signed_agreement.cc_id] - ), + reverse("cdsa:audit:signed_agreements:resolve", args=[signed_agreement.cc_id]), ) def test_remove_access(self): @@ -77,9 +69,7 @@ def test_remove_access(self): self.assertEqual(instance.action, "Remove access") self.assertEqual( instance.get_action_url(), - reverse( - "cdsa:audit:signed_agreements:resolve", args=[signed_agreement.cc_id] - ), + reverse("cdsa:audit:signed_agreements:resolve", args=[signed_agreement.cc_id]), ) def test_error(self): @@ -90,9 +80,7 @@ def test_error(self): ) self.assertEqual( instance.get_action_url(), - reverse( - "cdsa:audit:signed_agreements:resolve", args=[signed_agreement.cc_id] - ), + reverse("cdsa:audit:signed_agreements:resolve", args=[signed_agreement.cc_id]), ) def test_anvil_group_name(self): @@ -119,9 +107,7 @@ class SignedAgreementAccessAuditTest(TestCase): def setUp(self): super().setUp() - self.cdsa_group = ManagedGroupFactory.create( - name=settings.ANVIL_CDSA_GROUP_NAME - ) + self.cdsa_group = ManagedGroupFactory.create(name=settings.ANVIL_CDSA_GROUP_NAME) def test_completed(self): """completed is updated properly.""" @@ -165,9 +151,7 @@ def test_signed_agreement_queryset(self): this_agreement = factories.MemberAgreementFactory.create() factories.MemberAgreementFactory.create() cdsa_audit = signed_agreement_audit.SignedAgreementAccessAudit( - signed_agreement_queryset=models.SignedAgreement.objects.filter( - pk=this_agreement.signed_agreement.pk - ) + signed_agreement_queryset=models.SignedAgreement.objects.filter(pk=this_agreement.signed_agreement.pk) ) cdsa_audit.run_audit() self.assertEqual(len(cdsa_audit.verified), 0) @@ -321,9 +305,7 @@ def test_member_component_has_primary_in_group(self): """Member component agreement, with valid version, with primary with valid version, in CDSA group.""" study_site = StudySiteFactory.create() factories.MemberAgreementFactory.create(study_site=study_site) - this_agreement = factories.MemberAgreementFactory.create( - is_primary=False, study_site=study_site - ) + this_agreement = factories.MemberAgreementFactory.create(is_primary=False, study_site=study_site) # Add the signed agreement access group to the CDSA group. GroupGroupMembershipFactory.create( parent_group=self.cdsa_group, @@ -343,9 +325,7 @@ def test_member_component_has_primary_not_in_group(self): """Member component agreement, with valid version, with primary with valid version, not in CDSA group.""" study_site = StudySiteFactory.create() factories.MemberAgreementFactory.create(study_site=study_site) - this_agreement = factories.MemberAgreementFactory.create( - is_primary=False, study_site=study_site - ) + this_agreement = factories.MemberAgreementFactory.create(is_primary=False, study_site=study_site) # # Add the signed agreement access group to the CDSA group. # GroupGroupMembershipFactory.create( # parent_group=self.cdsa_group, @@ -416,9 +396,7 @@ def test_member_component_has_primary_with_invalid_version_in_group(self): study_site=study_site, signed_agreement__version__major_version__is_valid=False, ) - this_agreement = factories.MemberAgreementFactory.create( - is_primary=False, study_site=study_site - ) + this_agreement = factories.MemberAgreementFactory.create(is_primary=False, study_site=study_site) # Add the signed agreement access group to the CDSA group. GroupGroupMembershipFactory.create( parent_group=self.cdsa_group, @@ -441,9 +419,7 @@ def test_member_component_has_primary_with_invalid_version_not_in_group(self): study_site=study_site, signed_agreement__version__major_version__is_valid=False, ) - this_agreement = factories.MemberAgreementFactory.create( - is_primary=False, study_site=study_site - ) + this_agreement = factories.MemberAgreementFactory.create(is_primary=False, study_site=study_site) # # Add the signed agreement access group to the CDSA group. # GroupGroupMembershipFactory.create( # parent_group=self.cdsa_group, @@ -466,9 +442,7 @@ def test_member_component_has_inactive_primary_in_group(self): study_site=study_site, signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN, ) - this_agreement = factories.MemberAgreementFactory.create( - is_primary=False, study_site=study_site - ) + this_agreement = factories.MemberAgreementFactory.create(is_primary=False, study_site=study_site) # Add the signed agreement access group to the CDSA group. GroupGroupMembershipFactory.create( parent_group=self.cdsa_group, @@ -491,9 +465,7 @@ def test_member_component_has_inactive_primary_not_in_group(self): study_site=study_site, signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN, ) - this_agreement = factories.MemberAgreementFactory.create( - is_primary=False, study_site=study_site - ) + this_agreement = factories.MemberAgreementFactory.create(is_primary=False, study_site=study_site) # # Add the signed agreement access group to the CDSA group. # GroupGroupMembershipFactory.create( # parent_group=self.cdsa_group, @@ -512,9 +484,7 @@ def test_member_component_has_inactive_primary_not_in_group(self): def test_member_component_no_primary_in_group(self): """Member component agreement, with valid version, with no primary, in CDSA group.""" study_site = StudySiteFactory.create() - this_agreement = factories.MemberAgreementFactory.create( - is_primary=False, study_site=study_site - ) + this_agreement = factories.MemberAgreementFactory.create(is_primary=False, study_site=study_site) # Add the signed agreement access group to the CDSA group. GroupGroupMembershipFactory.create( parent_group=self.cdsa_group, @@ -533,9 +503,7 @@ def test_member_component_no_primary_in_group(self): def test_member_component_no_primary_not_in_group(self): """Member component agreement, with valid version, with no primary, not in CDSA group.""" study_site = StudySiteFactory.create() - this_agreement = factories.MemberAgreementFactory.create( - is_primary=False, study_site=study_site - ) + this_agreement = factories.MemberAgreementFactory.create(is_primary=False, study_site=study_site) # # Add the signed agreement access group to the CDSA group. # GroupGroupMembershipFactory.create( # parent_group=self.cdsa_group, @@ -813,9 +781,7 @@ def test_data_affiliate_component_has_primary_in_group(self): """Member component agreement, with valid version, with primary with valid version, in CDSA group.""" study = StudyFactory.create() factories.DataAffiliateAgreementFactory.create(study=study) - this_agreement = factories.DataAffiliateAgreementFactory.create( - is_primary=False, study=study - ) + this_agreement = factories.DataAffiliateAgreementFactory.create(is_primary=False, study=study) # Add the signed agreement access group to the CDSA group. GroupGroupMembershipFactory.create( parent_group=self.cdsa_group, @@ -835,9 +801,7 @@ def test_data_affiliate_component_has_primary_not_in_group(self): """Member component agreement, with valid version, with primary with valid version, not in CDSA group.""" study = StudyFactory.create() factories.DataAffiliateAgreementFactory.create(study=study) - this_agreement = factories.DataAffiliateAgreementFactory.create( - is_primary=False, study=study - ) + this_agreement = factories.DataAffiliateAgreementFactory.create(is_primary=False, study=study) # # Add the signed agreement access group to the CDSA group. # GroupGroupMembershipFactory.create( # parent_group=self.cdsa_group, @@ -908,9 +872,7 @@ def test_data_affiliate_component_has_primary_with_invalid_version_in_group(self study=study, signed_agreement__version__major_version__is_valid=False, ) - this_agreement = factories.DataAffiliateAgreementFactory.create( - is_primary=False, study=study - ) + this_agreement = factories.DataAffiliateAgreementFactory.create(is_primary=False, study=study) # Add the signed agreement access group to the CDSA group. GroupGroupMembershipFactory.create( parent_group=self.cdsa_group, @@ -935,9 +897,7 @@ def test_data_affiliate_component_has_primary_with_invalid_version_not_in_group( study=study, signed_agreement__version__major_version__is_valid=False, ) - this_agreement = factories.DataAffiliateAgreementFactory.create( - is_primary=False, study=study - ) + this_agreement = factories.DataAffiliateAgreementFactory.create(is_primary=False, study=study) # # Add the signed agreement access group to the CDSA group. # GroupGroupMembershipFactory.create( # parent_group=self.cdsa_group, @@ -960,9 +920,7 @@ def test_data_affiliate_component_has_inactive_primary_in_group(self): study=study, signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN, ) - this_agreement = factories.DataAffiliateAgreementFactory.create( - is_primary=False, study=study - ) + this_agreement = factories.DataAffiliateAgreementFactory.create(is_primary=False, study=study) # Add the signed agreement access group to the CDSA group. GroupGroupMembershipFactory.create( parent_group=self.cdsa_group, @@ -985,9 +943,7 @@ def test_data_affiliate_component_has_inactive_primary_not_in_group(self): study=study, signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN, ) - this_agreement = factories.DataAffiliateAgreementFactory.create( - is_primary=False, study=study - ) + this_agreement = factories.DataAffiliateAgreementFactory.create(is_primary=False, study=study) # # Add the signed agreement access group to the CDSA group. # GroupGroupMembershipFactory.create( # parent_group=self.cdsa_group, @@ -1006,9 +962,7 @@ def test_data_affiliate_component_has_inactive_primary_not_in_group(self): def test_data_affiliate_component_no_primary_in_group(self): """Member component agreement, with valid version, with no primary, in CDSA group.""" study = StudyFactory.create() - this_agreement = factories.DataAffiliateAgreementFactory.create( - is_primary=False, study=study - ) + this_agreement = factories.DataAffiliateAgreementFactory.create(is_primary=False, study=study) # Add the signed agreement access group to the CDSA group. GroupGroupMembershipFactory.create( parent_group=self.cdsa_group, @@ -1027,9 +981,7 @@ def test_data_affiliate_component_no_primary_in_group(self): def test_data_affiliate_component_no_primary_not_in_group(self): """Member component agreement, with valid version, with no primary, not in CDSA group.""" study = StudyFactory.create() - this_agreement = factories.DataAffiliateAgreementFactory.create( - is_primary=False, study=study - ) + this_agreement = factories.DataAffiliateAgreementFactory.create(is_primary=False, study=study) # # Add the signed agreement access group to the CDSA group. # GroupGroupMembershipFactory.create( # parent_group=self.cdsa_group, @@ -1310,9 +1262,7 @@ class SignedAgreementAccessAuditTableTest(TestCase): def test_no_rows(self): """Table works with no rows.""" table = signed_agreement_audit.SignedAgreementAccessAuditTable([]) - self.assertIsInstance( - table, signed_agreement_audit.SignedAgreementAccessAuditTable - ) + self.assertIsInstance(table, signed_agreement_audit.SignedAgreementAccessAuditTable) self.assertEqual(len(table.rows), 0) def test_one_row(self): @@ -1329,9 +1279,7 @@ def test_one_row(self): } ] table = signed_agreement_audit.SignedAgreementAccessAuditTable(data) - self.assertIsInstance( - table, signed_agreement_audit.SignedAgreementAccessAuditTable - ) + self.assertIsInstance(table, signed_agreement_audit.SignedAgreementAccessAuditTable) self.assertEqual(len(table.rows), 1) self.assertIn( str(member_agreement.signed_agreement.cc_id), @@ -1362,9 +1310,7 @@ def test_two_rows(self): }, ] table = signed_agreement_audit.SignedAgreementAccessAuditTable(data) - self.assertIsInstance( - table, signed_agreement_audit.SignedAgreementAccessAuditTable - ) + self.assertIsInstance(table, signed_agreement_audit.SignedAgreementAccessAuditTable) self.assertEqual(len(table.rows), 2) self.assertIn( str(signed_agreement_1.signed_agreement.cc_id), @@ -1383,16 +1329,12 @@ class WorkspaceAuditResultTest(TestCase): def setUp(self): super().setUp() - self.cdsa_group = ManagedGroupFactory.create( - name=settings.ANVIL_CDSA_GROUP_NAME - ) + self.cdsa_group = ManagedGroupFactory.create(name=settings.ANVIL_CDSA_GROUP_NAME) self.study = StudyFactory.create() def test_verified_access(self): workspace = factories.CDSAWorkspaceFactory.create(study=self.study) - data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( - study=self.study - ) + data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(study=self.study) instance = workspace_audit.VerifiedAccess( workspace=workspace, data_affiliate_agreement=data_affiliate_agreement, @@ -1411,9 +1353,7 @@ def test_verified_access(self): def test_verified_no_access(self): workspace = factories.CDSAWorkspaceFactory.create(study=self.study) - data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( - study=self.study - ) + data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(study=self.study) instance = workspace_audit.VerifiedNoAccess( workspace=workspace, data_affiliate_agreement=data_affiliate_agreement, @@ -1432,9 +1372,7 @@ def test_verified_no_access(self): def test_grant_access(self): workspace = factories.CDSAWorkspaceFactory.create(study=self.study) - data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( - study=self.study - ) + data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(study=self.study) instance = workspace_audit.GrantAccess( workspace=workspace, data_affiliate_agreement=data_affiliate_agreement, @@ -1453,9 +1391,7 @@ def test_grant_access(self): def test_remove_access(self): workspace = factories.CDSAWorkspaceFactory.create(study=self.study) - data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( - study=self.study - ) + data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(study=self.study) instance = workspace_audit.RemoveAccess( workspace=workspace, data_affiliate_agreement=data_affiliate_agreement, @@ -1474,9 +1410,7 @@ def test_remove_access(self): def test_error(self): workspace = factories.CDSAWorkspaceFactory.create(study=self.study) - data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( - study=self.study - ) + data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(study=self.study) instance = workspace_audit.OtherError( workspace=workspace, data_affiliate_agreement=data_affiliate_agreement, @@ -1512,9 +1446,7 @@ def test_error_no_data_affiliate_agreement(self): def test_anvil_group_name(self): workspace = factories.CDSAWorkspaceFactory.create(study=self.study) - data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( - study=self.study - ) + data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(study=self.study) instance = workspace_audit.OtherError( workspace=workspace, data_affiliate_agreement=data_affiliate_agreement, @@ -1526,9 +1458,7 @@ def test_anvil_group_name(self): def test_anvil_group_name_setting(self): group = ManagedGroupFactory.create(name="FOO") workspace = factories.CDSAWorkspaceFactory.create(study=self.study) - data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( - study=self.study - ) + data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(study=self.study) instance = workspace_audit.OtherError( workspace=workspace, data_affiliate_agreement=data_affiliate_agreement, @@ -1542,9 +1472,7 @@ class WorkspaceAccessAuditTest(TestCase): def setUp(self): super().setUp() - self.cdsa_group = ManagedGroupFactory.create( - name=settings.ANVIL_CDSA_GROUP_NAME - ) + self.cdsa_group = ManagedGroupFactory.create(name=settings.ANVIL_CDSA_GROUP_NAME) def test_completed(self): """completed is updated properly.""" @@ -1558,9 +1486,7 @@ def test_cdsa_workspace_queryset(self): cdsa_workspace = factories.CDSAWorkspaceFactory.create() factories.CDSAWorkspaceFactory.create() cdsa_audit = workspace_audit.WorkspaceAccessAudit( - cdsa_workspace_queryset=models.CDSAWorkspace.objects.filter( - pk=cdsa_workspace.workspace.pk - ) + cdsa_workspace_queryset=models.CDSAWorkspace.objects.filter(pk=cdsa_workspace.workspace.pk) ) cdsa_audit.run_audit() self.assertEqual(len(cdsa_audit.verified), 1) @@ -1575,9 +1501,7 @@ def test_cdsa_workspace_queryset(self): def test_cdsa_workspace_queryset_wrong_class(self): """Audit raises error if dbgap_application_queryset has the wrong model class.""" with self.assertRaises(ValueError) as e: - workspace_audit.WorkspaceAccessAudit( - cdsa_workspace_queryset=models.SignedAgreement.objects.all() - ) + workspace_audit.WorkspaceAccessAudit(cdsa_workspace_queryset=models.SignedAgreement.objects.all()) self.assertEqual( str(e.exception), "cdsa_workspace_queryset must be a queryset of CDSAWorkspace objects.", @@ -1596,9 +1520,7 @@ def test_cdsa_workspace_queryset_not_queryset(self): def test_primary_in_auth_domain(self): study = StudyFactory.create() workspace = factories.CDSAWorkspaceFactory.create(study=study) - data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( - study=study - ) + data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(study=study) # Add the CDSA group to the auth domain. GroupGroupMembershipFactory.create( parent_group=workspace.workspace.authorization_domains.first(), @@ -1618,9 +1540,7 @@ def test_primary_in_auth_domain(self): def test_primary_not_in_auth_domain(self): study = StudyFactory.create() workspace = factories.CDSAWorkspaceFactory.create(study=study) - data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( - study=study - ) + data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(study=study) # Do not add the CDSA group to the auth domain. # GroupGroupMembershipFactory.create( # parent_group=workspace.workspace.authorization_domains.first(), @@ -1804,9 +1724,7 @@ def test_component_agreement_in_auth_domain(self): def test_two_valid_primary_agreements_in_auth_domain(self): study = StudyFactory.create() workspace = factories.CDSAWorkspaceFactory.create(study=study) - factories.DataAffiliateAgreementFactory.create( - study=study, signed_agreement__version__major_version__version=1 - ) + factories.DataAffiliateAgreementFactory.create(study=study, signed_agreement__version__major_version__version=1) data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( study=study, signed_agreement__version__major_version__version=2 ) @@ -1859,9 +1777,7 @@ def test_two_valid_primary_agreements_same_major_version_in_auth_domain(self): def test_two_valid_primary_agreements_not_in_auth_domain(self): study = StudyFactory.create() workspace = factories.CDSAWorkspaceFactory.create(study=study) - factories.DataAffiliateAgreementFactory.create( - study=study, signed_agreement__version__major_version__version=1 - ) + factories.DataAffiliateAgreementFactory.create(study=study, signed_agreement__version__major_version__version=1) data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( study=study, signed_agreement__version__major_version__version=2 ) @@ -2064,9 +1980,7 @@ def test_two_workspaces(self): study = StudyFactory.create() workspace_1 = factories.CDSAWorkspaceFactory.create(study=study) workspace_2 = factories.CDSAWorkspaceFactory.create(study=study) - data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( - study=study - ) + data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(study=study) # Add the CDSA group to the auth domain. GroupGroupMembershipFactory.create( parent_group=workspace_1.workspace.authorization_domains.first(), diff --git a/primed/cdsa/tests/test_commands.py b/primed/cdsa/tests/test_commands.py index fbef817c..ad4ccf64 100644 --- a/primed/cdsa/tests/test_commands.py +++ b/primed/cdsa/tests/test_commands.py @@ -95,9 +95,7 @@ def test_directory_exists(self): os.mkdir(self.outdir) out = StringIO() with self.assertRaises(CommandError) as e: - call_command( - "cdsa_records", "--outdir", self.outdir, "--no-color", stdout=out - ) + call_command("cdsa_records", "--outdir", self.outdir, "--no-color", stdout=out) self.assertIn("already exists", str(e.exception)) @@ -106,26 +104,18 @@ class RunCDSAAuditTest(TestCase): def setUp(self): super().setUp() - self.cdsa_group = ManagedGroupFactory.create( - name=settings.ANVIL_CDSA_GROUP_NAME - ) + self.cdsa_group = ManagedGroupFactory.create(name=settings.ANVIL_CDSA_GROUP_NAME) def test_command_output_no_records(self): """Test command output.""" out = StringIO() call_command("run_cdsa_audit", "--no-color", stdout=out) expected_output = ( - "Running SignedAgreement access audit... ok!\n" - "* Verified: 0\n" - "* Needs action: 0\n" - "* Errors: 0\n" + "Running SignedAgreement access audit... ok!\n" "* Verified: 0\n" "* Needs action: 0\n" "* Errors: 0\n" ) self.assertIn(expected_output, out.getvalue()) expected_output = ( - "Running CDSAWorkspace access audit... ok!\n" - "* Verified: 0\n" - "* Needs action: 0\n" - "* Errors: 0\n" + "Running CDSAWorkspace access audit... ok!\n" "* Verified: 0\n" "* Needs action: 0\n" "* Errors: 0\n" ) self.assertIn(expected_output, out.getvalue()) # Zero messages have been sent by default. @@ -137,17 +127,11 @@ def test_command_run_audit_one_agreement_verified(self): out = StringIO() call_command("run_cdsa_audit", "--no-color", stdout=out) expected_output = ( - "Running SignedAgreement access audit... ok!\n" - "* Verified: 1\n" - "* Needs action: 0\n" - "* Errors: 0\n" + "Running SignedAgreement access audit... ok!\n" "* Verified: 1\n" "* Needs action: 0\n" "* Errors: 0\n" ) self.assertIn(expected_output, out.getvalue()) expected_output = ( - "Running CDSAWorkspace access audit... ok!\n" - "* Verified: 0\n" - "* Needs action: 0\n" - "* Errors: 0\n" + "Running CDSAWorkspace access audit... ok!\n" "* Verified: 0\n" "* Needs action: 0\n" "* Errors: 0\n" ) self.assertIn(expected_output, out.getvalue()) # Zero messages have been sent by default. @@ -195,9 +179,7 @@ def test_command_run_audit_one_agreement_verified_email(self): """No email is sent when there are no errors.""" factories.MemberAgreementFactory.create(is_primary=False) out = StringIO() - call_command( - "run_cdsa_audit", "--no-color", email="test@example.com", stdout=out - ) + call_command("run_cdsa_audit", "--no-color", email="test@example.com", stdout=out) self.assertIn("Running CDSAWorkspace access audit... ok!", out.getvalue()) self.assertIn("Running SignedAgreement access audit... ok!", out.getvalue()) # Zero messages have been sent by default. @@ -207,9 +189,7 @@ def test_command_run_audit_one_agreement_needs_action_email(self): """Email is sent for one needs_action instance.""" factories.MemberAgreementFactory.create() out = StringIO() - call_command( - "run_cdsa_audit", "--no-color", email="test@example.com", stdout=out - ) + call_command("run_cdsa_audit", "--no-color", email="test@example.com", stdout=out) expected_output = ( "Running SignedAgreement access audit... problems found.\n" "* Verified: 0\n" @@ -223,9 +203,7 @@ def test_command_run_audit_one_agreement_needs_action_email(self): email = mail.outbox[0] self.assertEqual(email.to, ["test@example.com"]) self.assertEqual(email.subject, "CDSA SignedAgreementAccessAudit errors") - self.assertIn( - reverse("cdsa:audit:signed_agreements:all"), email.alternatives[0][0] - ) + self.assertIn(reverse("cdsa:audit:signed_agreements:all"), email.alternatives[0][0]) def test_command_run_audit_one_agreement_error_email(self): """Test command output with one error instance.""" @@ -235,9 +213,7 @@ def test_command_run_audit_one_agreement_error_email(self): child_group=agreement.signed_agreement.anvil_access_group, ) out = StringIO() - call_command( - "run_cdsa_audit", "--no-color", email="test@example.com", stdout=out - ) + call_command("run_cdsa_audit", "--no-color", email="test@example.com", stdout=out) expected_output = ( "Running SignedAgreement access audit... problems found.\n" "* Verified: 0\n" @@ -252,9 +228,7 @@ def test_command_run_audit_one_agreement_error_email(self): email = mail.outbox[0] self.assertEqual(email.to, ["test@example.com"]) self.assertEqual(email.subject, "CDSA SignedAgreementAccessAudit errors") - self.assertIn( - reverse("cdsa:audit:signed_agreements:all"), email.alternatives[0][0] - ) + self.assertIn(reverse("cdsa:audit:signed_agreements:all"), email.alternatives[0][0]) def test_command_run_audit_one_workspace_verified(self): """Test command output with one verified instance.""" @@ -262,17 +236,11 @@ def test_command_run_audit_one_workspace_verified(self): out = StringIO() call_command("run_cdsa_audit", "--no-color", stdout=out) expected_output = ( - "Running SignedAgreement access audit... ok!\n" - "* Verified: 0\n" - "* Needs action: 0\n" - "* Errors: 0\n" + "Running SignedAgreement access audit... ok!\n" "* Verified: 0\n" "* Needs action: 0\n" "* Errors: 0\n" ) self.assertIn(expected_output, out.getvalue()) expected_output = ( - "Running CDSAWorkspace access audit... ok!\n" - "* Verified: 1\n" - "* Needs action: 0\n" - "* Errors: 0\n" + "Running CDSAWorkspace access audit... ok!\n" "* Verified: 1\n" "* Needs action: 0\n" "* Errors: 0\n" ) self.assertIn(expected_output, out.getvalue()) # Zero messages have been sent by default. @@ -325,9 +293,7 @@ def test_command_run_audit_one_workspace_verified_email(self): """No email is sent when there are no errors.""" factories.CDSAWorkspaceFactory.create() out = StringIO() - call_command( - "run_cdsa_audit", "--no-color", email="test@example.com", stdout=out - ) + call_command("run_cdsa_audit", "--no-color", email="test@example.com", stdout=out) self.assertIn("Running CDSAWorkspace access audit... ok!", out.getvalue()) self.assertIn("Running SignedAgreement access audit... ok!", out.getvalue()) # Zero messages have been sent by default. @@ -342,9 +308,7 @@ def test_command_run_audit_one_workspace_needs_action_email(self): ) factories.CDSAWorkspaceFactory.create(study=agreement.study) out = StringIO() - call_command( - "run_cdsa_audit", "--no-color", email="test@example.com", stdout=out - ) + call_command("run_cdsa_audit", "--no-color", email="test@example.com", stdout=out) expected_output = ( "Running CDSAWorkspace access audit... problems found.\n" "* Verified: 0\n" @@ -368,9 +332,7 @@ def test_command_run_audit_one_workspace_error_email(self): child_group=self.cdsa_group, ) out = StringIO() - call_command( - "run_cdsa_audit", "--no-color", email="test@example.com", stdout=out - ) + call_command("run_cdsa_audit", "--no-color", email="test@example.com", stdout=out) expected_output = ( "Running CDSAWorkspace access audit... problems found.\n" "* Verified: 0\n" @@ -413,9 +375,7 @@ def test_signed_agreement_and_workspace_needs_action_email(self): agreement = factories.DataAffiliateAgreementFactory.create() factories.CDSAWorkspaceFactory.create(study=agreement.study) out = StringIO() - call_command( - "run_cdsa_audit", "--no-color", email="test@example.com", stdout=out - ) + call_command("run_cdsa_audit", "--no-color", email="test@example.com", stdout=out) expected_output = ( "Running CDSAWorkspace access audit... problems found.\n" "* Verified: 0\n" @@ -435,9 +395,7 @@ def test_signed_agreement_and_workspace_needs_action_email(self): email = mail.outbox[0] self.assertEqual(email.to, ["test@example.com"]) self.assertEqual(email.subject, "CDSA SignedAgreementAccessAudit errors") - self.assertIn( - reverse("cdsa:audit:signed_agreements:all"), email.alternatives[0][0] - ) + self.assertIn(reverse("cdsa:audit:signed_agreements:all"), email.alternatives[0][0]) email = mail.outbox[1] self.assertEqual(email.to, ["test@example.com"]) self.assertEqual(email.subject, "CDSA WorkspaceAccessAudit errors") @@ -450,9 +408,7 @@ def test_different_domain(self): factories.MemberAgreementFactory.create() with self.settings(SITE_ID=site.id): out = StringIO() - call_command( - "run_cdsa_audit", "--no-color", email="test@example.com", stdout=out - ) + call_command("run_cdsa_audit", "--no-color", email="test@example.com", stdout=out) self.assertIn( "Running SignedAgreement access audit... problems found.", out.getvalue(), diff --git a/primed/cdsa/tests/test_forms.py b/primed/cdsa/tests/test_forms.py index 1dcea0af..5b01b31e 100644 --- a/primed/cdsa/tests/test_forms.py +++ b/primed/cdsa/tests/test_forms.py @@ -244,9 +244,7 @@ class MemberAgreementFormTest(TestCase): def setUp(self): """Create related objects for use in the form.""" - self.signed_agreement = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.MEMBER - ) + self.signed_agreement = factories.SignedAgreementFactory.create(type=models.SignedAgreement.MEMBER) self.study_site = StudySiteFactory.create() def test_valid(self): @@ -317,9 +315,7 @@ def test_invalid_signed_agreement_already_has_member_agreement(self): def test_invalid_signed_agreement_wrong_type(self): """Form is invalid when the signed_agreement has the wrong type.""" - obj = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.DATA_AFFILIATE - ) + obj = factories.SignedAgreementFactory.create(type=models.SignedAgreement.DATA_AFFILIATE) form_data = { "signed_agreement": obj, "is_primary": True, @@ -339,9 +335,7 @@ class DataAffiliateAgreementFormTest(TestCase): def setUp(self): """Create related objects for use in the form.""" - self.signed_agreement = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.DATA_AFFILIATE - ) + self.signed_agreement = factories.SignedAgreementFactory.create(type=models.SignedAgreement.DATA_AFFILIATE) self.study = StudyFactory.create() def test_valid(self): @@ -412,9 +406,7 @@ def test_invalid_signed_agreement_already_has_agreement_type(self): def test_invalid_signed_agreement_wrong_type(self): """Form is invalid when the signed_agreement has the wrong type.""" - obj = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.MEMBER - ) + obj = factories.SignedAgreementFactory.create(type=models.SignedAgreement.MEMBER) form_data = { "signed_agreement": obj, "is_primary": True, @@ -449,9 +441,7 @@ def test_invalid_component_with_additional_limitations(self): self.assertFalse(form.is_valid()) self.assertIn("additional_limitations", form.errors) self.assertEqual(len(form.errors["additional_limitations"]), 1) - self.assertIn( - "only allowed for primary", form.errors["additional_limitations"][0] - ) + self.assertIn("only allowed for primary", form.errors["additional_limitations"][0]) def test_valid_primary_with_requires_study_review_true(self): """Form is valid with necessary input.""" @@ -476,9 +466,7 @@ def test_invalid_component_with_requires_study_review_true(self): self.assertFalse(form.is_valid()) self.assertIn("requires_study_review", form.errors) self.assertEqual(len(form.errors["requires_study_review"]), 1) - self.assertIn( - "can only be True for primary", form.errors["requires_study_review"][0] - ) + self.assertIn("can only be True for primary", form.errors["requires_study_review"][0]) class NonDataAffiliateAgreementFormTest(TestCase): @@ -488,9 +476,7 @@ class NonDataAffiliateAgreementFormTest(TestCase): def setUp(self): """Create related objects for use in the form.""" - self.signed_agreement = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.NON_DATA_AFFILIATE - ) + self.signed_agreement = factories.SignedAgreementFactory.create(type=models.SignedAgreement.NON_DATA_AFFILIATE) def test_valid(self): """Form is valid with necessary input.""" @@ -542,9 +528,7 @@ def test_invalid_signed_agreement_already_has_agreement_type(self): def test_invalid_signed_agreement_wrong_type(self): """Form is invalid when the signed_agreement has the wrong type.""" - obj = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.MEMBER - ) + obj = factories.SignedAgreementFactory.create(type=models.SignedAgreement.MEMBER) form_data = { "signed_agreement": obj, "affiliation": "Foo Bar", diff --git a/primed/cdsa/tests/test_migrations.py b/primed/cdsa/tests/test_migrations.py index 7a609516..9446ac25 100644 --- a/primed/cdsa/tests/test_migrations.py +++ b/primed/cdsa/tests/test_migrations.py @@ -130,7 +130,7 @@ def prepare(self): type="member", version=agreement_version, anvil_access_group=ManagedGroup.objects.create(name="testaccess1", email="testaccess1@example.com"), - is_primary=True + is_primary=True, ) self.member_agreement_1 = MemberAgreement.objects.create( signed_agreement=tmp, @@ -144,7 +144,7 @@ def prepare(self): type="member", version=agreement_version, anvil_access_group=ManagedGroup.objects.create(name="testaccess2", email="testaccess2@example.com"), - is_primary=False + is_primary=False, ) self.member_agreement_2 = MemberAgreement.objects.create( signed_agreement=tmp, @@ -158,7 +158,7 @@ def prepare(self): type="data_affiliate", version=agreement_version, anvil_access_group=ManagedGroup.objects.create(name="testaccess3", email="testaccess3@example.com"), - is_primary=True + is_primary=True, ) self.data_affiliate_agreement_1 = DataAffiliateAgreement.objects.create( signed_agreement=tmp, @@ -173,7 +173,7 @@ def prepare(self): type="data_affiliate", version=agreement_version, anvil_access_group=ManagedGroup.objects.create(name="testaccess4", email="testaccess4@example.com"), - is_primary=False + is_primary=False, ) self.data_affiliate_agreement_2 = DataAffiliateAgreement.objects.create( signed_agreement=tmp, @@ -188,14 +188,14 @@ def prepare(self): type="non_data_affiliate", version=agreement_version, anvil_access_group=ManagedGroup.objects.create(name="testaccess5", email="testaccess5@example.com"), - is_primary=False + is_primary=False, ) self.non_data_affiliate_agreement = NonDataAffiliateAgreement.objects.create( signed_agreement=tmp, ) def test_is_primary_correctly_populated(self): -# import ipdb; ipdb.set_trace() + # import ipdb; ipdb.set_trace() MemberAgreement = self.new_state.apps.get_model("cdsa", "MemberAgreement") DataAffiliateAgreement = self.new_state.apps.get_model("cdsa", "DataAffiliateAgreement") NonDataAffiliateAgreement = self.new_state.apps.get_model("cdsa", "NonDataAffiliateAgreement") diff --git a/primed/cdsa/tests/test_models.py b/primed/cdsa/tests/test_models.py index eb9fbe66..d6b98d3c 100644 --- a/primed/cdsa/tests/test_models.py +++ b/primed/cdsa/tests/test_models.py @@ -53,9 +53,7 @@ def test_version_zero(self): self.assertEqual(len(e.exception.message_dict), 1) self.assertIn("version", e.exception.message_dict) self.assertEqual(len(e.exception.message_dict["version"]), 1) - self.assertIn( - "greater than or equal to", e.exception.message_dict["version"][0] - ) + self.assertIn("greater than or equal to", e.exception.message_dict["version"][0]) def test_version_negative(self): """ValidationError raised when version is negative.""" @@ -65,9 +63,7 @@ def test_version_negative(self): self.assertEqual(len(e.exception.message_dict), 1) self.assertIn("version", e.exception.message_dict) self.assertEqual(len(e.exception.message_dict["version"]), 1) - self.assertIn( - "greater than or equal to", e.exception.message_dict["version"][0] - ) + self.assertIn("greater than or equal to", e.exception.message_dict["version"][0]) def test_str(self): """__str__ method works as expected.""" @@ -85,20 +81,14 @@ class AgreementVersionTest(TestCase): def test_model_saving(self): major_version = factories.AgreementMajorVersionFactory.create() - instance = models.AgreementVersion( - major_version=major_version, minor_version=0, date_approved=datetime.today() - ) + instance = models.AgreementVersion(major_version=major_version, minor_version=0, date_approved=datetime.today()) instance.save() self.assertIsInstance(instance, models.AgreementVersion) def test_unique(self): major_version = factories.AgreementMajorVersionFactory.create() - factories.AgreementVersionFactory.create( - major_version=major_version, minor_version=0 - ) - instance = factories.AgreementVersionFactory.build( - major_version=major_version, minor_version=0 - ) + factories.AgreementVersionFactory.create(major_version=major_version, minor_version=0) + instance = factories.AgreementVersionFactory.build(major_version=major_version, minor_version=0) with self.assertRaisesMessage(ValidationError, "already exists"): instance.full_clean() with self.assertRaises(IntegrityError): @@ -107,50 +97,36 @@ def test_unique(self): def test_minor_version_zero(self): """full_clean raises no exception when minor_version is zero.""" major_version = factories.AgreementMajorVersionFactory.create() - instance = factories.AgreementVersionFactory.build( - major_version=major_version, minor_version=0 - ) + instance = factories.AgreementVersionFactory.build(major_version=major_version, minor_version=0) instance.full_clean() def test_minor_version_negative(self): """ValidationError raised when minor_version is negative.""" major_version = factories.AgreementMajorVersionFactory.create() - instance = factories.AgreementVersionFactory.build( - major_version=major_version, minor_version=-1 - ) + instance = factories.AgreementVersionFactory.build(major_version=major_version, minor_version=-1) with self.assertRaises(ValidationError) as e: instance.full_clean() self.assertEqual(len(e.exception.message_dict), 1) self.assertIn("minor_version", e.exception.message_dict) self.assertEqual(len(e.exception.message_dict["minor_version"]), 1) - self.assertIn( - "greater than or equal to", e.exception.message_dict["minor_version"][0] - ) + self.assertIn("greater than or equal to", e.exception.message_dict["minor_version"][0]) def test_full_version(self): """full_version property works as expected.""" self.assertEqual( - factories.AgreementVersionFactory( - major_version__version=1, minor_version=0 - ).full_version, + factories.AgreementVersionFactory(major_version__version=1, minor_version=0).full_version, "v1.0", ) self.assertEqual( - factories.AgreementVersionFactory( - major_version__version=1, minor_version=5 - ).full_version, + factories.AgreementVersionFactory(major_version__version=1, minor_version=5).full_version, "v1.5", ) self.assertEqual( - factories.AgreementVersionFactory( - major_version__version=1, minor_version=10 - ).full_version, + factories.AgreementVersionFactory(major_version__version=1, minor_version=10).full_version, "v1.10", ) self.assertEqual( - factories.AgreementVersionFactory( - major_version__version=2, minor_version=3 - ).full_version, + factories.AgreementVersionFactory(major_version__version=2, minor_version=3).full_version, "v2.3", ) @@ -196,31 +172,19 @@ def test_str_method(self): def test_get_absolute_url(self): """get_absolute_url method works correctly.""" instance = factories.MemberAgreementFactory.create() - self.assertEqual( - instance.signed_agreement.get_absolute_url(), instance.get_absolute_url() - ) + self.assertEqual(instance.signed_agreement.get_absolute_url(), instance.get_absolute_url()) instance = factories.DataAffiliateAgreementFactory.create() - self.assertEqual( - instance.signed_agreement.get_absolute_url(), instance.get_absolute_url() - ) + self.assertEqual(instance.signed_agreement.get_absolute_url(), instance.get_absolute_url()) instance = factories.NonDataAffiliateAgreementFactory.create() - self.assertEqual( - instance.signed_agreement.get_absolute_url(), instance.get_absolute_url() - ) + self.assertEqual(instance.signed_agreement.get_absolute_url(), instance.get_absolute_url()) def test_member_choices(self): """Can create instances with all of the member choices.""" - instance = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.MEMBER - ) + instance = factories.SignedAgreementFactory.create(type=models.SignedAgreement.MEMBER) self.assertEqual(instance.type, models.SignedAgreement.MEMBER) - instance = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.DATA_AFFILIATE - ) + instance = factories.SignedAgreementFactory.create(type=models.SignedAgreement.DATA_AFFILIATE) self.assertEqual(instance.type, models.SignedAgreement.DATA_AFFILIATE) - instance = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.NON_DATA_AFFILIATE - ) + instance = factories.SignedAgreementFactory.create(type=models.SignedAgreement.NON_DATA_AFFILIATE) self.assertEqual(instance.type, models.SignedAgreement.NON_DATA_AFFILIATE) def test_unique_cc_id(self): @@ -293,19 +257,13 @@ def test_status_field(self): self.assertEqual(instance.status, instance.StatusChoices.ACTIVE) instance.full_clean() # other choices - instance = factories.SignedAgreementFactory.create( - status=models.SignedAgreement.StatusChoices.WITHDRAWN - ) + instance = factories.SignedAgreementFactory.create(status=models.SignedAgreement.StatusChoices.WITHDRAWN) self.assertEqual(instance.status, instance.StatusChoices.WITHDRAWN) instance.full_clean() - instance = factories.SignedAgreementFactory.create( - status=models.SignedAgreement.StatusChoices.LAPSED - ) + instance = factories.SignedAgreementFactory.create(status=models.SignedAgreement.StatusChoices.LAPSED) self.assertEqual(instance.status, instance.StatusChoices.LAPSED) instance.full_clean() - instance = factories.SignedAgreementFactory.create( - status=models.SignedAgreement.StatusChoices.REPLACED - ) + instance = factories.SignedAgreementFactory.create(status=models.SignedAgreement.StatusChoices.REPLACED) self.assertEqual(instance.status, instance.StatusChoices.REPLACED) instance.full_clean() @@ -352,9 +310,7 @@ def test_is_in_cdsa_group(self): cdsa_group = ManagedGroupFactory.create(name="TEST_PRIMED_CDSA") self.assertFalse(obj.is_in_cdsa_group()) # Add agreement and check again, - GroupGroupMembershipFactory.create( - parent_group=cdsa_group, child_group=obj.anvil_access_group - ) + GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=obj.anvil_access_group) self.assertTrue(obj.is_in_cdsa_group()) @override_settings(ANVIL_CDSA_GROUP_NAME="FOO") @@ -368,9 +324,7 @@ def test_is_in_cdsa_group_different_group_name(self): cdsa_group = ManagedGroupFactory.create(name="FOO") self.assertFalse(obj.is_in_cdsa_group()) # Add agreement and check again, - GroupGroupMembershipFactory.create( - parent_group=cdsa_group, child_group=obj.anvil_access_group - ) + GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=obj.anvil_access_group) self.assertTrue(obj.is_in_cdsa_group()) @@ -379,9 +333,7 @@ class MemberAgreementTest(TestCase): def test_model_saving(self): """Creation using the model constructor and .save() works.""" - signed_agreement = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.MEMBER - ) + signed_agreement = factories.SignedAgreementFactory.create(type=models.SignedAgreement.MEMBER) study_site = StudySiteFactory.create() instance = models.MemberAgreement( signed_agreement=signed_agreement, @@ -399,12 +351,8 @@ def test_is_primary(self): self.assertEqual(instance.is_primary, False) def test_clean_incorrect_type(self): - signed_agreement = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.DATA_AFFILIATE - ) - instance = factories.MemberAgreementFactory.build( - signed_agreement=signed_agreement - ) + signed_agreement = factories.SignedAgreementFactory.create(type=models.SignedAgreement.DATA_AFFILIATE) + instance = factories.MemberAgreementFactory.build(signed_agreement=signed_agreement) with self.assertRaises(ValidationError) as e: instance.full_clean() self.assertIn("signed_agreement", e.exception.error_dict) @@ -437,9 +385,7 @@ def test_error_duplicate_signed_agreement(self): instance_2.full_clean() self.assertIn("signed_agreement", e.exception.error_dict) self.assertEqual(len(e.exception.error_dict["signed_agreement"]), 1) - self.assertIn( - "already exists", e.exception.error_dict["signed_agreement"][0].messages[0] - ) + self.assertIn("already exists", e.exception.error_dict["signed_agreement"][0].messages[0]) with self.assertRaises(IntegrityError): instance_2.save() @@ -453,9 +399,7 @@ class DataAffiliateAgreementTest(TestCase): def test_defaults(self): upload_group = ManagedGroupFactory.create() - signed_agreement = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.DATA_AFFILIATE - ) + signed_agreement = factories.SignedAgreementFactory.create(type=models.SignedAgreement.DATA_AFFILIATE) study = StudyFactory.create() instance = models.DataAffiliateAgreement( signed_agreement=signed_agreement, @@ -468,9 +412,7 @@ def test_defaults(self): def test_model_saving(self): """Creation using the model constructor and .save() works.""" upload_group = ManagedGroupFactory.create() - signed_agreement = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.DATA_AFFILIATE - ) + signed_agreement = factories.SignedAgreementFactory.create(type=models.SignedAgreement.DATA_AFFILIATE) study = StudyFactory.create() instance = models.DataAffiliateAgreement( signed_agreement=signed_agreement, @@ -489,9 +431,7 @@ def test_is_primary(self): self.assertEqual(instance.is_primary, False) def test_clean_incorrect_type(self): - signed_agreement = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.MEMBER - ) + signed_agreement = factories.SignedAgreementFactory.create(type=models.SignedAgreement.MEMBER) study = StudyFactory.create() upload_group = ManagedGroupFactory.create() instance = factories.DataAffiliateAgreementFactory.build( @@ -555,9 +495,7 @@ def test_error_duplicate_signed_agreement(self): instance_2.full_clean() self.assertIn("signed_agreement", e.exception.error_dict) self.assertEqual(len(e.exception.error_dict["signed_agreement"]), 1) - self.assertIn( - "already exists", e.exception.error_dict["signed_agreement"][0].messages[0] - ) + self.assertIn("already exists", e.exception.error_dict["signed_agreement"][0].messages[0]) with self.assertRaises(IntegrityError): instance_2.save() @@ -567,9 +505,7 @@ def test_get_agreement_group(self): def test_requires_study_review_primary(self): """Can set requires_study_review""" - instance = factories.DataAffiliateAgreementFactory.create( - requires_study_review=True - ) + instance = factories.DataAffiliateAgreementFactory.create(requires_study_review=True) self.assertTrue(instance.requires_study_review) def test_requires_study_review_not_primary(self): @@ -594,9 +530,7 @@ class NonDataAffiliateAgreementTest(TestCase): def test_model_saving(self): """Creation using the model constructor and .save() works.""" - signed_agreement = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.NON_DATA_AFFILIATE - ) + signed_agreement = factories.SignedAgreementFactory.create(type=models.SignedAgreement.NON_DATA_AFFILIATE) instance = models.NonDataAffiliateAgreement( signed_agreement=signed_agreement, affiliation="Foo", @@ -605,9 +539,7 @@ def test_model_saving(self): self.assertIsInstance(instance, models.NonDataAffiliateAgreement) def test_clean_incorrect_type(self): - signed_agreement = factories.SignedAgreementFactory.create( - type=models.SignedAgreement.MEMBER - ) + signed_agreement = factories.SignedAgreementFactory.create(type=models.SignedAgreement.MEMBER) instance = factories.NonDataAffiliateAgreementFactory.build( signed_agreement=signed_agreement, affiliation="Foo Bar", @@ -642,9 +574,7 @@ def test_error_duplicate_signed_agreement(self): instance_2.full_clean() self.assertIn("signed_agreement", e.exception.error_dict) self.assertEqual(len(e.exception.error_dict["signed_agreement"]), 1) - self.assertIn( - "already exists", e.exception.error_dict["signed_agreement"][0].messages[0] - ) + self.assertIn("already exists", e.exception.error_dict["signed_agreement"][0].messages[0]) with self.assertRaises(IntegrityError): instance_2.save() diff --git a/primed/cdsa/tests/test_tables.py b/primed/cdsa/tests/test_tables.py index 83f6bf55..2339a4fa 100644 --- a/primed/cdsa/tests/test_tables.py +++ b/primed/cdsa/tests/test_tables.py @@ -58,13 +58,9 @@ def test_number_accessors(self): """Table shows correct count for number of accessors.""" factories.MemberAgreementFactory.create() obj = factories.MemberAgreementFactory.create() - GroupAccountMembershipFactory.create( - group=obj.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create(group=obj.signed_agreement.anvil_access_group) obj_2 = factories.MemberAgreementFactory.create() - GroupAccountMembershipFactory.create_batch( - 2, group=obj_2.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create_batch(2, group=obj_2.signed_agreement.anvil_access_group) table = self.table_class(self.model.objects.all()) self.assertEqual(table.rows[0].get_cell("number_accessors"), 0) self.assertEqual(table.rows[1].get_cell("number_accessors"), 1) @@ -102,13 +98,9 @@ def test_number_accessors(self): """Table shows correct count for number of accessors.""" self.model_factory.create() obj = self.model_factory.create() - GroupAccountMembershipFactory.create( - group=obj.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create(group=obj.signed_agreement.anvil_access_group) obj_2 = self.model_factory.create() - GroupAccountMembershipFactory.create_batch( - 2, group=obj_2.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create_batch(2, group=obj_2.signed_agreement.anvil_access_group) table = self.table_class(self.model.objects.all()) self.assertEqual(table.rows[0].get_cell("number_accessors"), 0) self.assertEqual(table.rows[1].get_cell("number_accessors"), 1) @@ -146,13 +138,9 @@ def test_number_accessors(self): """Table shows correct count for number of accessors.""" self.model_factory.create() obj = self.model_factory.create() - GroupAccountMembershipFactory.create( - group=obj.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create(group=obj.signed_agreement.anvil_access_group) obj_2 = self.model_factory.create() - GroupAccountMembershipFactory.create_batch( - 2, group=obj_2.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create_batch(2, group=obj_2.signed_agreement.anvil_access_group) table = self.table_class(self.model.objects.all()) self.assertEqual(table.rows[0].get_cell("number_accessors"), 0) self.assertEqual(table.rows[1].get_cell("number_accessors"), 1) @@ -190,13 +178,9 @@ def test_number_accessors(self): """Table shows correct count for number of accessors.""" self.model_factory.create() obj = self.model_factory.create() - GroupAccountMembershipFactory.create( - group=obj.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create(group=obj.signed_agreement.anvil_access_group) obj_2 = self.model_factory.create() - GroupAccountMembershipFactory.create_batch( - 2, group=obj_2.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create_batch(2, group=obj_2.signed_agreement.anvil_access_group) table = self.table_class(self.model.objects.all()) self.assertEqual(table.rows[0].get_cell("number_accessors"), 0) self.assertEqual(table.rows[1].get_cell("number_accessors"), 1) @@ -238,32 +222,22 @@ def test_render_signing_group(self): # Members. study_site = StudySiteFactory.create(short_name="Test Site") record = factories.MemberAgreementFactory(study_site=study_site) - self.assertEqual( - table.render_signing_group(record.signed_agreement), "Test Site" - ) + self.assertEqual(table.render_signing_group(record.signed_agreement), "Test Site") # Data affiliates. study = StudyFactory.create(short_name="Test Study") record = factories.DataAffiliateAgreementFactory(study=study) - self.assertEqual( - table.render_signing_group(record.signed_agreement), "Test Study" - ) + self.assertEqual(table.render_signing_group(record.signed_agreement), "Test Study") # Non-data affiliates. record = factories.NonDataAffiliateAgreementFactory(affiliation="Test Affil") - self.assertEqual( - table.render_signing_group(record.signed_agreement), "Test Affil" - ) + self.assertEqual(table.render_signing_group(record.signed_agreement), "Test Affil") # Other catch-all case that shouldn't happen. record = factories.SignedAgreementFactory() self.assertIsNone(table.render_signing_group(record)) def test_ordering(self): """Instances are ordered alphabetically by representative name.""" - instance_1 = factories.MemberAgreementFactory.create( - signed_agreement__representative__name="zzz" - ) - instance_2 = factories.MemberAgreementFactory.create( - signed_agreement__representative__name="aaa" - ) + instance_1 = factories.MemberAgreementFactory.create(signed_agreement__representative__name="zzz") + instance_2 = factories.MemberAgreementFactory.create(signed_agreement__representative__name="aaa") table = self.table_class(self.model.objects.all()) self.assertEqual(table.data[0], instance_2.signed_agreement) self.assertEqual(table.data[1], instance_1.signed_agreement) @@ -312,41 +286,29 @@ def test_row_count_with_no_objects(self): def test_row_count_with_one_agreement(self): member_agreement = factories.MemberAgreementFactory.create() - GroupAccountMembershipFactory.create( - group__signedagreement=member_agreement.signed_agreement - ) + GroupAccountMembershipFactory.create(group__signedagreement=member_agreement.signed_agreement) table = self.table_class(self.model.objects.all()) self.assertEqual(len(table.rows), 1) def test_row_count_with_one_agreement_multiple_members(self): member_agreement = factories.MemberAgreementFactory.create() - GroupAccountMembershipFactory.create_batch( - 5, group__signedagreement=member_agreement.signed_agreement - ) + GroupAccountMembershipFactory.create_batch(5, group__signedagreement=member_agreement.signed_agreement) table = self.table_class(self.model.objects.all()) self.assertEqual(len(table.rows), 5) def test_row_count_with_two_agreements_multiple_members(self): member_agreement_1 = factories.MemberAgreementFactory.create() - GroupAccountMembershipFactory.create_batch( - 2, group__signedagreement=member_agreement_1.signed_agreement - ) + GroupAccountMembershipFactory.create_batch(2, group__signedagreement=member_agreement_1.signed_agreement) member_agreement_2 = factories.MemberAgreementFactory.create() - GroupAccountMembershipFactory.create_batch( - 3, group__signedagreement=member_agreement_2.signed_agreement - ) + GroupAccountMembershipFactory.create_batch(3, group__signedagreement=member_agreement_2.signed_agreement) table = self.table_class(self.model.objects.all()) self.assertEqual(len(table.rows), 5) def test_includes_components(self): agreement_1 = factories.MemberAgreementFactory.create(is_primary=True) - GroupAccountMembershipFactory.create( - group__signedagreement=agreement_1.signed_agreement - ) + GroupAccountMembershipFactory.create(group__signedagreement=agreement_1.signed_agreement) agreement_2 = factories.MemberAgreementFactory.create(is_primary=False) - GroupAccountMembershipFactory.create( - group__signedagreement=agreement_2.signed_agreement - ) + GroupAccountMembershipFactory.create(group__signedagreement=agreement_2.signed_agreement) table = self.table_class(self.model.objects.all()) self.assertEqual(len(table.rows), 2) @@ -354,23 +316,15 @@ def test_render_signing_group(self): table = self.table_class(self.model.objects.all()) # Members. agreement = factories.MemberAgreementFactory(study_site__short_name="Test Site") - record = GroupAccountMembershipFactory.create( - group__signedagreement=agreement.signed_agreement - ) + record = GroupAccountMembershipFactory.create(group__signedagreement=agreement.signed_agreement) self.assertEqual(table.render_signing_group(record), "Test Site") # Data affiliates. - agreement = factories.DataAffiliateAgreementFactory( - study__short_name="Test Study" - ) - record = GroupAccountMembershipFactory.create( - group__signedagreement=agreement.signed_agreement - ) + agreement = factories.DataAffiliateAgreementFactory(study__short_name="Test Study") + record = GroupAccountMembershipFactory.create(group__signedagreement=agreement.signed_agreement) self.assertEqual(table.render_signing_group(record), "Test Study") # Non-data affiliates. agreement = factories.NonDataAffiliateAgreementFactory(affiliation="Test affil") - record = GroupAccountMembershipFactory.create( - group__signedagreement=agreement.signed_agreement - ) + record = GroupAccountMembershipFactory.create(group__signedagreement=agreement.signed_agreement) self.assertEqual(table.render_signing_group(record), "Test affil") # Other catch-all case that shouldn't happen. agreement = factories.SignedAgreementFactory() @@ -424,20 +378,14 @@ def test_render_date_shared(self): cdsa_workspace = factories.CDSAWorkspaceFactory.create() self.assertEqual(table.render_date_shared(cdsa_workspace), "—") # Shared. - WorkspaceGroupSharingFactory.create( - workspace=cdsa_workspace.workspace, group__name="PRIMED_ALL" - ) + WorkspaceGroupSharingFactory.create(workspace=cdsa_workspace.workspace, group__name="PRIMED_ALL") self.assertNotEqual(table.render_date_shared(cdsa_workspace), "—") def test_ordering(self): """Instances are ordered alphabetically by user name.""" agreement = factories.DataAffiliateAgreementFactory.create() - instance_1 = factories.CDSAWorkspaceFactory.create( - study=agreement.study, workspace__name="zzz" - ) - instance_2 = factories.CDSAWorkspaceFactory.create( - study=agreement.study, workspace__name="aaa" - ) + instance_1 = factories.CDSAWorkspaceFactory.create(study=agreement.study, workspace__name="zzz") + instance_2 = factories.CDSAWorkspaceFactory.create(study=agreement.study, workspace__name="aaa") table = self.table_class(self.model.objects.all()) self.assertEqual(table.data[0], instance_2) self.assertEqual(table.data[1], instance_1) diff --git a/primed/cdsa/tests/test_views.py b/primed/cdsa/tests/test_views.py index ab203d0a..e772d1f2 100644 --- a/primed/cdsa/tests/test_views.py +++ b/primed/cdsa/tests/test_views.py @@ -62,9 +62,7 @@ def test_links_for_staff_view(self): """Returns successful response code.""" user = User.objects.create_user(username="test", password="test") user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url()) @@ -74,25 +72,17 @@ def test_links_for_staff_view(self): self.assertContains(response, reverse("cdsa:records:index")) # Links to add CDSAs. self.assertNotContains(response, reverse("cdsa:signed_agreements:members:new")) - self.assertNotContains( - response, reverse("cdsa:signed_agreements:data_affiliates:new") - ) - self.assertNotContains( - response, reverse("cdsa:signed_agreements:non_data_affiliates:new") - ) + self.assertNotContains(response, reverse("cdsa:signed_agreements:data_affiliates:new")) + self.assertNotContains(response, reverse("cdsa:signed_agreements:non_data_affiliates:new")) def test_links_for_staff_edit(self): """Returns successful response code.""" user = User.objects.create_user(username="test", password="test") user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url()) @@ -102,12 +92,8 @@ def test_links_for_staff_edit(self): self.assertContains(response, reverse("cdsa:records:index")) # Links to add CDSAs. self.assertContains(response, reverse("cdsa:signed_agreements:members:new")) - self.assertContains( - response, reverse("cdsa:signed_agreements:data_affiliates:new") - ) - self.assertContains( - response, reverse("cdsa:signed_agreements:non_data_affiliates:new") - ) + self.assertContains(response, reverse("cdsa:signed_agreements:data_affiliates:new")) + self.assertContains(response, reverse("cdsa:signed_agreements:non_data_affiliates:new")) class AgreementVersionListTest(TestCase): @@ -119,9 +105,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -149,9 +133,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -162,9 +144,7 @@ def test_table_class(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.AgreementVersionTable - ) + self.assertIsInstance(response.context_data["table"], tables.AgreementVersionTable) def test_workspace_table_none(self): """No rows are shown if there are no AgreementVersion objects.""" @@ -191,9 +171,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) # Create an object test this with. self.obj = factories.AgreementMajorVersionFactory.create() @@ -223,9 +201,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(2)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -254,12 +230,8 @@ def test_context_table_classes(self): self.assertEqual(response.status_code, 200) self.assertIn("tables", response.context_data) self.assertEqual(len(response.context_data["tables"]), 2) - self.assertIsInstance( - response.context_data["tables"][0], tables.AgreementVersionTable - ) - self.assertIsInstance( - response.context_data["tables"][1], tables.SignedAgreementTable - ) + self.assertIsInstance(response.context_data["tables"][0], tables.AgreementVersionTable) + self.assertIsInstance(response.context_data["tables"][1], tables.SignedAgreementTable) def test_response_includes_agreement_version_table(self): """agreement_version_table includes agreement_versions with this major version.""" @@ -270,9 +242,7 @@ def test_response_includes_agreement_version_table(self): def test_response_includes_agreement_version_table_other_major_version(self): """agreement_version_table includes only agreement_versions with this major version.""" - other_agreement = factories.AgreementVersionFactory.create( - major_version__version=self.obj.version + 1 - ) + other_agreement = factories.AgreementVersionFactory.create(major_version__version=self.obj.version + 1) self.client.force_login(self.user) response = self.client.get(self.get_url(self.obj.version)) self.assertEqual(len(response.context_data["tables"][0].rows), 0) @@ -280,9 +250,7 @@ def test_response_includes_agreement_version_table_other_major_version(self): def test_response_signed_agreement_table_three_agreements(self): """signed_agreement_table includes all types of agreements.""" - factories.MemberAgreementFactory.create( - signed_agreement__version__major_version__version=self.obj.version - ) + factories.MemberAgreementFactory.create(signed_agreement__version__major_version__version=self.obj.version) factories.DataAffiliateAgreementFactory.create( signed_agreement__version__major_version__version=self.obj.version ) @@ -328,14 +296,10 @@ def test_invalidate_button_valid_user_has_edit_perm(self): """Invalidate button appears when the user has edit permission and the instance is valid.""" user = User.objects.create_user(username="test_edit", password="test_edit") user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url(self.obj.version)) @@ -365,14 +329,10 @@ def test_invalidate_button_invalid_user_has_edit_perm(self): self.obj.save() user = User.objects.create_user(username="test_edit", password="test_edit") user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url(self.obj.version)) @@ -409,14 +369,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -431,9 +387,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url(1)) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1) - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1)) def test_status_code_with_user_permission_edit(self): """Returns successful response code.""" @@ -444,9 +398,7 @@ def test_status_code_with_user_permission_edit(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -455,13 +407,9 @@ def test_access_without_user_permission(self): def test_access_without_user_permission_view(self): """Raises permission denied if user has only view permission.""" instance = factories.AgreementMajorVersionFactory.create() - user_view_perm = User.objects.create_user( - username="test-none", password="test-none" - ) + user_view_perm = User.objects.create_user(username="test-none", password="test-none") user_view_perm.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url(instance.version)) request.user = user_view_perm @@ -494,9 +442,7 @@ def test_form_class(self): instance = factories.AgreementMajorVersionFactory.create() self.client.force_login(self.user) response = self.client.get(self.get_url(instance.version)) - self.assertIsInstance( - response.context_data["form"], forms.AgreementMajorVersionIsValidForm - ) + self.assertIsInstance(response.context_data["form"], forms.AgreementMajorVersionIsValidForm) def test_invalidates_instance(self): """Can invalidate the instance.""" @@ -510,37 +456,25 @@ def test_invalidates_instance(self): def test_sets_one_signed_agreement_to_lapsed(self): """Sets SignedAgreements associated with this major version to LAPSED.""" instance = factories.AgreementMajorVersionFactory.create() - signed_agreement = factories.SignedAgreementFactory.create( - version__major_version=instance - ) + signed_agreement = factories.SignedAgreementFactory.create(version__major_version=instance) self.client.force_login(self.user) response = self.client.post(self.get_url(instance.version), {}) self.assertEqual(response.status_code, 302) signed_agreement.refresh_from_db() - self.assertEqual( - signed_agreement.status, models.SignedAgreement.StatusChoices.LAPSED - ) + self.assertEqual(signed_agreement.status, models.SignedAgreement.StatusChoices.LAPSED) def test_sets_two_signed_agreements_to_lapsed(self): """Sets SignedAgreements associated with this major version to LAPSED.""" instance = factories.AgreementMajorVersionFactory.create() - signed_agreement_1 = factories.SignedAgreementFactory.create( - version__major_version=instance - ) - signed_agreement_2 = factories.SignedAgreementFactory.create( - version__major_version=instance - ) + signed_agreement_1 = factories.SignedAgreementFactory.create(version__major_version=instance) + signed_agreement_2 = factories.SignedAgreementFactory.create(version__major_version=instance) self.client.force_login(self.user) response = self.client.post(self.get_url(instance.version), {}) self.assertEqual(response.status_code, 302) signed_agreement_1.refresh_from_db() - self.assertEqual( - signed_agreement_1.status, models.SignedAgreement.StatusChoices.LAPSED - ) + self.assertEqual(signed_agreement_1.status, models.SignedAgreement.StatusChoices.LAPSED) signed_agreement_2.refresh_from_db() - self.assertEqual( - signed_agreement_2.status, models.SignedAgreement.StatusChoices.LAPSED - ) + self.assertEqual(signed_agreement_2.status, models.SignedAgreement.StatusChoices.LAPSED) def test_only_sets_active_signed_agreements_to_lapsed(self): """Does not set SignedAgreements with a different status to LAPSED.""" @@ -561,17 +495,11 @@ def test_only_sets_active_signed_agreements_to_lapsed(self): response = self.client.post(self.get_url(instance.version), {}) self.assertEqual(response.status_code, 302) lapsed_agreement.refresh_from_db() - self.assertEqual( - lapsed_agreement.status, models.SignedAgreement.StatusChoices.LAPSED - ) + self.assertEqual(lapsed_agreement.status, models.SignedAgreement.StatusChoices.LAPSED) withdrawn_agreement.refresh_from_db() - self.assertEqual( - withdrawn_agreement.status, models.SignedAgreement.StatusChoices.WITHDRAWN - ) + self.assertEqual(withdrawn_agreement.status, models.SignedAgreement.StatusChoices.WITHDRAWN) replaced_agreement.refresh_from_db() - self.assertEqual( - replaced_agreement.status, models.SignedAgreement.StatusChoices.REPLACED - ) + self.assertEqual(replaced_agreement.status, models.SignedAgreement.StatusChoices.REPLACED) def test_only_sets_associated_signed_agreements_to_lapsed(self): """Does not set SignedAgreements associated with a different version to LAPSED.""" @@ -581,9 +509,7 @@ def test_only_sets_associated_signed_agreements_to_lapsed(self): response = self.client.post(self.get_url(instance.version), {}) self.assertEqual(response.status_code, 302) signed_agreement.refresh_from_db() - self.assertEqual( - signed_agreement.status, models.SignedAgreement.StatusChoices.ACTIVE - ) + self.assertEqual(signed_agreement.status, models.SignedAgreement.StatusChoices.ACTIVE) def test_redirect_url(self): """Redirects to successful url.""" @@ -599,9 +525,7 @@ def test_success_message(self): response = self.client.post(self.get_url(instance.version), {}) messages = [m.message for m in get_messages(response.wsgi_request)] self.assertEqual(len(messages), 1) - self.assertEqual( - views.AgreementMajorVersionInvalidate.success_message, str(messages[0]) - ) + self.assertEqual(views.AgreementMajorVersionInvalidate.success_message, str(messages[0])) def test_version_already_invalid_get(self): instance = factories.AgreementMajorVersionFactory.create(is_valid=False) @@ -635,9 +559,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) # Create an object test this with. self.obj = factories.AgreementVersionFactory.create() @@ -662,16 +584,12 @@ def test_view_redirect_not_logged_in(self): def test_status_code_with_user_permission(self): """Returns successful response code.""" self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.obj.major_version.version, self.obj.minor_version) - ) + response = self.client.get(self.get_url(self.obj.major_version.version, self.obj.minor_version)) self.assertEqual(response.status_code, 200) def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(2, 5)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -681,16 +599,12 @@ def test_view_status_code_with_existing_object(self): """Returns a successful status code for an existing object pk.""" # Only clients load the template. self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.obj.major_version.version, self.obj.minor_version) - ) + response = self.client.get(self.get_url(self.obj.major_version.version, self.obj.minor_version)) self.assertEqual(response.status_code, 200) def test_view_status_code_with_invalid_version(self): """Raises a 404 error with an invalid major and minor version.""" - request = self.factory.get( - self.get_url(self.obj.major_version.version + 1, self.obj.minor_version + 1) - ) + request = self.factory.get(self.get_url(self.obj.major_version.version + 1, self.obj.minor_version + 1)) request.user = self.user with self.assertRaises(Http404): self.get_view()( @@ -701,9 +615,7 @@ def test_view_status_code_with_invalid_version(self): def test_view_status_code_with_other_major_version(self): """Raises a 404 error with an invalid object major version.""" - request = self.factory.get( - self.get_url(self.obj.major_version.version + 1, self.obj.minor_version) - ) + request = self.factory.get(self.get_url(self.obj.major_version.version + 1, self.obj.minor_version)) request.user = self.user with self.assertRaises(Http404): self.get_view()( @@ -714,9 +626,7 @@ def test_view_status_code_with_other_major_version(self): def test_view_status_code_with_other_minor_version(self): """Raises a 404 error with an invalid object minor version.""" - request = self.factory.get( - self.get_url(self.obj.major_version.version, self.obj.minor_version + 1) - ) + request = self.factory.get(self.get_url(self.obj.major_version.version, self.obj.minor_version + 1)) request.user = self.user with self.assertRaises(Http404): self.get_view()( @@ -736,30 +646,18 @@ def test_view_status_code_with_other_minor_version(self): def test_response_includes_signed_agreement_table(self): """Response includes a table of SignedAgreements.""" self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.obj.major_version.version, self.obj.minor_version) - ) + response = self.client.get(self.get_url(self.obj.major_version.version, self.obj.minor_version)) self.assertEqual(response.status_code, 200) self.assertIn("signed_agreement_table", response.context_data) - self.assertIsInstance( - response.context_data["signed_agreement_table"], tables.SignedAgreementTable - ) + self.assertIsInstance(response.context_data["signed_agreement_table"], tables.SignedAgreementTable) def test_response_signed_agreement_table_three_agreements(self): """signed_agreement_table includes all types of agreements.""" - member_agreement = factories.MemberAgreementFactory.create( - signed_agreement__version=self.obj - ) - da_agreement = factories.DataAffiliateAgreementFactory.create( - signed_agreement__version=self.obj - ) - nda_agreement = factories.NonDataAffiliateAgreementFactory.create( - signed_agreement__version=self.obj - ) + member_agreement = factories.MemberAgreementFactory.create(signed_agreement__version=self.obj) + da_agreement = factories.DataAffiliateAgreementFactory.create(signed_agreement__version=self.obj) + nda_agreement = factories.NonDataAffiliateAgreementFactory.create(signed_agreement__version=self.obj) self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.obj.major_version.version, self.obj.minor_version) - ) + response = self.client.get(self.get_url(self.obj.major_version.version, self.obj.minor_version)) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.context_data["signed_agreement_table"].rows), 3) self.assertIn( @@ -781,9 +679,7 @@ def test_response_signed_agreement_table_other_version(self): da_agreement = factories.DataAffiliateAgreementFactory.create() nda_agreement = factories.NonDataAffiliateAgreementFactory.create() self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.obj.major_version.version, self.obj.minor_version) - ) + response = self.client.get(self.get_url(self.obj.major_version.version, self.obj.minor_version)) self.assertEqual(response.status_code, 200) self.assertEqual(len(response.context_data["signed_agreement_table"].rows), 0) self.assertNotIn( @@ -802,9 +698,7 @@ def test_response_signed_agreement_table_other_version(self): def test_response_show_deprecation_message_valid(self): """response context does not show a deprecation warning when AgreementMajorVersion is valid.""" self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.obj.major_version.version, self.obj.minor_version) - ) + response = self.client.get(self.get_url(self.obj.major_version.version, self.obj.minor_version)) self.assertEqual(response.status_code, 200) self.assertIn("show_deprecation_message", response.context_data) self.assertFalse(response.context_data["show_deprecation_message"]) @@ -815,9 +709,7 @@ def test_response_show_deprecation_message_not_valid(self): self.obj.major_version.is_valid = False self.obj.major_version.save() self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.obj.major_version.version, self.obj.minor_version) - ) + response = self.client.get(self.get_url(self.obj.major_version.version, self.obj.minor_version)) self.assertEqual(response.status_code, 200) self.assertIn("show_deprecation_message", response.context_data) self.assertTrue(response.context_data["show_deprecation_message"]) @@ -833,9 +725,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -863,9 +753,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -876,9 +764,7 @@ def test_table_class(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.SignedAgreementTable - ) + self.assertIsInstance(response.context_data["table"], tables.SignedAgreementTable) def test_workspace_table_none(self): """No rows are shown if there are no SignedAgreement objects.""" @@ -908,14 +794,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -930,9 +812,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url(1)) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1) - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1)) def test_status_code_with_user_permission(self): """Returns successful response code.""" @@ -943,13 +823,9 @@ def test_status_code_with_user_permission(self): def test_access_with_view_permission(self): """Raises permission denied if user has only view permission.""" - user_with_view_perm = User.objects.create_user( - username="test-other", password="test-other" - ) + user_with_view_perm = User.objects.create_user(username="test-other", password="test-other") user_with_view_perm.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url(1)) request.user = user_with_view_perm @@ -958,9 +834,7 @@ def test_access_with_view_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -987,9 +861,7 @@ def test_has_form_in_context(self): self.client.force_login(self.user) response = self.client.get(self.get_url(instance.signed_agreement.cc_id)) self.assertTrue("form" in response.context_data) - self.assertIsInstance( - response.context_data["form"], forms.SignedAgreementStatusForm - ) + self.assertIsInstance(response.context_data["form"], forms.SignedAgreementStatusForm) def test_can_modify_status(self): """Can change the status.""" @@ -1014,9 +886,7 @@ def test_invalid_status(self): signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE ) self.client.force_login(self.user) - response = self.client.post( - self.get_url(instance.signed_agreement.cc_id), {"status": "foo"} - ) + response = self.client.post(self.get_url(instance.signed_agreement.cc_id), {"status": "foo"}) self.assertEqual(response.status_code, 200) self.assertIn("form", response.context) form = response.context_data["form"] @@ -1042,9 +912,7 @@ def test_success_message(self): ) messages = [m.message for m in get_messages(response.wsgi_request)] self.assertEqual(len(messages), 1) - self.assertEqual( - views.SignedAgreementStatusUpdate.success_message, str(messages[0]) - ) + self.assertEqual(views.SignedAgreementStatusUpdate.success_message, str(messages[0])) def test_redirects_to_object_detail(self): """After successfully creating an object, view redirects to the object's detail page.""" @@ -1068,14 +936,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -1090,9 +954,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url(1)) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1) - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1)) def test_status_code_with_user_permission(self): """Returns successful response code.""" @@ -1103,13 +965,9 @@ def test_status_code_with_user_permission(self): def test_access_with_view_permission(self): """Raises permission denied if user has only view permission.""" - user_with_view_perm = User.objects.create_user( - username="test-other", password="test-other" - ) + user_with_view_perm = User.objects.create_user(username="test-other", password="test-other") user_with_view_perm.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url(1)) request.user = user_with_view_perm @@ -1118,9 +976,7 @@ def test_access_with_view_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -1147,9 +1003,7 @@ def test_has_form_in_context(self): self.client.force_login(self.user) response = self.client.get(self.get_url(instance.signed_agreement.cc_id)) self.assertTrue("form" in response.context_data) - self.assertIsInstance( - response.context_data["form"], forms.SignedAgreementStatusForm - ) + self.assertIsInstance(response.context_data["form"], forms.SignedAgreementStatusForm) def test_can_modify_status(self): """Can change the status.""" @@ -1174,9 +1028,7 @@ def test_invalid_status(self): signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE ) self.client.force_login(self.user) - response = self.client.post( - self.get_url(instance.signed_agreement.cc_id), {"status": "foo"} - ) + response = self.client.post(self.get_url(instance.signed_agreement.cc_id), {"status": "foo"}) self.assertEqual(response.status_code, 200) self.assertIn("form", response.context) form = response.context_data["form"] @@ -1202,9 +1054,7 @@ def test_success_message(self): ) messages = [m.message for m in get_messages(response.wsgi_request)] self.assertEqual(len(messages), 1) - self.assertEqual( - views.SignedAgreementStatusUpdate.success_message, str(messages[0]) - ) + self.assertEqual(views.SignedAgreementStatusUpdate.success_message, str(messages[0])) def test_redirects_to_object_detail(self): """After successfully creating an object, view redirects to the object's detail page.""" @@ -1228,14 +1078,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -1250,9 +1096,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url(1)) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1) - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1)) def test_status_code_with_user_permission(self): """Returns successful response code.""" @@ -1263,13 +1107,9 @@ def test_status_code_with_user_permission(self): def test_access_with_view_permission(self): """Raises permission denied if user has only view permission.""" - user_with_view_perm = User.objects.create_user( - username="test-other", password="test-other" - ) + user_with_view_perm = User.objects.create_user(username="test-other", password="test-other") user_with_view_perm.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url(1)) request.user = user_with_view_perm @@ -1278,9 +1118,7 @@ def test_access_with_view_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -1307,9 +1145,7 @@ def test_has_form_in_context(self): self.client.force_login(self.user) response = self.client.get(self.get_url(instance.signed_agreement.cc_id)) self.assertTrue("form" in response.context_data) - self.assertIsInstance( - response.context_data["form"], forms.SignedAgreementStatusForm - ) + self.assertIsInstance(response.context_data["form"], forms.SignedAgreementStatusForm) def test_can_modify_status(self): """Can change the status.""" @@ -1334,9 +1170,7 @@ def test_invalid_status(self): signed_agreement__status=models.SignedAgreement.StatusChoices.ACTIVE ) self.client.force_login(self.user) - response = self.client.post( - self.get_url(instance.signed_agreement.cc_id), {"status": "foo"} - ) + response = self.client.post(self.get_url(instance.signed_agreement.cc_id), {"status": "foo"}) self.assertEqual(response.status_code, 200) self.assertIn("form", response.context) form = response.context_data["form"] @@ -1362,9 +1196,7 @@ def test_success_message(self): ) messages = [m.message for m in get_messages(response.wsgi_request)] self.assertEqual(len(messages), 1) - self.assertEqual( - views.SignedAgreementStatusUpdate.success_message, str(messages[0]) - ) + self.assertEqual(views.SignedAgreementStatusUpdate.success_message, str(messages[0])) def test_redirects_to_object_detail(self): """After successfully creating an object, view redirects to the object's detail page.""" @@ -1388,14 +1220,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) # Create the admins group. self.cc_admins_group = ManagedGroupFactory.create(name="TEST_PRIMED_CC_ADMINS") @@ -1412,9 +1240,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_with_user_permission_edit(self): """Returns successful response code.""" @@ -1424,9 +1250,7 @@ def test_status_code_with_user_permission_edit(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -1434,13 +1258,9 @@ def test_access_without_user_permission(self): def test_access_without_user_permission_view(self): """Raises permission denied if user has only view permission.""" - user_view_perm = User.objects.create_user( - username="test-none", password="test-none" - ) + user_view_perm = User.objects.create_user(username="test-none", password="test-none") user_view_perm.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url()) request.user = user_view_perm @@ -1460,9 +1280,7 @@ def test_form_classes(self): response = self.client.get(self.get_url()) self.assertIsInstance(response.context_data["form"], forms.SignedAgreementForm) self.assertEqual(len(response.context_data["formset"].forms), 1) - self.assertIsInstance( - response.context_data["formset"].forms[0], forms.MemberAgreementForm - ) + self.assertIsInstance(response.context_data["formset"].forms[0], forms.MemberAgreementForm) def test_can_create_object(self): """Can create an object.""" @@ -1471,13 +1289,8 @@ def test_can_create_object(self): agreement_version = factories.AgreementVersionFactory.create() study_site = StudySiteFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -1515,12 +1328,8 @@ def test_can_create_object(self): self.assertEqual(new_agreement.type, new_agreement.MEMBER) # AnVIL group was set correctly. self.assertIsInstance(new_agreement.anvil_access_group, ManagedGroup) - self.assertEqual( - new_agreement.anvil_access_group.name, "TEST_PRIMED_CDSA_ACCESS_1234" - ) - self.assertEqual( - new_agreement.status, models.SignedAgreement.StatusChoices.ACTIVE - ) + self.assertEqual(new_agreement.anvil_access_group.name, "TEST_PRIMED_CDSA_ACCESS_1234") + self.assertEqual(new_agreement.status, models.SignedAgreement.StatusChoices.ACTIVE) # Check the agreement type. self.assertEqual(models.MemberAgreement.objects.count(), 1) new_agreement_type = models.MemberAgreement.objects.latest("pk") @@ -1535,13 +1344,8 @@ def test_redirect_url(self): agreement_version = factories.AgreementVersionFactory.create() study_site = StudySiteFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -1576,13 +1380,8 @@ def test_success_message(self): agreement_version = factories.AgreementVersionFactory.create() study_site = StudySiteFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -2068,12 +1867,8 @@ def test_error_duplicate_project_id(self): ) self.assertEqual(response.status_code, 200) # No new objects were created. - self.assertEqual( - models.SignedAgreement.objects.count(), 1 - ) # One already existed. - self.assertEqual( - models.MemberAgreement.objects.count(), 1 - ) # One already existed. + self.assertEqual(models.SignedAgreement.objects.count(), 1) # One already existed. + self.assertEqual(models.MemberAgreement.objects.count(), 1) # One already existed. # Form has errors in the correct field. form = response.context_data["form"] self.assertFalse(form.is_valid()) @@ -2096,13 +1891,8 @@ def test_creates_anvil_access_group(self): representative = UserFactory.create() agreement_version = factories.AgreementVersionFactory.create() study_site = StudySiteFactory.create() - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -2148,12 +1938,8 @@ def test_creates_anvil_groups_different_setting_access_group_prefix(self): representative = UserFactory.create() agreement_version = factories.AgreementVersionFactory.create() study_site = StudySiteFactory.create() - api_url = ( - self.api_client.sam_entry_point + "/api/groups/v1/foo_CDSA_ACCESS_2345" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/foo_CDSA_ACCESS_2345" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -2195,18 +1981,12 @@ def test_creates_anvil_groups_different_setting_cc_admins_group_name(self): representative = UserFactory.create() agreement_version = factories.AgreementVersionFactory.create() study_site = StudySiteFactory.create() - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345/admin/foo@firecloud.org", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345/admin/foo@firecloud.org", status=204, ) response = self.client.post( @@ -2241,12 +2021,8 @@ def test_manage_group_create_api_error(self): agreement_version = factories.AgreementVersionFactory.create() study_site = StudySiteFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=500, json={"message": "other error"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1" + self.anvil_response_mock.add(responses.POST, api_url, status=500, json={"message": "other error"}) response = self.client.post( self.get_url(), { @@ -2311,9 +2087,7 @@ def test_managed_group_already_exists_in_app(self): # ...but there was an error with the group name. messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) - self.assertEqual( - views.MemberAgreementCreate.ERROR_CREATING_GROUP, str(messages[0]) - ) + self.assertEqual(views.MemberAgreementCreate.ERROR_CREATING_GROUP, str(messages[0])) # No dbGaPApplication was created. self.assertEqual(models.SignedAgreement.objects.count(), 0) @@ -2322,13 +2096,8 @@ def test_admin_group_membership_api_error(self): representative = UserFactory.create() agreement_version = factories.AgreementVersionFactory.create() study_site = StudySiteFactory.create() - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -2378,9 +2147,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) # Create an object test this with. self.obj = factories.MemberAgreementFactory.create() @@ -2410,9 +2177,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -2436,9 +2201,7 @@ def test_response_includes_link_to_user_profile(self): """Response includes a link to the user profile page.""" self.client.force_login(self.user) response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id)) - self.assertContains( - response, self.obj.signed_agreement.representative.get_absolute_url() - ) + self.assertContains(response, self.obj.signed_agreement.representative.get_absolute_url()) def test_response_includes_link_to_study_site(self): """Response includes a link to the study site detail page.""" @@ -2450,9 +2213,7 @@ def test_response_includes_link_to_anvil_access_group(self): """Response includes a link to the AnVIL access group detail page.""" self.client.force_login(self.user) response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id)) - self.assertContains( - response, self.obj.signed_agreement.anvil_access_group.get_absolute_url() - ) + self.assertContains(response, self.obj.signed_agreement.anvil_access_group.get_absolute_url()) def test_response_show_deprecation_message_valid(self): """response context does not show a deprecation warning when AgreementMajorVersion is valid.""" @@ -2478,14 +2239,10 @@ def test_change_status_button_user_has_edit_perm(self): """Invalidate button appears when the user has edit permission and the instance is valid.""" user = User.objects.create_user(username="test_edit", password="test_edit") user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id)) @@ -2548,9 +2305,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -2578,9 +2333,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -2591,9 +2344,7 @@ def test_table_class(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.MemberAgreementTable - ) + self.assertIsInstance(response.context_data["table"], tables.MemberAgreementTable) def test_workspace_table_none(self): """No rows are shown if there are no MemberAgreement objects.""" @@ -2621,14 +2372,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) # Create the admins group. self.cc_admins_group = ManagedGroupFactory.create(name="TEST_PRIMED_CC_ADMINS") @@ -2645,9 +2392,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_with_user_permission_edit(self): """Returns successful response code.""" @@ -2657,9 +2402,7 @@ def test_status_code_with_user_permission_edit(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -2667,13 +2410,9 @@ def test_access_without_user_permission(self): def test_access_without_user_permission_view(self): """Raises permission denied if user has only view permission.""" - user_view_perm = User.objects.create_user( - username="test-none", password="test-none" - ) + user_view_perm = User.objects.create_user(username="test-none", password="test-none") user_view_perm.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url()) request.user = user_view_perm @@ -2692,9 +2431,7 @@ def test_form_classes(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIsInstance(response.context_data["form"], forms.SignedAgreementForm) - self.assertIsInstance( - response.context_data["formset"].forms[0], forms.DataAffiliateAgreementForm - ) + self.assertIsInstance(response.context_data["formset"].forms[0], forms.DataAffiliateAgreementForm) def test_can_create_object(self): """Can create an object.""" @@ -2705,15 +2442,13 @@ def test_can_create_object(self): # API response to create the associated anvil_access_group. self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", status=201, json={"message": "mock message"}, ) self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1234", status=201, json={"message": "mock message"}, ) @@ -2760,12 +2495,8 @@ def test_can_create_object(self): self.assertEqual(new_agreement.type, new_agreement.DATA_AFFILIATE) # AnVIL group was set correctly. self.assertIsInstance(new_agreement.anvil_access_group, ManagedGroup) - self.assertEqual( - new_agreement.anvil_access_group.name, "TEST_PRIMED_CDSA_ACCESS_1234" - ) - self.assertEqual( - new_agreement.status, models.SignedAgreement.StatusChoices.ACTIVE - ) + self.assertEqual(new_agreement.anvil_access_group.name, "TEST_PRIMED_CDSA_ACCESS_1234") + self.assertEqual(new_agreement.status, models.SignedAgreement.StatusChoices.ACTIVE) # Check the agreement type. self.assertEqual(models.DataAffiliateAgreement.objects.count(), 1) new_agreement_type = models.DataAffiliateAgreement.objects.latest("pk") @@ -2773,9 +2504,7 @@ def test_can_create_object(self): self.assertEqual(new_agreement_type.is_primary, True) self.assertEqual(new_agreement_type.study, study) self.assertIsInstance(new_agreement_type.anvil_upload_group, ManagedGroup) - self.assertEqual( - new_agreement_type.anvil_upload_group.name, "TEST_PRIMED_CDSA_UPLOAD_1234" - ) + self.assertEqual(new_agreement_type.anvil_upload_group.name, "TEST_PRIMED_CDSA_UPLOAD_1234") def test_redirect_url(self): """Redirects to successful url.""" @@ -2786,15 +2515,13 @@ def test_redirect_url(self): # API response to create the associated anvil_access_group. self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", status=201, json={"message": "mock message"}, ) self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1234", status=201, json={"message": "mock message"}, ) @@ -2840,15 +2567,13 @@ def test_success_message(self): # API response to create the associated anvil_access_group. self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", status=201, json={"message": "mock message"}, ) self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1234", status=201, json={"message": "mock message"}, ) @@ -2884,9 +2609,7 @@ def test_success_message(self): ) messages = [m.message for m in get_messages(response.wsgi_request)] self.assertEqual(len(messages), 1) - self.assertEqual( - views.DataAffiliateAgreementCreate.success_message, str(messages[0]) - ) + self.assertEqual(views.DataAffiliateAgreementCreate.success_message, str(messages[0])) def test_can_create_primary_with_requires_study_review(self): """Can create an object.""" @@ -2897,15 +2620,13 @@ def test_can_create_primary_with_requires_study_review(self): # API response to create the associated anvil_access_group. self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", status=201, json={"message": "mock message"}, ) self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1234", status=201, json={"message": "mock message"}, ) @@ -2995,15 +2716,13 @@ def test_can_create_primary_with_additional_limitations(self): # API response to create the associated anvil_access_group. self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", status=201, json={"message": "mock message"}, ) self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1234", status=201, json={"message": "mock message"}, ) @@ -3541,12 +3260,8 @@ def test_error_duplicate_project_id(self): ) self.assertEqual(response.status_code, 200) # No new objects were created. - self.assertEqual( - models.SignedAgreement.objects.count(), 1 - ) # One already existed. - self.assertEqual( - models.DataAffiliateAgreement.objects.count(), 1 - ) # One already existed. + self.assertEqual(models.SignedAgreement.objects.count(), 1) # One already existed. + self.assertEqual(models.DataAffiliateAgreement.objects.count(), 1) # One already existed. # Form has errors in the correct field. form = response.context_data["form"] self.assertFalse(form.is_valid()) @@ -3571,15 +3286,13 @@ def test_creates_anvil_groups(self): study = StudyFactory.create() self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345", status=201, json={"message": "mock message"}, ) self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_2345", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_2345", status=201, json={"message": "mock message"}, ) @@ -3615,18 +3328,14 @@ def test_creates_anvil_groups(self): self.assertEqual(response.status_code, 302) new_object = models.SignedAgreement.objects.latest("pk") # An access group was created. - self.assertEqual( - new_object.anvil_access_group.name, "TEST_PRIMED_CDSA_ACCESS_2345" - ) + self.assertEqual(new_object.anvil_access_group.name, "TEST_PRIMED_CDSA_ACCESS_2345") self.assertTrue(new_object.anvil_access_group.is_managed_by_app) # An upload group was created. self.assertEqual( new_object.dataaffiliateagreement.anvil_upload_group.name, "TEST_PRIMED_CDSA_UPLOAD_2345", ) - self.assertTrue( - new_object.dataaffiliateagreement.anvil_upload_group.is_managed_by_app - ) + self.assertTrue(new_object.dataaffiliateagreement.anvil_upload_group.is_managed_by_app) # Group-group memberships was created with PRIMED_CC_ADMINS as an admin of the access/uploader group. new_membership_1 = GroupGroupMembership.objects.get( parent_group=new_object.anvil_access_group, child_group=self.cc_admins_group @@ -3698,9 +3407,7 @@ def test_creates_anvil_access_group_different_setting(self): new_object.dataaffiliateagreement.anvil_upload_group.name, "foo_CDSA_UPLOAD_2345", ) - self.assertTrue( - new_object.dataaffiliateagreement.anvil_upload_group.is_managed_by_app - ) + self.assertTrue(new_object.dataaffiliateagreement.anvil_upload_group.is_managed_by_app) @override_settings(ANVIL_CC_ADMINS_GROUP_NAME="foo") def test_creates_anvil_groups_different_setting_cc_admins_group_name(self): @@ -3712,29 +3419,25 @@ def test_creates_anvil_groups_different_setting_cc_admins_group_name(self): study = StudyFactory.create() self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345", status=201, json={"message": "mock message"}, ) self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_2345", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_2345", status=201, json={"message": "mock message"}, ) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345/admin/foo@firecloud.org", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345/admin/foo@firecloud.org", status=204, ) self.anvil_response_mock.add( responses.PUT, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_2345/admin/foo@firecloud.org", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_2345/admin/foo@firecloud.org", status=204, ) response = self.client.post( @@ -3776,8 +3479,7 @@ def test_access_group_create_api_error(self): # API response to create the associated anvil_access_group. self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1", status=500, json={"message": "other error"}, ) @@ -3822,8 +3524,7 @@ def test_upload_group_create_api_error(self): # API response to create the associated anvil_access_group. self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1", status=201, json={"message": "mock message"}, ) @@ -3836,8 +3537,7 @@ def test_upload_group_create_api_error(self): ) self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1", status=500, json={"message": "other error"}, ) @@ -3905,9 +3605,7 @@ def test_access_group_already_exists_in_app(self): # ...but there was an error with the group name. messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) - self.assertEqual( - views.DataAffiliateAgreementCreate.ERROR_CREATING_GROUP, str(messages[0]) - ) + self.assertEqual(views.DataAffiliateAgreementCreate.ERROR_CREATING_GROUP, str(messages[0])) self.assertEqual(models.SignedAgreement.objects.count(), 0) def test_upload_group_already_exists_in_app(self): @@ -3942,9 +3640,7 @@ def test_upload_group_already_exists_in_app(self): # ...but there was an error with the group name. messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) - self.assertEqual( - views.DataAffiliateAgreementCreate.ERROR_CREATING_GROUP, str(messages[0]) - ) + self.assertEqual(views.DataAffiliateAgreementCreate.ERROR_CREATING_GROUP, str(messages[0])) self.assertEqual(models.SignedAgreement.objects.count(), 0) def test_admin_group_membership_access_api_error(self): @@ -3956,8 +3652,7 @@ def test_admin_group_membership_access_api_error(self): # API response to create the associated anvil_access_group. self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", status=201, json={"message": "mock message"}, ) @@ -4022,15 +3717,13 @@ def test_admin_group_membership_upload_api_error(self): # API response to create the associated anvil_access_group. self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", status=201, json={"message": "mock message"}, ) self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_UPLOAD_1234", status=201, json={"message": "mock message"}, ) @@ -4089,9 +3782,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) # Create an object test this with. self.obj = factories.DataAffiliateAgreementFactory.create() @@ -4121,9 +3812,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -4147,9 +3836,7 @@ def test_response_includes_link_to_user_profile(self): """Response includes a link to the user profile page.""" self.client.force_login(self.user) response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id)) - self.assertContains( - response, self.obj.signed_agreement.representative.get_absolute_url() - ) + self.assertContains(response, self.obj.signed_agreement.representative.get_absolute_url()) def test_response_includes_link_to_study(self): """Response includes a link to the study detail page.""" @@ -4161,9 +3848,7 @@ def test_response_includes_link_to_anvil_access_group(self): """Response includes a link to the AnVIL access group detail page.""" self.client.force_login(self.user) response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id)) - self.assertContains( - response, self.obj.signed_agreement.anvil_access_group.get_absolute_url() - ) + self.assertContains(response, self.obj.signed_agreement.anvil_access_group.get_absolute_url()) def test_response_includes_link_to_anvil_upload_group(self): """Response includes a link to the AnVIL access group detail page.""" @@ -4195,14 +3880,10 @@ def test_change_status_button_user_has_edit_perm(self): """Invalidate button appears when the user has edit permission and the instance is valid.""" user = User.objects.create_user(username="test_edit", password="test_edit") user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id)) @@ -4241,9 +3922,7 @@ def test_response_includes_additional_limitations(self): self.client.force_login(self.user) response = self.client.get(self.get_url(instance.signed_agreement.cc_id)) self.assertContains(response, "Additional limitations") - self.assertContains( - response, "Test limitations for this data affiliate agreement" - ) + self.assertContains(response, "Test limitations for this data affiliate agreement") def test_response_with_no_additional_limitations(self): """Response includes a link to the study detail page.""" @@ -4280,9 +3959,7 @@ def test_response_is_primary(self): def test_response_requires_study_review(self): """Response includes info about requires_study_review.""" - instance = factories.DataAffiliateAgreementFactory.create( - is_primary=True, requires_study_review=True - ) + instance = factories.DataAffiliateAgreementFactory.create(is_primary=True, requires_study_review=True) self.client.force_login(self.user) response = self.client.get(self.get_url(instance.signed_agreement.cc_id)) self.assertContains(response, "Study review required?") @@ -4312,9 +3989,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -4342,9 +4017,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -4355,9 +4028,7 @@ def test_table_class(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.DataAffiliateAgreementTable - ) + self.assertIsInstance(response.context_data["table"], tables.DataAffiliateAgreementTable) def test_workspace_table_none(self): """No rows are shown if there are no DataAffiliateAgreement objects.""" @@ -4385,14 +4056,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) # Create the admins group. self.cc_admins_group = ManagedGroupFactory.create(name="TEST_PRIMED_CC_ADMINS") @@ -4409,9 +4076,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_with_user_permission_edit(self): """Returns successful response code.""" @@ -4421,9 +4086,7 @@ def test_status_code_with_user_permission_edit(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -4431,13 +4094,9 @@ def test_access_without_user_permission(self): def test_access_without_user_permission_view(self): """Raises permission denied if user has only view permission.""" - user_view_perm = User.objects.create_user( - username="test-none", password="test-none" - ) + user_view_perm = User.objects.create_user(username="test-none", password="test-none") user_view_perm.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url()) request.user = user_view_perm @@ -4469,8 +4128,7 @@ def test_can_create_object(self): # API response to create the associated anvil_access_group. self.anvil_response_mock.add( responses.POST, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234", status=201, json={"message": "mock message"}, ) @@ -4510,12 +4168,8 @@ def test_can_create_object(self): self.assertEqual(new_agreement.type, new_agreement.NON_DATA_AFFILIATE) # AnVIL group was set correctly. self.assertIsInstance(new_agreement.anvil_access_group, ManagedGroup) - self.assertEqual( - new_agreement.anvil_access_group.name, "TEST_PRIMED_CDSA_ACCESS_1234" - ) - self.assertEqual( - new_agreement.status, models.SignedAgreement.StatusChoices.ACTIVE - ) + self.assertEqual(new_agreement.anvil_access_group.name, "TEST_PRIMED_CDSA_ACCESS_1234") + self.assertEqual(new_agreement.status, models.SignedAgreement.StatusChoices.ACTIVE) # Check the agreement type. self.assertEqual(models.NonDataAffiliateAgreement.objects.count(), 1) new_agreement_type = models.NonDataAffiliateAgreement.objects.latest("pk") @@ -4528,13 +4182,8 @@ def test_redirect_url(self): representative = UserFactory.create() agreement_version = factories.AgreementVersionFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -4567,13 +4216,8 @@ def test_success_message(self): representative = UserFactory.create() agreement_version = factories.AgreementVersionFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1234" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -4599,9 +4243,7 @@ def test_success_message(self): ) messages = [m.message for m in get_messages(response.wsgi_request)] self.assertEqual(len(messages), 1) - self.assertEqual( - views.NonDataAffiliateAgreementCreate.success_message, str(messages[0]) - ) + self.assertEqual(views.NonDataAffiliateAgreementCreate.success_message, str(messages[0])) def test_error_missing_cc_id(self): """Form shows an error when cc_id is missing.""" @@ -4964,12 +4606,8 @@ def test_error_duplicate_project_id(self): ) self.assertEqual(response.status_code, 200) # No new objects were created. - self.assertEqual( - models.SignedAgreement.objects.count(), 1 - ) # One already existed. - self.assertEqual( - models.NonDataAffiliateAgreement.objects.count(), 1 - ) # One already existed. + self.assertEqual(models.SignedAgreement.objects.count(), 1) # One already existed. + self.assertEqual(models.NonDataAffiliateAgreement.objects.count(), 1) # One already existed. # Form has errors in the correct field. form = response.context_data["form"] self.assertFalse(form.is_valid()) @@ -4991,13 +4629,8 @@ def test_creates_anvil_access_group(self): self.client.force_login(self.user) representative = UserFactory.create() agreement_version = factories.AgreementVersionFactory.create() - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -5041,12 +4674,8 @@ def test_creates_anvil_groups_different_setting(self): self.client.force_login(self.user) representative = UserFactory.create() agreement_version = factories.AgreementVersionFactory.create() - api_url = ( - self.api_client.sam_entry_point + "/api/groups/v1/foo_CDSA_ACCESS_2345" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/foo_CDSA_ACCESS_2345" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -5086,18 +4715,12 @@ def test_creates_anvil_groups_different_setting_cc_admins_group_name(self): self.client.force_login(self.user) representative = UserFactory.create() agreement_version = factories.AgreementVersionFactory.create() - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345/admin/foo@firecloud.org", + self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_2345/admin/foo@firecloud.org", status=204, ) response = self.client.post( @@ -5130,12 +4753,8 @@ def test_manage_group_create_api_error(self): representative = UserFactory.create() agreement_version = factories.AgreementVersionFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=500, json={"message": "other error"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_CDSA_ACCESS_1" + self.anvil_response_mock.add(responses.POST, api_url, status=500, json={"message": "other error"}) response = self.client.post( self.get_url(), { @@ -5197,9 +4816,7 @@ def test_managed_group_already_exists_in_app(self): # ...but there was an error with the group name. messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) - self.assertEqual( - views.NonDataAffiliateAgreementCreate.ERROR_CREATING_GROUP, str(messages[0]) - ) + self.assertEqual(views.NonDataAffiliateAgreementCreate.ERROR_CREATING_GROUP, str(messages[0])) # No dbGaPApplication was created. self.assertEqual(models.SignedAgreement.objects.count(), 0) @@ -5213,9 +4830,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) # Create an object test this with. self.obj = factories.NonDataAffiliateAgreementFactory.create() @@ -5245,9 +4860,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -5271,17 +4884,13 @@ def test_response_includes_link_to_user_profile(self): """Response includes a link to the user profile page.""" self.client.force_login(self.user) response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id)) - self.assertContains( - response, self.obj.signed_agreement.representative.get_absolute_url() - ) + self.assertContains(response, self.obj.signed_agreement.representative.get_absolute_url()) def test_response_includes_link_to_anvil_access_group(self): """Response includes a link to the AnVIL access group detail page.""" self.client.force_login(self.user) response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id)) - self.assertContains( - response, self.obj.signed_agreement.anvil_access_group.get_absolute_url() - ) + self.assertContains(response, self.obj.signed_agreement.anvil_access_group.get_absolute_url()) def test_response_show_deprecation_message_valid(self): """response context does not show a deprecation warning when AgreementMajorVersion is valid.""" @@ -5307,14 +4916,10 @@ def test_change_status_button_user_has_edit_perm(self): """Invalidate button appears when the user has edit permission and the instance is valid.""" user = User.objects.create_user(username="test_edit", password="test_edit") user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url(self.obj.signed_agreement.cc_id)) @@ -5354,9 +4959,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -5384,9 +4987,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -5397,9 +4998,7 @@ def test_table_class(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.NonDataAffiliateAgreementTable - ) + self.assertIsInstance(response.context_data["table"], tables.NonDataAffiliateAgreementTable) def test_workspace_table_none(self): """No rows are shown if there are no NonDataAffiliateAgreement objects.""" @@ -5482,9 +5081,7 @@ def test_table_class(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.RepresentativeRecordsTable - ) + self.assertIsInstance(response.context_data["table"], tables.RepresentativeRecordsTable) def test_table_no_rows(self): """No rows are shown if there are no SignedAgreement objects.""" @@ -5534,9 +5131,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) # Create the test group. self.anvil_cdsa_group = ManagedGroupFactory.create(name="TEST_PRIMED_CDSA") @@ -5569,9 +5164,7 @@ def test_status_code_with_user_permission_view(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -5712,14 +5305,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) # Create the test group. self.anvil_cdsa_group = ManagedGroupFactory.create(name="TEST_PRIMED_CDSA") @@ -5745,9 +5334,7 @@ def test_status_code_with_user_permission_view(self): """Returns forbidden response code if the user only has view permission.""" user_view = User.objects.create_user(username="test-view", password="test-view") user_view.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url(1)) request.user = user_view @@ -5756,9 +5343,7 @@ def test_status_code_with_user_permission_view(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -5773,9 +5358,7 @@ def test_get_context_data_access_audit(self): """The data_access_audit exists in the context.""" member_agreement = factories.MemberAgreementFactory.create() self.client.force_login(self.user) - response = self.client.get( - self.get_url(member_agreement.signed_agreement.cc_id) - ) + response = self.client.get(self.get_url(member_agreement.signed_agreement.cc_id)) self.assertIn("audit_result", response.context_data) self.assertIsInstance( response.context_data["audit_result"], @@ -5791,9 +5374,7 @@ def test_get_context_verified_access(self): ) # Check the audit_result in the context. self.client.force_login(self.user) - response = self.client.get( - self.get_url(member_agreement.signed_agreement.cc_id) - ) + response = self.client.get(self.get_url(member_agreement.signed_agreement.cc_id)) self.assertIn("audit_result", response.context_data) audit_result = response.context_data["audit_result"] self.assertIsInstance(audit_result, signed_agreement_audit.VerifiedAccess) @@ -5811,9 +5392,7 @@ def test_get_context_verified_no_access(self): # ) # Check the audit_result in the context. self.client.force_login(self.user) - response = self.client.get( - self.get_url(member_agreement.signed_agreement.cc_id) - ) + response = self.client.get(self.get_url(member_agreement.signed_agreement.cc_id)) self.assertIn("audit_result", response.context_data) audit_result = response.context_data["audit_result"] self.assertIsInstance(audit_result, signed_agreement_audit.VerifiedNoAccess) @@ -5831,9 +5410,7 @@ def test_get_context_remove_access(self): ) # Check the audit_result in the context. self.client.force_login(self.user) - response = self.client.get( - self.get_url(member_agreement.signed_agreement.cc_id) - ) + response = self.client.get(self.get_url(member_agreement.signed_agreement.cc_id)) self.assertIn("audit_result", response.context_data) audit_result = response.context_data["audit_result"] self.assertIsInstance(audit_result, signed_agreement_audit.RemoveAccess) @@ -5849,9 +5426,7 @@ def test_get_context_grant_access(self): # ) # Check the audit_result in the context. self.client.force_login(self.user) - response = self.client.get( - self.get_url(member_agreement.signed_agreement.cc_id) - ) + response = self.client.get(self.get_url(member_agreement.signed_agreement.cc_id)) self.assertIn("audit_result", response.context_data) audit_result = response.context_data["audit_result"] self.assertIsInstance(audit_result, signed_agreement_audit.GrantAccess) @@ -5869,9 +5444,7 @@ def test_post_context_verified_access(self): ) # Check the response. self.client.force_login(self.user) - response = self.client.post( - self.get_url(member_agreement.signed_agreement.cc_id), {} - ) + response = self.client.post(self.get_url(member_agreement.signed_agreement.cc_id), {}) self.assertRedirects(response, member_agreement.get_absolute_url()) # Make sure the membership hasn't changed. membership.refresh_from_db() @@ -5888,9 +5461,7 @@ def test_post_context_verified_no_access(self): # ) # Check the response. self.client.force_login(self.user) - response = self.client.post( - self.get_url(member_agreement.signed_agreement.cc_id), {} - ) + response = self.client.post(self.get_url(member_agreement.signed_agreement.cc_id), {}) self.assertRedirects(response, member_agreement.get_absolute_url()) self.assertEqual(GroupGroupMembership.objects.count(), 0) @@ -5916,9 +5487,7 @@ def test_post_context_remove_access(self): ) # Check the response. self.client.force_login(self.user) - response = self.client.post( - self.get_url(member_agreement.signed_agreement.cc_id), {} - ) + response = self.client.post(self.get_url(member_agreement.signed_agreement.cc_id), {}) self.assertRedirects(response, member_agreement.get_absolute_url()) # Make sure the membership hasn't changed. with self.assertRaises(GroupGroupMembership.DoesNotExist): @@ -5947,21 +5516,15 @@ def test_post_htmx_context_remove_access(self): # Check the response. self.client.force_login(self.user) header = {"HTTP_HX-Request": "true"} - response = self.client.post( - self.get_url(member_agreement.signed_agreement.cc_id), {}, **header - ) - self.assertEqual( - response.content.decode(), views.SignedAgreementAuditResolve.htmx_success - ) + response = self.client.post(self.get_url(member_agreement.signed_agreement.cc_id), {}, **header) + self.assertEqual(response.content.decode(), views.SignedAgreementAuditResolve.htmx_success) # Make sure the membership hasn't changed. with self.assertRaises(GroupGroupMembership.DoesNotExist): membership.refresh_from_db() def test_post_context_grant_access(self): """Context with GrantAccess.""" - member_agreement = factories.MemberAgreementFactory.create( - signed_agreement__cc_id=2345 - ) + member_agreement = factories.MemberAgreementFactory.create(signed_agreement__cc_id=2345) # GroupGroupMembershipFactory.create( # parent_group=self.anvil_cdsa_group, # child_group=member_agreement.signed_agreement.anvil_access_group, @@ -5978,9 +5541,7 @@ def test_post_context_grant_access(self): ) # Check the response. self.client.force_login(self.user) - response = self.client.post( - self.get_url(member_agreement.signed_agreement.cc_id), {} - ) + response = self.client.post(self.get_url(member_agreement.signed_agreement.cc_id), {}) self.assertRedirects(response, member_agreement.get_absolute_url()) membership = GroupGroupMembership.objects.get( parent_group=self.anvil_cdsa_group, @@ -5990,9 +5551,7 @@ def test_post_context_grant_access(self): def test_post_htmx_grant_access(self): """Context with GrantAccess.""" - member_agreement = factories.MemberAgreementFactory.create( - signed_agreement__cc_id=2345 - ) + member_agreement = factories.MemberAgreementFactory.create(signed_agreement__cc_id=2345) # GroupGroupMembershipFactory.create( # parent_group=self.anvil_cdsa_group, # child_group=member_agreement.signed_agreement.anvil_access_group, @@ -6010,12 +5569,8 @@ def test_post_htmx_grant_access(self): # Check the response. self.client.force_login(self.user) header = {"HTTP_HX-Request": "true"} - response = self.client.post( - self.get_url(member_agreement.signed_agreement.cc_id), {}, **header - ) - self.assertEqual( - response.content.decode(), views.SignedAgreementAuditResolve.htmx_success - ) + response = self.client.post(self.get_url(member_agreement.signed_agreement.cc_id), {}, **header) + self.assertEqual(response.content.decode(), views.SignedAgreementAuditResolve.htmx_success) membership = GroupGroupMembership.objects.get( parent_group=self.anvil_cdsa_group, child_group=member_agreement.signed_agreement.anvil_access_group, @@ -6027,9 +5582,7 @@ def test_get_only_this_signed_agreement(self): factories.MemberAgreementFactory.create(signed_agreement__cc_id=1234) member_agreement = factories.MemberAgreementFactory.create() self.client.force_login(self.user) - response = self.client.get( - self.get_url(member_agreement.signed_agreement.cc_id) - ) + response = self.client.get(self.get_url(member_agreement.signed_agreement.cc_id)) self.assertIn("audit_result", response.context_data) self.assertIsInstance( response.context_data["audit_result"], @@ -6043,9 +5596,7 @@ def test_get_only_this_signed_agreement(self): def test_post_only_this_signed_agreement(self): """Only runs on the specified signed_agreement.""" factories.MemberAgreementFactory.create(signed_agreement__cc_id=1234) - member_agreement = factories.MemberAgreementFactory.create( - signed_agreement__cc_id=2345 - ) + member_agreement = factories.MemberAgreementFactory.create(signed_agreement__cc_id=2345) # GroupGroupMembershipFactory.create( # parent_group=self.anvil_cdsa_group, # child_group=member_agreement.signed_agreement.anvil_access_group, @@ -6062,9 +5613,7 @@ def test_post_only_this_signed_agreement(self): ) # Check the response. self.client.force_login(self.user) - response = self.client.post( - self.get_url(member_agreement.signed_agreement.cc_id), {} - ) + response = self.client.post(self.get_url(member_agreement.signed_agreement.cc_id), {}) self.assertRedirects(response, member_agreement.get_absolute_url()) membership = GroupGroupMembership.objects.get( parent_group=self.anvil_cdsa_group, @@ -6074,9 +5623,7 @@ def test_post_only_this_signed_agreement(self): def test_anvil_api_error_grant(self): """AnVIL API errors are properly handled.""" - member_agreement = factories.MemberAgreementFactory.create( - signed_agreement__cc_id=2345 - ) + member_agreement = factories.MemberAgreementFactory.create(signed_agreement__cc_id=2345) # GroupGroupMembershipFactory.create( # parent_group=self.anvil_cdsa_group, # child_group=member_agreement.signed_agreement.anvil_access_group, @@ -6094,9 +5641,7 @@ def test_anvil_api_error_grant(self): ) # Check the response. self.client.force_login(self.user) - response = self.client.post( - self.get_url(member_agreement.signed_agreement.cc_id), {} - ) + response = self.client.post(self.get_url(member_agreement.signed_agreement.cc_id), {}) self.assertEqual(response.status_code, 200) # No group membership was created. self.assertEqual(GroupGroupMembership.objects.count(), 0) @@ -6111,9 +5656,7 @@ def test_anvil_api_error_grant(self): def test_anvil_api_error_grant_htmx(self): """AnVIL API errors are properly handled.""" - member_agreement = factories.MemberAgreementFactory.create( - signed_agreement__cc_id=2345 - ) + member_agreement = factories.MemberAgreementFactory.create(signed_agreement__cc_id=2345) # GroupGroupMembershipFactory.create( # parent_group=self.anvil_cdsa_group, # child_group=member_agreement.signed_agreement.anvil_access_group, @@ -6132,12 +5675,8 @@ def test_anvil_api_error_grant_htmx(self): # Check the response. self.client.force_login(self.user) header = {"HTTP_HX-Request": "true"} - response = self.client.post( - self.get_url(member_agreement.signed_agreement.cc_id), {}, **header - ) - self.assertEqual( - response.content.decode(), views.SignedAgreementAuditResolve.htmx_error - ) + response = self.client.post(self.get_url(member_agreement.signed_agreement.cc_id), {}, **header) + self.assertEqual(response.content.decode(), views.SignedAgreementAuditResolve.htmx_error) # No group membership was created. self.assertEqual(GroupGroupMembership.objects.count(), 0) # No messages waere added. @@ -6167,9 +5706,7 @@ def test_anvil_api_error_remove(self): ) # Check the response. self.client.force_login(self.user) - response = self.client.post( - self.get_url(member_agreement.signed_agreement.cc_id), {} - ) + response = self.client.post(self.get_url(member_agreement.signed_agreement.cc_id), {}) self.assertEqual(response.status_code, 200) # The group-group membership still exists. membership.refresh_from_db() @@ -6206,12 +5743,8 @@ def test_anvil_api_error_remove_htmx(self): # Check the response. self.client.force_login(self.user) header = {"HTTP_HX-Request": "true"} - response = self.client.post( - self.get_url(member_agreement.signed_agreement.cc_id), {}, **header - ) - self.assertEqual( - response.content.decode(), views.SignedAgreementAuditResolve.htmx_error - ) + response = self.client.post(self.get_url(member_agreement.signed_agreement.cc_id), {}, **header) + self.assertEqual(response.content.decode(), views.SignedAgreementAuditResolve.htmx_error) # The group-group membership still exists. membership.refresh_from_db() # No messages was added. @@ -6222,17 +5755,14 @@ def test_anvil_api_error_remove_htmx(self): def test_anvil_cdsa_group_does_not_exist(self): """Settings file has a different CDSA group name.""" cdsa_group = ManagedGroupFactory.create(name="FOOBAR") - member_agreement = factories.MemberAgreementFactory.create( - signed_agreement__cc_id=2345 - ) + member_agreement = factories.MemberAgreementFactory.create(signed_agreement__cc_id=2345) # GroupGroupMembershipFactory.create( # parent_group=self.anvil_cdsa_group, # child_group=member_agreement.signed_agreement.anvil_access_group, # ) # Add API response api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/FOOBAR/member/TEST_PRIMED_CDSA_ACCESS_2345@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/FOOBAR/member/TEST_PRIMED_CDSA_ACCESS_2345@firecloud.org" ) self.anvil_response_mock.add( responses.PUT, @@ -6241,9 +5771,7 @@ def test_anvil_cdsa_group_does_not_exist(self): ) # Check the response. self.client.force_login(self.user) - response = self.client.post( - self.get_url(member_agreement.signed_agreement.cc_id), {} - ) + response = self.client.post(self.get_url(member_agreement.signed_agreement.cc_id), {}) self.assertRedirects(response, member_agreement.get_absolute_url()) membership = GroupGroupMembership.objects.get( parent_group=cdsa_group, @@ -6261,9 +5789,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.anvil_cdsa_group = ManagedGroupFactory.create(name="TEST_PRIMED_CDSA") @@ -6295,9 +5821,7 @@ def test_status_code_with_user_permission_view(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -6447,14 +5971,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) # Create the test group. self.anvil_cdsa_group = ManagedGroupFactory.create(name="TEST_PRIMED_CDSA") @@ -6480,9 +6000,7 @@ def test_status_code_with_user_permission_view(self): """Returns forbidden response code if the user only has view permission.""" user_view = User.objects.create_user(username="test-view", password="test-view") user_view.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url("foo", "bar")) request.user = user_view @@ -6491,9 +6009,7 @@ def test_status_code_with_user_permission_view(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url("foo", "bar")) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -6508,20 +6024,14 @@ def test_billing_project_does_not_exist(self): def test_workspace_name_does_not_exist(self): cdsa_workspace = factories.CDSAWorkspaceFactory.create() self.client.force_login(self.user) - response = self.client.get( - self.get_url(cdsa_workspace.workspace.billing_project.name, "foo") - ) + response = self.client.get(self.get_url(cdsa_workspace.workspace.billing_project.name, "foo")) self.assertEqual(response.status_code, 404) def test_get_context_audit_result(self): """The data_access_audit exists in the context.""" workspace = factories.CDSAWorkspaceFactory.create() self.client.force_login(self.user) - response = self.client.get( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ) - ) + response = self.client.get(self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name)) self.assertIn("audit_result", response.context_data) self.assertIsInstance( response.context_data["audit_result"], @@ -6539,11 +6049,7 @@ def test_get_context_verified_access(self): ) # Check the table in the context. self.client.force_login(self.user) - response = self.client.get( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ) - ) + response = self.client.get(self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name)) self.assertIn("audit_result", response.context_data) audit_result = response.context_data["audit_result"] self.assertIsInstance( @@ -6563,11 +6069,7 @@ def test_get_verified_no_access(self): workspace = factories.CDSAWorkspaceFactory.create() # Check the table in the context. self.client.force_login(self.user) - response = self.client.get( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ) - ) + response = self.client.get(self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name)) self.assertIn("audit_result", response.context_data) audit_result = response.context_data["audit_result"] self.assertIsInstance( @@ -6576,9 +6078,7 @@ def test_get_verified_no_access(self): ) self.assertEqual(audit_result.workspace, workspace) self.assertIsNone(audit_result.data_affiliate_agreement) - self.assertEqual( - audit_result.note, workspace_audit.WorkspaceAccessAudit.NO_PRIMARY_AGREEMENT - ) + self.assertEqual(audit_result.note, workspace_audit.WorkspaceAccessAudit.NO_PRIMARY_AGREEMENT) self.assertIsNone(audit_result.action) def test_get_grant_access(self): @@ -6588,11 +6088,7 @@ def test_get_grant_access(self): workspace = factories.CDSAWorkspaceFactory.create(study=study) # Check the table in the context. self.client.force_login(self.user) - response = self.client.get( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ) - ) + response = self.client.get(self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name)) self.assertIn("audit_result", response.context_data) audit_result = response.context_data["audit_result"] self.assertIsInstance( @@ -6615,19 +6111,13 @@ def test_get_remove_access(self): child_group=self.anvil_cdsa_group, ) self.client.force_login(self.user) - response = self.client.get( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ) - ) + response = self.client.get(self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name)) self.assertIn("audit_result", response.context_data) audit_result = response.context_data["audit_result"] self.assertIsInstance(audit_result, workspace_audit.RemoveAccess) self.assertEqual(audit_result.workspace, workspace) self.assertIsNone(audit_result.data_affiliate_agreement) - self.assertEqual( - audit_result.note, workspace_audit.WorkspaceAccessAudit.NO_PRIMARY_AGREEMENT - ) + self.assertEqual(audit_result.note, workspace_audit.WorkspaceAccessAudit.NO_PRIMARY_AGREEMENT) self.assertEqual(audit_result.action, "Remove access") def test_post_verified_access(self): @@ -6644,18 +6134,14 @@ def test_post_verified_access(self): # Check the response self.client.force_login(self.user) response = self.client.post( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ), + self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name), {}, ) self.assertRedirects(response, workspace.get_absolute_url()) # Make sure the membership hasn't changed. membership.refresh_from_db() self.assertEqual(membership.modified, date_created) - self.assertEqual( - membership.parent_group, workspace.workspace.authorization_domains.first() - ) + self.assertEqual(membership.parent_group, workspace.workspace.authorization_domains.first()) self.assertEqual(membership.child_group, self.anvil_cdsa_group) def test_post_verified_no_access(self): @@ -6669,9 +6155,7 @@ def test_post_verified_no_access(self): # Check the response self.client.force_login(self.user) response = self.client.post( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ), + self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name), {}, ) self.assertRedirects(response, workspace.get_absolute_url()) @@ -6681,14 +6165,11 @@ def test_post_grant_access(self): """Context with GrantAccess.""" study = StudyFactory.create() factories.DataAffiliateAgreementFactory.create(study=study) - workspace = factories.CDSAWorkspaceFactory.create( - study=study, workspace__name="TEST_CDSA" - ) + workspace = factories.CDSAWorkspaceFactory.create(study=study, workspace__name="TEST_CDSA") # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_CDSA/member/TEST_PRIMED_CDSA@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_CDSA/member/TEST_PRIMED_CDSA@firecloud.org" ) self.anvil_response_mock.add( responses.PUT, @@ -6698,9 +6179,7 @@ def test_post_grant_access(self): # Check the response. self.client.force_login(self.user) response = self.client.post( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ), + self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name), {}, ) self.assertRedirects(response, workspace.get_absolute_url()) @@ -6714,14 +6193,11 @@ def test_post_grant_access_htmx(self): """Context with GrantAccess.""" study = StudyFactory.create() factories.DataAffiliateAgreementFactory.create(study=study) - workspace = factories.CDSAWorkspaceFactory.create( - study=study, workspace__name="TEST_CDSA" - ) + workspace = factories.CDSAWorkspaceFactory.create(study=study, workspace__name="TEST_CDSA") # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_CDSA/member/TEST_PRIMED_CDSA@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_CDSA/member/TEST_PRIMED_CDSA@firecloud.org" ) self.anvil_response_mock.add( responses.PUT, @@ -6732,15 +6208,9 @@ def test_post_grant_access_htmx(self): self.client.force_login(self.user) header = {"HTTP_HX-Request": "true"} response = self.client.post( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ), - {}, - **header - ) - self.assertEqual( - response.content.decode(), views.SignedAgreementAuditResolve.htmx_success + self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name), {}, **header ) + self.assertEqual(response.content.decode(), views.SignedAgreementAuditResolve.htmx_success) # Membership has been created. membership = GroupGroupMembership.objects.get( parent_group=workspace.workspace.authorization_domains.first(), @@ -6750,17 +6220,14 @@ def test_post_grant_access_htmx(self): def test_post_remove_access(self): """Get request with RemoveAccess audit result.""" - workspace = factories.CDSAWorkspaceFactory.create( - workspace__name="TEST_WORKSPACE" - ) + workspace = factories.CDSAWorkspaceFactory.create(workspace__name="TEST_WORKSPACE") membership = GroupGroupMembershipFactory.create( parent_group=workspace.workspace.authorization_domains.first(), child_group=self.anvil_cdsa_group, ) # Add API response api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_WORKSPACE/member/TEST_PRIMED_CDSA@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_WORKSPACE/member/TEST_PRIMED_CDSA@firecloud.org" ) self.anvil_response_mock.add( responses.DELETE, @@ -6769,9 +6236,7 @@ def test_post_remove_access(self): ) self.client.force_login(self.user) response = self.client.post( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ), + self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name), {}, ) self.assertRedirects(response, workspace.get_absolute_url()) @@ -6781,17 +6246,14 @@ def test_post_remove_access(self): def test_post_htmx_remove_access_htmx(self): """HTMX post request with RemoveAccess audit result.""" - workspace = factories.CDSAWorkspaceFactory.create( - workspace__name="TEST_WORKSPACE" - ) + workspace = factories.CDSAWorkspaceFactory.create(workspace__name="TEST_WORKSPACE") membership = GroupGroupMembershipFactory.create( parent_group=workspace.workspace.authorization_domains.first(), child_group=self.anvil_cdsa_group, ) # Add API response api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_WORKSPACE/member/TEST_PRIMED_CDSA@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_WORKSPACE/member/TEST_PRIMED_CDSA@firecloud.org" ) self.anvil_response_mock.add( responses.DELETE, @@ -6801,15 +6263,9 @@ def test_post_htmx_remove_access_htmx(self): self.client.force_login(self.user) header = {"HTTP_HX-Request": "true"} response = self.client.post( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ), - {}, - **header - ) - self.assertEqual( - response.content.decode(), views.CDSAWorkspaceAuditResolve.htmx_success + self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name), {}, **header ) + self.assertEqual(response.content.decode(), views.CDSAWorkspaceAuditResolve.htmx_success) # Make sure the membership has been deleted. with self.assertRaises(GroupGroupMembership.DoesNotExist): membership.refresh_from_db() @@ -6819,11 +6275,7 @@ def test_get_only_this_workspace(self): factories.CDSAWorkspaceFactory.create() workspace = factories.CDSAWorkspaceFactory.create() self.client.force_login(self.user) - response = self.client.get( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ) - ) + response = self.client.get(self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name)) self.assertIn("audit_result", response.context_data) self.assertIsInstance( response.context_data["audit_result"], @@ -6835,14 +6287,11 @@ def test_anvil_api_error_grant(self): """AnVIL API errors are properly handled.""" study = StudyFactory.create() factories.DataAffiliateAgreementFactory.create(study=study) - workspace = factories.CDSAWorkspaceFactory.create( - study=study, workspace__name="TEST_CDSA" - ) + workspace = factories.CDSAWorkspaceFactory.create(study=study, workspace__name="TEST_CDSA") # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_CDSA/member/TEST_PRIMED_CDSA@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_CDSA/member/TEST_PRIMED_CDSA@firecloud.org" ) self.anvil_response_mock.add( responses.PUT, @@ -6853,9 +6302,7 @@ def test_anvil_api_error_grant(self): # Check the response. self.client.force_login(self.user) response = self.client.post( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ), + self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name), {}, ) self.assertEqual(response.status_code, 200) @@ -6874,14 +6321,11 @@ def test_anvil_api_error_grant_htmx(self): """AnVIL API errors are properly handled with htmx.""" study = StudyFactory.create() factories.DataAffiliateAgreementFactory.create(study=study) - workspace = factories.CDSAWorkspaceFactory.create( - study=study, workspace__name="TEST_CDSA" - ) + workspace = factories.CDSAWorkspaceFactory.create(study=study, workspace__name="TEST_CDSA") # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_CDSA/member/TEST_PRIMED_CDSA@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_CDSA/member/TEST_PRIMED_CDSA@firecloud.org" ) self.anvil_response_mock.add( responses.PUT, @@ -6893,15 +6337,9 @@ def test_anvil_api_error_grant_htmx(self): self.client.force_login(self.user) header = {"HTTP_HX-Request": "true"} response = self.client.post( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ), - {}, - **header - ) - self.assertEqual( - response.content.decode(), views.SignedAgreementAuditResolve.htmx_error + self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name), {}, **header ) + self.assertEqual(response.content.decode(), views.SignedAgreementAuditResolve.htmx_error) # No group membership was created. self.assertEqual(GroupGroupMembership.objects.count(), 0) # No messages were added. @@ -6910,17 +6348,14 @@ def test_anvil_api_error_grant_htmx(self): def test_anvil_api_error_remove(self): """AnVIL API errors are properly handled.""" - workspace = factories.CDSAWorkspaceFactory.create( - workspace__name="TEST_WORKSPACE" - ) + workspace = factories.CDSAWorkspaceFactory.create(workspace__name="TEST_WORKSPACE") membership = GroupGroupMembershipFactory.create( parent_group=workspace.workspace.authorization_domains.first(), child_group=self.anvil_cdsa_group, ) # Add API response api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_WORKSPACE/member/TEST_PRIMED_CDSA@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_WORKSPACE/member/TEST_PRIMED_CDSA@firecloud.org" ) self.anvil_response_mock.add( responses.DELETE, @@ -6930,9 +6365,7 @@ def test_anvil_api_error_remove(self): ) self.client.force_login(self.user) response = self.client.post( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ), + self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name), {}, ) self.assertEqual(response.status_code, 200) @@ -6949,17 +6382,14 @@ def test_anvil_api_error_remove(self): def test_anvil_api_error_remove_htmx(self): """AnVIL API errors are properly handled.""" - workspace = factories.CDSAWorkspaceFactory.create( - workspace__name="TEST_WORKSPACE" - ) + workspace = factories.CDSAWorkspaceFactory.create(workspace__name="TEST_WORKSPACE") membership = GroupGroupMembershipFactory.create( parent_group=workspace.workspace.authorization_domains.first(), child_group=self.anvil_cdsa_group, ) # Add API response api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_WORKSPACE/member/TEST_PRIMED_CDSA@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_WORKSPACE/member/TEST_PRIMED_CDSA@firecloud.org" ) self.anvil_response_mock.add( responses.DELETE, @@ -6970,15 +6400,9 @@ def test_anvil_api_error_remove_htmx(self): self.client.force_login(self.user) header = {"HTTP_HX-Request": "true"} response = self.client.post( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ), - {}, - **header - ) - self.assertEqual( - response.content.decode(), views.SignedAgreementAuditResolve.htmx_error + self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name), {}, **header ) + self.assertEqual(response.content.decode(), views.SignedAgreementAuditResolve.htmx_error) # The group-group membership still exists. membership.refresh_from_db() # No messages was added. @@ -6991,15 +6415,10 @@ def test_different_cdsa_group_name(self): cdsa_group = ManagedGroupFactory.create(name="FOOBAR") study = StudyFactory.create() factories.DataAffiliateAgreementFactory.create(study=study) - workspace = factories.CDSAWorkspaceFactory.create( - study=study, workspace__name="TEST_CDSA" - ) + workspace = factories.CDSAWorkspaceFactory.create(study=study, workspace__name="TEST_CDSA") # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_CDSA/member/FOOBAR@firecloud.org" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_CDSA/member/FOOBAR@firecloud.org" self.anvil_response_mock.add( responses.PUT, api_url, @@ -7008,9 +6427,7 @@ def test_different_cdsa_group_name(self): # Check the response. self.client.force_login(self.user) response = self.client.post( - self.get_url( - workspace.workspace.billing_project.name, workspace.workspace.name - ), + self.get_url(workspace.workspace.billing_project.name, workspace.workspace.name), {}, ) self.assertRedirects(response, workspace.get_absolute_url()) @@ -7070,12 +6487,8 @@ def test_table_three_rows(self): def test_only_shows_data_affiliate_records(self): member_agreement = factories.MemberAgreementFactory.create(is_primary=True) - data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create( - is_primary=True - ) - non_data_affiliate_agreement = ( - factories.NonDataAffiliateAgreementFactory.create() - ) + data_affiliate_agreement = factories.DataAffiliateAgreementFactory.create(is_primary=True) + non_data_affiliate_agreement = factories.NonDataAffiliateAgreementFactory.create() self.client.force_login(self.user) response = self.client.get(self.get_url()) table = response.context_data["table"] @@ -7085,12 +6498,8 @@ def test_only_shows_data_affiliate_records(self): self.assertNotIn(non_data_affiliate_agreement, table.data) def test_only_shows_primary_data_affiliate_records(self): - primary_agreement = factories.DataAffiliateAgreementFactory.create( - is_primary=True - ) - component_agreement = factories.DataAffiliateAgreementFactory.create( - is_primary=False - ) + primary_agreement = factories.DataAffiliateAgreementFactory.create(is_primary=True) + component_agreement = factories.DataAffiliateAgreementFactory.create(is_primary=False) self.client.force_login(self.user) response = self.client.get(self.get_url()) table = response.context_data["table"] @@ -7150,9 +6559,7 @@ def test_table_class(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.UserAccessRecordsTable - ) + self.assertIsInstance(response.context_data["table"], tables.UserAccessRecordsTable) def test_table_no_rows(self): """No rows are shown if there are no users in CDSA access groups.""" @@ -7172,9 +6579,7 @@ def test_table_one_agreement_no_members(self): def test_table_one_agreement_one_member(self): """One row is shown if there is one agreement and one account group member.""" agreement = factories.MemberAgreementFactory.create(is_primary=True) - GroupAccountMembershipFactory.create( - group=agreement.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create(group=agreement.signed_agreement.anvil_access_group) self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) @@ -7183,9 +6588,7 @@ def test_table_one_agreement_one_member(self): def test_table_one_agreements_two_members(self): """Two rows are shown if there is one agreement with two account group members.""" agreement = factories.MemberAgreementFactory.create(is_primary=True) - GroupAccountMembershipFactory.create_batch( - 2, group=agreement.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create_batch(2, group=agreement.signed_agreement.anvil_access_group) self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) @@ -7194,13 +6597,9 @@ def test_table_one_agreements_two_members(self): def test_table_two_agreements(self): """Multiple rows is shown if there are two agreements and multiple account group members.""" agreement_1 = factories.MemberAgreementFactory.create(is_primary=True) - GroupAccountMembershipFactory.create_batch( - 2, group=agreement_1.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create_batch(2, group=agreement_1.signed_agreement.anvil_access_group) agreement_2 = factories.MemberAgreementFactory.create(is_primary=True) - GroupAccountMembershipFactory.create_batch( - 3, group=agreement_2.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create_batch(3, group=agreement_2.signed_agreement.anvil_access_group) self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) @@ -7208,17 +6607,11 @@ def test_table_two_agreements(self): def test_only_shows_records_for_all_agreement_types(self): agreement_1 = factories.MemberAgreementFactory.create(is_primary=True) - GroupAccountMembershipFactory.create( - group=agreement_1.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create(group=agreement_1.signed_agreement.anvil_access_group) agreement_2 = factories.DataAffiliateAgreementFactory.create(is_primary=True) - GroupAccountMembershipFactory.create( - group=agreement_2.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create(group=agreement_2.signed_agreement.anvil_access_group) agreement_3 = factories.NonDataAffiliateAgreementFactory.create() - GroupAccountMembershipFactory.create( - group=agreement_3.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create(group=agreement_3.signed_agreement.anvil_access_group) self.client.force_login(self.user) response = self.client.get(self.get_url()) table = response.context_data["table"] @@ -7226,13 +6619,9 @@ def test_only_shows_records_for_all_agreement_types(self): def test_shows_includes_component_agreements(self): agreement_1 = factories.MemberAgreementFactory.create(is_primary=False) - GroupAccountMembershipFactory.create( - group=agreement_1.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create(group=agreement_1.signed_agreement.anvil_access_group) agreement_2 = factories.DataAffiliateAgreementFactory.create(is_primary=False) - GroupAccountMembershipFactory.create( - group=agreement_2.signed_agreement.anvil_access_group - ) + GroupAccountMembershipFactory.create(group=agreement_2.signed_agreement.anvil_access_group) self.client.force_login(self.user) response = self.client.get(self.get_url()) table = response.context_data["table"] @@ -7256,15 +6645,11 @@ def test_does_not_show_other_group_members(self): def test_only_includes_active_agreements(self): active_agreement = factories.MemberAgreementFactory.create() - active_member = GroupAccountMembershipFactory.create( - group=active_agreement.signed_agreement.anvil_access_group - ) + active_member = GroupAccountMembershipFactory.create(group=active_agreement.signed_agreement.anvil_access_group) lapsed_agreement = factories.MemberAgreementFactory.create( signed_agreement__status=models.SignedAgreement.StatusChoices.LAPSED ) - lapsed_member = GroupAccountMembershipFactory.create( - group=lapsed_agreement.signed_agreement.anvil_access_group - ) + lapsed_member = GroupAccountMembershipFactory.create(group=lapsed_agreement.signed_agreement.anvil_access_group) withdrawn_agreement = factories.MemberAgreementFactory.create( signed_agreement__status=models.SignedAgreement.StatusChoices.WITHDRAWN ) @@ -7318,9 +6703,7 @@ def test_table_class(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.CDSAWorkspaceRecordsTable - ) + self.assertIsInstance(response.context_data["table"], tables.CDSAWorkspaceRecordsTable) def test_table_no_rows(self): """No rows are shown if there are no CDSAWorkspaces objects.""" @@ -7380,9 +6763,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def test_status_code_with_user_permission(self): @@ -7439,11 +6820,7 @@ def test_render_duo_modifiers(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 - ) - ) + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) obj = factories.CDSAWorkspaceFactory.create() DataPrepWorkspaceFactory.create(target_workspace=obj.workspace) @@ -7471,15 +6848,11 @@ def test_associated_data_prep_workspaces_context_exists(self): def test_only_show_one_associated_data_prep_workspace(self): cdsa_obj = factories.CDSAWorkspaceFactory.create() - dataPrep_obj = DataPrepWorkspaceFactory.create( - target_workspace=cdsa_obj.workspace - ) + dataPrep_obj = DataPrepWorkspaceFactory.create(target_workspace=cdsa_obj.workspace) self.client.force_login(self.user) response = self.client.get(cdsa_obj.get_absolute_url()) self.assertIn("associated_data_prep_workspaces", response.context_data) - self.assertEqual( - len(response.context_data["associated_data_prep_workspaces"].rows), 1 - ) + self.assertEqual(len(response.context_data["associated_data_prep_workspaces"].rows), 1) self.assertIn( dataPrep_obj.workspace, response.context_data["associated_data_prep_workspaces"].data, @@ -7487,18 +6860,12 @@ def test_only_show_one_associated_data_prep_workspace(self): def test_show_two_associated_data_prep_workspaces(self): cdsa_obj = factories.CDSAWorkspaceFactory.create() - dataPrep_obj1 = DataPrepWorkspaceFactory.create( - target_workspace=cdsa_obj.workspace - ) - dataPrep_obj2 = DataPrepWorkspaceFactory.create( - target_workspace=cdsa_obj.workspace - ) + dataPrep_obj1 = DataPrepWorkspaceFactory.create(target_workspace=cdsa_obj.workspace) + dataPrep_obj2 = DataPrepWorkspaceFactory.create(target_workspace=cdsa_obj.workspace) self.client.force_login(self.user) response = self.client.get(cdsa_obj.get_absolute_url()) self.assertIn("associated_data_prep_workspaces", response.context_data) - self.assertEqual( - len(response.context_data["associated_data_prep_workspaces"].rows), 2 - ) + self.assertEqual(len(response.context_data["associated_data_prep_workspaces"].rows), 2) self.assertIn( dataPrep_obj1.workspace, response.context_data["associated_data_prep_workspaces"].data, @@ -7517,9 +6884,7 @@ def test_context_data_prep_active_with_no_prep_workspace(self): def test_context_data_prep_active_with_one_inactive_prep_workspace(self): instance = factories.CDSAWorkspaceFactory.create() - DataPrepWorkspaceFactory.create( - target_workspace=instance.workspace, is_active=False - ) + DataPrepWorkspaceFactory.create(target_workspace=instance.workspace, is_active=False) self.client.force_login(self.user) response = self.client.get(instance.get_absolute_url()) self.assertIn("data_prep_active", response.context_data) @@ -7527,9 +6892,7 @@ def test_context_data_prep_active_with_one_inactive_prep_workspace(self): def test_context_data_prep_active_with_one_active_prep_workspace(self): instance = factories.CDSAWorkspaceFactory.create() - DataPrepWorkspaceFactory.create( - target_workspace=instance.workspace, is_active=True - ) + DataPrepWorkspaceFactory.create(target_workspace=instance.workspace, is_active=True) self.client.force_login(self.user) response = self.client.get(instance.get_absolute_url()) self.assertIn("data_prep_active", response.context_data) @@ -7537,12 +6900,8 @@ def test_context_data_prep_active_with_one_active_prep_workspace(self): def test_context_data_prep_active_with_one_active_one_inactive_prep_workspace(self): instance = factories.CDSAWorkspaceFactory.create() - DataPrepWorkspaceFactory.create( - target_workspace=instance.workspace, is_active=True - ) - DataPrepWorkspaceFactory.create( - target_workspace=instance.workspace, is_active=True - ) + DataPrepWorkspaceFactory.create(target_workspace=instance.workspace, is_active=True) + DataPrepWorkspaceFactory.create(target_workspace=instance.workspace, is_active=True) self.client.force_login(self.user) response = self.client.get(instance.get_absolute_url()) self.assertIn("data_prep_active", response.context_data) @@ -7571,9 +6930,7 @@ def test_response_includes_additional_limitations(self): ) self.client.force_login(self.user) response = self.client.get(instance.get_absolute_url()) - self.assertContains( - response, "Test limitations for this data affiliate agreement" - ) + self.assertContains(response, "Test limitations for this data affiliate agreement") def test_response_data_use_limitations(self): """All data use limitations appear in the response content.""" @@ -7582,12 +6939,8 @@ def test_response_data_use_limitations(self): data_use_permission__abbreviation="P", additional_limitations="Test additional limitations for workspace", ) - modifier_1 = DataUseModifierFactory.create( - abbreviation="M1", definition="Test modifier 1." - ) - modifier_2 = DataUseModifierFactory.create( - abbreviation="M2", definition="Test modifier 2." - ) + modifier_1 = DataUseModifierFactory.create(abbreviation="M1", definition="Test modifier 1.") + modifier_2 = DataUseModifierFactory.create(abbreviation="M2", definition="Test modifier 2.") instance.data_use_modifiers.add(modifier_1, modifier_2) # Create an agreement with data use limitations. factories.DataAffiliateAgreementFactory.create( @@ -7663,7 +7016,7 @@ def test_response_no_primary_cdsa(self): self.assertContains( response, # """
Associated CDSA
mdash;
""" - """No primary CDSA""" + """No primary CDSA""", # """
Associated CDSA
""", # noqa: E501 ) @@ -7680,14 +7033,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.requester = UserFactory.create() self.workspace_type = "cdsa" @@ -7797,9 +7146,7 @@ def test_creates_upload_workspace_with_duo_modifiers(self): def test_creates_upload_workspace_with_disease_term(self): """Posting valid data to the form creates a workspace data object when using a custom adapter.""" study = factories.StudyFactory.create() - data_use_permission = DataUsePermissionFactory.create( - requires_disease_term=True - ) + data_use_permission = DataUsePermissionFactory.create(requires_disease_term=True) # Create an extra that won't be specified. billing_project = BillingProjectFactory.create(name="test-billing-project") url = self.api_client.rawls_entry_point + "/api/workspaces" diff --git a/primed/cdsa/urls.py b/primed/cdsa/urls.py index 2a991365..ee4d487d 100644 --- a/primed/cdsa/urls.py +++ b/primed/cdsa/urls.py @@ -48,9 +48,7 @@ [ path("", views.DataAffiliateAgreementList.as_view(), name="list"), path("new/", views.DataAffiliateAgreementCreate.as_view(), name="new"), - path( - "/", views.DataAffiliateAgreementDetail.as_view(), name="detail" - ), + path("/", views.DataAffiliateAgreementDetail.as_view(), name="detail"), path( "/update/", views.SignedAgreementStatusUpdate.as_view(), diff --git a/primed/cdsa/views.py b/primed/cdsa/views.py index c2bc83bb..5986db22 100644 --- a/primed/cdsa/views.py +++ b/primed/cdsa/views.py @@ -31,9 +31,7 @@ logger = logging.getLogger(__name__) -class AgreementMajorVersionDetail( - AnVILConsortiumManagerStaffViewRequired, MultiTableMixin, DetailView -): +class AgreementMajorVersionDetail(AnVILConsortiumManagerStaffViewRequired, MultiTableMixin, DetailView): """Display a "detail" page for an agreement major version (e.g., 1.x).""" model = models.AgreementMajorVersion @@ -47,18 +45,13 @@ def get_object(self, queryset=None): obj = queryset.get(version=major_version) except (KeyError, self.model.DoesNotExist): raise Http404( - _("No %(verbose_name)s found matching the query") - % {"verbose_name": queryset.model._meta.verbose_name} + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} ) return obj def get_tables_data(self): - agreement_version_qs = models.AgreementVersion.objects.filter( - major_version=self.object - ) - signed_agreement_qs = models.SignedAgreement.objects.filter( - version__major_version=self.object - ) + agreement_version_qs = models.AgreementVersion.objects.filter(major_version=self.object) + signed_agreement_qs = models.SignedAgreement.objects.filter(version__major_version=self.object) return [agreement_version_qs, signed_agreement_qs] def get_context_data(self, **kwargs): @@ -67,17 +60,13 @@ def get_context_data(self, **kwargs): edit_permission_codename = "anvil_consortium_manager." + ( AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME ) - context[ - "show_invalidate_button" - ] = self.object.is_valid and self.request.user.has_perm( + context["show_invalidate_button"] = self.object.is_valid and self.request.user.has_perm( edit_permission_codename ) return context -class AgreementMajorVersionInvalidate( - AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, UpdateView -): +class AgreementMajorVersionInvalidate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, UpdateView): """A view to invalidate an AgreementMajorVersion instance. This view sets the is_valid field to False. It also sets the status of all associated @@ -99,8 +88,7 @@ def get_object(self, queryset=None): obj = queryset.get(version=major_version) except (KeyError, self.model.DoesNotExist): raise Http404( - _("No %(verbose_name)s found matching the query") - % {"verbose_name": queryset.model._meta.verbose_name} + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} ) return obj @@ -137,9 +125,7 @@ def get_success_url(self): # Change status for CDSAs to lapsed when their major version is invalidated. -class AgreementVersionDetail( - AnVILConsortiumManagerStaffViewRequired, SingleTableMixin, DetailView -): +class AgreementVersionDetail(AnVILConsortiumManagerStaffViewRequired, SingleTableMixin, DetailView): """Display a "detail" page for an agreement major/minor version (e.g., 1.3).""" model = models.AgreementVersion @@ -156,13 +142,10 @@ def get_object(self, queryset=None): try: major_version = self.kwargs["major_version"] minor_version = self.kwargs["minor_version"] - obj = queryset.get( - major_version__version=major_version, minor_version=minor_version - ) + obj = queryset.get(major_version__version=major_version, minor_version=minor_version) except (KeyError, self.model.DoesNotExist): raise Http404( - _("No %(verbose_name)s found matching the query") - % {"verbose_name": queryset.model._meta.verbose_name} + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} ) return obj @@ -237,16 +220,12 @@ def get_agreement(self, form, formset): settings.ANVIL_DATA_ACCESS_GROUP_PREFIX, form.instance.cc_id, ) - access_group = ManagedGroup( - name=access_group_name, email=access_group_name + "@firecloud.org" - ) + access_group = ManagedGroup(name=access_group_name, email=access_group_name + "@firecloud.org") # Make sure the group doesn't exist already. access_group.full_clean() access_group.save() # Add the cc admins group as a member. - cc_admins_group = ManagedGroup.objects.get( - name=settings.ANVIL_CC_ADMINS_GROUP_NAME - ) + cc_admins_group = ManagedGroup.objects.get(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) self.admin_access_membership = GroupGroupMembership( parent_group=access_group, child_group=cc_admins_group, @@ -290,22 +269,16 @@ def form_valid(self, form): except ValidationError as e: # log the error. logger.error(str(e)) - messages.add_message( - self.request, messages.ERROR, self.ERROR_CREATING_GROUP - ) + messages.add_message(self.request, messages.ERROR, self.ERROR_CREATING_GROUP) return self.render_to_response(self.get_context_data(form=form)) except AnVILAPIError as e: # log the error. logger.error(str(e)) - messages.add_message( - self.request, messages.ERROR, "AnVIL API Error: " + str(e) - ) + messages.add_message(self.request, messages.ERROR, "AnVIL API Error: " + str(e)) return self.render_to_response(self.get_context_data(form=form)) def form_invalid(self, form, formset): - return self.render_to_response( - self.get_context_data(form=form, formset=formset) - ) + return self.render_to_response(self.get_context_data(form=form, formset=formset)) def get_success_url(self): return self.object.get_absolute_url() @@ -357,16 +330,12 @@ def get_agreement_type(self, form, formset): settings.ANVIL_DATA_ACCESS_GROUP_PREFIX, form.instance.cc_id, ) - upload_group = ManagedGroup( - name=upload_group_name, email=upload_group_name + "@firecloud.org" - ) + upload_group = ManagedGroup(name=upload_group_name, email=upload_group_name + "@firecloud.org") # Make sure the group doesn't exist already. upload_group.full_clean() upload_group.save() # Add the cc admins group as a member. - cc_admins_group = ManagedGroup.objects.get( - name=settings.ANVIL_CC_ADMINS_GROUP_NAME - ) + cc_admins_group = ManagedGroup.objects.get(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) self.admin_upload_membership = GroupGroupMembership( parent_group=upload_group, child_group=cc_admins_group, @@ -390,22 +359,17 @@ def get_object(self, queryset=None): obj = queryset.get(signed_agreement__cc_id=self.kwargs.get("cc_id")) except queryset.model.DoesNotExist: raise Http404( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} + "No %(verbose_name)s found matching the query" % {"verbose_name": queryset.model._meta.verbose_name} ) return obj def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context[ - "show_deprecation_message" - ] = not self.object.signed_agreement.version.major_version.is_valid + context["show_deprecation_message"] = not self.object.signed_agreement.version.major_version.is_valid edit_permission_codename = "anvil_consortium_manager." + ( AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME ) - context["show_update_button"] = self.request.user.has_perm( - edit_permission_codename - ) + context["show_update_button"] = self.request.user.has_perm(edit_permission_codename) return context @@ -416,10 +380,7 @@ class MemberAgreementList(AnVILConsortiumManagerStaffViewRequired, SingleTableVi table_class = tables.MemberAgreementTable -class SignedAgreementStatusUpdate( - AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, UpdateView -): - +class SignedAgreementStatusUpdate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, UpdateView): model = models.SignedAgreement form_class = forms.SignedAgreementStatusForm template_name = "cdsa/signedagreement_status_update.html" @@ -430,13 +391,10 @@ def get_object(self, queryset=None): """Look up the agreement by agreement_type_indicator and CDSA cc_id.""" queryset = self.get_queryset() try: - obj = queryset.get( - cc_id=self.kwargs.get("cc_id"), type=self.kwargs.get("agreement_type") - ) + obj = queryset.get(cc_id=self.kwargs.get("cc_id"), type=self.kwargs.get("agreement_type")) except queryset.model.DoesNotExist: raise Http404( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} + "No %(verbose_name)s found matching the query" % {"verbose_name": queryset.model._meta.verbose_name} ) return obj @@ -453,28 +411,21 @@ def get_object(self, queryset=None): obj = queryset.get(signed_agreement__cc_id=self.kwargs.get("cc_id")) except queryset.model.DoesNotExist: raise Http404( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} + "No %(verbose_name)s found matching the query" % {"verbose_name": queryset.model._meta.verbose_name} ) return obj def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context[ - "show_deprecation_message" - ] = not self.object.signed_agreement.version.major_version.is_valid + context["show_deprecation_message"] = not self.object.signed_agreement.version.major_version.is_valid edit_permission_codename = "anvil_consortium_manager." + ( AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME ) - context["show_update_button"] = self.request.user.has_perm( - edit_permission_codename - ) + context["show_update_button"] = self.request.user.has_perm(edit_permission_codename) return context -class DataAffiliateAgreementList( - AnVILConsortiumManagerStaffViewRequired, SingleTableView -): +class DataAffiliateAgreementList(AnVILConsortiumManagerStaffViewRequired, SingleTableView): """Display a list of DataAffiliateAgreement objects.""" model = models.DataAffiliateAgreement @@ -498,9 +449,7 @@ class NonDataAffiliateAgreementCreate( ERROR_CREATING_GROUP = "Error creating access group on AnVIL." -class NonDataAffiliateAgreementDetail( - AnVILConsortiumManagerStaffViewRequired, DetailView -): +class NonDataAffiliateAgreementDetail(AnVILConsortiumManagerStaffViewRequired, DetailView): """View to show details about a `NonDataAffiliateAgreement`.""" model = models.NonDataAffiliateAgreement @@ -512,28 +461,21 @@ def get_object(self, queryset=None): obj = queryset.get(signed_agreement__cc_id=self.kwargs.get("cc_id")) except queryset.model.DoesNotExist: raise Http404( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} + "No %(verbose_name)s found matching the query" % {"verbose_name": queryset.model._meta.verbose_name} ) return obj def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context[ - "show_deprecation_message" - ] = not self.object.signed_agreement.version.major_version.is_valid + context["show_deprecation_message"] = not self.object.signed_agreement.version.major_version.is_valid edit_permission_codename = "anvil_consortium_manager." + ( AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME ) - context["show_update_button"] = self.request.user.has_perm( - edit_permission_codename - ) + context["show_update_button"] = self.request.user.has_perm(edit_permission_codename) return context -class NonDataAffiliateAgreementList( - AnVILConsortiumManagerStaffViewRequired, SingleTableView -): +class NonDataAffiliateAgreementList(AnVILConsortiumManagerStaffViewRequired, SingleTableView): """Display a list of NonDataAffiliateAgreement objects.""" model = models.NonDataAffiliateAgreement @@ -544,19 +486,13 @@ class SignedAgreementAudit(AnVILConsortiumManagerStaffViewRequired, TemplateView """View to show audit results for `SignedAgreements`.""" template_name = "cdsa/signedagreement_audit.html" - ERROR_CDSA_GROUP_DOES_NOT_EXIST = ( - """The CDSA group "{}" does not exist in the app.""" - ) + ERROR_CDSA_GROUP_DOES_NOT_EXIST = """The CDSA group "{}" does not exist in the app.""" def get(self, request, *args, **kwargs): - if not models.ManagedGroup.objects.filter( - name=settings.ANVIL_CDSA_GROUP_NAME - ).exists(): + if not models.ManagedGroup.objects.filter(name=settings.ANVIL_CDSA_GROUP_NAME).exists(): messages.error( self.request, - self.ERROR_CDSA_GROUP_DOES_NOT_EXIST.format( - settings.ANVIL_CDSA_GROUP_NAME - ), + self.ERROR_CDSA_GROUP_DOES_NOT_EXIST.format(settings.ANVIL_CDSA_GROUP_NAME), ) return HttpResponseRedirect(reverse("anvil_consortium_manager:index")) return super().get(request, *args, **kwargs) @@ -572,10 +508,7 @@ def get_context_data(self, **kwargs): return context -class SignedAgreementAuditResolve( - AnVILConsortiumManagerStaffEditRequired, SingleObjectMixin, FormView -): - +class SignedAgreementAuditResolve(AnVILConsortiumManagerStaffEditRequired, SingleObjectMixin, FormView): model = models.SignedAgreement form_class = Form template_name = "cdsa/signedagreement_audit_resolve.html" @@ -589,16 +522,13 @@ def get_object(self, queryset=None): obj = queryset.get(cc_id=self.kwargs.get("cc_id")) except queryset.model.DoesNotExist: raise Http404( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} + "No %(verbose_name)s found matching the query" % {"verbose_name": queryset.model._meta.verbose_name} ) return obj def get_audit_result(self): audit = signed_agreement_audit.SignedAgreementAccessAudit( - signed_agreement_queryset=models.SignedAgreement.objects.filter( - pk=self.object.pk - ) + signed_agreement_queryset=models.SignedAgreement.objects.filter(pk=self.object.pk) ) audit.run_audit() return audit.get_all_results()[0] @@ -664,9 +594,7 @@ class CDSAWorkspaceAudit(AnVILConsortiumManagerStaffViewRequired, TemplateView): """View to show audit results for `CDSAWorkspaces`.""" template_name = "cdsa/cdsaworkspace_audit.html" - ERROR_CDSA_GROUP_DOES_NOT_EXIST = ( - """The CDSA group "{}" does not exist in the app.""" - ) + ERROR_CDSA_GROUP_DOES_NOT_EXIST = """The CDSA group "{}" does not exist in the app.""" def get(self, request, *args, **kwargs): try: @@ -674,9 +602,7 @@ def get(self, request, *args, **kwargs): except models.ManagedGroup.DoesNotExist: messages.error( self.request, - self.ERROR_CDSA_GROUP_DOES_NOT_EXIST.format( - settings.ANVIL_CDSA_GROUP_NAME - ), + self.ERROR_CDSA_GROUP_DOES_NOT_EXIST.format(settings.ANVIL_CDSA_GROUP_NAME), ) return HttpResponseRedirect(reverse("anvil_consortium_manager:index")) return super().get(request, *args, **kwargs) @@ -692,10 +618,7 @@ def get_context_data(self, **kwargs): return context -class CDSAWorkspaceAuditResolve( - AnVILConsortiumManagerStaffEditRequired, SingleObjectMixin, FormView -): - +class CDSAWorkspaceAuditResolve(AnVILConsortiumManagerStaffEditRequired, SingleObjectMixin, FormView): model = models.CDSAWorkspace form_class = Form template_name = "cdsa/cdsaworkspace_audit_resolve.html" @@ -707,23 +630,18 @@ def get_object(self, queryset=None): queryset = self.get_queryset() try: obj = queryset.get( - workspace__billing_project__name=self.kwargs.get( - "billing_project_slug" - ), + workspace__billing_project__name=self.kwargs.get("billing_project_slug"), workspace__name=self.kwargs.get("workspace_slug"), ) except queryset.model.DoesNotExist: raise Http404( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} + "No %(verbose_name)s found matching the query" % {"verbose_name": queryset.model._meta.verbose_name} ) return obj def get_audit_result(self): audit = workspace_audit.WorkspaceAccessAudit( - cdsa_workspace_queryset=models.CDSAWorkspace.objects.filter( - pk=self.object.pk - ) + cdsa_workspace_queryset=models.CDSAWorkspace.objects.filter(pk=self.object.pk) ) audit.run_audit() return audit.get_all_results()[0] diff --git a/primed/collaborative_analysis/adapters.py b/primed/collaborative_analysis/adapters.py index 0e439385..f070d926 100644 --- a/primed/collaborative_analysis/adapters.py +++ b/primed/collaborative_analysis/adapters.py @@ -15,6 +15,4 @@ class CollaborativeAnalysisWorkspaceAdapter(BaseWorkspaceAdapter): workspace_form_class = WorkspaceForm workspace_data_model = models.CollaborativeAnalysisWorkspace workspace_data_form_class = forms.CollaborativeAnalysisWorkspaceForm - workspace_detail_template_name = ( - "collaborative_analysis/collaborativeanalysisworkspace_detail.html" - ) + workspace_detail_template_name = "collaborative_analysis/collaborativeanalysisworkspace_detail.html" diff --git a/primed/collaborative_analysis/audit.py b/primed/collaborative_analysis/audit.py index e4764e6f..2633ce59 100644 --- a/primed/collaborative_analysis/audit.py +++ b/primed/collaborative_analysis/audit.py @@ -114,9 +114,7 @@ class CollaborativeAnalysisWorkspaceAccessAudit(PRIMEDAudit): DCC_ACCESS = "CC groups are allowed access." # Allowed reasons for no access. - NOT_IN_SOURCE_AUTH_DOMAINS = ( - "Account is not in all source auth domains for this workspace." - ) + NOT_IN_SOURCE_AUTH_DOMAINS = "Account is not in all source auth domains for this workspace." NOT_IN_ANALYST_GROUP = "Account is not in the analyst group for this workspace." INACTIVE_ACCOUNT = "Account is inactive." NON_DCC_GROUP = "Non-CC groups are not allowed access." @@ -149,9 +147,7 @@ def _audit_workspace(self, workspace): # Get a list of accounts in the auth domain. auth_domain_membership = [ x.account - for x in GroupAccountMembership.objects.filter( - group=workspace.workspace.authorization_domains.first() - ) + for x in GroupAccountMembership.objects.filter(group=workspace.workspace.authorization_domains.first()) ] for membership in analyst_memberships: self._audit_workspace_and_account(workspace, membership.account) @@ -199,15 +195,9 @@ def _audit_workspace_and_group(self, collaborative_analysis_workspace, group): access_allowed = group.name in [ "PRIMED_CC_WRITERS", ] - in_auth_domain = ( - collaborative_analysis_workspace.workspace.authorization_domains.first() - ) - auth_domain = ( - collaborative_analysis_workspace.workspace.authorization_domains.first() - ) - in_auth_domain = GroupGroupMembership.objects.filter( - parent_group=auth_domain, child_group=group - ).exists() + in_auth_domain = collaborative_analysis_workspace.workspace.authorization_domains.first() + auth_domain = collaborative_analysis_workspace.workspace.authorization_domains.first() + in_auth_domain = GroupGroupMembership.objects.filter(parent_group=auth_domain, child_group=group).exists() if access_allowed and in_auth_domain: self.verified.append( VerifiedAccess( @@ -254,22 +244,15 @@ def _audit_workspace_and_account(self, collaborative_analysis_workspace, account # Get all groups for the account. account_groups = account.get_all_groups() # Check whether the account is in the analyst group. - in_analyst_group = ( - collaborative_analysis_workspace.analyst_group in account_groups - ) + in_analyst_group = collaborative_analysis_workspace.analyst_group in account_groups # Check whether the account is in the auth domain of the collab workspace. - in_auth_domain = ( - collaborative_analysis_workspace.workspace.authorization_domains.first() - in account_groups - ) + in_auth_domain = collaborative_analysis_workspace.workspace.authorization_domains.first() in account_groups if in_analyst_group: # Check whether access is allowed. Start by assuming yes, and then # set to false if the account should not have access. access_allowed = True # Loop over all source workspaces. - for ( - source_workspace - ) in collaborative_analysis_workspace.source_workspaces.all(): + for source_workspace in collaborative_analysis_workspace.source_workspaces.all(): # Loop over all auth domains for that source workspace. for source_auth_domain in source_workspace.authorization_domains.all(): # If the user is not in the auth domain, they are not allowed to have access to the workspace. diff --git a/primed/collaborative_analysis/management/commands/run_collaborative_analysis_audit.py b/primed/collaborative_analysis/management/commands/run_collaborative_analysis_audit.py index e92790c0..e8af4904 100644 --- a/primed/collaborative_analysis/management/commands/run_collaborative_analysis_audit.py +++ b/primed/collaborative_analysis/management/commands/run_collaborative_analysis_audit.py @@ -25,9 +25,7 @@ def handle(self, *args, **options): # Report errors and needs access. audit_ok = data_access_audit.ok() # Construct the url for handling errors. - url = ( - "https://" + Site.objects.get_current().domain + reverse("dbgap:audit:all") - ) + url = "https://" + Site.objects.get_current().domain + reverse("dbgap:audit:all") if audit_ok: self.stdout.write(self.style.SUCCESS("ok!")) else: @@ -35,15 +33,11 @@ def handle(self, *args, **options): # Print results self.stdout.write("* Verified: {}".format(len(data_access_audit.verified))) - self.stdout.write( - "* Needs action: {}".format(len(data_access_audit.needs_action)) - ) + self.stdout.write("* Needs action: {}".format(len(data_access_audit.needs_action))) self.stdout.write("* Errors: {}".format(len(data_access_audit.errors))) if not audit_ok: - self.stdout.write( - self.style.ERROR(f"Please visit {url} to resolve these issues.") - ) + self.stdout.write(self.style.ERROR(f"Please visit {url} to resolve these issues.")) # Send email if requested and there are problems. email = options["email"] diff --git a/primed/collaborative_analysis/models.py b/primed/collaborative_analysis/models.py index edcac496..475d488a 100644 --- a/primed/collaborative_analysis/models.py +++ b/primed/collaborative_analysis/models.py @@ -6,7 +6,6 @@ # Note that "RequesterModel" is not included, because we have the "custodian" tracked instead. class CollaborativeAnalysisWorkspace(TimeStampedModel, BaseWorkspaceData): - purpose = models.TextField( help_text="The intended purpose for this workspace.", ) diff --git a/primed/collaborative_analysis/tests/factories.py b/primed/collaborative_analysis/tests/factories.py index 30175a73..243ac017 100644 --- a/primed/collaborative_analysis/tests/factories.py +++ b/primed/collaborative_analysis/tests/factories.py @@ -19,9 +19,7 @@ class Meta: custodian = SubFactory(UserFactory) analyst_group = SubFactory( ManagedGroupFactory, - name=LazyAttribute( - lambda o: "analysts_{}".format(o.factory_parent.workspace.name) - ), + name=LazyAttribute(lambda o: "analysts_{}".format(o.factory_parent.workspace.name)), ) @post_generation @@ -32,9 +30,7 @@ def authorization_domains(self, create, extracted, **kwargs): return # Create an authorization domain. - auth_domain = ManagedGroupFactory.create( - name="auth_{}".format(self.workspace.name) - ) + auth_domain = ManagedGroupFactory.create(name="auth_{}".format(self.workspace.name)) print(auth_domain) self.workspace.authorization_domains.add(auth_domain) diff --git a/primed/collaborative_analysis/tests/test_audit.py b/primed/collaborative_analysis/tests/test_audit.py index b29f1756..ffb50388 100644 --- a/primed/collaborative_analysis/tests/test_audit.py +++ b/primed/collaborative_analysis/tests/test_audit.py @@ -23,9 +23,7 @@ def setUp(self): def test_account_verified_access(self): workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() account = AccountFactory.create() - instance = audit.VerifiedAccess( - collaborative_analysis_workspace=workspace, member=account, note="test" - ) + instance = audit.VerifiedAccess(collaborative_analysis_workspace=workspace, member=account, note="test") expected_url = reverse( "collaborative_analysis:audit:resolve", args=[ @@ -39,9 +37,7 @@ def test_account_verified_access(self): def test_account_verified_no_access(self): workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() account = AccountFactory.create() - instance = audit.VerifiedNoAccess( - collaborative_analysis_workspace=workspace, member=account, note="test" - ) + instance = audit.VerifiedNoAccess(collaborative_analysis_workspace=workspace, member=account, note="test") expected_url = reverse( "collaborative_analysis:audit:resolve", args=[ @@ -55,9 +51,7 @@ def test_account_verified_no_access(self): def test_account_grant_access(self): workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() account = AccountFactory.create() - instance = audit.GrantAccess( - collaborative_analysis_workspace=workspace, member=account, note="test" - ) + instance = audit.GrantAccess(collaborative_analysis_workspace=workspace, member=account, note="test") expected_url = reverse( "collaborative_analysis:audit:resolve", args=[ @@ -71,9 +65,7 @@ def test_account_grant_access(self): def test_account_remove_access(self): workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() account = AccountFactory.create() - instance = audit.RemoveAccess( - collaborative_analysis_workspace=workspace, member=account, note="test" - ) + instance = audit.RemoveAccess(collaborative_analysis_workspace=workspace, member=account, note="test") expected_url = reverse( "collaborative_analysis:audit:resolve", args=[ @@ -87,9 +79,7 @@ def test_account_remove_access(self): def test_group_verified_access(self): workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() group = ManagedGroupFactory.create() - instance = audit.VerifiedAccess( - collaborative_analysis_workspace=workspace, member=group, note="test" - ) + instance = audit.VerifiedAccess(collaborative_analysis_workspace=workspace, member=group, note="test") expected_url = reverse( "collaborative_analysis:audit:resolve", args=[ @@ -103,9 +93,7 @@ def test_group_verified_access(self): def test_group_verified_no_access(self): workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() group = ManagedGroupFactory.create() - instance = audit.VerifiedNoAccess( - collaborative_analysis_workspace=workspace, member=group, note="test" - ) + instance = audit.VerifiedNoAccess(collaborative_analysis_workspace=workspace, member=group, note="test") expected_url = reverse( "collaborative_analysis:audit:resolve", args=[ @@ -119,9 +107,7 @@ def test_group_verified_no_access(self): def test_group_grant_access(self): workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() group = ManagedGroupFactory.create() - instance = audit.GrantAccess( - collaborative_analysis_workspace=workspace, member=group, note="test" - ) + instance = audit.GrantAccess(collaborative_analysis_workspace=workspace, member=group, note="test") expected_url = reverse( "collaborative_analysis:audit:resolve", args=[ @@ -135,9 +121,7 @@ def test_group_grant_access(self): def test_group_remove_access(self): workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() group = ManagedGroupFactory.create() - instance = audit.RemoveAccess( - collaborative_analysis_workspace=workspace, member=group, note="test" - ) + instance = audit.RemoveAccess(collaborative_analysis_workspace=workspace, member=group, note="test") expected_url = reverse( "collaborative_analysis:audit:resolve", args=[ @@ -185,18 +169,14 @@ def test_analyst_in_collab_auth_domain_in_source_auth_domain(self): source_workspace = dbGaPWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_workspace.workspace.authorization_domains.first(), account=account, ) # CollaborativeAnalysisWorkspace auth domain membership. - GroupAccountMembershipFactory.create( - group=workspace.workspace.authorization_domains.first(), account=account - ) + GroupAccountMembershipFactory.create(group=workspace.workspace.authorization_domains.first(), account=account) # Set up audit collab_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit() # Run audit @@ -219,17 +199,13 @@ def test_analyst_in_collab_auth_domain_not_in_source_auth_domain(self): source_workspace = dbGaPWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. # GroupAccountMembershipFactory.create( # group=source_workspace.workspace.authorization_domains.first(), account=account # ) # CollaborativeAnalysisWorkspace auth domain membership. - GroupAccountMembershipFactory.create( - group=workspace.workspace.authorization_domains.first(), account=account - ) + GroupAccountMembershipFactory.create(group=workspace.workspace.authorization_domains.first(), account=account) # Set up audit collab_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit() # Run audit @@ -252,9 +228,7 @@ def test_analyst_not_in_collab_auth_domain_in_source_auth_domain(self): source_workspace = dbGaPWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_workspace.workspace.authorization_domains.first(), @@ -286,9 +260,7 @@ def test_analyst_not_in_collab_auth_domain_not_in_source_auth_domain(self): source_workspace = dbGaPWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. # GroupAccountMembershipFactory.create( # group=source_workspace.workspace.authorization_domains.first(), account=account @@ -318,13 +290,9 @@ def test_analyst_in_collab_auth_domain_two_source_auth_domains_in_both(self): # Set up source workspaces. source_workspace = dbGaPWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace.workspace) - source_auth_domain_2 = WorkspaceAuthorizationDomainFactory.create( - workspace=source_workspace.workspace - ) + source_auth_domain_2 = WorkspaceAuthorizationDomainFactory.create(workspace=source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_workspace.workspace.authorization_domains.all()[0], @@ -335,9 +303,7 @@ def test_analyst_in_collab_auth_domain_two_source_auth_domains_in_both(self): account=account, ) # CollaborativeAnalysisWorkspace auth domain membership. - GroupAccountMembershipFactory.create( - group=workspace.workspace.authorization_domains.first(), account=account - ) + GroupAccountMembershipFactory.create(group=workspace.workspace.authorization_domains.first(), account=account) # Set up audit collab_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit() # Run audit @@ -363,9 +329,7 @@ def test_analyst_in_collab_auth_domain_two_source_auth_domains_in_one(self): # add an extra auth doamin WorkspaceAuthorizationDomainFactory.create(workspace=source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_workspace.workspace.authorization_domains.first(), @@ -376,9 +340,7 @@ def test_analyst_in_collab_auth_domain_two_source_auth_domains_in_one(self): # account=account, # ) # CollaborativeAnalysisWorkspace auth domain membership. - GroupAccountMembershipFactory.create( - group=workspace.workspace.authorization_domains.first(), account=account - ) + GroupAccountMembershipFactory.create(group=workspace.workspace.authorization_domains.first(), account=account) # Set up audit collab_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit() # Run audit @@ -404,9 +366,7 @@ def test_analyst_in_collab_auth_domain_two_source_auth_domains_in_neither(self): # add an extra auth doamin WorkspaceAuthorizationDomainFactory.create(workspace=source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. # GroupAccountMembershipFactory.create( # group=source_workspace.workspace.authorization_domains.first(), @@ -417,9 +377,7 @@ def test_analyst_in_collab_auth_domain_two_source_auth_domains_in_neither(self): # account=account, # ) # CollaborativeAnalysisWorkspace auth domain membership. - GroupAccountMembershipFactory.create( - group=workspace.workspace.authorization_domains.first(), account=account - ) + GroupAccountMembershipFactory.create(group=workspace.workspace.authorization_domains.first(), account=account) # Set up audit collab_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit() # Run audit @@ -442,13 +400,9 @@ def test_analyst_not_in_collab_auth_domain_two_source_auth_domains_in_both(self) source_workspace = dbGaPWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace.workspace) source_auth_domain_1 = source_workspace.workspace.authorization_domains.first() - source_auth_domain_2 = WorkspaceAuthorizationDomainFactory.create( - workspace=source_workspace.workspace - ) + source_auth_domain_2 = WorkspaceAuthorizationDomainFactory.create(workspace=source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_auth_domain_1, @@ -483,13 +437,9 @@ def test_analyst_not_in_collab_auth_domain_two_source_auth_domains_in_one(self): # Set up source workspaces. source_workspace = dbGaPWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace.workspace) - source_auth_domain_2 = WorkspaceAuthorizationDomainFactory.create( - workspace=source_workspace.workspace - ) + source_auth_domain_2 = WorkspaceAuthorizationDomainFactory.create(workspace=source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. # GroupAccountMembershipFactory.create( # group=source_auth_domain_1, @@ -526,9 +476,7 @@ def test_analyst_not_in_collab_auth_domain_two_source_auth_domains_in_neither(se workspace.source_workspaces.add(source_workspace.workspace) WorkspaceAuthorizationDomainFactory.create(workspace=source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. # GroupAccountMembershipFactory.create( # group=source_auth_domain_1, @@ -564,14 +512,10 @@ def test_in_collab_auth_domain_no_source_workspaces(self): source_workspace = WorkspaceFactory.create() workspace.source_workspaces.add(source_workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. # CollaborativeAnalysisWorkspace auth domain membership. - GroupAccountMembershipFactory.create( - group=workspace.workspace.authorization_domains.first(), account=account - ) + GroupAccountMembershipFactory.create(group=workspace.workspace.authorization_domains.first(), account=account) # Set up audit collab_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit() # Run audit @@ -594,9 +538,7 @@ def test_not_in_collab_auth_domain_no_source_workspaces(self): source_workspace = WorkspaceFactory.create() workspace.source_workspaces.add(source_workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. # CollaborativeAnalysisWorkspace auth domain membership. # GroupAccountMembershipFactory.create( @@ -626,9 +568,7 @@ def test_in_collab_auth_domain_two_source_workspaces_in_both_auth_domains(self): source_workspace_2 = CDSAWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace_2.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_workspace_1.workspace.authorization_domains.first(), @@ -639,9 +579,7 @@ def test_in_collab_auth_domain_two_source_workspaces_in_both_auth_domains(self): account=account, ) # CollaborativeAnalysisWorkspace auth domain membership. - GroupAccountMembershipFactory.create( - group=workspace.workspace.authorization_domains.first(), account=account - ) + GroupAccountMembershipFactory.create(group=workspace.workspace.authorization_domains.first(), account=account) # Set up audit collab_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit() # Run audit @@ -666,9 +604,7 @@ def test_in_collab_auth_domain_two_source_workspaces_in_one_auth_domains(self): source_workspace_2 = CDSAWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace_2.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_workspace_1.workspace.authorization_domains.first(), @@ -679,9 +615,7 @@ def test_in_collab_auth_domain_two_source_workspaces_in_one_auth_domains(self): # account=account, # ) # CollaborativeAnalysisWorkspace auth domain membership. - GroupAccountMembershipFactory.create( - group=workspace.workspace.authorization_domains.first(), account=account - ) + GroupAccountMembershipFactory.create(group=workspace.workspace.authorization_domains.first(), account=account) # Set up audit collab_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit() # Run audit @@ -706,9 +640,7 @@ def test_in_collab_auth_domain_two_source_workspaces_in_neither_auth_domains(sel source_workspace_2 = CDSAWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace_2.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. # GroupAccountMembershipFactory.create( # group=source_workspace_1.workspace.authorization_domains.first(), @@ -719,9 +651,7 @@ def test_in_collab_auth_domain_two_source_workspaces_in_neither_auth_domains(sel # account=account, # ) # CollaborativeAnalysisWorkspace auth domain membership. - GroupAccountMembershipFactory.create( - group=workspace.workspace.authorization_domains.first(), account=account - ) + GroupAccountMembershipFactory.create(group=workspace.workspace.authorization_domains.first(), account=account) # Set up audit collab_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit() # Run audit @@ -746,9 +676,7 @@ def test_not_in_collab_auth_domain_two_source_workspaces_in_both_auth_domains(se source_workspace_2 = CDSAWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace_2.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_workspace_1.workspace.authorization_domains.first(), @@ -786,9 +714,7 @@ def test_not_in_collab_auth_domain_two_source_workspaces_in_one_auth_domains(sel source_workspace_2 = CDSAWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace_2.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_workspace_1.workspace.authorization_domains.first(), @@ -828,9 +754,7 @@ def test_not_in_collab_auth_domain_two_source_workspaces_in_neither_auth_domains source_workspace_2 = CDSAWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace_2.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=account) # Source workspace auth domains membership. # GroupAccountMembershipFactory.create( # group=source_workspace_1.workspace.authorization_domains.first(), @@ -861,17 +785,11 @@ def test_two_analysts(self): # Create an analyst that needs access. workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() analyst_1 = AccountFactory.create() - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=analyst_1 - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=analyst_1) # Create an analyst that has access. analyst_2 = AccountFactory.create() - GroupAccountMembershipFactory.create( - group=workspace.analyst_group, account=analyst_2 - ) - GroupAccountMembershipFactory.create( - group=workspace.workspace.authorization_domains.first(), account=analyst_2 - ) + GroupAccountMembershipFactory.create(group=workspace.analyst_group, account=analyst_2) + GroupAccountMembershipFactory.create(group=workspace.workspace.authorization_domains.first(), account=analyst_2) collab_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit() collab_audit._audit_workspace(workspace) self.assertEqual(len(collab_audit.verified), 1) @@ -893,9 +811,7 @@ def test_not_in_analyst_group(self): workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() # Create an analyst that has access but is not in the analyst group. analyst = AccountFactory.create() - GroupAccountMembershipFactory.create( - group=workspace.workspace.authorization_domains.first(), account=analyst - ) + GroupAccountMembershipFactory.create(group=workspace.workspace.authorization_domains.first(), account=analyst) collab_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit() collab_audit._audit_workspace(workspace) self.assertEqual(len(collab_audit.verified), 0) @@ -1015,15 +931,11 @@ def test_two_workspaces(self): # Create a workspace with an analyst that needs access. workspace_1 = factories.CollaborativeAnalysisWorkspaceFactory.create() analyst_1 = AccountFactory.create() - GroupAccountMembershipFactory.create( - group=workspace_1.analyst_group, account=analyst_1 - ) + GroupAccountMembershipFactory.create(group=workspace_1.analyst_group, account=analyst_1) # Create a workspace with an analyst that has access. workspace_2 = factories.CollaborativeAnalysisWorkspaceFactory.create() analyst_2 = AccountFactory.create() - GroupAccountMembershipFactory.create( - group=workspace_2.analyst_group, account=analyst_2 - ) + GroupAccountMembershipFactory.create(group=workspace_2.analyst_group, account=analyst_2) GroupAccountMembershipFactory.create( group=workspace_2.workspace.authorization_domains.first(), account=analyst_2 ) @@ -1048,21 +960,15 @@ def test_queryset(self): # Create a workspace with an analyst that needs access. workspace_1 = factories.CollaborativeAnalysisWorkspaceFactory.create() analyst_1 = AccountFactory.create() - GroupAccountMembershipFactory.create( - group=workspace_1.analyst_group, account=analyst_1 - ) + GroupAccountMembershipFactory.create(group=workspace_1.analyst_group, account=analyst_1) # Create a workspace with an analyst that has access. workspace_2 = factories.CollaborativeAnalysisWorkspaceFactory.create() analyst_2 = AccountFactory.create() - GroupAccountMembershipFactory.create( - group=workspace_2.analyst_group, account=analyst_2 - ) + GroupAccountMembershipFactory.create(group=workspace_2.analyst_group, account=analyst_2) GroupAccountMembershipFactory.create( group=workspace_2.workspace.authorization_domains.first(), account=analyst_2 ) - collab_audit_1 = audit.CollaborativeAnalysisWorkspaceAccessAudit( - queryset=[workspace_1] - ) + collab_audit_1 = audit.CollaborativeAnalysisWorkspaceAccessAudit(queryset=[workspace_1]) collab_audit_1.run_audit() self.assertEqual(len(collab_audit_1.verified), 0) self.assertEqual(len(collab_audit_1.needs_action), 1) @@ -1072,9 +978,7 @@ def test_queryset(self): self.assertEqual(record.collaborative_analysis_workspace, workspace_1) self.assertEqual(record.member, analyst_1) self.assertEqual(record.note, collab_audit_1.IN_SOURCE_AUTH_DOMAINS) - collab_audit_2 = audit.CollaborativeAnalysisWorkspaceAccessAudit( - queryset=[workspace_2] - ) + collab_audit_2 = audit.CollaborativeAnalysisWorkspaceAccessAudit(queryset=[workspace_2]) collab_audit_2.run_audit() self.assertEqual(len(collab_audit_2.verified), 1) self.assertEqual(len(collab_audit_2.needs_action), 0) diff --git a/primed/collaborative_analysis/tests/test_commands.py b/primed/collaborative_analysis/tests/test_commands.py index 2fa3be81..36e1e04c 100644 --- a/primed/collaborative_analysis/tests/test_commands.py +++ b/primed/collaborative_analysis/tests/test_commands.py @@ -24,9 +24,7 @@ def test_command_output_no_records(self): """Test command output.""" out = StringIO() call_command("run_collaborative_analysis_audit", "--no-color", stdout=out) - self.assertIn( - "Running Collaborative analysis access audit... ok!", out.getvalue() - ) + self.assertIn("Running Collaborative analysis access audit... ok!", out.getvalue()) self.assertIn("* Verified: 0", out.getvalue()) self.assertIn("* Needs action: 0", out.getvalue()) self.assertIn("* Errors: 0", out.getvalue()) @@ -42,9 +40,7 @@ def test_command_run_audit_one_instance_verified(self): GroupAccountMembershipFactory.create(group=workspace.analyst_group) out = StringIO() call_command("run_collaborative_analysis_audit", "--no-color", stdout=out) - self.assertIn( - "Running Collaborative analysis access audit... ok!", out.getvalue() - ) + self.assertIn("Running Collaborative analysis access audit... ok!", out.getvalue()) self.assertIn("* Verified: 1", out.getvalue()) self.assertIn("* Needs action: 0", out.getvalue()) self.assertIn("* Errors: 0", out.getvalue()) @@ -76,9 +72,7 @@ def test_command_run_audit_one_instance_error(self): workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace.workspace) # One group with unexpected access. - GroupGroupMembershipFactory.create( - parent_group=workspace.workspace.authorization_domains.first() - ) + GroupGroupMembershipFactory.create(parent_group=workspace.workspace.authorization_domains.first()) out = StringIO() call_command("run_collaborative_analysis_audit", "--no-color", stdout=out) self.assertIn( @@ -105,9 +99,7 @@ def test_command_run_audit_one_instance_verified_email(self): email="test@example.com", stdout=out, ) - self.assertIn( - "Running Collaborative analysis access audit... ok!", out.getvalue() - ) + self.assertIn("Running Collaborative analysis access audit... ok!", out.getvalue()) # Zero messages have been sent by default. self.assertEqual(len(mail.outbox), 0) @@ -137,9 +129,7 @@ def test_command_run_audit_one_instance_needs_action_email(self): self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] self.assertEqual(email.to, ["test@example.com"]) - self.assertEqual( - email.subject, "Collaborative analysis access audit - problems found" - ) + self.assertEqual(email.subject, "Collaborative analysis access audit - problems found") def test_command_run_audit_one_instance_error_email(self): """Test command output with one error instance.""" @@ -148,9 +138,7 @@ def test_command_run_audit_one_instance_error_email(self): workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() workspace.source_workspaces.add(source_workspace.workspace) # One group with unexpected access. - GroupGroupMembershipFactory.create( - parent_group=workspace.workspace.authorization_domains.first() - ) + GroupGroupMembershipFactory.create(parent_group=workspace.workspace.authorization_domains.first()) out = StringIO() call_command( "run_collaborative_analysis_audit", @@ -169,9 +157,7 @@ def test_command_run_audit_one_instance_error_email(self): self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] self.assertEqual(email.to, ["test@example.com"]) - self.assertEqual( - email.subject, "Collaborative analysis access audit - problems found" - ) + self.assertEqual(email.subject, "Collaborative analysis access audit - problems found") def test_different_domain(self): """Test command output when a different domain is specified.""" diff --git a/primed/collaborative_analysis/tests/test_tables.py b/primed/collaborative_analysis/tests/test_tables.py index f8482f99..4399fc59 100644 --- a/primed/collaborative_analysis/tests/test_tables.py +++ b/primed/collaborative_analysis/tests/test_tables.py @@ -38,9 +38,7 @@ def test_number_source_workspaces_one(self): source_workspace = WorkspaceFactory.create() obj = self.model_factory.create() obj.source_workspaces.add(source_workspace) - table = self.table_class( - self.model.objects.filter(workspace_type="collab_analysis") - ) + table = self.table_class(self.model.objects.filter(workspace_type="collab_analysis")) self.assertEqual(table.rows[0].get_cell("number_source_workspaces"), 1) def test_number_source_workspaces_two(self): @@ -50,9 +48,7 @@ def test_number_source_workspaces_two(self): obj = self.model_factory.create() obj.source_workspaces.add(source_workspace_1) obj.source_workspaces.add(source_workspace_2) - table = self.table_class( - self.model.objects.filter(workspace_type="collab_analysis") - ) + table = self.table_class(self.model.objects.filter(workspace_type="collab_analysis")) self.assertEqual(table.rows[0].get_cell("number_source_workspaces"), 2) @@ -86,9 +82,7 @@ def test_number_source_workspaces_one(self): source_workspace = WorkspaceFactory.create() obj = self.model_factory.create() obj.source_workspaces.add(source_workspace) - table = self.table_class( - self.model.objects.filter(workspace_type="collab_analysis") - ) + table = self.table_class(self.model.objects.filter(workspace_type="collab_analysis")) self.assertEqual(table.rows[0].get_cell("number_source_workspaces"), 1) def test_number_source_workspaces_two(self): @@ -98,7 +92,5 @@ def test_number_source_workspaces_two(self): obj = self.model_factory.create() obj.source_workspaces.add(source_workspace_1) obj.source_workspaces.add(source_workspace_2) - table = self.table_class( - self.model.objects.filter(workspace_type="collab_analysis") - ) + table = self.table_class(self.model.objects.filter(workspace_type="collab_analysis")) self.assertEqual(table.rows[0].get_cell("number_source_workspaces"), 2) diff --git a/primed/collaborative_analysis/tests/test_views.py b/primed/collaborative_analysis/tests/test_views.py index 361f8fc4..36327a31 100644 --- a/primed/collaborative_analysis/tests/test_views.py +++ b/primed/collaborative_analysis/tests/test_views.py @@ -58,9 +58,7 @@ def test_links_for_staff_view(self): """Returns successful response code.""" user = User.objects.create_user(username="test", password="test") user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url()) @@ -69,11 +67,7 @@ def test_links_for_staff_view(self): def test_links_for_view(self): """Returns successful response code.""" user = User.objects.create_user(username="test", password="test") - user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) - ) + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) self.client.force_login(user) response = self.client.get(self.get_url()) self.assertNotContains(response, reverse("collaborative_analysis:audit:all")) @@ -86,9 +80,7 @@ def setUp(self): """Set up test class.""" self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) def test_status_code_with_user_permission(self): @@ -114,29 +106,21 @@ def test_links_to_source_workspace(self): response = self.client.get(obj.workspace.get_absolute_url()) self.assertIn(dbgap_workspace.get_absolute_url(), response.content.decode()) self.assertIn(cdsa_workspace.get_absolute_url(), response.content.decode()) - self.assertIn( - open_access_workspace.get_absolute_url(), response.content.decode() - ) + self.assertIn(open_access_workspace.get_absolute_url(), response.content.decode()) def test_link_to_custodian(self): """Links to the custodian's user profile appear on the detail page.""" custodian = UserFactory.create() - obj = factories.CollaborativeAnalysisWorkspaceFactory.create( - custodian=custodian - ) + obj = factories.CollaborativeAnalysisWorkspaceFactory.create(custodian=custodian) self.client.force_login(self.user) response = self.client.get(obj.workspace.get_absolute_url()) self.assertIn(custodian.get_absolute_url(), response.content.decode()) def test_link_to_analyst_group_staff_view(self): """Links to the analyst group's detail page appear on the detail page for staff_viewers.""" - user = User.objects.create_user( - username="test-staff-view", password="test-staff-view" - ) + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) obj = factories.CollaborativeAnalysisWorkspaceFactory.create() self.client.force_login(user) @@ -149,20 +133,14 @@ def test_link_to_analyst_group_view(self): obj = factories.CollaborativeAnalysisWorkspaceFactory.create() self.client.force_login(self.user) response = self.client.get(obj.workspace.get_absolute_url()) - self.assertNotIn( - obj.analyst_group.get_absolute_url(), response.content.decode() - ) + self.assertNotIn(obj.analyst_group.get_absolute_url(), response.content.decode()) self.assertNotIn(obj.analyst_group.name, response.content.decode()) def test_link_to_audit_staff_view(self): """Links to the audit view page do appear on the detail page for staff viewers.""" - user = User.objects.create_user( - username="test-staff-view", password="test-staff-view" - ) + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) obj = factories.CollaborativeAnalysisWorkspaceFactory.create() self.client.force_login(user) @@ -199,14 +177,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.workspace_type = "collab_analysis" self.custodian = UserFactory.create() @@ -270,14 +244,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.custodian = UserFactory.create() self.source_workspace = dbGaPWorkspaceFactory.create().workspace @@ -290,25 +260,15 @@ def get_url(self, *args): def get_api_url(self, billing_project_name, workspace_name): """Return the Terra API url for a given billing project and workspace.""" - return ( - self.api_client.rawls_entry_point - + "/api/workspaces/" - + billing_project_name - + "/" - + workspace_name - ) + return self.api_client.rawls_entry_point + "/api/workspaces/" + billing_project_name + "/" + workspace_name - def get_api_json_response( - self, billing_project, workspace, authorization_domains=[], access="OWNER" - ): + def get_api_json_response(self, billing_project, workspace, authorization_domains=[], access="OWNER"): """Return a pared down version of the json response from the AnVIL API with only fields we need.""" json_data = { "accessLevel": access, "owners": [], "workspace": { - "authorizationDomain": [ - {"membersGroupName": x} for x in authorization_domains - ], + "authorizationDomain": [{"membersGroupName": x} for x in authorization_domains], "name": workspace, "namespace": billing_project, "isLocked": False, @@ -326,9 +286,7 @@ def test_creates_workspace(self): responses.GET, workspace_list_url, match=[ - responses.matchers.query_param_matcher( - {"fields": "workspace.namespace,workspace.name,accessLevel"} - ) + responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"}) ], status=200, json=[self.get_api_json_response(billing_project.name, workspace_name)], @@ -398,13 +356,9 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) - ) - self.collaborative_analysis_workspace = ( - factories.CollaborativeAnalysisWorkspaceFactory.create() + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) + self.collaborative_analysis_workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() def get_url(self, *args): """Get the url for the view being tested.""" @@ -444,9 +398,7 @@ def test_status_code_with_user_permission_view(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get( self.get_url( self.collaborative_analysis_workspace.workspace.billing_project.name, @@ -463,9 +415,7 @@ def test_access_without_user_permission(self): def test_invalid_billing_project_name(self): """Raises a 404 error with an invalid object dbgap_application_pk.""" - request = self.factory.get( - self.get_url("foo", self.collaborative_analysis_workspace.workspace.name) - ) + request = self.factory.get(self.get_url("foo", self.collaborative_analysis_workspace.workspace.name)) request.user = self.user with self.assertRaises(Http404): self.get_view()( @@ -476,9 +426,7 @@ def test_invalid_billing_project_name(self): def test_invalid_workspace_name(self): """Raises a 404 error with an invalid object dbgap_application_pk.""" - request = self.factory.get( - self.get_url(self.collaborative_analysis_workspace.workspace.name, "foo") - ) + request = self.factory.get(self.get_url(self.collaborative_analysis_workspace.workspace.name, "foo")) request.user = self.user with self.assertRaises(Http404): self.get_view()( @@ -512,13 +460,9 @@ def test_context_verified_table_access(self): account = AccountFactory.create() # Set up source workspaces. source_workspace = dbGaPWorkspaceFactory.create() - self.collaborative_analysis_workspace.source_workspaces.add( - source_workspace.workspace - ) + self.collaborative_analysis_workspace.source_workspaces.add(source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=self.collaborative_analysis_workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=self.collaborative_analysis_workspace.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_workspace.workspace.authorization_domains.first(), @@ -561,13 +505,9 @@ def test_context_verified_table_no_access(self): account = AccountFactory.create() # Set up source workspaces. source_workspace = dbGaPWorkspaceFactory.create() - self.collaborative_analysis_workspace.source_workspaces.add( - source_workspace.workspace - ) + self.collaborative_analysis_workspace.source_workspaces.add(source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=self.collaborative_analysis_workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=self.collaborative_analysis_workspace.analyst_group, account=account) # Source workspace auth domains membership. # GroupAccountMembershipFactory.create( # group=source_workspace.workspace.authorization_domains.first(), @@ -609,13 +549,9 @@ def test_context_needs_action_table_grant(self): account = AccountFactory.create() # Set up source workspaces. source_workspace = dbGaPWorkspaceFactory.create() - self.collaborative_analysis_workspace.source_workspaces.add( - source_workspace.workspace - ) + self.collaborative_analysis_workspace.source_workspaces.add(source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=self.collaborative_analysis_workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=self.collaborative_analysis_workspace.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_workspace.workspace.authorization_domains.first(), @@ -657,13 +593,9 @@ def test_context_needs_action_table_remoe(self): account = AccountFactory.create() # Set up source workspaces. source_workspace = dbGaPWorkspaceFactory.create() - self.collaborative_analysis_workspace.source_workspaces.add( - source_workspace.workspace - ) + self.collaborative_analysis_workspace.source_workspaces.add(source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=self.collaborative_analysis_workspace.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=self.collaborative_analysis_workspace.analyst_group, account=account) # Source workspace auth domains membership. # GroupAccountMembershipFactory.create( # group=source_workspace.workspace.authorization_domains.first(), @@ -744,9 +676,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -778,9 +708,7 @@ def test_status_code_with_user_permission_view(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -840,9 +768,7 @@ def test_context_verified_table_access(self): source_workspace = dbGaPWorkspaceFactory.create() instance.source_workspaces.add(source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=instance.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=instance.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_workspace.workspace.authorization_domains.first(), @@ -883,9 +809,7 @@ def test_context_verified_table_no_access(self): source_workspace = dbGaPWorkspaceFactory.create() instance.source_workspaces.add(source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=instance.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=instance.analyst_group, account=account) # Source workspace auth domains membership. # GroupAccountMembershipFactory.create( # group=source_workspace.workspace.authorization_domains.first(), @@ -925,9 +849,7 @@ def test_context_needs_action_table_grant(self): source_workspace = dbGaPWorkspaceFactory.create() instance.source_workspaces.add(source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=instance.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=instance.analyst_group, account=account) # Source workspace auth domains membership. GroupAccountMembershipFactory.create( group=source_workspace.workspace.authorization_domains.first(), @@ -967,9 +889,7 @@ def test_context_needs_action_table_remove(self): source_workspace = dbGaPWorkspaceFactory.create() instance.source_workspaces.add(source_workspace.workspace) # Analyst group membership. - GroupAccountMembershipFactory.create( - group=instance.analyst_group, account=account - ) + GroupAccountMembershipFactory.create(group=instance.analyst_group, account=account) # Source workspace auth domains membership. # GroupAccountMembershipFactory.create( # group=source_workspace.workspace.authorization_domains.first(), @@ -1042,14 +962,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -1069,9 +985,7 @@ def test_view_redirect_not_logged_in(self): response = self.client.get(self.get_url("foo", "bar", "test@example.com")) self.assertRedirects( response, - resolve_url(settings.LOGIN_URL) - + "?next=" - + self.get_url("foo", "bar", "test@example.com"), + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url("foo", "bar", "test@example.com"), ) def test_status_code_account_with_user_permission_view(self): @@ -1104,9 +1018,7 @@ def test_status_code_group_with_user_permission_view(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url("foo", "bar", "test@example.com")) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -1117,9 +1029,7 @@ def test_invalid_billing_project_name(self): collab_workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() account = AccountFactory.create() self.client.force_login(self.user) - response = self.client.get( - self.get_url("foo", collab_workspace.workspace.name, account.email) - ) + response = self.client.get(self.get_url("foo", collab_workspace.workspace.name, account.email)) self.assertEqual(response.status_code, 404) def test_invalid_workspace_name(self): @@ -1127,11 +1037,7 @@ def test_invalid_workspace_name(self): collab_workspace = factories.CollaborativeAnalysisWorkspaceFactory.create() account = AccountFactory.create() self.client.force_login(self.user) - response = self.client.get( - self.get_url( - collab_workspace.workspace.billing_project.name, "foo", account.email - ) - ) + response = self.client.get(self.get_url(collab_workspace.workspace.billing_project.name, "foo", account.email)) self.assertEqual(response.status_code, 404) def test_invalid_email(self): @@ -1544,9 +1450,7 @@ def test_post_verified_no_access_group(self): def test_post_grant_access_account(self): """Get request with verified access.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") account = AccountFactory.create(email="test@example.com") # Analyst group membership. GroupAccountMembershipFactory.create( @@ -1560,10 +1464,7 @@ def test_post_grant_access_account(self): # ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" self.anvil_response_mock.add( responses.PUT, api_url, @@ -1588,9 +1489,7 @@ def test_post_grant_access_account(self): def test_post_grant_access_group(self): """Get request with verified access for a group.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") group = ManagedGroupFactory.create(name="PRIMED_CC_WRITERS") # Auth domain membership. # GroupGroupMembershipFactory.create( @@ -1600,8 +1499,7 @@ def test_post_grant_access_group(self): # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/PRIMED_CC_WRITERS@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/PRIMED_CC_WRITERS@firecloud.org" ) self.anvil_response_mock.add( responses.PUT, @@ -1627,9 +1525,7 @@ def test_post_grant_access_group(self): def test_post_remove_access_account(self): """Get request with verified access.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") account = AccountFactory.create(email="test@example.com") # Analyst group membership. # GroupAccountMembershipFactory.create( @@ -1643,10 +1539,7 @@ def test_post_remove_access_account(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" self.anvil_response_mock.add( responses.DELETE, api_url, @@ -1667,9 +1560,7 @@ def test_post_remove_access_account(self): def test_post_remove_access_group(self): """Get request with verified access for a group.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") group = ManagedGroupFactory.create(name="test-group") # Auth domain membership. membership = GroupGroupMembershipFactory.create( @@ -1678,10 +1569,7 @@ def test_post_remove_access_group(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/test-group@firecloud.org" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/test-group@firecloud.org" self.anvil_response_mock.add( responses.DELETE, api_url, @@ -1702,9 +1590,7 @@ def test_post_remove_access_group(self): def test_post_grant_access_account_htmx(self): """Get request with verified access.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") account = AccountFactory.create(email="test@example.com") # Analyst group membership. GroupAccountMembershipFactory.create( @@ -1718,10 +1604,7 @@ def test_post_grant_access_account_htmx(self): # ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" self.anvil_response_mock.add( responses.PUT, api_url, @@ -1736,7 +1619,7 @@ def test_post_grant_access_account_htmx(self): account.email, ), {}, - **header + **header, ) self.assertEqual( response.content.decode(), @@ -1751,9 +1634,7 @@ def test_post_grant_access_account_htmx(self): def test_post_grant_access_group_htmx(self): """Get request with verified access for a group.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") group = ManagedGroupFactory.create(name="PRIMED_CC_WRITERS") # Auth domain membership. # GroupGroupMembershipFactory.create( @@ -1763,8 +1644,7 @@ def test_post_grant_access_group_htmx(self): # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/PRIMED_CC_WRITERS@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/PRIMED_CC_WRITERS@firecloud.org" ) self.anvil_response_mock.add( responses.PUT, @@ -1780,7 +1660,7 @@ def test_post_grant_access_group_htmx(self): group.email, ), {}, - **header + **header, ) self.assertEqual( response.content.decode(), @@ -1795,9 +1675,7 @@ def test_post_grant_access_group_htmx(self): def test_post_remove_access_account_htmx(self): """Get request with verified access.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") account = AccountFactory.create(email="test@example.com") # Analyst group membership. # GroupAccountMembershipFactory.create( @@ -1811,10 +1689,7 @@ def test_post_remove_access_account_htmx(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" self.anvil_response_mock.add( responses.DELETE, api_url, @@ -1829,7 +1704,7 @@ def test_post_remove_access_account_htmx(self): account.email, ), {}, - **header + **header, ) self.assertEqual( response.content.decode(), @@ -1840,9 +1715,7 @@ def test_post_remove_access_account_htmx(self): def test_post_remove_access_group_htmx(self): """Get request with verified access for a group.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") group = ManagedGroupFactory.create(name="test-group") # Auth domain membership. membership = GroupGroupMembershipFactory.create( @@ -1851,10 +1724,7 @@ def test_post_remove_access_group_htmx(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/test-group@firecloud.org" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/test-group@firecloud.org" self.anvil_response_mock.add( responses.DELETE, api_url, @@ -1869,7 +1739,7 @@ def test_post_remove_access_group_htmx(self): group.email, ), {}, - **header + **header, ) self.assertEqual( response.content.decode(), @@ -1880,9 +1750,7 @@ def test_post_remove_access_group_htmx(self): def test_anvil_error_grant_access_account(self): """Get request with verified access.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") account = AccountFactory.create(email="test@example.com") # Analyst group membership. GroupAccountMembershipFactory.create( @@ -1896,10 +1764,7 @@ def test_anvil_error_grant_access_account(self): # ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" self.anvil_response_mock.add( responses.PUT, api_url, @@ -1929,9 +1794,7 @@ def test_anvil_error_grant_access_account(self): def test_anvil_error_grant_access_group(self): """Get request with verified access for a group.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") group = ManagedGroupFactory.create(name="PRIMED_CC_WRITERS") # Auth domain membership. # GroupGroupMembershipFactory.create( @@ -1941,8 +1804,7 @@ def test_anvil_error_grant_access_group(self): # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/PRIMED_CC_WRITERS@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/PRIMED_CC_WRITERS@firecloud.org" ) self.anvil_response_mock.add( responses.PUT, @@ -1973,9 +1835,7 @@ def test_anvil_error_grant_access_group(self): def test_anvil_error_remove_access_account(self): """Get request with verified access.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") account = AccountFactory.create(email="test@example.com") # Analyst group membership. # GroupAccountMembershipFactory.create( @@ -1989,10 +1849,7 @@ def test_anvil_error_remove_access_account(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" self.anvil_response_mock.add( responses.DELETE, api_url, @@ -2022,9 +1879,7 @@ def test_anvil_error_remove_access_account(self): def test_anvil_error_remove_access_group(self): """Get request with verified access for a group.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") group = ManagedGroupFactory.create(name="test-group") # Auth domain membership. membership = GroupGroupMembershipFactory.create( @@ -2033,10 +1888,7 @@ def test_anvil_error_remove_access_group(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/test-group@firecloud.org" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/test-group@firecloud.org" self.anvil_response_mock.add( responses.DELETE, api_url, @@ -2066,9 +1918,7 @@ def test_anvil_error_remove_access_group(self): def test_anvil_error_grant_access_account_htmx(self): """Get request with verified access.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") account = AccountFactory.create(email="test@example.com") # Analyst group membership. GroupAccountMembershipFactory.create( @@ -2082,10 +1932,7 @@ def test_anvil_error_grant_access_account_htmx(self): # ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" self.anvil_response_mock.add( responses.PUT, api_url, @@ -2101,7 +1948,7 @@ def test_anvil_error_grant_access_account_htmx(self): account.email, ), {}, - **header + **header, ) self.assertEqual( response.content.decode(), @@ -2115,9 +1962,7 @@ def test_anvil_error_grant_access_account_htmx(self): def test_anvil_error_grant_access_group_htmx(self): """Get request with verified access for a group.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") group = ManagedGroupFactory.create(name="PRIMED_CC_WRITERS") # Auth domain membership. # GroupGroupMembershipFactory.create( @@ -2127,8 +1972,7 @@ def test_anvil_error_grant_access_group_htmx(self): # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/PRIMED_CC_WRITERS@firecloud.org" + self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/PRIMED_CC_WRITERS@firecloud.org" ) self.anvil_response_mock.add( responses.PUT, @@ -2145,7 +1989,7 @@ def test_anvil_error_grant_access_group_htmx(self): group.email, ), {}, - **header + **header, ) self.assertEqual( response.content.decode(), @@ -2159,9 +2003,7 @@ def test_anvil_error_grant_access_group_htmx(self): def test_anvil_error_remove_access_account_htmx(self): """Get request with verified access.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") account = AccountFactory.create(email="test@example.com") # Analyst group membership. # GroupAccountMembershipFactory.create( @@ -2175,10 +2017,7 @@ def test_anvil_error_remove_access_account_htmx(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/test@example.com" self.anvil_response_mock.add( responses.DELETE, api_url, @@ -2194,7 +2033,7 @@ def test_anvil_error_remove_access_account_htmx(self): account.email, ), {}, - **header + **header, ) self.assertEqual( response.content.decode(), @@ -2208,9 +2047,7 @@ def test_anvil_error_remove_access_account_htmx(self): def test_anvil_error_remove_access_group_htmx(self): """Get request with verified access for a group.""" - workspace = factories.CollaborativeAnalysisWorkspaceFactory.create( - workspace__name="TEST_COLLAB" - ) + workspace = factories.CollaborativeAnalysisWorkspaceFactory.create(workspace__name="TEST_COLLAB") group = ManagedGroupFactory.create(name="test-group") # Auth domain membership. membership = GroupGroupMembershipFactory.create( @@ -2219,10 +2056,7 @@ def test_anvil_error_remove_access_group_htmx(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/auth_TEST_COLLAB/member/test-group@firecloud.org" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/auth_TEST_COLLAB/member/test-group@firecloud.org" self.anvil_response_mock.add( responses.DELETE, api_url, @@ -2238,7 +2072,7 @@ def test_anvil_error_remove_access_group_htmx(self): group.email, ), {}, - **header + **header, ) self.assertEqual( response.content.decode(), diff --git a/primed/collaborative_analysis/views.py b/primed/collaborative_analysis/views.py index fe3e08f2..d27205b4 100644 --- a/primed/collaborative_analysis/views.py +++ b/primed/collaborative_analysis/views.py @@ -42,17 +42,14 @@ def get_object(self, queryset=None): obj = queryset.get() except queryset.model.DoesNotExist: raise Http404( - _("No %(verbose_name)s found matching the query") - % {"verbose_name": queryset.model._meta.verbose_name} + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} ) return obj def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Run the audit - data_access_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit( - queryset=[self.object] - ) + data_access_audit = audit.CollaborativeAnalysisWorkspaceAccessAudit(queryset=[self.object]) data_access_audit.run_audit() context["verified_table"] = data_access_audit.get_verified_table() context["errors_table"] = data_access_audit.get_errors_table() @@ -64,9 +61,7 @@ def get_context_data(self, **kwargs): class WorkspaceAuditAll(AnVILConsortiumManagerStaffViewRequired, TemplateView): """View to show audit results for all `CollaborativeAnalysisWorkspace` objects.""" - template_name = ( - "collaborative_analysis/collaborativeanalysisworkspace_audit_all.html" - ) + template_name = "collaborative_analysis/collaborativeanalysisworkspace_audit_all.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -82,10 +77,7 @@ def get_context_data(self, **kwargs): return context -class CollaborativeAnalysisAuditResolve( - AnVILConsortiumManagerStaffEditRequired, FormView -): - +class CollaborativeAnalysisAuditResolve(AnVILConsortiumManagerStaffEditRequired, FormView): form_class = Form template_name = "collaborative_analysis/audit_resolve.html" htmx_success = """ Handled!""" @@ -105,8 +97,7 @@ def get_collaborative_analysis_workspace(self): obj = queryset.get() except queryset.model.DoesNotExist: raise Http404( - _("No %(verbose_name)s found matching the query") - % {"verbose_name": queryset.model._meta.verbose_name} + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} ) return obj @@ -125,50 +116,37 @@ def get_member(self): return ManagedGroup.objects.get(email=email) except ManagedGroup.DoesNotExist: raise Http404( - _("No %(verbose_name)s found matching the query") - % {"verbose_name": "Account or ManagedGroup"} + _("No %(verbose_name)s found matching the query") % {"verbose_name": "Account or ManagedGroup"} ) def get_audit_result(self): instance = audit.CollaborativeAnalysisWorkspaceAccessAudit( - queryset=models.CollaborativeAnalysisWorkspace.objects.filter( - pk=self.collaborative_analysis_workspace.pk - ) + queryset=models.CollaborativeAnalysisWorkspace.objects.filter(pk=self.collaborative_analysis_workspace.pk) ) # No way to include a queryset of members at this point - need to call the sub method directly. if isinstance(self.member, Account): - instance._audit_workspace_and_account( - self.collaborative_analysis_workspace, self.member - ) + instance._audit_workspace_and_account(self.collaborative_analysis_workspace, self.member) else: - instance._audit_workspace_and_group( - self.collaborative_analysis_workspace, self.member - ) + instance._audit_workspace_and_group(self.collaborative_analysis_workspace, self.member) # Set to completed, because we are just running this one specific check. instance.completed = True return instance.get_all_results()[0] def get(self, request, *args, **kwargs): - self.collaborative_analysis_workspace = ( - self.get_collaborative_analysis_workspace() - ) + self.collaborative_analysis_workspace = self.get_collaborative_analysis_workspace() self.member = self.get_member() self.audit_result = self.get_audit_result() return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): - self.collaborative_analysis_workspace = ( - self.get_collaborative_analysis_workspace() - ) + self.collaborative_analysis_workspace = self.get_collaborative_analysis_workspace() self.member = self.get_member() self.audit_result = self.get_audit_result() return super().post(request, *args, **kwargs) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context[ - "collaborative_analysis_workspace" - ] = self.collaborative_analysis_workspace + context["collaborative_analysis_workspace"] = self.collaborative_analysis_workspace context["member"] = self.member context["audit_result"] = self.audit_result return context @@ -177,9 +155,7 @@ def get_success_url(self): return self.collaborative_analysis_workspace.get_absolute_url() def form_valid(self, form): - auth_domain = ( - self.collaborative_analysis_workspace.workspace.authorization_domains.first() - ) + auth_domain = self.collaborative_analysis_workspace.workspace.authorization_domains.first() # Handle the result. try: with transaction.atomic(): diff --git a/primed/dbgap/adapters.py b/primed/dbgap/adapters.py index a2ed57a9..d1eaf728 100644 --- a/primed/dbgap/adapters.py +++ b/primed/dbgap/adapters.py @@ -22,13 +22,7 @@ class dbGaPWorkspaceAdapter(BaseWorkspaceAdapter): def get_extra_detail_context_data(self, workspace, request): extra_context = {} - associated_data_prep = Workspace.objects.filter( - dataprepworkspace__target_workspace=workspace - ) - extra_context["associated_data_prep_workspaces"] = DataPrepWorkspaceUserTable( - associated_data_prep - ) - extra_context["data_prep_active"] = associated_data_prep.filter( - dataprepworkspace__is_active=True - ).exists() + associated_data_prep = Workspace.objects.filter(dataprepworkspace__target_workspace=workspace) + extra_context["associated_data_prep_workspaces"] = DataPrepWorkspaceUserTable(associated_data_prep) + extra_context["data_prep_active"] = associated_data_prep.filter(dataprepworkspace__is_active=True).exists() return extra_context diff --git a/primed/dbgap/audit.py b/primed/dbgap/audit.py index d76eb26f..b70e459c 100644 --- a/primed/dbgap/audit.py +++ b/primed/dbgap/audit.py @@ -29,12 +29,9 @@ class AuditResult(PRIMEDAuditResult): def __post_init__(self): if self.data_access_request and ( - self.data_access_request.dbgap_data_access_snapshot.dbgap_application - != self.dbgap_application + self.data_access_request.dbgap_data_access_snapshot.dbgap_application != self.dbgap_application ): - raise ValueError( - "data_access_request application and dbgap_application do not match." - ) + raise ValueError("data_access_request application and dbgap_application do not match.") def get_action_url(self): """The URL that handles the action needed.""" @@ -128,16 +125,13 @@ class dbGaPAccessAuditTable(tables.Table): dar_consent = tables.Column(verbose_name="DAR consent") has_access = BooleanIconColumn(show_false_icon=True) note = tables.Column() - action = tables.TemplateColumn( - template_name="dbgap/snippets/dbgap_audit_action_button.html" - ) + action = tables.TemplateColumn(template_name="dbgap/snippets/dbgap_audit_action_button.html") class Meta: attrs = {"class": "table align-middle"} class dbGaPAccessAudit(PRIMEDAudit): - # Access verified. APPROVED_DAR = "Approved DAR." @@ -161,22 +155,14 @@ def __init__(self, dbgap_application_queryset=None, dbgap_workspace_queryset=Non if dbgap_application_queryset is None: dbgap_application_queryset = dbGaPApplication.objects.all() if not ( - isinstance(dbgap_application_queryset, QuerySet) - and dbgap_application_queryset.model is dbGaPApplication + isinstance(dbgap_application_queryset, QuerySet) and dbgap_application_queryset.model is dbGaPApplication ): - raise ValueError( - "dbgap_application_queryset must be a queryset of dbGaPApplication objects." - ) + raise ValueError("dbgap_application_queryset must be a queryset of dbGaPApplication objects.") self.dbgap_application_queryset = dbgap_application_queryset if dbgap_workspace_queryset is None: dbgap_workspace_queryset = dbGaPWorkspace.objects.all() - if not ( - isinstance(dbgap_workspace_queryset, QuerySet) - and dbgap_workspace_queryset.model is dbGaPWorkspace - ): - raise ValueError( - "dbgap_workspace_queryset must be a queryset of dbGaPWorkspace objects." - ) + if not (isinstance(dbgap_workspace_queryset, QuerySet) and dbgap_workspace_queryset.model is dbGaPWorkspace): + raise ValueError("dbgap_workspace_queryset must be a queryset of dbGaPWorkspace objects.") self.dbgap_workspace_queryset = dbgap_workspace_queryset def _run_audit(self): @@ -186,15 +172,11 @@ def _run_audit(self): def audit_application_and_workspace(self, dbgap_application, dbgap_workspace): """Audit access for a specific dbGaP application and a specific workspace.""" - in_auth_domain = dbgap_workspace.workspace.is_in_authorization_domain( - dbgap_application.anvil_access_group - ) + in_auth_domain = dbgap_workspace.workspace.is_in_authorization_domain(dbgap_application.anvil_access_group) # Get the most recent snapshot. try: - dar_snapshot = dbgap_application.dbgapdataaccesssnapshot_set.get( - is_most_recent=True - ) + dar_snapshot = dbgap_application.dbgapdataaccesssnapshot_set.get(is_most_recent=True) except dbGaPDataAccessSnapshot.DoesNotExist: if in_auth_domain: # Error! @@ -219,9 +201,7 @@ def audit_application_and_workspace(self, dbgap_application, dbgap_workspace): try: # There should only be one DAR from this snapshot associated with a given workspace. - dar = dbgap_workspace.get_data_access_requests().get( - dbgap_data_access_snapshot=dar_snapshot - ) + dar = dbgap_workspace.get_data_access_requests().get(dbgap_data_access_snapshot=dar_snapshot) except dbGaPDataAccessRequest.DoesNotExist: # No matching DAR exists for this application. if in_auth_domain: diff --git a/primed/dbgap/constants.py b/primed/dbgap/constants.py index a8515dfc..91873559 100644 --- a/primed/dbgap/constants.py +++ b/primed/dbgap/constants.py @@ -1,8 +1,6 @@ DBGAP_STUDY_URL = "https://www.ncbi.nlm.nih.gov/projects/gap/cgi-bin/study.cgi" PHS_REGEX = r"^phs(?P\d{6})$" -FULL_ACCESSION_REGEX = ( - r"^phs(?P\d{6})\.v(?P\d+?)\.p(?P\d+?)$" -) +FULL_ACCESSION_REGEX = r"^phs(?P\d{6})\.v(?P\d+?)\.p(?P\d+?)$" JSON_DAR_DEFS = { "project": { diff --git a/primed/dbgap/forms.py b/primed/dbgap/forms.py index 8f690d66..efe22479 100644 --- a/primed/dbgap/forms.py +++ b/primed/dbgap/forms.py @@ -116,9 +116,7 @@ def clean_dbgap_dar_data(self): except jsonschema.exceptions.ValidationError as e: # Replace the full json string because it will be very long error_message = e.message.replace(str(e.instance), "JSON array") - raise ValidationError( - self.ERROR_JSON_VALIDATION, params={"error": error_message} - ) + raise ValidationError(self.ERROR_JSON_VALIDATION, params={"error": error_message}) # Verify that there is only one project in the json. if len(data) > 1: raise ValidationError("JSON array includes more than one project ID.") @@ -131,9 +129,7 @@ def clean_dbgap_dar_data(self): class dbGaPDataAccessSnapshotMultipleForm(forms.Form): """Form to create new dbGaPDataAccessSnapshots for multiple dbGaPApplications at once.""" - ERROR_PROJECT_ID_DOES_NOT_EXIST = ( - "dbGaP Application(s) for some project id(s) do not exist in app." - ) + ERROR_PROJECT_ID_DOES_NOT_EXIST = "dbGaP Application(s) for some project id(s) do not exist in app." ERROR_JSON_VALIDATION = "JSON validation error: %(error)s" dbgap_dar_data = forms.JSONField() @@ -145,16 +141,12 @@ def clean_dbgap_dar_data(self): except jsonschema.exceptions.ValidationError as e: # Replace the full json string because it will be very long error_message = e.message.replace(str(e.instance), "JSON array") - raise ValidationError( - self.ERROR_JSON_VALIDATION, params={"error": error_message} - ) + raise ValidationError(self.ERROR_JSON_VALIDATION, params={"error": error_message}) # Verify that all projects exist. missing_ids = [] for project_json in data: project_id = project_json["Project_id"] - if not models.dbGaPApplication.objects.filter( - dbgap_project_id=project_id - ).exists(): + if not models.dbGaPApplication.objects.filter(dbgap_project_id=project_id).exists(): missing_ids.append(str(project_id)) if missing_ids: raise ValidationError(self.ERROR_PROJECT_ID_DOES_NOT_EXIST) diff --git a/primed/dbgap/management/commands/run_dbgap_audit.py b/primed/dbgap/management/commands/run_dbgap_audit.py index 57089930..e7eed490 100644 --- a/primed/dbgap/management/commands/run_dbgap_audit.py +++ b/primed/dbgap/management/commands/run_dbgap_audit.py @@ -25,9 +25,7 @@ def handle(self, *args, **options): # Report errors and needs access. audit_ok = data_access_audit.ok() # Construct the url for handling errors. - url = ( - "https://" + Site.objects.get_current().domain + reverse("dbgap:audit:all") - ) + url = "https://" + Site.objects.get_current().domain + reverse("dbgap:audit:all") if audit_ok: self.stdout.write(self.style.SUCCESS("ok!")) else: @@ -35,15 +33,11 @@ def handle(self, *args, **options): # Print results self.stdout.write("* Verified: {}".format(len(data_access_audit.verified))) - self.stdout.write( - "* Needs action: {}".format(len(data_access_audit.needs_action)) - ) + self.stdout.write("* Needs action: {}".format(len(data_access_audit.needs_action))) self.stdout.write("* Errors: {}".format(len(data_access_audit.errors))) if not audit_ok: - self.stdout.write( - self.style.ERROR(f"Please visit {url} to resolve these issues.") - ) + self.stdout.write(self.style.ERROR(f"Please visit {url} to resolve these issues.")) # Send email if requested and there are problems. email = options["email"] diff --git a/primed/dbgap/models.py b/primed/dbgap/models.py index 0260a9ce..7749d577 100644 --- a/primed/dbgap/models.py +++ b/primed/dbgap/models.py @@ -53,14 +53,10 @@ def __str__(self): return "phs{phs:06d}".format(phs=self.dbgap_phs) def get_absolute_url(self): - return reverse( - "dbgap:dbgap_study_accessions:detail", kwargs={"dbgap_phs": self.dbgap_phs} - ) + return reverse("dbgap:dbgap_study_accessions:detail", kwargs={"dbgap_phs": self.dbgap_phs}) -class dbGaPWorkspace( - RequesterModel, DataUseOntologyModel, TimeStampedModel, BaseWorkspaceData -): +class dbGaPWorkspace(RequesterModel, DataUseOntologyModel, TimeStampedModel, BaseWorkspaceData): """A model to track additional data about dbGaP data in a workspace.""" # PositiveIntegerField allows 0 and we want this to be 1 or higher. @@ -99,13 +95,9 @@ class dbGaPWorkspace( # Unfortunately, there are often legacy codes that don't fit into the current main/modifiers model. # We also need this field to match to dbGaP authorized access, so store it separately.""" - data_use_limitations = models.TextField( - help_text="""The full data use limitations for this workspace.""" - ) + data_use_limitations = models.TextField(help_text="""The full data use limitations for this workspace.""") - acknowledgments = models.TextField( - help_text="Acknowledgments associated with data in this workspace." - ) + acknowledgments = models.TextField(help_text="Acknowledgments associated with data in this workspace.") available_data = models.ManyToManyField( AvailableData, help_text="Data available in this accession.", @@ -242,20 +234,13 @@ def clean(self): """ if self.dbgap_dar_data: try: - jsonschema.validate( - self.dbgap_dar_data, constants.JSON_PROJECT_DAR_SCHEMA - ) + jsonschema.validate(self.dbgap_dar_data, constants.JSON_PROJECT_DAR_SCHEMA) except jsonschema.exceptions.ValidationError as e: # Replace the full json string because it will be very long error_message = e.message.replace(str(e.instance), "JSON array") raise ValidationError({"dbgap_dar_data": error_message}) - if ( - self.dbgap_dar_data["Project_id"] - != self.dbgap_application.dbgap_project_id - ): - raise ValidationError( - "Project_id in JSON does not match dbgap_application.dbgap_project_id." - ) + if self.dbgap_dar_data["Project_id"] != self.dbgap_application.dbgap_project_id: + raise ValidationError("Project_id in JSON does not match dbgap_application.dbgap_project_id.") def create_dars_from_json(self): """Add DARs for this application from the dbGaP json for this project snapshot. @@ -280,19 +265,13 @@ def create_dars_from_json(self): # Make sure that the dbgap_project_id matches. project_id = project_json["Project_id"] if project_id != self.dbgap_application.dbgap_project_id: - raise ValueError( - "project_id does not match dbgap_application.dbgap_project_id." - ) + raise ValueError("project_id does not match dbgap_application.dbgap_project_id.") # Loop over studies and requests to create DARs. # Do not save them until everything has been successfully created. for study_json in project_json["studies"]: # Consider making this a model manager method for dbGaPStudyAccession, since it may be common. # Get the dbGaPStudyAccession associated with this phs. - phs = int( - re.match(constants.PHS_REGEX, study_json["study_accession"]).group( - "phs" - ) - ) + phs = int(re.match(constants.PHS_REGEX, study_json["study_accession"]).group("phs")) # Create the DAR. for request_json in study_json["requests"]: # dbGaP does not keep track of the original version and participant set associated with a DAR. @@ -313,10 +292,7 @@ def create_dars_from_json(self): raise ValueError("dbgap_phs mismatch") if previous_dar.dbgap_consent_code != request_json["consent_code"]: raise ValueError("dbgap_consent_code mismatch") - if ( - previous_dar.dbgap_data_access_snapshot.dbgap_application.dbgap_project_id - != project_id - ): + if previous_dar.dbgap_data_access_snapshot.dbgap_application.dbgap_project_id != project_id: raise ValueError("project_id mismatch") # If everything looks good, pull the original version and participant set from the previous DAR. original_version = previous_dar.original_version @@ -340,9 +316,7 @@ def create_dars_from_json(self): original_participant_set = int(match.group("participant_set")) except ValueError as e: # Log an error and re-raise. - msg = "DAR ID mismatch for snapshot pk {} and DAR ID {}".format( - self.pk, previous_dar.dbgap_dar_id - ) + msg = "DAR ID mismatch for snapshot pk {} and DAR ID {}".format(self.pk, previous_dar.dbgap_dar_id) logger.error(msg) logger.error(str(e)) raise diff --git a/primed/dbgap/tables.py b/primed/dbgap/tables.py index fa22c5b1..717a9e77 100644 --- a/primed/dbgap/tables.py +++ b/primed/dbgap/tables.py @@ -19,7 +19,7 @@ def __init__( accessor="get_dbgap_accession", dbgap_link_accessor="get_dbgap_link", verbose_name="dbGaP accession", - **kwargs + **kwargs, ): self.dbgap_link_accessor = dbgap_link_accessor super().__init__(accessor=accessor, verbose_name=verbose_name, **kwargs) @@ -29,9 +29,7 @@ def render(self, record): if self.dbgap_link_accessor: url = tables.A(self.dbgap_link_accessor).resolve(record) return format_html( - """{} """.format( - url, value - ) + """{} """.format(url, value) ) else: return value @@ -46,9 +44,7 @@ class ManyToManyDateTimeColumn(tables.columns.ManyToManyColumn): def transform(self, obj): context = Context() context.update({"value": obj.created, "default": self.default}) - return Template( - """{{ value|date:"DATETIME_FORMAT"|default:default }}""" - ).render(context) + return Template("""{{ value|date:"DATETIME_FORMAT"|default:default }}""").render(context) class dbGaPStudyAccessionTable(tables.Table): @@ -87,9 +83,7 @@ class dbGaPWorkspaceUserTable(tables.Table): "dbgapworkspace__dbgap_participant_set", ), ) - dbgapworkspace__dbgap_consent_abbreviation = tables.columns.Column( - verbose_name="Consent" - ) + dbgapworkspace__dbgap_consent_abbreviation = tables.columns.Column(verbose_name="Consent") dbgapworkspace__gsr_restricted = BooleanIconColumn( orderable=False, true_icon="dash-circle-fill", true_color="#ffc107" ) @@ -266,9 +260,7 @@ class dbGaPDataAccessRequestBySnapshotTable(dbGaPDataAccessRequestTable): dbgap_data_access_snapshot__dbgap_application__dbgap_project_id = None dbgap_data_access_snapshot__created = None - matching_workspaces = tables.columns.Column( - accessor="get_dbgap_workspaces", orderable=False, default=" " - ) + matching_workspaces = tables.columns.Column(accessor="get_dbgap_workspaces", orderable=False, default=" ") class Meta: model = models.dbGaPDataAccessRequest diff --git a/primed/dbgap/tests/factories.py b/primed/dbgap/tests/factories.py index 32151573..78e43981 100644 --- a/primed/dbgap/tests/factories.py +++ b/primed/dbgap/tests/factories.py @@ -91,9 +91,7 @@ def authorization_domains(self, create, extracted, **kwargs): return # Create an authorization domain. - auth_domain = ManagedGroupFactory.create( - name="auth_{}".format(self.workspace.name) - ) + auth_domain = ManagedGroupFactory.create(name="auth_{}".format(self.workspace.name)) self.workspace.authorization_domains.add(auth_domain) @@ -157,18 +155,12 @@ class dbGaPDataAccessRequestForWorkspaceFactory(DjangoModelFactory): """A factory for the dbGaPApplication model to match a workspace.""" dbgap_data_access_snapshot = SubFactory(dbGaPDataAccessSnapshotFactory) - dbgap_phs = LazyAttribute( - lambda o: o.dbgap_workspace.dbgap_study_accession.dbgap_phs - ) + dbgap_phs = LazyAttribute(lambda o: o.dbgap_workspace.dbgap_study_accession.dbgap_phs) dbgap_dar_id = Sequence(lambda n: n + 1) original_version = LazyAttribute(lambda o: o.dbgap_workspace.dbgap_version) - original_participant_set = LazyAttribute( - lambda o: o.dbgap_workspace.dbgap_participant_set - ) + original_participant_set = LazyAttribute(lambda o: o.dbgap_workspace.dbgap_participant_set) dbgap_consent_code = LazyAttribute(lambda o: o.dbgap_workspace.dbgap_consent_code) - dbgap_consent_abbreviation = LazyAttribute( - lambda o: o.dbgap_workspace.dbgap_consent_abbreviation - ) + dbgap_consent_abbreviation = LazyAttribute(lambda o: o.dbgap_workspace.dbgap_consent_abbreviation) dbgap_current_status = models.dbGaPDataAccessRequest.APPROVED dbgap_dac = Faker("word") @@ -211,6 +203,4 @@ class dbGaPJSONProjectFactory(DictFactory): studies = List([SubFactory(dbGaPJSONStudyFactory)]) class Params: - dbgap_application = Trait( - Project_id=LazyAttribute(lambda o: o.dbgap_application.dbgap_project_id) - ) + dbgap_application = Trait(Project_id=LazyAttribute(lambda o: o.dbgap_application.dbgap_project_id)) diff --git a/primed/dbgap/tests/test_audit.py b/primed/dbgap/tests/test_audit.py index f2699c6a..10e076dd 100644 --- a/primed/dbgap/tests/test_audit.py +++ b/primed/dbgap/tests/test_audit.py @@ -221,9 +221,7 @@ def test_verified_access(self): """run_audit with one application and one workspace that has verified access.""" # Create a workspace and matching DAR. dbgap_workspace = factories.dbGaPWorkspaceFactory.create() - dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=dbgap_workspace - ) + dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=dbgap_workspace) # Add the anvil group to the auth group for the workspace. GroupGroupMembershipFactory( parent_group=dbgap_workspace.workspace.authorization_domains.first(), @@ -237,9 +235,7 @@ def test_verified_access(self): record = dbgap_audit.verified[0] self.assertIsInstance(record, audit.VerifiedAccess) self.assertEqual(record.workspace, dbgap_workspace) - self.assertEqual( - record.dbgap_application, dar.dbgap_data_access_snapshot.dbgap_application - ) + self.assertEqual(record.dbgap_application, dar.dbgap_data_access_snapshot.dbgap_application) self.assertEqual(record.data_access_request, dar) self.assertTrue(dbgap_audit.ok()) @@ -297,9 +293,7 @@ def test_verified_no_access_dar_not_approved(self): record = dbgap_audit.verified[0] self.assertIsInstance(record, audit.VerifiedNoAccess) self.assertEqual(record.workspace, dbgap_workspace) - self.assertEqual( - record.dbgap_application, dar.dbgap_data_access_snapshot.dbgap_application - ) + self.assertEqual(record.dbgap_application, dar.dbgap_data_access_snapshot.dbgap_application) self.assertEqual(record.data_access_request, dar) self.assertEqual(record.note, audit.dbGaPAccessAudit.DAR_NOT_APPROVED) self.assertTrue(dbgap_audit.ok()) @@ -308,9 +302,7 @@ def test_verified_no_access_no_dar(self): """run_audit with one application and one workspace that has verified no access.""" # Create a workspace and matching DAR. dbgap_application = factories.dbGaPApplicationFactory.create() - factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application - ) + factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application) dbgap_workspace = factories.dbGaPWorkspaceFactory.create() # Do not add the anvil group to the auth group for the workspace. dbgap_audit = audit.dbGaPAccessAudit() @@ -329,9 +321,7 @@ def test_verified_no_access_no_dar(self): def test_grant_access_new_approved_dar(self): # Create a workspace and matching DAR. # Workspace was created before the snapshot. - dbgap_workspace = factories.dbGaPWorkspaceFactory.create( - created=timezone.now() - timedelta(weeks=3) - ) + dbgap_workspace = factories.dbGaPWorkspaceFactory.create(created=timezone.now() - timedelta(weeks=3)) dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( dbgap_workspace=dbgap_workspace, dbgap_data_access_snapshot__created=timezone.now() - timedelta(weeks=2), @@ -345,9 +335,7 @@ def test_grant_access_new_approved_dar(self): record = dbgap_audit.needs_action[0] self.assertIsInstance(record, audit.GrantAccess) self.assertEqual(record.workspace, dbgap_workspace) - self.assertEqual( - record.dbgap_application, dar.dbgap_data_access_snapshot.dbgap_application - ) + self.assertEqual(record.dbgap_application, dar.dbgap_data_access_snapshot.dbgap_application) self.assertEqual(record.data_access_request, dar) self.assertEqual(record.note, audit.dbGaPAccessAudit.NEW_APPROVED_DAR) self.assertFalse(dbgap_audit.ok()) @@ -355,9 +343,7 @@ def test_grant_access_new_approved_dar(self): def test_grant_access_new_workspace(self): # Create a workspace and matching DAR. # Workspace was created after the snapshot. - dbgap_workspace = factories.dbGaPWorkspaceFactory.create( - created=timezone.now() - timedelta(weeks=2) - ) + dbgap_workspace = factories.dbGaPWorkspaceFactory.create(created=timezone.now() - timedelta(weeks=2)) dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( dbgap_workspace=dbgap_workspace, dbgap_data_access_snapshot__created=timezone.now() - timedelta(weeks=3), @@ -371,9 +357,7 @@ def test_grant_access_new_workspace(self): record = dbgap_audit.needs_action[0] self.assertIsInstance(record, audit.GrantAccess) self.assertEqual(record.workspace, dbgap_workspace) - self.assertEqual( - record.dbgap_application, dar.dbgap_data_access_snapshot.dbgap_application - ) + self.assertEqual(record.dbgap_application, dar.dbgap_data_access_snapshot.dbgap_application) self.assertEqual(record.data_access_request, dar) self.assertEqual(record.note, audit.dbGaPAccessAudit.NEW_WORKSPACE) self.assertFalse(dbgap_audit.ok()) @@ -381,9 +365,7 @@ def test_grant_access_new_workspace(self): def test_grant_access_updated_dar(self): # Create a workspace and matching DAR. # Workspace was created before the snapshot. - dbgap_workspace = factories.dbGaPWorkspaceFactory.create( - created=timezone.now() - timedelta(weeks=4) - ) + dbgap_workspace = factories.dbGaPWorkspaceFactory.create(created=timezone.now() - timedelta(weeks=4)) # Create an old snapshot where the DAR was not approved. dbgap_application = factories.dbGaPApplicationFactory.create() old_dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( @@ -408,9 +390,7 @@ def test_grant_access_updated_dar(self): record = dbgap_audit.needs_action[0] self.assertIsInstance(record, audit.GrantAccess) self.assertEqual(record.workspace, dbgap_workspace) - self.assertEqual( - record.dbgap_application, dar.dbgap_data_access_snapshot.dbgap_application - ) + self.assertEqual(record.dbgap_application, dar.dbgap_data_access_snapshot.dbgap_application) self.assertEqual(record.data_access_request, dar) self.assertEqual(record.note, audit.dbGaPAccessAudit.NEW_APPROVED_DAR) self.assertFalse(dbgap_audit.ok()) @@ -447,9 +427,7 @@ def test_remove_access_udpated_dar(self): record = dbgap_audit.needs_action[0] self.assertIsInstance(record, audit.RemoveAccess) self.assertEqual(record.workspace, dbgap_workspace) - self.assertEqual( - record.dbgap_application, dar.dbgap_data_access_snapshot.dbgap_application - ) + self.assertEqual(record.dbgap_application, dar.dbgap_data_access_snapshot.dbgap_application) self.assertEqual(record.data_access_request, dar) self.assertEqual(record.note, audit.dbGaPAccessAudit.PREVIOUS_APPROVAL) self.assertFalse(dbgap_audit.ok()) @@ -568,16 +546,12 @@ def test_two_applications(self): record = dbgap_audit.verified[0] self.assertIsInstance(record, audit.VerifiedAccess) self.assertEqual(record.workspace, dbgap_workspace) - self.assertEqual( - record.dbgap_application, dar_1.dbgap_data_access_snapshot.dbgap_application - ) + self.assertEqual(record.dbgap_application, dar_1.dbgap_data_access_snapshot.dbgap_application) self.assertEqual(record.data_access_request, dar_1) record = dbgap_audit.needs_action[0] self.assertIsInstance(record, audit.GrantAccess) self.assertEqual(record.workspace, dbgap_workspace) - self.assertEqual( - record.dbgap_application, dar_2.dbgap_data_access_snapshot.dbgap_application - ) + self.assertEqual(record.dbgap_application, dar_2.dbgap_data_access_snapshot.dbgap_application) self.assertEqual(record.data_access_request, dar_2) def test_dbgap_application_queryset(self): @@ -613,9 +587,7 @@ def test_dbgap_workspace_queryset(self): def test_dbgap_workspace_queryset_wrong_class(self): """dbGaPAccessAudit raises error if dbgap_workspace_queryset has the wrong model class.""" with self.assertRaises(ValueError) as e: - audit.dbGaPAccessAudit( - dbgap_workspace_queryset=models.dbGaPApplication.objects.all() - ) + audit.dbGaPAccessAudit(dbgap_workspace_queryset=models.dbGaPApplication.objects.all()) self.assertEqual( str(e.exception), "dbgap_workspace_queryset must be a queryset of dbGaPWorkspace objects.", @@ -634,9 +606,7 @@ def test_dbgap_workspace_queryset_not_queryset(self): def test_dbgap_application_queryset_wrong_class(self): """dbGaPAccessAudit raises error if dbgap_application_queryset has the wrong model class.""" with self.assertRaises(ValueError) as e: - audit.dbGaPAccessAudit( - dbgap_application_queryset=models.dbGaPWorkspace.objects.all() - ) + audit.dbGaPAccessAudit(dbgap_application_queryset=models.dbGaPWorkspace.objects.all()) self.assertEqual( str(e.exception), "dbgap_application_queryset must be a queryset of dbGaPApplication objects.", @@ -658,17 +628,13 @@ def test_two_applications_two_workspaces(self): def test_ok_with_verified_and_needs_action(self): # Create a workspace and matching DAR. dbgap_workspace = factories.dbGaPWorkspaceFactory.create() - other_dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=dbgap_workspace - ) + other_dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=dbgap_workspace) # Add the anvil group to the auth group for the workspace. GroupGroupMembershipFactory( parent_group=dbgap_workspace.workspace.authorization_domains.first(), child_group=other_dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group, ) - factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=dbgap_workspace - ) + factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=dbgap_workspace) # # Add the anvil group to the auth group for the workspace. # GroupGroupMembershipFactory( # parent_group=dbgap_workspace.workspace.authorization_domains.first(), @@ -683,9 +649,7 @@ def test_ok_with_verified_and_needs_action(self): def test_ok_with_verified_and_error(self): # Create a workspace and matching DAR. dbgap_workspace = factories.dbGaPWorkspaceFactory.create() - other_dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=dbgap_workspace - ) + other_dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=dbgap_workspace) # Add the anvil group to the auth group for the workspace. GroupGroupMembershipFactory( parent_group=dbgap_workspace.workspace.authorization_domains.first(), diff --git a/primed/dbgap/tests/test_commands.py b/primed/dbgap/tests/test_commands.py index e3b4dc93..97f8f3fb 100644 --- a/primed/dbgap/tests/test_commands.py +++ b/primed/dbgap/tests/test_commands.py @@ -43,9 +43,7 @@ def test_command_run_audit_one_instance_needs_action(self): """Test command output with one needs_action instance.""" # Create a workspace and matching DAR. dbgap_workspace = factories.dbGaPWorkspaceFactory.create() - factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=dbgap_workspace - ) + factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=dbgap_workspace) out = StringIO() call_command("run_dbgap_audit", "--no-color", stdout=out) self.assertIn("Running dbGaP access audit... problems found.", out.getvalue()) @@ -79,9 +77,7 @@ def test_command_run_audit_one_instance_verified_email(self): factories.dbGaPWorkspaceFactory.create() factories.dbGaPApplicationFactory.create() out = StringIO() - call_command( - "run_dbgap_audit", "--no-color", email="test@example.com", stdout=out - ) + call_command("run_dbgap_audit", "--no-color", email="test@example.com", stdout=out) self.assertIn("Running dbGaP access audit... ok!", out.getvalue()) # Zero messages have been sent by default. self.assertEqual(len(mail.outbox), 0) @@ -90,13 +86,9 @@ def test_command_run_audit_one_instance_needs_action_email(self): """Email is sent for one needs_action instance.""" # Create a workspace and matching DAR. dbgap_workspace = factories.dbGaPWorkspaceFactory.create() - factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=dbgap_workspace - ) + factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=dbgap_workspace) out = StringIO() - call_command( - "run_dbgap_audit", "--no-color", email="test@example.com", stdout=out - ) + call_command("run_dbgap_audit", "--no-color", email="test@example.com", stdout=out) self.assertIn("Running dbGaP access audit... problems found.", out.getvalue()) self.assertIn("* Verified: 0", out.getvalue()) self.assertIn("* Needs action: 1", out.getvalue()) @@ -117,9 +109,7 @@ def test_command_run_audit_one_instance_error_email(self): child_group=dbgap_application.anvil_access_group, ) out = StringIO() - call_command( - "run_dbgap_audit", "--no-color", email="test@example.com", stdout=out - ) + call_command("run_dbgap_audit", "--no-color", email="test@example.com", stdout=out) self.assertIn("Running dbGaP access audit... problems found.", out.getvalue()) self.assertIn("* Verified: 0", out.getvalue()) self.assertIn("* Needs action: 0", out.getvalue()) @@ -136,12 +126,8 @@ def test_different_domain(self): site.save() with self.settings(SITE_ID=site.id): dbgap_workspace = factories.dbGaPWorkspaceFactory.create() - factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=dbgap_workspace - ) + factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=dbgap_workspace) out = StringIO() call_command("run_dbgap_audit", "--no-color", stdout=out) - self.assertIn( - "Running dbGaP access audit... problems found.", out.getvalue() - ) + self.assertIn("Running dbGaP access audit... problems found.", out.getvalue()) self.assertIn("https://foobar.com", out.getvalue()) diff --git a/primed/dbgap/tests/test_forms.py b/primed/dbgap/tests/test_forms.py index 96eeba23..88fac8b8 100644 --- a/primed/dbgap/tests/test_forms.py +++ b/primed/dbgap/tests/test_forms.py @@ -579,13 +579,7 @@ def setUp(self): def test_valid(self): """Form is valid with necessary input.""" form_data = { - "dbgap_dar_data": json.dumps( - [ - factories.dbGaPJSONProjectFactory( - dbgap_application=self.dbgap_application - ) - ] - ), + "dbgap_dar_data": json.dumps([factories.dbGaPJSONProjectFactory(dbgap_application=self.dbgap_application)]), "dbgap_application": self.dbgap_application, } form = self.form_class(data=form_data) @@ -593,9 +587,7 @@ def test_valid(self): def test_missing_dbgap_application(self): """Form is invalid when dbgap_application is missing.""" - form = self.form_class( - data={"dbgap_dar_data": json.dumps([factories.dbGaPJSONProjectFactory()])} - ) + form = self.form_class(data={"dbgap_dar_data": json.dumps([factories.dbGaPJSONProjectFactory()])}) from django.core.exceptions import ObjectDoesNotExist with self.assertRaises(ObjectDoesNotExist): @@ -679,9 +671,7 @@ def test_dbgap_dar_data_missing_dbgap_project_id(self): def test_dbgap_dar_data_missing_studies(self): """Form is invalid when studies is missing from the JSON.""" - project_json = factories.dbGaPJSONProjectFactory.create( - dbgap_application=self.dbgap_application - ) + project_json = factories.dbGaPJSONProjectFactory.create(dbgap_application=self.dbgap_application) project_json.pop("studies") form_data = { "dbgap_dar_data": json.dumps([project_json]), @@ -697,9 +687,7 @@ def test_dbgap_dar_data_missing_studies(self): def test_dbgap_dar_data_missing_study_accession(self): """Form is invalid when study_accession is missing from the JSON.""" - project_json = factories.dbGaPJSONProjectFactory.create( - dbgap_application=self.dbgap_application - ) + project_json = factories.dbGaPJSONProjectFactory.create(dbgap_application=self.dbgap_application) project_json["studies"][0].pop("study_accession") form_data = { "dbgap_dar_data": json.dumps([project_json]), @@ -835,9 +823,7 @@ def test_dbgap_dar_data_missing_DAC_abbrev(self): def test_dbgap_project_id_does_not_match(self): """Form is not valid when the dbgap_project_id does not match.""" form_data = { - "dbgap_dar_data": json.dumps( - [factories.dbGaPJSONProjectFactory(Project_id=2)] - ), + "dbgap_dar_data": json.dumps([factories.dbGaPJSONProjectFactory(Project_id=2)]), "dbgap_application": self.dbgap_application, } form = self.form_class(data=form_data) @@ -856,9 +842,7 @@ class dbGaPDataAccessSnapshotMultipleFormTest(TestCase): def test_valid_one_project(self): """Form is valid with necessary input.""" dbgap_application = factories.dbGaPApplicationFactory.create() - project_json = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application - ) + project_json = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application) form_data = { "dbgap_dar_data": json.dumps([project_json]), } @@ -867,13 +851,9 @@ def test_valid_one_project(self): def test_valid_two_projects(self): dbgap_application_1 = factories.dbGaPApplicationFactory.create() - project_json_1 = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application_1 - ) + project_json_1 = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application_1) dbgap_application_2 = factories.dbGaPApplicationFactory.create() - project_json_2 = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application_2 - ) + project_json_2 = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application_2) form_data = { "dbgap_dar_data": json.dumps([project_json_1, project_json_2]), } @@ -1075,9 +1055,7 @@ def test_two_projects_do_not_exist(self): def test_two_projects_one_does_not_exist(self): dbgap_application = factories.dbGaPApplicationFactory.create() - project_json_1 = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application - ) + project_json_1 = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application) project_json_2 = factories.dbGaPJSONProjectFactory() form_data = { "dbgap_dar_data": json.dumps([project_json_1, project_json_2]), diff --git a/primed/dbgap/tests/test_models.py b/primed/dbgap/tests/test_models.py index dc3fa0c9..96377228 100644 --- a/primed/dbgap/tests/test_models.py +++ b/primed/dbgap/tests/test_models.py @@ -62,9 +62,7 @@ def test_unique_dbgap_study_accession(self): instance.full_clean() self.assertIn("dbgap_phs", e.exception.error_dict) self.assertEqual(len(e.exception.error_dict["dbgap_phs"]), 1) - self.assertIn( - "already exists", e.exception.error_dict["dbgap_phs"][0].messages[0] - ) + self.assertIn("already exists", e.exception.error_dict["dbgap_phs"][0].messages[0]) with self.assertRaises(IntegrityError): instance.save() @@ -185,18 +183,14 @@ def test_unique_dbgap_workspace(self): instance.full_clean() self.assertIn("__all__", e.exception.error_dict) self.assertEqual(len(e.exception.error_dict["__all__"]), 1) - self.assertIn( - "already exists", e.exception.error_dict["__all__"][0].messages[0] - ) + self.assertIn("already exists", e.exception.error_dict["__all__"][0].messages[0]) with self.assertRaises(IntegrityError): instance.save() def test_dbgap_study_accession_protect(self): """Cannot delete a dbGaPStudyAccession if it has an associated dbGaPWorkspace.""" dbgap_study_accession = factories.dbGaPStudyAccessionFactory.create() - factories.dbGaPWorkspaceFactory.create( - dbgap_study_accession=dbgap_study_accession - ) + factories.dbGaPWorkspaceFactory.create(dbgap_study_accession=dbgap_study_accession) with self.assertRaises(ProtectedError): dbgap_study_accession.delete() @@ -356,9 +350,7 @@ def test_get_data_access_requests_larger_participant_set(self): def test_get_data_access_requests_smaller_participant_set(self): """Does return a DAR where participant set is smaller than the workspace.""" - workspace = factories.dbGaPWorkspaceFactory.create( - dbgap_version=2, dbgap_participant_set=2 - ) + workspace = factories.dbGaPWorkspaceFactory.create(dbgap_version=2, dbgap_participant_set=2) dar = factories.dbGaPDataAccessRequestFactory.create( dbgap_phs=workspace.dbgap_study_accession.dbgap_phs, original_version=1, @@ -383,9 +375,7 @@ def test_get_data_access_requests_different_consent_code(self): def test_get_data_access_requests_one_application_one_snapshot_one_match(self): """Returns 1 results when there is one matching DARs.""" workspace = factories.dbGaPWorkspaceFactory.create() - dar = factories.dbGaPDataAccessRequestForWorkspaceFactory( - dbgap_workspace=workspace - ) + dar = factories.dbGaPDataAccessRequestForWorkspaceFactory(dbgap_workspace=workspace) results = workspace.get_data_access_requests() self.assertEqual(len(results), 1) self.assertIn(dar, results) @@ -551,9 +541,7 @@ def test_unique_dbgap_application(self): instance.full_clean() self.assertIn("dbgap_project_id", e.exception.error_dict) self.assertEqual(len(e.exception.error_dict["dbgap_project_id"]), 1) - self.assertIn( - "already exists", e.exception.error_dict["dbgap_project_id"][0].messages[0] - ) + self.assertIn("already exists", e.exception.error_dict["dbgap_project_id"][0].messages[0]) with self.assertRaises(IntegrityError): instance.save() @@ -657,9 +645,7 @@ def test_clean_invalid_json(self): def test_dbgap_application_protect(self): """Cannot delete a dbGaPApplication if it has an associated dbGaPDataAccessSnapshot.""" dbgap_application = factories.dbGaPApplicationFactory.create() - factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application - ) + factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application) with self.assertRaises(ProtectedError): dbgap_application.delete() @@ -673,9 +659,7 @@ def test_dbgap_create_dars_from_json_one_study_one_dar(self): DAR=1234, current_DAR_status="approved", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession="phs000421", requests=[dar_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession="phs000421", requests=[dar_json]) project_json = factories.dbGaPJSONProjectFactory(studies=[study_json]) dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( dbgap_application__dbgap_project_id=project_json["Project_id"], @@ -686,9 +670,7 @@ def test_dbgap_create_dars_from_json_one_study_one_dar(self): responses.GET, constants.DBGAP_STUDY_URL, status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18"}, ) dars = dbgap_snapshot.create_dars_from_json() self.assertEqual(len(dars), 1) @@ -722,9 +704,7 @@ def test_dbgap_create_dars_from_json_one_study_two_dars(self): DAR=1235, current_DAR_status="approved", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession="phs000421", requests=[dar_json_1, dar_json_2] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession="phs000421", requests=[dar_json_1, dar_json_2]) project_json = factories.dbGaPJSONProjectFactory(studies=[study_json]) dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( dbgap_application__dbgap_project_id=project_json["Project_id"], @@ -735,9 +715,7 @@ def test_dbgap_create_dars_from_json_one_study_two_dars(self): responses.GET, constants.DBGAP_STUDY_URL, status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18"}, ) dars = dbgap_snapshot.create_dars_from_json() self.assertEqual(len(dars), 2) @@ -783,15 +761,9 @@ def test_dbgap_create_dars_from_json_two_studies_one_dar_each(self): DAR=1235, current_DAR_status="approved", ) - study_json_1 = factories.dbGaPJSONStudyFactory( - study_accession="phs000421", requests=[dar_json_1] - ) - study_json_2 = factories.dbGaPJSONStudyFactory( - study_accession="phs000896", requests=[dar_json_2] - ) - project_json = factories.dbGaPJSONProjectFactory( - studies=[study_json_1, study_json_2] - ) + study_json_1 = factories.dbGaPJSONStudyFactory(study_accession="phs000421", requests=[dar_json_1]) + study_json_2 = factories.dbGaPJSONStudyFactory(study_accession="phs000896", requests=[dar_json_2]) + project_json = factories.dbGaPJSONProjectFactory(studies=[study_json_1, study_json_2]) dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( dbgap_application__dbgap_project_id=project_json["Project_id"], @@ -803,18 +775,14 @@ def test_dbgap_create_dars_from_json_two_studies_one_dar_each(self): constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": "phs000421"})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18"}, ) responses.add( responses.GET, constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": "phs000896"})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=phs000896.v2.p1" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=phs000896.v2.p1"}, ) dars = dbgap_snapshot.create_dars_from_json() self.assertEqual(len(dars), 2) @@ -879,9 +847,7 @@ def test_does_include_dars_that_are_not_approved(self): DAR=1234, current_DAR_status="rejected", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession="phs000421", requests=[dar_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession="phs000421", requests=[dar_json]) project_json = factories.dbGaPJSONProjectFactory(studies=[study_json]) dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( dbgap_application__dbgap_project_id=project_json["Project_id"], @@ -892,9 +858,7 @@ def test_does_include_dars_that_are_not_approved(self): responses.GET, constants.DBGAP_STUDY_URL, status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18"}, ) dars = dbgap_snapshot.create_dars_from_json() self.assertEqual(len(dars), 1) @@ -913,9 +877,7 @@ def test_dbgap_create_dars_updated_dars(self): DAR=1234, current_DAR_status="approved", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession="phs000421", requests=[dar_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession="phs000421", requests=[dar_json]) project_json = factories.dbGaPJSONProjectFactory( Project_id=dbgap_application.dbgap_project_id, studies=[study_json] ) @@ -950,9 +912,7 @@ def test_dbgap_create_dars_updated_dars(self): self.assertEqual(updated_dar.dbgap_data_access_snapshot, second_snapshot) # These should be pulled from the original dar. self.assertEqual(updated_dar.original_version, original_dar.original_version) - self.assertEqual( - updated_dar.original_participant_set, original_dar.original_participant_set - ) + self.assertEqual(updated_dar.original_participant_set, original_dar.original_participant_set) @responses.activate def test_dbgap_create_dars_version_change_between_new_and_approved(self): @@ -965,9 +925,7 @@ def test_dbgap_create_dars_version_change_between_new_and_approved(self): DAR=1234, current_DAR_status="approved", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession="phs000421", requests=[dar_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession="phs000421", requests=[dar_json]) project_json = factories.dbGaPJSONProjectFactory( Project_id=dbgap_application.dbgap_project_id, studies=[study_json] ) @@ -999,9 +957,7 @@ def test_dbgap_create_dars_version_change_between_new_and_approved(self): responses.GET, constants.DBGAP_STUDY_URL, status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v33.p19" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v33.p19"}, ) updated_dars = second_snapshot.create_dars_from_json() self.assertEqual(len(updated_dars), 1) @@ -1025,9 +981,7 @@ def test_created_dars_from_json_assertion_error_phs(self): DAR=1234, current_DAR_status="approved", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession="phs000892", requests=[dar_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession="phs000892", requests=[dar_json]) project_json = factories.dbGaPJSONProjectFactory( Project_id=dbgap_application.dbgap_project_id, studies=[study_json] ) @@ -1069,9 +1023,7 @@ def test_created_dars_from_json_assertion_error_consent_code(self): DAR=1234, current_DAR_status="approved", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession="phs000421", requests=[dar_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession="phs000421", requests=[dar_json]) project_json = factories.dbGaPJSONProjectFactory( Project_id=dbgap_application.dbgap_project_id, studies=[study_json] ) @@ -1113,9 +1065,7 @@ def test_created_dars_from_json_assertion_error_dbgap_project_id(self): DAR=1234, current_DAR_status="approved", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession="phs000421", requests=[dar_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession="phs000421", requests=[dar_json]) project_json = factories.dbGaPJSONProjectFactory( Project_id=dbgap_application.dbgap_project_id, studies=[study_json] ) @@ -1185,9 +1135,7 @@ def test_unique_dbgap_dar_id(self): instance.full_clean() self.assertIn("__all__", e.exception.error_dict) self.assertEqual(len(e.exception.error_dict["__all__"]), 1) - self.assertIn( - "already exists", e.exception.error_dict["__all__"][0].messages[0] - ) + self.assertIn("already exists", e.exception.error_dict["__all__"][0].messages[0]) with self.assertRaises(IntegrityError): instance.save() @@ -1203,9 +1151,7 @@ def test_unique_dbgap_data_access_request(self): instance.full_clean() self.assertIn("__all__", e.exception.error_dict) self.assertEqual(len(e.exception.error_dict["__all__"]), 1) - self.assertIn( - "already exists", e.exception.error_dict["__all__"][0].messages[0] - ) + self.assertIn("already exists", e.exception.error_dict["__all__"][0].messages[0]) with self.assertRaises(IntegrityError): instance.save() @@ -1220,18 +1166,14 @@ def test_unique_dbgap_data_access_dar_id(self): instance.full_clean() self.assertIn("__all__", e.exception.error_dict) self.assertEqual(len(e.exception.error_dict["__all__"]), 1) - self.assertIn( - "already exists", e.exception.error_dict["__all__"][0].messages[0] - ) + self.assertIn("already exists", e.exception.error_dict["__all__"][0].messages[0]) with self.assertRaises(IntegrityError): instance.save() def test_dbgap_data_access_snapshot_protect(self): """Cannot delete a dbGaPApplication if it has an associated dbGaPDataAccessSnapshot.""" dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create() - dar = factories.dbGaPDataAccessRequestFactory.create( - dbgap_data_access_snapshot=dbgap_snapshot - ) + dar = factories.dbGaPDataAccessRequestFactory.create(dbgap_data_access_snapshot=dbgap_snapshot) dbgap_snapshot.delete() with self.assertRaises(models.dbGaPDataAccessSnapshot.DoesNotExist): dbgap_snapshot.refresh_from_db() @@ -1409,9 +1351,7 @@ def test_approved(self): expired_dar = factories.dbGaPDataAccessRequestFactory.create( dbgap_current_status=models.dbGaPDataAccessRequest.EXPIRED ) - new_dar = factories.dbGaPDataAccessRequestFactory.create( - dbgap_current_status=models.dbGaPDataAccessRequest.NEW - ) + new_dar = factories.dbGaPDataAccessRequestFactory.create(dbgap_current_status=models.dbGaPDataAccessRequest.NEW) qs = models.dbGaPDataAccessRequest.objects.approved() self.assertEqual(len(qs), 1) self.assertIn(approved_dar, qs) @@ -1457,9 +1397,7 @@ def test_get_dbgap_workspaces_no_study_accession(self): def test_get_dbgap_workspaces_no_matches(self): """Returns an empty queryset when there is no matching workspace.""" study_accession = factories.dbGaPStudyAccessionFactory.create() - dar = factories.dbGaPDataAccessRequestFactory.create( - dbgap_phs=study_accession.dbgap_phs - ) + dar = factories.dbGaPDataAccessRequestFactory.create(dbgap_phs=study_accession.dbgap_phs) self.assertEqual(dar.get_dbgap_workspaces().count(), 0) def test_get_dbgap_workspaces_one_match(self): @@ -1541,9 +1479,7 @@ def test_get_dbgap_workspaces_smaller_participant_set(self): def test_get_dbgap_workspaces_larger_participant_set(self): """Finds a matching workspace with a larger version and participant set.""" - workspace = factories.dbGaPWorkspaceFactory.create( - dbgap_version=2, dbgap_participant_set=2 - ) + workspace = factories.dbGaPWorkspaceFactory.create(dbgap_version=2, dbgap_participant_set=2) dar = factories.dbGaPDataAccessRequestFactory.create( dbgap_phs=workspace.dbgap_study_accession.dbgap_phs, original_version=1, @@ -1556,9 +1492,7 @@ def test_get_dbgap_workspaces_larger_participant_set(self): def test_get_dbgap_workspaces_different_dbgap_study_accession(self): """Raises ObjectNotFound for workspace with the same phs/version but different phs.""" - workspace = factories.dbGaPWorkspaceFactory.create( - dbgap_study_accession__dbgap_phs=1 - ) + workspace = factories.dbGaPWorkspaceFactory.create(dbgap_study_accession__dbgap_phs=1) dar = factories.dbGaPDataAccessRequestFactory.create( dbgap_phs=2, original_version=workspace.dbgap_version, diff --git a/primed/dbgap/tests/test_tables.py b/primed/dbgap/tests/test_tables.py index 4c901a44..8aeda44b 100644 --- a/primed/dbgap/tests/test_tables.py +++ b/primed/dbgap/tests/test_tables.py @@ -95,13 +95,9 @@ def test_number_workspaces(self): """Table shows correct count for number of workspaces.""" self.model_factory.create() dbgap_study_accession_2 = self.model_factory.create() - factories.dbGaPWorkspaceFactory.create( - dbgap_study_accession=dbgap_study_accession_2 - ) + factories.dbGaPWorkspaceFactory.create(dbgap_study_accession=dbgap_study_accession_2) dbgap_study_accession_3 = self.model_factory.create() - factories.dbGaPWorkspaceFactory.create_batch( - 2, dbgap_study_accession=dbgap_study_accession_3 - ) + factories.dbGaPWorkspaceFactory.create_batch(2, dbgap_study_accession=dbgap_study_accession_3) table = self.table_class(self.model.objects.all()) self.assertEqual(table.rows[0].get_cell("number_workspaces"), 0) self.assertEqual(table.rows[1].get_cell("number_workspaces"), 1) @@ -257,9 +253,7 @@ def test_principal_investigator(self): def test_number_approved_dars_zero(self): """Table shows correct count for number of approved DARs when an application has a snapshot but no DARs.""" dbgap_application = self.model_factory.create() - factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application - ) + factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application) table = self.table_class(self.model.objects.all()) self.assertEqual(table.rows[0].get_cell_value("number_approved_dars"), "0") @@ -272,9 +266,7 @@ def test_number_approved_dars_zero_no_snapshot(self): def test_number_approved_dars_one(self): """Table shows correct count for number of approved DARs when there is one.""" dbgap_application = self.model_factory.create() - dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application - ) + dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application) factories.dbGaPDataAccessRequestFactory.create( dbgap_data_access_snapshot=dbgap_snapshot, dbgap_current_status=models.dbGaPDataAccessRequest.APPROVED, @@ -285,9 +277,7 @@ def test_number_approved_dars_one(self): def test_number_approved_dars_two(self): """Table shows correct count for number of approved DARs when there are two.""" dbgap_application = self.model_factory.create() - dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application - ) + dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application) factories.dbGaPDataAccessRequestFactory.create_batch( 2, dbgap_data_access_snapshot=dbgap_snapshot, @@ -299,9 +289,7 @@ def test_number_approved_dars_two(self): def test_number_approved_dars_other(self): """Number of approved DARs does not include DARs with status that is not "approved".""" dbgap_application = self.model_factory.create() - dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application - ) + dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application) factories.dbGaPDataAccessRequestFactory.create( dbgap_data_access_snapshot=dbgap_snapshot, dbgap_current_status=models.dbGaPDataAccessRequest.APPROVED, @@ -332,18 +320,14 @@ def test_number_approved_dars_other(self): def test_number_approved_dars_two_applications(self): """Number of approved DARs is correct for two applications.""" dbgap_application_1 = self.model_factory.create() - dbgap_snapshot_1 = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application_1 - ) + dbgap_snapshot_1 = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application_1) factories.dbGaPDataAccessRequestFactory.create_batch( 2, dbgap_data_access_snapshot=dbgap_snapshot_1, dbgap_current_status=models.dbGaPDataAccessRequest.APPROVED, ) dbgap_application_2 = self.model_factory.create() - dbgap_snapshot_2 = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application_2 - ) + dbgap_snapshot_2 = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application_2) factories.dbGaPDataAccessRequestFactory.create( dbgap_data_access_snapshot=dbgap_snapshot_2, dbgap_current_status=models.dbGaPDataAccessRequest.APPROVED, @@ -355,9 +339,7 @@ def test_number_approved_dars_two_applications(self): def test_number_requested_dars_zero(self): """Table shows correct count for number of requested DARs when an application has a snapshot but no DARs.""" dbgap_application = self.model_factory.create() - factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application - ) + factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application) table = self.table_class(self.model.objects.all()) self.assertEqual(table.rows[0].get_cell_value("number_requested_dars"), "0") @@ -370,9 +352,7 @@ def test_number_requested_dars_zero_no_snapshot(self): def test_number_requested_dars(self): """Table shows correct count for number of requested DARs when there is one.""" dbgap_application = self.model_factory.create() - dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application - ) + dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application) factories.dbGaPDataAccessRequestFactory.create( dbgap_data_access_snapshot=dbgap_snapshot, dbgap_current_status=models.dbGaPDataAccessRequest.APPROVED, @@ -399,17 +379,13 @@ def test_number_requested_dars(self): def test_number_requested_dars_two_applications(self): """Number of requested DARs is correct for two applications.""" dbgap_application_1 = self.model_factory.create() - dbgap_snapshot_1 = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application_1 - ) + dbgap_snapshot_1 = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application_1) factories.dbGaPDataAccessRequestFactory.create_batch( 2, dbgap_data_access_snapshot=dbgap_snapshot_1, ) dbgap_application_2 = self.model_factory.create() - dbgap_snapshot_2 = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application_2 - ) + dbgap_snapshot_2 = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application_2) factories.dbGaPDataAccessRequestFactory.create( dbgap_data_access_snapshot=dbgap_snapshot_2, ) @@ -426,29 +402,21 @@ def test_last_update_no_snapshot(self): def test_last_update_one_snapshot(self): """Last update shows correct date with one snapshot.""" dbgap_application = self.model_factory.create() - snapshot = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application - ) + snapshot = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application) table = self.table_class(self.model.objects.all()) self.assertIsNotNone(table.rows[0].get_cell_value("last_update")) - self.assertIn( - snapshot.get_absolute_url(), table.rows[0].get_cell("last_update") - ) + self.assertIn(snapshot.get_absolute_url(), table.rows[0].get_cell("last_update")) def test_last_update_two_snapshots(self): """Last update shows correct date with two snapshots.""" dbgap_application = self.model_factory.create() last_month = timezone.now() - timedelta(weeks=4) - factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=dbgap_application, created=last_month - ) + factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=dbgap_application, created=last_month) latest_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( dbgap_application=dbgap_application, created=timezone.now() ) table = self.table_class(self.model.objects.all()) - self.assertIn( - latest_snapshot.get_absolute_url(), table.rows[0].get_cell("last_update") - ) + self.assertIn(latest_snapshot.get_absolute_url(), table.rows[0].get_cell("last_update")) def test_ordering(self): """Instances are ordered alphabetically by dbgap_project_id.""" @@ -582,12 +550,8 @@ def test_row_count_with_two_objects(self): def test_ordering(self): """Instances are ordered alphabetically by dbgap_application and dbgap_dar_id.""" - dbgap_application_1 = factories.dbGaPApplicationFactory.create( - dbgap_project_id=2 - ) - dbgap_application_2 = factories.dbGaPApplicationFactory.create( - dbgap_project_id=1 - ) + dbgap_application_1 = factories.dbGaPApplicationFactory.create(dbgap_project_id=2) + dbgap_application_2 = factories.dbGaPApplicationFactory.create(dbgap_project_id=1) instance_1 = self.model_factory.create( dbgap_dar_id=4, dbgap_data_access_snapshot__dbgap_application=dbgap_application_1, @@ -638,16 +602,12 @@ def test_ordering(self): instance_1 = self.model_factory.create( dbgap_data_access_snapshot=dbgap_snapshot_1, ) - dbgap_snapshot_2 = factories.dbGaPDataAccessSnapshotFactory.create( - created=timezone.now() - timedelta(weeks=4) - ) + dbgap_snapshot_2 = factories.dbGaPDataAccessSnapshotFactory.create(created=timezone.now() - timedelta(weeks=4)) instance_2 = self.model_factory.create( dbgap_dar_id=instance_1.dbgap_dar_id, dbgap_data_access_snapshot=dbgap_snapshot_2, ) - dbgap_snapshot_3 = factories.dbGaPDataAccessSnapshotFactory.create( - created=timezone.now() - timedelta(weeks=3) - ) + dbgap_snapshot_3 = factories.dbGaPDataAccessSnapshotFactory.create(created=timezone.now() - timedelta(weeks=3)) instance_3 = self.model_factory.create( dbgap_dar_id=instance_1.dbgap_dar_id, dbgap_data_access_snapshot=dbgap_snapshot_3, @@ -680,9 +640,7 @@ def test_row_count_with_two_objects(self): def test_one_matching_workspace_with_access(self): """Table works if there is a matching workspace with access.""" workspace = factories.dbGaPWorkspaceFactory.create() - dar = factories.dbGaPDataAccessRequestForWorkspaceFactory( - dbgap_workspace=workspace - ) + dar = factories.dbGaPDataAccessRequestForWorkspaceFactory(dbgap_workspace=workspace) GroupGroupMembershipFactory.create( parent_group=workspace.workspace.authorization_domains.first(), child_group=dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group, @@ -696,9 +654,7 @@ def test_one_matching_workspace_with_access(self): def test_one_matching_workspace_without_access(self): """Table works if there is a matching workspace with access.""" workspace = factories.dbGaPWorkspaceFactory.create() - dar = factories.dbGaPDataAccessRequestForWorkspaceFactory( - dbgap_workspace=workspace - ) + dar = factories.dbGaPDataAccessRequestForWorkspaceFactory(dbgap_workspace=workspace) table = self.table_class([dar]) value = table.render_matching_workspaces(dar.get_dbgap_workspaces(), dar) self.assertIn(workspace.workspace.name, value) @@ -740,15 +696,12 @@ def test_ordering(self): class dbGaPDataAccessRequestSummaryTable(TestCase): - model = models.dbGaPDataAccessRequest model_factory = factories.dbGaPDataAccessRequestFactory table_class = tables.dbGaPDataAccessRequestSummaryTable def annotate(self, qs): - return qs.values("dbgap_dac", "dbgap_current_status").annotate( - total=Count("pk") - ) + return qs.values("dbgap_dac", "dbgap_current_status").annotate(total=Count("pk")) def test_row_count_with_no_objects(self): table = self.table_class(self.annotate(self.model.objects.all())) @@ -760,9 +713,7 @@ def test_row_count_with_one_row(self): self.assertEqual(len(table.rows), 1) def test_row_count_with_two_dacs(self): - self.model_factory.create( - dbgap_dac="FOO", dbgap_current_status=self.model.APPROVED - ) + self.model_factory.create(dbgap_dac="FOO", dbgap_current_status=self.model.APPROVED) self.model_factory.create(dbgap_dac="BAR", dbgap_current_status=self.model.NEW) table = self.table_class(self.annotate(self.model.objects.all())) self.assertEqual(len(table.rows), 2) diff --git a/primed/dbgap/tests/test_views.py b/primed/dbgap/tests/test_views.py index a2ebf614..e668b5f0 100644 --- a/primed/dbgap/tests/test_views.py +++ b/primed/dbgap/tests/test_views.py @@ -52,9 +52,7 @@ class dbGaPResponseTestMixin: def setUp(self): super().setUp() - self.dbgap_response_mock = responses.RequestsMock( - assert_all_requests_are_fired=True - ) + self.dbgap_response_mock = responses.RequestsMock(assert_all_requests_are_fired=True) self.dbgap_response_mock.start() def tearDown(self): @@ -79,9 +77,7 @@ def test_links_for_staff_view(self): """Returns successful response code.""" user = User.objects.create_user(username="test", password="test") user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url()) @@ -90,22 +86,16 @@ def test_links_for_staff_view(self): # Links to add dbGaP info. self.assertNotContains(response, reverse("dbgap:dbgap_study_accessions:new")) self.assertNotContains(response, reverse("dbgap:dbgap_applications:new")) - self.assertNotContains( - response, reverse("dbgap:dbgap_applications:update_dars") - ) + self.assertNotContains(response, reverse("dbgap:dbgap_applications:update_dars")) def test_links_for_staff_edit(self): """Returns successful response code.""" user = User.objects.create_user(username="test", password="test") user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url()) @@ -126,9 +116,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -157,9 +145,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -171,9 +157,7 @@ def test_table_class(self): request.user = self.user response = self.get_view()(request) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.dbGaPStudyAccessionTable - ) + self.assertIsInstance(response.context_data["table"], tables.dbGaPStudyAccessionTable) def test_workspace_table_none(self): """No rows are shown if there are no dbGaPStudyAccession objects.""" @@ -209,9 +193,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) # Create an object test this with. self.obj = factories.dbGaPStudyAccessionFactory.create() @@ -230,9 +212,7 @@ def test_view_redirect_not_logged_in(self): response = self.client.get(self.get_url(self.obj.dbgap_phs)) self.assertRedirects( response, - resolve_url(settings.LOGIN_URL) - + "?next=" - + self.get_url(self.obj.dbgap_phs), + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(self.obj.dbgap_phs), ) def test_status_code_with_user_permission(self): @@ -244,9 +224,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(self.obj.dbgap_phs)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -272,9 +250,7 @@ def test_workspace_table(self): request.user = self.user response = self.get_view()(request, dbgap_phs=self.obj.dbgap_phs) self.assertIn("workspace_table", response.context_data) - self.assertIsInstance( - response.context_data["workspace_table"], tables.dbGaPWorkspaceStaffTable - ) + self.assertIsInstance(response.context_data["workspace_table"], tables.dbGaPWorkspaceStaffTable) def test_workspace_table_none(self): """No workspaces are shown if the dbGaPStudyAccession does not have any workspaces.""" @@ -305,9 +281,7 @@ def test_workspace_table_two(self): def test_shows_workspace_for_only_this_dbGaPStudyAccession(self): """Only shows workspaces for this dbGaPStudyAccession.""" other_dbgap_study_accession = factories.dbGaPStudyAccessionFactory.create() - factories.dbGaPWorkspaceFactory.create( - dbgap_study_accession=other_dbgap_study_accession - ) + factories.dbGaPWorkspaceFactory.create(dbgap_study_accession=other_dbgap_study_accession) request = self.factory.get(self.get_url(self.obj.dbgap_phs)) request.user = self.user response = self.get_view()(request, dbgap_phs=self.obj.dbgap_phs) @@ -317,12 +291,8 @@ def test_shows_workspace_for_only_this_dbGaPStudyAccession(self): def test_context_show_edit_links_with_edit_permission(self): edit_user = User.objects.create_user(username="edit", password="test") edit_user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ), - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ), + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME), + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME), ) self.client.force_login(edit_user) account = factories.dbGaPStudyAccessionFactory.create() @@ -340,9 +310,7 @@ def test_context_show_edit_links_with_edit_permission(self): def test_context_show_edit_links_with_view_permission(self): view_user = User.objects.create_user(username="edit", password="test") view_user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ), + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME), ) self.client.force_login(view_user) account = factories.dbGaPStudyAccessionFactory.create() @@ -368,14 +336,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -390,9 +354,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_with_user_permission_edit(self): """Returns successful response code.""" @@ -403,9 +365,7 @@ def test_status_code_with_user_permission_edit(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -413,13 +373,9 @@ def test_access_without_user_permission(self): def test_access_without_user_permission_view(self): """Raises permission denied if user has no permissions.""" - user_view_perm = User.objects.create_user( - username="test-none", password="test-none" - ) + user_view_perm = User.objects.create_user(username="test-none", password="test-none") user_view_perm.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url()) request.user = user_view_perm @@ -438,17 +394,13 @@ def test_form_class(self): request = self.factory.get(self.get_url()) request.user = self.user response = self.get_view()(request) - self.assertIsInstance( - response.context_data["form"], forms.dbGaPStudyAccessionForm - ) + self.assertIsInstance(response.context_data["form"], forms.dbGaPStudyAccessionForm) def test_can_create_object(self): """Can create an object.""" self.client.force_login(self.user) study = StudyFactory.create() - response = self.client.post( - self.get_url(), {"studies": [study.pk], "dbgap_phs": 1} - ) + response = self.client.post(self.get_url(), {"studies": [study.pk], "dbgap_phs": 1}) self.assertEqual(response.status_code, 302) # A new object was created. self.assertEqual(models.dbGaPStudyAccession.objects.count(), 1) @@ -461,9 +413,7 @@ def test_redirect_url(self): """Redirects to successful url.""" self.client.force_login(self.user) study = StudyFactory.create() - response = self.client.post( - self.get_url(), {"studies": [study.pk], "dbgap_phs": 1} - ) + response = self.client.post(self.get_url(), {"studies": [study.pk], "dbgap_phs": 1}) new_object = models.dbGaPStudyAccession.objects.latest("pk") self.assertRedirects(response, new_object.get_absolute_url()) @@ -479,9 +429,7 @@ def test_success_message(self): self.assertIn("messages", response.context) messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) - self.assertEqual( - views.dbGaPStudyAccessionCreate.success_message, str(messages[0]) - ) + self.assertEqual(views.dbGaPStudyAccessionCreate.success_message, str(messages[0])) def test_error_missing_studies(self): """Form shows an error when studies is missing.""" @@ -565,14 +513,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -586,9 +530,7 @@ def get_view(self): def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." response = self.client.get(self.get_url(1)) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1) - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1)) def test_status_code_with_user_permission(self): """Returns successful response code.""" @@ -600,13 +542,9 @@ def test_status_code_with_user_permission(self): def test_access_with_view_permission(self): """Raises permission denied if user has only view permission.""" instance = factories.dbGaPStudyAccessionFactory.create() - user_with_view_perm = User.objects.create_user( - username="test-other", password="test-other" - ) + user_with_view_perm = User.objects.create_user(username="test-other", password="test-other") user_with_view_perm.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url(instance.dbgap_phs)) request.user = user_with_view_perm @@ -616,9 +554,7 @@ def test_access_with_view_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" instance = factories.dbGaPStudyAccessionFactory.create() - user_no_perms = User.objects.create_user( - username="test-other", password="test-other" - ) + user_no_perms = User.objects.create_user(username="test-other", password="test-other") request = self.factory.get(self.get_url(instance.dbgap_phs)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -644,9 +580,7 @@ def test_can_modify_studies(self): study_2 = StudyFactory.create() instance = factories.dbGaPStudyAccessionFactory.create(studies=[study_1]) self.client.force_login(self.user) - response = self.client.post( - self.get_url(instance.dbgap_phs), {"studies": [study_1.pk, study_2.pk]} - ) + response = self.client.post(self.get_url(instance.dbgap_phs), {"studies": [study_1.pk, study_2.pk]}) self.assertEqual(response.status_code, 302) instance.refresh_from_db() self.assertEqual(instance.studies.count(), 2) @@ -656,13 +590,9 @@ def test_can_modify_studies(self): def test_does_not_modify_phs(self): """Does not modify phs when updating a dbGaPStudyAccession.""" study = StudyFactory.create() - instance = factories.dbGaPStudyAccessionFactory.create( - dbgap_phs=1234, studies=[study] - ) + instance = factories.dbGaPStudyAccessionFactory.create(dbgap_phs=1234, studies=[study]) self.client.force_login(self.user) - response = self.client.post( - self.get_url(instance.dbgap_phs), {"dbgap_phs": 2345, "studies": [study.pk]} - ) + response = self.client.post(self.get_url(instance.dbgap_phs), {"dbgap_phs": 2345, "studies": [study.pk]}) self.assertEqual(response.status_code, 302) instance.refresh_from_db() self.assertEqual(instance.dbgap_phs, 1234) @@ -670,30 +600,20 @@ def test_does_not_modify_phs(self): def test_success_message(self): """Response includes a success message if successful.""" study = StudyFactory.create() - instance = factories.dbGaPStudyAccessionFactory.create( - dbgap_phs=1234, studies=[study] - ) + instance = factories.dbGaPStudyAccessionFactory.create(dbgap_phs=1234, studies=[study]) self.client.force_login(self.user) - response = self.client.post( - self.get_url(instance.dbgap_phs), {"studies": [study.pk]}, follow=True - ) + response = self.client.post(self.get_url(instance.dbgap_phs), {"studies": [study.pk]}, follow=True) self.assertIn("messages", response.context) messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) - self.assertEqual( - views.dbGaPStudyAccessionUpdate.success_message, str(messages[0]) - ) + self.assertEqual(views.dbGaPStudyAccessionUpdate.success_message, str(messages[0])) def test_redirects_to_object_detail(self): """After successfully creating an object, view redirects to the object's detail page.""" study = StudyFactory.create() - instance = factories.dbGaPStudyAccessionFactory.create( - dbgap_phs=1234, studies=[study] - ) + instance = factories.dbGaPStudyAccessionFactory.create(dbgap_phs=1234, studies=[study]) self.client.force_login(self.user) - response = self.client.post( - self.get_url(instance.dbgap_phs), {"studies": [study.pk]} - ) + response = self.client.post(self.get_url(instance.dbgap_phs), {"studies": [study.pk]}) self.assertRedirects(response, instance.get_absolute_url()) @@ -704,9 +624,7 @@ def setUp(self): # Create a user with the correct permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -721,9 +639,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_with_user_permission(self): """Returns successful response code.""" @@ -733,9 +649,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -746,14 +660,9 @@ def test_returns_all_objects(self): objects = factories.dbGaPStudyAccessionFactory.create_batch(10) self.client.force_login(self.user) response = self.client.get(self.get_url()) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 10) - self.assertEqual( - sorted(returned_ids), sorted([object.pk for object in objects]) - ) + self.assertEqual(sorted(returned_ids), sorted([object.pk for object in objects])) def test_returns_correct_object_match(self): """Queryset returns the correct objects when query matches the phs.""" @@ -761,10 +670,7 @@ def test_returns_correct_object_match(self): factories.dbGaPStudyAccessionFactory.create(dbgap_phs=8) self.client.force_login(self.user) response = self.client.get(self.get_url(), {"q": "7"}) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) @@ -774,10 +680,7 @@ def test_returns_correct_object_starting_with_query(self): factories.dbGaPStudyAccessionFactory.create(dbgap_phs=8) self.client.force_login(self.user) response = self.client.get(self.get_url(), {"q": "7"}) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) @@ -787,10 +690,7 @@ def test_returns_correct_object_containing_query(self): factories.dbGaPStudyAccessionFactory.create(dbgap_phs=754) self.client.force_login(self.user) response = self.client.get(self.get_url(), {"q": "6"}) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) @@ -799,10 +699,7 @@ def test_ignores_phs_in_query(self): object = factories.dbGaPStudyAccessionFactory.create(dbgap_phs=7) self.client.force_login(self.user) response = self.client.get(self.get_url(), {"q": "phs7"}) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) @@ -811,10 +708,7 @@ def test_removes_leading_zeros(self): object = factories.dbGaPStudyAccessionFactory.create(dbgap_phs=7) self.client.force_login(self.user) response = self.client.get(self.get_url(), {"q": "0007"}) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) @@ -824,10 +718,7 @@ def test_does_not_remove_trailing_zeros(self): factories.dbGaPStudyAccessionFactory.create(dbgap_phs=71) self.client.force_login(self.user) response = self.client.get(self.get_url(), {"q": "700"}) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) @@ -841,9 +732,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.workspace_type = "dbgap" @@ -861,9 +750,7 @@ def test_view_has_correct_table_class(self): request.user = self.user response = self.get_view()(request, workspace_type=self.workspace_type) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.dbGaPWorkspaceStaffTable - ) + self.assertIsInstance(response.context_data["table"], tables.dbGaPWorkspaceStaffTable) class dbGaPWorkspaceDetailTest(TestCase): @@ -874,9 +761,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def test_status_code_with_user_permission(self): @@ -920,9 +805,7 @@ def test_response_contains_dbgap_link(self): def test_links_audit_access_staff_view(self): user = UserFactory.create() user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) obj = factories.dbGaPWorkspaceFactory.create() self.client.force_login(user) @@ -937,11 +820,7 @@ def test_links_audit_access_staff_view(self): def test_links_audit_access_view_permission(self): user = UserFactory.create() - user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) - ) + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) obj = factories.dbGaPWorkspaceFactory.create() self.client.force_login(user) response = self.client.get(obj.get_absolute_url()) @@ -956,11 +835,7 @@ 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 - ) - ) + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) obj = factories.dbGaPWorkspaceFactory.create() DataPrepWorkspaceFactory.create(target_workspace=obj.workspace) @@ -988,15 +863,11 @@ def test_associated_data_prep_workspaces_context_exists(self): def test_only_show_one_associated_data_prep_workspace(self): dbGaP_obj = factories.dbGaPWorkspaceFactory.create() - dataPrep_obj = DataPrepWorkspaceFactory.create( - target_workspace=dbGaP_obj.workspace - ) + dataPrep_obj = DataPrepWorkspaceFactory.create(target_workspace=dbGaP_obj.workspace) self.client.force_login(self.user) response = self.client.get(dbGaP_obj.get_absolute_url()) self.assertIn("associated_data_prep_workspaces", response.context_data) - self.assertEqual( - len(response.context_data["associated_data_prep_workspaces"].rows), 1 - ) + self.assertEqual(len(response.context_data["associated_data_prep_workspaces"].rows), 1) self.assertIn( dataPrep_obj.workspace, response.context_data["associated_data_prep_workspaces"].data, @@ -1004,18 +875,12 @@ def test_only_show_one_associated_data_prep_workspace(self): def test_show_two_associated_data_prep_workspaces(self): dbGaP_obj = factories.dbGaPWorkspaceFactory.create() - dataPrep_obj1 = DataPrepWorkspaceFactory.create( - target_workspace=dbGaP_obj.workspace - ) - dataPrep_obj2 = DataPrepWorkspaceFactory.create( - target_workspace=dbGaP_obj.workspace - ) + dataPrep_obj1 = DataPrepWorkspaceFactory.create(target_workspace=dbGaP_obj.workspace) + dataPrep_obj2 = DataPrepWorkspaceFactory.create(target_workspace=dbGaP_obj.workspace) self.client.force_login(self.user) response = self.client.get(dbGaP_obj.get_absolute_url()) self.assertIn("associated_data_prep_workspaces", response.context_data) - self.assertEqual( - len(response.context_data["associated_data_prep_workspaces"].rows), 2 - ) + self.assertEqual(len(response.context_data["associated_data_prep_workspaces"].rows), 2) self.assertIn( dataPrep_obj1.workspace, response.context_data["associated_data_prep_workspaces"].data, @@ -1034,9 +899,7 @@ def test_context_data_prep_active_with_no_prep_workspace(self): def test_context_data_prep_active_with_one_inactive_prep_workspace(self): instance = factories.dbGaPWorkspaceFactory.create() - DataPrepWorkspaceFactory.create( - target_workspace=instance.workspace, is_active=False - ) + DataPrepWorkspaceFactory.create(target_workspace=instance.workspace, is_active=False) self.client.force_login(self.user) response = self.client.get(instance.get_absolute_url()) self.assertIn("data_prep_active", response.context_data) @@ -1044,9 +907,7 @@ def test_context_data_prep_active_with_one_inactive_prep_workspace(self): def test_context_data_prep_active_with_one_active_prep_workspace(self): instance = factories.dbGaPWorkspaceFactory.create() - DataPrepWorkspaceFactory.create( - target_workspace=instance.workspace, is_active=True - ) + DataPrepWorkspaceFactory.create(target_workspace=instance.workspace, is_active=True) self.client.force_login(self.user) response = self.client.get(instance.get_absolute_url()) self.assertIn("data_prep_active", response.context_data) @@ -1054,12 +915,8 @@ def test_context_data_prep_active_with_one_active_prep_workspace(self): def test_context_data_prep_active_with_one_active_one_inactive_prep_workspace(self): instance = factories.dbGaPWorkspaceFactory.create() - DataPrepWorkspaceFactory.create( - target_workspace=instance.workspace, is_active=True - ) - DataPrepWorkspaceFactory.create( - target_workspace=instance.workspace, is_active=True - ) + DataPrepWorkspaceFactory.create(target_workspace=instance.workspace, is_active=True) + DataPrepWorkspaceFactory.create(target_workspace=instance.workspace, is_active=True) self.client.force_login(self.user) response = self.client.get(instance.get_absolute_url()) self.assertIn("data_prep_active", response.context_data) @@ -1078,14 +935,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.requester = UserFactory.create() self.workspace_type = "dbgap" @@ -1096,13 +949,7 @@ def get_url(self, *args): def get_api_url(self, billing_project_name, workspace_name): """Return the Terra API url for a given billing project and workspace.""" - return ( - self.api_client.rawls_entry_point - + "/api/workspaces/" - + billing_project_name - + "/" - + workspace_name - ) + return self.api_client.rawls_entry_point + "/api/workspaces/" + billing_project_name + "/" + workspace_name def test_creates_upload_workspace_without_duos(self): """Posting valid data to the form creates a workspace data object when using a custom adapter.""" @@ -1151,9 +998,7 @@ def test_creates_upload_workspace_without_duos(self): self.assertEqual(models.dbGaPWorkspace.objects.count(), 1) new_workspace_data = models.dbGaPWorkspace.objects.latest("pk") self.assertEqual(new_workspace_data.workspace, new_workspace) - self.assertEqual( - new_workspace_data.dbgap_study_accession, dbgap_study_accession - ) + self.assertEqual(new_workspace_data.dbgap_study_accession, dbgap_study_accession) self.assertEqual(new_workspace_data.dbgap_version, 2) self.assertEqual(new_workspace_data.dbgap_participant_set, 3) self.assertEqual(new_workspace_data.dbgap_consent_abbreviation, "GRU-TEST") @@ -1230,14 +1075,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.requester = UserFactory.create() self.workspace_type = "dbgap" @@ -1248,25 +1089,15 @@ def get_url(self, *args): def get_api_url(self, billing_project_name, workspace_name): """Return the Terra API url for a given billing project and workspace.""" - return ( - self.api_client.rawls_entry_point - + "/api/workspaces/" - + billing_project_name - + "/" - + workspace_name - ) + return self.api_client.rawls_entry_point + "/api/workspaces/" + billing_project_name + "/" + workspace_name - def get_api_json_response( - self, billing_project, workspace, authorization_domains=[], access="OWNER" - ): + def get_api_json_response(self, billing_project, workspace, authorization_domains=[], access="OWNER"): """Return a pared down version of the json response from the AnVIL API with only fields we need.""" json_data = { "accessLevel": access, "owners": [], "workspace": { - "authorizationDomain": [ - {"membersGroupName": x} for x in authorization_domains - ], + "authorizationDomain": [{"membersGroupName": x} for x in authorization_domains], "name": workspace, "namespace": billing_project, "isLocked": False, @@ -1287,9 +1118,7 @@ def test_creates_dbgap_workspace_without_duos(self): responses.GET, workspace_list_url, match=[ - responses.matchers.query_param_matcher( - {"fields": "workspace.namespace,workspace.name,accessLevel"} - ) + responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"}) ], status=200, json=[self.get_api_json_response(billing_project.name, workspace_name)], @@ -1353,9 +1182,7 @@ def test_creates_dbgap_workspace_without_duos(self): self.assertEqual(models.dbGaPWorkspace.objects.count(), 1) new_workspace_data = models.dbGaPWorkspace.objects.latest("pk") self.assertEqual(new_workspace_data.workspace, new_workspace) - self.assertEqual( - new_workspace_data.dbgap_study_accession, dbgap_study_accession - ) + self.assertEqual(new_workspace_data.dbgap_study_accession, dbgap_study_accession) self.assertEqual(new_workspace_data.dbgap_version, 2) self.assertEqual(new_workspace_data.dbgap_participant_set, 3) self.assertEqual(new_workspace_data.dbgap_consent_abbreviation, "GRU-TEST") @@ -1380,9 +1207,7 @@ def test_creates_dbgap_workspace_with_duos(self): responses.GET, workspace_list_url, match=[ - responses.matchers.query_param_matcher( - {"fields": "workspace.namespace,workspace.name,accessLevel"} - ) + responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"}) ], status=200, json=[self.get_api_json_response(billing_project.name, workspace_name)], @@ -1461,9 +1286,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -1492,9 +1315,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -1506,9 +1327,7 @@ def test_table_class(self): request.user = self.user response = self.get_view()(request) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.dbGaPApplicationTable - ) + self.assertIsInstance(response.context_data["table"], tables.dbGaPApplicationTable) class dbGaPApplicationDetailTest(TestCase): @@ -1518,9 +1337,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) # Create an object test this with. self.obj = factories.dbGaPApplicationFactory.create() @@ -1539,9 +1356,7 @@ def test_view_redirect_not_logged_in(self): response = self.client.get(self.get_url(self.obj.dbgap_project_id)) self.assertRedirects( response, - resolve_url(settings.LOGIN_URL) - + "?next=" - + self.get_url(self.obj.dbgap_project_id), + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(self.obj.dbgap_project_id), ) def test_status_code_with_user_permission(self): @@ -1553,9 +1368,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(self.obj.dbgap_project_id)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -1577,9 +1390,7 @@ def test_view_status_code_with_invalid_pk(self): def test_staff_edit_links(self): self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.client.force_login(self.user) response = self.client.get(self.get_url(self.obj.dbgap_project_id)) @@ -1629,9 +1440,7 @@ def test_snapshot_table_none(self): request.user = self.user response = self.get_view()(request, dbgap_project_id=self.obj.dbgap_project_id) self.assertIn("data_access_snapshot_table", response.context_data) - self.assertEqual( - len(response.context_data["data_access_snapshot_table"].rows), 0 - ) + self.assertEqual(len(response.context_data["data_access_snapshot_table"].rows), 0) def test_snapshot_table_one(self): """One snapshots is shown if the dbGaPApplication has one snapshots.""" @@ -1640,37 +1449,27 @@ def test_snapshot_table_one(self): request.user = self.user response = self.get_view()(request, dbgap_project_id=self.obj.dbgap_project_id) self.assertIn("data_access_snapshot_table", response.context_data) - self.assertEqual( - len(response.context_data["data_access_snapshot_table"].rows), 1 - ) + self.assertEqual(len(response.context_data["data_access_snapshot_table"].rows), 1) def test_snapshot_table_two(self): """Two snapshots are shown if the dbGaPApplication has two snapshots.""" - factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=self.obj, is_most_recent=False - ) + factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=self.obj, is_most_recent=False) factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=self.obj) request = self.factory.get(self.get_url(self.obj.dbgap_project_id)) request.user = self.user response = self.get_view()(request, dbgap_project_id=self.obj.dbgap_project_id) self.assertIn("data_access_snapshot_table", response.context_data) - self.assertEqual( - len(response.context_data["data_access_snapshot_table"].rows), 2 - ) + self.assertEqual(len(response.context_data["data_access_snapshot_table"].rows), 2) def test_shows_snapshots_for_only_this_application(self): """Only shows snapshots for this dbGaPApplication.""" other_dbgap_application = factories.dbGaPApplicationFactory.create() - factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=other_dbgap_application - ) + factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=other_dbgap_application) request = self.factory.get(self.get_url(self.obj.dbgap_project_id)) request.user = self.user response = self.get_view()(request, dbgap_project_id=self.obj.dbgap_project_id) self.assertIn("data_access_snapshot_table", response.context_data) - self.assertEqual( - len(response.context_data["data_access_snapshot_table"].rows), 0 - ) + self.assertEqual(len(response.context_data["data_access_snapshot_table"].rows), 0) def test_context_latest_snapshot_no_snapshot(self): """latest_snapshot is None in context when there are no dbGaPDataAccessSnapshots for this application.""" @@ -1682,9 +1481,7 @@ def test_context_latest_snapshot_no_snapshot(self): def test_context_latest_snapshot_one_snapshot(self): """latest_snapshot is correct in context when there is one dbGaPDataAccessSnapshot for this application.""" - dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=self.obj - ) + dbgap_snapshot = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=self.obj) request = self.factory.get(self.get_url(self.obj.dbgap_project_id)) request.user = self.user response = self.get_view()(request, dbgap_project_id=self.obj.dbgap_project_id) @@ -1714,9 +1511,7 @@ def test_table_default_ordering(self): created=timezone.now() - timedelta(weeks=4), is_most_recent=False, ) - snapshot_2 = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=self.obj, created=timezone.now() - ) + snapshot_2 = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=self.obj, created=timezone.now()) request = self.factory.get(self.get_url(self.obj.dbgap_project_id)) request.user = self.user response = self.get_view()(request, dbgap_project_id=self.obj.dbgap_project_id) @@ -1736,14 +1531,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) # Create the admin group. self.cc_admin_group = ManagedGroupFactory.create(name="TEST_PRIMED_CC_ADMINS") @@ -1760,9 +1551,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_with_user_permission_edit(self): """Returns successful response code.""" @@ -1773,9 +1562,7 @@ def test_status_code_with_user_permission_edit(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -1783,13 +1570,9 @@ def test_access_without_user_permission(self): def test_access_without_user_permission_view(self): """Raises permission denied if user has no permissions.""" - user_view_perm = User.objects.create_user( - username="test-none", password="test-none" - ) + user_view_perm = User.objects.create_user(username="test-none", password="test-none") user_view_perm.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url()) request.user = user_view_perm @@ -1815,13 +1598,8 @@ def test_can_create_object(self): self.client.force_login(self.user) pi = UserFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_1" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_1" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -1829,9 +1607,7 @@ def test_can_create_object(self): + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_1/admin/TEST_PRIMED_CC_ADMINS@firecloud.org", status=204, ) - response = self.client.post( - self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 1} - ) + response = self.client.post(self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 1}) self.assertEqual(response.status_code, 302) # A new object was created. self.assertEqual(models.dbGaPApplication.objects.count(), 1) @@ -1844,22 +1620,15 @@ def test_redirect_url(self): self.client.force_login(self.user) pi = UserFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_1" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_1" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, api_url + "/admin/TEST_PRIMED_CC_ADMINS@firecloud.org", status=204, ) - response = self.client.post( - self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 1} - ) + response = self.client.post(self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 1}) new_object = models.dbGaPApplication.objects.latest("pk") self.assertRedirects(response, new_object.get_absolute_url()) @@ -1868,13 +1637,8 @@ def test_success_message(self): self.client.force_login(self.user) pi = UserFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_1" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_1" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -1989,22 +1753,15 @@ def test_creates_anvil_access_group(self): self.client.force_login(self.user) pi = UserFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_12498" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_12498" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, api_url + "/admin/TEST_PRIMED_CC_ADMINS@firecloud.org", status=204, ) - response = self.client.post( - self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 12498} - ) + response = self.client.post(self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 12498}) self.assertEqual(response.status_code, 302) new_object = models.dbGaPApplication.objects.latest("pk") # A new group was created. @@ -2027,21 +1784,15 @@ def test_creates_anvil_access_group_different_setting_data_access_group_prefix( self.client.force_login(self.user) pi = UserFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point + "/api/groups/v1/foo_DBGAP_ACCESS_12498" - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/foo_DBGAP_ACCESS_12498" # CC admins group membership. self.anvil_response_mock.add( responses.PUT, api_url + "/admin/TEST_PRIMED_CC_ADMINS@firecloud.org", status=204, ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) - response = self.client.post( - self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 12498} - ) + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) + response = self.client.post(self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 12498}) self.assertEqual(response.status_code, 302) new_object = models.dbGaPApplication.objects.latest("pk") # A new group was created. @@ -2063,22 +1814,15 @@ def test_creates_anvil_access_group_different_setting_cc_admin_group(self): self.client.force_login(self.user) pi = UserFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_12498" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "mock message"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_12498" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "mock message"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, api_url + "/admin/foo@firecloud.org", status=204, ) - response = self.client.post( - self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 12498} - ) + response = self.client.post(self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 12498}) self.assertEqual(response.status_code, 302) new_object = models.dbGaPApplication.objects.latest("pk") # A new group was created. @@ -2098,16 +1842,9 @@ def test_manage_group_create_api_error(self): self.client.force_login(self.user) pi = UserFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_1" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=500, json={"message": "other error"} - ) - response = self.client.post( - self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 1} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_1" + self.anvil_response_mock.add(responses.POST, api_url, status=500, json={"message": "other error"}) + response = self.client.post(self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 1}) self.assertEqual(response.status_code, 200) # The form is valid... form = response.context["form"] @@ -2126,9 +1863,7 @@ def test_managed_group_already_exists_in_app(self): pi = UserFactory.create() # Create a group with the same name. ManagedGroupFactory.create(name="TEST_PRIMED_DBGAP_ACCESS_1") - response = self.client.post( - self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 1} - ) + response = self.client.post(self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 1}) self.assertEqual(response.status_code, 200) # The form is valid... form = response.context["form"] @@ -2136,9 +1871,7 @@ def test_managed_group_already_exists_in_app(self): # ...but there was an error with the group name. messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) - self.assertEqual( - views.dbGaPApplicationCreate.ERROR_CREATING_GROUP, str(messages[0]) - ) + self.assertEqual(views.dbGaPApplicationCreate.ERROR_CREATING_GROUP, str(messages[0])) # No dbGaPApplication was created. self.assertEqual(models.dbGaPApplication.objects.count(), 0) @@ -2147,13 +1880,8 @@ def test_admin_group_membership_api_error(self): self.client.force_login(self.user) pi = UserFactory.create() # API response to create the associated anvil_access_group. - api_url = ( - self.api_client.sam_entry_point - + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_1" - ) - self.anvil_response_mock.add( - responses.POST, api_url, status=201, json={"message": "other error"} - ) + api_url = self.api_client.sam_entry_point + "/api/groups/v1/TEST_PRIMED_DBGAP_ACCESS_1" + self.anvil_response_mock.add(responses.POST, api_url, status=201, json={"message": "other error"}) # CC admins group membership. self.anvil_response_mock.add( responses.PUT, @@ -2161,9 +1889,7 @@ def test_admin_group_membership_api_error(self): status=404, json={"message": "other error"}, ) - response = self.client.post( - self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 1} - ) + response = self.client.post(self.get_url(), {"principal_investigator": pi.pk, "dbgap_project_id": 1}) self.assertEqual(response.status_code, 200) # The form is valid... form = response.context["form"] @@ -2188,14 +1914,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.dbgap_application = factories.dbGaPApplicationFactory.create() self.pi_name = fake.name() @@ -2214,99 +1936,63 @@ def get_view(self): def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. - response = self.client.get( - self.get_url(self.dbgap_application.dbgap_project_id) - ) + response = self.client.get(self.get_url(self.dbgap_application.dbgap_project_id)) self.assertRedirects( response, - resolve_url(settings.LOGIN_URL) - + "?next=" - + self.get_url(self.dbgap_application.dbgap_project_id), + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(self.dbgap_application.dbgap_project_id), ) def test_status_code_with_user_permission_edit(self): """Returns successful response code.""" - request = self.factory.get( - self.get_url(self.dbgap_application.dbgap_project_id) - ) + request = self.factory.get(self.get_url(self.dbgap_application.dbgap_project_id)) request.user = self.user - response = self.get_view()( - request, dbgap_project_id=self.dbgap_application.dbgap_project_id - ) + response = self.get_view()(request, dbgap_project_id=self.dbgap_application.dbgap_project_id) self.assertEqual(response.status_code, 200) def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) - request = self.factory.get( - self.get_url(self.dbgap_application.dbgap_project_id) - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url(self.dbgap_application.dbgap_project_id)) request.user = user_no_perms with self.assertRaises(PermissionDenied): - self.get_view()( - request, dbgap_project_id=self.dbgap_application.dbgap_project_id - ) + self.get_view()(request, dbgap_project_id=self.dbgap_application.dbgap_project_id) def test_access_without_user_permission_view(self): """Raises permission denied if user has no permissions.""" - user_view_perm = User.objects.create_user( - username="test-none", password="test-none" - ) + user_view_perm = User.objects.create_user(username="test-none", password="test-none") user_view_perm.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) - ) - request = self.factory.get( - self.get_url(self.dbgap_application.dbgap_project_id) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) + request = self.factory.get(self.get_url(self.dbgap_application.dbgap_project_id)) request.user = user_view_perm with self.assertRaises(PermissionDenied): - self.get_view()( - request, dbgap_project_id=self.dbgap_application.dbgap_project_id - ) + self.get_view()(request, dbgap_project_id=self.dbgap_application.dbgap_project_id) def test_has_form_in_context(self): """Response includes a form.""" - request = self.factory.get( - self.get_url(self.dbgap_application.dbgap_project_id) - ) + request = self.factory.get(self.get_url(self.dbgap_application.dbgap_project_id)) request.user = self.user - response = self.get_view()( - request, dbgap_project_id=self.dbgap_application.dbgap_project_id - ) + response = self.get_view()(request, dbgap_project_id=self.dbgap_application.dbgap_project_id) self.assertTrue("form" in response.context_data) def test_form_class(self): """Form is the expected class.""" - request = self.factory.get( - self.get_url(self.dbgap_application.dbgap_project_id) - ) + request = self.factory.get(self.get_url(self.dbgap_application.dbgap_project_id)) request.user = self.user - response = self.get_view()( - request, dbgap_project_id=self.dbgap_application.dbgap_project_id - ) - self.assertIsInstance( - response.context_data["form"], forms.dbGaPDataAccessSnapshotForm - ) + response = self.get_view()(request, dbgap_project_id=self.dbgap_application.dbgap_project_id) + self.assertIsInstance(response.context_data["form"], forms.dbGaPDataAccessSnapshotForm) def test_can_create_object(self): """Can create a dbGaPSnapshot and related dbGaPDataAccessRequests for this dbGaPApplication.""" phs = "phs{phs:06d}".format(phs=fake.random_int()) study_json = factories.dbGaPJSONStudyFactory(study_accession=phs) - project_json = factories.dbGaPJSONProjectFactory( - dbgap_application=self.dbgap_application, studies=[study_json] - ) + project_json = factories.dbGaPJSONProjectFactory(dbgap_application=self.dbgap_application, studies=[study_json]) self.dbgap_response_mock.add( responses.GET, constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v1.p1" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v1.p1"}, ) self.client.force_login(self.user) response = self.client.post( @@ -2336,21 +2022,15 @@ def test_can_create_object_and_related_dars(self): current_DAR_status="approved", was_approved="yes", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession="phs000421", requests=[dar_json] - ) - project_json = factories.dbGaPJSONProjectFactory( - dbgap_application=self.dbgap_application, studies=[study_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession="phs000421", requests=[dar_json]) + project_json = factories.dbGaPJSONProjectFactory(dbgap_application=self.dbgap_application, studies=[study_json]) # Add responses with the study version and participant_set. self.dbgap_response_mock.add( responses.GET, constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": "phs000421"})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18"}, ) self.client.force_login(self.user) response = self.client.post( @@ -2393,10 +2073,7 @@ def test_redirect_url(self): constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL - + "?study_id={}.v32.p18".format(phs) - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id={}.v32.p18".format(phs)}, ) self.client.force_login(self.user) response = self.client.post( @@ -2422,10 +2099,7 @@ def test_success_message(self): constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL - + "?study_id={}.v32.p18".format(phs) - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id={}.v32.p18".format(phs)}, ) self.client.force_login(self.user) response = self.client.post( @@ -2440,9 +2114,7 @@ def test_success_message(self): self.assertIn("messages", response.context) messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) - self.assertEqual( - views.dbGaPDataAccessSnapshotCreate.success_message, str(messages[0]) - ) + self.assertEqual(views.dbGaPDataAccessSnapshotCreate.success_message, str(messages[0])) def test_error_missing_json(self): """Form shows an error when dbgap_dar_data is missing.""" @@ -2491,9 +2163,7 @@ def test_has_form_when_one_snapshot_exists(self): current_DAR_status="approved", DAC_abbrev="FOOBAR", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession=phs, requests=[request_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession=phs, requests=[request_json]) project_json = factories.dbGaPJSONProjectFactory( dbgap_application=self.dbgap_application, studies=[study_json], @@ -2517,9 +2187,7 @@ def test_has_form_when_one_snapshot_exists(self): ) # Now try to load the page again. self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.dbgap_application.dbgap_project_id) - ) + response = self.client.get(self.get_url(self.dbgap_application.dbgap_project_id)) self.assertEqual(response.status_code, 200) self.assertTrue("form" in response.context_data) @@ -2534,9 +2202,7 @@ def test_updates_existing_snapshot_is_most_recent(self): current_DAR_status="approved", DAC_abbrev="FOOBAR", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession=phs, requests=[request_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession=phs, requests=[request_json]) project_json = factories.dbGaPJSONProjectFactory( dbgap_application=self.dbgap_application, studies=[study_json], @@ -2589,9 +2255,7 @@ def test_can_add_a_second_snapshot_with_dars(self): current_DAR_status="approved", DAC_abbrev="FOOBAR", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession=phs, requests=[request_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession=phs, requests=[request_json]) project_json = factories.dbGaPJSONProjectFactory( dbgap_application=self.dbgap_application, studies=[study_json], @@ -2638,9 +2302,7 @@ def test_can_add_a_second_snapshot_with_dars(self): self.assertEqual(new_dar.dbgap_current_status, "approved") # These should be obtained from the original dar. self.assertEqual(new_dar.original_version, original_dar.original_version) - self.assertEqual( - new_dar.original_participant_set, original_dar.original_participant_set - ) + self.assertEqual(new_dar.original_participant_set, original_dar.original_participant_set) def test_post_invalid_json(self): """JSON is invalid.""" @@ -2667,9 +2329,7 @@ def test_post_invalid_json(self): def test_json_project_id_does_not_match(self): """Error message when project_id in JSON does not match project_id in dbGaPApplication.""" - project_json = factories.dbGaPJSONProjectFactory( - Project_id=self.dbgap_application.dbgap_project_id + 1 - ) + project_json = factories.dbGaPJSONProjectFactory(Project_id=self.dbgap_application.dbgap_project_id + 1) self.client.force_login(self.user) response = self.client.post( self.get_url(self.dbgap_application.dbgap_project_id), @@ -2695,17 +2355,11 @@ def test_json_project_id_does_not_match(self): def test_context_includes_dbgap_application(self): """Response context data includes the dbGaP application.""" - request = self.factory.get( - self.get_url(self.dbgap_application.dbgap_project_id) - ) + request = self.factory.get(self.get_url(self.dbgap_application.dbgap_project_id)) request.user = self.user - response = self.get_view()( - request, dbgap_project_id=self.dbgap_application.dbgap_project_id - ) + response = self.get_view()(request, dbgap_project_id=self.dbgap_application.dbgap_project_id) self.assertTrue("dbgap_application" in response.context_data) - self.assertEqual( - response.context_data["dbgap_application"], self.dbgap_application - ) + self.assertEqual(response.context_data["dbgap_application"], self.dbgap_application) def test_snapshot_not_created_if_http404(self): """The dbGaPDataAccessSnapshot is not created if DARs cannot be created due to a HTTP 404 response.""" @@ -2718,9 +2372,7 @@ def test_snapshot_not_created_if_http404(self): current_DAR_status="approved", DAC_abbrev="FOOBAR", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession=phs, requests=[request_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession=phs, requests=[request_json]) project_json = factories.dbGaPJSONProjectFactory( dbgap_application=self.dbgap_application, studies=[study_json], @@ -2749,9 +2401,7 @@ def test_snapshot_not_created_if_http404(self): self.assertIn("messages", response.context) messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) - self.assertEqual( - views.dbGaPDataAccessSnapshotCreate.ERROR_CREATING_DARS, str(messages[0]) - ) + self.assertEqual(views.dbGaPDataAccessSnapshotCreate.ERROR_CREATING_DARS, str(messages[0])) def test_existing_snapshot_not_updated_http404(self): """The dbGaPDataAccessSnapshot is not created if there is an HTTP 404 error.""" @@ -2764,9 +2414,7 @@ def test_existing_snapshot_not_updated_http404(self): current_DAR_status="approved", DAC_abbrev="FOOBAR", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession=phs, requests=[request_json] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession=phs, requests=[request_json]) project_json = factories.dbGaPJSONProjectFactory( dbgap_application=self.dbgap_application, studies=[study_json], @@ -2816,9 +2464,7 @@ def test_snapshot_not_created_if_dar_error(self): current_DAR_status="approved", DAC_abbrev="FOOBAR", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession=phs, requests=[request_json_1, request_json_2] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession=phs, requests=[request_json_1, request_json_2]) project_json = factories.dbGaPJSONProjectFactory( dbgap_application=self.dbgap_application, studies=[study_json], @@ -2829,9 +2475,7 @@ def test_snapshot_not_created_if_dar_error(self): constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18"}, ) self.client.force_login(self.user) response = self.client.post( @@ -2864,9 +2508,7 @@ def test_existing_snapshot_is_most_recent_with_dar_errors(self): current_DAR_status="approved", DAC_abbrev="FOOBAR", ) - study_json = factories.dbGaPJSONStudyFactory( - study_accession=phs, requests=[request_json_1, request_json_2] - ) + study_json = factories.dbGaPJSONStudyFactory(study_accession=phs, requests=[request_json_1, request_json_2]) project_json = factories.dbGaPJSONProjectFactory( dbgap_application=self.dbgap_application, studies=[study_json], @@ -2877,9 +2519,7 @@ def test_existing_snapshot_is_most_recent_with_dar_errors(self): constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=phs000421.v32.p18"}, ) # Create an existing snapshot. existing_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( @@ -2914,14 +2554,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) def tearDown(self): @@ -2956,9 +2592,7 @@ def test_status_code_with_user_permission_edit(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -2966,13 +2600,9 @@ def test_access_without_user_permission(self): def test_access_without_user_permission_view(self): """Raises permission denied if user has no permissions.""" - user_view_perm = User.objects.create_user( - username="test-none", password="test-none" - ) + user_view_perm = User.objects.create_user(username="test-none", password="test-none") user_view_perm.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url()) request.user = user_view_perm @@ -2989,9 +2619,7 @@ def test_form_class(self): """Form is the expected class.""" self.client.force_login(self.user) response = self.client.get(self.get_url()) - self.assertIsInstance( - response.context_data["form"], forms.dbGaPDataAccessSnapshotMultipleForm - ) + self.assertIsInstance(response.context_data["form"], forms.dbGaPDataAccessSnapshotMultipleForm) def test_context_dbgap_dar_json_url(self): """Response context includes dbgap_dar_json_url.""" @@ -3001,23 +2629,17 @@ def test_context_dbgap_dar_json_url(self): def test_updates_one_application(self): dbgap_application = factories.dbGaPApplicationFactory.create() - project_json = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application - ) + project_json = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application) phs = project_json["studies"][0]["study_accession"] self.dbgap_response_mock.add( responses.GET, constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v1.p1" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v1.p1"}, ) self.client.force_login(self.user) - response = self.client.post( - self.get_url(), {"dbgap_dar_data": json.dumps([project_json])} - ) + response = self.client.post(self.get_url(), {"dbgap_dar_data": json.dumps([project_json])}) self.assertEqual(response.status_code, 302) dbgap_application.refresh_from_db() self.assertEqual(dbgap_application.dbgapdataaccesssnapshot_set.count(), 1) @@ -3027,33 +2649,25 @@ def test_updates_one_application(self): def test_updates_two_applications(self): # First application and associated JSON. dbgap_application_1 = factories.dbGaPApplicationFactory.create() - project_json_1 = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application_1 - ) + project_json_1 = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application_1) phs_1 = project_json_1["studies"][0]["study_accession"] self.dbgap_response_mock.add( responses.GET, constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs_1})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs_1 + ".v1.p1" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs_1 + ".v1.p1"}, ) # Second application and associated JSON. dbgap_application_2 = factories.dbGaPApplicationFactory.create() - project_json_2 = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application_2 - ) + project_json_2 = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application_2) phs_2 = project_json_2["studies"][0]["study_accession"] self.dbgap_response_mock.add( responses.GET, constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs_2})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs_2 + ".v2.p2" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs_2 + ".v2.p2"}, ) self.client.force_login(self.user) response = self.client.post( @@ -3075,45 +2689,33 @@ def test_updates_two_applications(self): def test_redirect_url(self): """Redirects to successful url.""" dbgap_application = factories.dbGaPApplicationFactory.create() - project_json = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application - ) + project_json = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application) phs = project_json["studies"][0]["study_accession"] self.dbgap_response_mock.add( responses.GET, constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v1.p1" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v1.p1"}, ) self.client.force_login(self.user) - response = self.client.post( - self.get_url(), {"dbgap_dar_data": json.dumps([project_json])} - ) + response = self.client.post(self.get_url(), {"dbgap_dar_data": json.dumps([project_json])}) self.assertRedirects(response, reverse("dbgap:dbgap_applications:list")) def test_success_message(self): """Redirects to successful url.""" dbgap_application = factories.dbGaPApplicationFactory.create() - project_json = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application - ) + project_json = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application) phs = project_json["studies"][0]["study_accession"] self.dbgap_response_mock.add( responses.GET, constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v1.p1" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v1.p1"}, ) self.client.force_login(self.user) - response = self.client.post( - self.get_url(), {"dbgap_dar_data": json.dumps([project_json])}, follow=True - ) + response = self.client.post(self.get_url(), {"dbgap_dar_data": json.dumps([project_json])}, follow=True) self.assertIn("messages", response.context) messages = list(response.context["messages"]) self.assertEqual(len(messages), 1) @@ -3142,9 +2744,7 @@ def test_dbgap_application_does_not_exist(self): """Shows an error when the dbGaP application does not exist.""" project_json = factories.dbGaPJSONProjectFactory() self.client.force_login(self.user) - response = self.client.post( - self.get_url(), {"dbgap_dar_data": json.dumps([project_json])} - ) + response = self.client.post(self.get_url(), {"dbgap_dar_data": json.dumps([project_json])}) self.assertEqual(response.status_code, 200) # Form has errors in the correct field. self.assertIn("form", response.context_data) @@ -3161,9 +2761,7 @@ def test_dbgap_application_does_not_exist(self): def test_second_dbgap_application_does_not_exist(self): """Shows an error when one dbGaP application does not exist.""" dbgap_application_1 = factories.dbGaPApplicationFactory.create() - project_json_1 = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application_1 - ) + project_json_1 = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application_1) project_json_2 = factories.dbGaPJSONProjectFactory() self.client.force_login(self.user) response = self.client.post( @@ -3186,9 +2784,7 @@ def test_second_dbgap_application_does_not_exist(self): def test_updates_existing_snapshot_is_most_recent(self): """Updates the is_most_recent for older snapshots.""" dbgap_application = factories.dbGaPApplicationFactory.create() - project_json = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application - ) + project_json = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application) phs = project_json["studies"][0]["study_accession"] existing_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( dbgap_application=dbgap_application, @@ -3201,15 +2797,11 @@ def test_updates_existing_snapshot_is_most_recent(self): constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v1.p1" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v1.p1"}, ) # Now add a new snapshot. self.client.force_login(self.user) - response = self.client.post( - self.get_url(), {"dbgap_dar_data": json.dumps([project_json])} - ) + response = self.client.post(self.get_url(), {"dbgap_dar_data": json.dumps([project_json])}) self.client.force_login(self.user) self.assertEqual(response.status_code, 302) self.assertEqual(models.dbGaPDataAccessSnapshot.objects.count(), 2) @@ -3224,9 +2816,7 @@ def test_updates_existing_snapshot_is_most_recent(self): def test_can_add_a_second_snapshot_with_dars(self): """Can add a second snapshot and new DARs.""" dbgap_application = factories.dbGaPApplicationFactory.create() - project_json = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application - ) + project_json = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application) phs = project_json["studies"][0]["study_accession"] factories.dbGaPDataAccessSnapshotFactory.create( dbgap_application=dbgap_application, @@ -3239,15 +2829,11 @@ def test_can_add_a_second_snapshot_with_dars(self): constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v1.p1" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v1.p1"}, ) # Now add a new snapshot. self.client.force_login(self.user) - response = self.client.post( - self.get_url(), {"dbgap_dar_data": json.dumps([project_json])} - ) + response = self.client.post(self.get_url(), {"dbgap_dar_data": json.dumps([project_json])}) self.assertEqual(response.status_code, 302) self.assertEqual(models.dbGaPDataAccessSnapshot.objects.count(), 2) new_snapshot = models.dbGaPDataAccessSnapshot.objects.latest("pk") @@ -3277,9 +2863,7 @@ def test_post_invalid_json(self): def test_snapshot_not_created_if_http404(self): """The dbGaPDataAccessSnapshot is not created if DARs cannot be created due to a HTTP 404 response.""" dbgap_application = factories.dbGaPApplicationFactory.create() - project_json = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application - ) + project_json = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application) phs = project_json["studies"][0]["study_accession"] # Add responses with the study version and participant_set. self.dbgap_response_mock.add( @@ -3314,9 +2898,7 @@ def test_snapshot_not_created_if_http404(self): def test_existing_snapshot_not_updated_http404(self): """The dbGaPDataAccessSnapshot is not created if there is an HTTP 404 error.""" dbgap_application = factories.dbGaPApplicationFactory.create() - project_json = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application - ) + project_json = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application) phs = project_json["studies"][0]["study_accession"] existing_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( dbgap_application=dbgap_application, @@ -3359,12 +2941,8 @@ def test_snapshot_not_created_if_dar_validation_error(self): dbgap_application = factories.dbGaPApplicationFactory.create() request_json_1 = factories.dbGaPJSONRequestFactory(DAR=12345) request_json_2 = factories.dbGaPJSONRequestFactory(DAR=12345) - study_json = factories.dbGaPJSONStudyFactory( - requests=[request_json_1, request_json_2] - ) - project_json = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application, studies=[study_json] - ) + study_json = factories.dbGaPJSONStudyFactory(requests=[request_json_1, request_json_2]) + project_json = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application, studies=[study_json]) phs = project_json["studies"][0]["study_accession"] # Add responses with the study version and participant_set. self.dbgap_response_mock.add( @@ -3372,9 +2950,7 @@ def test_snapshot_not_created_if_dar_validation_error(self): constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v32.p18" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v32.p18"}, ) self.client.force_login(self.user) response = self.client.post( @@ -3404,12 +2980,8 @@ def test_existing_snapshot_is_most_recent_with_dar_validation_error(self): dbgap_application = factories.dbGaPApplicationFactory.create() request_json_1 = factories.dbGaPJSONRequestFactory(DAR=12345) request_json_2 = factories.dbGaPJSONRequestFactory(DAR=12345) - study_json = factories.dbGaPJSONStudyFactory( - requests=[request_json_1, request_json_2] - ) - project_json = factories.dbGaPJSONProjectFactory( - dbgap_application=dbgap_application, studies=[study_json] - ) + study_json = factories.dbGaPJSONStudyFactory(requests=[request_json_1, request_json_2]) + project_json = factories.dbGaPJSONProjectFactory(dbgap_application=dbgap_application, studies=[study_json]) phs = project_json["studies"][0]["study_accession"] existing_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( @@ -3423,9 +2995,7 @@ def test_existing_snapshot_is_most_recent_with_dar_validation_error(self): constants.DBGAP_STUDY_URL, match=[responses.matchers.query_param_matcher({"study_id": phs})], status=302, - headers={ - "Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v32.p18" - }, + headers={"Location": constants.DBGAP_STUDY_URL + "?study_id=" + phs + ".v32.p18"}, ) self.client.force_login(self.user) response = self.client.post( @@ -3461,14 +3031,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.application = factories.dbGaPApplicationFactory.create() - self.snapshot = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=self.application - ) + self.snapshot = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=self.application) def tearDown(self): super().tearDown() @@ -3489,9 +3055,7 @@ def get_view(self): def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. - response = self.client.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) + response = self.client.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) self.assertRedirects( response, resolve_url(settings.LOGIN_URL) @@ -3501,9 +3065,7 @@ def test_view_redirect_not_logged_in(self): def test_status_code_with_user_permission_view(self): """Returns successful response code if the user has view permission.""" - request = self.factory.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) + request = self.factory.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) request.user = self.user response = self.get_view()( request, @@ -3514,12 +3076,8 @@ def test_status_code_with_user_permission_view(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) - request = self.factory.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") + request = self.factory.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) request.user = user_no_perms with self.assertRaises(PermissionDenied): self.get_view()( @@ -3530,9 +3088,7 @@ def test_access_without_user_permission(self): def test_invalid_dbgap_application_pk(self): """Raises a 404 error with an invalid object dbgap_application_pk.""" - request = self.factory.get( - self.get_url(self.application.dbgap_project_id + 1, self.snapshot.pk) - ) + request = self.factory.get(self.get_url(self.application.dbgap_project_id + 1, self.snapshot.pk)) request.user = self.user with self.assertRaises(Http404): self.get_view()( @@ -3543,9 +3099,7 @@ def test_invalid_dbgap_application_pk(self): def test_invalid_dbgap_data_access_snapshot_pk(self): """Raises a 404 error with an invalid object dbgap_data_access_snapshot_pk.""" - request = self.factory.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk + 1) - ) + request = self.factory.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk + 1)) request.user = self.user with self.assertRaises(Http404): self.get_view()( @@ -3557,9 +3111,7 @@ def test_invalid_dbgap_data_access_snapshot_pk(self): def test_mismatch_application_snapshot(self): """Raises a 404 error when dbgap application and snapshot pk don't match.""" other_snapshot = factories.dbGaPDataAccessSnapshotFactory.create() - request = self.factory.get( - self.get_url(self.application.dbgap_project_id, other_snapshot.pk) - ) + request = self.factory.get(self.get_url(self.application.dbgap_project_id, other_snapshot.pk)) request.user = self.user with self.assertRaises(Http404): self.get_view()( @@ -3571,9 +3123,7 @@ def test_mismatch_application_snapshot(self): def test_context_dar_table(self): """The data_access_request_table exists in the context.""" self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) + response = self.client.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) self.assertIn("data_access_request_table", response.context_data) self.assertIsInstance( response.context_data["data_access_request_table"], @@ -3583,75 +3133,45 @@ def test_context_dar_table(self): def test_context_dar_table_none(self): """The data_access_request_table works with one DAR.""" self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) + response = self.client.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) self.assertIn("data_access_request_table", response.context_data) - self.assertEqual( - len(response.context_data["data_access_request_table"].rows), 0 - ) + self.assertEqual(len(response.context_data["data_access_request_table"].rows), 0) def test_context_dar_table_one(self): """The data_access_request_table works with one DAR.""" - dar = factories.dbGaPDataAccessRequestFactory.create( - dbgap_data_access_snapshot=self.snapshot - ) + dar = factories.dbGaPDataAccessRequestFactory.create(dbgap_data_access_snapshot=self.snapshot) self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) + response = self.client.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) self.assertIn("data_access_request_table", response.context_data) - self.assertEqual( - len(response.context_data["data_access_request_table"].rows), 1 - ) + self.assertEqual(len(response.context_data["data_access_request_table"].rows), 1) self.assertIn(dar, response.context_data["data_access_request_table"].data) def test_context_dar_table_two(self): """The data_access_request_table works with one DAR.""" - dars = factories.dbGaPDataAccessRequestFactory.create_batch( - 2, dbgap_data_access_snapshot=self.snapshot - ) + dars = factories.dbGaPDataAccessRequestFactory.create_batch(2, dbgap_data_access_snapshot=self.snapshot) self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) + response = self.client.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) self.assertIn("data_access_request_table", response.context_data) - self.assertEqual( - len(response.context_data["data_access_request_table"].rows), 2 - ) + self.assertEqual(len(response.context_data["data_access_request_table"].rows), 2) self.assertIn(dars[0], response.context_data["data_access_request_table"].data) self.assertIn(dars[1], response.context_data["data_access_request_table"].data) def test_context_dar_table_only_shows_dars_for_this_snapshot(self): """The data_access_request_table only shows DARs associated with this snapshot.""" - dar = factories.dbGaPDataAccessRequestFactory.create( - dbgap_data_access_snapshot=self.snapshot - ) - other_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=self.application - ) - other_dar = factories.dbGaPDataAccessRequestFactory.create( - dbgap_data_access_snapshot=other_snapshot - ) + dar = factories.dbGaPDataAccessRequestFactory.create(dbgap_data_access_snapshot=self.snapshot) + other_snapshot = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=self.application) + other_dar = factories.dbGaPDataAccessRequestFactory.create(dbgap_data_access_snapshot=other_snapshot) self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) + response = self.client.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) self.assertIn("data_access_request_table", response.context_data) - self.assertEqual( - len(response.context_data["data_access_request_table"].rows), 1 - ) + self.assertEqual(len(response.context_data["data_access_request_table"].rows), 1) self.assertIn(dar, response.context_data["data_access_request_table"].data) - self.assertNotIn( - other_dar, response.context_data["data_access_request_table"].data - ) + self.assertNotIn(other_dar, response.context_data["data_access_request_table"].data) def test_context_summary_table(self): """The data_access_request_table exists in the context.""" self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) + response = self.client.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) self.assertIn("summary_table", response.context_data) self.assertIsInstance( response.context_data["summary_table"], @@ -3661,9 +3181,7 @@ def test_context_summary_table(self): def test_context_summary_table_none(self): """The data_access_request_table works with no DARs.""" self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) + response = self.client.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) self.assertIn("summary_table", response.context_data) self.assertEqual(len(response.context_data["summary_table"].rows), 0) @@ -3697,9 +3215,7 @@ def test_context_summary_table_contents(self): dbgap_current_status=models.dbGaPDataAccessRequest.NEW, ) self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) + response = self.client.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) self.assertIn("summary_table", response.context_data) table = response.context_data["summary_table"] @@ -3733,18 +3249,14 @@ def test_context_summary_table_contents(self): def test_context_summary_table_only_shows_dars_for_this_snapshot(self): """The data_access_request_table only shows DARs associated with this snapshot.""" - other_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=self.application - ) + other_snapshot = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=self.application) factories.dbGaPDataAccessRequestFactory.create( dbgap_data_access_snapshot=other_snapshot, dbgap_current_status=models.dbGaPDataAccessRequest.APPROVED, dbgap_dac="FOO", ) self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) + response = self.client.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) self.assertIn("summary_table", response.context_data) table = response.context_data["summary_table"] self.assertEqual(len(table.rows), 0) @@ -3752,12 +3264,8 @@ def test_context_summary_table_only_shows_dars_for_this_snapshot(self): def test_no_alert_for_most_recent_snapshot(self): """No alert is shown when this is the most recent snapshot for an application.""" self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.application.dbgap_project_id, self.snapshot.pk) - ) - self.assertNotContains( - response, "not the most recent snapshot", status_code=200 - ) + response = self.client.get(self.get_url(self.application.dbgap_project_id, self.snapshot.pk)) + self.assertNotContains(response, "not the most recent snapshot", status_code=200) def test_alert_when_not_most_recent_snapshot(self): """An alert is shown when this is not the most recent snapshot for an application.""" @@ -3767,9 +3275,7 @@ def test_alert_when_not_most_recent_snapshot(self): is_most_recent=False, ) self.client.force_login(self.user) - response = self.client.get( - self.get_url(self.application.dbgap_project_id, old_snapshot.pk) - ) + response = self.client.get(self.get_url(self.application.dbgap_project_id, old_snapshot.pk)) self.assertContains(response, "not the most recent snapshot", status_code=200) @@ -3780,9 +3286,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -3811,9 +3315,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -3824,9 +3326,7 @@ def test_table_class(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.dbGaPDataAccessRequestTable - ) + self.assertIsInstance(response.context_data["table"], tables.dbGaPDataAccessRequestTable) def test_one_dar(self): """Table displays one dar.""" @@ -3845,9 +3345,7 @@ def test_two_dars(self): self.assertIn(dar_2, response.context_data["table"].data) def test_only_current_dars_shown(self): - dar = factories.dbGaPDataAccessRequestFactory.create( - dbgap_data_access_snapshot__is_most_recent=False - ) + dar = factories.dbGaPDataAccessRequestFactory.create(dbgap_data_access_snapshot__is_most_recent=False) self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertNotIn(dar, response.context_data["table"].data) @@ -3863,9 +3361,7 @@ def test_export(self): self.assertIn("Content-Type", response) self.assertEqual(response["Content-Type"], "text/tsv; charset=utf-8") self.assertIn("Content-Disposition", response) - self.assertEqual( - response["Content-Disposition"], 'attachment; filename="dars_table.tsv"' - ) + self.assertEqual(response["Content-Disposition"], 'attachment; filename="dars_table.tsv"') class dbGaPDataAccessRequestHistoryTest(TestCase): @@ -3875,9 +3371,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -3906,9 +3400,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -3927,9 +3419,7 @@ def test_table_class(self): self.client.force_login(self.user) response = self.client.get(self.get_url(instance.dbgap_dar_id)) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.dbGaPDataAccessRequestHistoryTable - ) + self.assertIsInstance(response.context_data["table"], tables.dbGaPDataAccessRequestHistoryTable) def test_one_dars(self): """Table displays two dars.""" @@ -3965,16 +3455,12 @@ def test_table_ordering(self): created=timezone.now() - timedelta(weeks=5), is_most_recent=False, ) - dar_1 = factories.dbGaPDataAccessRequestFactory.create( - dbgap_dar_id=1, dbgap_data_access_snapshot=old_snapshot - ) + dar_1 = factories.dbGaPDataAccessRequestFactory.create(dbgap_dar_id=1, dbgap_data_access_snapshot=old_snapshot) new_snapshot = factories.dbGaPDataAccessSnapshotFactory.create( created=timezone.now() - timedelta(weeks=1), is_most_recent=True, ) - dar_2 = factories.dbGaPDataAccessRequestFactory.create( - dbgap_dar_id=1, dbgap_data_access_snapshot=new_snapshot - ) + dar_2 = factories.dbGaPDataAccessRequestFactory.create(dbgap_dar_id=1, dbgap_data_access_snapshot=new_snapshot) self.client.force_login(self.user) response = self.client.get(self.get_url(1)) self.assertEqual(len(response.context_data["table"].rows), 2) @@ -3991,14 +3477,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.application = factories.dbGaPApplicationFactory.create() - self.snapshot = factories.dbGaPDataAccessSnapshotFactory.create( - dbgap_application=self.application - ) + self.snapshot = factories.dbGaPDataAccessSnapshotFactory.create(dbgap_application=self.application) def get_url(self, *args): """Get the url for the view being tested.""" @@ -4017,25 +3499,19 @@ def test_view_redirect_not_logged_in(self): response = self.client.get(self.get_url(self.application.dbgap_project_id)) self.assertRedirects( response, - resolve_url(settings.LOGIN_URL) - + "?next=" - + self.get_url(self.application.dbgap_project_id), + resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(self.application.dbgap_project_id), ) def test_status_code_with_user_permission_view(self): """Returns successful response code if the user has view permission.""" request = self.factory.get(self.get_url(self.application.dbgap_project_id)) request.user = self.user - response = self.get_view()( - request, dbgap_project_id=self.application.dbgap_project_id - ) + response = self.get_view()(request, dbgap_project_id=self.application.dbgap_project_id) self.assertEqual(response.status_code, 200) def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(self.application.dbgap_project_id)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -4068,16 +3544,12 @@ def test_context_data_access_audit_does_not_include_other_applications(self): response = self.client.get(self.get_url(self.application.dbgap_project_id)) data_access_audit = response.context_data["data_access_audit"] self.assertEqual(data_access_audit.dbgap_application_queryset.count(), 1) - self.assertNotIn( - other_application, data_access_audit.dbgap_application_queryset - ) + self.assertNotIn(other_application, data_access_audit.dbgap_application_queryset) def test_context_verified_table_access(self): """verified_table shows a record when audit has verified access.""" # Add a verified workspace. - workspace = factories.dbGaPWorkspaceFactory.create( - dbgap_study_accession__dbgap_phs=1 - ) + workspace = factories.dbGaPWorkspaceFactory.create(dbgap_study_accession__dbgap_phs=1) dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( dbgap_data_access_snapshot=self.snapshot, dbgap_workspace=workspace ) @@ -4126,9 +3598,7 @@ def test_context_verified_table_no_access(self): def test_context_needs_action_table_grant(self): """needs_action_table shows a record when audit finds that access needs to be granted.""" - workspace = factories.dbGaPWorkspaceFactory.create( - created=timezone.now() - timedelta(weeks=4) - ) + workspace = factories.dbGaPWorkspaceFactory.create(created=timezone.now() - timedelta(weeks=4)) dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( dbgap_data_access_snapshot=self.snapshot, dbgap_workspace=workspace ) @@ -4262,9 +3732,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.dbgap_workspace = factories.dbGaPWorkspaceFactory.create() @@ -4306,9 +3774,7 @@ def test_status_code_with_user_permission_view(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get( self.get_url( self.dbgap_workspace.workspace.billing_project.name, @@ -4325,9 +3791,7 @@ def test_access_without_user_permission(self): def test_invalid_billing_project_name(self): """Raises a 404 error with an invalid object dbgap_application_pk.""" - request = self.factory.get( - self.get_url("foo", self.dbgap_workspace.workspace.name) - ) + request = self.factory.get(self.get_url("foo", self.dbgap_workspace.workspace.name)) request.user = self.user with self.assertRaises(Http404): self.get_view()( @@ -4338,9 +3802,7 @@ def test_invalid_billing_project_name(self): def test_invalid_workspace_name(self): """Raises a 404 error with an invalid object dbgap_application_pk.""" - request = self.factory.get( - self.get_url(self.dbgap_workspace.workspace.name, "foo") - ) + request = self.factory.get(self.get_url(self.dbgap_workspace.workspace.name, "foo")) request.user = self.user with self.assertRaises(Http404): self.get_view()( @@ -4385,9 +3847,7 @@ def test_context_data_access_audit_does_not_include_other_applications(self): def test_context_verified_table_access(self): """verified_table shows a record when audit has verified access.""" # Add a verified workspace. - dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=self.dbgap_workspace - ) + dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=self.dbgap_workspace) GroupGroupMembershipFactory.create( parent_group=self.dbgap_workspace.workspace.authorization_domains.first(), child_group=dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group, @@ -4407,9 +3867,7 @@ def test_context_verified_table_access(self): audit.dbGaPAccessAuditTable, ) self.assertEqual(len(table.rows), 1) - self.assertEqual( - table.rows[0].get_cell_value("workspace"), self.dbgap_workspace - ) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.dbgap_workspace) self.assertEqual(table.rows[0].get_cell_value("data_access_request"), dar) self.assertEqual( table.rows[0].get_cell_value("note"), @@ -4435,9 +3893,7 @@ def test_context_verified_table_no_snapshot(self): audit.dbGaPAccessAuditTable, ) self.assertEqual(len(table.rows), 1) - self.assertEqual( - table.rows[0].get_cell_value("workspace"), self.dbgap_workspace - ) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.dbgap_workspace) self.assertIsNone(table.rows[0].get_cell_value("data_access_request")) self.assertEqual( table.rows[0].get_cell_value("note"), @@ -4463,9 +3919,7 @@ def test_context_verified_table_no_dar(self): audit.dbGaPAccessAuditTable, ) self.assertEqual(len(table.rows), 1) - self.assertEqual( - table.rows[0].get_cell_value("workspace"), self.dbgap_workspace - ) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.dbgap_workspace) self.assertIsNone(table.rows[0].get_cell_value("data_access_request")) self.assertEqual( table.rows[0].get_cell_value("note"), @@ -4495,9 +3949,7 @@ def test_context_verified_table_other_dar(self): audit.dbGaPAccessAuditTable, ) self.assertEqual(len(table.rows), 1) - self.assertEqual( - table.rows[0].get_cell_value("workspace"), self.dbgap_workspace - ) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.dbgap_workspace) self.assertIsNone(table.rows[0].get_cell_value("data_access_request")) self.assertEqual( table.rows[0].get_cell_value("note"), @@ -4526,9 +3978,7 @@ def test_context_verified_table_dar_not_approved(self): audit.dbGaPAccessAuditTable, ) self.assertEqual(len(table.rows), 1) - self.assertEqual( - table.rows[0].get_cell_value("workspace"), self.dbgap_workspace - ) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.dbgap_workspace) self.assertEqual(table.rows[0].get_cell_value("data_access_request"), dar) self.assertEqual( table.rows[0].get_cell_value("note"), @@ -4538,9 +3988,7 @@ def test_context_verified_table_dar_not_approved(self): def test_context_needs_action_table_grant(self): """needs_action_table shows a record when audit finds that access needs to be granted.""" - dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=self.dbgap_workspace - ) + dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=self.dbgap_workspace) # Check the table in the context. self.client.force_login(self.user) response = self.client.get( @@ -4556,9 +4004,7 @@ def test_context_needs_action_table_grant(self): audit.dbGaPAccessAuditTable, ) self.assertEqual(len(table.rows), 1) - self.assertEqual( - table.rows[0].get_cell_value("workspace"), self.dbgap_workspace - ) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.dbgap_workspace) self.assertEqual(table.rows[0].get_cell_value("data_access_request"), dar) self.assertEqual( table.rows[0].get_cell_value("note"), @@ -4603,9 +4049,7 @@ def test_context_needs_action_table_remove(self): audit.dbGaPAccessAuditTable, ) self.assertEqual(len(table.rows), 1) - self.assertEqual( - table.rows[0].get_cell_value("workspace"), self.dbgap_workspace - ) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.dbgap_workspace) self.assertEqual(table.rows[0].get_cell_value("data_access_request"), dar) self.assertEqual( table.rows[0].get_cell_value("note"), @@ -4640,9 +4084,7 @@ def test_context_error_table_has_access(self): audit.dbGaPAccessAuditTable, ) self.assertEqual(len(table.rows), 1) - self.assertEqual( - table.rows[0].get_cell_value("workspace"), self.dbgap_workspace - ) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.dbgap_workspace) self.assertEqual(table.rows[0].get_cell_value("data_access_request"), dar) self.assertEqual( table.rows[0].get_cell_value("note"), @@ -4673,9 +4115,7 @@ def test_context_errors_table_no_snapshot_has_access(self): audit.dbGaPAccessAuditTable, ) self.assertEqual(len(table.rows), 1) - self.assertEqual( - table.rows[0].get_cell_value("workspace"), self.dbgap_workspace - ) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.dbgap_workspace) self.assertIsNone(table.rows[0].get_cell_value("data_access_request")) self.assertEqual( table.rows[0].get_cell_value("note"), @@ -4706,9 +4146,7 @@ def test_context_errors_table_no_dar_has_access(self): audit.dbGaPAccessAuditTable, ) self.assertEqual(len(table.rows), 1) - self.assertEqual( - table.rows[0].get_cell_value("workspace"), self.dbgap_workspace - ) + self.assertEqual(table.rows[0].get_cell_value("workspace"), self.dbgap_workspace) self.assertIsNone(table.rows[0].get_cell_value("data_access_request")) self.assertEqual( table.rows[0].get_cell_value("note"), @@ -4726,9 +4164,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -4760,11 +4196,7 @@ def test_status_code_with_user_permission_staff_view(self): def test_status_code_with_user_permission_view(self): """Returns successful response code if the user has view permission.""" user = User.objects.create_user(username="test-none", password="test-none") - user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) - ) + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) request = self.factory.get(self.get_url()) request.user = user with self.assertRaises(PermissionDenied): @@ -4772,9 +4204,7 @@ def test_status_code_with_user_permission_view(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -4854,9 +4284,7 @@ def test_context_verified_table_access(self): """verified_table shows a record when audit has verified access.""" # Add a verified workspace. workspace = factories.dbGaPWorkspaceFactory.create() - dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=workspace - ) + dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=workspace) GroupGroupMembershipFactory.create( parent_group=workspace.workspace.authorization_domains.first(), child_group=dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group, @@ -4893,9 +4321,7 @@ def test_context_verified_table_no_access(self): audit.dbGaPAccessAuditTable, ) self.assertEqual(len(table.rows), 1) - self.assertEqual( - table.rows[0].get_cell_value("application"), snapshot.dbgap_application - ) + self.assertEqual(table.rows[0].get_cell_value("application"), snapshot.dbgap_application) self.assertEqual(table.rows[0].get_cell_value("workspace"), workspace) self.assertIsNone(table.rows[0].get_cell_value("data_access_request")) self.assertEqual( @@ -4906,12 +4332,8 @@ def test_context_verified_table_no_access(self): def test_context_needs_action_table_grant(self): """needs_action_table shows a record when audit finds that access needs to be granted.""" - workspace = factories.dbGaPWorkspaceFactory.create( - created=timezone.now() - timedelta(weeks=4) - ) - dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=workspace - ) + workspace = factories.dbGaPWorkspaceFactory.create(created=timezone.now() - timedelta(weeks=4)) + dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=workspace) # Check the table in the context. self.client.force_login(self.user) response = self.client.get(self.get_url()) @@ -5018,14 +4440,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -5066,9 +4484,7 @@ def test_status_code_with_user_permission_staff_view(self): """Returns 403 response code if the user has staff view permission.""" user_view = User.objects.create_user(username="test-view", password="test-view") user_view.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.client.force_login(self.user) request = self.factory.get(self.get_url(1, "foo", "bar")) @@ -5079,11 +4495,7 @@ def test_status_code_with_user_permission_staff_view(self): def test_status_code_with_user_permission_view(self): """Returns forbidden response code if the user has view permission.""" user = User.objects.create_user(username="test-none", password="test-none") - user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) - ) + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) request = self.factory.get(self.get_url(1, "foo", "bar")) request.user = user with self.assertRaises(PermissionDenied): @@ -5091,9 +4503,7 @@ def test_status_code_with_user_permission_view(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1, "foo", "bar")) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -5129,11 +4539,7 @@ def test_billing_project_does_not_exist(self): def test_workspace_does_not_exist(self): dbgap_application = factories.dbGaPApplicationFactory.create() billing_project = BillingProjectFactory.create() - request = self.factory.get( - self.get_url( - dbgap_application.dbgap_project_id, billing_project.name, "foo" - ) - ) + request = self.factory.get(self.get_url(dbgap_application.dbgap_project_id, billing_project.name, "foo")) request.user = self.user with self.assertRaises(Http404): self.get_view()(request) @@ -5190,9 +4596,7 @@ def test_get_verified_access(self): """Get request with verified access.""" # Add a verified workspace. workspace = factories.dbGaPWorkspaceFactory.create() - dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=workspace - ) + dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=workspace) GroupGroupMembershipFactory.create( parent_group=workspace.workspace.authorization_domains.first(), child_group=dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group, @@ -5246,12 +4650,8 @@ def test_get_verified_no_access(self): def test_get_grant_access(self): """needs_action_table shows a record when audit finds that access needs to be granted.""" - workspace = factories.dbGaPWorkspaceFactory.create( - created=timezone.now() - timedelta(weeks=4) - ) - dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=workspace - ) + workspace = factories.dbGaPWorkspaceFactory.create(created=timezone.now() - timedelta(weeks=4)) + dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=workspace) self.client.force_login(self.user) response = self.client.get( self.get_url( @@ -5322,9 +4722,7 @@ def test_post_verified_access(self): """post with VerifiedAccess audit result.""" # Add a verified workspace. workspace = factories.dbGaPWorkspaceFactory.create() - dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create( - dbgap_workspace=workspace - ) + dar = factories.dbGaPDataAccessRequestForWorkspaceFactory.create(dbgap_workspace=workspace) date_created = timezone.now() - timedelta(weeks=3) with freeze_time(date_created): membership = GroupGroupMembershipFactory.create( @@ -5344,9 +4742,7 @@ def test_post_verified_access(self): # Membership hasn't changed. membership.refresh_from_db() self.assertEqual(membership.created, date_created) - self.assertEqual( - membership.parent_group, workspace.workspace.authorization_domains.first() - ) + self.assertEqual(membership.parent_group, workspace.workspace.authorization_domains.first()) self.assertEqual( membership.child_group, dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group, @@ -5377,13 +4773,8 @@ def test_post_grant_access(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - group_name = ( - dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name - ) - api_url = ( - self.api_client.sam_entry_point - + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" - ) + group_name = dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name + api_url = self.api_client.sam_entry_point + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" self.anvil_response_mock.add( responses.PUT, api_url, @@ -5414,13 +4805,8 @@ def test_post_grant_access_htmx(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - group_name = ( - dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name - ) - api_url = ( - self.api_client.sam_entry_point - + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" - ) + group_name = dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name + api_url = self.api_client.sam_entry_point + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" self.anvil_response_mock.add( responses.PUT, api_url, @@ -5437,9 +4823,7 @@ def test_post_grant_access_htmx(self): {}, **header, ) - self.assertEqual( - response.content.decode(), views.dbGaPAuditResolve.htmx_success - ) + self.assertEqual(response.content.decode(), views.dbGaPAuditResolve.htmx_success) # Membership has been created. membership = GroupGroupMembership.objects.get( parent_group=workspace.workspace.authorization_domains.first(), @@ -5469,13 +4853,8 @@ def test_post_remove_access(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - group_name = ( - dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name - ) - api_url = ( - self.api_client.sam_entry_point - + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" - ) + group_name = dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name + api_url = self.api_client.sam_entry_point + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" self.anvil_response_mock.add( responses.DELETE, api_url, @@ -5503,13 +4882,8 @@ def test_anvil_api_error_grant(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - group_name = ( - dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name - ) - api_url = ( - self.api_client.sam_entry_point - + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" - ) + group_name = dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name + api_url = self.api_client.sam_entry_point + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" self.anvil_response_mock.add( responses.PUT, api_url, @@ -5545,13 +4919,8 @@ def test_anvil_api_error_grant_htmx(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - group_name = ( - dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name - ) - api_url = ( - self.api_client.sam_entry_point - + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" - ) + group_name = dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name + api_url = self.api_client.sam_entry_point + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" self.anvil_response_mock.add( responses.PUT, api_url, @@ -5599,13 +4968,8 @@ def test_anvil_api_error_remove(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - group_name = ( - dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name - ) - api_url = ( - self.api_client.sam_entry_point - + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" - ) + group_name = dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name + api_url = self.api_client.sam_entry_point + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" self.anvil_response_mock.add( responses.DELETE, api_url, @@ -5655,13 +5019,8 @@ def test_anvil_api_error_remove_htmx(self): ) # Add API response # Note that the auth domain group is created automatically by the factory using the workspace name. - group_name = ( - dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name - ) - api_url = ( - self.api_client.sam_entry_point - + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" - ) + group_name = dar.dbgap_data_access_snapshot.dbgap_application.anvil_access_group.name + api_url = self.api_client.sam_entry_point + f"/api/groups/v1/auth_TEST_DBGAP/member/{group_name}@firecloud.org" self.anvil_response_mock.add( responses.DELETE, api_url, @@ -5751,9 +5110,7 @@ def test_table_class(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("table", response.context_data) - self.assertIsInstance( - response.context_data["table"], tables.dbGaPApplicationRecordsTable - ) + self.assertIsInstance(response.context_data["table"], tables.dbGaPApplicationRecordsTable) def test_table_no_rows(self): """No rows are shown if there are no dbGaPApplications objects.""" diff --git a/primed/dbgap/urls.py b/primed/dbgap/urls.py index 0aea48d2..fb302b47 100644 --- a/primed/dbgap/urls.py +++ b/primed/dbgap/urls.py @@ -13,9 +13,7 @@ views.dbGaPStudyAccessionAutocomplete.as_view(), name="autocomplete", ), - path( - "", views.dbGaPStudyAccessionDetail.as_view(), name="detail" - ), + path("", views.dbGaPStudyAccessionDetail.as_view(), name="detail"), path( "/update/", views.dbGaPStudyAccessionUpdate.as_view(), diff --git a/primed/dbgap/views.py b/primed/dbgap/views.py index f9f59185..f4ebfa2a 100644 --- a/primed/dbgap/views.py +++ b/primed/dbgap/views.py @@ -39,9 +39,7 @@ logger = logging.getLogger(__name__) -class dbGaPStudyAccessionDetail( - AnVILConsortiumManagerStaffViewRequired, SingleTableMixin, DetailView -): +class dbGaPStudyAccessionDetail(AnVILConsortiumManagerStaffViewRequired, SingleTableMixin, DetailView): """View to show details about a `dbGaPStudyAccession`.""" model = models.dbGaPStudyAccession @@ -53,8 +51,7 @@ def get_object(self, queryset=None): obj = queryset.get(dbgap_phs=self.kwargs.get("dbgap_phs")) except queryset.model.DoesNotExist: raise Http404( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} + "No %(verbose_name)s found matching the query" % {"verbose_name": queryset.model._meta.verbose_name} ) return obj @@ -70,12 +67,8 @@ def get_table(self): def get_context_data(self, **kwargs): """Add show_edit_links to context data.""" context = super().get_context_data(**kwargs) - edit_permission_codename = ( - AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) - context["show_edit_links"] = self.request.user.has_perm( - "anvil_consortium_manager." + edit_permission_codename - ) + edit_permission_codename = AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME + context["show_edit_links"] = self.request.user.has_perm("anvil_consortium_manager." + edit_permission_codename) return context @@ -86,9 +79,7 @@ class dbGaPStudyAccessionList(AnVILConsortiumManagerStaffViewRequired, SingleTab table_class = tables.dbGaPStudyAccessionTable -class dbGaPStudyAccessionCreate( - AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, CreateView -): +class dbGaPStudyAccessionCreate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, CreateView): """View to create a new dbGaPStudyAccession.""" model = models.dbGaPStudyAccession @@ -97,9 +88,7 @@ class dbGaPStudyAccessionCreate( template_name = "dbgap/dbgapstudyaccession_create.html" -class dbGaPStudyAccessionUpdate( - AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, UpdateView -): +class dbGaPStudyAccessionUpdate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, UpdateView): """View to update a dbGaPStudyAccession.""" model = models.dbGaPStudyAccession @@ -113,15 +102,12 @@ def get_object(self, queryset=None): obj = queryset.get(dbgap_phs=self.kwargs.get("dbgap_phs")) except queryset.model.DoesNotExist: raise Http404( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} + "No %(verbose_name)s found matching the query" % {"verbose_name": queryset.model._meta.verbose_name} ) return obj -class dbGaPStudyAccessionAutocomplete( - AnVILConsortiumManagerStaffViewRequired, autocomplete.Select2QuerySetView -): +class dbGaPStudyAccessionAutocomplete(AnVILConsortiumManagerStaffViewRequired, autocomplete.Select2QuerySetView): """View to provide autocompletion for dbGaPStudyAccessions.""" def get_queryset(self): @@ -138,9 +124,7 @@ def get_queryset(self): return qs -class dbGaPApplicationDetail( - AnVILConsortiumManagerStaffViewRequired, SingleTableMixin, DetailView -): +class dbGaPApplicationDetail(AnVILConsortiumManagerStaffViewRequired, SingleTableMixin, DetailView): """View to show details about a `dbGaPApplication`.""" model = models.dbGaPApplication @@ -153,8 +137,7 @@ def get_object(self, queryset=None): obj = queryset.get(dbgap_project_id=self.kwargs.get("dbgap_project_id")) except queryset.model.DoesNotExist: raise Http404( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} + "No %(verbose_name)s found matching the query" % {"verbose_name": queryset.model._meta.verbose_name} ) return obj @@ -171,9 +154,7 @@ def get_latest_snapshot(self): return None def get_table_data(self): - return models.dbGaPDataAccessSnapshot.objects.filter( - dbgap_application=self.object - ).order_by("-created") + return models.dbGaPDataAccessSnapshot.objects.filter(dbgap_application=self.object).order_by("-created") def get_context_data(self, *args, **kwargs): """Add to the context. @@ -196,9 +177,7 @@ class dbGaPApplicationList(AnVILConsortiumManagerStaffViewRequired, SingleTableV table_class = tables.dbGaPApplicationTable -class dbGaPApplicationCreate( - AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, CreateView -): +class dbGaPApplicationCreate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, CreateView): """View to create a new dbGaPApplication.""" model = models.dbGaPApplication @@ -210,25 +189,17 @@ class dbGaPApplicationCreate( def form_valid(self, form): """Create a managed group in the app on AnVIL and link it to this application.""" project_id = form.cleaned_data["dbgap_project_id"] - group_name = "{}_DBGAP_ACCESS_{}".format( - settings.ANVIL_DATA_ACCESS_GROUP_PREFIX, project_id - ) - managed_group = ManagedGroup( - name=group_name, email=group_name + "@firecloud.org" - ) + group_name = "{}_DBGAP_ACCESS_{}".format(settings.ANVIL_DATA_ACCESS_GROUP_PREFIX, project_id) + managed_group = ManagedGroup(name=group_name, email=group_name + "@firecloud.org") try: managed_group.full_clean() except ValidationError: - messages.add_message( - self.request, messages.ERROR, self.ERROR_CREATING_GROUP - ) + messages.add_message(self.request, messages.ERROR, self.ERROR_CREATING_GROUP) return self.render_to_response(self.get_context_data(form=form)) try: managed_group.anvil_create() except AnVILAPIError as e: - messages.add_message( - self.request, messages.ERROR, "AnVIL API Error: " + str(e) - ) + messages.add_message(self.request, messages.ERROR, "AnVIL API Error: " + str(e)) return self.render_to_response(self.get_context_data(form=form)) # Need to wrap this entire block in a transaction because we are creating multiple objects, and don't want # any of them to be saved if the API call fails. @@ -236,9 +207,7 @@ def form_valid(self, form): with transaction.atomic(): managed_group.save() # Create the dbgap access group. - cc_admins_group = ManagedGroup.objects.get( - name=settings.ANVIL_CC_ADMINS_GROUP_NAME - ) + cc_admins_group = ManagedGroup.objects.get(name=settings.ANVIL_CC_ADMINS_GROUP_NAME) membership = GroupGroupMembership.objects.create( parent_group=managed_group, child_group=cc_admins_group, @@ -248,37 +217,24 @@ def form_valid(self, form): membership.anvil_create() membership.save() except AnVILAPIError as e: - messages.add_message( - self.request, messages.ERROR, "AnVIL API Error: " + str(e) - ) + messages.add_message(self.request, messages.ERROR, "AnVIL API Error: " + str(e)) return self.render_to_response(self.get_context_data(form=form)) form.instance.anvil_access_group = managed_group return super().form_valid(form) -class dbGaPDataAccessSnapshotCreate( - AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, FormView -): - +class dbGaPDataAccessSnapshotCreate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, FormView): form_class = forms.dbGaPDataAccessSnapshotForm template_name = "dbgap/dbgapdataaccesssnapshot_form.html" - ERROR_DARS_ALREADY_ADDED = ( - "Data Access Requests have already been added for this application." - ) - ERROR_PROJECT_ID_DOES_NOT_MATCH = ( - "Project id in JSON does not match dbGaP application project id." - ) + ERROR_DARS_ALREADY_ADDED = "Data Access Requests have already been added for this application." + ERROR_PROJECT_ID_DOES_NOT_MATCH = "Project id in JSON does not match dbGaP application project id." ERROR_STUDY_ACCESSION_NOT_FOUND = "Study accession(s) not found in app." ERROR_CREATING_DARS = "Error creating Data Access Requests." - success_message = ( - "Successfully added Data Access Requests for this dbGaP application." - ) + success_message = "Successfully added Data Access Requests for this dbGaP application." def get_dbgap_application(self): try: - dbgap_application = models.dbGaPApplication.objects.get( - dbgap_project_id=self.kwargs["dbgap_project_id"] - ) + dbgap_application = models.dbGaPApplication.objects.get(dbgap_project_id=self.kwargs["dbgap_project_id"]) except models.dbGaPApplication.DoesNotExist: raise Http404( "No %(verbose_name)s found matching the query" @@ -344,10 +300,7 @@ def get_context_data(self, *args, **kwargs): return super().get_context_data(*args, **kwargs) -class dbGaPDataAccessSnapshotCreateMultiple( - AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, FormView -): - +class dbGaPDataAccessSnapshotCreateMultiple(AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, FormView): form_class = forms.dbGaPDataAccessSnapshotMultipleForm template_name = "dbgap/dbgapdataaccesssnapshot_form_multiple.html" # ERROR_DARS_ALREADY_ADDED = ( @@ -364,12 +317,7 @@ def get_context_data(self, **kwargs): """Add to the context data.""" context = super().get_context_data(**kwargs) # The URL for updating all applications. - project_ids = [ - x - for x in models.dbGaPApplication.objects.values_list( - "dbgap_project_id", flat=True - ) - ] + project_ids = [x for x in models.dbGaPApplication.objects.values_list("dbgap_project_id", flat=True)] context["dbgap_dar_json_url"] = helpers.get_dbgap_dar_json_url(project_ids) return context @@ -386,9 +334,7 @@ def form_valid(self, form): # Loop over projects. for project_json in dbgap_dar_data: dbgap_project_id = project_json["Project_id"] - dbgap_application = models.dbGaPApplication.objects.get( - dbgap_project_id=dbgap_project_id - ) + dbgap_application = models.dbGaPApplication.objects.get(dbgap_project_id=dbgap_project_id) try: previous_snapshot = models.dbGaPDataAccessSnapshot.objects.get( dbgap_application=dbgap_application, @@ -455,9 +401,7 @@ def form_valid(self, form): return super().form_valid(form) -class dbGaPDataAccessSnapshotDetail( - AnVILConsortiumManagerStaffViewRequired, DetailView -): +class dbGaPDataAccessSnapshotDetail(AnVILConsortiumManagerStaffViewRequired, DetailView): """View to show details about a `dbGaPDataAccessSnapshot`.""" model = models.dbGaPDataAccessSnapshot @@ -466,9 +410,7 @@ class dbGaPDataAccessSnapshotDetail( def get_dbgap_application(self): model = models.dbGaPApplication try: - application = model.objects.get( - dbgap_project_id=self.kwargs.get("dbgap_project_id") - ) + application = model.objects.get(dbgap_project_id=self.kwargs.get("dbgap_project_id")) except model.DoesNotExist: raise Http404( "No %(verbose_name)s found matching the query" @@ -483,15 +425,11 @@ def get_object(self, queryset=None): self.dbgap_application = self.get_dbgap_application() if not queryset: queryset = self.model.objects - return super().get_object( - queryset=queryset.filter(dbgap_application=self.dbgap_application) - ) + return super().get_object(queryset=queryset.filter(dbgap_application=self.dbgap_application)) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context[ - "data_access_request_table" - ] = tables.dbGaPDataAccessRequestBySnapshotTable( + context["data_access_request_table"] = tables.dbGaPDataAccessRequestBySnapshotTable( self.object.dbgapdataaccessrequest_set.all() ) context["summary_table"] = tables.dbGaPDataAccessRequestSummaryTable( @@ -503,9 +441,7 @@ def get_context_data(self, **kwargs): return context -class dbGaPDataAccessRequestList( - AnVILConsortiumManagerStaffViewRequired, ExportMixin, SingleTableView -): +class dbGaPDataAccessRequestList(AnVILConsortiumManagerStaffViewRequired, ExportMixin, SingleTableView): """View to show current DARs.""" model = models.dbGaPDataAccessRequest @@ -513,14 +449,10 @@ class dbGaPDataAccessRequestList( export_name = "dars_table" def get_table_data(self): - return self.get_queryset().filter( - dbgap_data_access_snapshot__is_most_recent=True - ) + return self.get_queryset().filter(dbgap_data_access_snapshot__is_most_recent=True) -class dbGaPDataAccessRequestHistory( - AnVILConsortiumManagerStaffViewRequired, ExportMixin, SingleTableView -): +class dbGaPDataAccessRequestHistory(AnVILConsortiumManagerStaffViewRequired, ExportMixin, SingleTableView): """View to show the history of a given DAR.""" model = models.dbGaPDataAccessRequest @@ -582,8 +514,7 @@ def get_object(self, queryset=None): obj = queryset.get(dbgap_project_id=self.kwargs.get("dbgap_project_id")) except queryset.model.DoesNotExist: raise Http404( - "No %(verbose_name)s found matching the query" - % {"verbose_name": queryset.model._meta.verbose_name} + "No %(verbose_name)s found matching the query" % {"verbose_name": queryset.model._meta.verbose_name} ) return obj @@ -641,8 +572,7 @@ def get_object(self, queryset=None): obj = queryset.get() except queryset.model.DoesNotExist: raise Http404( - _("No %(verbose_name)s found matching the query") - % {"verbose_name": queryset.model._meta.verbose_name} + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} ) return obj @@ -661,7 +591,6 @@ def get_context_data(self, **kwargs): class dbGaPAuditResolve(AnVILConsortiumManagerStaffEditRequired, FormView): - form_class = Form template_name = "dbgap/audit_resolve.html" htmx_success = """ Handled!""" @@ -681,29 +610,22 @@ def get_dbgap_workspace(self): obj = queryset.get() except queryset.model.DoesNotExist: raise Http404( - _("No %(verbose_name)s found matching the query") - % {"verbose_name": queryset.model._meta.verbose_name} + _("No %(verbose_name)s found matching the query") % {"verbose_name": queryset.model._meta.verbose_name} ) return obj def get_dbgap_application(self, queryset=None): """Look up the dbGaPApplication by dbgap_project_id.""" try: - obj = models.dbGaPApplication.objects.get( - dbgap_project_id=self.kwargs.get("dbgap_project_id") - ) + obj = models.dbGaPApplication.objects.get(dbgap_project_id=self.kwargs.get("dbgap_project_id")) except models.dbGaPApplication.DoesNotExist: raise Http404("No dbGaPApplications found matching the query") return obj def get_audit_result(self): instance = audit.dbGaPAccessAudit( - dbgap_workspace_queryset=models.dbGaPWorkspace.objects.filter( - pk=self.dbgap_workspace.pk - ), - dbgap_application_queryset=models.dbGaPApplication.objects.filter( - pk=self.dbgap_application.pk - ), + dbgap_workspace_queryset=models.dbGaPWorkspace.objects.filter(pk=self.dbgap_workspace.pk), + dbgap_application_queryset=models.dbGaPApplication.objects.filter(pk=self.dbgap_application.pk), ) instance.run_audit() return instance.get_all_results()[0] diff --git a/primed/drupal_oauth_provider/provider.py b/primed/drupal_oauth_provider/provider.py index f9f112ab..92c84dd0 100644 --- a/primed/drupal_oauth_provider/provider.py +++ b/primed/drupal_oauth_provider/provider.py @@ -24,7 +24,6 @@ class CustomAccount(ProviderAccount): class CustomProvider(OAuth2Provider): - id = "drupal_oauth_provider" name = OVERRIDE_NAME account_class = CustomAccount @@ -74,9 +73,7 @@ def get_provider_scope_config(self): ) if not isinstance(gregor_oauth_scopes, list): - raise ImproperlyConfigured( - "[get_provider_scope_config] provider setting SCOPES should be a list" - ) + raise ImproperlyConfigured("[get_provider_scope_config] provider setting SCOPES should be a list") return gregor_oauth_scopes diff --git a/primed/drupal_oauth_provider/views.py b/primed/drupal_oauth_provider/views.py index eb8fc82b..c8a78575 100644 --- a/primed/drupal_oauth_provider/views.py +++ b/primed/drupal_oauth_provider/views.py @@ -50,7 +50,6 @@ def _get_public_key_jwk(self, headers): return keys[0] def get_public_key(self, headers): - provider_settings = app_settings.PROVIDERS.get(self.provider_id, {}) config_public_key = provider_settings.get("PUBLIC_KEY") @@ -59,9 +58,7 @@ def get_public_key(self, headers): public_key_jwk = self._get_public_key_jwk(headers) try: - public_key = jwt.algorithms.RSAAlgorithm.from_jwk( - json.dumps(public_key_jwk) - ) + public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(public_key_jwk)) except Exception as e: logger.error(f"[get_public_key] failed to convert jwk to public key {e}") else: @@ -89,9 +86,7 @@ def get_scopes_from_token(self, id_token, headers): logger.error(f"Invalid id_token {e} {id_token.token}") raise OAuth2Error("Invalid id_token") from e except Exception as e: - logger.error( - f"Other exception parsing token {e} header {unverified_header} token {id_token}" - ) + logger.error(f"Other exception parsing token {e} header {unverified_header} token {id_token}") raise OAuth2Error("Error when decoding token {e}") else: scopes = token_payload.get("scope") @@ -102,9 +97,7 @@ def complete_login(self, request, app, token, **kwargs): headers = {"Authorization": "Bearer {0}".format(token.token)} scopes_granted = self.get_scopes_from_token(token, headers) - managed_scope_status = self.get_provider().get_provider_managed_scope_status( - scopes_granted - ) + managed_scope_status = self.get_provider().get_provider_managed_scope_status(scopes_granted) resp = requests.get(self.profile_url, headers=headers) resp.raise_for_status() @@ -116,9 +109,7 @@ def complete_login(self, request, app, token, **kwargs): ) extra_data["scopes_granted"] = scopes_granted extra_data["managed_scope_status"] = managed_scope_status - social_login = self.get_provider().sociallogin_from_response( - request, extra_data - ) + social_login = self.get_provider().sociallogin_from_response(request, extra_data) return social_login diff --git a/primed/duo/management/commands/load_duo.py b/primed/duo/management/commands/load_duo.py index 1668ddf3..9f67f787 100644 --- a/primed/duo/management/commands/load_duo.py +++ b/primed/duo/management/commands/load_duo.py @@ -34,21 +34,14 @@ def add_arguments(self, parser): def handle(self, *args, **options): # Error if anything is loaded into either model. - if ( - models.DataUsePermission.objects.exists() - or models.DataUseModifier.objects.exists() - ): - raise CommandError( - "At least one DataUsePermission or DataUseModifier already exists." - ) + if models.DataUsePermission.objects.exists() or models.DataUseModifier.objects.exists(): + raise CommandError("At least one DataUsePermission or DataUseModifier already exists.") duo_file = options["duo_file"] if not duo_file: # Use the default. tmppath = os.path.dirname(os.path.realpath(__file__)) - duo_file = os.path.join( - tmppath, os.pardir, os.pardir, "fixtures", "duo-basic.owl" - ) + duo_file = os.path.join(tmppath, os.pardir, os.pardir, "fixtures", "duo-basic.owl") self.stdout.write("Loading DUO terms from {}".format(duo_file)) # Read in the ontology. @@ -57,9 +50,7 @@ def handle(self, *args, **options): # Check that specified terms are in the file. permissions_code = options["permissions_code"] if permissions_code not in duo.terms(): - msg = "permissions-code '{}' not in available terms.".format( - permissions_code - ) + msg = "permissions-code '{}' not in available terms.".format(permissions_code) raise CommandError(self.style.ERROR(msg)) modifiers_code = options["modifiers_code"] @@ -84,9 +75,7 @@ def handle(self, *args, **options): def _get_term_abbreviation(self, term): """Return the abbreviation for the term.""" - abbreviation = [ - a.literal for a in term.annotations if "shorthand" in a.property - ] + abbreviation = [a.literal for a in term.annotations if "shorthand" in a.property] if len(abbreviation) != 1: import ipdb diff --git a/primed/duo/models.py b/primed/duo/models.py index 9b3414e9..558a1dfa 100644 --- a/primed/duo/models.py +++ b/primed/duo/models.py @@ -39,9 +39,7 @@ def __str__(self): return "{}".format(self.term) def get_ols_url(self): - return "http://purl.obolibrary.org/obo/{}".format( - self.identifier.replace("DUO:", "DUO_") - ) + return "http://purl.obolibrary.org/obo/{}".format(self.identifier.replace("DUO:", "DUO_")) def get_short_definition(self): text = re.sub(r"This .+? indicates that ", "", self.definition) @@ -103,8 +101,7 @@ def clean(self): if hasattr(self, "data_use_permission") and self.data_use_permission: if self.data_use_permission.requires_disease_term and not self.disease_term: raise ValidationError( - "`disease_term` must not be None " - "because data_use_permission requires a disease restriction." + "`disease_term` must not be None " "because data_use_permission requires a disease restriction." ) if not self.data_use_permission.requires_disease_term and self.disease_term: raise ValidationError( diff --git a/primed/duo/tests/test_commands.py b/primed/duo/tests/test_commands.py index a2abe6f7..02423483 100644 --- a/primed/duo/tests/test_commands.py +++ b/primed/duo/tests/test_commands.py @@ -29,9 +29,7 @@ def test_command_permissions_code_not_in_ontology(self): """Raises exception when specified permissions-code is not in the ontology.""" with self.assertRaises(CommandError) as e: call_command("load_duo", permissions_code="foo") - self.assertIn( - "permissions-code 'foo' not in available terms.", str(e.exception) - ) + self.assertIn("permissions-code 'foo' not in available terms.", str(e.exception)) def test_command_modifiers_code_not_in_ontology(self): """Raises exception when specified modifiers-code is not in the ontology.""" @@ -49,6 +47,4 @@ def test_command_output(self): """Correct output.""" out = StringIO() call_command("load_duo", stdout=out) - self.assertIn( - "5 DataUsePermissions and 18 DataUseModifiers loaded.", out.getvalue() - ) + self.assertIn("5 DataUsePermissions and 18 DataUseModifiers loaded.", out.getvalue()) diff --git a/primed/duo/tests/test_models.py b/primed/duo/tests/test_models.py index baeb1416..42c63206 100644 --- a/primed/duo/tests/test_models.py +++ b/primed/duo/tests/test_models.py @@ -27,9 +27,7 @@ def test_model_saving(self): def test_str_method(self): """The custom __str__ method returns the correct string.""" - instance = factories.DataUsePermissionFactory.create( - term="test group", identifier="foo" - ) + instance = factories.DataUsePermissionFactory.create(term="test group", identifier="foo") instance.save() self.assertIsInstance(instance.__str__(), str) self.assertEqual(instance.__str__(), "test group") @@ -81,28 +79,20 @@ def test_unique_identifier(self): instance2.full_clean() self.assertIn("identifier", e.exception.error_dict) self.assertEqual(len(e.exception.error_dict["identifier"]), 1) - self.assertIn( - "already exists", e.exception.error_dict["identifier"][0].messages[0] - ) + self.assertIn("already exists", e.exception.error_dict["identifier"][0].messages[0]) with self.assertRaises(IntegrityError): instance2.save() def test_get_short_definition(self): - instance = factories.DataUsePermissionFactory.create( - definition="Test definition" - ) + instance = factories.DataUsePermissionFactory.create(definition="Test definition") self.assertEqual(instance.get_short_definition(), "Test definition") def test_get_short_definition_re_sub(self): - instance = factories.DataUsePermissionFactory.create( - definition="This XXX indicates that everything is fine." - ) + instance = factories.DataUsePermissionFactory.create(definition="This XXX indicates that everything is fine.") self.assertEqual(instance.get_short_definition(), "Everything is fine.") def test_get_short_definition_capitalization(self): - instance = factories.DataUsePermissionFactory.create( - definition="Test definition XyXy" - ) + instance = factories.DataUsePermissionFactory.create(definition="Test definition XyXy") self.assertEqual(instance.get_short_definition(), "Test definition XyXy") @@ -123,9 +113,7 @@ def test_model_saving(self): def test_str_method(self): """The custom __str__ method returns the correct string.""" - instance = factories.DataUseModifierFactory.create( - term="test group", identifier="foo" - ) + instance = factories.DataUseModifierFactory.create(term="test group", identifier="foo") instance.save() self.assertIsInstance(instance.__str__(), str) self.assertEqual(instance.__str__(), "test group") @@ -169,9 +157,7 @@ def test_unique_identifier(self): instance2.full_clean() self.assertIn("identifier", e.exception.error_dict) self.assertEqual(len(e.exception.error_dict["identifier"]), 1) - self.assertIn( - "already exists", e.exception.error_dict["identifier"][0].messages[0] - ) + self.assertIn("already exists", e.exception.error_dict["identifier"][0].messages[0]) with self.assertRaises(IntegrityError): instance2.save() @@ -180,9 +166,7 @@ def test_get_short_definition(self): self.assertEqual(instance.get_short_definition(), "Test definition") def test_get_short_definition_re_sub(self): - instance = factories.DataUseModifierFactory.create( - definition="This XXX indicates that use is allowed." - ) + instance = factories.DataUseModifierFactory.create(definition="This XXX indicates that use is allowed.") self.assertEqual(instance.get_short_definition(), "Use is allowed.") @@ -193,42 +177,28 @@ class DataUseOntologyTestCase(TestCase): def test_clean_requires_disease_term_false_with_no_disease_term(self): """Clean succeeds if disease_term is not set and requires_disease_term is False.""" - data_use_permission = factories.DataUsePermissionFactory.create( - requires_disease_term=False - ) - workspace = dbGaPWorkspaceFactory.create( - data_use_permission=data_use_permission - ) + data_use_permission = factories.DataUsePermissionFactory.create(requires_disease_term=False) + workspace = dbGaPWorkspaceFactory.create(data_use_permission=data_use_permission) # No errors should be raised. workspace.clean() def test_clean_requires_disease_term_true_with_disease_term(self): """Clean succeeds if disease_term is set and requires_disease_term is True.""" - data_use_permission = factories.DataUsePermissionFactory.create( - requires_disease_term=True - ) - workspace = dbGaPWorkspaceFactory.create( - data_use_permission=data_use_permission, disease_term="foo" - ) + data_use_permission = factories.DataUsePermissionFactory.create(requires_disease_term=True) + workspace = dbGaPWorkspaceFactory.create(data_use_permission=data_use_permission, disease_term="foo") workspace.clean() def test_clean_requires_disease_term_false_with_disease_term(self): """Clean fails if disease_term is set when requires_disease_term is False.""" - data_use_permission = factories.DataUsePermissionFactory.create( - requires_disease_term=False - ) - workspace = dbGaPWorkspaceFactory.create( - data_use_permission=data_use_permission, disease_term="foo" - ) + data_use_permission = factories.DataUsePermissionFactory.create(requires_disease_term=False) + workspace = dbGaPWorkspaceFactory.create(data_use_permission=data_use_permission, disease_term="foo") with self.assertRaises(ValidationError) as e: workspace.clean() self.assertIn("does not require a disease restriction", str(e.exception)) def test_clean_requires_disease_term_true_with_no_disease_term(self): """Clean fails if disease_term is not set when requires_disease_term is True.""" - data_use_permission = factories.DataUsePermissionFactory.create( - requires_disease_term=True - ) + data_use_permission = factories.DataUsePermissionFactory.create(requires_disease_term=True) workspace = dbGaPWorkspaceFactory.create( data_use_permission=data_use_permission, ) diff --git a/primed/duo/tests/test_views.py b/primed/duo/tests/test_views.py index e22ca408..45d9c787 100644 --- a/primed/duo/tests/test_views.py +++ b/primed/duo/tests/test_views.py @@ -22,9 +22,7 @@ def setUp(self): # Create a user with staff view permission. self.user = UserFactory.create(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -125,9 +123,7 @@ def setUp(self): # Create a user with view permission. self.user = UserFactory.create(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -179,9 +175,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = UserFactory.create(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -281,9 +275,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = UserFactory.create(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): diff --git a/primed/duo/views.py b/primed/duo/views.py index 308cc3e1..733b284d 100644 --- a/primed/duo/views.py +++ b/primed/duo/views.py @@ -6,7 +6,6 @@ class DataUsePermissionList(AnVILConsortiumManagerViewRequired, ListView): - model = models.DataUsePermission def get_context_data(self, **kwargs): @@ -19,7 +18,6 @@ def get_context_data(self, **kwargs): class DataUsePermissionDetail(AnVILConsortiumManagerViewRequired, DetailView): - model = models.DataUsePermission def get_object(self): @@ -27,8 +25,7 @@ def get_object(self): obj = self.model.objects.get(identifier=self.kwargs.get("id")) except self.model.DoesNotExist: raise Http404( - "No %(verbose_name)s found matching the query" - % {"verbose_name": self.model._meta.verbose_name} + "No %(verbose_name)s found matching the query" % {"verbose_name": self.model._meta.verbose_name} ) return obj @@ -39,7 +36,6 @@ def get_context_data(self, **kwargs): class DataUseModifierList(AnVILConsortiumManagerViewRequired, ListView): - model = models.DataUseModifier def get_context_data(self, **kwargs): @@ -52,7 +48,6 @@ def get_context_data(self, **kwargs): class DataUseModifierDetail(AnVILConsortiumManagerViewRequired, DetailView): - model = models.DataUseModifier def get_object(self): @@ -60,8 +55,7 @@ def get_object(self): obj = self.model.objects.get(identifier=self.kwargs.get("id")) except self.model.DoesNotExist: raise Http404( - "No %(verbose_name)s found matching the query" - % {"verbose_name": self.model._meta.verbose_name} + "No %(verbose_name)s found matching the query" % {"verbose_name": self.model._meta.verbose_name} ) return obj diff --git a/primed/miscellaneous_workspaces/adapters.py b/primed/miscellaneous_workspaces/adapters.py index 17bdfb9b..42a2d72c 100644 --- a/primed/miscellaneous_workspaces/adapters.py +++ b/primed/miscellaneous_workspaces/adapters.py @@ -22,9 +22,7 @@ class SimulatedDataWorkspaceAdapter(BaseWorkspaceAdapter): workspace_form_class = WorkspaceForm workspace_data_model = models.SimulatedDataWorkspace workspace_data_form_class = forms.SimulatedDataWorkspaceForm - workspace_detail_template_name = ( - "miscellaneous_workspaces/simulateddataworkspace_detail.html" - ) + workspace_detail_template_name = "miscellaneous_workspaces/simulateddataworkspace_detail.html" class ConsortiumDevelWorkspaceAdapter(BaseWorkspaceAdapter): @@ -60,9 +58,7 @@ class TemplateWorkspaceAdapter(BaseWorkspaceAdapter): type = "template" name = "Template workspace" - description = ( - "Template workspaces that will be cloned by the CC to create other workspaces" - ) + description = "Template workspaces that will be cloned by the CC to create other workspaces" list_table_class_staff_view = DefaultWorkspaceStaffTable list_table_class_view = DefaultWorkspaceUserTable workspace_form_class = WorkspaceForm @@ -82,9 +78,7 @@ class OpenAccessWorkspaceAdapter(BaseWorkspaceAdapter): workspace_form_class = WorkspaceForm workspace_data_model = models.OpenAccessWorkspace workspace_data_form_class = forms.OpenAccessWorkspaceForm - workspace_detail_template_name = ( - "miscellaneous_workspaces/openaccessworkspace_detail.html" - ) + workspace_detail_template_name = "miscellaneous_workspaces/openaccessworkspace_detail.html" class DataPrepWorkspaceAdapter(BaseWorkspaceAdapter): @@ -98,6 +92,4 @@ class DataPrepWorkspaceAdapter(BaseWorkspaceAdapter): workspace_form_class = WorkspaceForm workspace_data_model = models.DataPrepWorkspace workspace_data_form_class = forms.DataPrepWorkspaceForm - workspace_detail_template_name = ( - "miscellaneous_workspaces/dataprepworkspace_detail.html" - ) + workspace_detail_template_name = "miscellaneous_workspaces/dataprepworkspace_detail.html" diff --git a/primed/miscellaneous_workspaces/models.py b/primed/miscellaneous_workspaces/models.py index d702122d..97ad892d 100644 --- a/primed/miscellaneous_workspaces/models.py +++ b/primed/miscellaneous_workspaces/models.py @@ -59,16 +59,8 @@ class DataPrepWorkspace(RequesterModel, TimeStampedModel, BaseWorkspaceData): def clean(self): if hasattr(self, "target_workspace"): if self.target_workspace.workspace_type == "data_prep": - raise ValidationError( - { - "target_workspace": "target_workspace cannot be a DataPrepWorkspace." - } - ) + raise ValidationError({"target_workspace": "target_workspace cannot be a DataPrepWorkspace."}) if hasattr(self, "target_workspace") and hasattr(self, "workspace"): if self.target_workspace == self.workspace: - raise ValidationError( - { - "target_workspace": "target_workspace must be different than workspace." - } - ) + raise ValidationError({"target_workspace": "target_workspace must be different than workspace."}) diff --git a/primed/miscellaneous_workspaces/tables.py b/primed/miscellaneous_workspaces/tables.py index 014f746c..cc2b7b51 100644 --- a/primed/miscellaneous_workspaces/tables.py +++ b/primed/miscellaneous_workspaces/tables.py @@ -48,12 +48,8 @@ class DataPrepWorkspaceUserTable(tables.Table): """Class to render a table of Workspace objects with DataPrepWorkspace workspace data.""" name = tables.columns.Column(linkify=True) - dataprepworkspace__target_workspace__name = tables.columns.Column( - linkify=True, verbose_name="Target workspace" - ) - dataprepworkspace__is_active = BooleanIconColumn( - verbose_name="Active?", show_false_icon=True - ) + dataprepworkspace__target_workspace__name = tables.columns.Column(linkify=True, verbose_name="Target workspace") + dataprepworkspace__is_active = BooleanIconColumn(verbose_name="Active?", show_false_icon=True) class Meta: model = Workspace diff --git a/primed/miscellaneous_workspaces/tests/test_forms.py b/primed/miscellaneous_workspaces/tests/test_forms.py index b6dd125a..3fc86728 100644 --- a/primed/miscellaneous_workspaces/tests/test_forms.py +++ b/primed/miscellaneous_workspaces/tests/test_forms.py @@ -1,4 +1,4 @@ -""""Form tests for the `workspaces` app.""" +""" "Form tests for the `workspaces` app.""" from anvil_consortium_manager.adapters.workspace import workspace_adapter_registry from anvil_consortium_manager.tests.factories import WorkspaceFactory @@ -12,7 +12,6 @@ class SimulatedDataWorkspaceFormTest(TestCase): - form_class = forms.SimulatedDataWorkspaceForm def setUp(self): @@ -55,7 +54,6 @@ def test_invalid_missing_requester(self): class ConsortiumDevelWorkspaceFormTest(TestCase): - form_class = forms.ConsortiumDevelWorkspaceForm def setUp(self): @@ -98,7 +96,6 @@ def test_invalid_missing_requester(self): class ResourceWorkspaceFormTest(TestCase): - form_class = forms.ResourceWorkspaceForm def setUp(self): @@ -141,7 +138,6 @@ def test_invalid_missing_requester(self): class TemplateWorkspaceFormTest(TestCase): - form_class = forms.TemplateWorkspaceForm def setUp(self): @@ -196,7 +192,6 @@ def test_invalid_blank_intended_usage(self): class OpenAccessWorkspaceFormTest(TestCase): - form_class = forms.OpenAccessWorkspaceForm def setUp(self): @@ -342,7 +337,6 @@ def test_invalid_data_url_is_not_url(self): class DataPrepWorkspaceFormTest(TestCase): - form_class = forms.DataPrepWorkspaceForm def setUp(self): @@ -412,9 +406,7 @@ def test_form_all_registered_workspaces(self): # Cannot create data prep workspaces for data prep workspace target_workspaces. pass else: - target_workspace = WorkspaceFactory.create( - workspace_type=workspace_type - ) + target_workspace = WorkspaceFactory.create(workspace_type=workspace_type) form_data = { "workspace": self.workspace, "target_workspace": target_workspace, diff --git a/primed/miscellaneous_workspaces/tests/test_models.py b/primed/miscellaneous_workspaces/tests/test_models.py index c8222656..4f8ad2c3 100644 --- a/primed/miscellaneous_workspaces/tests/test_models.py +++ b/primed/miscellaneous_workspaces/tests/test_models.py @@ -1,4 +1,4 @@ -""""Model tests for the `miscellaneous_workspaces` app.""" +""" "Model tests for the `miscellaneous_workspaces` app.""" from anvil_consortium_manager.tests.factories import WorkspaceFactory from django.core.exceptions import ValidationError @@ -23,9 +23,7 @@ def test_model_saving(self): self.assertIsInstance(instance, models.SimulatedDataWorkspace) def test_str_method(self): - workspace = WorkspaceFactory.create( - billing_project__name="test-bp", name="test-ws" - ) + workspace = WorkspaceFactory.create(billing_project__name="test-bp", name="test-ws") instance = factories.SimulatedDataWorkspaceFactory.create(workspace=workspace) self.assertIsInstance(str(instance), str) self.assertEqual(str(instance), "test-bp/test-ws") @@ -38,16 +36,12 @@ def test_model_saving(self): """Creation using the model constructor and .save() works.""" workspace = WorkspaceFactory.create() user = UserFactory.create() - instance = models.ConsortiumDevelWorkspace( - workspace=workspace, requested_by=user - ) + instance = models.ConsortiumDevelWorkspace(workspace=workspace, requested_by=user) instance.save() self.assertIsInstance(instance, models.ConsortiumDevelWorkspace) def test_str_method(self): - workspace = WorkspaceFactory.create( - billing_project__name="test-bp", name="test-ws" - ) + workspace = WorkspaceFactory.create(billing_project__name="test-bp", name="test-ws") instance = factories.ConsortiumDevelWorkspaceFactory.create(workspace=workspace) self.assertIsInstance(str(instance), str) self.assertEqual(str(instance), "test-bp/test-ws") @@ -65,9 +59,7 @@ def test_model_saving(self): self.assertIsInstance(instance, models.ResourceWorkspace) def test_str_method(self): - workspace = WorkspaceFactory.create( - billing_project__name="test-bp", name="test-ws" - ) + workspace = WorkspaceFactory.create(billing_project__name="test-bp", name="test-ws") instance = factories.ResourceWorkspaceFactory.create(workspace=workspace) self.assertIsInstance(str(instance), str) self.assertEqual(str(instance), "test-bp/test-ws") @@ -84,9 +76,7 @@ def test_model_saving(self): self.assertIsInstance(instance, models.TemplateWorkspace) def test_str_method(self): - workspace = WorkspaceFactory.create( - billing_project__name="test-bp", name="test-ws" - ) + workspace = WorkspaceFactory.create(billing_project__name="test-bp", name="test-ws") instance = factories.TemplateWorkspaceFactory.create(workspace=workspace) self.assertIsInstance(str(instance), str) self.assertEqual(str(instance), "test-bp/test-ws") @@ -104,9 +94,7 @@ def test_model_saving(self): self.assertIsInstance(instance, models.OpenAccessWorkspace) def test_str_method(self): - workspace = WorkspaceFactory.create( - billing_project__name="test-bp", name="test-ws" - ) + workspace = WorkspaceFactory.create(billing_project__name="test-bp", name="test-ws") instance = factories.OpenAccessWorkspaceFactory.create(workspace=workspace) self.assertIsInstance(str(instance), str) self.assertEqual(str(instance), "test-bp/test-ws") @@ -144,9 +132,7 @@ def test_two_available_data(self): def test_data_url(self): workspace = WorkspaceFactory.create() user = UserFactory.create() - instance = models.OpenAccessWorkspace( - workspace=workspace, requested_by=user, data_url="http://www.example.com" - ) + instance = models.OpenAccessWorkspace(workspace=workspace, requested_by=user, data_url="http://www.example.com") self.assertEqual(instance.data_url, "http://www.example.com") @@ -158,28 +144,20 @@ def test_model_saving(self): workspace = WorkspaceFactory.create() target_workspace = WorkspaceFactory.create() user = UserFactory.create() - instance = models.DataPrepWorkspace( - workspace=workspace, target_workspace=target_workspace, requested_by=user - ) + instance = models.DataPrepWorkspace(workspace=workspace, target_workspace=target_workspace, requested_by=user) instance.save() self.assertIsInstance(instance, models.DataPrepWorkspace) def test_str_method(self): - workspace = WorkspaceFactory.create( - billing_project__name="test-bp", name="test-ws" - ) + workspace = WorkspaceFactory.create(billing_project__name="test-bp", name="test-ws") instance = factories.DataPrepWorkspaceFactory.create(workspace=workspace) self.assertIsInstance(str(instance), str) self.assertEqual(str(instance), "test-bp/test-ws") def test_two_update_workspaces_for_same_final_workspace(self): target_workspace = WorkspaceFactory.create() - instance_1 = factories.DataPrepWorkspaceFactory.create( - target_workspace=target_workspace - ) - instance_2 = factories.DataPrepWorkspaceFactory.create( - target_workspace=target_workspace - ) + instance_1 = factories.DataPrepWorkspaceFactory.create(target_workspace=target_workspace) + instance_2 = factories.DataPrepWorkspaceFactory.create(target_workspace=target_workspace) self.assertEqual(target_workspace.data_prep_workspaces.count(), 2) self.assertIn(instance_1, target_workspace.data_prep_workspaces.all()) self.assertIn(instance_2, target_workspace.data_prep_workspaces.all()) @@ -188,9 +166,7 @@ def test_clean_original_workspace_different_than_workspace(self): """Clean method raises ValidationError when workspace is the same as original_workspace.""" workspace = WorkspaceFactory.create() user = UserFactory.create() - instance = models.DataPrepWorkspace( - requested_by=user, workspace=workspace, target_workspace=workspace - ) + instance = models.DataPrepWorkspace(requested_by=user, workspace=workspace, target_workspace=workspace) with self.assertRaises(ValidationError) as e: instance.full_clean() self.assertEqual(len(e.exception.message_dict), 1) diff --git a/primed/miscellaneous_workspaces/tests/test_views.py b/primed/miscellaneous_workspaces/tests/test_views.py index 6e8d5916..ac351cea 100644 --- a/primed/miscellaneous_workspaces/tests/test_views.py +++ b/primed/miscellaneous_workspaces/tests/test_views.py @@ -29,9 +29,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def test_status_code_with_user_permission(self): @@ -54,14 +52,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.requester = UserFactory.create() self.workspace_type = "simulated_data" @@ -120,14 +114,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.requester = UserFactory.create() self.workspace_type = "simulated_data" @@ -138,25 +128,15 @@ def get_url(self, *args): def get_api_url(self, billing_project_name, workspace_name): """Return the Terra API url for a given billing project and workspace.""" - return ( - self.api_client.rawls_entry_point - + "/api/workspaces/" - + billing_project_name - + "/" - + workspace_name - ) + return self.api_client.rawls_entry_point + "/api/workspaces/" + billing_project_name + "/" + workspace_name - def get_api_json_response( - self, billing_project, workspace, authorization_domains=[], access="OWNER" - ): + def get_api_json_response(self, billing_project, workspace, authorization_domains=[], access="OWNER"): """Return a pared down version of the json response from the AnVIL API with only fields we need.""" json_data = { "accessLevel": access, "owners": [], "workspace": { - "authorizationDomain": [ - {"membersGroupName": x} for x in authorization_domains - ], + "authorizationDomain": [{"membersGroupName": x} for x in authorization_domains], "name": workspace, "namespace": billing_project, "isLocked": False, @@ -174,9 +154,7 @@ def test_creates_workspace(self): responses.GET, workspace_list_url, match=[ - responses.matchers.query_param_matcher( - {"fields": "workspace.namespace,workspace.name,accessLevel"} - ) + responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"}) ], status=200, json=[self.get_api_json_response(billing_project.name, workspace_name)], @@ -242,9 +220,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def test_status_code_with_user_permission(self): @@ -267,14 +243,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.requester = UserFactory.create() self.workspace_type = "devel" @@ -333,14 +305,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.requester = UserFactory.create() self.workspace_type = "devel" @@ -351,25 +319,15 @@ def get_url(self, *args): def get_api_url(self, billing_project_name, workspace_name): """Return the Terra API url for a given billing project and workspace.""" - return ( - self.api_client.rawls_entry_point - + "/api/workspaces/" - + billing_project_name - + "/" - + workspace_name - ) + return self.api_client.rawls_entry_point + "/api/workspaces/" + billing_project_name + "/" + workspace_name - def get_api_json_response( - self, billing_project, workspace, authorization_domains=[], access="OWNER" - ): + def get_api_json_response(self, billing_project, workspace, authorization_domains=[], access="OWNER"): """Return a pared down version of the json response from the AnVIL API with only fields we need.""" json_data = { "accessLevel": access, "owners": [], "workspace": { - "authorizationDomain": [ - {"membersGroupName": x} for x in authorization_domains - ], + "authorizationDomain": [{"membersGroupName": x} for x in authorization_domains], "name": workspace, "namespace": billing_project, "isLocked": False, @@ -387,9 +345,7 @@ def test_creates_workspace(self): responses.GET, workspace_list_url, match=[ - responses.matchers.query_param_matcher( - {"fields": "workspace.namespace,workspace.name,accessLevel"} - ) + responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"}) ], status=200, json=[self.get_api_json_response(billing_project.name, workspace_name)], @@ -455,9 +411,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def test_status_code_with_user_permission(self): @@ -480,14 +434,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.requester = UserFactory.create() self.workspace_type = "resource" @@ -546,14 +496,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.requester = UserFactory.create() self.workspace_type = "resource" @@ -564,25 +510,15 @@ def get_url(self, *args): def get_api_url(self, billing_project_name, workspace_name): """Return the Terra API url for a given billing project and workspace.""" - return ( - self.api_client.rawls_entry_point - + "/api/workspaces/" - + billing_project_name - + "/" - + workspace_name - ) + return self.api_client.rawls_entry_point + "/api/workspaces/" + billing_project_name + "/" + workspace_name - def get_api_json_response( - self, billing_project, workspace, authorization_domains=[], access="OWNER" - ): + def get_api_json_response(self, billing_project, workspace, authorization_domains=[], access="OWNER"): """Return a pared down version of the json response from the AnVIL API with only fields we need.""" json_data = { "accessLevel": access, "owners": [], "workspace": { - "authorizationDomain": [ - {"membersGroupName": x} for x in authorization_domains - ], + "authorizationDomain": [{"membersGroupName": x} for x in authorization_domains], "name": workspace, "namespace": billing_project, "isLocked": False, @@ -600,9 +536,7 @@ def test_creates_workspace(self): responses.GET, workspace_list_url, match=[ - responses.matchers.query_param_matcher( - {"fields": "workspace.namespace,workspace.name,accessLevel"} - ) + responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"}) ], status=200, json=[self.get_api_json_response(billing_project.name, workspace_name)], @@ -668,9 +602,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def test_status_code_with_user_permission(self): @@ -693,14 +625,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.workspace_type = "template" @@ -759,14 +687,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.workspace_type = "template" @@ -776,25 +700,15 @@ def get_url(self, *args): def get_api_url(self, billing_project_name, workspace_name): """Return the Terra API url for a given billing project and workspace.""" - return ( - self.api_client.rawls_entry_point - + "/api/workspaces/" - + billing_project_name - + "/" - + workspace_name - ) + return self.api_client.rawls_entry_point + "/api/workspaces/" + billing_project_name + "/" + workspace_name - def get_api_json_response( - self, billing_project, workspace, authorization_domains=[], access="OWNER" - ): + def get_api_json_response(self, billing_project, workspace, authorization_domains=[], access="OWNER"): """Return a pared down version of the json response from the AnVIL API with only fields we need.""" json_data = { "accessLevel": access, "owners": [], "workspace": { - "authorizationDomain": [ - {"membersGroupName": x} for x in authorization_domains - ], + "authorizationDomain": [{"membersGroupName": x} for x in authorization_domains], "name": workspace, "namespace": billing_project, "isLocked": False, @@ -812,9 +726,7 @@ def test_creates_workspace(self): responses.GET, workspace_list_url, match=[ - responses.matchers.query_param_matcher( - {"fields": "workspace.namespace,workspace.name,accessLevel"} - ) + responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"}) ], status=200, json=[self.get_api_json_response(billing_project.name, workspace_name)], @@ -881,9 +793,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def test_status_code_with_user_permission(self): @@ -910,14 +820,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.workspace_type = "open_access" self.requester = UserFactory.create() @@ -983,14 +889,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.workspace_type = "open_access" self.requester = UserFactory.create() @@ -1002,25 +904,15 @@ def get_url(self, *args): def get_api_url(self, billing_project_name, workspace_name): """Return the Terra API url for a given billing project and workspace.""" - return ( - self.api_client.rawls_entry_point - + "/api/workspaces/" - + billing_project_name - + "/" - + workspace_name - ) + return self.api_client.rawls_entry_point + "/api/workspaces/" + billing_project_name + "/" + workspace_name - def get_api_json_response( - self, billing_project, workspace, authorization_domains=[], access="OWNER" - ): + def get_api_json_response(self, billing_project, workspace, authorization_domains=[], access="OWNER"): """Return a pared down version of the json response from the AnVIL API with only fields we need.""" json_data = { "accessLevel": access, "owners": [], "workspace": { - "authorizationDomain": [ - {"membersGroupName": x} for x in authorization_domains - ], + "authorizationDomain": [{"membersGroupName": x} for x in authorization_domains], "name": workspace, "namespace": billing_project, "isLocked": False, @@ -1038,9 +930,7 @@ def test_creates_workspace(self): responses.GET, workspace_list_url, match=[ - responses.matchers.query_param_matcher( - {"fields": "workspace.namespace,workspace.name,accessLevel"} - ) + responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"}) ], status=200, json=[self.get_api_json_response(billing_project.name, workspace_name)], @@ -1112,9 +1002,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def test_status_code_with_user_permission(self): @@ -1155,14 +1043,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.requester = UserFactory.create() self.target_workspace = WorkspaceFactory.create() @@ -1223,14 +1107,10 @@ def setUp(self): # Create a user with both view and edit permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.requester = UserFactory.create() self.target_workspace = WorkspaceFactory.create() @@ -1242,25 +1122,15 @@ def get_url(self, *args): def get_api_url(self, billing_project_name, workspace_name): """Return the Terra API url for a given billing project and workspace.""" - return ( - self.api_client.rawls_entry_point - + "/api/workspaces/" - + billing_project_name - + "/" - + workspace_name - ) + return self.api_client.rawls_entry_point + "/api/workspaces/" + billing_project_name + "/" + workspace_name - def get_api_json_response( - self, billing_project, workspace, authorization_domains=[], access="OWNER" - ): + def get_api_json_response(self, billing_project, workspace, authorization_domains=[], access="OWNER"): """Return a pared down version of the json response from the AnVIL API with only fields we need.""" json_data = { "accessLevel": access, "owners": [], "workspace": { - "authorizationDomain": [ - {"membersGroupName": x} for x in authorization_domains - ], + "authorizationDomain": [{"membersGroupName": x} for x in authorization_domains], "name": workspace, "namespace": billing_project, "isLocked": False, @@ -1278,9 +1148,7 @@ def test_creates_workspace(self): responses.GET, workspace_list_url, match=[ - responses.matchers.query_param_matcher( - {"fields": "workspace.namespace,workspace.name,accessLevel"} - ) + responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"}) ], status=200, json=[self.get_api_json_response(billing_project.name, workspace_name)], diff --git a/primed/primed_anvil/adapters.py b/primed/primed_anvil/adapters.py index 8d22bdee..f62a4593 100644 --- a/primed/primed_anvil/adapters.py +++ b/primed/primed_anvil/adapters.py @@ -14,9 +14,7 @@ class AccountAdapter(BaseAccountAdapter): def get_autocomplete_queryset(self, queryset, q): """Filter to Accounts where the email or the associated user name matches the query `q`.""" if q: - queryset = queryset.filter( - Q(email__icontains=q) | Q(user__name__icontains=q) - ) + queryset = queryset.filter(Q(email__icontains=q) | Q(user__name__icontains=q)) return queryset def get_autocomplete_label(self, account): diff --git a/primed/primed_anvil/audit.py b/primed/primed_anvil/audit.py index b01ac5c0..0eca6a15 100644 --- a/primed/primed_anvil/audit.py +++ b/primed/primed_anvil/audit.py @@ -90,9 +90,7 @@ def get_all_results(self): def _check_completed(self): if not self.completed: - raise ValueError( - "Audit has not been completed. Use run_audit() to run the audit." - ) + raise ValueError("Audit has not been completed. Use run_audit() to run the audit.") def get_verified_table(self): """Return a table of verified audit results. @@ -103,9 +101,7 @@ def get_verified_table(self): results_table_class: A table of verified results. """ self._check_completed() - return self.results_table_class( - [x.get_table_dictionary() for x in self.verified] - ) + return self.results_table_class([x.get_table_dictionary() for x in self.verified]) def get_needs_action_table(self): """Return a table of needs_action audit results. @@ -116,9 +112,7 @@ def get_needs_action_table(self): results_table_class: A table of need action results. """ self._check_completed() - return self.results_table_class( - [x.get_table_dictionary() for x in self.needs_action] - ) + return self.results_table_class([x.get_table_dictionary() for x in self.needs_action]) def get_errors_table(self): """Return a table of error audit results. diff --git a/primed/primed_anvil/helpers.py b/primed/primed_anvil/helpers.py index e0f6cacf..911271f4 100644 --- a/primed/primed_anvil/helpers.py +++ b/primed/primed_anvil/helpers.py @@ -18,9 +18,7 @@ def get_summary_table_data(): # If no available data objects exist, raise ???. available_data_types = AvailableData.objects.values_list("name", flat=True) if not len(available_data_types): - raise RuntimeError( - "get_summary_table_data requires at least one AvailableData object to exist." - ) + raise RuntimeError("get_summary_table_data requires at least one AvailableData object to exist.") # This query will be used to add information about whether a study has workspaces # that are shared with the consortium. diff --git a/primed/primed_anvil/models.py b/primed/primed_anvil/models.py index 2277c9b8..87389d1b 100644 --- a/primed/primed_anvil/models.py +++ b/primed/primed_anvil/models.py @@ -8,12 +8,8 @@ class Study(TimeStampedModel, models.Model): """A model to track studies.""" - short_name = models.CharField( - max_length=31, unique=True, help_text="The short name for this Study." - ) - full_name = models.CharField( - max_length=255, help_text="The full name for this Study." - ) + short_name = models.CharField(max_length=31, unique=True, help_text="The short name for this Study.") + full_name = models.CharField(max_length=255, help_text="The full name for this Study.") history = HistoricalRecords() diff --git a/primed/primed_anvil/tables.py b/primed/primed_anvil/tables.py index 24534216..092f88fe 100644 --- a/primed/primed_anvil/tables.py +++ b/primed/primed_anvil/tables.py @@ -7,7 +7,6 @@ class BooleanIconColumn(tables.BooleanColumn): - # attrs = {"td": {"align": "center"}} # attrs = {"th": {"class": "center"}} @@ -53,9 +52,7 @@ def _get_bool_value(self, record, value, bound_column): # Check if it is a workspace if not isinstance(record, Workspace): raise ImproperlyConfigured("record must be a Workspace") - is_shared = record.workspacegroupsharing_set.filter( - group__name="PRIMED_ALL" - ).exists() + is_shared = record.workspacegroupsharing_set.filter(group__name="PRIMED_ALL").exists() return is_shared @@ -146,18 +143,11 @@ class Meta: class DataSummaryTable(tables.Table): - study = tables.Column() access_mechanism = tables.Column() - is_shared = tables.BooleanColumn( - verbose_name="Status", yesno="Shared,Preparing data" - ) + is_shared = tables.BooleanColumn(verbose_name="Status", yesno="Shared,Preparing data") def __init__(self, *args, **kwargs): - available_data_types = models.AvailableData.objects.values_list( - "name", flat=True - ) - extra_columns = [ - (x, BooleanIconColumn(default=False)) for x in available_data_types - ] + available_data_types = models.AvailableData.objects.values_list("name", flat=True) + extra_columns = [(x, BooleanIconColumn(default=False)) for x in available_data_types] super().__init__(*args, extra_columns=extra_columns, **kwargs) diff --git a/primed/primed_anvil/tests/test_adapters.py b/primed/primed_anvil/tests/test_adapters.py index b08c0551..515b4b10 100644 --- a/primed/primed_anvil/tests/test_adapters.py +++ b/primed/primed_anvil/tests/test_adapters.py @@ -30,16 +30,10 @@ def test_get_autocomplete_label_no_linked_user(self): def test_autocomplete_queryset_matches_user_name(self): """get_autocomplete_label returns correct account when user name matches.""" user_1 = UserFactory.create(name="First Last") - account_1 = AccountFactory.create( - email="test1@test.com", user=user_1, verified=True - ) + account_1 = AccountFactory.create(email="test1@test.com", user=user_1, verified=True) user_2 = UserFactory.create(name="Foo Bar") - account_2 = AccountFactory.create( - email="test2@test.com", user=user_2, verified=True - ) - queryset = adapters.AccountAdapter().get_autocomplete_queryset( - Account.objects.all(), "last" - ) + account_2 = AccountFactory.create(email="test2@test.com", user=user_2, verified=True) + queryset = adapters.AccountAdapter().get_autocomplete_queryset(Account.objects.all(), "last") self.assertEqual(len(queryset), 1) self.assertIn(account_1, queryset) self.assertNotIn(account_2, queryset) @@ -47,16 +41,10 @@ def test_autocomplete_queryset_matches_user_name(self): def test_autocomplete_queryset_matches_account_email(self): """get_autocomplete_label returns correct account when user email matches.""" user_1 = UserFactory.create(name="First Last") - account_1 = AccountFactory.create( - email="test1@test.com", user=user_1, verified=True - ) + account_1 = AccountFactory.create(email="test1@test.com", user=user_1, verified=True) user_2 = UserFactory.create(name="Foo Bar") - account_2 = AccountFactory.create( - email="username@domain.com", user=user_2, verified=True - ) - queryset = adapters.AccountAdapter().get_autocomplete_queryset( - Account.objects.all(), "test" - ) + account_2 = AccountFactory.create(email="username@domain.com", user=user_2, verified=True) + queryset = adapters.AccountAdapter().get_autocomplete_queryset(Account.objects.all(), "test") self.assertEqual(len(queryset), 1) self.assertIn(account_1, queryset) self.assertNotIn(account_2, queryset) @@ -65,9 +53,7 @@ def test_autocomplete_queryset_no_linked_user(self): """get_autocomplete_label returns correct account when user name matches.""" account_1 = AccountFactory.create(email="foo@bar.com") account_2 = AccountFactory.create(email="test@test.com") - queryset = adapters.AccountAdapter().get_autocomplete_queryset( - Account.objects.all(), "bar" - ) + queryset = adapters.AccountAdapter().get_autocomplete_queryset(Account.objects.all(), "bar") self.assertEqual(len(queryset), 1) self.assertIn(account_1, queryset) self.assertNotIn(account_2, queryset) diff --git a/primed/primed_anvil/tests/test_audit.py b/primed/primed_anvil/tests/test_audit.py index fb34965d..897d5db8 100644 --- a/primed/primed_anvil/tests/test_audit.py +++ b/primed/primed_anvil/tests/test_audit.py @@ -1,4 +1,5 @@ """Tests for the `audit.py` module.""" + from dataclasses import dataclass from unittest import TestCase @@ -9,7 +10,6 @@ @dataclass class TempAuditResult(audit.PRIMEDAuditResult): - value: str def get_table_dictionary(self): diff --git a/primed/primed_anvil/tests/test_helpers.py b/primed/primed_anvil/tests/test_helpers.py index a5bf54f1..a7ab68e8 100644 --- a/primed/primed_anvil/tests/test_helpers.py +++ b/primed/primed_anvil/tests/test_helpers.py @@ -126,9 +126,7 @@ def test_one_dbgap_workspace_one_study_shared_no_available_data(self): AvailableDataFactory.create(name="Foo") study = StudyFactory.create(short_name="TEST") workspace = dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group__name="PRIMED_ALL" - ) + 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) @@ -164,16 +162,10 @@ 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") - workspace_1 = dbGaPWorkspaceFactory.create( - dbgap_study_accession__studies=[study] - ) + workspace_1 = dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) workspace_1.available_data.add(available_data_1) - WorkspaceGroupSharingFactory.create( - workspace=workspace_1.workspace, group__name="PRIMED_ALL" - ) - workspace_2 = dbGaPWorkspaceFactory.create( - dbgap_study_accession__studies=[study] - ) + WorkspaceGroupSharingFactory.create(workspace=workspace_1.workspace, group__name="PRIMED_ALL") + workspace_2 = dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) workspace_2.available_data.add(available_data_2) res = helpers.get_summary_table_data() self.assertEqual(len(res), 2) @@ -371,9 +363,7 @@ 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" - ) + 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) @@ -411,9 +401,7 @@ def test_two_cdsa_workspaces_one_study_one_shared(self): 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" - ) + 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() @@ -580,9 +568,7 @@ def test_one_dbgap_workspace_shared_one_study(self): workspace__name="test-ws", dbgap_study_accession__studies=[study], ) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all_group) res = helpers.get_workspaces_for_phenotype_inventory() self.assertEqual(len(res), 1) self.assertIn("test-bp/test-ws", res) @@ -598,9 +584,7 @@ def test_one_dbgap_workspace_shared_two_studies(self): workspace__name="test-ws", dbgap_study_accession=study_accession, ) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all_group) res = helpers.get_workspaces_for_phenotype_inventory() self.assertEqual(len(res), 1) self.assertIn("test-bp/test-ws", res) @@ -614,18 +598,14 @@ def test_two_dbgap_workspaces(self): workspace__name="test-ws-1", dbgap_study_accession__studies=[study_1], ) - WorkspaceGroupSharingFactory.create( - workspace=workspace_1.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace_1.workspace, group=self.primed_all_group) study_2 = StudyFactory.create(short_name="TEST 2") workspace_2 = dbGaPWorkspaceFactory.create( workspace__billing_project__name="test-bp-2", workspace__name="test-ws-2", dbgap_study_accession__studies=[study_2], ) - WorkspaceGroupSharingFactory.create( - workspace=workspace_2.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace_2.workspace, group=self.primed_all_group) res = helpers.get_workspaces_for_phenotype_inventory() self.assertEqual(len(res), 2) self.assertIn("test-bp-1/test-ws-1", res) @@ -647,9 +627,7 @@ def test_one_cdsa_workspace_shared_one_study(self): workspace__name="test-ws", study=study, ) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all_group) res = helpers.get_workspaces_for_phenotype_inventory() self.assertEqual(len(res), 1) self.assertIn("test-bp/test-ws", res) @@ -663,18 +641,14 @@ def test_two_cdsa_workspaces(self): workspace__name="test-ws-1", study=study_1, ) - WorkspaceGroupSharingFactory.create( - workspace=workspace_1.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace_1.workspace, group=self.primed_all_group) study_2 = StudyFactory.create(short_name="TEST 2") workspace_2 = CDSAWorkspaceFactory.create( workspace__billing_project__name="test-bp-2", workspace__name="test-ws-2", study=study_2, ) - WorkspaceGroupSharingFactory.create( - workspace=workspace_2.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace_2.workspace, group=self.primed_all_group) res = helpers.get_workspaces_for_phenotype_inventory() self.assertEqual(len(res), 2) self.assertIn("test-bp-1/test-ws-1", res) @@ -694,9 +668,7 @@ def test_one_open_access_workspace_shared_no_study(self): workspace__billing_project__name="test-bp", workspace__name="test-ws", ) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all_group) res = helpers.get_workspaces_for_phenotype_inventory() self.assertEqual(len(res), 1) self.assertIn("test-bp/test-ws", res) @@ -710,9 +682,7 @@ def test_one_open_access_workspace_shared_one_study(self): workspace__name="test-ws", ) workspace.studies.add(study) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all_group) res = helpers.get_workspaces_for_phenotype_inventory() self.assertEqual(len(res), 1) self.assertIn("test-bp/test-ws", res) @@ -727,9 +697,7 @@ def test_one_open_access_workspace_shared_two_studies(self): workspace__name="test-ws", ) workspace.studies.add(study_1, study_2) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all_group) res = helpers.get_workspaces_for_phenotype_inventory() self.assertEqual(len(res), 1) self.assertIn("test-bp/test-ws", res) @@ -741,18 +709,14 @@ def test_two_open_access_workspaces(self): workspace__billing_project__name="test-bp-1", workspace__name="test-ws-1", ) - WorkspaceGroupSharingFactory.create( - workspace=workspace_1.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace_1.workspace, group=self.primed_all_group) study_2 = StudyFactory.create(short_name="TEST 2") workspace_2 = OpenAccessWorkspaceFactory.create( workspace__billing_project__name="test-bp-2", workspace__name="test-ws-2", ) workspace_2.studies.add(study_2) - WorkspaceGroupSharingFactory.create( - workspace=workspace_2.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace_2.workspace, group=self.primed_all_group) res = helpers.get_workspaces_for_phenotype_inventory() self.assertEqual(len(res), 2) self.assertIn("test-bp-1/test-ws-1", res) @@ -768,27 +732,21 @@ def test_multiple_workspace_types_same_study(self): workspace__name="test-ws-dbgap", dbgap_study_accession__studies=[study], ) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all_group) # CDSA workspace = CDSAWorkspaceFactory.create( workspace__billing_project__name="test-bp-cdsa", workspace__name="test-ws-cdsa", study=study, ) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all_group) # Open access workspace = OpenAccessWorkspaceFactory.create( workspace__billing_project__name="test-bp-open", workspace__name="test-ws-open", ) workspace.studies.add(study) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all_group) res = helpers.get_workspaces_for_phenotype_inventory() self.assertEqual(len(res), 3) self.assertIn("test-bp-dbgap/test-ws-dbgap", res) @@ -806,9 +764,7 @@ def test_multiple_workspace_types_separate_studies(self): workspace__name="test-ws-dbgap", dbgap_study_accession__studies=[study_1], ) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all_group) # CDSA study_2 = StudyFactory.create(short_name="TEST 2") workspace = CDSAWorkspaceFactory.create( @@ -816,9 +772,7 @@ def test_multiple_workspace_types_separate_studies(self): workspace__name="test-ws-cdsa", study=study_2, ) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all_group) # Open access study_3 = StudyFactory.create(short_name="TEST 3") workspace = OpenAccessWorkspaceFactory.create( @@ -826,9 +780,7 @@ def test_multiple_workspace_types_separate_studies(self): workspace__name="test-ws-open", ) workspace.studies.add(study_3) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all_group - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all_group) res = helpers.get_workspaces_for_phenotype_inventory() self.assertEqual(len(res), 3) self.assertIn("test-bp-dbgap/test-ws-dbgap", res) diff --git a/primed/primed_anvil/tests/test_models.py b/primed/primed_anvil/tests/test_models.py index 93eb829f..b4f249c6 100644 --- a/primed/primed_anvil/tests/test_models.py +++ b/primed/primed_anvil/tests/test_models.py @@ -32,16 +32,12 @@ def test_get_absolute_url(self): def test_unique_short_name(self): """Saving a model with a duplicate short name fails.""" factories.StudyFactory.create(short_name="FOO") - instance2 = factories.StudyFactory.build( - short_name="FOO", full_name="full name" - ) + instance2 = factories.StudyFactory.build(short_name="FOO", full_name="full name") with self.assertRaises(ValidationError) as e: instance2.full_clean() self.assertIn("short_name", e.exception.error_dict) self.assertEqual(len(e.exception.error_dict["short_name"]), 1) - self.assertIn( - "already exists", e.exception.error_dict["short_name"][0].messages[0] - ) + self.assertIn("already exists", e.exception.error_dict["short_name"][0].messages[0]) with self.assertRaises(IntegrityError): instance2.save() diff --git a/primed/primed_anvil/tests/test_tables.py b/primed/primed_anvil/tests/test_tables.py index 60431ffa..c25c9092 100644 --- a/primed/primed_anvil/tests/test_tables.py +++ b/primed/primed_anvil/tests/test_tables.py @@ -145,7 +145,6 @@ def test_ordering(self): class DataSummaryTableTest(TestCase): - table_class = tables.DataSummaryTable def test_row_count_with_no_objects(self): @@ -233,9 +232,7 @@ def test_render_is_not_shared(self): def test_render_is_shared(self): workspace = WorkspaceFactory.create() - WorkspaceGroupSharingFactory.create( - workspace=workspace, group__name="PRIMED_ALL" - ) + WorkspaceGroupSharingFactory.create(workspace=workspace, group__name="PRIMED_ALL") column = tables.WorkspaceSharedWithConsortiumColumn() value = column.render(None, workspace, None) self.assertIn("bi-check-circle-fill", value) diff --git a/primed/primed_anvil/tests/test_views.py b/primed/primed_anvil/tests/test_views.py index b800b6df..e28f43bc 100644 --- a/primed/primed_anvil/tests/test_views.py +++ b/primed/primed_anvil/tests/test_views.py @@ -55,9 +55,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -67,9 +65,7 @@ def get_url(self, *args): def test_staff_view_links(self): user = UserFactory.create() user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url()) @@ -78,14 +74,10 @@ def test_staff_view_links(self): def test_staff_edit_links(self): user = UserFactory.create() user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url()) @@ -102,9 +94,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -115,9 +105,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_logged_in(self): """Returns successful response code.""" @@ -134,45 +122,33 @@ def test_user_has_not_linked_account(self): self.client.force_login(self.user) AccountFactory.create(user=self.user, verified=True) response = self.client.get(self.get_url()) - self.assertNotContains( - response, reverse("anvil_consortium_manager:accounts:link") - ) + self.assertNotContains(response, reverse("anvil_consortium_manager:accounts:link")) def test_unauthenticated_user_has_not_linked_account_message(self): response = self.client.get(settings.LOGIN_URL, follow=True) - self.assertNotContains( - response, reverse("anvil_consortium_manager:accounts:link") - ) + self.assertNotContains(response, reverse("anvil_consortium_manager:accounts:link")) def test_staff_view_links(self): user = UserFactory.create() user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url()) # Note: we need quotes around the link because anvil/accounts/link does appear in the response, # so we can't test if "anvil/" is in the response. We need to test if '"anvil/"' is in the response. - self.assertContains( - response, '"{}"'.format(reverse("anvil_consortium_manager:index")) - ) + self.assertContains(response, '"{}"'.format(reverse("anvil_consortium_manager:index"))) def test_view_links(self): user = UserFactory.create() user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url()) # Note: we need quotes around the link because anvil/accounts/link does appear in the response, # so we can't test if "anvil/" is in the response. We need to test if '"anvil/"' is in the response. - self.assertNotContains( - response, '"{}"'.format(reverse("anvil_consortium_manager:index")) - ) + self.assertNotContains(response, '"{}"'.format(reverse("anvil_consortium_manager:index"))) def test_site_announcement_no_text(self): self.client.force_login(self.user) @@ -203,9 +179,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -220,9 +194,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url(1)) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1) - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1)) def test_status_code_with_user_permission(self): """Returns successful response code.""" @@ -234,9 +206,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -255,9 +225,7 @@ def test_status_code_with_limited_view_permission(self): obj = self.model_factory.create() user = User.objects.create_user(username="test-2", password="test-2") user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url(obj.pk)) @@ -273,9 +241,7 @@ def test_content_view_permission(self): obj = self.model_factory.create() user = User.objects.create_user(username="test-2", password="test-2") user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url(obj.pk)) @@ -286,45 +252,29 @@ def test_table_classes_view_permission(self): self.client.force_login(self.user) response = self.client.get(self.get_url(obj.pk)) self.assertIn("tables", response.context_data) - self.assertIsInstance( - response.context_data["tables"][0], dbGaPWorkspaceStaffTable - ) - self.assertIsInstance( - response.context_data["tables"][1], CDSAWorkspaceStaffTable - ) - self.assertIsInstance( - response.context_data["tables"][3], OpenAccessWorkspaceStaffTable - ) + self.assertIsInstance(response.context_data["tables"][0], dbGaPWorkspaceStaffTable) + self.assertIsInstance(response.context_data["tables"][1], CDSAWorkspaceStaffTable) + self.assertIsInstance(response.context_data["tables"][3], OpenAccessWorkspaceStaffTable) def test_table_classes_limited_view_permission(self): """Table classes are correct when the user has limited view permission.""" user = User.objects.create_user(username="test-2", password="test-2") user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) obj = self.model_factory.create() self.client.force_login(user) response = self.client.get(self.get_url(obj.pk)) self.assertIn("tables", response.context_data) - self.assertIsInstance( - response.context_data["tables"][0], dbGaPWorkspaceUserTable - ) - self.assertIsInstance( - response.context_data["tables"][1], CDSAWorkspaceUserTable - ) - self.assertIsInstance( - response.context_data["tables"][3], OpenAccessWorkspaceUserTable - ) + self.assertIsInstance(response.context_data["tables"][0], dbGaPWorkspaceUserTable) + self.assertIsInstance(response.context_data["tables"][1], CDSAWorkspaceUserTable) + self.assertIsInstance(response.context_data["tables"][3], OpenAccessWorkspaceUserTable) def test_dbgap_workspace_table(self): """Contains a table of dbGaPWorkspaces with the correct studies.""" obj = self.model_factory.create() dbgap_study_accession = dbGaPStudyAccessionFactory.create(studies=[obj]) - dbgap_workspace = dbGaPWorkspaceFactory.create( - dbgap_study_accession=dbgap_study_accession - ) + dbgap_workspace = dbGaPWorkspaceFactory.create(dbgap_study_accession=dbgap_study_accession) other_workspace = dbGaPWorkspaceFactory.create() self.client.force_login(self.user) response = self.client.get(self.get_url(obj.pk)) @@ -378,9 +328,7 @@ def setUp(self): # Create a user with the correct permissions. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -395,9 +343,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_with_user_permission(self): """Returns successful response code.""" @@ -408,9 +354,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -422,139 +366,92 @@ def test_returns_all_objects(self): request = self.factory.get(self.get_url()) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 10) - self.assertEqual( - sorted(returned_ids), sorted([object.pk for object in objects]) - ) + self.assertEqual(sorted(returned_ids), sorted([object.pk for object in objects])) def test_returns_correct_object_match_short_name(self): """Queryset returns the correct objects when query matches the short_name.""" - object = factories.StudyFactory.create( - short_name="test", full_name="other study" - ) + object = factories.StudyFactory.create(short_name="test", full_name="other study") request = self.factory.get(self.get_url(), {"q": "test"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) def test_returns_correct_object_starting_with_query_short_name(self): """Queryset returns the correct objects when query matches the beginning of the short_name.""" - object = factories.StudyFactory.create( - short_name="test", full_name="other study" - ) + object = factories.StudyFactory.create(short_name="test", full_name="other study") request = self.factory.get(self.get_url(), {"q": "test"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) def test_returns_correct_object_containing_query_short_name(self): """Queryset returns the correct objects when the short_name contains the query.""" - object = factories.StudyFactory.create( - short_name="test", full_name="other study" - ) + object = factories.StudyFactory.create(short_name="test", full_name="other study") request = self.factory.get(self.get_url(), {"q": "es"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) def test_returns_correct_object_case_insensitive_short_name(self): """Queryset returns the correct objects when query matches the beginning of the short_name.""" - object = factories.StudyFactory.create( - short_name="TEST", full_name="other study" - ) + object = factories.StudyFactory.create(short_name="TEST", full_name="other study") request = self.factory.get(self.get_url(), {"q": "test"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) def test_returns_correct_object_match_full_name(self): """Queryset returns the correct objects when query matches the full_name.""" - object = factories.StudyFactory.create( - short_name="other", full_name="test study" - ) + object = factories.StudyFactory.create(short_name="other", full_name="test study") request = self.factory.get(self.get_url(), {"q": "test study"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) def test_returns_correct_object_starting_with_query_full_name(self): """Queryset returns the correct objects when query matches the beginning of the full_name.""" - object = factories.StudyFactory.create( - short_name="other", full_name="test study" - ) + object = factories.StudyFactory.create(short_name="other", full_name="test study") request = self.factory.get(self.get_url(), {"q": "test"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) def test_returns_correct_object_containing_query_full_name(self): """Queryset returns the correct objects when the full_name contains the query.""" - object = factories.StudyFactory.create( - short_name="other", full_name="test study" - ) + object = factories.StudyFactory.create(short_name="other", full_name="test study") request = self.factory.get(self.get_url(), {"q": "stu"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) def test_returns_correct_object_case_insensitive_full_name(self): """Queryset returns the correct objects when query matches the beginning of the full_name.""" - object = factories.StudyFactory.create( - short_name="other", full_name="TEST STUDY" - ) + object = factories.StudyFactory.create(short_name="other", full_name="TEST STUDY") request = self.factory.get(self.get_url(), {"q": "test study"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], object.pk) def test_get_result_label(self): - instance = factories.StudyFactory.create( - full_name="Test Name", short_name="TEST" - ) + instance = factories.StudyFactory.create(full_name="Test Name", short_name="TEST") request = self.factory.get(self.get_url()) request.user = self.user view = views.StudyAutocomplete() @@ -562,9 +459,7 @@ def test_get_result_label(self): self.assertEqual(view.get_result_label(instance), "Test Name (TEST)") def test_get_selected_result_label(self): - instance = factories.StudyFactory.create( - full_name="Test Name", short_name="TEST" - ) + instance = factories.StudyFactory.create(full_name="Test Name", short_name="TEST") request = self.factory.get(self.get_url()) request.user = self.user view = views.StudyAutocomplete() @@ -582,14 +477,10 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -604,9 +495,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_with_user_permission_edit(self): """Returns successful response code.""" @@ -617,9 +506,7 @@ def test_status_code_with_user_permission_edit(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -627,13 +514,9 @@ def test_access_without_user_permission(self): def test_access_without_user_permission_view(self): """Raises permission denied if user has no permissions.""" - user_view_perm = User.objects.create_user( - username="test-none", password="test-none" - ) + user_view_perm = User.objects.create_user(username="test-none", password="test-none") user_view_perm.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url()) request.user = user_view_perm @@ -650,9 +533,7 @@ def test_has_form_in_context(self): def test_can_create_object(self): """Can create an object.""" self.client.force_login(self.user) - response = self.client.post( - self.get_url(), {"short_name": "TEST", "full_name": "Test study"} - ) + response = self.client.post(self.get_url(), {"short_name": "TEST", "full_name": "Test study"}) self.assertEqual(response.status_code, 302) # A new object was created. self.assertEqual(models.Study.objects.count(), 1) @@ -663,9 +544,7 @@ def test_can_create_object(self): def test_redirect_url(self): """Redirects to successful url.""" self.client.force_login(self.user) - response = self.client.post( - self.get_url(), {"short_name": "TEST", "full_name": "Test study"} - ) + response = self.client.post(self.get_url(), {"short_name": "TEST", "full_name": "Test study"}) new_object = models.Study.objects.latest("pk") self.assertRedirects(response, new_object.get_absolute_url()) @@ -718,9 +597,7 @@ def test_error_duplicate_short_name(self): """Form shows an error when trying to create a duplicate short name.""" factories.StudyFactory.create(short_name="TEST", full_name="Test study") self.client.force_login(self.user) - response = self.client.post( - self.get_url(), {"short_name": "TEST", "full_name": "Test study 2"} - ) + response = self.client.post(self.get_url(), {"short_name": "TEST", "full_name": "Test study 2"}) self.assertEqual(response.status_code, 200) # No new objects were created. self.assertEqual(models.Study.objects.count(), 1) @@ -760,9 +637,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self): @@ -777,9 +652,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_view_render(self): obj = self.model_factory.create() @@ -800,9 +673,7 @@ def test_status_code_with_limited_view_permission(self): """Returns successful response code.""" user = User.objects.create_user(username="test-2", password="test-2") user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) self.client.force_login(user) response = self.client.get(self.get_url()) @@ -810,9 +681,7 @@ def test_status_code_with_limited_view_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -866,9 +735,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -883,9 +750,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url(1)) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1) - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1)) def test_view_render(self): obj = self.model_factory.create() @@ -904,9 +769,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -971,9 +834,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self): @@ -988,9 +849,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_view_render(self): obj = self.model_factory.create() @@ -1009,9 +868,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -1065,9 +922,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self): @@ -1122,9 +977,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -1139,9 +992,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url(1)) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1) - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1)) def test_status_code_with_user_permission(self): """Returns successful response code.""" @@ -1153,9 +1004,7 @@ def test_status_code_with_user_permission(self): def test_access_without_user_permission(self): """Raises permission denied if user has no permissions.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url(1)) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -1208,9 +1057,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_with_authenticated_user(self): """Returns successful response code.""" @@ -1224,9 +1071,7 @@ def test_table_class(self): self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("summary_table", response.context_data) - self.assertIsInstance( - response.context_data["summary_table"], tables.DataSummaryTable - ) + self.assertIsInstance(response.context_data["summary_table"], tables.DataSummaryTable) def test_table_rows(self): """A summary table exists.""" @@ -1282,9 +1127,7 @@ def setUp(self): # Create a user with both view and edit permission. self.user = User.objects.create_user(username="test", password="test") self.user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) self.primed_all = ManagedGroupFactory.create(name="PRIMED_ALL") @@ -1300,15 +1143,11 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_with_authenticated_user(self): """Redirects to login view when user has no perms.""" - user_no_perms = User.objects.create_user( - username="test-none", password="test-none" - ) + user_no_perms = User.objects.create_user(username="test-none", password="test-none") request = self.factory.get(self.get_url()) request.user = user_no_perms with self.assertRaises(PermissionDenied): @@ -1318,9 +1157,7 @@ def test_status_code_with_view_user(self): """Redirects to login view when user has view perm.""" user = User.objects.create_user(username="test-none", password="test-none") user.user_permissions.add( - Permission.objects.get( - codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) request = self.factory.get(self.get_url()) request.user = user @@ -1339,9 +1176,7 @@ def test_context_workspaces_input_one_workspace(self): workspace = OpenAccessWorkspaceFactory.create( workspace__billing_project__name="test-bp", workspace__name="test-ws" ) - WorkspaceGroupSharingFactory.create( - workspace=workspace.workspace, group=self.primed_all - ) + WorkspaceGroupSharingFactory.create(workspace=workspace.workspace, group=self.primed_all) self.client.force_login(self.user) response = self.client.get(self.get_url()) self.assertIn("workspaces_input", response.context_data) diff --git a/primed/primed_anvil/views.py b/primed/primed_anvil/views.py index f85e787b..844f0160 100644 --- a/primed/primed_anvil/views.py +++ b/primed/primed_anvil/views.py @@ -53,14 +53,10 @@ class StudyDetail(AnVILConsortiumManagerViewRequired, MultiTableMixin, DetailVie # context_table_name = "dbgap_workspace_table" def get_tables(self): - dbgap_qs = Workspace.objects.filter( - dbgapworkspace__dbgap_study_accession__studies=self.object - ) + dbgap_qs = Workspace.objects.filter(dbgapworkspace__dbgap_study_accession__studies=self.object) cdsa_qs = Workspace.objects.filter(cdsaworkspace__study=self.object) agreement_qs = DataAffiliateAgreement.objects.filter(study=self.object) - open_access_qs = Workspace.objects.filter( - openaccessworkspace__studies=self.object - ) + open_access_qs = Workspace.objects.filter(openaccessworkspace__studies=self.object) # Check permissions to determine table type. apm_content_type = ContentType.objects.get_for_model(AnVILProjectManagerAccess) full_view_perm = f"{apm_content_type.app_label}.{AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME}" @@ -88,9 +84,7 @@ class StudyList(AnVILConsortiumManagerViewRequired, SingleTableView): table_class = tables.StudyTable -class StudyCreate( - AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, CreateView -): +class StudyCreate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, CreateView): """View to create a new `Study`.""" model = models.Study @@ -101,9 +95,7 @@ def get_success_url(self): return self.object.get_absolute_url() -class StudyAutocomplete( - AnVILConsortiumManagerStaffViewRequired, autocomplete.Select2QuerySetView -): +class StudyAutocomplete(AnVILConsortiumManagerStaffViewRequired, autocomplete.Select2QuerySetView): """View to provide autocompletion for `Study`s. Match either the `short_name` or `full_name`.""" def get_result_label(self, result): @@ -118,16 +110,12 @@ def get_queryset(self): qs = models.Study.objects.order_by("short_name") if self.q: - qs = qs.filter( - Q(short_name__icontains=self.q) | Q(full_name__icontains=self.q) - ) + qs = qs.filter(Q(short_name__icontains=self.q) | Q(full_name__icontains=self.q)) return qs -class StudySiteDetail( - AnVILConsortiumManagerStaffViewRequired, MultiTableMixin, DetailView -): +class StudySiteDetail(AnVILConsortiumManagerStaffViewRequired, MultiTableMixin, DetailView): """View to show details about a `StudySite`.""" model = models.StudySite @@ -141,9 +129,7 @@ class StudySiteDetail( # return UserTable(User.objects.filter(study_sites=self.object)) def get_tables_data(self): user_qs = User.objects.filter(study_sites=self.object) - dbgap_qs = dbGaPApplication.objects.filter( - principal_investigator__study_sites=self.object - ) + dbgap_qs = dbGaPApplication.objects.filter(principal_investigator__study_sites=self.object) cdsa_qs = MemberAgreement.objects.filter(study_site=self.object) return [user_qs, dbgap_qs, cdsa_qs] @@ -162,9 +148,7 @@ class AvailableDataList(AnVILConsortiumManagerStaffViewRequired, SingleTableView table_class = tables.AvailableDataTable -class AvailableDataDetail( - AnVILConsortiumManagerStaffViewRequired, SingleTableMixin, DetailView -): +class AvailableDataDetail(AnVILConsortiumManagerStaffViewRequired, SingleTableMixin, DetailView): """View to show details about a `AvailableData`.""" model = models.AvailableData @@ -177,7 +161,6 @@ def get_table_data(self): class DataSummaryView(LoginRequiredMixin, TemplateView): - template_name = "primed_anvil/data_summary.html" def get_context_data(self, **kwargs): @@ -187,15 +170,10 @@ def get_context_data(self, **kwargs): return context -class PhenotypeInventoryInputsView( - AnVILConsortiumManagerStaffViewRequired, TemplateView -): - +class PhenotypeInventoryInputsView(AnVILConsortiumManagerStaffViewRequired, TemplateView): template_name = "primed_anvil/phenotype_inventory_inputs.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["workspaces_input"] = json.dumps( - helpers.get_workspaces_for_phenotype_inventory(), indent=2 - ) + context["workspaces_input"] = json.dumps(helpers.get_workspaces_for_phenotype_inventory(), indent=2) return context diff --git a/primed/users/adapters.py b/primed/users/adapters.py index 24e6d91e..a2b33115 100644 --- a/primed/users/adapters.py +++ b/primed/users/adapters.py @@ -32,8 +32,7 @@ def update_user_info(self, user, extra_data: Dict, apply_update=True): user_changed = False if user.name != full_name: logger.info( - f"[SocialAccountAdatpter:update_user_name] user {user} " - f"name updated from {user.name} to {full_name}" + f"[SocialAccountAdatpter:update_user_name] user {user} " f"name updated from {user.name} to {full_name}" ) user.name = full_name user_changed = True @@ -63,9 +62,7 @@ def update_user_study_sites(self, user, extra_data: Dict, apply_update=True): user_sites_updated = False if research_center_or_site: if not isinstance(research_center_or_site, list): - raise ImproperlyConfigured( - "sociallogin.extra_data.study_site_or_center should be a list" - ) + raise ImproperlyConfigured("sociallogin.extra_data.study_site_or_center should be a list") for rc_name in research_center_or_site: try: rc = StudySite.objects.get(short_name=rc_name) @@ -106,14 +103,10 @@ def update_user_groups(self, user, extra_data: Dict): added_groups = [] removed_groups = [] if not isinstance(managed_scope_status, dict): - raise ImproperlyConfigured( - "sociallogin.extra_data.managed_scope_status should be a dict" - ) + raise ImproperlyConfigured("sociallogin.extra_data.managed_scope_status should be a dict") else: for group_name, user_has_group in managed_scope_status.items(): - user_group, was_created = Group.objects.get_or_create( - name=group_name - ) + user_group, was_created = Group.objects.get_or_create(name=group_name) if was_created: logger.debug( f"[SocialAccountAdatpter:update_user_data] created mapped user group: {group_name}" @@ -134,7 +127,6 @@ def update_user_groups(self, user, extra_data: Dict): ) def update_user_data(self, sociallogin: Any): - logger.debug( f"[SocialAccountAdatpter:update_user_data] account: {sociallogin.account} " f"extra_data {sociallogin.account.extra_data} " diff --git a/primed/users/admin.py b/primed/users/admin.py index 7d71a538..ee8da869 100644 --- a/primed/users/admin.py +++ b/primed/users/admin.py @@ -10,7 +10,6 @@ @admin.register(User) class UserAdmin(auth_admin.UserAdmin): - form = UserChangeForm add_form = UserCreationForm fieldsets = ( diff --git a/primed/users/audit.py b/primed/users/audit.py index 371d7760..c81fd479 100644 --- a/primed/users/audit.py +++ b/primed/users/audit.py @@ -69,9 +69,7 @@ def get_table_dictionary(self): if self.remote_user_data: row.update( { - "remote_user_id": self.remote_user_data.attributes.get( - "drupal_internal__uid" - ), + "remote_user_id": self.remote_user_data.attributes.get("drupal_internal__uid"), "remote_username": self.remote_user_data.attributes.get("name"), } ) @@ -140,42 +138,29 @@ def _run_audit(self): user_count = 0 while user_endpoint_url is not None: - users_endpoint = json_api.endpoint(user_endpoint_url) users_endpoint_response = users_endpoint.get() # If there are more, there will be a 'next' link - user_endpoint_url = users_endpoint_response.content.links.get( - "next", {} - ).get("href") + user_endpoint_url = users_endpoint_response.content.links.get("next", {}).get("href") for user in users_endpoint_response.data: drupal_uid = user.attributes.get("drupal_internal__uid") drupal_username = user.attributes.get("name") drupal_email = user.attributes.get("mail") drupal_firstname = user.attributes.get("field_given_first_name_s_") - drupal_lastname = user.attributes.get( - "field_examples_family_last_name_" - ) - drupal_full_name = " ".join( - part for part in (drupal_firstname, drupal_lastname) if part - ) - drupal_study_sites_rel = user.relationships.get( - "field_study_site_or_center" - ) + drupal_lastname = user.attributes.get("field_examples_family_last_name_") + drupal_full_name = " ".join(part for part in (drupal_firstname, drupal_lastname) if part) + drupal_study_sites_rel = user.relationships.get("field_study_site_or_center") drupal_user_study_site_shortnames = [] if drupal_study_sites_rel: for dss in drupal_study_sites_rel.data: study_site_uuid = dss.id study_site_info = study_sites[study_site_uuid] - drupal_user_study_site_shortnames.append( - study_site_info["short_name"] - ) - new_user_sites = StudySite.objects.filter( - short_name__in=drupal_user_study_site_shortnames - ) + drupal_user_study_site_shortnames.append(study_site_info["short_name"]) + new_user_sites = StudySite.objects.filter(short_name__in=drupal_user_study_site_shortnames) # no uid is blocked or anonymous if not drupal_uid: # potential blocked user, but will no longer have a drupal uid @@ -201,16 +186,12 @@ def _run_audit(self): uid=user.attributes["drupal_internal__uid"], provider=CustomProvider.id, ) - self.needs_action.append( - NewUser(local_user=sa, remote_user_data=user) - ) + self.needs_action.append(NewUser(local_user=sa, remote_user_data=user)) if sa: user_updates = {} if sa.user.name != drupal_full_name: - user_updates.update( - {"name": {"old": sa.user.name, "new": drupal_full_name}} - ) + user_updates.update({"name": {"old": sa.user.name, "new": drupal_full_name}}) sa.user.name = drupal_full_name if sa.user.username != drupal_username: user_updates.update( @@ -223,21 +204,16 @@ def _run_audit(self): ) sa.user.username = drupal_username if sa.user.email != drupal_email: - user_updates.update( - {"email": {"old": sa.user.email, "new": drupal_email}} - ) + user_updates.update({"email": {"old": sa.user.email, "new": drupal_email}}) sa.user.email = drupal_email if sa.user.is_active is False: user_updates.update({"is_active": {"old": False, "new": True}}) sa.user.is_active = True - prev_user_site_names = set( - sa.user.study_sites.all().values_list("short_name", flat=True) - ) + prev_user_site_names = set(sa.user.study_sites.all().values_list("short_name", flat=True)) new_user_site_names = set(drupal_user_study_site_shortnames) if prev_user_site_names != new_user_site_names: - user_updates.update( { "sites": { @@ -247,9 +223,7 @@ def _run_audit(self): } ) # do not remove from sites by default - removed_sites = prev_user_site_names.difference( - new_user_site_names - ) + removed_sites = prev_user_site_names.difference(new_user_site_names) new_sites = new_user_site_names.difference(prev_user_site_names) if settings.DRUPAL_DATA_AUDIT_REMOVE_USER_SITES is True: @@ -282,9 +256,7 @@ def _run_audit(self): ) ) else: - self.verified.append( - VerifiedUser(local_user=sa, remote_user_data=user) - ) + self.verified.append(VerifiedUser(local_user=sa, remote_user_data=user)) drupal_uids.add(drupal_uid) user_count += 1 @@ -314,9 +286,7 @@ def _run_audit(self): handled = True self.needs_action.append(RemoveUser(local_user=uda)) if handled is False: - self.errors.append( - RemoveUser(local_user=uda, note=f"Over Threshold {over_threshold}") - ) + self.errors.append(RemoveUser(local_user=uda, note=f"Over Threshold {over_threshold}")) inactive_anvil_users = Account.objects.filter( Q(user__is_active=False) | Q(user__id__in=user_ids_to_check), @@ -327,9 +297,7 @@ def _run_audit(self): InactiveAnvilUser( anvil_account=inactive_anvil_user, anvil_groups=list( - inactive_anvil_user.groupaccountmembership_set.all().values_list( - "group__name", flat=True - ) + inactive_anvil_user.groupaccountmembership_set.all().values_list("group__name", flat=True) ), ) ) @@ -419,7 +387,6 @@ def _run_audit(self): json_api = get_drupal_json_api() study_sites = get_study_sites(json_api=json_api) for study_site_info in study_sites.values(): - short_name = study_site_info["short_name"] full_name = study_site_info["full_name"] node_id = study_site_info["node_id"] @@ -435,16 +402,12 @@ def _run_audit(self): short_name=short_name, full_name=full_name, ) - self.needs_action.append( - NewSite(remote_site_data=study_site_info, local_site=study_site) - ) + self.needs_action.append(NewSite(remote_site_data=study_site_info, local_site=study_site)) else: study_site_updates = {} if study_site.full_name != full_name: - study_site_updates.update( - {"full_name": {"old": study_site.full_name, "new": full_name}} - ) + study_site_updates.update({"full_name": {"old": study_site.full_name, "new": full_name}}) study_site.full_name = full_name if study_site.short_name != short_name: @@ -469,22 +432,15 @@ def _run_audit(self): ) ) else: - self.verified.append( - VerifiedSite( - local_site=study_site, remote_site_data=study_site_info - ) - ) + self.verified.append(VerifiedSite(local_site=study_site, remote_site_data=study_site_info)) invalid_study_sites = StudySite.objects.exclude(drupal_node_id__in=valid_nodes) for iss in invalid_study_sites: - self.errors.append( - RemoveSite(local_site=iss, note=self.ISSUE_TYPE_LOCAL_SITE_INVALID) - ) + self.errors.append(RemoveSite(local_site=iss, note=self.ISSUE_TYPE_LOCAL_SITE_INVALID)) def get_drupal_json_api(): - json_api_client_id = settings.DRUPAL_API_CLIENT_ID json_api_client_secret = settings.DRUPAL_API_CLIENT_SECRET diff --git a/primed/users/forms.py b/primed/users/forms.py index 5076a427..2ea6f0a3 100644 --- a/primed/users/forms.py +++ b/primed/users/forms.py @@ -17,9 +17,7 @@ class UserCreationForm(admin_forms.UserCreationForm): class Meta(admin_forms.UserCreationForm.Meta): model = User - error_messages = { - "username": {"unique": _("This username has already been taken.")} - } + error_messages = {"username": {"unique": _("This username has already been taken.")}} class UserLookupForm(Bootstrap5MediaFormMixin, forms.Form): diff --git a/primed/users/tests/factories.py b/primed/users/tests/factories.py index 6a0e0384..b99e1fc9 100644 --- a/primed/users/tests/factories.py +++ b/primed/users/tests/factories.py @@ -7,7 +7,6 @@ class UserFactory(DjangoModelFactory): - username = Faker("user_name") email = Faker("email") name = Faker("name") diff --git a/primed/users/tests/test_adapters.py b/primed/users/tests/test_adapters.py index c5c24a83..cae571dc 100644 --- a/primed/users/tests/test_adapters.py +++ b/primed/users/tests/test_adapters.py @@ -53,9 +53,7 @@ def test_drupal_social_login_adapter(self): sociallogin = SocialLogin(user=user, account=account) complete_social_login(request, sociallogin) - user = User.objects.get( - **{account_settings.USER_MODEL_USERNAME_FIELD: old_username} - ) + user = User.objects.get(**{account_settings.USER_MODEL_USERNAME_FIELD: old_username}) assert SocialAccount.objects.filter(user=user, uid=account.uid).exists() is True assert user.name == old_name assert user.username == old_username @@ -101,9 +99,7 @@ def test_update_user_study_sites_add(self): user.save() - adapter.update_user_study_sites( - user, dict(study_site_or_center=[rc1.short_name]) - ) + adapter.update_user_study_sites(user, dict(study_site_or_center=[rc1.short_name])) assert user.study_sites.filter(pk=rc1.pk).exists() assert user.study_sites.all().count() == 1 @@ -121,9 +117,7 @@ def test_update_user_study_sites_remove(self): user.study_sites.add(rc1, rc2) assert user.study_sites.all().count() == 2 - adapter.update_user_study_sites( - user, dict(study_site_or_center=[rc1.short_name]) - ) + adapter.update_user_study_sites(user, dict(study_site_or_center=[rc1.short_name])) assert user.study_sites.filter(pk=rc1.pk).exists() assert user.study_sites.all().count() == 1 @@ -135,10 +129,7 @@ def test_update_user_study_sites_unknown(self, settings): adapter.update_user_study_sites(user, dict(study_site_or_center=["UNKNOWN"])) assert user.study_sites.all().count() == 0 assert len(mail.outbox) == 1 - assert ( - mail.outbox[0].subject - == f"{settings.EMAIL_SUBJECT_PREFIX}Missing StudySite" - ) + assert mail.outbox[0].subject == f"{settings.EMAIL_SUBJECT_PREFIX}Missing StudySite" def test_update_study_sites_malformed(self): adapter = SocialAccountAdapter() @@ -157,9 +148,7 @@ def test_update_user_groups_add(self): user.save() - adapter.update_user_groups( - user, extra_data=dict(managed_scope_status={rc1.name: True}) - ) + adapter.update_user_groups(user, extra_data=dict(managed_scope_status={rc1.name: True})) assert user.groups.filter(pk=rc1.pk).exists() assert user.groups.all().count() == 1 @@ -173,9 +162,7 @@ def test_update_user_groups_add_new(self): user.save() new_group_name = "NEW_GROUP" - adapter.update_user_groups( - user, extra_data=dict(managed_scope_status={new_group_name: True}) - ) + adapter.update_user_groups(user, extra_data=dict(managed_scope_status={new_group_name: True})) assert user.groups.filter(name=new_group_name).exists() assert user.groups.all().count() == 1 diff --git a/primed/users/tests/test_audit.py b/primed/users/tests/test_audit.py index 155d114e..412618f2 100644 --- a/primed/users/tests/test_audit.py +++ b/primed/users/tests/test_audit.py @@ -162,13 +162,9 @@ def add_fake_study_sites_response(self): ) def add_fake_users_response(self): - url_path = ( - f"{settings.DRUPAL_SITE_URL}/{settings.DRUPAL_API_REL_PATH}/user/user/" - ) + url_path = f"{settings.DRUPAL_SITE_URL}/{settings.DRUPAL_API_REL_PATH}/user/user/" TEST_USER_DATA[0].field_study_site_or_center = [TEST_STUDY_SITE_DATA[0]] - user_data = UserSchema( - include_data=("field_study_site_or_center",), many=True - ).dump(TEST_USER_DATA) + user_data = UserSchema(include_data=("field_study_site_or_center",), many=True).dump(TEST_USER_DATA) responses.get( url=url_path, @@ -186,10 +182,7 @@ def get_fake_json_api(self): @responses.activate def test_get_json_api(self): json_api = self.get_fake_json_api() - assert ( - json_api.requests.config.AUTH._client.token["access_token"] - == self.token["access_token"] - ) + assert json_api.requests.config.AUTH._client.token["access_token"] == self.token["access_token"] @responses.activate def test_get_study_sites(self): @@ -198,19 +191,9 @@ def test_get_study_sites(self): study_sites = audit.get_study_sites(json_api=json_api) for test_study_site in TEST_STUDY_SITE_DATA: - - assert ( - test_study_site.field_long_name - == study_sites[test_study_site.drupal_internal__nid]["full_name"] - ) - assert ( - test_study_site.title - == study_sites[test_study_site.drupal_internal__nid]["short_name"] - ) - assert ( - test_study_site.drupal_internal__nid - == study_sites[test_study_site.drupal_internal__nid]["node_id"] - ) + assert test_study_site.field_long_name == study_sites[test_study_site.drupal_internal__nid]["full_name"] + assert test_study_site.title == study_sites[test_study_site.drupal_internal__nid]["short_name"] + assert test_study_site.drupal_internal__nid == study_sites[test_study_site.drupal_internal__nid]["node_id"] @responses.activate def test_audit_study_sites_no_update(self): @@ -273,9 +256,7 @@ def test_audit_study_sites_with_site_update(self): @responses.activate def test_audit_study_sites_with_extra_site(self): - StudySite.objects.create( - drupal_node_id=99, short_name="ExtraSite", full_name="ExtraSiteLong" - ) + StudySite.objects.create(drupal_node_id=99, short_name="ExtraSite", full_name="ExtraSiteLong") self.get_fake_json_api() self.add_fake_study_sites_response() site_audit = audit.SiteAudit(apply_changes=True) @@ -307,10 +288,7 @@ def test_full_user_audit(self): assert users.first().email == TEST_USER_DATA[0].mail assert users.first().username == TEST_USER_DATA[0].name assert users.first().study_sites.count() == 1 - assert ( - users.first().study_sites.first().short_name - == TEST_STUDY_SITE_DATA[0].title - ) + assert users.first().study_sites.first().short_name == TEST_STUDY_SITE_DATA[0].title @responses.activate def test_full_user_audit_check_only(self): @@ -449,9 +427,7 @@ def test_user_audit_remove_user_only_inform(self): full_name=TEST_STUDY_SITE_DATA[0].field_long_name, ) - new_user = get_user_model().objects.create( - username="username2", email="useremail2", name="user fullname2" - ) + new_user = get_user_model().objects.create(username="username2", email="useremail2", name="user fullname2") SocialAccount.objects.create( user=new_user, uid=999, @@ -476,9 +452,7 @@ def test_user_audit_remove_user(self): full_name=TEST_STUDY_SITE_DATA[0].field_long_name, ) - new_user = get_user_model().objects.create( - username="username2", email="useremail2", name="user fullname2" - ) + new_user = get_user_model().objects.create(username="username2", email="useremail2", name="user fullname2") SocialAccount.objects.create( user=new_user, uid=999, @@ -504,9 +478,7 @@ def test_user_audit_remove_user(self): self.assertFalse(user_audit.ok()) self.assertEqual(len(user_audit.errors), 1) self.assertEqual(user_audit.errors[0].anvil_account, new_anvil_account) - self.assertIn( - "InactiveAnvilUser", user_audit.get_errors_table().render_to_text() - ) + self.assertIn("InactiveAnvilUser", user_audit.get_errors_table().render_to_text()) self.assertEqual(len(user_audit.needs_action), 2) new_user.refresh_from_db() self.assertFalse(new_user.is_active) @@ -524,32 +496,24 @@ def test_user_audit_remove_user_threshold(self): ) SocialAccount.objects.create( - user=get_user_model().objects.create( - username="username2", email="useremail2", name="user fullname2" - ), + user=get_user_model().objects.create(username="username2", email="useremail2", name="user fullname2"), uid=996, provider=CustomProvider.id, ) SocialAccount.objects.create( - user=get_user_model().objects.create( - username="username3", email="useremail3", name="user fullname3" - ), + user=get_user_model().objects.create(username="username3", email="useremail3", name="user fullname3"), uid=997, provider=CustomProvider.id, ) SocialAccount.objects.create( - user=get_user_model().objects.create( - username="username4", email="useremail4", name="user fullname4" - ), + user=get_user_model().objects.create(username="username4", email="useremail4", name="user fullname4"), uid=998, provider=CustomProvider.id, ) SocialAccount.objects.create( - user=get_user_model().objects.create( - username="username5", email="useremail5", name="user fullname5" - ), + user=get_user_model().objects.create(username="username5", email="useremail5", name="user fullname5"), uid=999, provider=CustomProvider.id, ) @@ -561,9 +525,7 @@ def test_user_audit_remove_user_threshold(self): self.assertEqual(len(user_audit.needs_action), 1) self.assertEqual(user_audit.errors[0].note, "Over Threshold True") # Run again with ignore threshold, should move from error to needs action - user_audit = audit.UserAudit( - apply_changes=False, ignore_deactivate_threshold=True - ) + user_audit = audit.UserAudit(apply_changes=False, ignore_deactivate_threshold=True) user_audit.run_audit() self.assertFalse(user_audit.ok()) self.assertEqual(len(user_audit.errors), 0) @@ -583,16 +545,13 @@ def test_sync_drupal_data_command(self): @responses.activate def test_sync_drupal_data_command_with_issues(self): - StudySite.objects.create( drupal_node_id="999999", short_name=TEST_STUDY_SITE_DATA[0].title, full_name=TEST_STUDY_SITE_DATA[0].field_long_name, ) - new_user = get_user_model().objects.create( - username="username2", email="useremail2", name="user fullname2" - ) + new_user = get_user_model().objects.create(username="username2", email="useremail2", name="user fullname2") SocialAccount.objects.create( user=new_user, uid=999, diff --git a/primed/users/tests/test_forms.py b/primed/users/tests/test_forms.py index 8e34e7bb..81fe9d9b 100644 --- a/primed/users/tests/test_forms.py +++ b/primed/users/tests/test_forms.py @@ -1,6 +1,7 @@ """ Module for all Form Tests. """ + import pytest from django.test import TestCase from django.utils.translation import gettext_lazy as _ @@ -42,7 +43,6 @@ def test_username_validation_error_msg(self, user: User): class UserLookupFormTest(TestCase): - form_class = UserLookupForm def test_valid(self): diff --git a/primed/users/tests/test_urls.py b/primed/users/tests/test_urls.py index 9d224344..4ed608c7 100644 --- a/primed/users/tests/test_urls.py +++ b/primed/users/tests/test_urls.py @@ -7,10 +7,7 @@ def test_detail(user: User): - assert ( - reverse("users:detail", kwargs={"username": user.username}) - == f"/users/{user.username}/" - ) + assert reverse("users:detail", kwargs={"username": user.username}) == f"/users/{user.username}/" assert resolve(f"/users/{user.username}/").view_name == "users:detail" diff --git a/primed/users/tests/test_views.py b/primed/users/tests/test_views.py index 8fcced81..b4785706 100644 --- a/primed/users/tests/test_views.py +++ b/primed/users/tests/test_views.py @@ -113,14 +113,10 @@ def test_authenticated(self, client, user: User, rf: RequestFactory): assert response.status_code == 200 - def test_authenticated_with_verified_account( - self, client, user: User, rf: RequestFactory - ): + def test_authenticated_with_verified_account(self, client, user: User, rf: RequestFactory): client.force_login(user) user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) AccountFactory.create(email="foo@bar.com", user=user, verified=True) user_detail_url = reverse("users:detail", kwargs=dict(username=user.username)) @@ -128,14 +124,10 @@ def test_authenticated_with_verified_account( assert response.status_code == 200 - def test_authenticated_with_user_email_entry( - self, client, user: User, rf: RequestFactory - ): + def test_authenticated_with_user_email_entry(self, client, user: User, rf: RequestFactory): client.force_login(user) user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) UserEmailEntryFactory.create(email="foo@bar.com", user=user) user_detail_url = reverse("users:detail", kwargs=dict(username=user.username)) @@ -143,9 +135,7 @@ def test_authenticated_with_user_email_entry( assert response.status_code == 200 - def test_authenticated_with_unverified_account( - self, client, user: User, rf: RequestFactory - ): + def test_authenticated_with_unverified_account(self, client, user: User, rf: RequestFactory): client.force_login(user) AccountFactory.create(email="foo@bar.com", user=user, verified=False) user_detail_url = reverse("users:detail", kwargs=dict(username=user.username)) @@ -153,9 +143,7 @@ def test_authenticated_with_unverified_account( assert response.status_code == 200 - def test_authenticated_with_study_sites( - self, client, user: User, rf: RequestFactory - ): + def test_authenticated_with_study_sites(self, client, user: User, rf: RequestFactory): client.force_login(user) study_site = StudySiteFactory.create() user.study_sites.add(study_site) @@ -177,9 +165,7 @@ def test_not_authenticated(self, user: User, rf: RequestFactory): def test_staff_view_links(self, client, user: User, rf: RequestFactory): """Link to ACM account page is in response for users with STAFF_VIEW permission.""" user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME - ) + Permission.objects.get(codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) client.force_login(user) account = AccountFactory.create(email="foo@bar.com", user=user, verified=True) @@ -189,11 +175,7 @@ def test_staff_view_links(self, client, user: User, rf: RequestFactory): def test_view_links(self, client, user: User, rf: RequestFactory): """Link to ACM account page is not in response for users with VIEW permission.""" - user.user_permissions.add( - Permission.objects.get( - codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME - ) - ) + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) client.force_login(user) account = AccountFactory.create(email="foo@bar.com", user=user, verified=True) user_detail_url = reverse("users:detail", kwargs=dict(username=user.username)) @@ -220,9 +202,7 @@ def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." # Need a client for redirects. response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_with_user_permission(self): """Returns successful response code.""" @@ -237,10 +217,7 @@ def test_returns_all_instances(self): request = self.factory.get(self.get_url()) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] # The test user plus the ones we created in this test. self.assertEqual(len(returned_ids), 10) self.assertEqual( @@ -255,10 +232,7 @@ def test_returns_correct_instance_match_name(self): request = self.factory.get(self.get_url(), {"q": "First Last"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], instance.pk) @@ -269,10 +243,7 @@ def test_returns_correct_instance_starting_with_query_name(self): request = self.factory.get(self.get_url(), {"q": "Firs"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], instance.pk) @@ -283,10 +254,7 @@ def test_returns_correct_instance_containing_query_short_name(self): request = self.factory.get(self.get_url(), {"q": "ast"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], instance.pk) @@ -297,10 +265,7 @@ def test_returns_correct_instance_case_insensitive_name(self): request = self.factory.get(self.get_url(), {"q": "first last"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], instance.pk) @@ -311,10 +276,7 @@ def test_returns_correct_instance_match_email(self): request = self.factory.get(self.get_url(), {"q": "foo@bar.com"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], instance.pk) @@ -325,10 +287,7 @@ def test_returns_correct_instance_starting_with_query_email(self): request = self.factory.get(self.get_url(), {"q": "foo"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], instance.pk) @@ -339,10 +298,7 @@ def test_returns_correct_instance_containing_query_email(self): request = self.factory.get(self.get_url(), {"q": "bar"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], instance.pk) @@ -353,10 +309,7 @@ def test_returns_correct_instance_case_insensitive_email(self): request = self.factory.get(self.get_url(), {"q": "FOO@BAR.COM"}) request.user = self.user response = self.get_view()(request) - returned_ids = [ - int(x["id"]) - for x in json.loads(response.content.decode("utf-8"))["results"] - ] + returned_ids = [int(x["id"]) for x in json.loads(response.content.decode("utf-8"))["results"]] self.assertEqual(len(returned_ids), 1) self.assertEqual(returned_ids[0], instance.pk) @@ -376,9 +329,7 @@ def test_get_selected_result_label(self): request.user = self.user view = UserAutocompleteView() view.setup(request) - self.assertEqual( - view.get_selected_result_label(instance), "First Last (foo@bar.com)" - ) + self.assertEqual(view.get_selected_result_label(instance), "First Last (foo@bar.com)") class UserLookup(TestCase): @@ -405,9 +356,7 @@ def test_form_class(self): def test_view_redirect_not_logged_in(self): "View redirects to login view when user is not logged in." response = self.client.get(self.get_url()) - self.assertRedirects( - response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url() - ) + self.assertRedirects(response, resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url()) def test_status_code_with_user_permission(self): """Returns successful response code.""" diff --git a/primed/users/views.py b/primed/users/views.py index 07133ef7..881fc2ba 100644 --- a/primed/users/views.py +++ b/primed/users/views.py @@ -13,7 +13,6 @@ class UserDetailView(LoginRequiredMixin, DetailView): - model = User slug_field = "username" slug_url_kwarg = "username" @@ -23,7 +22,6 @@ class UserDetailView(LoginRequiredMixin, DetailView): class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): - model = User fields = ["name"] success_message = _("Information successfully updated") @@ -39,7 +37,6 @@ def get_object(self): class UserRedirectView(LoginRequiredMixin, RedirectView): - permanent = False def get_redirect_url(self): From 0a077847788383fd8619acfe0d5bbe7cfd04ebcf Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 29 Apr 2024 15:38:49 -0700 Subject: [PATCH 59/60] Replace black/isort/flake8 with ruff in pre-commit config --- .pre-commit-config.yaml | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3b9bd274..38956395 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,22 +10,15 @@ repos: - id: end-of-file-fixer - id: check-yaml - - repo: https://github.com/psf/black - rev: 22.3.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.4.2 hooks: - - id: black - - - repo: https://github.com/timothycrosley/isort - rev: 5.12.0 - hooks: - - id: isort - - - repo: https://github.com/PyCQA/flake8 - rev: 7.0.0 - hooks: - - id: flake8 - args: ['--config=setup.cfg'] - additional_dependencies: [flake8-isort] + # Run the linter. + - id: ruff + args: [ --fix ] + # Run the formatter. + - id: ruff-format - repo: https://github.com/gitleaks/gitleaks rev: v8.16.1 From 3dfbbc6d8f82988d348ac7875fc50cdc18701595 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 29 Apr 2024 15:55:09 -0700 Subject: [PATCH 60/60] Remove flake8, isort, and black from requirements file --- requirements/dev-requirements.in | 8 +++--- requirements/dev-requirements.txt | 44 ------------------------------- 2 files changed, 4 insertions(+), 48 deletions(-) diff --git a/requirements/dev-requirements.in b/requirements/dev-requirements.in index 37e02e6e..79a043ec 100644 --- a/requirements/dev-requirements.in +++ b/requirements/dev-requirements.in @@ -17,10 +17,10 @@ sphinx-autobuild # https://github.com/GaretJax/sphinx-autobuild # Code quality # ------------------------------------------------------------------------------ -flake8 # https://github.com/PyCQA/flake8 -flake8-isort # https://github.com/gforcada/flake8-isort -black # https://github.com/psf/black -pylint-django # https://github.com/PyCQA/pylint-django +# flake8 # https://github.com/PyCQA/flake8 +# flake8-isort # https://github.com/gforcada/flake8-isort +# black # https://github.com/psf/black +# pylint-django # https://github.com/PyCQA/pylint-django pre-commit # https://github.com/pre-commit/pre-commit ruff # https://github.com/astral-sh/ruff diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 133d6577..08c17266 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -10,8 +10,6 @@ asgiref==3.7.2 # via # -c requirements.txt # django -astroid==3.0.3 - # via pylint asttokens==2.4.1 # via stack-data babel==2.14.0 @@ -22,8 +20,6 @@ backports-zoneinfo==0.2.1 # via # -c requirements.txt # django -black==22.12.0 - # via -r dev-requirements.in certifi==2023.11.17 # via # -c requirements.txt @@ -39,15 +35,12 @@ charset-normalizer==3.3.2 click==8.1.3 # via # -c requirements.txt - # black colorama==0.4.6 # via sphinx-autobuild decorator==5.1.1 # via # ipdb # ipython -dill==0.3.8 - # via pylint distlib==0.3.8 # via virtualenv django==4.2.11 @@ -68,12 +61,6 @@ executing==2.0.1 # via stack-data filelock==3.13.1 # via virtualenv -flake8==7.0.0 - # via - # -r dev-requirements.in - # flake8-isort -flake8-isort==6.1.1 - # via -r dev-requirements.in identify==2.5.34 # via pre-commit idna==3.3 @@ -91,10 +78,6 @@ ipdb==0.13.13 # via -r dev-requirements.in ipython==8.12.3 # via ipdb -isort==5.13.2 - # via - # flake8-isort - # pylint jedi==0.19.1 # via ipython jinja2==3.1.3 @@ -107,15 +90,10 @@ markupsafe==2.1.5 # werkzeug matplotlib-inline==0.1.6 # via ipython -mccabe==0.7.0 - # via - # flake8 - # pylint mypy==1.9.0 # via -r dev-requirements.in mypy-extensions==1.0.0 # via - # black # mypy nodeenv==1.8.0 # via pre-commit @@ -126,16 +104,12 @@ packaging==21.3 # sphinx parso==0.8.3 # via jedi -pathspec==0.12.1 - # via black pexpect==4.9.0 # via ipython pickleshare==0.7.5 # via ipython platformdirs==4.1.0 # via - # black - # pylint # virtualenv pre-commit==3.5.0 # via -r dev-requirements.in @@ -145,22 +119,10 @@ ptyprocess==0.7.0 # via pexpect pure-eval==0.2.2 # via stack-data -pycodestyle==2.11.1 - # via flake8 -pyflakes==3.2.0 - # via flake8 pygments==2.17.2 # via # ipython # sphinx -pylint==3.0.3 - # via - # pylint-django - # pylint-plugin-utils -pylint-django==2.5.5 - # via -r dev-requirements.in -pylint-plugin-utils==0.8.2 - # via pylint-django pyparsing==3.1.1 # via # -c requirements.txt @@ -218,13 +180,9 @@ tomli==2.0.1 # via # -c requirements.txt # -c test-requirements.txt - # black # django-stubs # ipdb # mypy - # pylint -tomlkit==0.12.3 - # via pylint tornado==6.4 # via livereload traitlets==5.14.1 @@ -243,12 +201,10 @@ typing-extensions==4.8.0 # -c test-requirements.txt # asgiref # astroid - # black # django-stubs # django-stubs-ext # ipython # mypy - # pylint urllib3==2.1.0 # via # -c requirements.txt