From a975bc3bc85ed2ee344040cc4497003edfbd7722 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Jun 2024 19:35:08 +0000 Subject: [PATCH 01/12] Bump mypy from 1.10.0 to 1.10.1 Bumps [mypy](https://github.com/python/mypy) from 1.10.0 to 1.10.1. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.10.0...v1.10.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 6ca6c046..e5627b1c 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -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 From 9fe5b7a4b4ca464024779bcfc589b283a9c8ac18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Jun 2024 19:38:16 +0000 Subject: [PATCH 02/12] Bump ruff from 0.4.10 to 0.5.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.10 to 0.5.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.4.10...0.5.0) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements/dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index 6ca6c046..644d28c0 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -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.0 # via -r requirements/dev-requirements.in six==1.16.0 # via From 0a9f57b227dd8c0911f32024c5aaad8a9d31e556 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:25:27 +0000 Subject: [PATCH 03/12] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.9 → v0.5.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.9...v0.5.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf731237..9b07a0ee 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.0 hooks: # Run the linter. - id: ruff From c54673958dd1c5c6c344600e276ea559d4f1d055 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Mon, 1 Jul 2024 10:17:46 -0700 Subject: [PATCH 04/12] Add ubuntu-22.04 defaults to CI --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 4d52aad73952ca00977ed46939935874e2ddb1b0 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 3 Jul 2024 15:30:31 -0700 Subject: [PATCH 05/12] Add fields for RC member and uploader groups --- ...alresearchcenter_members_group_and_more.py | 35 +++++++++++ gregor_django/gregor_anvil/models.py | 24 +++++++- gregor_django/gregor_anvil/tests/factories.py | 10 ++- .../gregor_anvil/tests/test_models.py | 61 ++++++++++++++++++- 4 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_members_group_and_more.py diff --git a/gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_members_group_and_more.py b/gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_members_group_and_more.py new file mode 100644 index 00000000..87fe2e8a --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_members_group_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.13 on 2024-07-03 22:30 + +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='historicalresearchcenter', + name='members_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='uploaders_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='researchcenter', + name='members_group', + field=models.OneToOneField(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='uploaders_group', + field=models.OneToOneField(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..f43d029d 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,22 @@ class ResearchCenter(TimeStampedModel, models.Model): full_name = models.CharField(max_length=255, unique=True) """The full name of the Research Center.""" + members_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", + null=True, + ) + + uploaders_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", + null=True, + ) + history = HistoricalRecords() def __str__(self): @@ -56,6 +72,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.members_group and self.uploaders_group and self.members_group == self.uploaders_group: + raise ValidationError("members_group and uploaders_group must be different!") + class PartnerGroup(TimeStampedModel, models.Model): """A model to track Partner Groups""" diff --git a/gregor_django/gregor_anvil/tests/factories.py b/gregor_django/gregor_anvil/tests/factories.py index 5b77b97b..240422aa 100644 --- a/gregor_django/gregor_anvil/tests/factories.py +++ b/gregor_django/gregor_anvil/tests/factories.py @@ -1,6 +1,6 @@ from datetime import timedelta -from anvil_consortium_manager.tests.factories import WorkspaceFactory +from anvil_consortium_manager.tests.factories import ManagedGroupFactory, WorkspaceFactory from factory import Faker, LazyAttribute, Sequence, SubFactory from factory.django import DjangoModelFactory @@ -24,6 +24,14 @@ class ResearchCenterFactory(DjangoModelFactory): short_name = Faker("word") full_name = Faker("company") + members_group = SubFactory( + ManagedGroupFactory, + name=LazyAttribute(lambda o: "{}_members".format(o.factory_parent.short_name)), + ) + uploaders_group = SubFactory( + ManagedGroupFactory, + name=LazyAttribute(lambda o: "{}_uploaders".format(o.factory_parent.short_name)), + ) class Meta: model = models.ResearchCenter diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index f2be747c..00ab11b4 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,14 @@ 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") + members_group = ManagedGroupFactory.create() + uploaders_group = ManagedGroupFactory.create() + instance = models.ResearchCenter( + full_name="Test name", + short_name="TEST", + members_group=members_group, + uploaders_group=uploaders_group, + ) instance.save() self.assertIsInstance(instance, models.ResearchCenter) @@ -103,6 +110,56 @@ def test_unique_short_name(self): with self.assertRaises(IntegrityError): instance2.save() + def test_members_group_uploaders_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", + members_group=group, + uploaders_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_members_group(self): + """Cannot have the same member group for two RCs.""" + rc = factories.ResearchCenterFactory.create() + uploaders_group = ManagedGroupFactory.create() + instance = factories.ResearchCenterFactory.build( + full_name="Test name", + short_name="TEST", + members_group=rc.members_group, + uploaders_group=uploaders_group, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn("members_group", e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict["members_group"]), 1) + self.assertIn("already exists", str(e.exception.error_dict["members_group"][0])) + + def test_error_two_rcs_same_uploaders_group(self): + """Cannot have the same uploader group for two RCs.""" + rc = factories.ResearchCenterFactory.create() + members_group = ManagedGroupFactory.create() + instance = factories.ResearchCenterFactory.build( + full_name="Test name", + short_name="TEST", + members_group=members_group, + uploaders_group=rc.uploaders_group, + ) + with self.assertRaises(ValidationError) as e: + instance.full_clean() + self.assertEqual(len(e.exception.error_dict), 1) + self.assertIn("uploaders_group", e.exception.error_dict) + self.assertEqual(len(e.exception.error_dict["uploaders_group"]), 1) + self.assertIn("already exists", str(e.exception.error_dict["uploaders_group"][0])) + class UploadCycleTest(TestCase): """Tests for the UploadCycle model.""" From 3ca4fa182494b257a0da974805bf5da3c1fd35d1 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 3 Jul 2024 15:45:59 -0700 Subject: [PATCH 06/12] Rename fields to member_group and uploader_group --- ...alresearchcenter_member_group_and_more.py} | 10 ++--- gregor_django/gregor_anvil/models.py | 8 ++-- gregor_django/gregor_anvil/tests/factories.py | 4 +- .../gregor_anvil/tests/test_models.py | 42 +++++++++---------- 4 files changed, 32 insertions(+), 32 deletions(-) rename gregor_django/gregor_anvil/migrations/{0024_historicalresearchcenter_members_group_and_more.py => 0024_historicalresearchcenter_member_group_and_more.py} (90%) diff --git a/gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_members_group_and_more.py b/gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_member_group_and_more.py similarity index 90% rename from gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_members_group_and_more.py rename to gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_member_group_and_more.py index 87fe2e8a..4ee806ed 100644 --- a/gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_members_group_and_more.py +++ b/gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_member_group_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.13 on 2024-07-03 22:30 +# Generated by Django 4.2.13 on 2024-07-03 22:44 from django.db import migrations, models import django.db.models.deletion @@ -14,22 +14,22 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='historicalresearchcenter', - name='members_group', + 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='uploaders_group', + 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='researchcenter', - name='members_group', + name='member_group', field=models.OneToOneField(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='uploaders_group', + name='uploader_group', field=models.OneToOneField(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 f43d029d..04f12e62 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -42,7 +42,7 @@ class ResearchCenter(TimeStampedModel, models.Model): full_name = models.CharField(max_length=255, unique=True) """The full name of the Research Center.""" - members_group = models.OneToOneField( + member_group = models.OneToOneField( ManagedGroup, on_delete=models.PROTECT, help_text="The AnVIL group containing members from this Research Center.", @@ -50,7 +50,7 @@ class ResearchCenter(TimeStampedModel, models.Model): null=True, ) - uploaders_group = models.OneToOneField( + 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.", @@ -75,8 +75,8 @@ def get_absolute_url(self): def clean(self): """Custom cleaning methods.""" # Members group and uploaders group must be different. - if self.members_group and self.uploaders_group and self.members_group == self.uploaders_group: - raise ValidationError("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): diff --git a/gregor_django/gregor_anvil/tests/factories.py b/gregor_django/gregor_anvil/tests/factories.py index 240422aa..4da9315c 100644 --- a/gregor_django/gregor_anvil/tests/factories.py +++ b/gregor_django/gregor_anvil/tests/factories.py @@ -24,11 +24,11 @@ class ResearchCenterFactory(DjangoModelFactory): short_name = Faker("word") full_name = Faker("company") - members_group = SubFactory( + member_group = SubFactory( ManagedGroupFactory, name=LazyAttribute(lambda o: "{}_members".format(o.factory_parent.short_name)), ) - uploaders_group = SubFactory( + uploader_group = SubFactory( ManagedGroupFactory, name=LazyAttribute(lambda o: "{}_uploaders".format(o.factory_parent.short_name)), ) diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index 00ab11b4..88c00fa4 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -78,13 +78,13 @@ class ResearchCenterTest(TestCase): def test_model_saving(self): """Creation using the model constructor and .save() works.""" - members_group = ManagedGroupFactory.create() - uploaders_group = ManagedGroupFactory.create() + member_group = ManagedGroupFactory.create() + uploader_group = ManagedGroupFactory.create() instance = models.ResearchCenter( full_name="Test name", short_name="TEST", - members_group=members_group, - uploaders_group=uploaders_group, + member_group=member_group, + uploader_group=uploader_group, ) instance.save() self.assertIsInstance(instance, models.ResearchCenter) @@ -110,14 +110,14 @@ def test_unique_short_name(self): with self.assertRaises(IntegrityError): instance2.save() - def test_members_group_uploaders_group_must_be_different(self): + 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", - members_group=group, - uploaders_group=group, + member_group=group, + uploader_group=group, ) with self.assertRaises(ValidationError) as e: instance.full_clean() @@ -126,39 +126,39 @@ def test_members_group_uploaders_group_must_be_different(self): 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_members_group(self): + def test_error_two_rcs_same_member_group(self): """Cannot have the same member group for two RCs.""" rc = factories.ResearchCenterFactory.create() - uploaders_group = ManagedGroupFactory.create() + uploader_group = ManagedGroupFactory.create() instance = factories.ResearchCenterFactory.build( full_name="Test name", short_name="TEST", - members_group=rc.members_group, - uploaders_group=uploaders_group, + member_group=rc.member_group, + uploader_group=uploader_group, ) with self.assertRaises(ValidationError) as e: instance.full_clean() self.assertEqual(len(e.exception.error_dict), 1) - self.assertIn("members_group", e.exception.error_dict) - self.assertEqual(len(e.exception.error_dict["members_group"]), 1) - self.assertIn("already exists", str(e.exception.error_dict["members_group"][0])) + 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_uploaders_group(self): + def test_error_two_rcs_same_uploader_group(self): """Cannot have the same uploader group for two RCs.""" rc = factories.ResearchCenterFactory.create() - members_group = ManagedGroupFactory.create() + member_group = ManagedGroupFactory.create() instance = factories.ResearchCenterFactory.build( full_name="Test name", short_name="TEST", - members_group=members_group, - uploaders_group=rc.uploaders_group, + member_group=member_group, + uploader_group=rc.uploader_group, ) with self.assertRaises(ValidationError) as e: instance.full_clean() self.assertEqual(len(e.exception.error_dict), 1) - self.assertIn("uploaders_group", e.exception.error_dict) - self.assertEqual(len(e.exception.error_dict["uploaders_group"]), 1) - self.assertIn("already exists", str(e.exception.error_dict["uploaders_group"][0])) + 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): From 3dad8041b3f240da79a44f2894438506062b2b2c Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 3 Jul 2024 15:56:18 -0700 Subject: [PATCH 07/12] Fix migration tests for new ResearchCenter factory After updating the Research Center factory, migration tests using that factory broke because it was trying to create/save the members_group and uploaders_group fields. Update the tests to create the RCs directly instead of through factories. --- gregor_django/gregor_anvil/tests/test_migrations.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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, ) From 6a911b300f73babd1b7ba882ba4e88699390aa00 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 3 Jul 2024 16:03:45 -0700 Subject: [PATCH 08/12] Show the member and uploader groups on the RC detail page Also show tables of the Accounts that are in those groups. --- .../gregor_anvil/tests/test_views.py | 53 ++++++++++++++++++- gregor_django/gregor_anvil/views.py | 17 ++++-- .../gregor_anvil/researchcenter_detail.html | 50 ++++++++++++++++- 3 files changed, 112 insertions(+), 8 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 794716dd..8f0016ca 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,61 @@ 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.""" + obj = self.model_factory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + self.assertContains(response, obj.member_group.get_absolute_url()) + + def test_link_to_uploader_group(self): + """Response includes a link to the uploader group.""" + obj = self.model_factory.create() + self.client.force_login(self.user) + response = self.client.get(self.get_url(obj.pk)) + self.assertContains(response, obj.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.assertIsInstance(response.context_data["tables"][1], tables.AccountTable) + self.assertIsInstance(response.context_data["tables"][2], tables.AccountTable) + + def test_rc_member_table(self): + obj = self.model_factory.create() + account = acm_factories.AccountFactory.create(verified=True) + acm_factories.GroupAccountMembershipFactory.create(account=account, group=obj.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_rc_uploader_table(self): + obj = self.model_factory.create() + account = acm_factories.AccountFactory.create(verified=True) + acm_factories.GroupAccountMembershipFactory.create(account=account, group=obj.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) + class ResearchCenterListTest(TestCase): """Tests for the ResearchCenterList view.""" diff --git a/gregor_django/gregor_anvil/views.py b/gregor_django/gregor_anvil/views.py index cde7fed7..fb6dcc61 100644 --- a/gregor_django/gregor_anvil/views.py +++ b/gregor_django/gregor_anvil/views.py @@ -29,14 +29,23 @@ 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): + members = Account.objects.filter( + groupaccountmembership__group=self.object.member_group, + ) + 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): diff --git a/gregor_django/templates/gregor_anvil/researchcenter_detail.html b/gregor_django/templates/gregor_anvil/researchcenter_detail.html index ac71eaeb..ddc8878d 100644 --- a/gregor_django/templates/gregor_anvil/researchcenter_detail.html +++ b/gregor_django/templates/gregor_anvil/researchcenter_detail.html @@ -7,11 +7,57 @@ {% 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 %} From b1df1b8bcbf64deccc743085c90f6ca2b4354865 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 9 Jul 2024 10:17:35 -0700 Subject: [PATCH 09/12] Add member_group and uploader_group to the PartnerGroup model --- ...ricalpartnergroup_member_group_and_more.py | 35 ++++++++ gregor_django/gregor_anvil/models.py | 22 +++++ gregor_django/gregor_anvil/tests/factories.py | 8 ++ .../gregor_anvil/tests/test_models.py | 88 +++++++++++++++++++ 4 files changed, 153 insertions(+) create mode 100644 gregor_django/gregor_anvil/migrations/0025_historicalpartnergroup_member_group_and_more.py diff --git a/gregor_django/gregor_anvil/migrations/0025_historicalpartnergroup_member_group_and_more.py b/gregor_django/gregor_anvil/migrations/0025_historicalpartnergroup_member_group_and_more.py new file mode 100644 index 00000000..037d0639 --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0025_historicalpartnergroup_member_group_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.13 on 2024-07-09 17:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('anvil_consortium_manager', '0019_accountuserarchive'), + ('gregor_anvil', '0024_historicalresearchcenter_member_group_and_more'), + ] + + 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='partnergroup', + name='member_group', + field=models.OneToOneField(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(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'), + ), + ] diff --git a/gregor_django/gregor_anvil/models.py b/gregor_django/gregor_anvil/models.py index 04f12e62..87173f9f 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -88,6 +88,22 @@ 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, + ) + + 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, + ) + history = HistoricalRecords() def __str__(self): @@ -102,6 +118,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/factories.py b/gregor_django/gregor_anvil/tests/factories.py index 4da9315c..19487844 100644 --- a/gregor_django/gregor_anvil/tests/factories.py +++ b/gregor_django/gregor_anvil/tests/factories.py @@ -58,6 +58,14 @@ class PartnerGroupFactory(DjangoModelFactory): short_name = Faker("word") full_name = Faker("company") + member_group = SubFactory( + ManagedGroupFactory, + name=LazyAttribute(lambda o: "{}_members".format(o.factory_parent.short_name)), + ) + uploader_group = SubFactory( + ManagedGroupFactory, + name=LazyAttribute(lambda o: "{}_uploaders".format(o.factory_parent.short_name)), + ) class Meta: model = models.PartnerGroup diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index 88c00fa4..a8b14934 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -479,6 +479,94 @@ 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.""" + 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.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.""" + rc = factories.PartnerGroupFactory.create() + uploader_group = ManagedGroupFactory.create() + instance = factories.PartnerGroupFactory.build( + full_name="Test name", + short_name="TEST", + member_group=rc.member_group, + uploader_group=uploader_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.""" + rc = factories.PartnerGroupFactory.create() + member_group = ManagedGroupFactory.create() + instance = factories.PartnerGroupFactory.build( + full_name="Test name", + short_name="TEST", + member_group=member_group, + uploader_group=rc.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.""" From c8433cbcb174c98100ff381d94bdfa0a3b677122 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 9 Jul 2024 10:30:19 -0700 Subject: [PATCH 10/12] Show partner group member and uploader groups on detail page --- .../gregor_anvil/tests/test_views.py | 39 +++++++++++++- gregor_django/gregor_anvil/views.py | 19 +++++-- .../gregor_anvil/partnergroup_detail.html | 51 ++++++++++++++++++- 3 files changed, 100 insertions(+), 9 deletions(-) diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 8f0016ca..d3f56989 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -474,13 +474,48 @@ 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): + obj = self.model_factory.create() + account = acm_factories.AccountFactory.create(verified=True) + acm_factories.GroupAccountMembershipFactory.create(account=account, group=obj.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_uploader_table(self): + obj = self.model_factory.create() + account = acm_factories.AccountFactory.create(verified=True) + acm_factories.GroupAccountMembershipFactory.create(account=account, group=obj.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) + 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 fb6dcc61..b422a4b7 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 @@ -55,14 +55,23 @@ 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): + members = Account.objects.filter( + groupaccountmembership__group=self.object.member_group, + ) + 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..f72ec282 100644 --- a/gregor_django/templates/gregor_anvil/partnergroup_detail.html +++ b/gregor_django/templates/gregor_anvil/partnergroup_detail.html @@ -7,11 +7,58 @@ {% 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 %} From 8c835d9d6d2b7c466bbf578de1bee368f5dbe58d Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 9 Jul 2024 14:52:22 -0700 Subject: [PATCH 11/12] Allow member_group and upload_group to be blank --- ...lter_partnergroup_member_group_and_more.py | 35 +++++++++++++ gregor_django/gregor_anvil/models.py | 4 ++ gregor_django/gregor_anvil/tests/factories.py | 18 +------ .../gregor_anvil/tests/test_models.py | 50 +++++++++++++------ .../gregor_anvil/tests/test_views.py | 37 ++++++++------ .../gregor_anvil/partnergroup_detail.html | 16 +++++- .../gregor_anvil/researchcenter_detail.html | 16 +++++- 7 files changed, 125 insertions(+), 51 deletions(-) create mode 100644 gregor_django/gregor_anvil/migrations/0026_alter_partnergroup_member_group_and_more.py diff --git a/gregor_django/gregor_anvil/migrations/0026_alter_partnergroup_member_group_and_more.py b/gregor_django/gregor_anvil/migrations/0026_alter_partnergroup_member_group_and_more.py new file mode 100644 index 00000000..95adbfd5 --- /dev/null +++ b/gregor_django/gregor_anvil/migrations/0026_alter_partnergroup_member_group_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.13 on 2024-07-09 18:13 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('anvil_consortium_manager', '0019_accountuserarchive'), + ('gregor_anvil', '0025_historicalpartnergroup_member_group_and_more'), + ] + + operations = [ + migrations.AlterField( + 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.AlterField( + 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.AlterField( + 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.AlterField( + 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 87173f9f..8a3813df 100644 --- a/gregor_django/gregor_anvil/models.py +++ b/gregor_django/gregor_anvil/models.py @@ -47,6 +47,7 @@ class ResearchCenter(TimeStampedModel, models.Model): 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, ) @@ -55,6 +56,7 @@ class ResearchCenter(TimeStampedModel, models.Model): 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, ) @@ -94,6 +96,7 @@ class PartnerGroup(TimeStampedModel, models.Model): 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( @@ -102,6 +105,7 @@ class PartnerGroup(TimeStampedModel, models.Model): 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() diff --git a/gregor_django/gregor_anvil/tests/factories.py b/gregor_django/gregor_anvil/tests/factories.py index 19487844..5b77b97b 100644 --- a/gregor_django/gregor_anvil/tests/factories.py +++ b/gregor_django/gregor_anvil/tests/factories.py @@ -1,6 +1,6 @@ from datetime import timedelta -from anvil_consortium_manager.tests.factories import ManagedGroupFactory, WorkspaceFactory +from anvil_consortium_manager.tests.factories import WorkspaceFactory from factory import Faker, LazyAttribute, Sequence, SubFactory from factory.django import DjangoModelFactory @@ -24,14 +24,6 @@ class ResearchCenterFactory(DjangoModelFactory): short_name = Faker("word") full_name = Faker("company") - member_group = SubFactory( - ManagedGroupFactory, - name=LazyAttribute(lambda o: "{}_members".format(o.factory_parent.short_name)), - ) - uploader_group = SubFactory( - ManagedGroupFactory, - name=LazyAttribute(lambda o: "{}_uploaders".format(o.factory_parent.short_name)), - ) class Meta: model = models.ResearchCenter @@ -58,14 +50,6 @@ class PartnerGroupFactory(DjangoModelFactory): short_name = Faker("word") full_name = Faker("company") - member_group = SubFactory( - ManagedGroupFactory, - name=LazyAttribute(lambda o: "{}_members".format(o.factory_parent.short_name)), - ) - uploader_group = SubFactory( - ManagedGroupFactory, - name=LazyAttribute(lambda o: "{}_uploaders".format(o.factory_parent.short_name)), - ) class Meta: model = models.PartnerGroup diff --git a/gregor_django/gregor_anvil/tests/test_models.py b/gregor_django/gregor_anvil/tests/test_models.py index a8b14934..27f9833f 100644 --- a/gregor_django/gregor_anvil/tests/test_models.py +++ b/gregor_django/gregor_anvil/tests/test_models.py @@ -77,6 +77,16 @@ class ResearchCenterTest(TestCase): """Tests for the ResearchCenter model.""" def test_model_saving(self): + """Creation using the model constructor and .save() works.""" + 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() @@ -86,6 +96,7 @@ def test_model_saving(self): member_group=member_group, uploader_group=uploader_group, ) + instance.full_clean() instance.save() self.assertIsInstance(instance, models.ResearchCenter) @@ -128,13 +139,12 @@ def test_member_group_uploader_group_must_be_different(self): def test_error_two_rcs_same_member_group(self): """Cannot have the same member group for two RCs.""" - rc = factories.ResearchCenterFactory.create() - uploader_group = ManagedGroupFactory.create() + member_group = ManagedGroupFactory.create() + factories.ResearchCenterFactory.create(member_group=member_group) instance = factories.ResearchCenterFactory.build( full_name="Test name", short_name="TEST", - member_group=rc.member_group, - uploader_group=uploader_group, + member_group=member_group, ) with self.assertRaises(ValidationError) as e: instance.full_clean() @@ -145,13 +155,12 @@ def test_error_two_rcs_same_member_group(self): def test_error_two_rcs_same_uploader_group(self): """Cannot have the same uploader group for two RCs.""" - rc = factories.ResearchCenterFactory.create() - member_group = ManagedGroupFactory.create() + uploader_group = ManagedGroupFactory.create() + factories.ResearchCenterFactory.create(uploader_group=uploader_group) instance = factories.ResearchCenterFactory.build( full_name="Test name", short_name="TEST", - member_group=member_group, - uploader_group=rc.uploader_group, + uploader_group=uploader_group, ) with self.assertRaises(ValidationError) as e: instance.full_clean() @@ -483,6 +492,16 @@ 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() @@ -492,6 +511,7 @@ def test_model_saving(self): member_group=member_group, uploader_group=uploader_group, ) + instance.full_clean() instance.save() self.assertIsInstance(instance, models.PartnerGroup) @@ -534,13 +554,12 @@ def test_member_group_uploader_group_must_be_different(self): def test_error_two_groups_same_member_group(self): """Cannot have the same member group for two RCs.""" - rc = factories.PartnerGroupFactory.create() - uploader_group = ManagedGroupFactory.create() + member_group = ManagedGroupFactory.create() + factories.PartnerGroupFactory.create(member_group=member_group) instance = factories.PartnerGroupFactory.build( full_name="Test name", short_name="TEST", - member_group=rc.member_group, - uploader_group=uploader_group, + member_group=member_group, ) with self.assertRaises(ValidationError) as e: instance.full_clean() @@ -551,13 +570,12 @@ def test_error_two_groups_same_member_group(self): def test_error_two_groups_same_uploader_group(self): """Cannot have the same uploader group for two RCs.""" - rc = factories.PartnerGroupFactory.create() - member_group = ManagedGroupFactory.create() + uploader_group = ManagedGroupFactory.create() + factories.PartnerGroupFactory.create(uploader_group=uploader_group) instance = factories.PartnerGroupFactory.build( full_name="Test name", short_name="TEST", - member_group=member_group, - uploader_group=rc.uploader_group, + uploader_group=uploader_group, ) with self.assertRaises(ValidationError) as e: instance.full_clean() diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index d3f56989..22f32c35 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -291,18 +291,20 @@ def test_site_user_table(self): self.assertNotIn(non_site_user, table.data) def test_link_to_member_group(self): - """Response includes a link to the members group.""" - obj = self.model_factory.create() + """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, obj.member_group.get_absolute_url()) + self.assertContains(response, member_group.get_absolute_url()) def test_link_to_uploader_group(self): - """Response includes a link to the uploader group.""" - obj = self.model_factory.create() + """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, obj.uploader_group.get_absolute_url()) + self.assertContains(response, uploader_group.get_absolute_url()) def test_table_classes(self): """Table classes are correct.""" @@ -312,13 +314,17 @@ def test_table_classes(self): 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): - obj = self.model_factory.create() + 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=obj.member_group) + 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)) @@ -328,9 +334,10 @@ def test_rc_member_table(self): self.assertNotIn(other_account, table.data) def test_rc_uploader_table(self): - obj = self.model_factory.create() + 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=obj.uploader_group) + 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)) @@ -493,9 +500,10 @@ def test_table_classes(self): self.assertIsInstance(response.context_data["tables"][2], tables.AccountTable) def test_member_table(self): - obj = self.model_factory.create() + 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=obj.member_group) + 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)) @@ -505,9 +513,10 @@ def test_member_table(self): self.assertNotIn(other_account, table.data) def test_uploader_table(self): - obj = self.model_factory.create() + 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=obj.uploader_group) + 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)) diff --git a/gregor_django/templates/gregor_anvil/partnergroup_detail.html b/gregor_django/templates/gregor_anvil/partnergroup_detail.html index f72ec282..b1c3ebea 100644 --- a/gregor_django/templates/gregor_anvil/partnergroup_detail.html +++ b/gregor_django/templates/gregor_anvil/partnergroup_detail.html @@ -7,8 +7,20 @@ {% endblock panel %} diff --git a/gregor_django/templates/gregor_anvil/researchcenter_detail.html b/gregor_django/templates/gregor_anvil/researchcenter_detail.html index ddc8878d..b53fed26 100644 --- a/gregor_django/templates/gregor_anvil/researchcenter_detail.html +++ b/gregor_django/templates/gregor_anvil/researchcenter_detail.html @@ -7,8 +7,20 @@ {% endblock panel %} From 9adee4356433495aa19d94219eebbfc648c0134c Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Tue, 9 Jul 2024 15:07:05 -0700 Subject: [PATCH 12/12] Redo migrations before merge Collapse all migrations into one before merging. --- ...calresearchcenter_member_group_and_more.py | 35 ------------ .../migrations/0024_member_upload_groups.py | 55 +++++++++++++++++++ ...ricalpartnergroup_member_group_and_more.py | 35 ------------ ...lter_partnergroup_member_group_and_more.py | 35 ------------ 4 files changed, 55 insertions(+), 105 deletions(-) delete mode 100644 gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_member_group_and_more.py create mode 100644 gregor_django/gregor_anvil/migrations/0024_member_upload_groups.py delete mode 100644 gregor_django/gregor_anvil/migrations/0025_historicalpartnergroup_member_group_and_more.py delete mode 100644 gregor_django/gregor_anvil/migrations/0026_alter_partnergroup_member_group_and_more.py diff --git a/gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_member_group_and_more.py b/gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_member_group_and_more.py deleted file mode 100644 index 4ee806ed..00000000 --- a/gregor_django/gregor_anvil/migrations/0024_historicalresearchcenter_member_group_and_more.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 4.2.13 on 2024-07-03 22:44 - -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='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='researchcenter', - name='member_group', - field=models.OneToOneField(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(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/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/migrations/0025_historicalpartnergroup_member_group_and_more.py b/gregor_django/gregor_anvil/migrations/0025_historicalpartnergroup_member_group_and_more.py deleted file mode 100644 index 037d0639..00000000 --- a/gregor_django/gregor_anvil/migrations/0025_historicalpartnergroup_member_group_and_more.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 4.2.13 on 2024-07-09 17:12 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('anvil_consortium_manager', '0019_accountuserarchive'), - ('gregor_anvil', '0024_historicalresearchcenter_member_group_and_more'), - ] - - 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='partnergroup', - name='member_group', - field=models.OneToOneField(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(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'), - ), - ] diff --git a/gregor_django/gregor_anvil/migrations/0026_alter_partnergroup_member_group_and_more.py b/gregor_django/gregor_anvil/migrations/0026_alter_partnergroup_member_group_and_more.py deleted file mode 100644 index 95adbfd5..00000000 --- a/gregor_django/gregor_anvil/migrations/0026_alter_partnergroup_member_group_and_more.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 4.2.13 on 2024-07-09 18:13 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('anvil_consortium_manager', '0019_accountuserarchive'), - ('gregor_anvil', '0025_historicalpartnergroup_member_group_and_more'), - ] - - operations = [ - migrations.AlterField( - 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.AlterField( - 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.AlterField( - 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.AlterField( - 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'), - ), - ]