Install & Compatibility
Where this runs
tested against v1.3.0 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslpy 3.10–3.910 runs
installs and imports cleanly · install 0.0s · import 0.000s · 66.4MB
glibcpy 3.10–3.910 runs
installs and imports cleanly · install 3.5s · import 0.000s · 67MB
66MB installed
● package 66MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
LifecycleModelMixin
✓ from django_lifecycle import LifecycleModelMixin
✗ from django_lifecycle import LifecycleModelMixin
This quickstart demonstrates how to define a Django model with `LifecycleModelMixin` and use the `@hook` decorator for various events. It includes examples for capitalizing a name `BEFORE_SAVE`, logging a price change `AFTER_UPDATE` when the `price` field `has_changed`, and sending a low stock notification `BEFORE_SAVE` when `stock` falls within a specific range using `is_greater_than` and `is_less_than` conditions, and generating a SKU on `POST_INIT`.
import os
from django.db import models
from django_lifecycle import LifecycleModelMixin, hook, BEFORE_SAVE, AFTER_UPDATE, POST_INIT
from django_lifecycle.conditions import is_greater_than, is_less_than
# NOTE: This example assumes Django settings are configured, e.g., via manage.py shell
# or a test environment.
class Product(LifecycleModelMixin, models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
stock = models.IntegerField(default=0)
sku = models.CharField(max_length=100, unique=True, blank=True, null=True)
@hook(POST_INIT)
def on_init(self):
if not self.sku:
self.sku = f"SKU-{os.urandom(4).hex().upper()}"
@hook(BEFORE_SAVE)
def ensure_name_capitalized(self):
self.name = self.name.capitalize()
@hook(AFTER_UPDATE, when='price', has_changed=True)
def log_price_change(self):
print(f"Product '{self.name}' (SKU: {self.sku}) price changed from {self.initial_value('price')} to {self.price}")
@hook(BEFORE_SAVE, when='stock', is_greater_than=0, is_less_than=10)
def notify_low_stock(self):
print(f"WARNING: Stock for '{self.name}' (SKU: {self.sku}) is critically low ({self.stock})!")
def __str__(self):
return self.name
# Example Usage (run in a Django shell or similar):
# from your_app.models import Product # Replace 'your_app'
#
# p1 = Product.objects.create(name="keyboard", price=75.00, stock=20)
# print(f"Created: {p1.name} with SKU: {p1.sku}") # SKU will be auto-generated on POST_INIT
#
# p1.price = 80.50
# p1.save() # Triggers log_price_change
#
# p1.stock = 5
# p1.save() # Triggers notify_low_stock
#
# p2 = Product(name="mouse", price=25.00, stock=15)
# p2.save() # Triggers ensure_name_capitalized and on_init (for sku)
# print(f"Created: {p2.name} with SKU: {p2.sku}")
Debug
Known issues
breaking`django-lifecycle` versions 1.2.7 and above have removed support for Django versions prior to 4.2. Upgrading `django-lifecycle` to 1.2.7+ in projects running older Django versions will lead to `ImportError` or other runtime issues.fixUpgrade your Django project to Django 4.2 or newer, or pin `django-lifecycle` to a version below 1.2.7 (e.g., `django-lifecycle<1.2.7`) to maintain compatibility.
affects: >=1.2.7
gotchaThe `LifecycleModelMixin` must be the first base class in your model's inheritance list (e.g., `class MyModel(LifecycleModelMixin, models.Model):`). Placing `models.Model` before `LifecycleModelMixin` will prevent hooks from firing correctly or lead to unexpected behavior.fixAlways ensure `LifecycleModelMixin` is the very first parent class listed in your Django model definition: `class MyModel(LifecycleModelMixin, models.Model):`.
affects: All versions
gotchaWhen using `when` conditions with `has_changed` or `changed_to` for fields storing mutable data (e.g., `JSONField`, `ArrayField` with mutable elements), versions prior to 1.2.0 might not always correctly detect changes due to shallow copy behavior. This could lead to hooks not firing as expected.fixEnsure you are using `django-lifecycle` version 1.2.0 or newer for reliable detection of changes in mutable fields. For very complex or custom mutable fields, you may still need to implement specific comparison logic within your hook.
affects: <1.2.0
gotchaCustom hook conditions, such as the `condition` decorator or predefined conditions like `is_greater_than`, must be imported from `django_lifecycle.conditions`. Importing them directly from `django_lifecycle` will result in an `ImportError`.fixUpdate your imports to use `from django_lifecycle.conditions import condition, is_greater_than` (or other specific conditions).
affects: All versions (fixed package structure in 1.2.2 for distribution issues, but import path has been consistent)
Upgrade
Version history
1.3.0latest on PyPI · released Jun 14, 2026
Audit
Dependencies
djangorequireddjango-lifecycle is an extension for Django and requires it to function.