drf-actions
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
- You describe which models (tables) to watch in the
DRF_ACTIONS_SETTINGSsetting. - For each watched model you create an
ActionContentTyperecord. On save, the app generates a PL/pgSQL function and anAFTER INSERT OR UPDATE OR DELETEtrigger on the table. - Every change to the table inserts a row into the
EventJournaltable with the reason (INSERT/UPDATE/DELETE), the object id and a JSON snapshot of the configured fields. - 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
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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.