{"id":9676,"library":"django-lifecycle","title":"Django Lifecycle","description":"Django Lifecycle is a Python library that provides declarative lifecycle hooks for Django models. It allows developers to define methods that run automatically before or after database operations (e.g., save, delete, create, update) or when specific model field values change, using simple decorators. The current version is 1.2.7, and it maintains an active release cadence with frequent bug fixes and feature enhancements.","status":"active","version":"1.2.7","language":"en","source_language":"en","source_url":"https://github.com/rsinger86/django-lifecycle","tags":["django","orm","hooks","lifecycle","models","declarative"],"install":[{"cmd":"pip install django-lifecycle","lang":"bash","label":"Install latest version"}],"dependencies":[{"reason":"django-lifecycle is an extension for Django and requires it to function.","package":"django","optional":false}],"imports":[{"symbol":"LifecycleModelMixin","correct":"from django_lifecycle import LifecycleModelMixin"},{"note":"The `hook` decorator is directly available from the top-level `django_lifecycle` package.","wrong":"from django_lifecycle.hook import hook","symbol":"hook","correct":"from django_lifecycle import hook"},{"symbol":"BEFORE_SAVE","correct":"from django_lifecycle import BEFORE_SAVE"},{"symbol":"AFTER_UPDATE","correct":"from django_lifecycle import AFTER_UPDATE"},{"note":"Predefined conditions and the `condition` decorator itself are found in the `django_lifecycle.conditions` submodule.","wrong":"from django_lifecycle import condition","symbol":"condition","correct":"from django_lifecycle.conditions import condition"},{"note":"Predefined conditions like `is_greater_than` are found in the `django_lifecycle.conditions` submodule.","wrong":"from django_lifecycle import is_greater_than","symbol":"is_greater_than","correct":"from django_lifecycle.conditions import is_greater_than"}],"quickstart":{"code":"import os\nfrom django.db import models\nfrom django_lifecycle import LifecycleModelMixin, hook, BEFORE_SAVE, AFTER_UPDATE, POST_INIT\nfrom django_lifecycle.conditions import is_greater_than, is_less_than\n\n# NOTE: This example assumes Django settings are configured, e.g., via manage.py shell\n# or a test environment.\n\nclass Product(LifecycleModelMixin, models.Model):\n    name = models.CharField(max_length=255)\n    price = models.DecimalField(max_digits=10, decimal_places=2, default=0)\n    stock = models.IntegerField(default=0)\n    sku = models.CharField(max_length=100, unique=True, blank=True, null=True)\n\n    @hook(POST_INIT)\n    def on_init(self):\n        if not self.sku:\n            self.sku = f\"SKU-{os.urandom(4).hex().upper()}\"\n\n    @hook(BEFORE_SAVE)\n    def ensure_name_capitalized(self):\n        self.name = self.name.capitalize()\n\n    @hook(AFTER_UPDATE, when='price', has_changed=True)\n    def log_price_change(self):\n        print(f\"Product '{self.name}' (SKU: {self.sku}) price changed from {self.initial_value('price')} to {self.price}\")\n\n    @hook(BEFORE_SAVE, when='stock', is_greater_than=0, is_less_than=10)\n    def notify_low_stock(self):\n        print(f\"WARNING: Stock for '{self.name}' (SKU: {self.sku}) is critically low ({self.stock})!\")\n\n    def __str__(self):\n        return self.name\n\n# Example Usage (run in a Django shell or similar):\n# from your_app.models import Product # Replace 'your_app'\n#\n# p1 = Product.objects.create(name=\"keyboard\", price=75.00, stock=20)\n# print(f\"Created: {p1.name} with SKU: {p1.sku}\") # SKU will be auto-generated on POST_INIT\n#\n# p1.price = 80.50\n# p1.save() # Triggers log_price_change\n#\n# p1.stock = 5\n# p1.save() # Triggers notify_low_stock\n#\n# p2 = Product(name=\"mouse\", price=25.00, stock=15)\n# p2.save() # Triggers ensure_name_capitalized and on_init (for sku)\n# print(f\"Created: {p2.name} with SKU: {p2.sku}\")\n","lang":"python","description":"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`."},"warnings":[{"fix":"Upgrade 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.","message":"`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.","severity":"breaking","affected_versions":">=1.2.7"},{"fix":"Always ensure `LifecycleModelMixin` is the very first parent class listed in your Django model definition: `class MyModel(LifecycleModelMixin, models.Model):`.","message":"The `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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure 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.","message":"When 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.","severity":"gotcha","affected_versions":"<1.2.0"},{"fix":"Update your imports to use `from django_lifecycle.conditions import condition, is_greater_than` (or other specific conditions).","message":"Custom 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`.","severity":"gotcha","affected_versions":"All versions (fixed package structure in 1.2.2 for distribution issues, but import path has been consistent)"}],"env_vars":null,"last_verified":"2026-04-17T00:00:00.000Z","next_check":"2026-07-16T00:00:00.000Z","problems":[{"fix":"Ensure your model class inherits `LifecycleModelMixin` as the first parent: `class MyModel(LifecycleModelMixin, models.Model):`.","cause":"Your Django model is not inheriting from `LifecycleModelMixin`, or `LifecycleModelMixin` is not the first base class in the inheritance chain.","error":"AttributeError: 'MyModel' object has no attribute 'initial_value'"},{"fix":"Import conditions from the `django_lifecycle.conditions` submodule: `from django_lifecycle.conditions import condition` or `from django_lifecycle.conditions import is_greater_than`.","cause":"You are trying to import the `condition` decorator or other predefined conditions (e.g., `is_greater_than`) directly from the top-level `django_lifecycle` package.","error":"ImportError: cannot import name 'condition' from 'django_lifecycle'"},{"fix":"Verify that all methods intended to be lifecycle hooks are correctly decorated with `@hook` and specify the event type, e.g., `@hook(BEFORE_SAVE)`.","cause":"A method in your `LifecycleModelMixin` inherited model is intended to be a hook but is missing the `@hook(...)` decorator, or the decorator is improperly applied.","error":"TypeError: Lifecycle hooks require a method decorated with @hook."},{"fix":"Use one of the predefined event constants from `django_lifecycle` (e.g., `BEFORE_SAVE`, `AFTER_UPDATE`, `BEFORE_DELETE`, etc.). If you need custom logic, use `when` conditions or `condition` decorators on existing events.","cause":"You've used an event name in the `@hook` decorator that is not one of the predefined `django-lifecycle` events (e.g., `BEFORE_SAVE`, `AFTER_CREATE`).","error":"ValueError: Invalid hook event: 'MY_CUSTOM_EVENT'"}]}