netbox-sqlquery 0.1.5__tar.gz → 0.1.7__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.
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/PKG-INFO +1 -1
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/__init__.py +41 -17
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/abstract_schema.py +38 -2
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/management/commands/sqlquery_create_views.py +9 -1
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery.egg-info/PKG-INFO +1 -1
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/pyproject.toml +1 -1
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/LICENSE +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/README.md +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/access.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/api/__init__.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/api/serializers.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/api/urls.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/api/views.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/filtersets.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/forms.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/management/__init__.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/management/commands/__init__.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/migrations/0001_initial.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/migrations/0002_query_permissions.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/migrations/__init__.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/models.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/navigation.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/preferences.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/query.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/schema.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/static/netbox_sqlquery/editor.js +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/static/netbox_sqlquery/icon.LICENSE +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/static/netbox_sqlquery/icon.svg +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/tables.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/templates/netbox_sqlquery/query.html +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/templates/netbox_sqlquery/saved_query.html +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/tests/__init__.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/tests/test_access.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/tests/test_api.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/tests/test_models.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/tests/test_views.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/urls.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/views.py +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery.egg-info/SOURCES.txt +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery.egg-info/dependency_links.txt +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery.egg-info/top_level.txt +0 -0
- {netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: netbox-sqlquery
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: SQL query interface for NetBox with syntax highlighting, abstract views, and role-based access control
|
|
5
5
|
Author-email: Ravi Pina <ravi@pina.org>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import sys
|
|
3
2
|
|
|
4
3
|
from netbox.plugins import PluginConfig
|
|
5
4
|
|
|
6
5
|
logger = logging.getLogger("netbox_sqlquery")
|
|
7
6
|
|
|
8
|
-
# Management commands that modify schema — creating views during these
|
|
9
|
-
# can block migrations when a view depends on a column being altered.
|
|
10
|
-
_SKIP_VIEWS_COMMANDS = {"migrate", "makemigrations"}
|
|
11
|
-
|
|
12
7
|
|
|
13
8
|
class NetBoxSQLQueryConfig(PluginConfig):
|
|
14
9
|
name = "netbox_sqlquery"
|
|
@@ -17,7 +12,7 @@ class NetBoxSQLQueryConfig(PluginConfig):
|
|
|
17
12
|
"SQL query interface for NetBox with syntax highlighting,"
|
|
18
13
|
" abstract views, and role-based access control"
|
|
19
14
|
)
|
|
20
|
-
version = "0.1.
|
|
15
|
+
version = "0.1.7"
|
|
21
16
|
author = "Ravi Pina"
|
|
22
17
|
author_email = "ravi@pina.org"
|
|
23
18
|
base_url = "sqlquery"
|
|
@@ -46,25 +41,40 @@ class NetBoxSQLQueryConfig(PluginConfig):
|
|
|
46
41
|
# Register navigation based on top_level_menu setting
|
|
47
42
|
self._register_navigation()
|
|
48
43
|
|
|
49
|
-
#
|
|
50
|
-
|
|
44
|
+
# Drop views before migrations so PostgreSQL can alter columns
|
|
45
|
+
# that the views depend on. Recreate them after migrations
|
|
46
|
+
# complete against the new schema. The views are read-only
|
|
47
|
+
# projections and contain no data, so this is always safe.
|
|
48
|
+
from django.db.models.signals import post_migrate, pre_migrate
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
pre_migrate.connect(self._drop_views, sender=self)
|
|
51
|
+
post_migrate.connect(self._create_views_forced, sender=self)
|
|
53
52
|
|
|
54
|
-
# For normal app startup (gunicorn/uvicorn),
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
# For normal app startup (gunicorn/uvicorn), skip expensive view
|
|
54
|
+
# creation if views already exist — just populate the table map.
|
|
55
|
+
self._create_views(sender=self)
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _drop_views(sender, **kwargs):
|
|
59
|
+
try:
|
|
60
|
+
from .abstract_schema import drop_views
|
|
61
|
+
|
|
62
|
+
dropped = drop_views()
|
|
63
|
+
if dropped:
|
|
64
|
+
logger.info(
|
|
65
|
+
"Dropped %d abstract SQL view(s) before migration.",
|
|
66
|
+
len(dropped),
|
|
67
|
+
)
|
|
68
|
+
except Exception as exc:
|
|
69
|
+
logger.warning("Could not drop abstract SQL views: %s", exc)
|
|
61
70
|
|
|
62
71
|
@staticmethod
|
|
63
72
|
def _create_views(sender, **kwargs):
|
|
73
|
+
"""Normal startup — skips creation if views already exist."""
|
|
64
74
|
try:
|
|
65
75
|
from .abstract_schema import ensure_views
|
|
66
76
|
|
|
67
|
-
ensure_views()
|
|
77
|
+
ensure_views() # force=False: fast path when views exist
|
|
68
78
|
except Exception as exc:
|
|
69
79
|
logger.warning(
|
|
70
80
|
"Could not create abstract SQL views: %s. "
|
|
@@ -72,6 +82,20 @@ class NetBoxSQLQueryConfig(PluginConfig):
|
|
|
72
82
|
exc,
|
|
73
83
|
)
|
|
74
84
|
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _create_views_forced(sender, **kwargs):
|
|
87
|
+
"""Post-migrate — always rebuild views against the new schema."""
|
|
88
|
+
try:
|
|
89
|
+
from .abstract_schema import ensure_views
|
|
90
|
+
|
|
91
|
+
ensure_views(force=True)
|
|
92
|
+
except Exception as exc:
|
|
93
|
+
logger.warning(
|
|
94
|
+
"Could not create abstract SQL views after migration: %s. "
|
|
95
|
+
"Run 'manage.py sqlquery_create_views --force' manually.",
|
|
96
|
+
exc,
|
|
97
|
+
)
|
|
98
|
+
|
|
75
99
|
def _register_navigation(self):
|
|
76
100
|
from netbox.plugins.registration import register_menu, register_menu_items
|
|
77
101
|
|
|
@@ -136,6 +136,30 @@ COLUMN_RENAMES = {
|
|
|
136
136
|
ABSTRACT_TO_TABLES = {}
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def _views_exist():
|
|
140
|
+
"""Fast check: do any nb_* abstract views exist in the database?"""
|
|
141
|
+
with connection.cursor() as cursor:
|
|
142
|
+
cursor.execute("""
|
|
143
|
+
SELECT 1 FROM information_schema.views
|
|
144
|
+
WHERE table_schema = 'public' AND table_name LIKE 'nb\\_%'
|
|
145
|
+
LIMIT 1
|
|
146
|
+
""")
|
|
147
|
+
return cursor.fetchone() is not None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _populate_table_map():
|
|
151
|
+
"""Populate ABSTRACT_TO_TABLES from model metadata without FK introspection.
|
|
152
|
+
|
|
153
|
+
Used on normal startup when views already exist — avoids the expensive
|
|
154
|
+
information_schema queries that block gunicorn workers.
|
|
155
|
+
"""
|
|
156
|
+
ABSTRACT_TO_TABLES.clear()
|
|
157
|
+
for model in get_included_models():
|
|
158
|
+
table_name = model._meta.db_table
|
|
159
|
+
view_name = VIEW_NAME_OVERRIDES.get(table_name, f"nb_{table_name}")
|
|
160
|
+
ABSTRACT_TO_TABLES[view_name] = {table_name}
|
|
161
|
+
|
|
162
|
+
|
|
139
163
|
def _get_view_name(model):
|
|
140
164
|
"""Generate the nb_* view name for a model."""
|
|
141
165
|
label = f"{model._meta.app_label}.{model._meta.model_name}"
|
|
@@ -165,6 +189,7 @@ def _get_table_columns(table_name):
|
|
|
165
189
|
def _get_fk_map(table_name):
|
|
166
190
|
"""Get FK column -> target table mapping from information_schema."""
|
|
167
191
|
with connection.cursor() as cursor:
|
|
192
|
+
cursor.execute("SET LOCAL statement_timeout = '30s'")
|
|
168
193
|
cursor.execute(
|
|
169
194
|
"""
|
|
170
195
|
SELECT kcu.column_name, ccu.table_name
|
|
@@ -366,8 +391,19 @@ def get_included_models():
|
|
|
366
391
|
return models
|
|
367
392
|
|
|
368
393
|
|
|
369
|
-
def ensure_views(dry_run=False):
|
|
370
|
-
"""Create or replace all abstract views. Returns list of (view_name, sql) tuples.
|
|
394
|
+
def ensure_views(dry_run=False, force=False):
|
|
395
|
+
"""Create or replace all abstract views. Returns list of (view_name, sql) tuples.
|
|
396
|
+
|
|
397
|
+
When *force* is False (the default) and views already exist in the database,
|
|
398
|
+
skips the expensive FK introspection and DDL — only populates the in-process
|
|
399
|
+
ABSTRACT_TO_TABLES map from model metadata. Pass *force=True* after
|
|
400
|
+
migrations or when views need to be rebuilt.
|
|
401
|
+
"""
|
|
402
|
+
if not force and not dry_run and _views_exist():
|
|
403
|
+
logger.debug("Abstract views already exist; skipping creation.")
|
|
404
|
+
_populate_table_map()
|
|
405
|
+
return []
|
|
406
|
+
|
|
371
407
|
results = []
|
|
372
408
|
ABSTRACT_TO_TABLES.clear()
|
|
373
409
|
|
|
@@ -17,6 +17,11 @@ class Command(BaseCommand):
|
|
|
17
17
|
action="store_true",
|
|
18
18
|
help="Drop all nb_* views instead of creating them",
|
|
19
19
|
)
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--force",
|
|
22
|
+
action="store_true",
|
|
23
|
+
help="Rebuild views even if they already exist",
|
|
24
|
+
)
|
|
20
25
|
|
|
21
26
|
def handle(self, *args, **options):
|
|
22
27
|
if options["drop"]:
|
|
@@ -26,7 +31,10 @@ class Command(BaseCommand):
|
|
|
26
31
|
self.stdout.write(self.style.SUCCESS(f"Dropped {len(dropped)} views"))
|
|
27
32
|
return
|
|
28
33
|
|
|
29
|
-
results = ensure_views(
|
|
34
|
+
results = ensure_views(
|
|
35
|
+
dry_run=options["dry_run"],
|
|
36
|
+
force=options["force"] or options["dry_run"],
|
|
37
|
+
)
|
|
30
38
|
|
|
31
39
|
for view_name, sql in results:
|
|
32
40
|
if options["dry_run"]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: netbox-sqlquery
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: SQL query interface for NetBox with syntax highlighting, abstract views, and role-based access control
|
|
5
5
|
Author-email: Ravi Pina <ravi@pina.org>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "netbox-sqlquery"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.7"
|
|
8
8
|
description = "SQL query interface for NetBox with syntax highlighting, abstract views, and role-based access control"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/management/commands/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/migrations/0002_query_permissions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/static/netbox_sqlquery/editor.js
RENAMED
|
File without changes
|
{netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/static/netbox_sqlquery/icon.LICENSE
RENAMED
|
File without changes
|
{netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/static/netbox_sqlquery/icon.svg
RENAMED
|
File without changes
|
|
File without changes
|
{netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery/templates/netbox_sqlquery/query.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{netbox_sqlquery-0.1.5 → netbox_sqlquery-0.1.7}/netbox_sqlquery.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|