From 84b5186e853cd6599eed6950bb87bbffeb56b553 Mon Sep 17 00:00:00 2001 From: Federico Capoano Date: Thu, 23 Apr 2026 19:05:12 -0300 Subject: [PATCH 1/3] [fix] Added SocialAccount inline to UserAdmin when needed If SOCIALACCOUNT_ADMIN_NEEDED is True, admins should be able to manage the social account records of users. [backport 1.2] --- openwisp_users/admin.py | 44 ++++++++++++++++++++++-------- openwisp_users/tests/test_admin.py | 17 +++++++++++- tests/openwisp2/settings.py | 1 + 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/openwisp_users/admin.py b/openwisp_users/admin.py index b8e03c50..60e4d835 100644 --- a/openwisp_users/admin.py +++ b/openwisp_users/admin.py @@ -660,18 +660,38 @@ def get_user(self, obj): if admin.site.is_registered(EmailAddress): admin.site.unregister(EmailAddress) -if allauth_settings.SOCIALACCOUNT_ENABLED: - socialaccount_models = [ - ("socialaccount", "SocialToken"), - ("socialaccount", "SocialAccount"), - ] - # Allow managing secrets if OAuth/SAML is enabled - if not app_settings.SOCIALACCOUNT_ADMIN_NEEDED: - socialaccount_models.append(("socialaccount", "SocialApp")) - for model in socialaccount_models: - model_class = apps.get_model(*model) - if admin.site.is_registered(model_class): - admin.site.unregister(model_class) +_unregister_socialaccount_models = [ + ("socialaccount", "SocialToken"), + ("socialaccount", "SocialAccount"), +] +# allauth OAuth/SAML not enabled +if ( + allauth_settings.SOCIALACCOUNT_ENABLED + and not app_settings.SOCIALACCOUNT_ADMIN_NEEDED +): # pragma: no cover + _unregister_socialaccount_models.append(("socialaccount", "SocialApp")) +# allauth OAuth/SAML enabled +elif allauth_settings.SOCIALACCOUNT_ENABLED and app_settings.SOCIALACCOUNT_ADMIN_NEEDED: + from allauth.socialaccount.admin import SocialAccount + + class SocialAccountInline(admin.StackedInline): + model = SocialAccount + extra = 0 + readonly_fields = ("provider", "uid", "extra_data") + + def has_add_permission(self, request, obj): + return False + + def has_delete_permission(self, request, obj=None): + return False + + UserAdmin.inlines.append(SocialAccountInline) + +# Un-register cluttering socialaccount models +for model in _unregister_socialaccount_models: + model_class = apps.get_model(*model) + if admin.site.is_registered(model_class): + admin.site.unregister(model_class) if "rest_framework.authtoken" in settings.INSTALLED_APPS: # pragma: no cover TokenProxy = apps.get_model("authtoken", "TokenProxy") diff --git a/openwisp_users/tests/test_admin.py b/openwisp_users/tests/test_admin.py index 81f0b3f7..5fbe425d 100644 --- a/openwisp_users/tests/test_admin.py +++ b/openwisp_users/tests/test_admin.py @@ -73,7 +73,7 @@ def _get_user_edit_form_inline_params(self, user, organization): @property def add_user_inline_params(self): - return { + params = { "emailaddress_set-TOTAL_FORMS": 0, "emailaddress_set-INITIAL_FORMS": 0, "emailaddress_set-MIN_NUM_FORMS": 0, @@ -83,6 +83,19 @@ def add_user_inline_params(self): f"{self.app_label}_organizationuser-MIN_NUM_FORMS": 0, f"{self.app_label}_organizationuser-MAX_NUM_FORMS": 0, } + self._add_socialaccount_inline_params(params) + return params + + def _add_socialaccount_inline_params(self, params): + if app_settings.SOCIALACCOUNT_ADMIN_NEEDED: + params.update( + { + "socialaccount_set-TOTAL_FORMS": 0, + "socialaccount_set-INITIAL_FORMS": 0, + "socialaccount_set-MIN_NUM_FORMS": 0, + "socialaccount_set-MAX_NUM_FORMS": 0, + } + ) def test_admin_add_user_auto_email(self): admin = self._create_admin() @@ -1702,6 +1715,7 @@ class TestBasicUsersIntegration( app_label = "openwisp_users" is_integration_test = True + _add_socialaccount_inline_params = TestUsersAdmin._add_socialaccount_inline_params def _get_user_edit_form_inline_params(self, user, organization): params = { @@ -1740,6 +1754,7 @@ def _get_user_edit_form_inline_params(self, user, organization): f"{self.app_label}_organizationuser-0-user": str(user.pk), } ) + self._add_socialaccount_inline_params(params) return params def test_change_user(self): diff --git a/tests/openwisp2/settings.py b/tests/openwisp2/settings.py index a02f2d64..40a9f6ce 100644 --- a/tests/openwisp2/settings.py +++ b/tests/openwisp2/settings.py @@ -35,6 +35,7 @@ "allauth", "allauth.account", "allauth.socialaccount", + "allauth.socialaccount.providers.google", # for test coverage "openwisp_users", # openwisp2 admin theme # (must be loaded here) From 93347220e9523b29aec170a0db08720b02c185e7 Mon Sep 17 00:00:00 2001 From: Federico Capoano Date: Thu, 23 Apr 2026 19:48:55 -0300 Subject: [PATCH 2/3] [chores] Follow up with @coderabbitai --- openwisp_users/admin.py | 62 ++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/openwisp_users/admin.py b/openwisp_users/admin.py index 60e4d835..7d0cc727 100644 --- a/openwisp_users/admin.py +++ b/openwisp_users/admin.py @@ -660,38 +660,36 @@ def get_user(self, obj): if admin.site.is_registered(EmailAddress): admin.site.unregister(EmailAddress) -_unregister_socialaccount_models = [ - ("socialaccount", "SocialToken"), - ("socialaccount", "SocialAccount"), -] -# allauth OAuth/SAML not enabled -if ( - allauth_settings.SOCIALACCOUNT_ENABLED - and not app_settings.SOCIALACCOUNT_ADMIN_NEEDED -): # pragma: no cover - _unregister_socialaccount_models.append(("socialaccount", "SocialApp")) -# allauth OAuth/SAML enabled -elif allauth_settings.SOCIALACCOUNT_ENABLED and app_settings.SOCIALACCOUNT_ADMIN_NEEDED: - from allauth.socialaccount.admin import SocialAccount - - class SocialAccountInline(admin.StackedInline): - model = SocialAccount - extra = 0 - readonly_fields = ("provider", "uid", "extra_data") - - def has_add_permission(self, request, obj): - return False - - def has_delete_permission(self, request, obj=None): - return False - - UserAdmin.inlines.append(SocialAccountInline) - -# Un-register cluttering socialaccount models -for model in _unregister_socialaccount_models: - model_class = apps.get_model(*model) - if admin.site.is_registered(model_class): - admin.site.unregister(model_class) +if allauth_settings.SOCIALACCOUNT_ENABLED: + _unregister_socialaccount_models = [ + ("socialaccount", "SocialToken"), + ("socialaccount", "SocialAccount"), + ] + # allauth OAuth/SAML not enabled + if not app_settings.SOCIALACCOUNT_ADMIN_NEEDED: # pragma: no cover + _unregister_socialaccount_models.append(("socialaccount", "SocialApp")) + # allauth OAuth/SAML enabled + else: + from allauth.socialaccount.models import SocialAccount + + class SocialAccountInline(admin.StackedInline): + model = SocialAccount + extra = 0 + readonly_fields = ("provider", "uid", "extra_data") + + def has_add_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + + UserAdmin.inlines.append(SocialAccountInline) + + # Un-register cluttering socialaccount models + for model in _unregister_socialaccount_models: + model_class = apps.get_model(*model) + if admin.site.is_registered(model_class): + admin.site.unregister(model_class) if "rest_framework.authtoken" in settings.INSTALLED_APPS: # pragma: no cover TokenProxy = apps.get_model("authtoken", "TokenProxy") From 82d9e63eb1d593decf05046cbda83eefd7d1b41a Mon Sep 17 00:00:00 2001 From: Federico Capoano Date: Thu, 23 Apr 2026 20:05:27 -0300 Subject: [PATCH 3/3] [chores] Fixed social account URLs --- openwisp_users/accounts/urls.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/openwisp_users/accounts/urls.py b/openwisp_users/accounts/urls.py index eb415754..e7dd9d89 100644 --- a/openwisp_users/accounts/urls.py +++ b/openwisp_users/accounts/urls.py @@ -77,12 +77,11 @@ if app_settings.SOCIALACCOUNT_ENABLED: urlpatterns += [path("social/", include("allauth.socialaccount.urls"))] - -for provider in providers.registry.get_class_list(): - try: - prov_mod = import_module(provider.get_package() + ".urls") - except ImportError: - continue - prov_urlpatterns = getattr(prov_mod, "urlpatterns", None) - if prov_urlpatterns: - urlpatterns += prov_urlpatterns + for provider in providers.registry.get_class_list(): + try: + prov_mod = import_module(provider.get_package() + ".urls") + except ImportError: + continue + prov_urlpatterns = getattr(prov_mod, "urlpatterns", None) + if prov_urlpatterns: + urlpatterns += prov_urlpatterns