Declarative model lifecycle hooks, an alternative to Signals.
This project provides a
@hookdecorator as well as a base model and mixin to add lifecycle hooks to your Django models. Django's built-in approach to offering lifecycle hooks is Signals. However, my team often finds that Signals introduce unnecessary indirection and are at odds with Django's "fat models" approach.
Django Lifecycle Hooks supports Python 3.5, 3.6, 3.7 and 3.8, Django 2.0.x, 2.1.x, 2.2.x and 3.0.x.
In short, you can write model code like this:
from django_lifecycle import LifecycleModel, hook, BEFORE_UPDATE, AFTER_UPDATE
class Article(LifecycleModel): contents = models.TextField() updated_at = models.DateTimeField(null=True) status = models.ChoiceField(choices=['draft', 'published']) editor = models.ForeignKey(AuthUser)
@hook(BEFORE_UPDATE, when='contents', has_changed=True) def on_content_change(self): self.updated_at = timezone.now() @hook(AFTER_UPDATE, when="status", was="draft", is_now="published") def on_publish(self): send_email(self.editor.email, "An article has published!")
Instead of overriding
__init__in a clunky way that hurts readability:
# same class and field declarations as above ...
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._orig_contents = self.contents self._orig_status = self.status def save(self, *args, **kwargs): if self.pk is not None and self.contents != self._orig_contents: self.updated_at = timezone.now() super().save(*args, **kwargs) if self.status != self._orig_status: send_email(self.editor.email, "An article has published!")
Source Code: https://github.com/rsinger86/django-lifecycle
GenericForeignKey. Thanks @bmbouter!
utils._get_field_namesthat could cause recursion bug in some cases.
changes_tocondition - thanks @samitnuk! Also some typo fixes in docs.
when_anyhook parameter to watch multiple fields for state changes
initial_value(field_name)behavior - should return value even if no change. Thanks @adamJLev!
before_createcauses exception b/c PK does not exist yet. Thanks @garyd203!
@hookdecorators to same method.
skip_hooks, an optional boolean keyword argument that controls whether hooked methods are called.
_potentially_hooked_methodsthat caused unwanted side effects by accessing model instance methods decorated with
Tests are found in a simplified Django project in the
/testsfolder. Install the project requirements and do
./manage.py testto run them.