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).
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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_guitars-2.2.0.tar.gz.
File metadata
- Download URL: django_guitars-2.2.0.tar.gz
- Upload date:
- Size: 231.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5754fcae7eefd86423e19fd87b4892fac1fba27cbaacabb96d7c08a91748adac
|
|
| MD5 |
a3a8015b9364cbe46a787f2a341349c0
|
|
| BLAKE2b-256 |
529512e3307c727f857694c04d0099e1ad77948559c55e8fead6cb9f6f00bf6f
|
Provenance
The following attestation bundles were made for django_guitars-2.2.0.tar.gz:
Publisher:
release.yml on Behnam-RK/django-guitars
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_guitars-2.2.0.tar.gz -
Subject digest:
5754fcae7eefd86423e19fd87b4892fac1fba27cbaacabb96d7c08a91748adac - Sigstore transparency entry: 2514637970
- Sigstore integration time:
-
Permalink:
Behnam-RK/django-guitars@6ede61b5a494e214b80dde4f682dbf67517ffb5a -
Branch / Tag:
refs/tags/v2.2.0 - Owner: https://github.com/Behnam-RK
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6ede61b5a494e214b80dde4f682dbf67517ffb5a -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file django_guitars-2.2.0-py3-none-any.whl.
File metadata
- Download URL: django_guitars-2.2.0-py3-none-any.whl
- Upload date:
- Size: 105.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bacbf7f751b9656c81806353f50d3e2a6a07a3ba58f6dc19d6eb33ecddb4c398
|
|
| MD5 |
e665b98ad22fb1e36820e92543d8a303
|
|
| BLAKE2b-256 |
2f7d28d580977832e36e8c4e98fbbaa4c0603c8ba87eee44092ee0279c1ccdf5
|
Provenance
The following attestation bundles were made for django_guitars-2.2.0-py3-none-any.whl:
Publisher:
release.yml on Behnam-RK/django-guitars
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_guitars-2.2.0-py3-none-any.whl -
Subject digest:
bacbf7f751b9656c81806353f50d3e2a6a07a3ba58f6dc19d6eb33ecddb4c398 - Sigstore transparency entry: 2514637981
- Sigstore integration time:
-
Permalink:
Behnam-RK/django-guitars@6ede61b5a494e214b80dde4f682dbf67517ffb5a -
Branch / Tag:
refs/tags/v2.2.0 - Owner: https://github.com/Behnam-RK
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6ede61b5a494e214b80dde4f682dbf67517ffb5a -
Trigger Event:
workflow_dispatch
-
Statement type: