This project provides a
@hook decorator 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, 3.8 and 3.9, Django 2.0.x, 2.1.x, 2.2.x, 3.0.x and 3.1.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
delete()method override. Thanks @oaosman84!
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
/tests folder. Install the project requirements and do
./manage.py test to run them.