Skip to content

Commit 5bf09c3

Browse files
committed
Fix: Defer typed-tab DB queries from ready() to first request (#4)
- Use Django's request_started signal to run DB-dependent init once - Combined tabs still register immediately (no DB access needed) - Typed tabs, CO URL injection, and registry dedup deferred to first HTTP request - Prevents warnings from netbox_branching and Django about DB access during init
1 parent 4c2c049 commit 5bf09c3

1 file changed

Lines changed: 67 additions & 11 deletions

File tree

netbox_custom_objects_tab/views/__init__.py

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import threading
23

34
from django.apps import apps
45
from netbox.plugins import get_plugin_config
@@ -9,6 +10,18 @@
910

1011
logger = logging.getLogger("netbox_custom_objects_tab")
1112

13+
# ── Deferred initialisation (avoids DB queries during ready()) ───────────
14+
# Typed-tab registration queries CustomObjectTypeField and ContentType.
15+
# Running those queries in AppConfig.ready() triggers warnings from Django
16+
# ("Accessing the database during app initialization is discouraged") and
17+
# from netbox_branching ("Routing database query … before branching support
18+
# is initialized"). We defer that work to the first HTTP request via
19+
# Django's request_started signal. See GitHub issue #4.
20+
21+
_deferred_init_lock = threading.Lock()
22+
_deferred_init_done = False
23+
_deferred_config = {} # populated in register_tabs(), consumed in _deferred_typed_init()
24+
1225

1326
def _resolve_dynamic_custom_object_models():
1427
"""
@@ -162,10 +175,52 @@ def _deduplicate_registry():
162175
model_map[model_name] = deduped
163176

164177

178+
def _deferred_typed_init(sender=None, **kwargs):
179+
"""
180+
One-shot ``request_started`` handler that performs DB-dependent initialisation.
181+
182+
Registers typed tabs, injects CO URL patterns, and deduplicates the view
183+
registry. Runs exactly once on the first HTTP request, then disconnects
184+
itself so subsequent requests pay no overhead.
185+
"""
186+
global _deferred_init_done
187+
with _deferred_init_lock:
188+
if _deferred_init_done:
189+
return
190+
_deferred_init_done = True
191+
192+
from django.core.signals import request_started
193+
194+
request_started.disconnect(_deferred_typed_init, dispatch_uid="netbox_custom_objects_tab_deferred")
195+
196+
config = _deferred_config
197+
combined_models = config.get("combined_models", [])
198+
typed_labels = config.get("typed_labels", [])
199+
typed_weight = config.get("typed_weight", 2100)
200+
201+
typed_models = []
202+
if typed_labels:
203+
typed_models = _resolve_model_labels(typed_labels)
204+
register_typed_tabs(typed_models, typed_weight)
205+
206+
# Inject URL patterns for CO dynamic models (combined + typed).
207+
if any(m._meta.app_label == _CUSTOM_OBJECTS_APP for m in combined_models + typed_models):
208+
_inject_co_urls()
209+
210+
# Deduplicate the registry — netbox_custom_objects re-registers journal/changelog
211+
# on every get_model() cache miss, producing duplicate tabs.
212+
_deduplicate_registry()
213+
214+
165215
def register_tabs():
166216
"""
167217
Read plugin config and register both combined and typed tabs.
168218
Called from AppConfig.ready().
219+
220+
Combined tabs are registered immediately (no DB queries needed).
221+
Typed tabs and other DB-dependent work are deferred to the first HTTP
222+
request via the ``request_started`` signal to avoid DB access during
223+
app initialisation (GitHub issue #4).
169224
"""
170225
try:
171226
combined_labels = get_plugin_config("netbox_custom_objects_tab", "combined_models")
@@ -177,21 +232,22 @@ def register_tabs():
177232
logger.exception("Could not read netbox_custom_objects_tab plugin config")
178233
return
179234

235+
# Phase 1 — immediate: combined tabs do not query the database.
180236
combined_models = []
181237
if combined_labels:
182238
combined_models = _resolve_model_labels(combined_labels)
183239
register_combined_tabs(combined_models, combined_label, combined_weight)
184240

185-
typed_models = []
186-
if typed_labels:
187-
typed_models = _resolve_model_labels(typed_labels)
188-
register_typed_tabs(typed_models, typed_weight)
241+
# Phase 2 — deferred: typed tabs, CO URL injection, and registry dedup
242+
# all require DB access and must wait until the first request.
243+
_deferred_config.update(
244+
{
245+
"combined_models": combined_models,
246+
"typed_labels": typed_labels,
247+
"typed_weight": typed_weight,
248+
}
249+
)
189250

190-
# For any CO dynamic models we registered tabs for, inject URL patterns into
191-
# netbox_custom_objects.urls so that tab URL reversal works.
192-
if any(m._meta.app_label == _CUSTOM_OBJECTS_APP for m in combined_models + typed_models):
193-
_inject_co_urls()
251+
from django.core.signals import request_started
194252

195-
# Deduplicate the registry — netbox_custom_objects re-registers journal/changelog
196-
# on every get_model() cache miss, producing duplicate tabs.
197-
_deduplicate_registry()
253+
request_started.connect(_deferred_typed_init, dispatch_uid="netbox_custom_objects_tab_deferred")

0 commit comments

Comments
 (0)