diff --git a/.env.dist b/.env.dist index 518ad98a..b9e98292 100644 --- a/.env.dist +++ b/.env.dist @@ -23,3 +23,8 @@ 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= +DRUPAL_API_REL_PATH= diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8a8fd23..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 @@ -50,7 +29,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: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 842469d1..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: 3.9.2 - 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 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/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/) 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 new file mode 100644 index 00000000..fad9de8d --- /dev/null +++ b/add_phenotype_inventory_input_example_data.py @@ -0,0 +1,44 @@ +# 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) diff --git a/config/settings/base.py b/config/settings/base.py index 3ef49a5a..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", @@ -403,3 +397,9 @@ # 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="") +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) 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 b32edc11..78e6f169 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 @@ -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"] = DataPrepWorkspaceTable( - 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/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..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" @@ -175,21 +171,18 @@ 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() 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 - 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 @@ -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 4fd93ce1..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,19 +277,16 @@ 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 "—" -class CDSAWorkspaceStaffTable(tables.Table): +class CDSAWorkspaceUserTable(tables.Table): """A table for the CDSAWorkspace model.""" name = tables.Column(linkify=True) - billing_project = tables.Column(linkify=True) cdsaworkspace__data_use_permission__abbreviation = tables.Column( verbose_name="DUO permission", linkify=lambda record: record.cdsaworkspace.data_use_permission.get_absolute_url(), @@ -329,7 +312,6 @@ class Meta: model = Workspace fields = ( "name", - "billing_project", "cdsaworkspace__study", "cdsaworkspace__data_use_permission__abbreviation", "cdsaworkspace__data_use_modifiers", @@ -351,27 +333,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 +350,3 @@ class Meta: "cdsaworkspace__gsr_restricted", ) order_by = ("name",) - - def render_cdsaworkspace__requires_study_review(self, record): - try: - if record.cdsaworkspace.get_primary_cdsa().requires_study_review: - icon = "dash-circle-fill" - color = "#ffc107" - else: - return "" - except models.DataAffiliateAgreement.DoesNotExist: - icon = "question-circle-fill" - color = "red" - return mark_safe(f'') diff --git a/primed/cdsa/tests/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 d355392c..9446ac25 100644 --- a/primed/cdsa/tests/test_migrations.py +++ b/primed/cdsa/tests/test_migrations.py @@ -1,11 +1,6 @@ """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): @@ -135,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, @@ -149,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, @@ -163,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, @@ -178,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, @@ -193,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 beb8e4e8..e772d1f2 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, @@ -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): @@ -7436,6 +6817,25 @@ def test_render_duo_modifiers(self): self.assertContains(response, modifiers[0].abbreviation) self.assertContains(response, modifiers[1].abbreviation) + def test_associated_data_prep_view_user(self): + """View users do not see the associated data prep section""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) + + obj = factories.CDSAWorkspaceFactory.create() + DataPrepWorkspaceFactory.create(target_workspace=obj.workspace) + self.client.force_login(user) + response = self.client.get(obj.get_absolute_url()) + self.assertNotContains(response, "Associated data prep workspaces") + + def test_associated_data_prep_staff_view_user(self): + """Staff view users do see the associated data prep section.""" + obj = factories.CDSAWorkspaceFactory.create() + DataPrepWorkspaceFactory.create(target_workspace=obj.workspace) + self.client.force_login(self.user) + response = self.client.get(obj.get_absolute_url()) + self.assertContains(response, "Associated data prep workspaces") + def test_associated_data_prep_workspaces_context_exists(self): obj = factories.CDSAWorkspaceFactory.create() self.client.force_login(self.user) @@ -7443,20 +6843,16 @@ 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): 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, @@ -7464,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, @@ -7494,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) @@ -7504,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) @@ -7514,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) @@ -7548,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.""" @@ -7559,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( @@ -7640,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 ) @@ -7657,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" @@ -7774,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/tables.py b/primed/collaborative_analysis/tables.py index a17c5cb1..dfaeddb7 100644 --- a/primed/collaborative_analysis/tables.py +++ b/primed/collaborative_analysis/tables.py @@ -2,12 +2,10 @@ from anvil_consortium_manager.models import Workspace -class CollaborativeAnalysisWorkspaceStaffTable(tables.Table): +class CollaborativeAnalysisWorkspaceUserTable(tables.Table): """Class to render a table of Workspace objects with CollaborativeAnalysisWorkspace data.""" name = tables.columns.Column(linkify=True) - billing_project = tables.Column(linkify=True) - collaborativeanalysisworkspace__custodian = tables.Column(linkify=True) number_source_workspaces = tables.columns.Column( accessor="pk", verbose_name="Number of source workspaces", @@ -18,7 +16,6 @@ class Meta: model = Workspace fields = ( "name", - "billing_project", "collaborativeanalysisworkspace__custodian", "number_source_workspaces", ) @@ -29,16 +26,12 @@ def render_number_source_workspaces(self, record): return record.collaborativeanalysisworkspace.source_workspaces.count() -class CollaborativeAnalysisWorkspaceUserTable(tables.Table): +class CollaborativeAnalysisWorkspaceStaffTable(CollaborativeAnalysisWorkspaceUserTable): """Class to render a table of Workspace objects with CollaborativeAnalysisWorkspace data.""" name = tables.columns.Column(linkify=True) - billing_project = tables.Column() - number_source_workspaces = tables.columns.Column( - accessor="pk", - verbose_name="Number of source workspaces", - orderable=False, - ) + billing_project = tables.Column(linkify=True) + collaborativeanalysisworkspace__custodian = tables.Column(linkify=True) class Meta: model = Workspace @@ -49,7 +42,3 @@ class Meta: "number_source_workspaces", ) order_by = ("name",) - - def render_number_source_workspaces(self, record): - """Render the number of source workspaces.""" - return record.collaborativeanalysisworkspace.source_workspaces.count() diff --git a/primed/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 626a3c10..d1eaf728 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 @@ -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"] = DataPrepWorkspaceTable( - 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/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..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.", @@ -226,6 +218,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. @@ -233,29 +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." - ) - - 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, - }, - ) + 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 08443706..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): @@ -74,11 +70,10 @@ def render_dbgap_phs(self, value): return "phs{0:06d}".format(value) -class dbGaPWorkspaceStaffTable(tables.Table): +class dbGaPWorkspaceUserTable(tables.Table): """Class to render a table of Workspace objects with dbGaPWorkspace workspace data.""" name = tables.columns.Column(linkify=True) - billing_project = tables.Column(linkify=True) dbgap_accession = dbGaPAccessionColumn( accessor="dbgapworkspace__get_dbgap_accession", dbgap_link_accessor="dbgapworkspace__get_dbgap_link", @@ -88,14 +83,7 @@ class dbGaPWorkspaceStaffTable(tables.Table): "dbgapworkspace__dbgap_participant_set", ), ) - 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__dbgap_consent_abbreviation = tables.columns.Column(verbose_name="Consent") dbgapworkspace__gsr_restricted = BooleanIconColumn( orderable=False, true_icon="dash-circle-fill", true_color="#ffc107" ) @@ -105,43 +93,23 @@ class Meta: model = Workspace fields = ( "name", - "billing_project", "dbgap_accession", "dbgapworkspace__dbgap_consent_abbreviation", - "number_approved_dars", "dbgapworkspace__gsr_restricted", "is_shared", ) order_by = ("name",) - def render_number_approved_dars(self, record): - n = ( - record.dbgapworkspace.get_data_access_requests(most_recent=True) - .filter(dbgap_current_status=models.dbGaPDataAccessRequest.APPROVED) - .count() - ) - return n - -class dbGaPWorkspaceUserTable(tables.Table): +class dbGaPWorkspaceStaffTable(dbGaPWorkspaceUserTable): """Class to render a table of Workspace objects with dbGaPWorkspace workspace data.""" - name = tables.columns.Column(linkify=True) - billing_project = tables.Column() - dbgap_accession = dbGaPAccessionColumn( - accessor="dbgapworkspace__get_dbgap_accession", - dbgap_link_accessor="dbgapworkspace__get_dbgap_link", - order_by=( - "dbgapworkspace__dbgap_study_accession__dbgap_phs", - "dbgapworkspace__dbgap_version", - "dbgapworkspace__dbgap_participant_set", - ), - ) - dbgapworkspace__dbgap_consent_abbreviation = tables.columns.Column( - verbose_name="Consent" + billing_project = tables.Column(linkify=True) + number_approved_dars = tables.columns.Column( + accessor="pk", + verbose_name="Approved DARs", + orderable=False, ) - dbgapworkspace__gsr_restricted = BooleanIconColumn(orderable=False) - is_shared = WorkspaceSharedWithConsortiumColumn() class Meta: model = Workspace @@ -150,11 +118,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.""" @@ -283,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 c33fb131..e668b5f0 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, @@ -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()) @@ -953,6 +832,25 @@ def test_links_audit_access_view_permission(self): ), ) + def test_associated_data_prep_view_user(self): + """View users do not see the associated data prep section""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add(Permission.objects.get(codename=AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME)) + + obj = factories.dbGaPWorkspaceFactory.create() + DataPrepWorkspaceFactory.create(target_workspace=obj.workspace) + self.client.force_login(user) + response = self.client.get(obj.get_absolute_url()) + self.assertNotContains(response, "Associated data prep workspaces") + + def test_associated_data_prep_staff_view_user(self): + """Staff view users do see the associated data prep section.""" + obj = factories.dbGaPWorkspaceFactory.create() + DataPrepWorkspaceFactory.create(target_workspace=obj.workspace) + self.client.force_login(self.user) + response = self.client.get(obj.get_absolute_url()) + self.assertContains(response, "Associated data prep workspaces") + def test_associated_data_prep_workspaces_context_exists(self): obj = factories.dbGaPWorkspaceFactory.create() self.client.force_login(self.user) @@ -960,20 +858,16 @@ 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): 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, @@ -981,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, @@ -1011,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) @@ -1021,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) @@ -1031,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) @@ -1055,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" @@ -1073,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.""" @@ -1128,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") @@ -1207,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" @@ -1225,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, @@ -1264,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)], @@ -1330,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") @@ -1357,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)], @@ -1438,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): @@ -1469,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): @@ -1483,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): @@ -1495,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() @@ -1516,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): @@ -1530,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): @@ -1554,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)) @@ -1606,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.""" @@ -1617,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.""" @@ -1659,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) @@ -1691,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) @@ -1713,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") @@ -1737,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.""" @@ -1750,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): @@ -1760,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 @@ -1792,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, @@ -1806,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) @@ -1821,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()) @@ -1845,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, @@ -1966,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. @@ -2004,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. @@ -2040,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. @@ -2075,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"] @@ -2103,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"] @@ -2113,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) @@ -2124,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, @@ -2138,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"] @@ -2165,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() @@ -2191,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( @@ -2313,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( @@ -2370,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( @@ -2399,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( @@ -2417,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.""" @@ -2468,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], @@ -2494,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) @@ -2511,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], @@ -2566,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], @@ -2615,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.""" @@ -2644,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), @@ -2672,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.""" @@ -2695,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], @@ -2726,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.""" @@ -2741,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], @@ -2793,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], @@ -2806,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( @@ -2841,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], @@ -2854,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( @@ -2891,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): @@ -2933,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): @@ -2943,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 @@ -2966,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.""" @@ -2978,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) @@ -3004,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( @@ -3052,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) @@ -3119,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) @@ -3138,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( @@ -3163,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, @@ -3178,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) @@ -3201,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, @@ -3216,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") @@ -3254,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( @@ -3291,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, @@ -3336,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( @@ -3349,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( @@ -3381,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( @@ -3400,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( @@ -3438,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() @@ -3466,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) @@ -3478,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, @@ -3491,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()( @@ -3507,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()( @@ -3520,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()( @@ -3534,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()( @@ -3548,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"], @@ -3560,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"], @@ -3638,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) @@ -3674,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"] @@ -3710,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) @@ -3729,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.""" @@ -3744,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) @@ -3757,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): @@ -3788,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): @@ -3801,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.""" @@ -3822,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) @@ -3840,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): @@ -3852,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): @@ -3883,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): @@ -3904,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.""" @@ -3942,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) @@ -3968,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.""" @@ -3994,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): @@ -4045,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 ) @@ -4103,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 ) @@ -4239,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() @@ -4283,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, @@ -4302,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()( @@ -4315,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()( @@ -4362,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, @@ -4384,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"), @@ -4412,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"), @@ -4440,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"), @@ -4472,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"), @@ -4503,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"), @@ -4515,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( @@ -4533,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"), @@ -4580,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"), @@ -4617,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"), @@ -4650,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"), @@ -4683,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"), @@ -4703,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): @@ -4737,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): @@ -4749,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): @@ -4831,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, @@ -4870,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( @@ -4883,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()) @@ -4995,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): @@ -5043,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")) @@ -5056,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): @@ -5068,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): @@ -5106,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) @@ -5167,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, @@ -5223,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( @@ -5299,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( @@ -5321,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, @@ -5354,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, @@ -5391,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, @@ -5414,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(), @@ -5446,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, @@ -5480,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, @@ -5522,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, @@ -5576,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, @@ -5632,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, @@ -5728,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 7c6efb25..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) @@ -90,7 +88,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.", ) @@ -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 96f2a5af..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.STAFF_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.STAFF_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 f27c1e25..733b284d 100644 --- a/primed/duo/views.py +++ b/primed/duo/views.py @@ -1,15 +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 def get_context_data(self, **kwargs): @@ -22,7 +18,6 @@ def get_context_data(self, **kwargs): class DataUsePermissionDetail(AnVILConsortiumManagerViewRequired, DetailView): - model = models.DataUsePermission def get_object(self): @@ -30,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 @@ -41,8 +35,7 @@ def get_context_data(self, **kwargs): return context -class DataUseModifierList(AnVILConsortiumManagerStaffViewRequired, ListView): - +class DataUseModifierList(AnVILConsortiumManagerViewRequired, ListView): model = models.DataUseModifier def get_context_data(self, **kwargs): @@ -55,7 +48,6 @@ def get_context_data(self, **kwargs): class DataUseModifierDetail(AnVILConsortiumManagerViewRequired, DetailView): - model = models.DataUseModifier def get_object(self): @@ -63,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 24262017..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): @@ -93,11 +87,9 @@ 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 - workspace_detail_template_name = ( - "miscellaneous_workspaces/dataprepworkspace_detail.html" - ) + workspace_detail_template_name = "miscellaneous_workspaces/dataprepworkspace_detail.html" 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/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..97ad892d 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): @@ -81,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 fd1132df..cc2b7b51 100644 --- a/primed/miscellaneous_workspaces/tables.py +++ b/primed/miscellaneous_workspaces/tables.py @@ -9,30 +9,29 @@ ) -class OpenAccessWorkspaceStaffTable(tables.Table): +class OpenAccessWorkspaceUserTable(tables.Table): """Class to render a table of Workspace objects with OpenAccessWorkspace workspace data.""" name = tables.columns.Column(linkify=True) - billing_project = tables.Column(linkify=True) is_shared = WorkspaceSharedWithConsortiumColumn() + openaccessworkspace__studies = tables.ManyToManyColumn( + linkify_item=True, + ) class Meta: model = Workspace fields = ( "name", - "billing_project", "openaccessworkspace__studies", "is_shared", ) order_by = ("name",) -class OpenAccessWorkspaceUserTable(tables.Table): +class OpenAccessWorkspaceStaffTable(OpenAccessWorkspaceUserTable): """Class to render a table of Workspace objects with OpenAccessWorkspace workspace data.""" - name = tables.columns.Column(linkify=True) - billing_project = tables.Column() - is_shared = WorkspaceSharedWithConsortiumColumn() + billing_project = tables.Column(linkify=True) class Meta: model = Workspace @@ -45,22 +44,33 @@ 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" - ) - 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 + fields = ( + "name", + "dataprepworkspace__target_workspace__name", + "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", ) 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_forms.py b/primed/miscellaneous_workspaces/tests/test_forms.py index 40b20431..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): @@ -152,7 +148,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 +156,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 +165,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,66 +173,25 @@ 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": "", - } - 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", + "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("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): - form_class = forms.OpenAccessWorkspaceForm def setUp(self): @@ -382,7 +337,6 @@ def test_invalid_data_url_is_not_url(self): class DataPrepWorkspaceFormTest(TestCase): - form_class = forms.DataPrepWorkspaceForm def setUp(self): @@ -452,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_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() diff --git a/primed/miscellaneous_workspaces/tests/test_models.py b/primed/miscellaneous_workspaces/tests/test_models.py index 7b38836c..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 @@ -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 @@ -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") @@ -79,93 +71,16 @@ 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) 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") - 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.""" @@ -179,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") @@ -219,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") @@ -233,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()) @@ -263,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_tables.py b/primed/miscellaneous_workspaces/tests/test_tables.py index de536164..d6252a70 100644 --- a/primed/miscellaneous_workspaces/tests/test_tables.py +++ b/primed/miscellaneous_workspaces/tests/test_tables.py @@ -49,11 +49,32 @@ def test_row_count_with_two_objects(self): self.assertEqual(len(table.rows), 2) -class DataPrepWorkspaceTableTest(TestCase): - """Tests for the DataPrepWorkspaceTable table.""" +class DataPrepWorkspaceUserTableTest(TestCase): + """Tests for the DataPrepWorkspaceUserTable table.""" model_factory = factories.DataPrepWorkspaceFactory - table_class = tables.DataPrepWorkspaceTable + table_class = tables.DataPrepWorkspaceUserTable + + def test_row_count_with_no_objects(self): + table = self.table_class(Workspace.objects.filter(workspace_type="data_prep")) + self.assertEqual(len(table.rows), 0) + + def test_row_count_with_one_object(self): + self.model_factory.create() + table = self.table_class(Workspace.objects.filter(workspace_type="data_prep")) + self.assertEqual(len(table.rows), 1) + + def test_row_count_with_two_objects(self): + self.model_factory.create_batch(2) + table = self.table_class(Workspace.objects.filter(workspace_type="data_prep")) + self.assertEqual(len(table.rows), 2) + + +class DataPrepWorkspaceStaffTableTest(TestCase): + """Tests for the DataPrepWorkspaceUserTable table.""" + + model_factory = factories.DataPrepWorkspaceFactory + table_class = tables.DataPrepWorkspaceStaffTable def test_row_count_with_no_objects(self): table = self.table_class(Workspace.objects.filter(workspace_type="data_prep")) diff --git a/primed/miscellaneous_workspaces/tests/test_views.py b/primed/miscellaneous_workspaces/tests/test_views.py index ed75975b..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" @@ -734,7 +662,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 +672,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): @@ -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)], @@ -860,7 +772,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 +782,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): @@ -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/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/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 88460087..911271f4 100644 --- a/primed/primed_anvil/helpers.py +++ b/primed/primed_anvil/helpers.py @@ -1,7 +1,11 @@ +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 from primed.miscellaneous_workspaces.models import OpenAccessWorkspace @@ -14,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. @@ -34,7 +36,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 +50,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 +81,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 +106,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 +116,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: @@ -111,3 +128,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(sorted(study_names)) + + return json 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..87389d1b 100644 --- a/primed/primed_anvil/models.py +++ b/primed/primed_anvil/models.py @@ -8,12 +8,9 @@ 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() class Meta: @@ -32,13 +29,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/primed_anvil/tables.py b/primed/primed_anvil/tables.py index 30048579..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,48 +52,42 @@ 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 -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",) @@ -150,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 d62b5507..a7ab68e8 100644 --- a/primed/primed_anvil/tests/test_helpers.py +++ b/primed/primed_anvil/tests/test_helpers.py @@ -1,9 +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.dbgap.tests.factories import dbGaPWorkspaceFactory +from primed.cdsa.tests.factories import CDSAWorkspaceFactory +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 @@ -44,8 +51,8 @@ def test_one_dbgap_workspace_one_study_not_shared_no_available_data(self): def test_one_open_access_workspace_one_study_not_shared_no_available_data(self): AvailableDataFactory.create(name="Foo") study = StudyFactory.create(short_name="TEST") - open_access_workspace = OpenAccessWorkspaceFactory.create() - open_access_workspace.studies.add(study) + workspace = OpenAccessWorkspaceFactory.create() + workspace.studies.add(study) res = helpers.get_summary_table_data() self.assertEqual(len(res), 1) self.assertEqual(len(res[0]), 4) @@ -59,13 +66,11 @@ def test_one_open_access_workspace_one_study_not_shared_no_available_data(self): self.assertIn("Foo", res[0]) self.assertEqual(res[0]["Foo"], False) - def test_one_workspace_one_study_not_shared_with_one_available_data(self): + def test_one_dbgap_workspace_one_study_not_shared_with_one_available_data(self): available_data = AvailableDataFactory.create(name="Foo") study = StudyFactory.create(short_name="TEST") - dbgap_workspace = dbGaPWorkspaceFactory.create( - dbgap_study_accession__studies=[study] - ) - dbgap_workspace.available_data.add(available_data) + workspace = dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) + workspace.available_data.add(available_data) res = helpers.get_summary_table_data() self.assertEqual(len(res), 1) self.assertEqual(len(res[0]), 4) @@ -79,15 +84,13 @@ def test_one_workspace_one_study_not_shared_with_one_available_data(self): self.assertIn("Foo", res[0]) self.assertEqual(res[0]["Foo"], True) - def test_one_workspace_one_study_not_shared_with_two_available_data(self): + def test_one_dbgap_workspace_one_study_not_shared_with_two_available_data(self): available_data_1 = AvailableDataFactory.create(name="Foo") available_data_2 = AvailableDataFactory.create(name="Bar") study = StudyFactory.create(short_name="TEST") - dbgap_workspace = dbGaPWorkspaceFactory.create( - dbgap_study_accession__studies=[study] - ) - dbgap_workspace.available_data.add(available_data_1) - dbgap_workspace.available_data.add(available_data_2) + workspace = dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) + workspace.available_data.add(available_data_1) + workspace.available_data.add(available_data_2) res = helpers.get_summary_table_data() self.assertEqual(len(res), 1) self.assertEqual(len(res[0]), 5) @@ -101,7 +104,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,15 +122,11 @@ def test_one_workspace_two_studies_not_shared_no_available_data(self): self.assertIn("Foo", res[0]) self.assertEqual(res[0]["Foo"], False) - def test_one_workspace_one_study_shared_no_available_data(self): + def test_one_dbgap_workspace_one_study_shared_no_available_data(self): AvailableDataFactory.create(name="Foo") study = StudyFactory.create(short_name="TEST") - dbgap_workspace = dbGaPWorkspaceFactory.create( - dbgap_study_accession__studies=[study] - ) - WorkspaceGroupSharingFactory.create( - workspace=dbgap_workspace.workspace, group__name="PRIMED_ALL" - ) + workspace = dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[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) @@ -141,7 +140,7 @@ def test_one_workspace_one_study_shared_no_available_data(self): self.assertIn("Foo", res[0]) self.assertEqual(res[0]["Foo"], False) - def test_two_workspaces_one_study(self): + def test_two_dbgap_workspaces_one_study(self): AvailableDataFactory.create(name="Foo") study = StudyFactory.create(short_name="TEST") dbGaPWorkspaceFactory.create(dbgap_study_accession__studies=[study]) @@ -159,21 +158,15 @@ def test_two_workspaces_one_study(self): self.assertIn("Foo", res[0]) self.assertEqual(res[0]["Foo"], False) - def test_two_workspaces_one_study_one_shared(self): + def test_two_dbgap_workspaces_one_study_one_shared(self): available_data_1 = AvailableDataFactory.create(name="Foo") available_data_2 = AvailableDataFactory.create(name="Bar") study = StudyFactory.create(short_name="TEST") - dbgap_workspace_1 = dbGaPWorkspaceFactory.create( - dbgap_study_accession__studies=[study] - ) - dbgap_workspace_1.available_data.add(available_data_1) - WorkspaceGroupSharingFactory.create( - workspace=dbgap_workspace_1.workspace, group__name="PRIMED_ALL" - ) - dbgap_workspace_2 = dbGaPWorkspaceFactory.create( - dbgap_study_accession__studies=[study] - ) - dbgap_workspace_2.available_data.add(available_data_2) + 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]) + workspace_2.available_data.add(available_data_2) res = helpers.get_summary_table_data() self.assertEqual(len(res), 2) self.assertIn( @@ -197,7 +190,7 @@ def test_two_workspaces_one_study_one_shared(self): res, ) - def test_two_workspaces_multiple_studies(self): + def test_two_dbgap_workspaces_multiple_studies(self): AvailableDataFactory.create(name="Foo") study_1 = StudyFactory.create(short_name="TEST") study_2 = StudyFactory.create(short_name="Other") @@ -229,8 +222,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 +249,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 +277,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 +301,491 @@ 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, + ) + + +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") 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 9e1b079f..e28f43bc 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 @@ -51,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): @@ -63,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()) @@ -74,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()) @@ -98,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): @@ -111,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.""" @@ -130,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) @@ -199,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): @@ -216,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.""" @@ -230,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): @@ -251,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)) @@ -269,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)) @@ -282,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)) @@ -374,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): @@ -391,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.""" @@ -404,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): @@ -418,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() @@ -558,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() @@ -578,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): @@ -600,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.""" @@ -613,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): @@ -623,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 @@ -646,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) @@ -659,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()) @@ -714,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) @@ -756,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): @@ -773,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() @@ -796,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()) @@ -806,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): @@ -862,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): @@ -879,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() @@ -900,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): @@ -967,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): @@ -984,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() @@ -1005,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): @@ -1061,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): @@ -1118,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): @@ -1135,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.""" @@ -1149,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): @@ -1204,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.""" @@ -1220,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.""" @@ -1239,3 +1088,99 @@ def test_table_rows(self): response = self.client.get(self.get_url()) self.assertIn("summary_table", response.context_data) self.assertEqual(len(response.context_data["summary_table"].rows), 2) + + def test_includes_open_access_workspaces(self): + """Open access workspaces are included in the table.""" + study = StudyFactory.create() + open_workspace = OpenAccessWorkspaceFactory.create() + open_workspace.studies.add(study) + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("summary_table", response.context_data) + self.assertEqual(len(response.context_data["summary_table"].rows), 1) + + def test_includes_dbgap_workspaces(self): + """dbGaP workspaces are included in the table.""" + # One open access workspace with one study, with one available data type. + # One dbGaP workspae with two studies. + dbGaPWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("summary_table", response.context_data) + self.assertEqual(len(response.context_data["summary_table"].rows), 1) + + def test_includes_cdsa_workspaces(self): + """CDSA workspaces are included in the table.""" + CDSAWorkspaceFactory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url()) + self.assertIn("summary_table", response.context_data) + self.assertEqual(len(response.context_data["summary_table"].rows), 1) + + +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..844f0160 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, @@ -51,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}" @@ -86,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 @@ -99,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): @@ -116,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 @@ -139,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] @@ -160,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 @@ -175,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): @@ -183,3 +168,12 @@ 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..7dc372ce --- /dev/null +++ b/primed/templates/primed_anvil/phenotype_inventory_inputs.html @@ -0,0 +1,57 @@ +{% 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" field when running the workflow on AnVIL. +

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

