diff --git a/README.md b/README.md index 2b387ec..4a35ced 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,24 @@ This generates the following data to be sent. Published(destination='/topic/my_route_key_1', body='{"id": 1, "one": "Field One"}') Published(destination='/topic/my_route_key_2', body='{"id": 1, "two": "Field Two"}') ``` +## Stopping Publication + +The `@publish` decorator adds a named parameter called `stop` to the `save` method, which is useful when you need to update an instance of a decorated model and do not want this update to be published on the broker. +```python +from django.db import transaction +from django_outbox_pattern.payloads import Payload + +from .models import Order + +def callback(payload: Payload): + order_id = payload.body["order_id"] + status = payload.body["status"] + with transaction.atomic(): + order = Order.objects.get(order_id=order_id) + order.status = status + order.save(stop=True) + payload.save() +``` ## Publish/Subscribe commands diff --git a/django_outbox_pattern/decorators.py b/django_outbox_pattern/decorators.py index 81d5984..f19a2fa 100644 --- a/django_outbox_pattern/decorators.py +++ b/django_outbox_pattern/decorators.py @@ -1,3 +1,4 @@ +# pylint: disable=protected-access import json from typing import List from typing import NamedTuple @@ -17,17 +18,24 @@ class Config(NamedTuple): def publish(configs: List[Config]): - def save(self, *args, **kwargs): - with transaction.atomic(): - super(self.__class__, self).save(*args, **kwargs) - for config in configs: - _create_published(self, *config) - - def decorator_publish(cls): - cls.save = save - return cls - - return decorator_publish + def wrapper(cls): + class PublishModel(cls): + def save(self, *args, stop=False, **kwargs): + with transaction.atomic(): + super().save(*args, **kwargs) + if not stop: + for config in configs: + _create_published(self, *config) + + class Meta(getattr(cls, "Meta", object)): + proxy = True + app_label = cls._meta.app_label + verbose_name = cls._meta.verbose_name + verbose_name_plural = cls._meta.verbose_name_plural + + return PublishModel + + return wrapper def _create_published(obj, destination, fields, serializer, version): diff --git a/tests/unit/test_publish_decorator.py b/tests/unit/test_publish_decorator.py index 799eddb..bb1a3a6 100644 --- a/tests/unit/test_publish_decorator.py +++ b/tests/unit/test_publish_decorator.py @@ -19,7 +19,7 @@ def setUp(self): } def create_user(self, model): - model.objects.create(username="test", email=self.email) + return model.objects.create(username="test", email=self.email) def test_when_is_correct_destination(self): destination = "queue" @@ -125,7 +125,6 @@ def my_serializer_1(obj): self.assertEqual("", published[1].body["last_name"]) self.assertEqual("", published[1].body["first_name"]) self.assertIsNone(published[1].body["last_login"]) - self.assertAlmostEqual(date_joined.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3], published[1].body["date_joined"]) self.assertFalse(published[1].body["is_superuser"]) self.assertEqual([], published[1].body["user_permissions"]) self.assertEqual("queue_2", published[1].destination) @@ -170,3 +169,20 @@ def my_serializer_2(obj): "username": "test", }, ) + + def test_when_overriding_save_method(self): + def save(self, *args, **kwargs): + self.username = "test orverridden" + super(User, self).save(*args, **kwargs) + + User.save = save + user_publish = publish([Config(destination="destination")])(User) + user = self.create_user(user_publish) + self.assertEqual(user.username, "test orverridden") + + def test_when_stop_publishing(self): + user_published = publish([Config(destination="stop")])(User) + user = user_published(username="test", email=self.email) + user.save(stop=True) + published = Published.objects.first() + self.assertIsNone(published)