Metadata-Version: 2.4
Name: django-guitars
Version: 0.1.0
Summary: A reusable Django app for cataloging guitars.
Project-URL: Homepage, https://github.com/Behnam-RK/django-guitars
Project-URL: Repository, https://github.com/Behnam-RK/django-guitars
Project-URL: Issues, https://github.com/Behnam-RK/django-guitars/issues
Project-URL: Changelog, https://github.com/Behnam-RK/django-guitars/blob/main/CHANGELOG.md
Author-email: Behnam RK <behnam.rk47@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: django,guitars,reusable-app
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
Classifier: Framework :: Django :: 5.2
Classifier: Framework :: Django :: 6.0
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: django>=5.0
Description-Content-Type: text/markdown

# django-guitars

> Django Reinhardt was one of the greatest guitarists who ever lived — so
> **django-guitars** hands *the other* Django, the web framework, a better set
> of guitars.

A small, focused collection of **reusable Django utilities**: opinionated base
models, PostgreSQL-enforced soft deletion, and the helpers that make them work.
Use only the pieces you need.

[![PyPI version](https://img.shields.io/pypi/v/django-guitars.svg)](https://pypi.org/project/django-guitars/)
[![Python versions](https://img.shields.io/pypi/pyversions/django-guitars.svg)](https://pypi.org/project/django-guitars/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

## Requirements

- **Python** ≥ 3.10
- **Django** ≥ 5.0 — uses `db_default`
- **PostgreSQL** — soft deletion and the `updated_at` refresh are enforced by
  PostgreSQL rules and triggers

## Installation

```bash
pip install django-guitars
```

Add the app to your settings:

```python
INSTALLED_APPS = [
    # ...
    "guitars",
]
```

## What's inside

### Base models

| Base | What you get |
| --- | --- |
| `SetarModel` | `_created_at` / `_updated_at` (DB-default `NOW()`; `_updated_at` kept current by a PostgreSQL statement trigger, so it's accurate even for bulk/raw updates), `.update()` / `.aupdate()`, cached-property invalidation on `refresh_from_db()`, and `app_label()` / `model_name()` / `class_name()` helpers |
| `GuitarModel` | Everything in `SetarModel` **plus** soft deletion (it is `SetarModel` + `SoftDeletableModel`) |
| `SoftDeletableModel` | Soft deletion on its own |

```python
from django.db import models

from guitars.models import GuitarModel


class Article(GuitarModel):
    title = models.CharField(max_length=200)
```

The `.update()` helper sets attributes and saves in one call:

```python
article.update(title="New title")        # set fields + save (only changed fields)
article.update(title="x", _save=False)    # change in memory only, no DB write
await article.aupdate(title="async")      # async variant
```

### Soft deletion

For models inheriting `SoftDeletableModel` (or `GuitarModel`), `.delete()`
becomes a **soft delete**: the row stays and `_deleted_at` is set. Because this
is enforced by a PostgreSQL rule, it holds even for queryset bulk deletes and
raw SQL. Three managers expose the data:

```python
Article.objects.all()         # live rows only (the default manager)
Article._archives.all()       # soft-deleted rows only
Article._all_objects.all()    # everything

article.delete()              # soft delete — sets _deleted_at
article.is_deleted            # True
article.is_alive              # False

# Permanently remove rows (and CASCADE-related rows), bypassing the rule:
Article._all_objects.filter(...).hard_delete()
```

Soft-deleting a row also soft-deletes rows related by `on_delete=CASCADE`.

> ⚠️ **Required setup.** The soft-delete rule (and the `updated_at` trigger)
> live in a migration generated by [`makeguitarmigrations`](#makeguitarmigrations).
> Until you run that command and `migrate`, **`.delete()` permanently deletes the
> row** — the protection isn't active yet. Re-run it whenever you add or change a
> model that uses these bases.

### `makeguitarmigrations`

`makemigrations` does **not** create the triggers and rules — they live in
separate migrations generated by this command, and this step is **required** for
soft deletion and the `updated_at` trigger to work. After your usual
`makemigrations`, run:

```bash
python manage.py makeguitarmigrations
```

It scans your **first-party** apps for models with `_updated_at` / `_deleted_at`
and writes the matching trigger/rule migrations. Tell it which apps are yours:

```python
# settings.py
LOCAL_APPS = ["blog", "shop"]      # apps the command scans

# Optional: which app hosts the shared trigger-function migration.
# Defaults to LOCAL_APPS[0].
# TRIGGER_FUNCTION_APP = "blog"
```

Use `--check` in CI to fail when advanced migrations are missing:

```bash
python manage.py makeguitarmigrations --check
```

### `DisableSignals`

A context manager that temporarily disconnects Django signals — handy for bulk
imports or silent saves:

```python
from django.db.models.signals import post_save

from guitars.signals import DisableSignals

with DisableSignals():                    # all default signals
    instance.save()                       # nothing fires

with DisableSignals(signals=[post_save]): # only the listed signals
    instance.save()
```

## Development

Requires [uv](https://docs.astral.sh/uv/) and Docker (for PostgreSQL).

```bash
uv sync                  # install dependencies + the package (editable)
docker compose up -d     # start PostgreSQL (skip if you already run one on :5432)
uv run pytest            # run the test suite
uv run pytest --cov=guitars --cov-report=term-missing
```

The test suite defines concrete models in `tests/testapp` (the shipped package
is abstract-only) and runs against a real PostgreSQL database so the rules and
triggers are actually exercised.

## License

[MIT](LICENSE) © 2026 Behnam RK

## Why "guitars"?

Django Reinhardt was a legendary jazz guitarist. This package gives the *other*
Django — the web framework — some better guitars: a grab-bag of utilities that
make everyday Django a little nicer. The base models keep the theme going with
`SetarModel` and `GuitarModel`.
