Skip to content

Commit

Permalink
Merge branch 'main' into feature/permission-rename
Browse files Browse the repository at this point in the history
  • Loading branch information
amstilp authored Nov 7, 2023
2 parents 069048c + e475e2f commit 61a3be6
Show file tree
Hide file tree
Showing 15 changed files with 228 additions and 13 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ default_stages: [commit]

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml

- repo: https://github.com/psf/black
rev: 22.3.0
rev: 23.10.1
hooks:
- id: black

Expand All @@ -20,7 +20,7 @@ repos:
- id: isort

- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
rev: 6.1.0
hooks:
- id: flake8
args: ["--config=setup.cfg"]
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- `AnVILConsortiumManagerEditRequired` -> `AnVILConsortiumManagerStaffEditRequired`
- `AnVILConsortiumManagerViewRequired` -> `AnVILConsortiumManagerStaffViewRequired`
- `AnVILConsortiumManagerLimitedViewRequired` -> `AnVILConsortiumManagerViewRequired`
* Bugfix: Allow Workspace Data objects to have a second foreign key to `Workspace`.

## 0.19 (2023-10-27)

Expand Down
1 change: 0 additions & 1 deletion anvil_consortium_manager/anvil_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ def __init__(self):
This way, all instances should share the same authorized session.
"""
if AnVILAPIClient.auth_session is None:

credentials = service_account.Credentials.from_service_account_file(
settings.ANVIL_API_SERVICE_ACCOUNT_FILE
)
Expand Down
1 change: 0 additions & 1 deletion anvil_consortium_manager/audit/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def render_errors(self, record):


class NotInAppTable(tables.Table):

record = tables.columns.Column(orderable=False, empty_values=())


Expand Down
2 changes: 1 addition & 1 deletion anvil_consortium_manager/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def __init__(self, *args, **kwargs):
layout.Submit("submit", "Filter", css_class="btn btn-secondary mb-3"),
css_class="col-auto",
),
css_class="row align-items-center"
css_class="row align-items-center",
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@


class ErrorTableWithLink(audit.ErrorTable):

model_instance = tables.Column(
orderable=False,
linkify=lambda value, table: "https://{domain}{url}".format(
Expand All @@ -26,7 +25,6 @@ def __init__(self, *args, **kwargs):


class Command(BaseCommand):

help = """Management command to run an AnVIL audit."""

def add_arguments(self, parser):
Expand Down Expand Up @@ -91,7 +89,6 @@ def _run_audit(self, audit_results, **options):
)

def handle(self, *args, **options):

if options["models"]:
models_to_audit = options["models"]
else:
Expand Down
2 changes: 0 additions & 2 deletions anvil_consortium_manager/tests/api_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ class ErrorResponseFactory(MockAPIResponseFactory):


class GroupDetailsFactory(factory.DictFactory):

groupName = factory.Faker("word")
groupEmail = factory.LazyAttribute(lambda obj: "{}@firecloud.org".format(obj.groupName))
role = FuzzyChoice(["admin", "member"])
Expand Down Expand Up @@ -66,7 +65,6 @@ class Params:


class GetGroupMembershipResponseFactory(MockAPIResponseFactory):

response = factory.LazyAttribute(lambda o: [fake.email() for _ in range(o.n_emails)])

class Params:
Expand Down
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
)
1 change: 1 addition & 0 deletions anvil_consortium_manager/tests/test_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def test_render_user_without_get_absolute_url(self):

def test_render_user_with_get_absolute_url(self):
"""Table renders a link to the user profile when the user has a get_absolute_url method."""

# Dynamically set the get_absolute_url method. This is hacky...
def foo(self):
return "test_profile_{}".format(self.username)
Expand Down
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
1 change: 0 additions & 1 deletion example_site/app/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@


class CustomWorkspaceDataTable(tables.Table):

name = tables.columns.Column(linkify=True)

class Meta:
Expand Down

0 comments on commit 61a3be6

Please sign in to comment.