Skip to content

Commit

Permalink
Merge branch 'main' into feature/drupal-data-sync
Browse files Browse the repository at this point in the history
  • Loading branch information
amstilp authored Jul 9, 2024
2 parents b839fa0 + 5878203 commit da32f81
Show file tree
Hide file tree
Showing 11 changed files with 526 additions and 29 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 55 additions & 0 deletions gregor_django/gregor_anvil/migrations/0024_member_upload_groups.py
Original file line number Diff line number Diff line change
@@ -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'),
),
]
50 changes: 49 additions & 1 deletion gregor_django/gregor_anvil/models.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -45,6 +45,24 @@ class ResearchCenter(TimeStampedModel, models.Model):
drupal_node_id = models.IntegerField(blank=True, null=True)
"""Reference node ID for entity in drupal"""

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):
Expand All @@ -59,6 +77,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"""
Expand All @@ -72,6 +96,24 @@ class PartnerGroup(TimeStampedModel, models.Model):
drupal_node_id = models.IntegerField(blank=True, null=True)
"""Reference node ID for entity in drupal"""

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):
Expand All @@ -86,6 +128,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."""
Expand Down
9 changes: 6 additions & 3 deletions gregor_django/gregor_anvil/tests/test_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand All @@ -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,
)
Expand All @@ -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,
)
Expand Down
167 changes: 165 additions & 2 deletions gregor_django/gregor_anvil/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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."""
Expand Down Expand Up @@ -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."""

Expand Down
Loading

0 comments on commit da32f81

Please sign in to comment.