Skip to main content

django-maker-checker

CI Coverage PyPI Python Docs License: MIT

Documentation: https://django-maker-checker.readthedocs.io

Maker-checker (four-eyes) approval of sensitive changes for Django.

When a user (the maker) performs a guarded action, such as creating a user, changing a credit limit or approving a payment run, the change is not applied. It becomes an approval request that someone else (a checker) must approve, under rules each tenant configures. Only then is the change applied, re-checked against the current state of the data.

It is built for the controls that regulated businesses (banks, fintechs, back offices) are audited on:

  • The change is held, not just flagged. The action is captured and applied only on approval. If the data changed meanwhile, the request fails instead of overwriting someone else's edit.
  • Segregation of duties is enforced. A maker can never approve their own request, not as a superuser and not through a delegation. One person can't approve two steps. A request can't be raised if nobody else could approve it.
  • Rules are data, not code. Each tenant decides which actions need approval, by whom (users or roles), in what workflow, and how fast, without a deploy. Every rule change is versioned with who, when and why.
  • Nothing gets stuck. Each step has an SLA with a reminder and escalation to a backup approver.
  • Everything is on the record. Every request has a timeline, every decision is stored, and every event is passed to your audit log.
  • Concurrency-safe. Decisions lock the row, and only one open request may exist per item.

It is headless: it ships models, rules, services and read queries, and no UI. You build the screens your product needs on its Python API.

How it compares

django-maker-checker Workflow engines (e.g. django-approval-workflow) Moderation (e.g. django-approval)
What is approved A pending change, applied on approval An existing object moving through steps An edit to a model, kept in a sandbox
Who decides what needs approval Tenant-configured rules (data) Code, per flow Code, per model
Maker can't approve own change Enforced — Privileged users bypass by default
SLA and escalation Built in Tracked; you run the timeout loop —
Multi-tenant Built in — —

If you need rich routing (quorums, org-chart hierarchies, parallel tracks), a workflow engine is the better fit. If you need "nobody changes this alone, and we can prove it", this is.


Requirements

  • Python 3.11+ and Django 5.2, 6.0 or 6.1, with django.contrib.auth and django.contrib.contenttypes
  • django-fsm-2 (installed automatically)
  • PostgreSQL is recommended for production. SQLite works for development and tests. On MySQL / MariaDB the "one open request per item" guarantee is not enforced, because those databases ignore Django's conditional unique constraints.
  • Optional: Celery, to run the SLA job (pip install "django-maker-checker[celery]"). Without it, run a management command from cron.

Quick start

A single-tenant project on Django's own users, groups and permissions needs no configuration:

pip install django-maker-checker
# settings.py
INSTALLED_APPS = [..., "django.contrib.auth", "django.contrib.contenttypes", "django_maker_checker"]

Guard the function that makes the change, and register it from your app's AppConfig.ready():

from django_maker_checker.actions import approval_action


@approval_action(
    "credit_limit_change",
    label="Credit Limit Change",
    category="Accounts",
    lock_key=lambda p: f"account:{p['account_id']}",  # at most one open request per account
)
def change_credit_limit(*, tenant, maker, context, account_id, new_limit):
    Account.objects.filter(pk=account_id).update(credit_limit=new_limit)

Run python manage.py migrate, which also creates the permission approvals.approve_credit_limit_change. Grant it to a group, then switch on a rule naming that group:

from django_maker_checker import policies, services
from django_maker_checker.constants import Decision
from django_maker_checker.models import Tenant

policies.save_policy(
    policies.policy_for(Tenant.default(), "credit_limit_change"),
    policies.PolicySpec(approvers=[f"r:{risk_officers.pk}"]),
    by=admin_user,
)

outcome = change_credit_limit(
    tenant=Tenant.default(), maker=request.user, justification="Annual review", account_id=42, new_limit="5000.00"
)
outcome.pending  # True: nothing changed yet, and the risk officers were emailed

services.decide(outcome.approval_request.pk, risk_officer, Decision.APPROVE, "Within appetite")
# the credit limit changes now, recorded as the maker's change

A checker can also reject with a reason, or send the request back to the maker for changes. Rules can require one approver, any N of several, all of them, or ordered steps, with an SLA and escalation.

Multi-tenant, or your own roles and permissions? Point APPROVALS_TENANT_MODEL, APPROVALS_ROLE_MODEL and APPROVALS_ADAPTER at your models and an adapter class. See Integrating with your project.

Documentation

https://django-maker-checker.readthedocs.io

Contributing

Contributions are welcome; see CONTRIBUTING.md and the development guide. The tests are written as a specification of the library: start with tests/README.md.

License

MIT

Metadata

Release files for django-maker-checker 0.6.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 django-maker-checker 0.6.0
File Size Uploaded
django_maker_checker-0.6.0.tar.gz 79.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-maker-checker 0.6.0
File Interpreter ABI Platform
django_maker_checker-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 148.8 kB

Release files / django_maker_checker-0.6.0.tar.gz

Download URL django_maker_checker-0.6.0.tar.gz
Size 79.8 kB
Tags Source
SHA-256 checksum
How to use checksums
f045456bf2b69821ac75db47e22695330b4b8177ed8c28602ba2807b1aa062b1
BLAKE2b-256 checksum
How to use checksums
d56b04f266967da2ee1b04ecde66d5a879fd4f369f85111002bca15fc17aa63a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / django_maker_checker-0.6.0-py3-none-any.whl

Download URL django_maker_checker-0.6.0-py3-none-any.whl
Size 68.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d21856864e7ceab5702617d7268948b1c191f4a36935bb11c2a4c1363667a0ae
BLAKE2b-256 checksum
How to use checksums
b64630d9d5a25f7db1751421d61e1445d0eca7e0a5e4adaa384ef1540c3d5f58
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

0.8.0

2 release files

0.7.0

2 release files

This release

0.6.0 This release

2 release files

0.5.0

2 release files

0.4.0

2 release files

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