From fca2d47ef09da56eee551768f8f59cd0594c0c16 Mon Sep 17 00:00:00 2001 From: Jonas Carson Date: Fri, 6 Oct 2023 08:36:11 -0700 Subject: [PATCH 1/6] Update username and email on drupal login --- primed/users/adapters.py | 27 ++++++++++++++++--- primed/users/tests/test_adapters.py | 41 +++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/primed/users/adapters.py b/primed/users/adapters.py index ce34b704..b0f6d6a3 100644 --- a/primed/users/adapters.py +++ b/primed/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_study_sites(self, user, extra_data: Dict): @@ -116,6 +137,6 @@ 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_study_sites(user, extra_data) self.update_user_groups(user, extra_data) diff --git a/primed/users/tests/test_adapters.py b/primed/users/tests/test_adapters.py index c65c254f..c5c24a83 100644 --- a/primed/users/tests/test_adapters.py +++ b/primed/users/tests/test_adapters.py @@ -34,36 +34,61 @@ def test_drupal_social_login_adapter(self): User = get_user_model() user = User() old_name = "Old Name" - 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") + old_username = "test" + old_email = "test@example.com" + setattr(user, account_settings.USER_MODEL_USERNAME_FIELD, old_username) + setattr(user, "name", old_name) + setattr(user, account_settings.USER_MODEL_EMAIL_FIELD, old_email) 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) - user = User.objects.get(**{account_settings.USER_MODEL_USERNAME_FIELD: "test"}) + user = User.objects.get( + **{account_settings.USER_MODEL_USERNAME_FIELD: old_username} + ) 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_name(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_study_sites_add(self): adapter = SocialAccountAdapter() From 1bf94be4ab2f79e973018e6df5bb3da3a5070ec7 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Wed, 11 Oct 2023 11:08:05 -0700 Subject: [PATCH 2/6] Update text on study detail page --- primed/templates/primed_anvil/study_detail.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/primed/templates/primed_anvil/study_detail.html b/primed/templates/primed_anvil/study_detail.html index 7ac68c8f..313fcdf7 100644 --- a/primed/templates/primed_anvil/study_detail.html +++ b/primed/templates/primed_anvil/study_detail.html @@ -26,11 +26,7 @@

Workspaces on AnVIL

Workspaces that have been shared with the Consortium can be identified by a green check box in the "Shared with PRIMED?" column of the tables below.

- To access a workspace on AnVIL after it has been shared with the consortium, investigators must: -

    -
  1. link their AnVIL account in this app, and
  2. -
  3. have approval via the correct PRIMED data access mechanism for data in the workspace.
  4. -
+ To access a workspace on AnVIL after it has been shared with the consortium, investigators must have linked their AnVIL account in this app, and have approval via the correct PRIMED data access mechanism for data in the workspace. More information can be found on the Getting Started with Data page.

From d5f5a9e58f0e1bbe7e9fa2179f100e8cc6140ddb Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 27 Oct 2023 16:42:19 -0700 Subject: [PATCH 3/6] Update ACM to v0.19 in requirements file --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 0ddc10d5..ef96e72c 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -22,7 +22,7 @@ django-dbbackup==4.0.1 # https://github.com/jazzband/django-dbbackup django-extensions==3.2.1 # https://github.com/django-extensions/django-extensions # anvil_consortium_manager -git+https://github.com/UW-GAC/django-anvil-consortium-manager.git@v0.18 +git+https://github.com/UW-GAC/django-anvil-consortium-manager.git@v0.19 # Simple history - model history tracking django-simple-history==3.1.1 From 172fe71ee3dbe86a2abb75b7da65df9b0d9f60df Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 27 Oct 2023 16:43:39 -0700 Subject: [PATCH 4/6] Add crispy_bootstrap5 to installed apps This is required to use the built-in list filtering in templates. --- config/settings/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/config/settings/base.py b/config/settings/base.py index 045f3ac0..81499eeb 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -70,6 +70,7 @@ ] THIRD_PARTY_APPS = [ "crispy_forms", + "crispy_bootstrap5", "allauth", "allauth.account", "allauth.socialaccount", From 73e882a3ac9adfda8be33b4f45a2e1b3673de8e2 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 27 Oct 2023 16:46:14 -0700 Subject: [PATCH 5/6] Add default AccountListFilter to AccountAdapter Required by ACM v0.19 --- primed/primed_anvil/adapters.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/primed/primed_anvil/adapters.py b/primed/primed_anvil/adapters.py index c428b0c4..21912a6f 100644 --- a/primed/primed_anvil/adapters.py +++ b/primed/primed_anvil/adapters.py @@ -1,4 +1,5 @@ from anvil_consortium_manager.adapters.account import BaseAccountAdapter +from anvil_consortium_manager.filters import AccountListFilter from django.db.models import Q from .tables import AccountTable @@ -8,6 +9,7 @@ class AccountAdapter(BaseAccountAdapter): """Custom account adapter for PRIMED.""" list_table_class = AccountTable + list_filterset_class = AccountListFilter def get_autocomplete_queryset(self, queryset, q): """Filter to Accounts where the email or the associated user name matches the query `q`.""" From e57646af227aa1758af8b296a48119711217ba69 Mon Sep 17 00:00:00 2001 From: Adrienne Stilp Date: Fri, 27 Oct 2023 16:50:45 -0700 Subject: [PATCH 6/6] Allow users to filter by account user name Add a custom FilterSet for Accounts that allows filtering by the name of the user linked to an Account. --- primed/primed_anvil/adapters.py | 2 +- primed/primed_anvil/filters.py | 10 ++++++++++ primed/primed_anvil/tests/test_views.py | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 primed/primed_anvil/filters.py diff --git a/primed/primed_anvil/adapters.py b/primed/primed_anvil/adapters.py index 21912a6f..8d22bdee 100644 --- a/primed/primed_anvil/adapters.py +++ b/primed/primed_anvil/adapters.py @@ -1,7 +1,7 @@ from anvil_consortium_manager.adapters.account import BaseAccountAdapter -from anvil_consortium_manager.filters import AccountListFilter from django.db.models import Q +from .filters import AccountListFilter from .tables import AccountTable diff --git a/primed/primed_anvil/filters.py b/primed/primed_anvil/filters.py new file mode 100644 index 00000000..6100777d --- /dev/null +++ b/primed/primed_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/primed/primed_anvil/tests/test_views.py b/primed/primed_anvil/tests/test_views.py index b0180ef9..42066720 100644 --- a/primed/primed_anvil/tests/test_views.py +++ b/primed/primed_anvil/tests/test_views.py @@ -968,6 +968,21 @@ def test_view_with_two_objects(self): self.assertIn("table", response.context_data) self.assertEqual(len(response.context_data["table"].rows), 2) + def test_filter_by_name(self): + """Filtering by name works as expected.""" + user = UserFactory.create(name="First Last") + account = AccountFactory.create(user=user) + other_account = 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 AvailableDataTest(TestCase): """Tests for the StudyDetail view."""