-
Notifications
You must be signed in to change notification settings - Fork 89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change in #121 breaks subsequent model updates in on_commit=True hooks (infinite recursion) #134
Comments
Can you try to update django-lifecycle to the latest version? Maybe #121 fixes it |
Here is a minimal testcase class CommitTrigger(LifecycleModel):
foo = models.CharField(max_length=10, blank=True, null=True)
foo_ack = models.BooleanField(default=False)
t = 0
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.t = 0
@hook('after_update', when='foo', was=None, has_changed=True, on_commit=True)
def set_foo_ack(self):
print(f"set_foo_ack {self.foo=}")
self.foo_ack = True
self.save() # <== this should not trigger the hook b/c foo is not None
self.t += 1 from django.test import TestCase, TransactionTestCase
from django.db import transaction
from tests.testapp.models import CommitTrigger
class ModelCommitTestCase(TransactionTestCase):
def test_trigger_on_commit_1(self):
instance = CommitTrigger.objects.create()
self.assertEqual(instance.foo, None)
self.assertEqual(instance.foo_ack, False)
with transaction.atomic():
o = CommitTrigger.objects.get(pk=instance.pk)
o.foo = 'bar'
o.save()
self.assertEqual(o.foo, 'bar')
self.assertEqual(o.foo_ack, True)
self.assertEqual(o.t, 1) which gives
|
@mjl For your use case, would it work if you change to class Entry(models.Model):
document = models.BinaryField(blank=True, null=True)
document_received_ts = models.DateTimeField(null=True)
@hook(BEFORE_UPDATE, when='document', was=None, has_changed=True, on_commit=True)
def has_received_document(self):
logger.info("%s received document", self)
self.document_received_ts = datetime.now()
# … somewhere else …
some_entry.document = b'whatevs'
some_entry.save() |
@mjl For your use case, would it work if you change to `BEFORE_SAVE` and remove the `save()`?
It would not, I'm afraid. Background info: I use it to model a state machine, and I need to have states persisted to the db before any action on them is triggered (which can change states, of course).
Modifying it to use BEFORE_SAVE would break things, like multiple state changes in a row (the state machine would never be notified of interim states and then complain about invalid transitions), or being sure that some side effect has been performed and comitted, even if something further along fails (something like "create account" -> "charge credit card" -> "send welcome email" -- you would not want to roll back to the first state and charge the card again if sending the welcome mail fails!).
For the moment I just pinned the version required to 1.0.0, so no big deal.
|
Anything I can do to help track this down? |
I need a better understanding on how |
So here is my use case, simplified:
This used to work fine, however since 1.0.1 it runs into a
RecursionError: maximum recursion depth exceeded while calling a Python object
, calling the above hook over and over again, obviously triggered by thesave()
. I'm not sure why the above hook is triggered again, since it's anAFTER_UPDATE(on_commit=True)
, ie. the change has been persisted to the database. It should pick up that document has not changed from None again! However, it obviously does not and recurses until the stack runs out.I can not just do a
save(skip_hooks=True)
because there might be other hooks that depend on this one…My uneducated guess is that django-lifecycle needs to sync its change tracking info before running
on_commit
hooks?The text was updated successfully, but these errors were encountered: