diff --git a/openwisp_users/apps.py b/openwisp_users/apps.py index 918786c7..8072e8cc 100644 --- a/openwisp_users/apps.py +++ b/openwisp_users/apps.py @@ -98,11 +98,18 @@ def set_default_settings(self): def connect_receivers(self): OrganizationUser = load_model('openwisp_users', 'OrganizationUser') OrganizationOwner = load_model('openwisp_users', 'OrganizationOwner') + Organization = load_model('openwisp_users', 'Organization') signal_tuples = [ (post_save, 'post_save'), (post_delete, 'post_delete'), ] + pre_save.connect( + self.handle_org_is_active_change, + sender=Organization, + dispatch_uid='handle_org_is_active_change', + ) + for model in [OrganizationUser, OrganizationOwner]: for signal, name in signal_tuples: signal.connect( @@ -130,6 +137,21 @@ def connect_receivers(self): dispatch_uid='make_first_org_user_org_owner', ) + @classmethod + def handle_org_is_active_change(cls, instance, **kwargs): + if instance._state.adding: + # If it's a new organization, we don't need to update any cache + return + Organization = instance._meta.model + try: + old_instance = Organization.objects.only('is_active').get(pk=instance.pk) + except Organization.DoesNotExist: + return + from .tasks import invalidate_org_membership_cache + + if instance.is_active != old_instance.is_active: + invalidate_org_membership_cache.delay(instance.pk) + @classmethod def pre_save_update_organizations_dict(cls, instance, **kwargs): """ diff --git a/openwisp_users/tasks.py b/openwisp_users/tasks.py index e8d792d3..7a732902 100644 --- a/openwisp_users/tasks.py +++ b/openwisp_users/tasks.py @@ -12,10 +12,12 @@ from django.utils.timezone import now, timedelta from django.utils.translation import gettext_lazy as _ from openwisp_utils.admin_theme.email import send_email +from swapper import load_model from . import settings as app_settings User = get_user_model() +OrganizationUser = load_model('openwisp_users', 'OrganizationUser') @shared_task @@ -83,3 +85,17 @@ def password_expiration_email(): sleep(random.randint(1, 2)) else: email_counts += 1 + + +@shared_task +def invalidate_org_membership_cache(organization_pk): + """ + Invalidates organization membership cache of all users of an + organization when organization.is_active changes + (organization is disabled or enabled again). + """ + qs = OrganizationUser.objects.filter( + organization_id=organization_pk + ).select_related('user') + for org_user in qs.iterator(): + org_user.user._invalidate_user_organizations_dict() diff --git a/openwisp_users/tests/test_admin.py b/openwisp_users/tests/test_admin.py index 2e026ff5..b1f31ab4 100644 --- a/openwisp_users/tests/test_admin.py +++ b/openwisp_users/tests/test_admin.py @@ -1395,6 +1395,7 @@ def test_can_change_inline_org_owner(self): params = { 'name': org.name, 'slug': org.slug, + 'is_active': 'on', 'owner-TOTAL_FORMS': '1', 'owner-INITIAL_FORMS': '1', 'owner-MIN_NUM_FORMS': '0', diff --git a/openwisp_users/tests/test_api/test_api.py b/openwisp_users/tests/test_api/test_api.py index 20520242..a87c4e38 100644 --- a/openwisp_users/tests/test_api/test_api.py +++ b/openwisp_users/tests/test_api/test_api.py @@ -86,7 +86,7 @@ def test_organization_put_api(self): 'email': 'testorg@test.com', 'url': '', } - with self.assertNumQueries(6): + with self.assertNumQueries(8): r = self.client.put(path, data, content_type='application/json') self.assertEqual(r.status_code, 200) self.assertEqual(r.data['name'], 'test org change') @@ -99,7 +99,7 @@ def test_organization_patch_api(self): data = { 'name': 'test org change', } - with self.assertNumQueries(5): + with self.assertNumQueries(6): r = self.client.patch(path, data, content_type='application/json') self.assertEqual(r.status_code, 200) self.assertEqual(r.data['name'], 'test org change') @@ -110,7 +110,7 @@ def test_create_organization_owner_api(self): org1_user1 = self._create_org_user(user=user1, organization=org1) path = reverse('users:organization_detail', args=(org1.pk,)) data = {'owner': {'organization_user': org1_user1.pk}} - with self.assertNumQueries(17): + with self.assertNumQueries(18): r = self.client.patch(path, data, content_type='application/json') self.assertEqual(r.status_code, 200) self.assertEqual(r.data['owner']['organization_user'], org1_user1.pk) @@ -122,7 +122,7 @@ def test_remove_organization_owner_api(self): self._create_org_owner(organization_user=org1_user1, organization=org1) path = reverse('users:organization_detail', args=(org1.pk,)) data = {'owner': {'organization_user': ''}} - with self.assertNumQueries(11): + with self.assertNumQueries(12): r = self.client.patch(path, data, content_type='application/json') self.assertEqual(r.status_code, 200) self.assertEqual(r.data['owner'], None) @@ -166,7 +166,7 @@ def test_change_organizationowner_for_org(self): self.assertEqual(org1.owner.organization_user.id, org1_user1.id) path = reverse('users:organization_detail', args=(org1.pk,)) data = {'owner': {'organization_user': org1_user2.id}} - with self.assertNumQueries(26): + with self.assertNumQueries(27): r = self.client.patch(path, data, content_type='application/json') org1.refresh_from_db() self.assertEqual(org1.owner.organization_user.id, org1_user2.id) diff --git a/openwisp_users/tests/test_models.py b/openwisp_users/tests/test_models.py index 75dbef1f..05f34b4c 100644 --- a/openwisp_users/tests/test_models.py +++ b/openwisp_users/tests/test_models.py @@ -185,6 +185,16 @@ def test_invalidate_cache_org_user_user_changed(self): self.assertEqual(user1.is_member(org), False) self.assertEqual(user2.is_member(org), True) + def test_invalidate_cache_org_status_changed(self): + org = self._create_org(name='testorg1') + user1 = self._create_user(username='testuser1', email='user1@test.com') + self._create_org_user(user=user1, organization=org) + self.assertEqual(user1.is_member(org), True) + org.is_active = False + org.full_clean() + org.save() + self.assertEqual(user1.is_member(org), False) + def test_organizations_managed(self): user = self._create_user(username='organizations_pk') self.assertEqual(user.organizations_managed, [])