Skip to content

# django-pgtrigger

`django-pgtrigger` helps you write Postgres triggers 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).
- 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:

    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 active objects. In this example, the trigger conditionally runs when the row being deleted (the `OLD` row in trigger terminology) is still active:

    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.

## Compatibility

`django-pgtrigger` is compatible with Python 3.10 - 3.14, Django 4.2 - 6.0, Psycopg 2 - 3, and Postgres 14 - 18.

## Next steps

We recommend everyone first read:

- Installation for how to install the library.
- Basics for an overview and motivation.

After this, there are several usage guides:

- Cookbook for trigger examples.
- Conditional Triggers for all the ways one can create conditional triggers.
- Ignoring Execution for dynamically ignoring triggers.
- Deferrable Triggers for deferring trigger execution.
- Advanced Installation for installing triggers on third-party models, many-to-many models, programmatic installation, and more.
- Advanced Database Setups for notes on how triggers work in multi-database, mutli-schema, or partitioned database setups.

There's additional help in these sections:

- FAQ for common questions like testing and disabling triggers.
- Troubleshooting for advice on known issues.
- Upgrading for upgrading to new major versions.
- Further Reading for additional reading and videos.

Finally, core API information exists in these sections:

- Settings for all available Django settings.
- Commands for using the `python manage.py pgtrigger` management commands.
- Module for documentation of the `pgtrigger` module.
- Release Notes for information about every release.
- Contributing Guide for details on contributing to the codebase.