django-schematic 0.3.6__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_schematic-0.3.6/.gitignore +51 -0
- django_schematic-0.3.6/LICENSE +21 -0
- django_schematic-0.3.6/PKG-INFO +142 -0
- django_schematic-0.3.6/README.md +93 -0
- django_schematic-0.3.6/pyproject.toml +63 -0
- django_schematic-0.3.6/schematic/__init__.py +0 -0
- django_schematic-0.3.6/schematic/apps.py +7 -0
- django_schematic-0.3.6/schematic/schema.py +217 -0
- django_schematic-0.3.6/schematic/settings.py +15 -0
- django_schematic-0.3.6/schematic/static/schematic/main.css +1 -0
- django_schematic-0.3.6/schematic/static/schematic/main.js +62 -0
- django_schematic-0.3.6/schematic/templates/schematic/index.html +17 -0
- django_schematic-0.3.6/schematic/urls.py +10 -0
- django_schematic-0.3.6/schematic/views.py +48 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
.Python
|
|
7
|
+
*.egg
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
.eggs/
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
.env
|
|
16
|
+
.claude
|
|
17
|
+
|
|
18
|
+
# Hatch
|
|
19
|
+
.hatch/
|
|
20
|
+
|
|
21
|
+
# Django
|
|
22
|
+
*.sqlite3
|
|
23
|
+
local_settings.py
|
|
24
|
+
db.sqlite3
|
|
25
|
+
|
|
26
|
+
# Frontend
|
|
27
|
+
ui/node_modules/
|
|
28
|
+
ui/dist/
|
|
29
|
+
ui/.vite/
|
|
30
|
+
|
|
31
|
+
# Static files (keep the committed build artifact)
|
|
32
|
+
# schematic/static/schematic/main.js is committed intentionally
|
|
33
|
+
|
|
34
|
+
# Testing
|
|
35
|
+
.pytest_cache/
|
|
36
|
+
.coverage
|
|
37
|
+
htmlcov/
|
|
38
|
+
.tox/
|
|
39
|
+
|
|
40
|
+
# mypy
|
|
41
|
+
.mypy_cache/
|
|
42
|
+
|
|
43
|
+
# IDE
|
|
44
|
+
.vscode/
|
|
45
|
+
.idea/
|
|
46
|
+
*.swp
|
|
47
|
+
*.swo
|
|
48
|
+
.DS_Store
|
|
49
|
+
|
|
50
|
+
# Logs
|
|
51
|
+
*.log
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jsheffie
|
|
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.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-schematic
|
|
3
|
+
Version: 0.3.6
|
|
4
|
+
Summary: Interactive schema visualization for Django projects — React Flow + force-directed layout
|
|
5
|
+
Project-URL: Homepage, https://github.com/jsheffie/django-schematic
|
|
6
|
+
Project-URL: Repository, https://github.com/jsheffie/django-schematic
|
|
7
|
+
Project-URL: Issues, https://github.com/jsheffie/django-schematic/issues
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 jsheffie
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: django,graph,models,schema,visualization
|
|
31
|
+
Classifier: Development Status :: 3 - Alpha
|
|
32
|
+
Classifier: Framework :: Django
|
|
33
|
+
Classifier: Framework :: Django :: 5.1
|
|
34
|
+
Classifier: Framework :: Django :: 5.2
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
41
|
+
Requires-Python: >=3.12
|
|
42
|
+
Requires-Dist: django>=5.1
|
|
43
|
+
Provides-Extra: dev
|
|
44
|
+
Requires-Dist: django-stubs; extra == 'dev'
|
|
45
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
46
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
47
|
+
Requires-Dist: pytest-django; extra == 'dev'
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# django-schematic
|
|
51
|
+
|
|
52
|
+
Interactive schema visualization for Django projects.
|
|
53
|
+
|
|
54
|
+
See all your models, fields, and relationships as an interactive, force-directed graph — directly in your browser. Zero Node.js required at runtime.
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- Force-directed layout with physics settle animation (drag-to-pin support)
|
|
59
|
+
- Hierarchical layout via dagre
|
|
60
|
+
- Click to expand/collapse field lists per model
|
|
61
|
+
- App-based color coding
|
|
62
|
+
- Show/hide models and apps via sidebar
|
|
63
|
+
- Export/import view state as JSON
|
|
64
|
+
- Export diagram as PNG
|
|
65
|
+
- Clean JSON API endpoint (`GET /schema/api/`)
|
|
66
|
+
- Django 5.x + Python 3.12+ only
|
|
67
|
+
|
|
68
|
+
## Screenshots
|
|
69
|
+
|
|
70
|
+
<img width="1712" height="943" alt="Screenshot 2026-04-12 at 11 13 05 PM" src="https://github.com/user-attachments/assets/7048a332-fec1-420f-b331-52b6da2eb5ef" />
|
|
71
|
+
<img width="1337" height="928" alt="Screenshot 2026-04-12 at 11 13 46 PM" src="https://github.com/user-attachments/assets/5f8776c2-e62c-4154-8a49-c77282d4857e" />
|
|
72
|
+
<img width="996" height="865" alt="Screenshot 2026-04-12 at 11 15 06 PM" src="https://github.com/user-attachments/assets/9feb182b-921d-4765-88eb-cf0a5f845400" />
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
## Quick Start
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install django-schematic
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Add to `INSTALLED_APPS`:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
INSTALLED_APPS = [
|
|
85
|
+
...
|
|
86
|
+
"schematic",
|
|
87
|
+
]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Add to `urls.py` (behind a staff-only guard in production):
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from django.urls import include, path
|
|
94
|
+
|
|
95
|
+
urlpatterns = [
|
|
96
|
+
...
|
|
97
|
+
path("schema/", include("schematic.urls")),
|
|
98
|
+
]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Visit `http://localhost:8000/schema/` — your model graph will be there.
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# settings.py (all optional — these are the defaults)
|
|
107
|
+
SCHEMATIC = {
|
|
108
|
+
"visible": True, # or callable(request) -> bool
|
|
109
|
+
"include_apps": [], # empty = all apps
|
|
110
|
+
"exclude_apps": ["admin", "contenttypes", "sessions", "auth"],
|
|
111
|
+
"exclude_models": {}, # {"myapp": ["InternalModel"]}
|
|
112
|
+
"include_abstract": False,
|
|
113
|
+
"include_proxy": True,
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## API
|
|
118
|
+
|
|
119
|
+
The schema is also available as JSON:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
GET /schema/api/
|
|
123
|
+
GET /schema/api/?apps=myapp,otherapp
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Development
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Backend
|
|
130
|
+
pip install -e ".[dev]"
|
|
131
|
+
pytest
|
|
132
|
+
|
|
133
|
+
# Frontend
|
|
134
|
+
cd ui
|
|
135
|
+
npm install
|
|
136
|
+
npm run dev # Vite dev server with HMR
|
|
137
|
+
npm run build # Outputs to schematic/static/schematic/
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# django-schematic
|
|
2
|
+
|
|
3
|
+
Interactive schema visualization for Django projects.
|
|
4
|
+
|
|
5
|
+
See all your models, fields, and relationships as an interactive, force-directed graph — directly in your browser. Zero Node.js required at runtime.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Force-directed layout with physics settle animation (drag-to-pin support)
|
|
10
|
+
- Hierarchical layout via dagre
|
|
11
|
+
- Click to expand/collapse field lists per model
|
|
12
|
+
- App-based color coding
|
|
13
|
+
- Show/hide models and apps via sidebar
|
|
14
|
+
- Export/import view state as JSON
|
|
15
|
+
- Export diagram as PNG
|
|
16
|
+
- Clean JSON API endpoint (`GET /schema/api/`)
|
|
17
|
+
- Django 5.x + Python 3.12+ only
|
|
18
|
+
|
|
19
|
+
## Screenshots
|
|
20
|
+
|
|
21
|
+
<img width="1712" height="943" alt="Screenshot 2026-04-12 at 11 13 05 PM" src="https://github.com/user-attachments/assets/7048a332-fec1-420f-b331-52b6da2eb5ef" />
|
|
22
|
+
<img width="1337" height="928" alt="Screenshot 2026-04-12 at 11 13 46 PM" src="https://github.com/user-attachments/assets/5f8776c2-e62c-4154-8a49-c77282d4857e" />
|
|
23
|
+
<img width="996" height="865" alt="Screenshot 2026-04-12 at 11 15 06 PM" src="https://github.com/user-attachments/assets/9feb182b-921d-4765-88eb-cf0a5f845400" />
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install django-schematic
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Add to `INSTALLED_APPS`:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
INSTALLED_APPS = [
|
|
36
|
+
...
|
|
37
|
+
"schematic",
|
|
38
|
+
]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Add to `urls.py` (behind a staff-only guard in production):
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from django.urls import include, path
|
|
45
|
+
|
|
46
|
+
urlpatterns = [
|
|
47
|
+
...
|
|
48
|
+
path("schema/", include("schematic.urls")),
|
|
49
|
+
]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Visit `http://localhost:8000/schema/` — your model graph will be there.
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# settings.py (all optional — these are the defaults)
|
|
58
|
+
SCHEMATIC = {
|
|
59
|
+
"visible": True, # or callable(request) -> bool
|
|
60
|
+
"include_apps": [], # empty = all apps
|
|
61
|
+
"exclude_apps": ["admin", "contenttypes", "sessions", "auth"],
|
|
62
|
+
"exclude_models": {}, # {"myapp": ["InternalModel"]}
|
|
63
|
+
"include_abstract": False,
|
|
64
|
+
"include_proxy": True,
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## API
|
|
69
|
+
|
|
70
|
+
The schema is also available as JSON:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
GET /schema/api/
|
|
74
|
+
GET /schema/api/?apps=myapp,otherapp
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Development
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Backend
|
|
81
|
+
pip install -e ".[dev]"
|
|
82
|
+
pytest
|
|
83
|
+
|
|
84
|
+
# Frontend
|
|
85
|
+
cd ui
|
|
86
|
+
npm install
|
|
87
|
+
npm run dev # Vite dev server with HMR
|
|
88
|
+
npm run build # Outputs to schematic/static/schematic/
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "django-schematic"
|
|
7
|
+
version = "0.3.6"
|
|
8
|
+
description = "Interactive schema visualization for Django projects — React Flow + force-directed layout"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"django>=5.1",
|
|
14
|
+
]
|
|
15
|
+
keywords = ["django", "schema", "visualization", "graph", "models"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Framework :: Django",
|
|
19
|
+
"Framework :: Django :: 5.1",
|
|
20
|
+
"Framework :: Django :: 5.2",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = [
|
|
31
|
+
"pytest",
|
|
32
|
+
"pytest-django",
|
|
33
|
+
"mypy",
|
|
34
|
+
"django-stubs",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/jsheffie/django-schematic"
|
|
39
|
+
Repository = "https://github.com/jsheffie/django-schematic"
|
|
40
|
+
Issues = "https://github.com/jsheffie/django-schematic/issues"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["schematic"]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.sdist]
|
|
46
|
+
include = [
|
|
47
|
+
"schematic/",
|
|
48
|
+
"LICENSE",
|
|
49
|
+
"README.md",
|
|
50
|
+
"pyproject.toml",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
DJANGO_SETTINGS_MODULE = "tests.settings"
|
|
55
|
+
pythonpath = ["."]
|
|
56
|
+
|
|
57
|
+
[tool.mypy]
|
|
58
|
+
python_version = "3.12"
|
|
59
|
+
strict = true
|
|
60
|
+
plugins = ["mypy_django_plugin.main"]
|
|
61
|
+
|
|
62
|
+
[tool.django-stubs]
|
|
63
|
+
django_settings_module = "tests.settings"
|
|
File without changes
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Schema extraction — walks Django's app registry and model._meta API to build
|
|
3
|
+
a serializable graph of nodes (models) and edges (relationships).
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import dataclasses
|
|
8
|
+
import json
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from django.apps import apps as django_apps
|
|
12
|
+
|
|
13
|
+
from .settings import get_setting
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from django.db import models as django_models
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# Data model
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclasses.dataclass(frozen=True, order=True)
|
|
25
|
+
class FieldInfo:
|
|
26
|
+
name: str
|
|
27
|
+
field_type: str
|
|
28
|
+
is_relation: bool
|
|
29
|
+
null: bool
|
|
30
|
+
unique: bool
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclasses.dataclass(frozen=True, order=True)
|
|
34
|
+
class NodeInfo:
|
|
35
|
+
id: str # e.g. "myapp.Order"
|
|
36
|
+
name: str # e.g. "Order"
|
|
37
|
+
app_label: str # e.g. "myapp"
|
|
38
|
+
tags: tuple[str, ...] # "abstract", "proxy"
|
|
39
|
+
fields: tuple[FieldInfo, ...]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclasses.dataclass(frozen=True, order=True)
|
|
43
|
+
class EdgeInfo:
|
|
44
|
+
source: str # NodeInfo.id
|
|
45
|
+
target: str # NodeInfo.id
|
|
46
|
+
relation_type: str # "fk" | "o2o" | "m2m" | "subclass" | "proxy"
|
|
47
|
+
field_name: str
|
|
48
|
+
related_name: str | None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclasses.dataclass(frozen=True)
|
|
52
|
+
class SchemaGraph:
|
|
53
|
+
nodes: tuple[NodeInfo, ...]
|
|
54
|
+
edges: tuple[EdgeInfo, ...]
|
|
55
|
+
app_labels: tuple[str, ...]
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> dict:
|
|
58
|
+
return dataclasses.asdict(self)
|
|
59
|
+
|
|
60
|
+
def to_json(self) -> str:
|
|
61
|
+
return json.dumps(self.to_dict())
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Extraction
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _node_id(model: type[django_models.Model]) -> str:
|
|
70
|
+
return f"{model._meta.app_label}.{model.__name__}"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _extract_fields(model: type[django_models.Model]) -> tuple[FieldInfo, ...]:
|
|
74
|
+
fields = []
|
|
75
|
+
for f in model._meta.get_fields():
|
|
76
|
+
if not hasattr(f, "column"):
|
|
77
|
+
# Reverse relations — skip; they appear as edges from the other side
|
|
78
|
+
continue
|
|
79
|
+
fields.append(
|
|
80
|
+
FieldInfo(
|
|
81
|
+
name=f.name,
|
|
82
|
+
field_type=type(f).__name__,
|
|
83
|
+
is_relation=f.is_relation if hasattr(f, "is_relation") else False,
|
|
84
|
+
null=getattr(f, "null", False),
|
|
85
|
+
unique=getattr(f, "unique", False),
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
return tuple(sorted(fields))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _tags(model: type[django_models.Model]) -> tuple[str, ...]:
|
|
92
|
+
tags = []
|
|
93
|
+
if model._meta.abstract:
|
|
94
|
+
tags.append("abstract")
|
|
95
|
+
if model._meta.proxy:
|
|
96
|
+
tags.append("proxy")
|
|
97
|
+
return tuple(tags)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _extract_edges(
|
|
101
|
+
model: type[django_models.Model],
|
|
102
|
+
all_model_ids: set[str],
|
|
103
|
+
) -> list[EdgeInfo]:
|
|
104
|
+
edges: list[EdgeInfo] = []
|
|
105
|
+
source = _node_id(model)
|
|
106
|
+
|
|
107
|
+
for f in model._meta.get_fields():
|
|
108
|
+
if not getattr(f, "is_relation", False):
|
|
109
|
+
continue
|
|
110
|
+
if not hasattr(f, "related_model") or f.related_model is None:
|
|
111
|
+
continue
|
|
112
|
+
target = _node_id(f.related_model)
|
|
113
|
+
if target not in all_model_ids:
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
# Skip reverse accessors — process each relationship once from the owning side
|
|
117
|
+
if hasattr(f, "field") and not hasattr(f, "column"):
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
from django.db.models import ForeignKey, ManyToManyField, OneToOneField
|
|
121
|
+
|
|
122
|
+
if isinstance(f, OneToOneField):
|
|
123
|
+
rel = "o2o"
|
|
124
|
+
elif isinstance(f, ForeignKey):
|
|
125
|
+
rel = "fk"
|
|
126
|
+
elif isinstance(f, ManyToManyField):
|
|
127
|
+
rel = "m2m"
|
|
128
|
+
else:
|
|
129
|
+
rel = "fk"
|
|
130
|
+
|
|
131
|
+
edges.append(
|
|
132
|
+
EdgeInfo(
|
|
133
|
+
source=source,
|
|
134
|
+
target=target,
|
|
135
|
+
relation_type=rel,
|
|
136
|
+
field_name=f.name,
|
|
137
|
+
related_name=getattr(f, "related_query_name", lambda: None)() or None,
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Subclass edges (multi-table inheritance and proxy)
|
|
142
|
+
for parent in model.__bases__:
|
|
143
|
+
if not hasattr(parent, "_meta"):
|
|
144
|
+
continue
|
|
145
|
+
parent_id = _node_id(parent) # type: ignore[arg-type]
|
|
146
|
+
if parent_id not in all_model_ids:
|
|
147
|
+
continue
|
|
148
|
+
rel = "proxy" if model._meta.proxy else "subclass"
|
|
149
|
+
edges.append(
|
|
150
|
+
EdgeInfo(
|
|
151
|
+
source=source,
|
|
152
|
+
target=parent_id,
|
|
153
|
+
relation_type=rel,
|
|
154
|
+
field_name="",
|
|
155
|
+
related_name=None,
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return edges
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def build_schema(filter_apps: list[str] | None = None) -> SchemaGraph:
|
|
163
|
+
"""
|
|
164
|
+
Walk the Django app registry and return a SchemaGraph.
|
|
165
|
+
|
|
166
|
+
filter_apps: if provided, only include models from these app labels.
|
|
167
|
+
"""
|
|
168
|
+
exclude_apps: list[str] = get_setting("exclude_apps")
|
|
169
|
+
exclude_models: dict[str, list[str]] = get_setting("exclude_models")
|
|
170
|
+
include_abstract: bool = get_setting("include_abstract")
|
|
171
|
+
include_proxy: bool = get_setting("include_proxy")
|
|
172
|
+
include_apps: list[str] = get_setting("include_apps")
|
|
173
|
+
|
|
174
|
+
# Determine which apps to include
|
|
175
|
+
all_app_configs = django_apps.get_app_configs()
|
|
176
|
+
app_configs = [
|
|
177
|
+
ac for ac in all_app_configs
|
|
178
|
+
if ac.label not in exclude_apps
|
|
179
|
+
and (not include_apps or ac.label in include_apps)
|
|
180
|
+
and (not filter_apps or ac.label in filter_apps)
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
# Collect all models
|
|
184
|
+
all_models: list[type[django_models.Model]] = []
|
|
185
|
+
for ac in app_configs:
|
|
186
|
+
for model in ac.get_models():
|
|
187
|
+
if model._meta.abstract and not include_abstract:
|
|
188
|
+
continue
|
|
189
|
+
if model._meta.proxy and not include_proxy:
|
|
190
|
+
continue
|
|
191
|
+
excluded = exclude_models.get(ac.label, [])
|
|
192
|
+
if model.__name__ in excluded:
|
|
193
|
+
continue
|
|
194
|
+
all_models.append(model)
|
|
195
|
+
|
|
196
|
+
all_model_ids = {_node_id(m) for m in all_models}
|
|
197
|
+
|
|
198
|
+
nodes = tuple(
|
|
199
|
+
sorted(
|
|
200
|
+
NodeInfo(
|
|
201
|
+
id=_node_id(m),
|
|
202
|
+
name=m.__name__,
|
|
203
|
+
app_label=m._meta.app_label,
|
|
204
|
+
tags=_tags(m),
|
|
205
|
+
fields=_extract_fields(m),
|
|
206
|
+
)
|
|
207
|
+
for m in all_models
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
edges: list[EdgeInfo] = []
|
|
212
|
+
for m in all_models:
|
|
213
|
+
edges.extend(_extract_edges(m, all_model_ids))
|
|
214
|
+
|
|
215
|
+
app_labels = tuple(sorted({n.app_label for n in nodes}))
|
|
216
|
+
|
|
217
|
+
return SchemaGraph(nodes=nodes, edges=tuple(edges), app_labels=app_labels)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
|
|
3
|
+
DEFAULTS: dict = {
|
|
4
|
+
"visible": True,
|
|
5
|
+
"include_apps": [],
|
|
6
|
+
"exclude_apps": ["admin", "contenttypes", "sessions", "auth"],
|
|
7
|
+
"exclude_models": {},
|
|
8
|
+
"include_abstract": False,
|
|
9
|
+
"include_proxy": True,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_setting(key: str):
|
|
14
|
+
user_settings: dict = getattr(settings, "SCHEMATIC", {})
|
|
15
|
+
return user_settings.get(key, DEFAULTS[key])
|