Skip to content

Commit

Permalink
Merge pull request #435 from UW-GAC/bugfix/434-workspace-data-second-fk
Browse files Browse the repository at this point in the history
Allow WorkspaceData objects to have a second fk to Workspace
  • Loading branch information
amstilp authored Nov 7, 2023
2 parents d16b298 + f56ea50 commit e475e2f
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* Switch to using `pyproject.toml` where possible.
* Use hatch for backend building.
* Bugfix: Allow Workspace Data objects to have a second foreign key to `Workspace`.

## 0.19 (2023-10-27)

Expand Down
2 changes: 1 addition & 1 deletion anvil_consortium_manager/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.20.dev2"
__version__ = "0.20.dev3"
15 changes: 15 additions & 0 deletions anvil_consortium_manager/tests/test_app/adapters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from anvil_consortium_manager.adapters.account import BaseAccountAdapter
from anvil_consortium_manager.adapters.workspace import BaseWorkspaceAdapter
from anvil_consortium_manager.forms import WorkspaceForm
from anvil_consortium_manager.tables import WorkspaceTable

from . import filters, forms, models, tables

Expand Down Expand Up @@ -39,3 +41,16 @@ def get_autocomplete_queryset(self, queryset, q):

def get_autocomplete_label(self, account):
return "TEST {}".format(account.email)


class TestForeignKeyWorkspaceAdapter(BaseWorkspaceAdapter):
"""Adapter for TestForeignKeyWorkspace."""

name = "Test foreign key workspace"
type = "test_fk"
description = "Workspace type for testing"
list_table_class = WorkspaceTable
workspace_form_class = WorkspaceForm
workspace_data_model = models.TestForeignKeyWorkspaceData
workspace_data_form_class = forms.TestForeignKeyWorkspaceDataForm
workspace_detail_template_name = "workspace_detail.html"
8 changes: 8 additions & 0 deletions anvil_consortium_manager/tests/test_app/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,11 @@ def clean_name(self):
if name and name == "test-fail":
raise ValidationError("Workspace name cannot be 'test-fail'!")
return name


class TestForeignKeyWorkspaceDataForm(forms.ModelForm):
"""Form for a TestForeignKeyWorkspace object."""

class Meta:
model = models.TestForeignKeyWorkspaceData
fields = ("other_workspace", "workspace")
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Generated by Django 4.2.7 on 2023-11-07 20:38

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import simple_history.models


class Migration(migrations.Migration):

