Django Lifecycle Hooks

Package version Python versions

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 unnesseary indirection and are at odds with Django's "fat models" approach.

In short, you can write model code like this:

from django_lifecycle import LifecycleModel, hook


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 save and __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!")

Requirements

  • Python (3.3+)
  • Django (1.8+)

Installation

pip install django-lifecycle

Getting Started

Either extend the provided abstract base model class:

from django_lifecycle import LifecycleModel, hook


class YourModel(LifecycleModel):
    name = models.CharField(max_length=50)

Or add the mixin to your Django model definition:

from django.db import models
from django_lifecycle import LifecycleModelMixin, hook


class YourModel(LifecycleModelMixin, models.Model):
    name = models.CharField(max_length=50)

If you are using Django 1.8 or below and want to extend the base model, you also have to add django_lifecycle to INSTALLED_APPS_.

Read on to see more examples of how to use lifecycle hooks.