diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97210601..57eaceea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,11 +28,11 @@ jobs: backend: ["sqlite", "mariadb"] mariadb-version: ["10.4"] include: - - python-version: 3.12 # Possible future version. + - python-version: "3.10" # Future ubuntu 22.04 upgrade. backend: "mariadb" - mariadb-version: "10.5" + mariadb-version: "10.6" pip-recompile: true - - python-version: 3.12 # Possible future version. + - python-version: 3.12 # Future ubuntu 24.04.01 upgrade. backend: "mariadb" mariadb-version: "10.11" pip-recompile: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf731237..46d6a2e9 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.4.9 + rev: v0.5.1 hooks: # Run the linter. - id: ruff diff --git a/gregor_django/gregor_anvil/migrations/0024_member_upload_groups.py b/gregor_django/gregor_anvil/migrations/0024_member_upload_groups.py new file mode 100644 index 00000000..e4911d29 --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0024_member_upload_groups.py @@ -0,0 +1,55 @@ +# Generated by Django 4.2.13 on 2024-07-09 21:53 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('anvil_consortium_manager', '0019_accountuserarchive'), + ('gregor_anvil', '0023_resourceworkspace_add_brief_description'), + ] + + operations = [ + migrations.AddField( + model_name='historicalpartnergroup', + name='member_group', + field=models.ForeignKey(blank=True, db_constraint=False, help_text='The AnVIL group containing members from this Partner Group.', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='anvil_consortium_manager.managedgroup'), + ), + migrations.AddField( + model_name='historicalpartnergroup', + name='uploader_group', + field=models.ForeignKey(blank=True, db_constraint=False, help_text='The group that has write/upload access to workspaces associated with this Partner Group.', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='anvil_consortium_manager.managedgroup'), + ), + migrations.AddField( + model_name='historicalresearchcenter', + name='member_group', + field=models.ForeignKey(blank=True, db_constraint=False, help_text='The AnVIL group containing members from this Research Center.', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='anvil_consortium_manager.managedgroup'), + ), + migrations.AddField( + model_name='historicalresearchcenter', + name='uploader_group', + field=models.ForeignKey(blank=True, db_constraint=False, help_text='The group that has write/upload access to workspaces associated with this Research Center.', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='anvil_consortium_manager.managedgroup'), + ), + migrations.AddField( + model_name='partnergroup', + name='member_group', + field=models.OneToOneField(blank=True, help_text='The AnVIL group containing members from this Partner Group.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='partner_group_of_members', to='anvil_consortium_manager.managedgroup'), + ), + migrations.AddField( + model_name='partnergroup', + name='uploader_group', + field=models.OneToOneField(blank=True, help_text='The group that has write/upload access to workspaces associated with this Partner Group.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='partner_group_of_uploaders', to='anvil_consortium_manager.managedgroup'), + ), + migrations.AddField( + model_name='researchcenter', + name='member_group', + field=models.OneToOneField(blank=True, help_text='The AnVIL group containing members from this Research Center.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='research_center_of_members', to='anvil_consortium_manager.managedgroup'), + ), + migrations.AddField( + model_name='researchcenter', + name='uploader_group', + field=models.OneToOneField(blank=True, help_text='The group that has write/upload access to workspaces associated with this Research Center.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='research_center_of_uploaders', to='anvil_consortium_manager.managedgroup'), + ), + ] diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index a1ad0b72..8a3813df 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -1,4 +1,4 @@ -from anvil_consortium_manager.models import BaseWorkspaceData +from anvil_consortium_manager.models import BaseWorkspaceData, ManagedGroup from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator from django.db import models @@ -42,6 +42,24 @@ class ResearchCenter(TimeStampedModel, models.Model): full_name = models.CharField(max_length=255, unique=True) """The full name of the Research Center.""" + member_group = models.OneToOneField( + ManagedGroup, + on_delete=models.PROTECT, + help_text="The AnVIL group containing members from this Research Center.", + related_name="research_center_of_members", + blank=True, + null=True, + ) + + uploader_group = models.OneToOneField( + ManagedGroup, + on_delete=models.PROTECT, + help_text="The group that has write/upload access to workspaces associated with this Research Center.", + related_name="research_center_of_uploaders", + blank=True, + null=True, + ) + history = HistoricalRecords() def __str__(self): @@ -56,6 +74,12 @@ def get_absolute_url(self): """Return the absolute url for this object.""" return reverse("gregor_anvil:research_centers:detail", args=[self.pk]) + def clean(self): + """Custom cleaning methods.""" + # Members group and uploaders group must be different. + if self.member_group and self.uploader_group and self.member_group == self.uploader_group: + raise ValidationError("member_group and uploader_group must be different!") + class PartnerGroup(TimeStampedModel, models.Model): """A model to track Partner Groups""" @@ -66,6 +90,24 @@ class PartnerGroup(TimeStampedModel, models.Model): full_name = models.CharField(max_length=255, unique=True) """The full name of the Partner Group""" + member_group = models.OneToOneField( + ManagedGroup, + on_delete=models.PROTECT, + help_text="The AnVIL group containing members from this Partner Group.", + related_name="partner_group_of_members", + null=True, + blank=True, + ) + + uploader_group = models.OneToOneField( + ManagedGroup, + on_delete=models.PROTECT, + help_text="The group that has write/upload access to workspaces associated with this Partner Group.", + related_name="partner_group_of_uploaders", + null=True, + blank=True, + ) + history = HistoricalRecords() def __str__(self): @@ -80,6 +122,12 @@ def get_absolute_url(self): """Return the absolute url for this object.""" return reverse("gregor_anvil:partner_groups:detail", args=[self.pk]) + def clean(self): + """Custom cleaning methods.""" + # Members group and uploaders group must be different. + if self.member_group and self.uploader_group and self.member_group == self.uploader_group: + raise ValidationError("member_group and uploader_group must be different!") + class UploadCycle(TimeStampedModel, models.Model): """A model tracking the upload cycles that exist in the app.""" diff --git a/gregor_django/gregor_anvil/tests/test_migrations.py b/gregor_django/gregor_anvil/tests/test_migrations.py index a8c1f64b..148617de 100644 --- a/gregor_django/gregor_anvil/tests/test_migrations.py +++ b/gregor_django/gregor_anvil/tests/test_migrations.py @@ -35,9 +35,10 @@ def prepare(self): FACTORY_CLASS=WorkspaceFactory, billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), ) + research_center_1 = ResearchCenter.objects.create(short_name="rc1", full_name="Research Center 1") self.u_1 = UploadWorkspace.objects.create( version=1, - research_center=factory.create(ResearchCenter, FACTORY_CLASS=factories.ResearchCenterFactory), + research_center=research_center_1, consent_group=consent_group, workspace=workspace, ) @@ -47,9 +48,10 @@ def prepare(self): FACTORY_CLASS=WorkspaceFactory, billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), ) + research_center_2 = ResearchCenter.objects.create(short_name="rc2", full_name="Research Center 2") self.u_2 = UploadWorkspace.objects.create( version=1, - research_center=factory.create(ResearchCenter, FACTORY_CLASS=factories.ResearchCenterFactory), + research_center=research_center_2, consent_group=consent_group, workspace=workspace, ) @@ -59,9 +61,10 @@ def prepare(self): FACTORY_CLASS=WorkspaceFactory, billing_project=factory.create(BillingProject, FACTORY_CLASS=BillingProjectFactory), ) + research_center_3 = ResearchCenter.objects.create(short_name="rc3", full_name="Research Center 3") self.u_3 = UploadWorkspace.objects.create( version=2, - research_center=factory.create(ResearchCenter, FACTORY_CLASS=factories.ResearchCenterFactory), + research_center=research_center_3, consent_group=consent_group, workspace=workspace, ) diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index f2be747c..27f9833f 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -1,6 +1,6 @@ from datetime import date, timedelta -from anvil_consortium_manager.tests.factories import WorkspaceFactory +from anvil_consortium_manager.tests.factories import ManagedGroupFactory, WorkspaceFactory from django.core.exceptions import NON_FIELD_ERRORS, ValidationError from django.db.utils import IntegrityError from django.test import TestCase @@ -78,7 +78,25 @@ class ResearchCenterTest(TestCase): def test_model_saving(self): """Creation using the model constructor and .save() works.""" - instance = models.ResearchCenter(full_name="Test name", short_name="TEST") + instance = models.ResearchCenter( + full_name="Test name", + short_name="TEST", + ) + instance.full_clean() + instance.save() + self.assertIsInstance(instance, models.ResearchCenter) + + def test_model_saving_with_groups(self): + """Creation using the model constructor and .save() works.""" + member_group = ManagedGroupFactory.create() + uploader_group = ManagedGroupFactory.create() + instance = models.ResearchCenter( + full_name="Test name", + short_name="TEST", + member_group=member_group, + uploader_group=uploader_group, + ) + instance.full_clean() instance.save() self.assertIsInstance(instance, models.ResearchCenter) @@ -103,6 +121,54 @@ def test_unique_short_name(self): with self.assertRaises(IntegrityError): instance2.save() + def test_member_group_uploader_group_must_be_different(self): + """The same group cannot be used as the members group and uploaders group.""" + group = ManagedGroupFactory.create() + instance = models.ResearchCenter( + full_name="Test name", + short_name="TEST", + member_group=group, + uploader_group=group, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("must be different", str(e.exception.error_dict[NON_FIELD_ERRORS][0])) + + def test_error_two_rcs_same_member_group(self): + """Cannot have the same member group for two RCs.""" + member_group = ManagedGroupFactory.create() + factories.ResearchCenterFactory.create(member_group=member_group) + instance = factories.ResearchCenterFactory.build( + full_name="Test name", + short_name="TEST", + member_group=member_group, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn("member_group", e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict["member_group"]), 1) + self.assertIn("already exists", str(e.exception.error_dict["member_group"][0])) + + def test_error_two_rcs_same_uploader_group(self): + """Cannot have the same uploader group for two RCs.""" + uploader_group = ManagedGroupFactory.create() + factories.ResearchCenterFactory.create(uploader_group=uploader_group) + instance = factories.ResearchCenterFactory.build( + full_name="Test name", + short_name="TEST", + uploader_group=uploader_group, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn("uploader_group", e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict["uploader_group"]), 1) + self.assertIn("already exists", str(e.exception.error_dict["uploader_group"][0])) + class UploadCycleTest(TestCase): """Tests for the UploadCycle model.""" @@ -422,6 +488,103 @@ def test_duplicated_workspace(self): instance_2.save() +class PartnerGroupTest(TestCase): + """Tests for the ResearchCenter model.""" + + def test_model_saving(self): + """Creation using the model constructor and .save() works.""" + instance = models.PartnerGroup( + full_name="Test name", + short_name="TEST", + ) + instance.full_clean() + instance.save() + self.assertIsInstance(instance, models.PartnerGroup) + + def test_model_saving_with_groups(self): + """Creation using the model constructor and .save() works.""" + member_group = ManagedGroupFactory.create() + uploader_group = ManagedGroupFactory.create() + instance = models.PartnerGroup( + full_name="Test name", + short_name="TEST", + member_group=member_group, + uploader_group=uploader_group, + ) + instance.full_clean() + instance.save() + self.assertIsInstance(instance, models.PartnerGroup) + + def test_str_method(self): + """The custom __str__ method returns the correct string.""" + instance = factories.PartnerGroupFactory.create(short_name="Test") + instance.save() + self.assertIsInstance(instance.__str__(), str) + self.assertEqual(instance.__str__(), "Test") + + def test_get_absolute_url(self): + """The get_absolute_url() method works.""" + instance = factories.PartnerGroupFactory() + self.assertIsInstance(instance.get_absolute_url(), str) + + def test_unique_short_name(self): + """Saving a model with a duplicate short name fails.""" + factories.PartnerGroupFactory.create(short_name="FOO") + instance2 = models.PartnerGroup(short_name="FOO", full_name="full name") + with self.assertRaises(ValidationError): + instance2.full_clean() + with self.assertRaises(IntegrityError): + instance2.save() + + def test_member_group_uploader_group_must_be_different(self): + """The same group cannot be used as the members group and uploaders group.""" + group = ManagedGroupFactory.create() + instance = models.PartnerGroup( + full_name="Test name", + short_name="TEST", + member_group=group, + uploader_group=group, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn(NON_FIELD_ERRORS, e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict[NON_FIELD_ERRORS]), 1) + self.assertIn("must be different", str(e.exception.error_dict[NON_FIELD_ERRORS][0])) + + def test_error_two_groups_same_member_group(self): + """Cannot have the same member group for two RCs.""" + member_group = ManagedGroupFactory.create() + factories.PartnerGroupFactory.create(member_group=member_group) + instance = factories.PartnerGroupFactory.build( + full_name="Test name", + short_name="TEST", + member_group=member_group, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn("member_group", e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict["member_group"]), 1) + self.assertIn("already exists", str(e.exception.error_dict["member_group"][0])) + + def test_error_two_groups_same_uploader_group(self): + """Cannot have the same uploader group for two RCs.""" + uploader_group = ManagedGroupFactory.create() + factories.PartnerGroupFactory.create(uploader_group=uploader_group) + instance = factories.PartnerGroupFactory.build( + full_name="Test name", + short_name="TEST", + uploader_group=uploader_group, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn("uploader_group", e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict["uploader_group"]), 1) + self.assertIn("already exists", str(e.exception.error_dict["uploader_group"][0])) + + class PartnerUploadWorkspaceTest(TestCase): """Tests for the PartnerUploadWorkspace model.""" diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 794716dd..017146b7 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -18,6 +18,7 @@ from django.test import RequestFactory, TestCase from django.urls import reverse +from gregor_django.users.tables import UserTable from gregor_django.users.tests.factories import UserFactory from .. import forms, models, tables, views @@ -283,13 +284,84 @@ def test_site_user_table(self): self.client.force_login(self.user) response = self.client.get(self.get_url(obj.pk)) - self.assertIn("site_user_table", response.context_data) - table = response.context_data["site_user_table"] + table = response.context_data["tables"][0] self.assertEqual(len(table.rows), 1) self.assertIn(site_user, table.data) self.assertNotIn(non_site_user, table.data) + def test_link_to_member_group(self): + """Response includes a link to the members group if it exists.""" + member_group = acm_factories.ManagedGroupFactory.create() + obj = self.model_factory.create(member_group=member_group) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + self.assertContains(response, member_group.get_absolute_url()) + + def test_link_to_uploader_group(self): + """Response includes a link to the uploader group if it exists.""" + uploader_group = acm_factories.ManagedGroupFactory.create() + obj = self.model_factory.create(uploader_group=uploader_group) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + self.assertContains(response, uploader_group.get_absolute_url()) + + def test_table_classes(self): + """Table classes are correct.""" + obj = self.model_factory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + self.assertIn("tables", response.context_data) + self.assertEqual(len(response.context_data["tables"]), 3) + self.assertIsInstance(response.context_data["tables"][0], UserTable) + self.assertEqual(len(response.context_data["tables"][0].data), 0) + self.assertIsInstance(response.context_data["tables"][1], tables.AccountTable) + self.assertEqual(len(response.context_data["tables"][1].data), 0) + self.assertIsInstance(response.context_data["tables"][2], tables.AccountTable) + self.assertEqual(len(response.context_data["tables"][2].data), 0) + + def test_rc_member_table(self): + member_group = acm_factories.ManagedGroupFactory.create() + obj = self.model_factory.create(member_group=member_group) + account = acm_factories.AccountFactory.create(verified=True) + acm_factories.GroupAccountMembershipFactory.create(account=account, group=member_group) + other_account = acm_factories.AccountFactory.create(verified=True) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + table = response.context_data["tables"][1] + self.assertEqual(len(table.rows), 1) + self.assertIn(account, table.data) + self.assertNotIn(other_account, table.data) + + def test_member_table_group_not_set(self): + obj = self.model_factory.create() + acm_factories.AccountFactory.create(verified=True) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + table = response.context_data["tables"][1] + self.assertEqual(len(table.rows), 0) + + def test_rc_uploader_table(self): + uploader_group = acm_factories.ManagedGroupFactory.create() + obj = self.model_factory.create(uploader_group=uploader_group) + account = acm_factories.AccountFactory.create(verified=True) + acm_factories.GroupAccountMembershipFactory.create(account=account, group=uploader_group) + other_account = acm_factories.AccountFactory.create(verified=True) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + table = response.context_data["tables"][2] + self.assertEqual(len(table.rows), 1) + self.assertIn(account, table.data) + self.assertNotIn(other_account, table.data) + + def test_upload_table_group_not_set(self): + obj = self.model_factory.create() + acm_factories.AccountFactory.create(verified=True) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + table = response.context_data["tables"][2] + self.assertEqual(len(table.rows), 0) + class ResearchCenterListTest(TestCase): """Tests for the ResearchCenterList view.""" @@ -425,13 +497,66 @@ def test_site_user_table(self): self.client.force_login(self.user) response = self.client.get(self.get_url(obj.pk)) - self.assertIn("partner_group_user_table", response.context_data) - table = response.context_data["partner_group_user_table"] + self.assertIn("tables", response.context_data) + table = response.context_data["tables"][0] self.assertEqual(len(table.rows), 1) self.assertIn(pg_user, table.data) self.assertNotIn(non_pg_user, table.data) + def test_table_classes(self): + """Table classes are correct.""" + obj = self.model_factory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + self.assertIn("tables", response.context_data) + self.assertEqual(len(response.context_data["tables"]), 3) + self.assertIsInstance(response.context_data["tables"][0], UserTable) + self.assertIsInstance(response.context_data["tables"][1], tables.AccountTable) + self.assertIsInstance(response.context_data["tables"][2], tables.AccountTable) + + def test_member_table(self): + member_group = acm_factories.ManagedGroupFactory.create() + obj = self.model_factory.create(member_group=member_group) + account = acm_factories.AccountFactory.create(verified=True) + acm_factories.GroupAccountMembershipFactory.create(account=account, group=member_group) + other_account = acm_factories.AccountFactory.create(verified=True) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + table = response.context_data["tables"][1] + self.assertEqual(len(table.rows), 1) + self.assertIn(account, table.data) + self.assertNotIn(other_account, table.data) + + def test_member_table_group_not_set(self): + obj = self.model_factory.create() + acm_factories.AccountFactory.create(verified=True) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + table = response.context_data["tables"][1] + self.assertEqual(len(table.rows), 0) + + def test_uploader_table(self): + uploader_group = acm_factories.ManagedGroupFactory.create() + obj = self.model_factory.create(uploader_group=uploader_group) + account = acm_factories.AccountFactory.create(verified=True) + acm_factories.GroupAccountMembershipFactory.create(account=account, group=uploader_group) + other_account = acm_factories.AccountFactory.create(verified=True) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + table = response.context_data["tables"][2] + self.assertEqual(len(table.rows), 1) + self.assertIn(account, table.data) + self.assertNotIn(other_account, table.data) + + def test_upload_table_group_not_set(self): + obj = self.model_factory.create() + acm_factories.AccountFactory.create(verified=True) + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + table = response.context_data["tables"][2] + self.assertEqual(len(table.rows), 0) + class PartnerGroupListTest(TestCase): """Tests for the ResearchCenterList view.""" diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index cde7fed7..d925ad94 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -7,7 +7,7 @@ from django.contrib.messages.views import SuccessMessageMixin from django.db.models import Count, Q from django.views.generic import CreateView, DetailView, TemplateView -from django_tables2 import MultiTableMixin, SingleTableMixin, SingleTableView +from django_tables2 import MultiTableMixin, SingleTableView from gregor_django.users.tables import UserTable @@ -29,14 +29,29 @@ class ConsentGroupList(AnVILConsortiumManagerStaffViewRequired, SingleTableView) table_class = tables.ConsentGroupTable -class ResearchCenterDetail(AnVILConsortiumManagerStaffViewRequired, SingleTableMixin, DetailView): +class ResearchCenterDetail(AnVILConsortiumManagerStaffViewRequired, MultiTableMixin, DetailView): """View to show details about a `ResearchCenter`.""" model = models.ResearchCenter - context_table_name = "site_user_table" - def get_table(self): - return UserTable(User.objects.filter(research_centers=self.object)) + def get_tables(self): + if self.object.member_group is None: + members = Account.objects.none() + else: + members = Account.objects.filter( + groupaccountmembership__group=self.object.member_group, + ) + if self.object.uploader_group is None: + uploaders = Account.objects.none() + else: + uploaders = Account.objects.filter( + groupaccountmembership__group=self.object.uploader_group, + ) + return [ + UserTable(User.objects.filter(research_centers=self.object)), + tables.AccountTable(members, exclude=("user__research_centers", "number_groups")), + tables.AccountTable(uploaders, exclude=("user__research_centers", "number_groups")), + ] class ResearchCenterList(AnVILConsortiumManagerStaffViewRequired, SingleTableView): @@ -46,14 +61,29 @@ class ResearchCenterList(AnVILConsortiumManagerStaffViewRequired, SingleTableVie table_class = tables.ResearchCenterTable -class PartnerGroupDetail(AnVILConsortiumManagerStaffViewRequired, SingleTableMixin, DetailView): +class PartnerGroupDetail(AnVILConsortiumManagerStaffViewRequired, MultiTableMixin, DetailView): """View to show details about a `PartnerGroup`.""" model = models.PartnerGroup - context_table_name = "partner_group_user_table" - def get_table(self): - return UserTable(User.objects.filter(partner_groups=self.object)) + def get_tables(self): + if self.object.member_group is None: + members = Account.objects.none() + else: + members = Account.objects.filter( + groupaccountmembership__group=self.object.member_group, + ) + if self.object.uploader_group is None: + uploaders = Account.objects.none() + else: + uploaders = Account.objects.filter( + groupaccountmembership__group=self.object.uploader_group, + ) + return [ + UserTable(User.objects.filter(partner_groups=self.object)), + tables.AccountTable(members, exclude=("user__research_centers", "number_groups")), + tables.AccountTable(uploaders, exclude=("user__research_centers", "number_groups")), + ] class PartnerGroupList(AnVILConsortiumManagerStaffViewRequired, SingleTableView): diff --git a/gregor_django/templates/gregor_anvil/partnergroup_detail.html b/gregor_django/templates/gregor_anvil/partnergroup_detail.html index ac82d75b..b1c3ebea 100644 --- a/gregor_django/templates/gregor_anvil/partnergroup_detail.html +++ b/gregor_django/templates/gregor_anvil/partnergroup_detail.html @@ -7,11 +7,70 @@ {% endblock panel %} {% block after_panel %} -

