From 97327211cd0591e148665c1a63fae792ca29b6d3 Mon Sep 17 00:00:00 2001 From: Gary Donovan Date: Thu, 14 Jun 2018 16:38:24 +1000 Subject: [PATCH] Don't call a lifecycle hook twice with the same state. This allows users to listen for multiple specific change events and handle them all in a single function. --- .gitignore | 4 +++- django_lifecycle/mixins.py | 22 ++++++++++--------- .../0003_useraccount_name_changes.py | 18 +++++++++++++++ tests/testapp/models.py | 8 ++++++- tests/testapp/tests/test_mixin.py | 1 + tests/testapp/tests/test_user_account.py | 7 ++++++ 6 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 tests/testapp/migrations/0003_useraccount_name_changes.py diff --git a/.gitignore b/.gitignore index b2d40e4..8344f7c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ __pycache__/ README.txt .tox django_lifecycle.egg-info -site/ \ No newline at end of file +site/ +.idea/ +venv/ \ No newline at end of file diff --git a/django_lifecycle/mixins.py b/django_lifecycle/mixins.py index c6d97b5..0839555 100644 --- a/django_lifecycle/mixins.py +++ b/django_lifecycle/mixins.py @@ -189,17 +189,19 @@ def _run_hooked_methods(self, hook: str) -> List[str]: when_any_field = callback_specs.get("when_any") if when_field: - if self._check_callback_conditions(when_field, callback_specs): - fired.append(method.__name__) - method() + if not self._check_callback_conditions(when_field, callback_specs): + continue elif when_any_field: - for field_name in when_any_field: - if self._check_callback_conditions(field_name, callback_specs): - fired.append(method.__name__) - method() - else: - fired.append(method.__name__) - method() + if not any([ + self._check_callback_conditions(field_name, callback_specs) + for field_name in when_any_field + ]): + continue + + # Only call the method once per hook + fired.append(method.__name__) + method() + break return fired diff --git a/tests/testapp/migrations/0003_useraccount_name_changes.py b/tests/testapp/migrations/0003_useraccount_name_changes.py new file mode 100644 index 0000000..e3d711a --- /dev/null +++ b/tests/testapp/migrations/0003_useraccount_name_changes.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.8 on 2020-02-03 10:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('testapp', '0002_auto_20190928_2324'), + ] + + operations = [ + migrations.AddField( + model_name='useraccount', + name='name_changes', + field=models.IntegerField(default=0), + ), + ] diff --git a/tests/testapp/models.py b/tests/testapp/models.py index 9d5a451..9b98fe2 100644 --- a/tests/testapp/models.py +++ b/tests/testapp/models.py @@ -32,6 +32,7 @@ class UserAccount(LifecycleModel): joined_at = models.DateTimeField(null=True) has_trial = models.BooleanField(default=False) organization = models.ForeignKey(Organization, null=True, on_delete=models.SET_NULL) + name_changes = models.IntegerField(default=0) status = models.CharField( default="active", @@ -61,7 +62,12 @@ def do_after_create_jobs(self): def timestamp_password_change(self): self.password_updated_at = timezone.now() - @hook("before_delete", when="has_trial", was="*", is_now=True) + @hook('before_update', when='first_name', has_changed=True) + @hook('before_update', when='last_name', has_changed=True) + def count_name_changes(self): + self.name_changes += 1 + + @hook("before_delete", when='has_trial', was='*', is_now=True) def ensure_trial_not_active(self): raise CannotDeleteActiveTrial("Cannot delete trial user!") diff --git a/tests/testapp/tests/test_mixin.py b/tests/testapp/tests/test_mixin.py index 9527728..7d89ac8 100644 --- a/tests/testapp/tests/test_mixin.py +++ b/tests/testapp/tests/test_mixin.py @@ -42,6 +42,7 @@ def test_snapshot_state(self): "organization_id": org.id, "status": "active", "organization.name": "Dunder Mifflin", + "name_changes": 0, }, ) diff --git a/tests/testapp/tests/test_user_account.py b/tests/testapp/tests/test_user_account.py index c8b6afa..20d9b51 100644 --- a/tests/testapp/tests/test_user_account.py +++ b/tests/testapp/tests/test_user_account.py @@ -56,6 +56,13 @@ def test_email_after_delete(self): self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].subject, "We have deleted your account") + def test_only_call_hook_once(self): + account = UserAccount.objects.create(**self.stub_data) + account.first_name = "Waylon" + account.last_name = "Smithers" + account.save() + self.assertEqual(account.name_changes, 1) + def test_lowercase_email(self): data = self.stub_data data["email"] = "Homer.Simpson@SpringfieldNuclear.com"