diff --git a/django_lifecycle/mixins.py b/django_lifecycle/mixins.py index 9ca7dc4..7010455 100644 --- a/django_lifecycle/mixins.py +++ b/django_lifecycle/mixins.py @@ -3,7 +3,7 @@ from typing import Any, List from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist -from django.db import transaction +from django.db import transaction, router from django.utils.functional import cached_property from . import NotSet @@ -45,7 +45,10 @@ def run(self, instance: Any) -> None: # to ensure it's available to execute later. _on_commit_func = partial(self.method, instance) _on_commit_func.__name__ = self.name - transaction.on_commit(_on_commit_func) + transaction.on_commit( + _on_commit_func, + using=router.db_for_write(instance.__class__, instance=instance) + ) def instantiate_hooked_method(method: Any, callback_specs: HookConfig) -> AbstractHookedMethod: diff --git a/tests/db_router.py b/tests/db_router.py new file mode 100644 index 0000000..02b7f51 --- /dev/null +++ b/tests/db_router.py @@ -0,0 +1,19 @@ +class DBRouter: + + def db_for_write(self, model, **hints): + if model._meta.db_table == "post": + return "other" + return None + + def db_for_read(self, model, **hints): + if model._meta.db_table == "post": + return "other" + return None + + def allow_migrate(self, db, app_label, model_name=None, **hints): + return ( + db == "default" and model_name != "post" + ) or ( + db == "other" and model_name == "post" + ) + \ No newline at end of file diff --git a/tests/settings.py b/tests/settings.py index f8f6801..ccc7729 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -85,9 +85,16 @@ "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(BASE_DIR, "db.sqlite3"), - } + }, + "other": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "other.sqlite3"), + }, } +DATABASE_ROUTERS = [ + "tests.db_router.DBRouter" +] # Password validation # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators diff --git a/tests/testapp/migrations/0004_post.py b/tests/testapp/migrations/0004_post.py new file mode 100644 index 0000000..1e30888 --- /dev/null +++ b/tests/testapp/migrations/0004_post.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.8 on 2023-01-30 15:33 + +from django.db import migrations, models +import django_lifecycle.mixins +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('testapp', '0003_useraccount_name_changes'), + ] + + operations = [ + migrations.CreateModel( + name='Post', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('content', models.TextField(null=True)), + ], + options={ + 'db_table': 'post', + }, + bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model), + ), + ] diff --git a/tests/testapp/models.py b/tests/testapp/models.py index 18e5b75..dc86320 100644 --- a/tests/testapp/models.py +++ b/tests/testapp/models.py @@ -62,12 +62,12 @@ def do_after_create_jobs(self): def timestamp_password_change(self): self.password_updated_at = timezone.now() - @hook('before_update', when='first_name', has_changed=True) - @hook('before_update', when='last_name', has_changed=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) + @hook("before_delete", when="has_trial", was="*", is_now=True) def ensure_trial_not_active(self): raise CannotDeleteActiveTrial("Cannot delete trial user!") @@ -148,3 +148,18 @@ def timestamp_created_at(self): @hook("after_create") def answer_to_the_ultimate_question_of_life(self): self.answer = 42 + + +# Model stored in other database +class Post(LifecycleModel): + id = models.UUIDField(primary_key=True, default=uuid.uuid4) + content = models.TextField(null=True) + + @hook("after_create", on_commit=True) + def send_notification(self): + mail.send_mail( + "New Post!", "Click link below to have your latest post", "from@example.com", ["to@example.com"] + ) + + class Meta: + db_table = "post" diff --git a/tests/testapp/tests/test_post.py b/tests/testapp/tests/test_post.py new file mode 100644 index 0000000..1e23324 --- /dev/null +++ b/tests/testapp/tests/test_post.py @@ -0,0 +1,23 @@ +from django.core import mail +from django.test import TestCase + +from django_capture_on_commit_callbacks import capture_on_commit_callbacks + +from tests.testapp.models import Post + +class PostTestCase(TestCase): + databases = ("default", "other",) + + @property + def stub_data(self): + return { + "content": "plain text" + } + + def test_send_notification_mail_after_create(self): + with capture_on_commit_callbacks(execute=True, using="other") as callbacks: + Post.objects.create(**self.stub_data) + + self.assertEquals(len(callbacks), 1, msg=f"{callbacks}") + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].subject, "New Post!") \ No newline at end of file