diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..09dd06d8 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,13 @@ +sort-direction: ascending + +categories: + - title: "⬆️ Dependencies" + labels: + - "dependencies" + - "update-requirements-files" + - "combined-pr" + +template: | + ## What’s Changed + + $CHANGES diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97f12a24..620c4ed7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -140,6 +140,6 @@ jobs: python -m coverage report - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4.6.0 + uses: codecov/codecov-action@v5.0.7 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000..beb61166 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,114 @@ +name: Release Drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - deploy/production + +permissions: + contents: read + +jobs: + update_release_draft: + permissions: + # write permission is required to create a github release + contents: write + # write permission is required for autolabeler + # otherwise, read permission is required at least + pull-requests: read + runs-on: ubuntu-latest + steps: + + - name: Get current date + id: get-date + run: | + export CURRENT_DATE=$(TZ=":America/Los_Angeles" date "+%Y-%m-%d") + echo "current_date=${CURRENT_DATE}" >> $GITHUB_OUTPUT + echo "Current date set to ${CURRENT_DATE}" + + - name: Get number of releases for the current date + id: get-release-count + run: | + export RELEASE_COUNT=$(gh release list \ + --repo ${{ github.repository }} \ + --json tagName \ + --exclude-drafts \ + --jq "map(select(.tagName | startswith(\"${CURRENT_DATE}\")))|length" \ + ) + echo "release_count=${RELEASE_COUNT}" >> $GITHUB_OUTPUT + echo "Found ${RELEASE_COUNT} releases" + env: + CURRENT_DATE: ${{ steps.get-date.outputs.current_date }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Prepare release version + id: get-version + run: | + export VERSION="${CURRENT_DATE}" + + if [ $RELEASE_COUNT -gt 0 ]; then + echo "Release already exists for version ${VERSION}" + echo "Appending release count to version" + export VERSION="${CURRENT_DATE}-${RELEASE_COUNT}" + fi + + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "Version set to ${VERSION}" + env: + CURRENT_DATE: ${{ steps.get-date.outputs.current_date }} + RELEASE_COUNT: ${{ steps.get-release-count.outputs.release_count }} + + + - name: Check that version doesn't exist + id: check-release + run: | + echo "Checking version ${VERSION}" + + export CHECK=$(gh release list \ + --repo ${{ github.repository }} \ + --json tagName \ + --exclude-drafts \ + --jq "map(select(.tagName == \"${VERSION}\"))|length" \ + ) + echo "Found ${CHECK} releases" + + if [ $CHECK -gt 0 ]; then + echo "Release already exists for version ${VERSION}" + exit 1 + fi + env: + VERSION: ${{ steps.get-version.outputs.version }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check that tag doesn't exist + id: check-tag + run: | + echo "Checking tag for version ${VERSION}" + + # Query the API for this tag. + export CHECK=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/${{ github.repository }}/tags \ + --jq "map(select(.name == \"${VERSION}\"))|length" \ + ) + echo "Found ${CHECK} tags" + + if [ $CHECK -gt 0 ]; then + echo "Tag already exists for version ${VERSION}" + exit 1 + fi + env: + VERSION: ${{ steps.get-version.outputs.version }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + + - uses: release-drafter/release-drafter@v6 + with: + commitish: deploy/production + tag: ${{ steps.get-version.outputs.version }} + name: ${{ steps.get-version.outputs.version }} + version: ${{ steps.get-version.outputs.version }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 34714409..9c8f14bf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.7.0 + rev: v0.8.1 hooks: # Run the linter. - id: ruff @@ -21,7 +21,7 @@ repos: - id: ruff-format - repo: https://github.com/gitleaks/gitleaks - rev: v8.21.1 + rev: v8.21.2 hooks: - id: gitleaks diff --git a/config/settings/base.py b/config/settings/base.py index f33bb9ec..8895bb68 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -298,6 +298,7 @@ "socialaccount_signup", "admin:index", "admin:login", + "favicon", ] # django-dbbackup @@ -345,6 +346,11 @@ "request_scope": True, "django_group_name": "Approved by PI for AnVIL access", }, + { + "drupal_machine_name": "authenticated", + "request_scope": True, + "django_group_name": "Authenticated", + }, ], } } @@ -375,25 +381,19 @@ # django-anvil-consortium-manager # ------------------------------------------------------------------------------ ANVIL_WORKSPACE_ADAPTERS = [ - "gregor_django.gregor_anvil.adapters.ResourceWorkspaceAdapter", - "gregor_django.gregor_anvil.adapters.TemplateWorkspaceAdapter", + "gregor_django.gregor_anvil.adapters.CombinedConsortiumDataWorkspaceAdapter", "gregor_django.gregor_anvil.adapters.UploadWorkspaceAdapter", + "gregor_django.gregor_anvil.adapters.ResourceWorkspaceAdapter", + "gregor_django.gregor_anvil.adapters.ExchangeWorkspaceAdapter", "gregor_django.gregor_anvil.adapters.PartnerUploadWorkspaceAdapter", - "gregor_django.gregor_anvil.adapters.CombinedConsortiumDataWorkspaceAdapter", - "gregor_django.gregor_anvil.adapters.ReleaseWorkspaceAdapter", "gregor_django.gregor_anvil.adapters.DCCProcessingWorkspaceAdapter", "gregor_django.gregor_anvil.adapters.DCCProcessedDataWorkspaceAdapter", - "gregor_django.gregor_anvil.adapters.ExchangeWorkspaceAdapter", + "gregor_django.gregor_anvil.adapters.ReleaseWorkspaceAdapter", + "gregor_django.gregor_anvil.adapters.TemplateWorkspaceAdapter", ] ANVIL_ACCOUNT_ADAPTER = "gregor_django.gregor_anvil.adapters.AccountAdapter" ANVIL_MANAGED_GROUP_ADAPTER = "gregor_django.gregor_anvil.adapters.ManagedGroupAdapter" -# Specify the URL name that AccountLink and AccountLinkVerify redirect to. -ANVIL_ACCOUNT_LINK_REDIRECT = "users:redirect" -# Specify the subject for AnVIL account verification emails. -ANVIL_ACCOUNT_LINK_EMAIL_SUBJECT = "Verify your AnVIL account email" -ANVIL_ACCOUNT_VERIFY_NOTIFICATION_EMAIL = "gregorconsortium@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") diff --git a/config/urls.py b/config/urls.py index c5982f85..df1659ca 100644 --- a/config/urls.py +++ b/config/urls.py @@ -1,9 +1,11 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib import admin +from django.templatetags.static import static as static_url_tag from django.urls import include, path from django.views import defaults as default_views from django.views.generic import TemplateView +from django.views.generic.base import RedirectView urlpatterns = [ path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), @@ -22,6 +24,11 @@ "gregor_anvil/", include("gregor_django.gregor_anvil.urls", namespace="gregor_anvil"), ), + path( + "favicon.ico", + RedirectView.as_view(url=static_url_tag("images/favicons/favicon_1.jpg"), permanent=True), + name="favicon", + ), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/gregor_django/drupal_oauth_provider/tests.py b/gregor_django/drupal_oauth_provider/tests.py index ca2b598d..b133227b 100644 --- a/gregor_django/drupal_oauth_provider/tests.py +++ b/gregor_django/drupal_oauth_provider/tests.py @@ -116,7 +116,7 @@ def get_mocked_response(self): "name": "testmaster", "email": "test@testmaster.net", "email_verified": "True", - "sub": 20122 + "sub": "20122" }""", ), ] @@ -184,7 +184,7 @@ def get_id_token(self): "iat": self.setup_time, "aud": allowed_audience, "scope": ["authenticated", "oauth_client_user"], - "sub": 20122, + "sub": "20122", } ) diff --git a/gregor_django/gregor_anvil/adapters.py b/gregor_django/gregor_anvil/adapters.py index 8cf97765..f6939c8a 100644 --- a/gregor_django/gregor_anvil/adapters.py +++ b/gregor_django/gregor_anvil/adapters.py @@ -67,6 +67,9 @@ class AccountAdapter(BaseAccountAdapter): list_table_class = tables.AccountTable list_filterset_class = filters.AccountListFilter + account_link_verify_redirect = "users:redirect" + account_link_email_subject = "Verify your AnVIL account email" + account_verify_notification_email = "gregorconsortium@uw.edu" def get_autocomplete_queryset(self, queryset, q): """Filter to Accounts where the email or the associated user name matches the query `q`.""" @@ -115,6 +118,7 @@ class UploadWorkspaceAdapter(WorkspaceAdminSharingAdapterMixin, BaseWorkspaceAda workspace_data_form_class = forms.UploadWorkspaceForm workspace_form_class = WorkspaceForm workspace_detail_template_name = "gregor_anvil/uploadworkspace_detail.html" + workspace_list_template_name = "gregor_anvil/uploadworkspace_list.html" def get_autocomplete_queryset(self, queryset, q, forwarded={}): """Filter to Accounts where the email or the associated user name matches the query `q`.""" @@ -139,7 +143,7 @@ class PartnerUploadWorkspaceAdapter(WorkspaceAdminSharingAdapterMixin, BaseWorks name = "Partner upload workspace" description = "Workspaces that contain data uploaded by a Partner Group " list_table_class_view = tables.PartnerUploadWorkspaceTable - list_table_class_staff_view = tables.PartnerUploadWorkspaceTable + list_table_class_staff_view = tables.PartnerUploadWorkspaceStaffTable workspace_data_model = models.PartnerUploadWorkspace workspace_data_form_class = forms.PartnerUploadWorkspaceForm @@ -156,7 +160,7 @@ class ResourceWorkspaceAdapter(WorkspaceAdminSharingAdapterMixin, BaseWorkspaceA "Workspaces that contain general Consortium resources (e.g., examples of using AnVIL, working with data, etc.)" # noqa: E501 ) list_table_class_view = tables.DefaultWorkspaceTable - list_table_class_staff_view = tables.DefaultWorkspaceTable + list_table_class_staff_view = tables.DefaultWorkspaceStaffTable workspace_data_model = models.ResourceWorkspace workspace_data_form_class = forms.ResourceWorkspaceForm workspace_detail_template_name = "gregor_anvil/resourceworkspace_detail.html" @@ -189,6 +193,7 @@ class CombinedConsortiumDataWorkspaceAdapter(WorkspaceAdminSharingAdapterMixin, workspace_data_form_class = forms.CombinedConsortiumDataWorkspaceForm workspace_detail_template_name = "gregor_anvil/combinedconsortiumdataworkspace_detail.html" workspace_form_class = WorkspaceForm + workspace_list_template_name = "gregor_anvil/combinedconsortiumdataworkspace_list.html" class ReleaseWorkspaceAdapter(WorkspaceAdminSharingAdapterMixin, BaseWorkspaceAdapter): @@ -240,8 +245,9 @@ class ExchangeWorkspaceAdapter(WorkspaceAdminSharingAdapterMixin, BaseWorkspaceA name = "Exchange workspace" description = "Workspaces for exchanging data with a Research Center outside of an upload cycle" list_table_class_view = tables.ExchangeWorkspaceTable - list_table_class_staff_view = tables.ExchangeWorkspaceTable + list_table_class_staff_view = tables.ExchangeWorkspaceStaffTable workspace_data_model = models.ExchangeWorkspace workspace_data_form_class = forms.ExchangeWorkspaceForm workspace_form_class = WorkspaceForm workspace_detail_template_name = "gregor_anvil/exchangeworkspace_detail.html" + workspace_list_template_name = "gregor_anvil/exchangeworkspace_list.html" diff --git a/gregor_django/gregor_anvil/tables.py b/gregor_django/gregor_anvil/tables.py index 23ac7857..d35922cf 100644 --- a/gregor_django/gregor_anvil/tables.py +++ b/gregor_django/gregor_anvil/tables.py @@ -154,9 +154,24 @@ def render_consortium_access(self, record): class DefaultWorkspaceTable(WorkspaceConsortiumAccessTable, tables.Table): - """Class to use for default workspace tables in GREGoR.""" + """Class to use for default workspace tables in GREGoR for view users.""" name = tables.Column(linkify=True, verbose_name="Workspace") + billing_project = tables.Column() + + class Meta: + model = Workspace + fields = ( + "name", + "billing_project", + "consortium_access", + ) + order_by = ("name",) + + +class DefaultWorkspaceStaffTable(DefaultWorkspaceTable): + """Class to use for default workspace tables in GREGoR for staff users.""" + billing_project = tables.Column(linkify=True) number_groups = tables.Column( verbose_name="Number of groups shared with", @@ -165,15 +180,13 @@ class DefaultWorkspaceTable(WorkspaceConsortiumAccessTable, tables.Table): accessor="workspacegroupsharing_set__count", ) - class Meta: - model = Workspace + class Meta(DefaultWorkspaceTable.Meta): fields = ( "name", "billing_project", "number_groups", "consortium_access", ) - order_by = ("name",) class UploadWorkspaceTable(WorkspaceConsortiumAccessTable, tables.Table): @@ -198,7 +211,7 @@ class PartnerUploadWorkspaceTable(WorkspaceConsortiumAccessTable, tables.Table): """A table for Workspaces that includes fields from PartnerUploadWorkspace.""" name = tables.columns.Column(linkify=True) - partneruploadworkspace__partner_group = tables.columns.Column(linkify=True) + partneruploadworkspace__partner_group = tables.columns.Column() class Meta: model = Workspace @@ -212,6 +225,12 @@ class Meta: ) +class PartnerUploadWorkspaceStaffTable(PartnerUploadWorkspaceTable): + """A table for Workspaces that includes fields from PartnerUploadWorkspace.""" + + partneruploadworkspace__partner_group = tables.columns.Column(linkify=True) + + class TemplateWorkspaceTable(WorkspaceConsortiumAccessTable, tables.Table): """A table for Workspaces that includes fields from TemplateWorkspace.""" @@ -279,6 +298,7 @@ class DCCProcessingWorkspaceTable(tables.Table): """A table for Workspaces that includes fields from DCCProcessingWorkspace.""" name = tables.columns.Column(linkify=True) + dccprocessingworkspace__upload_cycle = tables.columns.Column(linkify=True) class Meta: model = Workspace @@ -293,7 +313,7 @@ class DCCProcessedDataWorkspaceTable(WorkspaceConsortiumAccessTable, tables.Tabl """A table for Workspaces that includes fields from DCCProcessedDataWorkspace.""" name = tables.columns.Column(linkify=True) - dccprocesseddataworkspace__consent_group = tables.columns.Column(linkify=True) + dccprocesseddataworkspace__upload_cycle = tables.columns.Column(linkify=True) class Meta: model = Workspace @@ -309,10 +329,17 @@ class ExchangeWorkspaceTable(tables.Table): """Class to use for ExchangeWorkspace tables.""" name = tables.Column(linkify=True, verbose_name="Workspace") - billing_project = tables.Column(linkify=True) - exchangeworkspace__research_center = tables.Column(linkify=True) + billing_project = tables.Column() + exchangeworkspace__research_center = tables.Column() class Meta: model = Workspace fields = ("name", "billing_project", "exchangeworkspace__research_center") order_by = ("name",) + + +class ExchangeWorkspaceStaffTable(ExchangeWorkspaceTable): + """Class to use for ExchangeWorkspace tables.""" + + billing_project = tables.Column(linkify=True) + exchangeworkspace__research_center = tables.Column(linkify=True) diff --git a/gregor_django/gregor_anvil/tests/test_migrations.py b/gregor_django/gregor_anvil/tests/test_migrations.py index fb377f31..0c0ed727 100644 --- a/gregor_django/gregor_anvil/tests/test_migrations.py +++ b/gregor_django/gregor_anvil/tests/test_migrations.py @@ -458,7 +458,9 @@ def prepare(self): ) WorkspaceGroupSharing = self.old_state.apps.get_model("anvil_consortium_manager", "WorkspaceGroupSharing") UploadCycle = self.old_state.apps.get_model("gregor_anvil", "UploadCycle") - CombinedConsortiumDataWorkspace = self.old_state.apps.get_model("gregor_anvil", "CombinedConsortiumDataWorkspace") + CombinedConsortiumDataWorkspace = self.old_state.apps.get_model( + "gregor_anvil", "CombinedConsortiumDataWorkspace" + ) # Create an auth domain for the combined workspaces. auth_domain_group = ManagedGroup.objects.create( name="auth_domain", @@ -517,7 +519,9 @@ def prepare(self): ) def test_date_completed(self): - CombinedConsortiumDataWorkspace = self.new_state.apps.get_model("gregor_anvil", "CombinedConsortiumDataWorkspace") + CombinedConsortiumDataWorkspace = self.new_state.apps.get_model( + "gregor_anvil", "CombinedConsortiumDataWorkspace" + ) workspace = CombinedConsortiumDataWorkspace.objects.get(pk=self.combined_workspace_shared.pk) self.assertEqual(workspace.date_completed, self.date_shared) workspace = CombinedConsortiumDataWorkspace.objects.get(pk=self.combined_workspace_not_shared.pk) diff --git a/gregor_django/gregor_anvil/tests/test_tables.py b/gregor_django/gregor_anvil/tests/test_tables.py index fb5d90c4..01a8434f 100644 --- a/gregor_django/gregor_anvil/tests/test_tables.py +++ b/gregor_django/gregor_anvil/tests/test_tables.py @@ -268,10 +268,10 @@ def test_is_shared_one_auth_domain_shared_with_different_group_in_auth_domain(se self.assertNotIn("check-circle-fill", table.render_consortium_access(workspace)) -class UploadWorkspaceTableTest(TestCase): +class DefaultWorkspaceTableTest(TestCase): model = Workspace - model_factory = factories.UploadWorkspaceFactory - table_class = tables.UploadWorkspaceTable + model_factory = factories.WorkspaceFactory + table_class = tables.DefaultWorkspaceTable def test_row_count_with_no_objects(self): table = self.table_class(self.model.objects.all()) @@ -289,10 +289,10 @@ def test_row_count_with_two_objects(self): self.assertEqual(len(table.rows), 2) -class PartnerUploadWorkspaceTableTest(TestCase): +class DefaultWorkspaceStaffTableTest(TestCase): model = Workspace - model_factory = factories.PartnerUploadWorkspaceFactory - table_class = tables.PartnerUploadWorkspaceTable + model_factory = factories.WorkspaceFactory + table_class = tables.DefaultWorkspaceStaffTable def test_row_count_with_no_objects(self): table = self.table_class(self.model.objects.all()) @@ -309,11 +309,22 @@ def test_row_count_with_two_objects(self): table = self.table_class(self.model.objects.all()) self.assertEqual(len(table.rows), 2) + def test_number_groups(self): + self.model_factory.create(name="aaa") + workspace_2 = self.model_factory.create(name="bbb") + workspace_3 = self.model_factory.create(name="ccc") + WorkspaceGroupSharingFactory.create_batch(1, workspace=workspace_2) + WorkspaceGroupSharingFactory.create_batch(2, workspace=workspace_3) + table = self.table_class(self.model.objects.all()) + self.assertEqual(table.rows[0].get_cell("number_groups"), 0) + self.assertEqual(table.rows[1].get_cell("number_groups"), 1) + self.assertEqual(table.rows[2].get_cell("number_groups"), 2) -class TemplateWorkspaceTableTest(TestCase): + +class UploadWorkspaceTableTest(TestCase): model = Workspace - model_factory = factories.TemplateWorkspaceFactory - table_class = tables.TemplateWorkspaceTable + model_factory = factories.UploadWorkspaceFactory + table_class = tables.UploadWorkspaceTable def test_row_count_with_no_objects(self): table = self.table_class(self.model.objects.all()) @@ -331,12 +342,10 @@ def test_row_count_with_two_objects(self): self.assertEqual(len(table.rows), 2) -class CombinedConsortiumDataWorkspaceTableTest(TestCase): - """Tests for the AccountTable in this app.""" - +class PartnerUploadWorkspaceTableTest(TestCase): model = Workspace - model_factory = factories.CombinedConsortiumDataWorkspaceFactory - table_class = tables.CombinedConsortiumDataWorkspaceTable + model_factory = factories.PartnerUploadWorkspaceFactory + table_class = tables.PartnerUploadWorkspaceTable def test_row_count_with_no_objects(self): table = self.table_class(self.model.objects.all()) @@ -348,17 +357,37 @@ def test_row_count_with_one_object(self): self.assertEqual(len(table.rows), 1) def test_row_count_with_two_objects(self): + # These values are coded into the model, so need to create separately. self.model_factory.create_batch(2) table = self.table_class(self.model.objects.all()) self.assertEqual(len(table.rows), 2) -class ReleaseWorkspaceTableTest(TestCase): - """Tests for the AccountTable in this app.""" +class PartnerUploadWorkspaceStaffTableTest(TestCase): + model = Workspace + model_factory = factories.PartnerUploadWorkspaceFactory + table_class = tables.PartnerUploadWorkspaceStaffTable + + def test_row_count_with_no_objects(self): + table = self.table_class(self.model.objects.all()) + self.assertEqual(len(table.rows), 0) + def test_row_count_with_one_object(self): + self.model_factory.create() + table = self.table_class(self.model.objects.all()) + self.assertEqual(len(table.rows), 1) + + def test_row_count_with_two_objects(self): + # These values are coded into the model, so need to create separately. + self.model_factory.create_batch(2) + table = self.table_class(self.model.objects.all()) + self.assertEqual(len(table.rows), 2) + + +class TemplateWorkspaceTableTest(TestCase): model = Workspace - model_factory = factories.ReleaseWorkspaceFactory - table_class = tables.ReleaseWorkspaceTable + model_factory = factories.TemplateWorkspaceFactory + table_class = tables.TemplateWorkspaceTable def test_row_count_with_no_objects(self): table = self.table_class(self.model.objects.all()) @@ -370,6 +399,7 @@ def test_row_count_with_one_object(self): self.assertEqual(len(table.rows), 1) def test_row_count_with_two_objects(self): + # These values are coded into the model, so need to create separately. self.model_factory.create_batch(2) table = self.table_class(self.model.objects.all()) self.assertEqual(len(table.rows), 2) @@ -411,6 +441,50 @@ def test_row_count_with_two_workspace_types(self): self.assertEqual(len(table.rows), 2) +class CombinedConsortiumDataWorkspaceTableTest(TestCase): + """Tests for the AccountTable in this app.""" + + model = Workspace + model_factory = factories.CombinedConsortiumDataWorkspaceFactory + table_class = tables.CombinedConsortiumDataWorkspaceTable + + def test_row_count_with_no_objects(self): + table = self.table_class(self.model.objects.all()) + self.assertEqual(len(table.rows), 0) + + def test_row_count_with_one_object(self): + self.model_factory.create() + table = self.table_class(self.model.objects.all()) + self.assertEqual(len(table.rows), 1) + + def test_row_count_with_two_objects(self): + self.model_factory.create_batch(2) + table = self.table_class(self.model.objects.all()) + self.assertEqual(len(table.rows), 2) + + +class ReleaseWorkspaceTableTest(TestCase): + """Tests for the AccountTable in this app.""" + + model = Workspace + model_factory = factories.ReleaseWorkspaceFactory + table_class = tables.ReleaseWorkspaceTable + + def test_row_count_with_no_objects(self): + table = self.table_class(self.model.objects.all()) + self.assertEqual(len(table.rows), 0) + + def test_row_count_with_one_object(self): + self.model_factory.create() + table = self.table_class(self.model.objects.all()) + self.assertEqual(len(table.rows), 1) + + def test_row_count_with_two_objects(self): + self.model_factory.create_batch(2) + table = self.table_class(self.model.objects.all()) + self.assertEqual(len(table.rows), 2) + + class DCCProcessingWorkspaceTableTest(TestCase): model = Workspace model_factory = factories.DCCProcessingWorkspaceFactory @@ -472,3 +546,24 @@ def test_row_count_with_two_objects(self): self.model_factory.create_batch(2) table = self.table_class(self.model.objects.all()) self.assertEqual(len(table.rows), 2) + + +class ExchangeWorkspaceStaffTableTest(TestCase): + model = Workspace + model_factory = factories.ExchangeWorkspaceFactory + table_class = tables.ExchangeWorkspaceStaffTable + + def test_row_count_with_no_objects(self): + table = self.table_class(self.model.objects.all()) + self.assertEqual(len(table.rows), 0) + + def test_row_count_with_one_object(self): + self.model_factory.create() + table = self.table_class(self.model.objects.all()) + self.assertEqual(len(table.rows), 1) + + def test_row_count_with_two_objects(self): + # These values are coded into the model, so need to create separately. + self.model_factory.create_batch(2) + table = self.table_class(self.model.objects.all()) + self.assertEqual(len(table.rows), 2) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index fc714590..512183e1 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -24,7 +24,7 @@ from gregor_django.users.tables import UserTable from gregor_django.users.tests.factories import UserFactory -from .. import forms, models, tables, views +from .. import adapters, forms, models, tables, views from ..audit import ( combined_workspace_audit, upload_workspace_audit, @@ -50,8 +50,10 @@ def test_acm_link_without_permission(self): user = User.objects.create_user(username="test-none", password="test-none") self.client.force_login(user) response = self.client.get(self.get_url()) - self.assertNotIn("AnVIL Consortium Manager", response.rendered_content) - self.assertNotIn(reverse("anvil_consortium_manager:index"), response.rendered_content) + html = """AnVIL Consortium Manager""".format( + reverse("anvil_consortium_manager:index") + ) + self.assertNotContains(response, html, html=True) def test_acm_link_with_view_permission(self): """ACM link shows up if you have view permission.""" @@ -61,8 +63,10 @@ def test_acm_link_with_view_permission(self): ) self.client.force_login(user) response = self.client.get(self.get_url()) - self.assertIn("AnVIL Consortium Manager", response.rendered_content) - self.assertIn(reverse("anvil_consortium_manager:index"), response.rendered_content) + html = """AnVIL Consortium Manager""".format( + reverse("anvil_consortium_manager:index") + ) + self.assertContains(response, html, html=True) def test_acm_link_with_view_and_edit_permission(self): """ACM link shows up if you have view and edit permission.""" @@ -78,19 +82,6 @@ def test_acm_link_with_view_and_edit_permission(self): self.assertIn("AnVIL Consortium Manager", response.rendered_content) self.assertIn(reverse("anvil_consortium_manager:index"), response.rendered_content) - def test_acm_link_with_edit_but_not_view_permission(self): - """ACM link does not show up if you only have edit permission. - - This is something that shouldn't happen but could if admin only gave EDIT but not VIEW permission.""" - user = User.objects.create_user(username="test-none", password="test-none") - user.user_permissions.add( - Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) - ) - self.client.force_login(user) - response = self.client.get(self.get_url()) - self.assertNotIn("AnVIL Consortium Manager", response.rendered_content) - self.assertNotIn(reverse("anvil_consortium_manager:index"), response.rendered_content) - def test_site_announcement_no_text(self): user = User.objects.create_user(username="test-none", password="test-none") self.client.force_login(user) @@ -1011,7 +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=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) def get_url(self, *args): @@ -1152,52 +1143,110 @@ def test_partner_upload_workspace_table(self): self.assertIn(workspace.workspace, table.data) self.assertNotIn(other_workspace.workspace, table.data) - def test_link_to_audit(self): - """Response includes a link to the audit page.""" + def test_links_view_user(self): + user = self.user obj = self.model_factory.create() - self.client.force_login(self.user) + self.client.force_login(user) response = self.client.get(self.get_url(obj.cycle)) - self.assertContains( + self.assertNotContains(response, reverse("gregor_anvil:upload_cycles:update", args=[obj.cycle])) + self.assertNotContains( response, reverse("gregor_anvil:audit:upload_workspaces:sharing:by_upload_cycle", args=[obj.cycle]) ) + self.assertNotContains( + response, reverse("gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_cycle", args=[obj.cycle]) + ) - def test_link_to_update_view_staff_edit(self): - """Response includes a link to the update view for staff edit users.""" + def test_links_staff_view_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) obj = self.model_factory.create() - self.user.user_permissions.add( + self.client.force_login(user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertNotContains(response, reverse("gregor_anvil:upload_cycles:update", args=[obj.cycle])) + self.assertContains( + response, reverse("gregor_anvil:audit:upload_workspaces:sharing:by_upload_cycle", args=[obj.cycle]) + ) + self.assertContains( + response, reverse("gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_cycle", args=[obj.cycle]) + ) + + def test_links_staff_edit_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + 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) ) - self.client.force_login(self.user) + obj = self.model_factory.create() + self.client.force_login(user) response = self.client.get(self.get_url(obj.cycle)) self.assertContains(response, reverse("gregor_anvil:upload_cycles:update", args=[obj.cycle])) + self.assertContains( + response, reverse("gregor_anvil:audit:upload_workspaces:sharing:by_upload_cycle", args=[obj.cycle]) + ) + self.assertContains( + response, reverse("gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_cycle", args=[obj.cycle]) + ) - def test_link_to_update_view_staff_view(self): - """Response includes a link to the update view for staff edit users.""" - obj = self.model_factory.create() + def test_includes_date_ready_for_compute(self): + obj = self.model_factory.create(is_past=True, date_ready_for_compute="2022-01-01") self.client.force_login(self.user) response = self.client.get(self.get_url(obj.cycle)) - self.assertNotContains(response, reverse("gregor_anvil:upload_cycles:update", args=[obj.cycle])) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Jan. 1, 2022") - def test_contains_sharing_audit_button(self): - obj = self.model_factory.create() + def test_alert_for_current_cycle(self): + upload_cycle = self.model_factory.create(is_current=True) self.client.force_login(self.user) - response = self.client.get(self.get_url(obj.cycle)) - url = reverse("gregor_anvil:audit:upload_workspaces:sharing:by_upload_cycle", args=[obj.cycle]) - self.assertContains(response, url) + response = self.client.get(self.get_url(upload_cycle.cycle)) + self.assertContains(response, "alert alert-success") + self.assertContains(response, "This is the current upload cycle.") - def test_contains_auth_domain_audit_button(self): - obj = self.model_factory.create() + def test_alert_for_past_cycle(self): + upload_cycle = self.model_factory.create(is_past=True) self.client.force_login(self.user) - response = self.client.get(self.get_url(obj.cycle)) - url = reverse("gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_cycle", args=[obj.cycle]) - self.assertContains(response, url) + response = self.client.get(self.get_url(upload_cycle.cycle)) + self.assertContains(response, "alert alert-danger") + self.assertContains(response, "This is a past upload cycle.") - def test_includes_date_ready_for_compute(self): - obj = self.model_factory.create(is_past=True, date_ready_for_compute="2022-01-01") + def test_alert_for_future_cycle(self): + upload_cycle = self.model_factory.create(is_future=True) + self.client.force_login(self.user) + response = self.client.get(self.get_url(upload_cycle.cycle)) + self.assertContains(response, "alert alert-danger") + self.assertContains(response, "This is a future upload cycle.") + + def test_note_view_user(self): + obj = self.model_factory.create(note="a test note") self.client.force_login(self.user) response = self.client.get(self.get_url(obj.cycle)) - self.assertEqual(response.status_code, 200) - self.assertContains(response, "Jan. 1, 2022") + self.assertNotContains(response, "a test note") + + def test_note_staff_view_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + obj = self.model_factory.create(note="a test note") + self.client.force_login(user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertContains(response, "a test note") + + def test_note_staff_edit_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + 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) + ) + obj = self.model_factory.create(note="a test note") + self.client.force_login(user) + response = self.client.get(self.get_url(obj.cycle)) + self.assertContains(response, "a test note") class UploadCycleListTest(TestCase): @@ -1210,7 +1259,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.VIEW_PERMISSION_CODENAME) ) def get_url(self): @@ -1310,10 +1359,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) - ) - self.user.user_permissions.add( - Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) self.object = factories.UploadWorkspaceFactory.create() @@ -1327,9 +1373,33 @@ def test_status_code(self): response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) self.assertEqual(response.status_code, 200) - def test_contains_sharing_audit_button(self): + def test_includes_date_qc_completed(self): + self.object.date_qc_completed = "2022-01-01" + self.object.save() self.client.force_login(self.user) response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Jan. 1, 2022") + + def test_links_view_user(self): + user = self.user + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + self.assertNotContains( + response, reverse("gregor_anvil:research_centers:detail", args=[self.object.research_center.pk]) + ) + self.assertNotContains( + response, reverse("gregor_anvil:consent_groups:detail", args=[self.object.consent_group.pk]) + ) + # Audit links. + url = reverse( + "gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_workspace", + args=[ + self.object.workspace.billing_project.name, + self.object.workspace.name, + ], + ) + self.assertNotContains(response, url) url = reverse( "gregor_anvil:audit:upload_workspaces:sharing:by_upload_workspace", args=[ @@ -1337,11 +1407,23 @@ def test_contains_sharing_audit_button(self): self.object.workspace.name, ], ) - self.assertContains(response, url) + self.assertNotContains(response, url) - def test_contains_auth_domain_audit_button(self): - self.client.force_login(self.user) - response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) + def test_links_staff_view_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + # Links to other resources + self.assertContains( + response, reverse("gregor_anvil:research_centers:detail", args=[self.object.research_center.pk]) + ) + self.assertContains( + response, reverse("gregor_anvil:consent_groups:detail", args=[self.object.consent_group.pk]) + ) + # Audit links. url = reverse( "gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_workspace", args=[ @@ -1350,14 +1432,48 @@ def test_contains_auth_domain_audit_button(self): ], ) self.assertContains(response, url) + url = reverse( + "gregor_anvil:audit:upload_workspaces:sharing:by_upload_workspace", + args=[ + self.object.workspace.billing_project.name, + self.object.workspace.name, + ], + ) + self.assertContains(response, url) - def test_includes_date_qc_completed(self): - self.object.date_qc_completed = "2022-01-01" - self.object.save() - self.client.force_login(self.user) - response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) - self.assertEqual(response.status_code, 200) - self.assertContains(response, "Jan. 1, 2022") + def test_links_staff_edit_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + 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) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + self.assertContains( + response, reverse("gregor_anvil:research_centers:detail", args=[self.object.research_center.pk]) + ) + self.assertContains( + response, reverse("gregor_anvil:consent_groups:detail", args=[self.object.consent_group.pk]) + ) + # Audit links. + url = reverse( + "gregor_anvil:audit:upload_workspaces:auth_domains:by_upload_workspace", + args=[ + self.object.workspace.billing_project.name, + self.object.workspace.name, + ], + ) + self.assertContains(response, url) + url = reverse( + "gregor_anvil:audit:upload_workspaces:sharing:by_upload_workspace", + args=[ + self.object.workspace.billing_project.name, + self.object.workspace.name, + ], + ) + self.assertContains(response, url) class UploadWorkspaceListTest(TestCase): @@ -1377,12 +1493,23 @@ def get_url(self, *args): """Get the url for the view being tested.""" return reverse("anvil_consortium_manager:workspaces:list", args=args) - def test_view_has_correct_table_class(self): + def test_view_has_correct_table_class_view_user(self): + """The view has the correct table class in the context.""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.UploadWorkspaceTable) + + def test_view_has_correct_table_class_staff_view_user(self): """The view has the correct table class in the context.""" self.client.force_login(self.user) response = self.client.get(self.get_url(self.workspace_type)) self.assertIn("table", response.context_data) - self.assertIsInstance(response.context_data["table"], tables.UploadWorkspaceTable) + self.assertIs(type(response.context_data["table"]), tables.UploadWorkspaceTable) class UploadWorkspaceCreateTest(AnVILAPIMockTestMixin, TestCase): @@ -1597,7 +1724,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.VIEW_PERMISSION_CODENAME) ) self.object = factories.ResourceWorkspaceFactory.create() @@ -1636,12 +1763,23 @@ def get_url(self, *args): """Get the url for the view being tested.""" return reverse("anvil_consortium_manager:workspaces:list", args=args) - def test_view_has_correct_table_class(self): + def test_view_has_correct_table_class_view_user(self): + """The view has the correct table class in the context.""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.DefaultWorkspaceTable) + + def test_view_has_correct_table_class_staff_view_user(self): """The view has the correct table class in the context.""" self.client.force_login(self.user) response = self.client.get(self.get_url(self.workspace_type)) self.assertIn("table", response.context_data) - self.assertIsInstance(response.context_data["table"], tables.DefaultWorkspaceTable) + self.assertIs(type(response.context_data["table"]), tables.DefaultWorkspaceStaffTable) class ResourceWorkspaceCreateTest(AnVILAPIMockTestMixin, TestCase): @@ -1734,7 +1872,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.VIEW_PERMISSION_CODENAME) ) self.object = factories.TemplateWorkspaceFactory.create() @@ -1766,12 +1904,23 @@ def get_url(self, *args): """Get the url for the view being tested.""" return reverse("anvil_consortium_manager:workspaces:list", args=args) - def test_view_has_correct_table_class(self): + def test_view_has_correct_table_class_view_user(self): + """The view has the correct table class in the context.""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.TemplateWorkspaceTable) + + def test_view_has_correct_table_class_staff_view_user(self): """The view has the correct table class in the context.""" self.client.force_login(self.user) response = self.client.get(self.get_url(self.workspace_type)) self.assertIn("table", response.context_data) - self.assertIsInstance(response.context_data["table"], tables.TemplateWorkspaceTable) + self.assertIs(type(response.context_data["table"]), tables.TemplateWorkspaceTable) class TemplateWorkspaceCreateTest(AnVILAPIMockTestMixin, TestCase): @@ -1855,8 +2004,8 @@ def test_creates_upload_workspace(self): self.assertEqual(new_workspace_data.intended_use, "foo bar") -class ConsortiumCombinedDataWorkspaceDetailTest(TestCase): - """Tests of the anvil_consortium_manager WorkspaceDetail view using the CombinedConsortiumDataWorkspace adapter.""" +class ConsortiumCombinedDataWorkspaceListTest(TestCase): + """Tests of the anvil_consortium_manager WorkspaceList view using this app's adapter.""" def setUp(self): """Set up test class.""" @@ -1866,8 +2015,41 @@ def setUp(self): self.user.user_permissions.add( Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) + self.workspace_type = "combined_consortium" + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse("anvil_consortium_manager:workspaces:list", args=args) + + def test_view_has_correct_table_class_view_user(self): + """The view has the correct table class in the context.""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.CombinedConsortiumDataWorkspaceTable) + + def test_view_has_correct_table_class_staff_view_user(self): + """The view has the correct table class in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.CombinedConsortiumDataWorkspaceTable) + + +class ConsortiumCombinedDataWorkspaceDetailTest(TestCase): + """Tests of the anvil_consortium_manager WorkspaceDetail view using the CombinedConsortiumDataWorkspace adapter.""" + + 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_EDIT_PERMISSION_CODENAME) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) self.object = factories.CombinedConsortiumDataWorkspaceFactory.create() @@ -1911,9 +2093,27 @@ def test_contains_upload_workspaces_from_previous_cycles(self): self.assertIn(upload_workspace_1, response.context_data["upload_workspace_table"].data) self.assertIn(upload_workspace_2, response.context_data["upload_workspace_table"].data) - def test_contains_sharing_audit_button(self): + def test_includes_date_completed(self): + self.object.date_completed = "2022-01-01" + self.object.save() self.client.force_login(self.user) response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Jan. 1, 2022") + + def test_links_view_user(self): + user = self.user + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + # Audit links. + url = reverse( + "gregor_anvil:audit:combined_workspaces:auth_domains:by_workspace", + args=[ + self.object.workspace.billing_project.name, + self.object.workspace.name, + ], + ) + self.assertNotContains(response, url) url = reverse( "gregor_anvil:audit:combined_workspaces:sharing:by_workspace", args=[ @@ -1921,11 +2121,16 @@ def test_contains_sharing_audit_button(self): self.object.workspace.name, ], ) - self.assertContains(response, url) + self.assertNotContains(response, url) - def test_contains_auth_domain_audit_button(self): - self.client.force_login(self.user) - response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) + def test_links_staff_view_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + # Audit links. url = reverse( "gregor_anvil:audit:combined_workspaces:auth_domains:by_workspace", args=[ @@ -1934,29 +2139,93 @@ def test_contains_auth_domain_audit_button(self): ], ) self.assertContains(response, url) + url = reverse( + "gregor_anvil:audit:combined_workspaces:sharing:by_workspace", + args=[ + self.object.workspace.billing_project.name, + self.object.workspace.name, + ], + ) + self.assertContains(response, url) - def test_includes_date_completed(self): - self.object.date_completed = "2022-01-01" - self.object.save() - self.client.force_login(self.user) - response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) - self.assertEqual(response.status_code, 200) - self.assertContains(response, "Jan. 1, 2022") - - -class ReleaseWorkspaceDetailTest(TestCase): - """Tests of the anvil_consortium_manager WorkspaceDetail view using the ReleaseWorkspaceAdapter.""" - - 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( + def test_links_staff_edit_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) ) - self.object = factories.ReleaseWorkspaceFactory.create() - + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + # Audit links. + url = reverse( + "gregor_anvil:audit:combined_workspaces:auth_domains:by_workspace", + args=[ + self.object.workspace.billing_project.name, + self.object.workspace.name, + ], + ) + self.assertContains(response, url) + url = reverse( + "gregor_anvil:audit:combined_workspaces:sharing:by_workspace", + args=[ + self.object.workspace.billing_project.name, + self.object.workspace.name, + ], + ) + self.assertContains(response, url) + + +class ReleaseWorkspaceListTest(TestCase): + """Tests of the anvil_consortium_manager WorkspaceList view using this app's adapter.""" + + 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.workspace_type = "release" + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse("anvil_consortium_manager:workspaces:list", args=args) + + def test_view_has_correct_table_class_view_user(self): + """The view has the correct table class in the context.""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.ReleaseWorkspaceTable) + + def test_view_has_correct_table_class_staff_view_user(self): + """The view has the correct table class in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.ReleaseWorkspaceTable) + + +class ReleaseWorkspaceDetailTest(TestCase): + """Tests of the anvil_consortium_manager WorkspaceDetail view using the ReleaseWorkspaceAdapter.""" + + 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.VIEW_PERMISSION_CODENAME) + ) + self.object = factories.ReleaseWorkspaceFactory.create() + def get_url(self, *args): """Get the url for the view being tested.""" return reverse("anvil_consortium_manager:workspaces:detail", args=args) @@ -2009,6 +2278,50 @@ def test_contains_upload_workspaces_from_previous_cycles(self): response.context_data["included_workspace_table"].data, ) + def test_links_view_user(self): + user = self.user + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + self.assertContains( + response, reverse("gregor_anvil:upload_cycles:detail", args=[self.object.upload_cycle.cycle]) + ) + self.assertNotContains( + response, reverse("gregor_anvil:consent_groups:detail", args=[self.object.consent_group.pk]) + ) + + def test_links_staff_view_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + # Links to other resources + self.assertContains( + response, reverse("gregor_anvil:upload_cycles:detail", args=[self.object.upload_cycle.cycle]) + ) + self.assertContains( + response, reverse("gregor_anvil:consent_groups:detail", args=[self.object.consent_group.pk]) + ) + + def test_links_staff_edit_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + 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) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + # Links to other resources + self.assertContains( + response, reverse("gregor_anvil:upload_cycles:detail", args=[self.object.upload_cycle.cycle]) + ) + self.assertContains( + response, reverse("gregor_anvil:consent_groups:detail", args=[self.object.consent_group.pk]) + ) + class WorkspaceReportTest(TestCase): def setUp(self): @@ -2171,6 +2484,42 @@ def test_correct_count_consortium_members_with_access_to_workspaces_in_context( self.assertEqual(response.context_data["verified_linked_accounts"], 1) +class DCCProcessingWorkspaceListTest(TestCase): + """Tests of the anvil_consortium_manager WorkspaceList view using this app's adapter.""" + + 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.workspace_type = "dcc_processing" + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse("anvil_consortium_manager:workspaces:list", args=args) + + def test_view_has_correct_table_class_view_user(self): + """The view has the correct table class in the context.""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.DCCProcessingWorkspaceTable) + + def test_view_has_correct_table_class_staff_view_user(self): + """The view has the correct table class in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.DCCProcessingWorkspaceTable) + + class DCCProcessingWorkspaceDetailTest(TestCase): """Tests of the anvil_consortium_manager WorkspaceDetail view using the DCCProcessingWorkspace adapter.""" @@ -2180,7 +2529,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.VIEW_PERMISSION_CODENAME) ) self.object = factories.DCCProcessingWorkspaceFactory.create() @@ -2194,6 +2543,77 @@ def test_status_code(self): response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) self.assertEqual(response.status_code, 200) + def test_links_view_user(self): + user = self.user + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + self.assertContains( + response, reverse("gregor_anvil:upload_cycles:detail", args=[self.object.upload_cycle.cycle]) + ) + + def test_links_staff_view_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + # Links to other resources + self.assertContains( + response, reverse("gregor_anvil:upload_cycles:detail", args=[self.object.upload_cycle.cycle]) + ) + + def test_links_staff_edit_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + 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) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + # Links to other resources + self.assertContains( + response, reverse("gregor_anvil:upload_cycles:detail", args=[self.object.upload_cycle.cycle]) + ) + + +class DCCProcessedDataWorkspaceListTest(TestCase): + """Tests of the anvil_consortium_manager WorkspaceList view using this app's adapter.""" + + 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.workspace_type = "dcc_processed_data" + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse("anvil_consortium_manager:workspaces:list", args=args) + + def test_view_has_correct_table_class_view_user(self): + """The view has the correct table class in the context.""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.DCCProcessedDataWorkspaceTable) + + def test_view_has_correct_table_class_staff_view_user(self): + """The view has the correct table class in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.DCCProcessedDataWorkspaceTable) + class DCCProcessedDataWorkspaceDetailTest(TestCase): """Tests of the anvil_consortium_manager WorkspaceDetail view using the DCCProcessedDataWorkspace adapter.""" @@ -2204,7 +2624,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.VIEW_PERMISSION_CODENAME) ) self.object = factories.DCCProcessedDataWorkspaceFactory.create() @@ -2218,6 +2638,49 @@ def test_status_code(self): response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) self.assertEqual(response.status_code, 200) + def test_links_view_user(self): + user = self.user + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + self.assertContains( + response, reverse("gregor_anvil:upload_cycles:detail", args=[self.object.upload_cycle.cycle]) + ) + self.assertNotContains( + response, reverse("gregor_anvil:consent_groups:detail", args=[self.object.consent_group.pk]) + ) + + def test_links_staff_view_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + # Links to other resources + self.assertContains( + response, reverse("gregor_anvil:upload_cycles:detail", args=[self.object.upload_cycle.cycle]) + ) + self.assertContains( + response, reverse("gregor_anvil:consent_groups:detail", args=[self.object.consent_group.pk]) + ) + + def test_links_staff_edit_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + 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) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + self.assertContains( + response, reverse("gregor_anvil:upload_cycles:detail", args=[self.object.upload_cycle.cycle]) + ) + self.assertContains( + response, reverse("gregor_anvil:consent_groups:detail", args=[self.object.consent_group.pk]) + ) + class ExchangeWorkspaceDetailTest(TestCase): """Tests of the anvil_consortium_manager WorkspaceDetail view using the ExchangeWorkspace adapter.""" @@ -2228,10 +2691,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) - ) - self.user.user_permissions.add( - Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_EDIT_PERMISSION_CODENAME) + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) ) self.object = factories.ExchangeWorkspaceFactory.create() @@ -2245,6 +2705,40 @@ def test_status_code(self): response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) self.assertEqual(response.status_code, 200) + def test_links_view_user(self): + user = self.user + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + self.assertNotContains( + response, reverse("gregor_anvil:research_centers:detail", args=[self.object.research_center.pk]) + ) + + def test_links_staff_view_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + # Links to other resources + self.assertContains( + response, reverse("gregor_anvil:research_centers:detail", args=[self.object.research_center.pk]) + ) + + def test_links_staff_edit_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + 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) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + self.assertContains( + response, reverse("gregor_anvil:research_centers:detail", args=[self.object.research_center.pk]) + ) + class ExchangeWorkspaceListTest(TestCase): """Tests of the anvil_consortium_manager WorkspaceList view using this app's adapter.""" @@ -2263,12 +2757,23 @@ def get_url(self, *args): """Get the url for the view being tested.""" return reverse("anvil_consortium_manager:workspaces:list", args=args) - def test_view_has_correct_table_class(self): + def test_view_has_correct_table_class_view_user(self): + """The view has the correct table class in the context.""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.ExchangeWorkspaceTable) + + def test_view_has_correct_table_class_staff_view_user(self): """The view has the correct table class in the context.""" self.client.force_login(self.user) response = self.client.get(self.get_url(self.workspace_type)) self.assertIn("table", response.context_data) - self.assertIsInstance(response.context_data["table"], tables.ExchangeWorkspaceTable) + self.assertIs(type(response.context_data["table"]), tables.ExchangeWorkspaceStaffTable) class ExchangeWorkspaceCreateTest(AnVILAPIMockTestMixin, TestCase): @@ -2353,6 +2858,109 @@ def test_creates_upload_workspace(self): self.assertEqual(new_workspace_data.research_center, research_center) +class PartnerUploadWorkspaceDetailTest(TestCase): + """Tests of the anvil_consortium_manager WorkspaceDetail view using the PartnerUploadWorkspace adapter.""" + + 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.VIEW_PERMISSION_CODENAME) + ) + self.object = factories.PartnerUploadWorkspaceFactory.create() + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse("anvil_consortium_manager:workspaces:detail", args=args) + + def test_status_code(self): + """Response has a status code of 200.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.object.workspace.billing_project.name, self.object.workspace.name)) + self.assertEqual(response.status_code, 200) + + def test_links_view_user(self): + user = self.user + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + self.assertNotContains( + response, reverse("gregor_anvil:partner_groups:detail", args=[self.object.partner_group.pk]) + ) + self.assertNotContains( + response, reverse("gregor_anvil:consent_groups:detail", args=[self.object.consent_group.pk]) + ) + + def test_links_staff_view_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + # Links to other resources + self.assertContains( + response, reverse("gregor_anvil:partner_groups:detail", args=[self.object.partner_group.pk]) + ) + self.assertContains( + response, reverse("gregor_anvil:consent_groups:detail", args=[self.object.consent_group.pk]) + ) + + def test_links_staff_edit_user(self): + user = User.objects.create_user(username="test-staff-view", password="test-staff-view") + user.user_permissions.add( + 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) + ) + self.client.force_login(user) + response = self.client.get(self.object.get_absolute_url()) + self.assertContains( + response, reverse("gregor_anvil:partner_groups:detail", args=[self.object.partner_group.pk]) + ) + self.assertContains( + response, reverse("gregor_anvil:consent_groups:detail", args=[self.object.consent_group.pk]) + ) + + +class PartnerUploadWorkspaceListTest(TestCase): + """Tests of the anvil_consortium_manager WorkspaceList view using this app's adapter.""" + + 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.workspace_type = adapters.PartnerUploadWorkspaceAdapter().get_type() + + def get_url(self, *args): + """Get the url for the view being tested.""" + return reverse("anvil_consortium_manager:workspaces:list", args=args) + + def test_view_has_correct_table_class_view_user(self): + """The view has the correct table class in the context.""" + user = User.objects.create_user(username="test-view", password="test-view") + user.user_permissions.add( + Permission.objects.get(codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME) + ) + self.client.force_login(user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.PartnerUploadWorkspaceTable) + + def test_view_has_correct_table_class_staff_view_user(self): + """The view has the correct table class in the context.""" + self.client.force_login(self.user) + response = self.client.get(self.get_url(self.workspace_type)) + self.assertIn("table", response.context_data) + self.assertIs(type(response.context_data["table"]), tables.PartnerUploadWorkspaceStaffTable) + + class ManagedGroupCreateTest(AnVILAPIMockTestMixin, TestCase): """Tests for custom ManagedGroup behavior.""" diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index 0b138be3..c231280c 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -1,6 +1,7 @@ from anvil_consortium_manager.auth import ( AnVILConsortiumManagerStaffEditRequired, AnVILConsortiumManagerStaffViewRequired, + AnVILConsortiumManagerViewRequired, ) from anvil_consortium_manager.models import ( Account, @@ -120,7 +121,7 @@ class UploadCycleUpdate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageM success_message = "Successfully updated Upload Cycle." -class UploadCycleDetail(AnVILConsortiumManagerStaffViewRequired, MultiTableMixin, DetailView): +class UploadCycleDetail(AnVILConsortiumManagerViewRequired, MultiTableMixin, DetailView): """View to show details about an `UploadCycle`.""" model = models.UploadCycle @@ -156,7 +157,7 @@ def get_tables_data(self): ] -class UploadCycleList(AnVILConsortiumManagerStaffViewRequired, SingleTableView): +class UploadCycleList(AnVILConsortiumManagerViewRequired, SingleTableView): """View to show a list of `UploadCycle` objects.""" model = models.UploadCycle diff --git a/gregor_django/templates/base.html b/gregor_django/templates/base.html index 4a6a5c08..ada610e9 100644 --- a/gregor_django/templates/base.html +++ b/gregor_django/templates/base.html @@ -79,7 +79,7 @@ -