+

{{ workspaces_input }}
+

+
+
+ + +{% endblock content %} + + +{% block inline_javascript %} + +{% endblock %} diff --git a/primed/templates/snippets/data_prep_workspace_table.html b/primed/templates/snippets/data_prep_workspace_table.html index cb9c9b66..32d04cff 100644 --- a/primed/templates/snippets/data_prep_workspace_table.html +++ b/primed/templates/snippets/data_prep_workspace_table.html @@ -1,5 +1,7 @@ {% load render_table from django_tables2 %} +{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %} +
@@ -18,3 +20,5 @@

+ +{% endif %} diff --git a/primed/templates/users/drupal_data_audit_email.html b/primed/templates/users/drupal_data_audit_email.html new file mode 100644 index 00000000..c957a32f --- /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/adapters.py b/primed/users/adapters.py index b0f6d6a3..a2b33115 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") @@ -32,8 +32,7 @@ def update_user_info(self, user, extra_data: Dict): 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 @@ -52,18 +51,18 @@ 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( - "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) @@ -79,19 +78,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") @@ -99,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}" @@ -127,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 new file mode 100644 index 00000000..c81fd479 --- /dev/null +++ b/primed/users/audit.py @@ -0,0 +1,483 @@ +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 TextTable(object): + def render_to_text(self): + return TableExport(export_format=TableExport.CSV, table=self).export() + + +class UserAuditResultsTable(tables.Table, TextTable): + """A table to show results from a UserAudit instance.""" + + 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() + + class Meta: + orderable = False + + +@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 get_table_dictionary(self): + """Return a dictionary that can be used to populate an instance of `SiteAuditResultsTable`.""" + + 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 + + +@dataclass +class VerifiedUser(UserAuditResult): + pass + + +@dataclass +class NewUser(UserAuditResult): + pass + + +@dataclass +class RemoveUser(UserAuditResult): + pass + + +@dataclass +class InactiveAnvilUser(UserAuditResult): + pass + + +@dataclass +class UpdateUser(UserAuditResult): + pass + + +@dataclass +class OverDeactivateThresholdUser(UserAuditResult): + pass + + +class UserAudit(PRIMEDAudit): + 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, ignore_deactivate_threshold=False): + """Initialize the audit. + + Args: + apply_changes: Whether to make changes to align the audit + """ + 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.""" + 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 + + 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)) + 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 = [] + 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 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=f"Over Threshold {over_threshold}")) + + 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) + ), + ) + ) + + +class SiteAuditResultsTable(tables.Table, TextTable): + """A table to show results from a SiteAudit instance.""" + + result_type = tables.Column() + local_site_name = tables.Column() + remote_site_name = tables.Column() + changes = tables.Column() + note = tables.Column() + + def value_local_site_name(self, value): + return value + + class Meta: + orderable = False + + +@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__, + } + 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 + + +@dataclass +class VerifiedSite(SiteAuditResult): + pass + + +@dataclass +class NewSite(SiteAuditResult): + pass + + +@dataclass +class RemoveSite(SiteAuditResult): + pass + + +@dataclass +class UpdateSite(SiteAuditResult): + changes: dict + + +class SiteAudit(PRIMEDAudit): + ISSUE_TYPE_LOCAL_SITE_INVALID = "Local site is invalid" + results_table_class = SiteAuditResultsTable + + def __init__(self, apply_changes=False): + """Initialize the audit. + + 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) + + try: + study_site = StudySite.objects.get(drupal_node_id=node_id) + except ObjectDoesNotExist: + 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, + ) + 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.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 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)) + + 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)) + + +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): + 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 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/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..d58113cc --- /dev/null +++ b/primed/users/management/commands/sync-drupal-data.py @@ -0,0 +1,105 @@ +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 + +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, + 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", + 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: + # 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 = "[command:sync-drupal-data] 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): + 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"Ignoring Threshold: {self.ignore_threshold} Start time: {localtime()}\n" + ) + site_audit = audit.SiteAudit(apply_changes=self.apply_changes) + site_audit.run_audit() + + 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" + ) + 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() + + user_audit = audit.UserAudit( + apply_changes=self.apply_changes, + ignore_deactivate_threshold=self.ignore_threshold, + ) + 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() + + self.stdout.write(notification_content) + if self.email: + self._send_email(user_audit, site_audit) 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 new file mode 100644 index 00000000..412618f2 --- /dev/null +++ b/primed/users/tests/test_audit.py @@ -0,0 +1,571 @@ +import json +import time +from io import StringIO + +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 import mail +from django.core.management import call_command +from django.test import TestCase +from marshmallow_jsonapi import Schema, fields + +from primed.drupal_oauth_provider.provider import CustomProvider +from primed.users import audit +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() + 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): + id = fields.Str(dump_only=True) + display_name = fields.Str() + 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( + many=True, schema="StudySiteSchema", type_="node--study_site_or_center" + ) + + 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 = [ + 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 = [ + 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": [], + } + ), + # second mock object is deactivated user (no drupal uid) + UserMockObject( + **{ + "id": "usr2", + "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": [], + } + ), +] + + +class TestUserDataAudit(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", # gitleaks:allow + "refresh_token": "sldvafkjw34509s8dfsdfTEST", # gitleaks:allow + "expires_in": 3600, + "expires_at": fake_time + 3600, + } + + def add_fake_study_sites_response(self): + 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}/{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) + + responses.get( + url=url_path, + body=json.dumps(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 audit.get_drupal_json_api() + + @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"] + + @responses.activate + def test_get_study_sites(self): + json_api = self.get_fake_json_api() + self.add_fake_study_sites_response() + 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"] + + @responses.activate + def test_audit_study_sites_no_update(self): + self.get_fake_json_api() + self.add_fake_study_sites_response() + 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): + self.get_fake_json_api() + self.add_fake_study_sites_response() + 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): + StudySite.objects.create( + drupal_node_id=TEST_STUDY_SITE_DATA[0].drupal_internal__nid, + short_name="WrongShortName", + full_name="WrongTitle", + ) + 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() + 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 + assert first_test_ss.short_name == TEST_STUDY_SITE_DATA[0].title + + @responses.activate + def test_audit_study_sites_with_extra_site(self): + 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) + 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): + 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, + ) + user_audit = audit.UserAudit(apply_changes=True) + user_audit.run_audit() + + self.assertFalse(user_audit.ok()) + self.assertEqual(len(user_audit.needs_action), 1) + + users = get_user_model().objects.all() + assert users.count() == 1 + + 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 + + @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, + ) + 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() + 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, + ) + 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() + + @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): + 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() + + @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", + is_active=False, + ) + SocialAccount.objects.create( + user=new_user, + uid=TEST_USER_DATA[0].drupal_internal__uid, + provider=CustomProvider.id, + ) + user_audit = audit.UserAudit(apply_changes=True) + user_audit.run_audit() + self.assertFalse(user_audit.ok()) + new_user.refresh_from_db() + + self.assertEqual(new_user.name, drupal_fullname) + self.assertEqual(len(user_audit.needs_action), 1) + + # 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, + ) + user_audit = audit.UserAudit(apply_changes=True) + user_audit.run_audit() + self.assertFalse(user_audit.ok()) + + new_user.refresh_from_db() + self.assertTrue(new_user.is_active) + + # 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, + ) + 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() + 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() + self.add_fake_study_sites_response() + self.add_fake_users_response() + out = StringIO() + call_command("sync-drupal-data", stdout=out) + 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): + 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_users_response() + out = StringIO() + 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") 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): diff --git a/requirements/dev-requirements.in b/requirements/dev-requirements.in index 54932d32..79a043ec 100644 --- a/requirements/dev-requirements.in +++ b/requirements/dev-requirements.in @@ -17,11 +17,12 @@ 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 # Django # ------------------------------------------------------------------------------ diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index d5c4d978..08c17266 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -8,10 +8,8 @@ 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 asttokens==2.4.1 # via stack-data babel==2.14.0 @@ -20,46 +18,41 @@ 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 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 - # black + # -c requirements.txt 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 # 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 @@ -68,33 +61,23 @@ executing==2.0.1 # via stack-data filelock==3.13.1 # via virtualenv -flake8==7.0.0 - # via - # -r requirements/dev-requirements.in - # flake8-isort -flake8-isort==6.1.1 - # via -r requirements/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 - # via - # flake8-isort - # pylint jedi==0.19.1 # via ipython jinja2==3.1.3 @@ -107,92 +90,73 @@ 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 requirements/dev-requirements.in + # via -r dev-requirements.in mypy-extensions==1.0.0 # via - # black # mypy 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 -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 requirements/dev-requirements.in + # via -r dev-requirements.in prompt-toolkit==3.0.43 # via ipython 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 requirements/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 +ruff==0.4.2 + # via -r requirements/dev-requirements.in 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,24 +169,20 @@ 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 - # black + # -c requirements.txt + # -c test-requirements.txt # django-stubs # ipdb # mypy - # pylint -tomlkit==0.12.3 - # via pylint tornado==6.4 # via livereload traitlets==5.14.1 @@ -233,35 +193,33 @@ types-pytz==2024.1.0.20240203 # via django-stubs types-pyyaml==6.0.12.12 # via django-stubs -types-requests==2.31.0.20240311 - # via -r requirements/dev-requirements.in +types-requests==2.31.0.20240406 + # 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 # django-stubs # django-stubs-ext # ipython # mypy - # 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 # via pre-commit wcwidth==0.2.13 # via prompt-toolkit -werkzeug==3.0.1 - # via -r requirements/dev-requirements.in +werkzeug==3.0.2 + # 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.in b/requirements/requirements.in index b855f9cc..b977ac59 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 655a8085..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 -django-model-utils==4.4.0 - # via -r requirements/requirements.in -django-picklefield==3.1 + # via -r requirements.in +django-model-utils==4.5.0 + # 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.16.1 - # via -r requirements/requirements.in +django-tree-queries==0.18.0 + # via -r requirements.in fastobo==0.12.3 # via pronto fontawesomefree==6.5.1 @@ -117,12 +117,14 @@ importlib-resources==6.1.1 # via # jsonschema # jsonschema-specifications +jsonapi-requests==0.7.0 + # 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 @@ -133,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 @@ -184,9 +186,10 @@ 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 # requests-oauthlib requests-oauthlib==1.3.1 # via django-allauth @@ -198,14 +201,16 @@ 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.5.0 - # via -r requirements/requirements.in +tablib==3.6.1 + # via -r requirements.in tenacity==8.2.3 - # via plotly + # via + # jsonapi-requests + # plotly tomli==2.0.1 # via # build @@ -217,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 diff --git a/requirements/test-requirements.in b/requirements/test-requirements.in index df760727..c5e95951 100644 --- a/requirements/test-requirements.in +++ b/requirements/test-requirements.in @@ -18,3 +18,5 @@ 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 25c305e9..f58f6830 100644 --- a/requirements/test-requirements.txt +++ b/requirements/test-requirements.txt @@ -34,9 +34,14 @@ idna==3.3 # requests iniconfig==2.0.0 # 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.4.0