{"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":"python","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":[{"wrong":"from django_lifecycle import LifecycleModelMixin","symbol":"LifecycleModelMixin","correct":"from django_lifecycle import LifecycleModelMixin"}],"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,"search_vec":"'1.2.7':50 'activ':55 'allow':18 'automat':25 'bug':60 'cadenc':57 'chang':42 'creat':34 'current':47 'databas':29 'declar':11,70 'decor':45 'defin':21 'delet':33 'develop':19 'django':1,3,15,65 'e.g':31 'enhanc':64 'featur':63 'field':40 'fix':61 'frequent':59 'hook':13,67 'librari':8 'lifecycl':2,4,12,68 'maintain':53 'method':22 'model':16,39,69 'oper':30 'orm':66 'provid':10 'python':7 'releas':56 'run':24 'save':32 'simpl':44 'specif':38 'updat':35 'use':43 'valu':41 'version':48","created_at":"2026-04-17T01:20:08.098789+00:00","updated_at":"2026-04-17T01:20:08.098789+00:00","problems":{"verify_error":"Traceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n  File \"/tmp/tmpj2jft7cc/venv/lib/python3.12/site-packages/django_lifecycle/__init__.py\", line 17, in <module>\n    from .models import LifecycleModel\n  File \"/tmp/tmpj2jft7cc/venv/lib/python3.12/site-packages/django_lifecycle/"},"ecosystem":"pypi","meta_description":null,"install_score":null,"quickstart_score":null,"quickstart_tag":null,"pypi_latest":"1.3.0","cli_name":"","cli_version":null,"type":"library","homepage":null,"github":"https://github.com/rsinger86/django-lifecycle","docs":"https://rsinger86.github.io/django-lifecycle/","changelog":null,"pypi":"https://pypi.org/project/django-lifecycle/","npm":null,"openapi_spec":null,"status_page":null,"smithery":null,"categories":["database","web-framework"],"base_url":null,"auth_type":null,"provenance":{"verified_status":"import_fail","verified_at":"2026-07-03","last_verified":"2026-07-03","next_check":"2026-07-10","install_tag":null}}