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.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 django-maker-checker 0.5.0
File Size Uploaded
django_maker_checker-0.5.0.tar.gz 76.5 kB Details

Built distribution (wheel)

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

Total release size: 143.1 kB

Release files / django_maker_checker-0.5.0.tar.gz

Download URL django_maker_checker-0.5.0.tar.gz
Size 76.5 kB
Tags Source
SHA-256 checksum
How to use checksums
f0372c05023b7b13336d7d21ad23893cc2d4d6df310a7a24fb1e31665bfb8e6b
BLAKE2b-256 checksum
How to use checksums
db180b4f737e6d6ca9cb3b7fbcfc4a8fa09ef959bbdd5428606ca6950b344a59
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.5.0-py3-none-any.whl

Download URL django_maker_checker-0.5.0-py3-none-any.whl
Size 66.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7372f24b0c0b215c690ac02c0c2a7da5c4ee8fc5fd70b7384e81b8fd1a5e76fc
BLAKE2b-256 checksum
How to use checksums
8b0472ebde0a7f7ce0d710368a490710e9ad268c35e90ce903ee53e8c69f395e
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

0.6.0

2 release files

This release

0.5.0 This release

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