django-guitars
🎸 Django object-metadata the database enforces — not your .save() method.
Most Django soft-delete and timestamp libraries live in Python: a signal here, a save() override there. It holds up right until a bulk_update, a raw UPDATE, or a queryset.delete() strolls straight past your code — and leaves the metadata lying.
django-guitars pushes that work down into PostgreSQL itself — rules and triggers, not signals. So _created_at/_updated_at/_deleted_at stay honest no matter how a row gets touched: ORM, bulk, raw SQL, all of it. The database keeps score; you just write models. Use only the pieces you need.
Requirements
Python ≥ 3.10 · Django 5.0–6.0 (uses db_default; CI samples 5.0/5.2/6.0 against Python 3.10/3.12/3.14) · PostgreSQL ≥ 14, currently the only supported backend since the soft-delete rule and _updated_at trigger live in the database itself (CI verifies 14 and 18) — a model your own database router sends elsewhere is skipped with a note rather than handed DDL that backend cannot run.
Status: the public API — base models, managers, the
guitars.sqlnames generated migrations depend on, andGUITARS_*settings — is stable since 1.0.0; breaking changes now require a major version. SeeCHANGELOG.md.
Installation
pip install django-guitars
# or, for psycopg[c] built against your system libpq (psycopg's own production recommendation):
pip install django-guitars[psycopg]
INSTALLED_APPS = [
# ...
"guitars",
]
Where to find what
| If you want to… | Read |
|---|---|
| pick a base model | Pick your instrument, below |
understand soft deletion, cascades and hard_delete |
docs/soft-deletion.md |
| scope rows to a tenant | docs/tenancy.md |
| use multi-table inheritance | docs/mti.md |
| know how the triggers, rules and policies get into your database | docs/migrations.md |
look up a setting, command flag, or the frozen guitars.sql names |
docs/api-reference.md |
| know why something was built this way | docs/adr/ |
Pick your instrument
The base models are named after string instruments, fewest strings to most — and the strings are the feature ladder (du = two, se = three in Persian; tar = "string"; a guitar has six): TarModel (.update()/.aupdate(), cached-property invalidation, no columns) → DutarModel (+ DB-managed _created_at/_updated_at, app_label()/model_name()/class_name()) → SetarModel (+ PostgreSQL soft deletion — the one to reach for by default) → GuitarModel (+ multi-tenancy: a tenant FK, tenant-scoped managers, an RLS policy — the full kit). Each capability is also a standalone mixin in guitars.models: UpdatableModel, HasCachedPropertyModel, DatedModel, SoftDeletableModel.
⚠️ Renamed in 1.0.0 — 0.7's
DutarModel→TarModel,SetarModel→DutarModel,GuitarModel→SetarModel(behaviour-identical);GuitarModelnow means "SetarModel+ tenancy". SeeCHANGELOG.md.
from django.db import models
from guitars.models import SetarModel
class Article(SetarModel):
title = models.CharField(max_length=200)
Quick taste
article.update(title="New title") # set fields + save (only changed fields)
article.delete() # soft delete: sets _deleted_at, row stays
Article.objects.all() # live rows only
Article._archives.all() # soft-deleted rows only
article.hard_delete() # actually gone, CASCADE children too
⚠️ Required setup. The soft-delete rule and
_updated_attrigger live in a migration generated bymakeguitarmigrations— by defaultmakemigrationsgenerates it for you. Until it's created and youmigrate,.delete()permanently deletes the row. Seedocs/migrations.md.
Multi-tenancy adds a scope requirement on top:
from guitars.tenancy import tenant, tenancy_bypassed
with tenant(org=acme):
Invoice.objects.all() # acme's invoices only
Invoice.objects.all() # TenantScopeMissing — no scope, no rows
with tenancy_bypassed(): # the one explicit cross-tenant path
Invoice.objects.count()
Full detail — settings, rollout onto a populated database, auditing, connection pooling — is in docs/tenancy.md.
guitars.signals.DisableSignals() temporarily disconnects Django's signals — with DisableSignals(): instance.save() fires nothing, handy for bulk imports or silent saves.
Development
Requires uv and Docker (for PostgreSQL).
uv sync # install dependencies + the package (editable)
docker compose up -d # start PostgreSQL (skip if you already run one on :4455)
uv run pytest # run the test suite
The suite defines concrete models in tests/testapp (the shipped package is abstract-only) and runs against a real PostgreSQL database as a deliberately non-superuser role, since a superuser bypasses RLS unconditionally. An old checkout needs docker compose down -v && docker compose up -d --wait once. See CLAUDE.md for the full command reference and scripts/README.md for releasing.
License
MIT © 2026 Behnam RK
Metadata
Release files for django-guitars 2.21.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 | |
|---|---|---|---|
| django_guitars-2.21.0.tar.gz | 617.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_guitars-2.21.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 828.7 kB
Release files / django_guitars-2.21.0.tar.gz
| Download URL | django_guitars-2.21.0.tar.gz |
|---|---|
| Size | 617.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ba68166a2876a2889c254060cba05d6c26a45393231b0b860f605b335f6e8272
|
|
BLAKE2b-256 checksum How to use checksums |
d8bc62a994887c76dc1142eb60bc47def711c28230f4fd22a388efa863c25be7
|
| 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 9, 2026.
Transparency logRelease files / django_guitars-2.21.0-py3-none-any.whl
| Download URL | django_guitars-2.21.0-py3-none-any.whl |
|---|---|
| Size | 211.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a8ccf3fc41a25bdb61bf8f261513a09d7d7a3cc26097e013d3e557b36709c692
|
|
BLAKE2b-256 checksum How to use checksums |
545b954b3e73374968457a4359c2c9c63fa88d86bca5ab0a3b93a7a24a958b84
|
| 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 9, 2026.
Transparency log