Partner Group Users

- {% render_table partner_group_user_table %} +
+
+
+

+ +

+
+
+

+ This table shows Accounts in the member group for this Partner Group. +

+ {% render_table tables.1 %} +
+
+
+
+
+ +
+
+
+

+ +

+
+
+

+ This table shows Accounts in the uploader group for this Partner Group. +

+ {% render_table tables.2 %} +
+
+
+
+
+ + +

Partner Group Users

+ {% render_table tables.0 %}

Partner Group user list only contains those group users who have created an account on this website.

{% endblock after_panel %} diff --git a/gregor_django/templates/gregor_anvil/researchcenter_detail.html b/gregor_django/templates/gregor_anvil/researchcenter_detail.html index ac71eaeb..b53fed26 100644 --- a/gregor_django/templates/gregor_anvil/researchcenter_detail.html +++ b/gregor_django/templates/gregor_anvil/researchcenter_detail.html @@ -7,11 +7,69 @@ {% endblock panel %} {% block after_panel %} -

Research Center Users

- {% render_table site_user_table %} +
+
+
+

+ +

+
+
+

+ This table shows Accounts in the member group for this Research Center. +

+ {% render_table tables.1 %} +
+
+
+
+
+ +
+
+
+

+ +

+
+
+

+ This table shows Accounts in the uploader group for this Research Center. +

+ {% render_table tables.2 %} +
+
+
+
+
+ +

Research Center Users

+ {% render_table tables.0 %}

Research center user list only contains those site users who have created an account on this website.

{% endblock after_panel %} diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 6ca6c046..0f4bd421 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -50,7 +50,7 @@ django==4.2.13 # django-debug-toolbar # django-stubs # django-stubs-ext -django-debug-toolbar==4.4.2 +django-debug-toolbar==4.4.5 # via -r requirements/dev-requirements.in django-stubs==4.2.7 # via -r requirements/dev-requirements.in @@ -95,7 +95,7 @@ matplotlib-inline==0.1.6 # via ipython mccabe==0.7.0 # via pylint -mypy==1.10.0 +mypy==1.10.1 # via -r requirements/dev-requirements.in mypy-extensions==1.0.0 # via mypy @@ -149,7 +149,7 @@ requests==2.32.3 # -c requirements/requirements.txt # -c requirements/test-requirements.txt # sphinx -ruff==0.4.10 +ruff==0.5.1 # via -r requirements/dev-requirements.in six==1.16.0 # via