Skip to main content

drf-actions

PyPI Python versions Django Tests License: MIT

Django app that keeps an event journal of row changes (INSERT / UPDATE / DELETE) for selected models using native PostgreSQL triggers, and exposes the journal through a read-only Django REST Framework API.

Because the journal is populated by database triggers (not Django signals), it captures every change — including raw SQL, bulk_update, and writes from other services sharing the same database.

How it works

  1. You describe which models (tables) to watch in the DRF_ACTIONS_SETTINGS setting.
  2. For each watched model you create an ActionContentType record. On save, the app generates a PL/pgSQL function and an AFTER INSERT OR UPDATE OR DELETE trigger on the table.
  3. Every change to the table inserts a row into the EventJournal table with the reason (INSERT / UPDATE / DELETE), the object id and a JSON snapshot of the configured fields.
  4. Consumers read the journal via the REST endpoint (protected by API keys) or directly via the ORM.

Requirements

  • Python 3.10+
  • Django 4.2 / 5.x (5.2 LTS is the primary target)
  • Django REST Framework 3.16+
  • PostgreSQL (the event journal is populated by native PG triggers)

Installation

pip install drf-actions

Add the app and its dependencies to INSTALLED_APPS:

INSTALLED_APPS = [
    # ...
    "rest_framework",
    "rest_framework_api_key",
    "django_filters",
    "django_json_widget",  # nicer JSON display in the admin
    "drf_actions",
]

Run the migrations:

python manage.py migrate drf_actions

Configuration

Describe the models you want to track with DRF_ACTIONS_SETTINGS in your Django settings:

DRF_ACTIONS_SETTINGS = {
    "content_types": {
        "user": {
            # primary key field of the watched table
            "pk": "id",
            # optional: column whose value is stored as "owner" in the event data
            "owner": None,
            # optional: fire UPDATE events only when these columns change
            "catch_update": [],
            # optional: many-to-many relations to embed into the event data
            "m2m": [
                # (through_table, fk_to_related, fk_to_this,
                #  related_table, related_pk, related_column, json_key)
                ("users_user_groups", "group_id", "user_id",
                 "auth_group", "id", "name", "groups"),
            ],
            # (app_label, model_name) of the watched model
            "model": ("users", "user"),
            # columns to snapshot: (json_key, table_column)
            "fields": (
                ("email", "email"),
                ("full_name", "full_name"),
            ),
        },
    },
    # backfill events for rows that already exist when a trigger is installed
    "create_event_for_new_entity": True,
}

Content type options

Key Required Description
pk yes Name of the primary key column of the watched table.
model yes (app_label, model_name) tuple used to resolve the Django model.
fields yes Iterable of (json_key, table_column) pairs snapshotted into EventJournal.data.
owner no Column stored as owner in the event data (e.g. to route notifications).
catch_update no List of columns; when set, UPDATE events fire only if one of them changes.
m2m no Many-to-many relations aggregated into the event data (see the tuple layout above).

Installing the triggers

Create an ActionContentType for each configured content type — the PostgreSQL function and trigger are created automatically on save:

from drf_actions.models import ActionContentType

ActionContentType.objects.create(content_type="user", table="users_user")

If create_event_for_new_entity is True, existing rows are backfilled into the journal as INSERT events at that moment. Deleting an ActionContentType drops the trigger and its function.

REST API

Hook up the bundled router:

# urls.py
from django.urls import include, path

urlpatterns = [
    # ...
    path("api/", include("drf_actions.urls")),
]

This exposes a read-only endpoint at api/actions/events/, protected with rest_framework_api_key (send Authorization: Api-Key <key>).

Supported query parameters:

  • filtering: id, id__in, id__lt/lte/gt/gte, created__range/lt/lte/gt/gte, reason, reason__in, content_type, content_type__in
  • ordering: ordering=id, ordering=-created
  • pagination: page, page_size (default 100, max 10000)

Example response item:

{
    "id": 42,
    "reason": "UPDATE",
    "object_id": "7",
    "content_type": "user",
    "data": {"email": "user@example.com", "full_name": "Jane Doe", "groups": ["editors"]},
    "created": "2026-07-13T12:00:00Z",
    "modified": "2026-07-13T12:00:00Z"
}

Development

docker run -d --name drf-actions-test-pg \
  -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=drf_actions \
  -p 54329:5432 postgres:16-alpine
uv venv --python 3.13 .venv
uv pip install --python .venv/bin/python -e . pytest pytest-django "psycopg[binary]"
.venv/bin/python -m pytest

License

MIT

Metadata

Release files for drf-actions 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for drf-actions 0.5.0
File Size Uploaded Provenance Actions
drf_actions-0.5.0.tar.gz 12.2 kB Without provenance Details for drf_actions-0.5.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for drf-actions 0.5.0
File Interpreter ABI Platform Actions
drf_actions-0.5.0-py3-none-any.whl Python 3 none any Details for drf_actions-0.5.0-py3-none-any.whl

Total release size: 26.1 kB

Release files / drf_actions-0.5.0.tar.gz

Download URL drf_actions-0.5.0.tar.gz
Size 12.2 kB
Tags Source
SHA-256 checksum
How to use checksums
3223ec7f900f04fbac611ba4a8f3a2967053f335700d34c82a0341b657cc44c3
BLAKE2b-256 checksum
How to use checksums
01e272d0154b4768bb1ecf4395014099fc39ecdb97df8b1de1d0e335dfb2b799
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/2.4.1 CPython/3.12.8 Linux/6.17.0-1018-azure

Release files / drf_actions-0.5.0-py3-none-any.whl

Download URL drf_actions-0.5.0-py3-none-any.whl
Size 14.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ddae6f29c1355dce4c942d3952ffdf6a3effa8ddca5dcd7bdc130c10e0c7325e
BLAKE2b-256 checksum
How to use checksums
35c63d046023d5e0af63ac28879fb93685b3a8d8cd84f3205b15fdcbe9695cd5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/2.4.1 CPython/3.12.8 Linux/6.17.0-1018-azure

Security for drf-actions 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page