django-lifecycle


Namedjango-lifecycle JSON
Version 1.2.3 PyPI version JSON
download
home_pagehttps://github.com/rsinger86/django-lifecycle
SummaryDeclarative model lifecycle hooks.
upload_time2024-03-01 08:39:24
maintainer
docs_urlNone
authorRobert Singer
requires_python
licenseMIT
keywords django model lifecycle hooks callbacks
VCS
bugtrack_url
requirements No requirements were recorded.
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, 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:

```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": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "django model lifecycle hooks callbacks",
    "author": "Robert Singer",
    "author_email": "robertgsinger@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/38/d6/5e4ace36af4a5bce03221f232dc9ffd5ff5a04cc4956d305a5c33f51e80e/django-lifecycle-1.2.3.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, when='contents', has_changed=True)\n    def on_content_change(self):\n        self.updated_at = timezone.now()\n\n    @hook(AFTER_UPDATE, when=\"status\", was=\"draft\", is_now=\"published\")\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.3",
    "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": "8cd8571ed9b951bfbd2e93a98d35d8d1f7d0d6caea6bc6db1e2a9ddebf5e90c3",
                "md5": "9d2d50273b0643814b9613c314b08821",
                "sha256": "774ac2e97f506c0599677dda8adcd43e65345e4ab3b45d6ad355c68619b2bc0e"
            },
            "downloads": -1,
            "filename": "django_lifecycle-1.2.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "9d2d50273b0643814b9613c314b08821",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 13887,
            "upload_time": "2024-03-01T08:39:23",
            "upload_time_iso_8601": "2024-03-01T08:39:23.176899Z",
            "url": "https://files.pythonhosted.org/packages/8c/d8/571ed9b951bfbd2e93a98d35d8d1f7d0d6caea6bc6db1e2a9ddebf5e90c3/django_lifecycle-1.2.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "38d65e4ace36af4a5bce03221f232dc9ffd5ff5a04cc4956d305a5c33f51e80e",
                "md5": "5a96c42fc04637870ed7d6b89e3caf30",
                "sha256": "d3d7e7288894cf6c8c88a01b573a775e15611e419677ef89d59ed56150be21dd"
            },
            "downloads": -1,
            "filename": "django-lifecycle-1.2.3.tar.gz",
            "has_sig": false,
            "md5_digest": "5a96c42fc04637870ed7d6b89e3caf30",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 11793,
            "upload_time": "2024-03-01T08:39:24",
            "upload_time_iso_8601": "2024-03-01T08:39:24.584727Z",
            "url": "https://files.pythonhosted.org/packages/38/d6/5e4ace36af4a5bce03221f232dc9ffd5ff5a04cc4956d305a5c33f51e80e/django-lifecycle-1.2.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-01 08:39:24",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "rsinger86",
    "github_project": "django-lifecycle",
    "travis_ci": true,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "tox": true,
    "lcname": "django-lifecycle"
}
        
Elapsed time: 0.31172s