dependencies = [
('anvil_consortium_manager', '0013_alter_anvilprojectmanageraccess_options'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('test_app', '0003_testworkspacedata_alter_study_name'),
]

operations = [
migrations.CreateModel(
name='TestForeignKeyWorkspaceData',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('other_workspace', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='test_foreign_key_workspaces', to='anvil_consortium_manager.workspace')),
('workspace', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='anvil_consortium_manager.workspace')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='HistoricalTestForeignKeyWorkspaceData',
fields=[
('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('other_workspace', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='anvil_consortium_manager.workspace')),
('workspace', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='anvil_consortium_manager.workspace')),
],
options={
'verbose_name': 'historical test foreign key workspace data',
'verbose_name_plural': 'historical test foreign key workspace datas',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
]
8 changes: 8 additions & 0 deletions anvil_consortium_manager/tests/test_app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ class ProtectedWorkspaceData(models.Model):
"""Model to test having a protected foreign key to DefaultWorkspaceData."""

workspace_data = models.ForeignKey(DefaultWorkspaceData, on_delete=models.PROTECT)


class TestForeignKeyWorkspaceData(BaseWorkspaceData):
"""Custom model with a second fk to Workspace."""

other_workspace = models.ForeignKey(
Workspace, related_name="test_foreign_key_workspaces", on_delete=models.PROTECT
)
141 changes: 140 additions & 1 deletion anvil_consortium_manager/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from .test_app import forms as app_forms
from .test_app import models as app_models
from .test_app import tables as app_tables
from .test_app.adapters import TestWorkspaceAdapter
from .test_app.adapters import TestForeignKeyWorkspaceAdapter, TestWorkspaceAdapter
from .test_app.factories import TestWorkspaceDataFactory
from .test_app.filters import TestAccountListFilter
from .utils import AnVILAPIMockTestMixin, TestCase # Redefined to work with Django < 4.2 and Django=4.2.
Expand Down Expand Up @@ -7642,6 +7642,64 @@ def test_adapter_does_not_create_object_if_workspace_form_invalid(self):
self.assertEqual(models.Workspace.objects.count(), 0)
self.assertEqual(len(responses.calls), 0)

def test_get_workspace_data_with_second_foreign_key_to_workspace(self):
# Overriding settings doesn't work, because appconfig.ready has already run and
# registered the default adapter. Instead, unregister the default and register the
# new adapter here.
workspace_adapter_registry.register(TestForeignKeyWorkspaceAdapter)
self.workspace_type = "test_fk"
self.client.force_login(self.user)
response = self.client.get(self.get_url(self.workspace_type))
self.assertEqual(response.status_code, 200)

def test_post_workspace_data_with_second_foreign_key_to_workspace(self):
"""Posting valid data to the form creates a workspace data object when using a custom adapter."""
# Overriding settings doesn't work, because appconfig.ready has already run and
# registered the default adapter. Instead, unregister the default and register the
# new adapter here.
workspace_adapter_registry.register(TestForeignKeyWorkspaceAdapter)
self.workspace_type = TestForeignKeyWorkspaceAdapter().get_type()
other_workspace = factories.WorkspaceFactory.create()
billing_project = factories.BillingProjectFactory.create(name="test-billing-project")
json_data = {
"namespace": "test-billing-project",
"name": "test-workspace",
"attributes": {},
}
self.anvil_response_mock.add(
responses.POST,
self.api_url,
status=self.api_success_code,
match=[responses.matchers.json_params_matcher(json_data)],
)
self.client.force_login(self.user)
response = self.client.post(
self.get_url(self.workspace_type),
{
"billing_project": billing_project.pk,
"name": "test-workspace",
# Default workspace data for formset.
"workspacedata-TOTAL_FORMS": 1,
"workspacedata-INITIAL_FORMS": 0,
"workspacedata-MIN_NUM_FORMS": 1,
"workspacedata-MAX_NUM_FORMS": 1,
"workspacedata-0-other_workspace": other_workspace.pk,
},
)
self.assertEqual(response.status_code, 302)
# The workspace is created.
new_workspace = models.Workspace.objects.latest("pk")
# workspace_type is set properly.
self.assertEqual(
new_workspace.workspace_type,
TestForeignKeyWorkspaceAdapter().get_type(),
)
# Workspace data is added.
self.assertEqual(app_models.TestForeignKeyWorkspaceData.objects.count(), 1)
new_workspace_data = app_models.TestForeignKeyWorkspaceData.objects.latest("pk")
self.assertEqual(new_workspace_data.workspace, new_workspace)
self.assertEqual(new_workspace_data.other_workspace, other_workspace)


class WorkspaceImportTest(AnVILAPIMockTestMixin, TestCase):
"""Tests for the WorkspaceImport view."""
Expand Down Expand Up @@ -9001,6 +9059,87 @@ def test_api_error_acl_call(self):
self.assertEqual(models.Workspace.objects.count(), 0)
self.assertEqual(models.WorkspaceGroupSharing.objects.count(), 0)

def test_get_workspace_data_with_second_foreign_key_to_workspace(self):
# Overriding settings doesn't work, because appconfig.ready has already run and
# registered the default adapter. Instead, unregister the default and register the
# new adapter here.
workspace_adapter_registry.register(TestForeignKeyWorkspaceAdapter)
self.workspace_type = TestForeignKeyWorkspaceAdapter().get_type()
billing_project = factories.BillingProjectFactory.create(name="billing-project")
workspace_name = "workspace"
# Available workspaces API call.
self.anvil_response_mock.add(
responses.GET,
self.workspace_list_url,
match=[
responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"})
],
status=200,
json=[self.get_api_json_response(billing_project.name, workspace_name)],
)
self.client.force_login(self.user)
response = self.client.get(self.get_url(self.workspace_type))
self.assertEqual(response.status_code, 200)

def test_post_workspace_data_with_second_foreign_key_to_workspace(self):
# Overriding settings doesn't work, because appconfig.ready has already run and
# registered the default adapter. Instead, unregister the default and register the
# new adapter here.
workspace_adapter_registry.register(TestForeignKeyWorkspaceAdapter)
self.workspace_type = TestForeignKeyWorkspaceAdapter().get_type()
billing_project = factories.BillingProjectFactory.create(name="billing-project")
workspace_name = "workspace"
other_workspace = factories.WorkspaceFactory.create()
# Available workspaces API call.
self.anvil_response_mock.add(
responses.GET,
self.workspace_list_url,
match=[
responses.matchers.query_param_matcher({"fields": "workspace.namespace,workspace.name,accessLevel"})
],
status=200,
json=[self.get_api_json_response(billing_project.name, workspace_name)],
)
# Response for ACL query.
self.anvil_response_mock.add(
responses.GET,
self.get_api_url_acl(billing_project.name, workspace_name),
status=200, # successful response code.
json=self.api_json_response_acl,
)
url = self.get_api_url(billing_project.name, workspace_name)
self.anvil_response_mock.add(
responses.GET,
url,
status=self.api_success_code,
json=self.get_api_json_response(billing_project.name, workspace_name),
)
self.client.force_login(self.user)
response = self.client.post(
self.get_url(self.workspace_type),
{
"workspace": billing_project.name + "/" + workspace_name,
# Default workspace data for formset.
"workspacedata-TOTAL_FORMS": 1,
"workspacedata-INITIAL_FORMS": 0,
"workspacedata-MIN_NUM_FORMS": 1,
"workspacedata-MAX_NUM_FORMS": 1,
"workspacedata-0-other_workspace": other_workspace.pk,
},
)
self.assertEqual(response.status_code, 302)
# The workspace is created.
new_workspace = models.Workspace.objects.latest("pk")
self.assertEqual(
new_workspace.workspace_type,
TestForeignKeyWorkspaceAdapter().get_type(),
)
# Workspace data is added.
self.assertEqual(app_models.TestForeignKeyWorkspaceData.objects.count(), 1)
new_workspace_data = app_models.TestForeignKeyWorkspaceData.objects.latest("pk")
self.assertEqual(new_workspace_data.workspace, new_workspace)
self.assertEqual(new_workspace_data.other_workspace, other_workspace)


class WorkspaceCloneTest(AnVILAPIMockTestMixin, TestCase):
"""Tests for the WorkspaceClone view."""
Expand Down
2 changes: 2 additions & 0 deletions anvil_consortium_manager/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,7 @@ def get_workspace_data_formset(self):
absolute_max=1,
max_num=1,
min_num=1,
fk_name="workspace",
)
if self.request.method in ("POST"):
formset = formset_factory(
Expand Down Expand Up @@ -1137,6 +1138,7 @@ def get_workspace_data_formset(self):
absolute_max=1,
max_num=1,
min_num=1,
fk_name="workspace",
)
if self.request.method in ("POST"):
formset = formset_factory(
Expand Down

0 comments on commit e475e2f

Please sign in to comment.