diff --git a/.gitignore b/.gitignore index e9f9715d..18860960 100644 --- a/.gitignore +++ b/.gitignore @@ -279,9 +279,7 @@ gregor_django/media/ ### test db gregor_django.db +*.db ### dbbackups dbbackups/ - - - diff --git a/config/settings/base.py b/config/settings/base.py index 0f4e6db1..b92cc0eb 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -72,6 +72,7 @@ ] THIRD_PARTY_APPS = [ "crispy_forms", + "crispy_bootstrap5", "allauth", "allauth.account", "allauth.socialaccount", diff --git a/gregor_django/gregor_anvil/adapters.py b/gregor_django/gregor_anvil/adapters.py index 62e47330..30af25e0 100644 --- a/gregor_django/gregor_anvil/adapters.py +++ b/gregor_django/gregor_anvil/adapters.py @@ -1,14 +1,16 @@ from anvil_consortium_manager.adapters.account import BaseAccountAdapter from anvil_consortium_manager.adapters.workspace import BaseWorkspaceAdapter +from anvil_consortium_manager.forms import WorkspaceForm from django.db.models import Q -from . import forms, models, tables +from . import filters, forms, models, tables class AccountAdapter(BaseAccountAdapter): """Custom account adapter for PRIMED.""" list_table_class = tables.AccountTable + list_filterset_class = filters.AccountListFilter def get_autocomplete_queryset(self, queryset, q): """Filter to Accounts where the email or the associated user name matches the query `q`.""" @@ -36,6 +38,7 @@ class UploadWorkspaceAdapter(BaseWorkspaceAdapter): list_table_class = tables.UploadWorkspaceTable workspace_data_model = models.UploadWorkspace workspace_data_form_class = forms.UploadWorkspaceForm + workspace_form_class = WorkspaceForm workspace_detail_template_name = "gregor_anvil/uploadworkspace_detail.html" def get_autocomplete_queryset(self, queryset, q, forwarded={}): @@ -66,6 +69,7 @@ class ExampleWorkspaceAdapter(BaseWorkspaceAdapter): workspace_data_model = models.ExampleWorkspace workspace_data_form_class = forms.ExampleWorkspaceForm workspace_detail_template_name = "anvil_consortium_manager/workspace_detail.html" + workspace_form_class = WorkspaceForm class TemplateWorkspaceAdapter(BaseWorkspaceAdapter): @@ -78,6 +82,7 @@ class TemplateWorkspaceAdapter(BaseWorkspaceAdapter): workspace_data_model = models.TemplateWorkspace workspace_data_form_class = forms.TemplateWorkspaceForm workspace_detail_template_name = "gregor_anvil/templateworkspace_detail.html" + workspace_form_class = WorkspaceForm class CombinedConsortiumDataWorkspaceAdapter(BaseWorkspaceAdapter): @@ -92,6 +97,7 @@ class CombinedConsortiumDataWorkspaceAdapter(BaseWorkspaceAdapter): workspace_detail_template_name = ( "gregor_anvil/combinedconsortiumdataworkspace_detail.html" ) + workspace_form_class = WorkspaceForm class ReleaseWorkspaceAdapter(BaseWorkspaceAdapter): @@ -104,6 +110,7 @@ class ReleaseWorkspaceAdapter(BaseWorkspaceAdapter): workspace_data_model = models.ReleaseWorkspace workspace_data_form_class = forms.ReleaseWorkspaceForm workspace_detail_template_name = "gregor_anvil/releaseworkspace_detail.html" + workspace_form_class = WorkspaceForm class DCCProcessingWorkspaceAdapter(BaseWorkspaceAdapter): @@ -116,6 +123,7 @@ class DCCProcessingWorkspaceAdapter(BaseWorkspaceAdapter): workspace_data_model = models.DCCProcessingWorkspace workspace_data_form_class = forms.DCCProcessingWorkspaceForm workspace_detail_template_name = "gregor_anvil/dccprocessingworkspace_detail.html" + workspace_form_class = WorkspaceForm class DCCProcessedDataWorkspaceAdapter(BaseWorkspaceAdapter): @@ -130,3 +138,4 @@ class DCCProcessedDataWorkspaceAdapter(BaseWorkspaceAdapter): workspace_detail_template_name = ( "gregor_anvil/dccprocesseddataworkspace_detail.html" ) + workspace_form_class = WorkspaceForm diff --git a/gregor_django/gregor_anvil/filters.py b/gregor_django/gregor_anvil/filters.py new file mode 100644 index 00000000..6100777d --- /dev/null +++ b/gregor_django/gregor_anvil/filters.py @@ -0,0 +1,10 @@ +from anvil_consortium_manager.forms import FilterForm +from anvil_consortium_manager.models import Account +from django_filters import FilterSet + + +class AccountListFilter(FilterSet): + class Meta: + model = Account + fields = {"email": ["icontains"], "user__name": ["icontains"]} + form = FilterForm diff --git a/gregor_django/gregor_anvil/tests/test_views.py b/gregor_django/gregor_anvil/tests/test_views.py index 0e7311da..bc93214d 100644 --- a/gregor_django/gregor_anvil/tests/test_views.py +++ b/gregor_django/gregor_anvil/tests/test_views.py @@ -948,6 +948,34 @@ def test_view_with_two_objects(self): self.assertEqual(len(response.context_data["table"].rows), 2) +class AccountListTest(TestCase): + def setUp(self): + """Set up test class.""" + self.factory = RequestFactory() + # Create a user with both view and edit permission. + self.user = User.objects.create_user(username="test", password="test") + self.user.user_permissions.add( + Permission.objects.get( + codename=acm_models.AnVILProjectManagerAccess.VIEW_PERMISSION_CODENAME + ) + ) + + def test_filter_by_name(self): + """Filtering by name works as expected.""" + user = UserFactory.create(name="First Last") + account = acm_factories.AccountFactory.create(user=user) + other_account = acm_factories.AccountFactory.create(verified=True) + self.client.force_login(self.user) + response = self.client.get( + reverse("anvil_consortium_manager:accounts:list"), + {"user__name__icontains": "First"}, + ) + self.assertIn("table", response.context_data) + self.assertEqual(len(response.context_data["table"].rows), 1) + self.assertIn(account, response.context_data["table"].data) + self.assertNotIn(other_account, response.context_data["table"].data) + + class UploadWorkspaceDetailTest(TestCase): """Tests of the anvil_consortium_manager WorkspaceDetail view using the UploadWorkspace adapter.""" diff --git a/gregor_django/users/adapters.py b/gregor_django/users/adapters.py index 22184279..01f5d210 100644 --- a/gregor_django/users/adapters.py +++ b/gregor_django/users/adapters.py @@ -23,15 +23,36 @@ class SocialAccountAdapter(DefaultSocialAccountAdapter): def is_open_for_signup(self, request: HttpRequest, sociallogin: Any): return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) - def update_user_name(self, user, extra_data: Dict): + def update_user_info(self, user, extra_data: Dict): + drupal_username = extra_data.get("preferred_username") + drupal_email = extra_data.get("email") first_name = extra_data.get("first_name") last_name = extra_data.get("last_name") full_name = " ".join(part for part in (first_name, last_name) if part) + user_changed = False if user.name != full_name: logger.info( - f"[SocialAccountAdatpter:update_user_name] user {user} name updated from {user.name} to {full_name}" + f"[SocialAccountAdatpter:update_user_name] user {user} " + f"name updated from {user.name} to {full_name}" ) user.name = full_name + user_changed = True + if user.username != drupal_username: + logger.info( + f"[SocialAccountAdatpter:update_user_name] user {user} " + f"username updated from {user.username} to {drupal_username}" + ) + user.username = drupal_username + user_changed = True + if user.email != drupal_email: + logger.info( + f"[SocialAccountAdatpter:update_user_name] user {user}" + f" email updated from {user.email} to {drupal_email}" + ) + user.email = drupal_email + user_changed = True + + if user_changed is True: user.save() def update_user_partner_groups(self, user, extra_data: Dict): @@ -172,7 +193,7 @@ def update_user_data(self, sociallogin: Any): extra_data = sociallogin.account.extra_data user = sociallogin.user - self.update_user_name(user, extra_data) + self.update_user_info(user, extra_data) self.update_user_research_centers(user, extra_data) self.update_user_partner_groups(user, extra_data) self.update_user_groups(user, extra_data) diff --git a/gregor_django/users/tests/test_adapters.py b/gregor_django/users/tests/test_adapters.py index 7305435e..d284a11a 100644 --- a/gregor_django/users/tests/test_adapters.py +++ b/gregor_django/users/tests/test_adapters.py @@ -36,6 +36,8 @@ def test_drupal_social_login_adapter(self): User = get_user_model() user = User() old_name = "Old Name" + old_username = "test" + old_email = "test@example.com" setattr(user, account_settings.USER_MODEL_USERNAME_FIELD, "test") setattr(user, "name", "Old Name") setattr(user, account_settings.USER_MODEL_EMAIL_FIELD, "test@example.com") @@ -43,7 +45,12 @@ def test_drupal_social_login_adapter(self): account = SocialAccount( provider="drupal_oauth_provider", uid="123", - extra_data=dict(first_name="Old", last_name="Name"), + extra_data=dict( + first_name="Old", + last_name="Name", + email=old_email, + preferred_username=old_username, + ), ) sociallogin = SocialLogin(user=user, account=account) complete_social_login(request, sociallogin) @@ -51,21 +58,37 @@ def test_drupal_social_login_adapter(self): user = User.objects.get(**{account_settings.USER_MODEL_USERNAME_FIELD: "test"}) assert SocialAccount.objects.filter(user=user, uid=account.uid).exists() is True assert user.name == old_name + assert user.username == old_username + assert user.email == old_email - def test_update_user_research_centers(self): + def test_update_user_info(self): adapter = SocialAccountAdapter() User = get_user_model() user = User() - new_name = "New Name" + new_first_name = "New" + new_last_name = "Name" + new_name = f"{new_first_name} {new_last_name}" + new_email = "newemail@example.com" + new_username = "newusername" setattr(user, account_settings.USER_MODEL_USERNAME_FIELD, "test") setattr(user, "name", "Old Name") setattr(user, account_settings.USER_MODEL_EMAIL_FIELD, "test@example.com") user.save() - adapter.update_user_name(user, dict(first_name="New", last_name="Name")) + adapter.update_user_info( + user, + dict( + first_name=new_first_name, + last_name=new_last_name, + email=new_email, + preferred_username=new_username, + ), + ) assert user.name == new_name + assert user.email == new_email + assert user.username == new_username def test_update_user_research_centers_add(self): adapter = SocialAccountAdapter() diff --git a/requirements/base.txt b/requirements/base.txt index 70f92ba9..c5d881c8 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -21,7 +21,7 @@ django-login-required-middleware==0.8.0 # https://github.com/CleitonDeLima/djang django-dbbackup==4.0.1 # https://github.com/jazzband/django-dbbackup django-extensions==3.2.1 # https://github.com/django-extensions/django-extensions -git+https://github.com/UW-GAC/django-anvil-consortium-manager.git@v0.17 +git+https://github.com/UW-GAC/django-anvil-consortium-manager.git@v0.19 # Simple history - model history tracking django-simple-history==3.1.1 # For tracking history