# django-pgtrigger
`django-pgtrigger` helps you write [Postgres triggers](https://www.postgresql.org/docs/current/sql-createtrigger.html) for your Django models.
## Why should I use triggers?
Triggers can solve a variety of complex problems more reliably, performantly, and succinctly than application code.
For example,
* Protecting operations on rows or columns (`pgtrigger.Protect`).
* Making read-only models or fields (`pgtrigger.ReadOnly`).
* Soft-deleting models (`pgtrigger.SoftDelete`).
* Snapshotting and tracking model changes ([django-pghistory](https://django-pghistory.readthedocs.io/)).
* Enforcing field transitions (`pgtrigger.FSM`).
* Keeping a search vector updated for full-text search (`pgtrigger.UpdateSearchVector`).
* Building official interfaces (e.g. enforcing use of `User.objects.create_user` and not `User.objects.create`).
* Versioning models, mirroring fields, computing unique model hashes, and the list goes on...
All of these examples require no overridden methods, no base models, and no signal handling.
## Quick start
Install `django-pgtrigger` with `pip3 install django-pgtrigger` and add `pgtrigger` to `settings.INSTALLED_APPS`.
`pgtrigger.Trigger` objects are added to `triggers` in model `Meta`. `django-pgtrigger` comes with several trigger classes, such as `pgtrigger.Protect`. In the following, we're protecting the model from being deleted:
```python
import pgtrigger
class ProtectedModel(models.Model):
"""This model cannot be deleted!"""
class Meta:
triggers = [
pgtrigger.Protect(name="protect_deletes", operation=pgtrigger.Delete)
]
```
When migrations are created and executed, `ProtectedModel` will raise an exception anytime a deletion is attempted.
Let's extend this example further and only protect deletions on inactive objects. In this example, the trigger conditionally runs when the row being deleted (the `OLD` row in trigger terminology) is still active:
```python
import pgtrigger
class ProtectedModel(models.Model):
"""Active object cannot be deleted!"""
is_active = models.BooleanField(default=True)
class Meta:
triggers = [
pgtrigger.Protect(
name="protect_deletes",
operation=pgtrigger.Delete,
condition=pgtrigger.Q(old__is_active=True)
)
]
```
`django-pgtrigger` uses `pgtrigger.Q` and `pgtrigger.F` objects to conditionally execute triggers based on the `OLD` and `NEW` rows. Combining these Django idioms with `pgtrigger.Trigger` objects can solve a wide variety of problems without ever writing SQL. Users, however, can still use raw SQL for complex cases.
Triggers are installed like other database objects. Run `python manage.py makemigrations` and `python manage.py migrate` to install triggers.
If triggers are new to you, don't worry. The [pgtrigger docs](https://django-pgtrigger.readthedocs.io/) cover triggers in more detail and provide many examples.
## Compatibility
`django-pgtrigger` is compatible with Python 3.9 - 3.13, Django 4.2 - 5.2, Psycopg 2 - 3, and Postgres 14 - 17.
## Documentation
[View the django-pgtrigger docs here](https://django-pgtrigger.readthedocs.io/) to learn more about:
* Trigger basics and motivation for using triggers.
* How to use the built-in triggers and how to build custom ones.
* Installing triggers on third-party models, many-to-many fields, and other advanced scenarios.
* Writing conditional triggers.
* Ignoring triggers dynamically and deferring trigger execution.
* Multiple database, schema, and partitioning support.
* Frequently asked questions, common issues, and upgrading.
* The commands, settings, and module.
## Installation
Install `django-pgtrigger` with:
pip3 install django-pgtrigger
After this, add `pgtrigger` to the `INSTALLED_APPS` setting of your Django project.
## Other Material
After you've read the docs, check out [this tutorial](https://wesleykendall.github.io/django-pgtrigger-tutorial/) with interactive examples from a Django meetup talk.
The [DjangoCon 2021 talk](https://www.youtube.com/watch?v=Tte3d4JjxCk) also breaks down triggers and shows several examples.
## Contributing Guide
For information on setting up django-pgtrigger for development and contributing changes, view [CONTRIBUTING.md](CONTRIBUTING.md).
## Creators
- [Wes Kendall](https://github.com/wesleykendall)
## Other Contributors
- @jzmiller1
- @rrauenza
- @ralokt
- @adamchainz
- @danifus
- @kekekekule
- @peterthomassen
- @pfouque
Raw data
{
"_id": null,
"home_page": "https://github.com/AmbitionEng/django-pgtrigger",
"name": "django-pgtrigger",
"maintainer": null,
"docs_url": null,
"requires_python": "<4,>=3.9.0",
"maintainer_email": null,
"keywords": null,
"author": "Wes Kendall",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/65/e1/47af2eb011017edf107be1d09f204ad4568021b2549380646d8da23238f6/django_pgtrigger-4.15.4.tar.gz",
"platform": null,
"description": "# django-pgtrigger\n\n`django-pgtrigger` helps you write [Postgres triggers](https://www.postgresql.org/docs/current/sql-createtrigger.html) for your Django models.\n\n## Why should I use triggers?\n\nTriggers can solve a variety of complex problems more reliably, performantly, and succinctly than application code.\nFor example,\n\n* Protecting operations on rows or columns (`pgtrigger.Protect`).\n* Making read-only models or fields (`pgtrigger.ReadOnly`).\n* Soft-deleting models (`pgtrigger.SoftDelete`).\n* Snapshotting and tracking model changes ([django-pghistory](https://django-pghistory.readthedocs.io/)).\n* Enforcing field transitions (`pgtrigger.FSM`).\n* Keeping a search vector updated for full-text search (`pgtrigger.UpdateSearchVector`).\n* Building official interfaces (e.g. enforcing use of `User.objects.create_user` and not `User.objects.create`).\n* Versioning models, mirroring fields, computing unique model hashes, and the list goes on...\n\nAll of these examples require no overridden methods, no base models, and no signal handling.\n\n## Quick start\n\nInstall `django-pgtrigger` with `pip3 install django-pgtrigger` and add `pgtrigger` to `settings.INSTALLED_APPS`.\n\n`pgtrigger.Trigger` objects are added to `triggers` in model `Meta`. `django-pgtrigger` comes with several trigger classes, such as `pgtrigger.Protect`. In the following, we're protecting the model from being deleted:\n\n```python\nimport pgtrigger\n\nclass ProtectedModel(models.Model):\n \"\"\"This model cannot be deleted!\"\"\"\n\n class Meta:\n triggers = [\n pgtrigger.Protect(name=\"protect_deletes\", operation=pgtrigger.Delete)\n ]\n```\n\nWhen migrations are created and executed, `ProtectedModel` will raise an exception anytime a deletion is attempted.\n\nLet's extend this example further and only protect deletions on inactive objects. In this example, the trigger conditionally runs when the row being deleted (the `OLD` row in trigger terminology) is still active:\n\n```python\nimport pgtrigger\n\nclass ProtectedModel(models.Model):\n \"\"\"Active object cannot be deleted!\"\"\"\n is_active = models.BooleanField(default=True)\n\n class Meta:\n triggers = [\n pgtrigger.Protect(\n name=\"protect_deletes\",\n operation=pgtrigger.Delete,\n condition=pgtrigger.Q(old__is_active=True)\n )\n ]\n```\n\n`django-pgtrigger` uses `pgtrigger.Q` and `pgtrigger.F` objects to conditionally execute triggers based on the `OLD` and `NEW` rows. Combining these Django idioms with `pgtrigger.Trigger` objects can solve a wide variety of problems without ever writing SQL. Users, however, can still use raw SQL for complex cases.\n\nTriggers are installed like other database objects. Run `python manage.py makemigrations` and `python manage.py migrate` to install triggers.\n\nIf triggers are new to you, don't worry. The [pgtrigger docs](https://django-pgtrigger.readthedocs.io/) cover triggers in more detail and provide many examples.\n\n## Compatibility\n\n`django-pgtrigger` is compatible with Python 3.9 - 3.13, Django 4.2 - 5.2, Psycopg 2 - 3, and Postgres 14 - 17.\n\n## Documentation\n\n[View the django-pgtrigger docs here](https://django-pgtrigger.readthedocs.io/) to learn more about:\n\n* Trigger basics and motivation for using triggers.\n* How to use the built-in triggers and how to build custom ones.\n* Installing triggers on third-party models, many-to-many fields, and other advanced scenarios.\n* Writing conditional triggers.\n* Ignoring triggers dynamically and deferring trigger execution.\n* Multiple database, schema, and partitioning support.\n* Frequently asked questions, common issues, and upgrading.\n* The commands, settings, and module.\n\n## Installation\n\nInstall `django-pgtrigger` with:\n\n pip3 install django-pgtrigger\nAfter this, add `pgtrigger` to the `INSTALLED_APPS` setting of your Django project.\n\n## Other Material\n\nAfter you've read the docs, check out [this tutorial](https://wesleykendall.github.io/django-pgtrigger-tutorial/) with interactive examples from a Django meetup talk.\n\nThe [DjangoCon 2021 talk](https://www.youtube.com/watch?v=Tte3d4JjxCk) also breaks down triggers and shows several examples.\n\n## Contributing Guide\n\nFor information on setting up django-pgtrigger for development and contributing changes, view [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Creators\n\n- [Wes Kendall](https://github.com/wesleykendall)\n\n## Other Contributors\n\n- @jzmiller1\n- @rrauenza\n- @ralokt\n- @adamchainz\n- @danifus\n- @kekekekule\n- @peterthomassen\n- @pfouque\n",
"bugtrack_url": null,
"license": "BSD-3-Clause",
"summary": "Postgres trigger support integrated with Django models.",
"version": "4.15.4",
"project_urls": {
"Documentation": "https://django-pgtrigger.readthedocs.io",
"Homepage": "https://github.com/AmbitionEng/django-pgtrigger",
"Repository": "https://github.com/AmbitionEng/django-pgtrigger"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "dd9cdeb7c7089ecef9912eff15a2cb1ac32a5ae695969473a84eee1f1fa7171d",
"md5": "99d49a225c02200f09236ed13da526eb",
"sha256": "6e1732c85bccbf22b183ca13b410d6908f3eaaeaf6103a3c62c236b0ae6a9072"
},
"downloads": -1,
"filename": "django_pgtrigger-4.15.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "99d49a225c02200f09236ed13da526eb",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4,>=3.9.0",
"size": 36148,
"upload_time": "2025-08-17T02:30:38",
"upload_time_iso_8601": "2025-08-17T02:30:38.847615Z",
"url": "https://files.pythonhosted.org/packages/dd/9c/deb7c7089ecef9912eff15a2cb1ac32a5ae695969473a84eee1f1fa7171d/django_pgtrigger-4.15.4-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "65e147af2eb011017edf107be1d09f204ad4568021b2549380646d8da23238f6",
"md5": "8eef8c6dd0a5dbded709dda0a90b1e51",
"sha256": "3dfef7dd8faca0af3602818075f28da1d540193af63a77bb848f56f532a968e4"
},
"downloads": -1,
"filename": "django_pgtrigger-4.15.4.tar.gz",
"has_sig": false,
"md5_digest": "8eef8c6dd0a5dbded709dda0a90b1e51",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4,>=3.9.0",
"size": 32791,
"upload_time": "2025-08-17T02:30:40",
"upload_time_iso_8601": "2025-08-17T02:30:40.038048Z",
"url": "https://files.pythonhosted.org/packages/65/e1/47af2eb011017edf107be1d09f204ad4568021b2549380646d8da23238f6/django_pgtrigger-4.15.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-17 02:30:40",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "AmbitionEng",
"github_project": "django-pgtrigger",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"circle": true,
"tox": true,
"lcname": "django-pgtrigger"
}