django-joist 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_joist-0.1.0/.github/workflows/release.yml +60 -0
- django_joist-0.1.0/.github/workflows/tests.yml +23 -0
- django_joist-0.1.0/.gitignore +7 -0
- django_joist-0.1.0/LICENSE +55 -0
- django_joist-0.1.0/PKG-INFO +230 -0
- django_joist-0.1.0/README.md +198 -0
- django_joist-0.1.0/pyproject.toml +60 -0
- django_joist-0.1.0/src/django_joist/__init__.py +54 -0
- django_joist-0.1.0/src/django_joist/apps.py +12 -0
- django_joist-0.1.0/src/django_joist/cache.py +153 -0
- django_joist-0.1.0/src/django_joist/cli.py +46 -0
- django_joist-0.1.0/src/django_joist/conf.py +213 -0
- django_joist-0.1.0/src/django_joist/diff/__init__.py +6 -0
- django_joist-0.1.0/src/django_joist/diff/baseline.py +113 -0
- django_joist-0.1.0/src/django_joist/diff/differ.py +114 -0
- django_joist-0.1.0/src/django_joist/doctor/__init__.py +26 -0
- django_joist-0.1.0/src/django_joist/doctor/collection.py +38 -0
- django_joist-0.1.0/src/django_joist/doctor/enums.py +44 -0
- django_joist-0.1.0/src/django_joist/doctor/finding.py +44 -0
- django_joist-0.1.0/src/django_joist/doctor/formatters.py +92 -0
- django_joist-0.1.0/src/django_joist/doctor/registry.py +98 -0
- django_joist-0.1.0/src/django_joist/doctor/report.py +158 -0
- django_joist-0.1.0/src/django_joist/doctor/rules/__init__.py +40 -0
- django_joist-0.1.0/src/django_joist/doctor/rules/base.py +54 -0
- django_joist-0.1.0/src/django_joist/doctor/rules/indexes.py +315 -0
- django_joist-0.1.0/src/django_joist/doctor/rules/integrity.py +404 -0
- django_joist-0.1.0/src/django_joist/doctor/rules/types.py +99 -0
- django_joist-0.1.0/src/django_joist/doctor/runner.py +53 -0
- django_joist-0.1.0/src/django_joist/export/__init__.py +10 -0
- django_joist-0.1.0/src/django_joist/export/annotator.py +105 -0
- django_joist-0.1.0/src/django_joist/export/builder.py +190 -0
- django_joist-0.1.0/src/django_joist/export/exporter.py +94 -0
- django_joist-0.1.0/src/django_joist/export/generators/__init__.py +1 -0
- django_joist-0.1.0/src/django_joist/export/generators/csv.py +66 -0
- django_joist-0.1.0/src/django_joist/export/generators/dbml.py +124 -0
- django_joist-0.1.0/src/django_joist/export/generators/json.py +25 -0
- django_joist-0.1.0/src/django_joist/export/generators/llm.py +89 -0
- django_joist-0.1.0/src/django_joist/export/generators/markdown.py +115 -0
- django_joist-0.1.0/src/django_joist/export/generators/mermaid.py +84 -0
- django_joist-0.1.0/src/django_joist/export/transforms.py +97 -0
- django_joist-0.1.0/src/django_joist/introspection/__init__.py +14 -0
- django_joist-0.1.0/src/django_joist/introspection/builder.py +227 -0
- django_joist-0.1.0/src/django_joist/introspection/data.py +45 -0
- django_joist-0.1.0/src/django_joist/introspection/fallback.py +140 -0
- django_joist-0.1.0/src/django_joist/introspection/native.py +248 -0
- django_joist-0.1.0/src/django_joist/introspection/serializer.py +62 -0
- django_joist-0.1.0/src/django_joist/management/__init__.py +0 -0
- django_joist-0.1.0/src/django_joist/management/commands/__init__.py +0 -0
- django_joist-0.1.0/src/django_joist/management/commands/joist_diff.py +148 -0
- django_joist-0.1.0/src/django_joist/management/commands/joist_doctor.py +110 -0
- django_joist-0.1.0/src/django_joist/management/commands/joist_export.py +159 -0
- django_joist-0.1.0/src/django_joist/management/commands/joist_open.py +56 -0
- django_joist-0.1.0/src/django_joist/management/commands/joist_rebuild.py +45 -0
- django_joist-0.1.0/src/django_joist/management/commands/joist_show.py +61 -0
- django_joist-0.1.0/src/django_joist/security.py +85 -0
- django_joist-0.1.0/src/django_joist/selection.py +37 -0
- django_joist-0.1.0/src/django_joist/signals.py +86 -0
- django_joist-0.1.0/src/django_joist/static/joist/css/joist.css +679 -0
- django_joist-0.1.0/src/django_joist/static/joist/fonts/IBM-Plex.LICENSE +93 -0
- django_joist-0.1.0/src/django_joist/static/joist/fonts/ibm-plex-mono-400.woff2 +0 -0
- django_joist-0.1.0/src/django_joist/static/joist/fonts/ibm-plex-mono-500.woff2 +0 -0
- django_joist-0.1.0/src/django_joist/static/joist/fonts/ibm-plex-mono-600.woff2 +0 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/diff-view.js +78 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/doctor-view.js +89 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/export-menu.js +95 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/export-request.js +38 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/focus-combobox.js +0 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/joist.js +1513 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/label-face-gate.js +71 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/mermaid-definition.js +145 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/selection.js +128 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/table-match.js +41 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/type-labels.js +101 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/url-state.js +36 -0
- django_joist-0.1.0/src/django_joist/static/joist/js/viewport.js +60 -0
- django_joist-0.1.0/src/django_joist/static/joist/vendor/mermaid.LICENSE +24 -0
- django_joist-0.1.0/src/django_joist/static/joist/vendor/mermaid.min.js +3587 -0
- django_joist-0.1.0/src/django_joist/templates/joist/index.html +153 -0
- django_joist-0.1.0/src/django_joist/theme.py +150 -0
- django_joist-0.1.0/src/django_joist/urls.py +27 -0
- django_joist-0.1.0/src/django_joist/views.py +232 -0
- django_joist-0.1.0/tests/__init__.py +0 -0
- django_joist-0.1.0/tests/conftest.py +74 -0
- django_joist-0.1.0/tests/settings.py +51 -0
- django_joist-0.1.0/tests/test_diff.py +168 -0
- django_joist-0.1.0/tests/test_doctor.py +525 -0
- django_joist-0.1.0/tests/test_export.py +471 -0
- django_joist-0.1.0/tests/test_facade.py +81 -0
- django_joist-0.1.0/tests/test_foundation.py +99 -0
- django_joist-0.1.0/tests/test_http.py +221 -0
- django_joist-0.1.0/tests/test_http_helpers.py +6 -0
- django_joist-0.1.0/tests/test_static.py +109 -0
- django_joist-0.1.0/tests/testapp/__init__.py +0 -0
- django_joist-0.1.0/tests/testapp/models.py +72 -0
- django_joist-0.1.0/tests/urls.py +3 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
python: ["3.13", "3.14"]
|
|
18
|
+
django: ["~=5.2.0", "~=6.0.0", "~=6.1.0"]
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python }}
|
|
24
|
+
- run: pip install "Django${{ matrix.django }}" pytest pytest-django
|
|
25
|
+
- run: pip install -e .
|
|
26
|
+
- run: python -m pytest -q
|
|
27
|
+
|
|
28
|
+
build:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v4
|
|
32
|
+
- uses: actions/setup-python@v5
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.14"
|
|
35
|
+
- name: Check tag matches package version
|
|
36
|
+
run: >-
|
|
37
|
+
python -c "import os, tomllib;
|
|
38
|
+
version = tomllib.load(open('pyproject.toml', 'rb'))['project']['version'];
|
|
39
|
+
assert os.environ['GITHUB_REF_NAME'] == f'v{version}',
|
|
40
|
+
f\"tag {os.environ['GITHUB_REF_NAME']} does not match version {version}\""
|
|
41
|
+
- run: python -m pip install build twine
|
|
42
|
+
- run: python -m build
|
|
43
|
+
- run: python -m twine check dist/*
|
|
44
|
+
- uses: actions/upload-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
name: python-package-distributions
|
|
47
|
+
path: dist/
|
|
48
|
+
|
|
49
|
+
publish:
|
|
50
|
+
needs: [test, build]
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
environment: pypi
|
|
53
|
+
permissions:
|
|
54
|
+
id-token: write
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/download-artifact@v4
|
|
57
|
+
with:
|
|
58
|
+
name: python-package-distributions
|
|
59
|
+
path: dist/
|
|
60
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python: ["3.13", "3.14"]
|
|
15
|
+
django: ["~=5.2.0", "~=6.0.0", "~=6.1.0"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python }}
|
|
21
|
+
- run: pip install "Django${{ matrix.django }}" pytest pytest-django
|
|
22
|
+
- run: pip install -e .
|
|
23
|
+
- run: python -m pytest -q
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Simone Fornasiere
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Portions of the front end (src/django_joist/static/joist/) are adapted from
|
|
26
|
+
Laravel Truss (https://github.com/albertoarena/laravel-truss), which is
|
|
27
|
+
distributed under the MIT License:
|
|
28
|
+
|
|
29
|
+
MIT License
|
|
30
|
+
|
|
31
|
+
Copyright (c) 2026 Alberto Arena
|
|
32
|
+
|
|
33
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
34
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
35
|
+
in the Software without restriction, including without limitation the rights
|
|
36
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
37
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
38
|
+
furnished to do so, subject to the following conditions:
|
|
39
|
+
|
|
40
|
+
The above copyright notice and this permission notice shall be included in all
|
|
41
|
+
copies or substantial portions of the Software.
|
|
42
|
+
|
|
43
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
44
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
45
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
46
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
47
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
48
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
49
|
+
SOFTWARE.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
The vendored Mermaid library and the IBM Plex Mono font carry their own
|
|
54
|
+
licenses, reproduced under src/django_joist/static/joist/ (mermaid.LICENSE and
|
|
55
|
+
IBM-Plex.LICENSE).
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: django-joist
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A live database structure viewer for Django. Renders your schema (never data) as a zoomable ER diagram, with exports, a schema doctor and a schema diff.
|
|
5
|
+
Project-URL: Homepage, https://github.com/sforna/django-joist
|
|
6
|
+
Project-URL: Issues, https://github.com/sforna/django-joist/issues
|
|
7
|
+
Author-email: Simone Fornasiere <s.fornasiere@gmail.com>
|
|
8
|
+
License-Expression: MIT AND OFL-1.1
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
License-File: src/django_joist/static/joist/fonts/IBM-Plex.LICENSE
|
|
11
|
+
License-File: src/django_joist/static/joist/vendor/mermaid.LICENSE
|
|
12
|
+
Keywords: data-dictionary,database,database-diagram,dbml,django,er-diagram,erd,introspection,mermaid,schema
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Web Environment
|
|
15
|
+
Classifier: Framework :: Django
|
|
16
|
+
Classifier: Framework :: Django :: 5.2
|
|
17
|
+
Classifier: Framework :: Django :: 6.0
|
|
18
|
+
Classifier: Framework :: Django :: 6.1
|
|
19
|
+
Classifier: Intended Audience :: Developers
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Database
|
|
25
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
26
|
+
Requires-Python: >=3.13
|
|
27
|
+
Requires-Dist: django>=5.2
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-django; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# django-joist
|
|
34
|
+
|
|
35
|
+
**A live database structure viewer for Django.** Joist introspects your live
|
|
36
|
+
schema and renders it as a scrollable, zoomable ER diagram right inside your
|
|
37
|
+
app, so you can see how tables actually connect without opening a DB client.
|
|
38
|
+
It reads **structure only** — tables, columns, keys, indexes, foreign keys;
|
|
39
|
+
row data is never queried or exposed.
|
|
40
|
+
|
|
41
|
+
> django-joist is a Django port of the concept implemented by
|
|
42
|
+
> [Laravel Truss](https://github.com/albertoarena/laravel-truss) (MIT,
|
|
43
|
+
> Alberto Arena), which inspired it. The name, branding and Django wiring are
|
|
44
|
+
> original; selected front-end code is adapted under MIT with attribution.
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- Live ER diagram of your database, rendered with Mermaid — self-hosted, no
|
|
49
|
+
CDN, no build step.
|
|
50
|
+
- Focus mode: a table and its foreign-key neighbours, with a searchable
|
|
51
|
+
picker that stays usable on schemas with hundreds of tables.
|
|
52
|
+
- Filter by table name; toggle native DB types against Django-style labels.
|
|
53
|
+
- Map-style pan and zoom, auto-fit with a legible floor, plus a Fit button.
|
|
54
|
+
- Exports: PNG/SVG from the browser; DBML, JSON, CSV, Markdown data
|
|
55
|
+
dictionary, Mermaid, or a token-trimmed `llm` format — from the dashboard
|
|
56
|
+
or from CI with `manage.py joist_export`. Deterministic: the same schema
|
|
57
|
+
always produces the same bytes, so `joist_export --check` fails the build
|
|
58
|
+
when a committed schema file drifts.
|
|
59
|
+
- Feed your real structure to a coding agent as grounding context:
|
|
60
|
+
annotations for business meaning, `--compact` to trim tokens, `--focus` to
|
|
61
|
+
narrow to one neighbourhood. Only the schema is read — row data is never touched.
|
|
62
|
+
- Schema diff: what changed since the last migration, in the dashboard's
|
|
63
|
+
Changes panel and via `manage.py joist_diff`.
|
|
64
|
+
- Schema doctor: `manage.py joist_doctor` reviews the structure for problems
|
|
65
|
+
(missing primary keys, unindexed foreign keys, duplicate indexes, risky
|
|
66
|
+
types) — deterministic, structure-only, safe in CI, with `--fail-on` exit
|
|
67
|
+
codes; the same findings power the dashboard's Health panel.
|
|
68
|
+
- Multiple databases: list aliases under `JOIST["connections"]` and switch
|
|
69
|
+
diagrams from the toolbar, each scoped to its own database.
|
|
70
|
+
- Light/dark "blueprint" theme, or bring your own: a handful of semantic
|
|
71
|
+
colour/font knobs re-skin the whole dashboard, served as a same-origin
|
|
72
|
+
stylesheet (CSP-safe, no inline styles).
|
|
73
|
+
- Rebuilds itself: the cached snapshot refreshes after every `migrate` and
|
|
74
|
+
degrades gracefully when the cache store is unreachable.
|
|
75
|
+
- Production-safe by design: routes answer 404 unless enabled, denials never
|
|
76
|
+
confirm the dashboard exists, and excluded tables are filtered server-side
|
|
77
|
+
so their structure never reaches the browser.
|
|
78
|
+
|
|
79
|
+
## Installation
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install django-joist
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Add the app and mount the dashboard:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
# settings.py
|
|
89
|
+
INSTALLED_APPS = [
|
|
90
|
+
...,
|
|
91
|
+
"django_joist",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
# urls.py
|
|
95
|
+
from django.urls import include, path
|
|
96
|
+
|
|
97
|
+
urlpatterns = [
|
|
98
|
+
...,
|
|
99
|
+
path("joist/", include("django_joist.urls")),
|
|
100
|
+
]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Requires Python 3.13+ and Django 5.2+. No migrations, no models, no
|
|
104
|
+
`collectstatic` needed (assets are served by the package, gated with the
|
|
105
|
+
rest of the dashboard). The dashboard HTML is a template shipped inside the
|
|
106
|
+
app, so your `TEMPLATES` must keep `"APP_DIRS": True` for `django_joist`
|
|
107
|
+
(the default in `django-admin startproject`); if you disabled it, add the
|
|
108
|
+
package's template directory to `DIRS`.
|
|
109
|
+
|
|
110
|
+
## Quick start
|
|
111
|
+
|
|
112
|
+
By default Joist is enabled when `DEBUG = True`. Start your dev server and
|
|
113
|
+
visit `/joist/`.
|
|
114
|
+
|
|
115
|
+
To run Joist gated on staging or production, set both switches:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
JOIST = {
|
|
119
|
+
"enabled": True,
|
|
120
|
+
"authorization": {
|
|
121
|
+
"allowed_emails": ["ada@example.com", "grace@example.com"],
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Outside `DEBUG` the shipped default authorizer admits only allow-listed
|
|
127
|
+
emails and **fails closed** on an empty list. To authorize by role instead,
|
|
128
|
+
point `authorization.callable` at your own `request -> bool` (a callable or a
|
|
129
|
+
dotted path); it fully replaces the default. Denials always return **404**.
|
|
130
|
+
|
|
131
|
+
> Your project's `AuthenticationMiddleware` must run for the mounted URLs
|
|
132
|
+
> (the normal case). Joist's own guard is part of the views and cannot be
|
|
133
|
+
> configured away.
|
|
134
|
+
|
|
135
|
+
## Command line
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
python manage.py joist_show # the structure as a terminal table
|
|
139
|
+
python manage.py joist_open # print/open the dashboard URL
|
|
140
|
+
python manage.py joist_rebuild # rebuild the cached snapshot (CI)
|
|
141
|
+
python manage.py joist_diff # what changed since the last migration
|
|
142
|
+
python manage.py joist_doctor # structure review; exits non-zero on findings
|
|
143
|
+
python manage.py joist_export # DBML to stdout by default
|
|
144
|
+
python manage.py joist_export --format=json --output=docs/schema.json
|
|
145
|
+
python manage.py joist_export --format=dbml --output=docs/schema.dbml --check # CI drift check
|
|
146
|
+
python manage.py joist_export --tables=orders,order_lines --focus=orders --depth=1 --compact
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`joist_doctor --format=json` and `--fail-on=warning` make it a CI gate;
|
|
150
|
+
`joist_export --check` regenerates, compares, writes nothing and exits `1` on
|
|
151
|
+
drift (`2` on usage errors), so a migration that changes the schema without
|
|
152
|
+
refreshing the committed file fails the build.
|
|
153
|
+
|
|
154
|
+
## As AI context
|
|
155
|
+
|
|
156
|
+
The same export doubles as grounding context for a coding agent, so it stops
|
|
157
|
+
inventing columns:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
python manage.py joist_export --format=llm # dense, token-trimmed plaintext
|
|
161
|
+
python manage.py joist_export --compact # drop defaults and non-unique indexes
|
|
162
|
+
python manage.py joist_export --focus=orders --depth=1
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Annotations add the business meaning a type cannot — declare them in
|
|
166
|
+
settings under `JOIST["annotations"]` (per-table, per-column, global notes)
|
|
167
|
+
or let Joist read native database comments (Postgres/MySQL) by keeping
|
|
168
|
+
`"database"` in `annotations["source"]`. The `llm` format and Markdown/DBML
|
|
169
|
+
all render annotations; `--no-annotations` strips them.
|
|
170
|
+
|
|
171
|
+
Programmatically, the builder is immutable and mirrors the CLI byte-for-byte:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from django_joist import snapshot
|
|
175
|
+
|
|
176
|
+
text = (
|
|
177
|
+
snapshot()
|
|
178
|
+
.only(["orders", "order_lines"])
|
|
179
|
+
.focus("orders", depth=1)
|
|
180
|
+
.compact()
|
|
181
|
+
.to_dbml()
|
|
182
|
+
)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Configuration
|
|
186
|
+
|
|
187
|
+
Everything lives in one `JOIST` dict; unset keys keep their defaults (nested
|
|
188
|
+
dicts merge key-by-key). The common knobs:
|
|
189
|
+
|
|
190
|
+
| Key | Purpose |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `enabled` | Master switch; defaults to `DEBUG`. Off = every route 404s |
|
|
193
|
+
| `connections` | Visualizable database aliases + per-alias overrides, e.g. `{"analytics": {"excluded_tables": [...]}}`; empty = just `default` |
|
|
194
|
+
| `excluded_tables` | Hidden server-side (never sent to the browser). Django bookkeeping tables are pre-listed |
|
|
195
|
+
| `authorization.callable` / `.allowed_emails` | Who may view outside `DEBUG`; a custom callable replaces the email allow-list |
|
|
196
|
+
| `cache.ttl` / `cache.alias` | Snapshot cache (seconds, `<= 0` = forever) and which `CACHES` entry to use |
|
|
197
|
+
| `diagram.type_labels` | `native` (default) or `django` short labels |
|
|
198
|
+
| `diagram.mermaid_url` | Point at your own copy/CDN; default self-hosts from the gated asset route |
|
|
199
|
+
| `focus.default_depth` | FK-neighbour hops shown on focus |
|
|
200
|
+
| `large_schema.warn_above` | Table count that triggers the "use focus/filter" hint |
|
|
201
|
+
| `diff.enabled` / `diff.dir` | Schema-diff switch and where the structure-only baseline lives (default `BASE_DIR/var/joist`) |
|
|
202
|
+
| `doctor.*` | Preset, per-rule severity/enable, ignore patterns, `fail_on`, dashboard panel switches |
|
|
203
|
+
| `annotations.*` | Global notes, per-table/per-column meaning, comment sources |
|
|
204
|
+
| `theme.*` | Semantic colour/font knobs for both light and dark |
|
|
205
|
+
| `base_url` | Used by `joist_open` to build a browsable URL |
|
|
206
|
+
|
|
207
|
+
The baseline file under `diff.dir` is the only thing Joist writes to disk;
|
|
208
|
+
it is derived and safe to delete — worth gitignoring alongside `var/`.
|
|
209
|
+
|
|
210
|
+
## The no-data promise
|
|
211
|
+
|
|
212
|
+
Structure is the `CREATE TABLE` definition: tables, columns (name, native
|
|
213
|
+
type, nullability, defaults), primary keys, indexes, foreign keys with
|
|
214
|
+
referential actions, and native comments. Row contents are never queried —
|
|
215
|
+
not by the dashboard, the API, the CLI, or the doctor. Config
|
|
216
|
+
`excluded_tables` are stripped server-side, so an excluded table never
|
|
217
|
+
reaches the client, the diff, or an export.
|
|
218
|
+
|
|
219
|
+
## Development
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
pip install -e ".[dev]"
|
|
223
|
+
python -m pytest
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
MIT. See [LICENSE](LICENSE) (includes the attribution to Laravel Truss).
|
|
229
|
+
Vendored assets carry their own licenses (Mermaid MIT, IBM Plex Mono SIL OFL)
|
|
230
|
+
under `src/django_joist/static/joist/`.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# django-joist
|
|
2
|
+
|
|
3
|
+
**A live database structure viewer for Django.** Joist introspects your live
|
|
4
|
+
schema and renders it as a scrollable, zoomable ER diagram right inside your
|
|
5
|
+
app, so you can see how tables actually connect without opening a DB client.
|
|
6
|
+
It reads **structure only** — tables, columns, keys, indexes, foreign keys;
|
|
7
|
+
row data is never queried or exposed.
|
|
8
|
+
|
|
9
|
+
> django-joist is a Django port of the concept implemented by
|
|
10
|
+
> [Laravel Truss](https://github.com/albertoarena/laravel-truss) (MIT,
|
|
11
|
+
> Alberto Arena), which inspired it. The name, branding and Django wiring are
|
|
12
|
+
> original; selected front-end code is adapted under MIT with attribution.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- Live ER diagram of your database, rendered with Mermaid — self-hosted, no
|
|
17
|
+
CDN, no build step.
|
|
18
|
+
- Focus mode: a table and its foreign-key neighbours, with a searchable
|
|
19
|
+
picker that stays usable on schemas with hundreds of tables.
|
|
20
|
+
- Filter by table name; toggle native DB types against Django-style labels.
|
|
21
|
+
- Map-style pan and zoom, auto-fit with a legible floor, plus a Fit button.
|
|
22
|
+
- Exports: PNG/SVG from the browser; DBML, JSON, CSV, Markdown data
|
|
23
|
+
dictionary, Mermaid, or a token-trimmed `llm` format — from the dashboard
|
|
24
|
+
or from CI with `manage.py joist_export`. Deterministic: the same schema
|
|
25
|
+
always produces the same bytes, so `joist_export --check` fails the build
|
|
26
|
+
when a committed schema file drifts.
|
|
27
|
+
- Feed your real structure to a coding agent as grounding context:
|
|
28
|
+
annotations for business meaning, `--compact` to trim tokens, `--focus` to
|
|
29
|
+
narrow to one neighbourhood. Only the schema is read — row data is never touched.
|
|
30
|
+
- Schema diff: what changed since the last migration, in the dashboard's
|
|
31
|
+
Changes panel and via `manage.py joist_diff`.
|
|
32
|
+
- Schema doctor: `manage.py joist_doctor` reviews the structure for problems
|
|
33
|
+
(missing primary keys, unindexed foreign keys, duplicate indexes, risky
|
|
34
|
+
types) — deterministic, structure-only, safe in CI, with `--fail-on` exit
|
|
35
|
+
codes; the same findings power the dashboard's Health panel.
|
|
36
|
+
- Multiple databases: list aliases under `JOIST["connections"]` and switch
|
|
37
|
+
diagrams from the toolbar, each scoped to its own database.
|
|
38
|
+
- Light/dark "blueprint" theme, or bring your own: a handful of semantic
|
|
39
|
+
colour/font knobs re-skin the whole dashboard, served as a same-origin
|
|
40
|
+
stylesheet (CSP-safe, no inline styles).
|
|
41
|
+
- Rebuilds itself: the cached snapshot refreshes after every `migrate` and
|
|
42
|
+
degrades gracefully when the cache store is unreachable.
|
|
43
|
+
- Production-safe by design: routes answer 404 unless enabled, denials never
|
|
44
|
+
confirm the dashboard exists, and excluded tables are filtered server-side
|
|
45
|
+
so their structure never reaches the browser.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install django-joist
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Add the app and mount the dashboard:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
# settings.py
|
|
57
|
+
INSTALLED_APPS = [
|
|
58
|
+
...,
|
|
59
|
+
"django_joist",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
# urls.py
|
|
63
|
+
from django.urls import include, path
|
|
64
|
+
|
|
65
|
+
urlpatterns = [
|
|
66
|
+
...,
|
|
67
|
+
path("joist/", include("django_joist.urls")),
|
|
68
|
+
]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Requires Python 3.13+ and Django 5.2+. No migrations, no models, no
|
|
72
|
+
`collectstatic` needed (assets are served by the package, gated with the
|
|
73
|
+
rest of the dashboard). The dashboard HTML is a template shipped inside the
|
|
74
|
+
app, so your `TEMPLATES` must keep `"APP_DIRS": True` for `django_joist`
|
|
75
|
+
(the default in `django-admin startproject`); if you disabled it, add the
|
|
76
|
+
package's template directory to `DIRS`.
|
|
77
|
+
|
|
78
|
+
## Quick start
|
|
79
|
+
|
|
80
|
+
By default Joist is enabled when `DEBUG = True`. Start your dev server and
|
|
81
|
+
visit `/joist/`.
|
|
82
|
+
|
|
83
|
+
To run Joist gated on staging or production, set both switches:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
JOIST = {
|
|
87
|
+
"enabled": True,
|
|
88
|
+
"authorization": {
|
|
89
|
+
"allowed_emails": ["ada@example.com", "grace@example.com"],
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Outside `DEBUG` the shipped default authorizer admits only allow-listed
|
|
95
|
+
emails and **fails closed** on an empty list. To authorize by role instead,
|
|
96
|
+
point `authorization.callable` at your own `request -> bool` (a callable or a
|
|
97
|
+
dotted path); it fully replaces the default. Denials always return **404**.
|
|
98
|
+
|
|
99
|
+
> Your project's `AuthenticationMiddleware` must run for the mounted URLs
|
|
100
|
+
> (the normal case). Joist's own guard is part of the views and cannot be
|
|
101
|
+
> configured away.
|
|
102
|
+
|
|
103
|
+
## Command line
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
python manage.py joist_show # the structure as a terminal table
|
|
107
|
+
python manage.py joist_open # print/open the dashboard URL
|
|
108
|
+
python manage.py joist_rebuild # rebuild the cached snapshot (CI)
|
|
109
|
+
python manage.py joist_diff # what changed since the last migration
|
|
110
|
+
python manage.py joist_doctor # structure review; exits non-zero on findings
|
|
111
|
+
python manage.py joist_export # DBML to stdout by default
|
|
112
|
+
python manage.py joist_export --format=json --output=docs/schema.json
|
|
113
|
+
python manage.py joist_export --format=dbml --output=docs/schema.dbml --check # CI drift check
|
|
114
|
+
python manage.py joist_export --tables=orders,order_lines --focus=orders --depth=1 --compact
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`joist_doctor --format=json` and `--fail-on=warning` make it a CI gate;
|
|
118
|
+
`joist_export --check` regenerates, compares, writes nothing and exits `1` on
|
|
119
|
+
drift (`2` on usage errors), so a migration that changes the schema without
|
|
120
|
+
refreshing the committed file fails the build.
|
|
121
|
+
|
|
122
|
+
## As AI context
|
|
123
|
+
|
|
124
|
+
The same export doubles as grounding context for a coding agent, so it stops
|
|
125
|
+
inventing columns:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
python manage.py joist_export --format=llm # dense, token-trimmed plaintext
|
|
129
|
+
python manage.py joist_export --compact # drop defaults and non-unique indexes
|
|
130
|
+
python manage.py joist_export --focus=orders --depth=1
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Annotations add the business meaning a type cannot — declare them in
|
|
134
|
+
settings under `JOIST["annotations"]` (per-table, per-column, global notes)
|
|
135
|
+
or let Joist read native database comments (Postgres/MySQL) by keeping
|
|
136
|
+
`"database"` in `annotations["source"]`. The `llm` format and Markdown/DBML
|
|
137
|
+
all render annotations; `--no-annotations` strips them.
|
|
138
|
+
|
|
139
|
+
Programmatically, the builder is immutable and mirrors the CLI byte-for-byte:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from django_joist import snapshot
|
|
143
|
+
|
|
144
|
+
text = (
|
|
145
|
+
snapshot()
|
|
146
|
+
.only(["orders", "order_lines"])
|
|
147
|
+
.focus("orders", depth=1)
|
|
148
|
+
.compact()
|
|
149
|
+
.to_dbml()
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Configuration
|
|
154
|
+
|
|
155
|
+
Everything lives in one `JOIST` dict; unset keys keep their defaults (nested
|
|
156
|
+
dicts merge key-by-key). The common knobs:
|
|
157
|
+
|
|
158
|
+
| Key | Purpose |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `enabled` | Master switch; defaults to `DEBUG`. Off = every route 404s |
|
|
161
|
+
| `connections` | Visualizable database aliases + per-alias overrides, e.g. `{"analytics": {"excluded_tables": [...]}}`; empty = just `default` |
|
|
162
|
+
| `excluded_tables` | Hidden server-side (never sent to the browser). Django bookkeeping tables are pre-listed |
|
|
163
|
+
| `authorization.callable` / `.allowed_emails` | Who may view outside `DEBUG`; a custom callable replaces the email allow-list |
|
|
164
|
+
| `cache.ttl` / `cache.alias` | Snapshot cache (seconds, `<= 0` = forever) and which `CACHES` entry to use |
|
|
165
|
+
| `diagram.type_labels` | `native` (default) or `django` short labels |
|
|
166
|
+
| `diagram.mermaid_url` | Point at your own copy/CDN; default self-hosts from the gated asset route |
|
|
167
|
+
| `focus.default_depth` | FK-neighbour hops shown on focus |
|
|
168
|
+
| `large_schema.warn_above` | Table count that triggers the "use focus/filter" hint |
|
|
169
|
+
| `diff.enabled` / `diff.dir` | Schema-diff switch and where the structure-only baseline lives (default `BASE_DIR/var/joist`) |
|
|
170
|
+
| `doctor.*` | Preset, per-rule severity/enable, ignore patterns, `fail_on`, dashboard panel switches |
|
|
171
|
+
| `annotations.*` | Global notes, per-table/per-column meaning, comment sources |
|
|
172
|
+
| `theme.*` | Semantic colour/font knobs for both light and dark |
|
|
173
|
+
| `base_url` | Used by `joist_open` to build a browsable URL |
|
|
174
|
+
|
|
175
|
+
The baseline file under `diff.dir` is the only thing Joist writes to disk;
|
|
176
|
+
it is derived and safe to delete — worth gitignoring alongside `var/`.
|
|
177
|
+
|
|
178
|
+
## The no-data promise
|
|
179
|
+
|
|
180
|
+
Structure is the `CREATE TABLE` definition: tables, columns (name, native
|
|
181
|
+
type, nullability, defaults), primary keys, indexes, foreign keys with
|
|
182
|
+
referential actions, and native comments. Row contents are never queried —
|
|
183
|
+
not by the dashboard, the API, the CLI, or the doctor. Config
|
|
184
|
+
`excluded_tables` are stripped server-side, so an excluded table never
|
|
185
|
+
reaches the client, the diff, or an export.
|
|
186
|
+
|
|
187
|
+
## Development
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pip install -e ".[dev]"
|
|
191
|
+
python -m pytest
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT. See [LICENSE](LICENSE) (includes the attribution to Laravel Truss).
|
|
197
|
+
Vendored assets carry their own licenses (Mermaid MIT, IBM Plex Mono SIL OFL)
|
|
198
|
+
under `src/django_joist/static/joist/`.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "django-joist"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A live database structure viewer for Django. Renders your schema (never data) as a zoomable ER diagram, with exports, a schema doctor and a schema diff."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT AND OFL-1.1"
|
|
11
|
+
license-files = [
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"src/django_joist/static/joist/vendor/mermaid.LICENSE",
|
|
14
|
+
"src/django_joist/static/joist/fonts/IBM-Plex.LICENSE",
|
|
15
|
+
]
|
|
16
|
+
requires-python = ">=3.13"
|
|
17
|
+
authors = [{ name = "Simone Fornasiere", email = "s.fornasiere@gmail.com" }]
|
|
18
|
+
keywords = [
|
|
19
|
+
"django",
|
|
20
|
+
"database",
|
|
21
|
+
"schema",
|
|
22
|
+
"er-diagram",
|
|
23
|
+
"erd",
|
|
24
|
+
"database-diagram",
|
|
25
|
+
"dbml",
|
|
26
|
+
"data-dictionary",
|
|
27
|
+
"introspection",
|
|
28
|
+
"mermaid",
|
|
29
|
+
]
|
|
30
|
+
classifiers = [
|
|
31
|
+
"Development Status :: 4 - Beta",
|
|
32
|
+
"Environment :: Web Environment",
|
|
33
|
+
"Framework :: Django",
|
|
34
|
+
"Framework :: Django :: 5.2",
|
|
35
|
+
"Framework :: Django :: 6.0",
|
|
36
|
+
"Framework :: Django :: 6.1",
|
|
37
|
+
"Intended Audience :: Developers",
|
|
38
|
+
"Operating System :: OS Independent",
|
|
39
|
+
"Programming Language :: Python :: 3",
|
|
40
|
+
"Programming Language :: Python :: 3.13",
|
|
41
|
+
"Programming Language :: Python :: 3.14",
|
|
42
|
+
"Topic :: Database",
|
|
43
|
+
"Topic :: Software Development :: Documentation",
|
|
44
|
+
]
|
|
45
|
+
dependencies = ["Django>=5.2"]
|
|
46
|
+
|
|
47
|
+
[project.optional-dependencies]
|
|
48
|
+
dev = ["pytest", "pytest-django"]
|
|
49
|
+
|
|
50
|
+
[project.urls]
|
|
51
|
+
Homepage = "https://github.com/sforna/django-joist"
|
|
52
|
+
Issues = "https://github.com/sforna/django-joist/issues"
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.wheel]
|
|
55
|
+
packages = ["src/django_joist"]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
DJANGO_SETTINGS_MODULE = "tests.settings"
|
|
59
|
+
pythonpath = ["src", "."]
|
|
60
|
+
testpaths = ["tests"]
|