django-lifecycle


Namedjango-lifecycle JSON
Version 1.2.4 PyPI version JSON
download
home_pagehttps://github.com/rsinger86/django-lifecycle
SummaryDeclarative model lifecycle hooks.
upload_time2024-06-07 10:40:33
maintainerNone
docs_urlNone
authorRobert Singer
requires_pythonNone
licenseMIT
keywords django model lifecycle hooks callbacks
VCS
bugtrack_url
requirements asgiref Click Django django-capture-on-commit-callbacks djangorestframework ghp-import importlib-metadata Jinja2 livereload Markdown MarkupSafe mergedeep mkdocs mkdocs-material packaging Pygments pymdown-extensions pyparsing python-dateutil pytz PyYAML pyyaml-env-tag six sqlparse tornado urlman watchdog zipp
Travis-CI
coveralls test coverage No coveralls.
            # Django Lifecycle Hooks

[![Package version](https://badge.fury.io/py/django-lifecycle.svg)](https://pypi.python.org/pypi/django-lifecycle)
[![Python versions](https://img.shields.io/pypi/status/django-lifecycle.svg)](https://img.shields.io/pypi/status/django-lifecycle.svg/)
[![Python versions](https://img.shields.io/pypi/pyversions/django-lifecycle.svg)](https://pypi.org/project/django-lifecycle/)
![PyPI - Django Version](https://img.shields.io/pypi/djversions/django-lifecycle)

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](https://docs.djangoproject.com/en/dev/topics/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.7, 3.8, 3.9, 3.10, 3.11, and 3.12
* Django 2.2, 3.2, 4.0, 4.1, 4.2, and 5.0

In short, you can write model code like this:

```python
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, WhenFieldHasChanged("contents", has_changed=True))
    def on_content_change(self):
        self.updated_at = timezone.now()

    @hook(
        AFTER_UPDATE, 
        condition=(
            WhenFieldValueWas("status", value="draft")
            & WhenFieldValueIs("status", value="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:

```python
    # 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!")
```

---

**Documentation**: <a href="https://rsinger86.github.io/django-lifecycle/" target="_blank">https://rsinger86.github.io/django-lifecycle</a>

**Source Code**: <a href="https://github.com/rsinger86/django-lifecycle/" target="_blank">https://github.com/rsinger86/django-lifecycle</a>

---

# Changelog

See [Changelog](CHANGELOG.md)

# Testing

Tests are found in a simplified Django project in the `/tests` folder. Install the project requirements and do `./manage.py test` to run them.

# License

See [License](LICENSE.md).

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/rsinger86/django-lifecycle",
    "name": "django-lifecycle",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": "django model lifecycle hooks callbacks",
    "author": "Robert Singer",
    "author_email": "robertgsinger@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/9c/ac/4e5ce8cb8b2eef726157bbfb6f227e79a4bf8e14d6346b21490b689464fb/django_lifecycle-1.2.4.tar.gz",
    "platform": null,
    "description": "# Django Lifecycle Hooks\n\n[![Package version](https://badge.fury.io/py/django-lifecycle.svg)](https://pypi.python.org/pypi/django-lifecycle)\n[![Python versions](https://img.shields.io/pypi/status/django-lifecycle.svg)](https://img.shields.io/pypi/status/django-lifecycle.svg/)\n[![Python versions](https://img.shields.io/pypi/pyversions/django-lifecycle.svg)](https://pypi.org/project/django-lifecycle/)\n![PyPI - Django Version](https://img.shields.io/pypi/djversions/django-lifecycle)\n\nThis 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](https://docs.djangoproject.com/en/dev/topics/signals/). However, my team often finds that Signals introduce unnecessary indirection and are at odds with Django's \"fat models\" approach.\n\n**Django Lifecycle Hooks** supports:\n\n* Python 3.7, 3.8, 3.9, 3.10, 3.11, and 3.12\n* Django 2.2, 3.2, 4.0, 4.1, 4.2, and 5.0\n\nIn short, you can write model code like this:\n\n```python\nfrom django_lifecycle import LifecycleModel, hook, BEFORE_UPDATE, AFTER_UPDATE\n\n\nclass Article(LifecycleModel):\n    contents = models.TextField()\n    updated_at = models.DateTimeField(null=True)\n    status = models.ChoiceField(choices=['draft', 'published'])\n    editor = models.ForeignKey(AuthUser)\n\n    @hook(BEFORE_UPDATE, WhenFieldHasChanged(\"contents\", has_changed=True))\n    def on_content_change(self):\n        self.updated_at = timezone.now()\n\n    @hook(\n        AFTER_UPDATE, \n        condition=(\n            WhenFieldValueWas(\"status\", value=\"draft\")\n            & WhenFieldValueIs(\"status\", value=\"published\")\n        )\n    )\n    def on_publish(self):\n        send_email(self.editor.email, \"An article has published!\")\n```\n\nInstead of overriding `save` and `__init__` in a clunky way that hurts readability:\n\n```python\n    # same class and field declarations as above ...\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._orig_contents = self.contents\n        self._orig_status = self.status\n\n\n    def save(self, *args, **kwargs):\n        if self.pk is not None and self.contents != self._orig_contents:\n            self.updated_at = timezone.now()\n\n        super().save(*args, **kwargs)\n\n        if self.status != self._orig_status:\n            send_email(self.editor.email, \"An article has published!\")\n```\n\n---\n\n**Documentation**: <a href=\"https://rsinger86.github.io/django-lifecycle/\" target=\"_blank\">https://rsinger86.github.io/django-lifecycle</a>\n\n**Source Code**: <a href=\"https://github.com/rsinger86/django-lifecycle/\" target=\"_blank\">https://github.com/rsinger86/django-lifecycle</a>\n\n---\n\n# Changelog\n\nSee [Changelog](CHANGELOG.md)\n\n# Testing\n\nTests are found in a simplified Django project in the `/tests` folder. Install the project requirements and do `./manage.py test` to run them.\n\n# License\n\nSee [License](LICENSE.md).\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Declarative model lifecycle hooks.",
    "version": "1.2.4",
    "project_urls": {
        "Documentation": "https://rsinger86.github.io/django-lifecycle/",
        "Homepage": "https://github.com/rsinger86/django-lifecycle",
        "Source": "https://github.com/rsinger86/django-lifecycle"
    },
    "split_keywords": [
        "django",
        "model",
        "lifecycle",
        "hooks",
        "callbacks"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ab3185dd0766c135e8688d88849fc3d245c783cab435cedfb6d9041a911ee22f",
                "md5": "e59bda2a6bc49bb2552ad4a6045c114d",
                "sha256": "b54aea17b50de45adb5c90a06eea0171afa0d547682f51990dffb578b82fc658"
            },
            "downloads": -1,
            "filename": "django_lifecycle-1.2.4-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "e59bda2a6bc49bb2552ad4a6045c114d",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 15012,
            "upload_time": "2024-06-07T10:40:31",
            "upload_time_iso_8601": "2024-06-07T10:40:31.822140Z",
            "url": "https://files.pythonhosted.org/packages/ab/31/85dd0766c135e8688d88849fc3d245c783cab435cedfb6d9041a911ee22f/django_lifecycle-1.2.4-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9cac4e5ce8cb8b2eef726157bbfb6f227e79a4bf8e14d6346b21490b689464fb",
                "md5": "3a4745c83f278ac8edb5421e80f8a674",
                "sha256": "b37add8a95d0e85f9f97e652fac989cd5914cddb2380d933b6568f80238ab61e"
            },
            "downloads": -1,
            "filename": "django_lifecycle-1.2.4.tar.gz",
            "has_sig": false,
            "md5_digest": "3a4745c83f278ac8edb5421e80f8a674",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 12223,
            "upload_time": "2024-06-07T10:40:33",
            "upload_time_iso_8601": "2024-06-07T10:40:33.678910Z",
            "url": "https://files.pythonhosted.org/packages/9c/ac/4e5ce8cb8b2eef726157bbfb6f227e79a4bf8e14d6346b21490b689464fb/django_lifecycle-1.2.4.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-06-07 10:40:33",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "rsinger86",
    "github_project": "django-lifecycle",
    "travis_ci": true,
    "coveralls": false,
    "github_actions": true,
    "requirements": [
        {
            "name": "asgiref",
            "specs": [
                [
                    "==",
                    "3.4.1"
                ]
            ]
        },
        {
            "name": "Click",
            "specs": [
                [
                    "==",
                    "7.0"
                ]
            ]
        },
        {
            "name": "Django",
            "specs": [
                [
                    "==",
                    "5.0b1"
                ]
            ]
        },
        {
            "name": "django-capture-on-commit-callbacks",
            "specs": [
                [
                    "==",
                    "1.10.0"
                ]
            ]
        },
        {
            "name": "djangorestframework",
            "specs": [
                [
                    "==",
                    "3.11.2"
                ]
            ]
        },
        {
            "name": "ghp-import",
            "specs": [
                [
                    "==",
                    "2.0.2"
                ]
            ]
        },
        {
            "name": "importlib-metadata",
            "specs": [
                [
                    "==",
                    "4.8.1"
                ]
            ]
        },
        {
            "name": "Jinja2",
            "specs": [
                [
                    "==",
                    "2.11.3"
                ]
            ]
        },
        {
            "name": "livereload",
            "specs": [
                [
                    "==",
                    "2.6.1"
                ]
            ]
        },
        {
            "name": "Markdown",
            "specs": [
                [
                    "==",
                    "3.2.1"
                ]
            ]
        },
        {
            "name": "MarkupSafe",
            "specs": [
                [
                    "==",
                    "1.1.1"
                ]
            ]
        },
        {
            "name": "mergedeep",
            "specs": [
                [
                    "==",
                    "1.3.4"
                ]
            ]
        },
        {
            "name": "mkdocs",
            "specs": [
                [
                    "==",
                    "1.2.3"
                ]
            ]
        },
        {
            "name": "mkdocs-material",
            "specs": [
                [
                    "==",
                    "4.6.3"
                ]
            ]
        },
        {
            "name": "packaging",
            "specs": [
                [
                    "==",
                    "21.0"
                ]
            ]
        },
        {
            "name": "Pygments",
            "specs": [
                [
                    "==",
                    "2.7.4"
                ]
            ]
        },
        {
            "name": "pymdown-extensions",
            "specs": [
                [
                    "==",
                    "6.3"
                ]
            ]
        },
        {
            "name": "pyparsing",
            "specs": [
                [
                    "==",
                    "3.0.0"
                ]
            ]
        },
        {
            "name": "python-dateutil",
            "specs": [
                [
                    "==",
                    "2.8.2"
                ]
            ]
        },
        {
            "name": "pytz",
            "specs": [
                [
                    "==",
                    "2023.3"
                ]
            ]
        },
        {
            "name": "PyYAML",
            "specs": [
                [
                    "==",
                    "6.0.1"
                ]
            ]
        },
        {
            "name": "pyyaml-env-tag",
            "specs": [
                [
                    "==",
                    "0.1"
                ]
            ]
        },
        {
            "name": "six",
            "specs": [
                [
                    "==",
                    "1.14.0"
                ]
            ]
        },
        {
            "name": "sqlparse",
            "specs": [
                [
                    "==",
                    "0.3.0"
                ]
            ]
        },
        {
            "name": "tornado",
            "specs": [
                [
                    "==",
                    "6.0.3"
                ]
            ]
        },
        {
            "name": "urlman",
            "specs": [
                [
                    "==",
                    "1.2.0"
                ]
            ]
        },
        {
            "name": "watchdog",
            "specs": [
                [
                    "==",
                    "2.1.6"
                ]
            ]
        },
        {
            "name": "zipp",
            "specs": [
                [
                    "==",
                    "3.6.0"
                ]
            ]
        }
    ],
    "tox": true,
    "lcname": "django-lifecycle"
}
        
Elapsed time: 0.90899s