mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-17 17:56:29 +00:00
Fixes #20290: Prevent ObjectType queries when table doesn't exist
In v4.4.0, ObjectType became a concrete model with a database table. Signal handlers query this table during migrations, but the table doesn't exist yet during 3.7.x→4.4.0 upgrades. The query failures poison the transaction and abort migrations. Added objecttype_table_exists() helper and three table existence checks: - has_feature() in netbox/models/features.py - update_object_types() in core/signals.py - Search backend cache() in search/backends.py If core_objecttype table doesn't exist, operations return early instead of querying. Once table exists, normal operation resumes.
This commit is contained in:
parent
f23eb53312
commit
52d990463f
@ -13,6 +13,7 @@ from django_prometheus.models import model_deletes, model_inserts, model_updates
|
|||||||
from core.choices import JobStatusChoices, ObjectChangeActionChoices
|
from core.choices import JobStatusChoices, ObjectChangeActionChoices
|
||||||
from core.events import *
|
from core.events import *
|
||||||
from core.models import ObjectType
|
from core.models import ObjectType
|
||||||
|
from utilities.object_types import objecttype_table_exists
|
||||||
from extras.events import enqueue_event
|
from extras.events import enqueue_event
|
||||||
from extras.models import Tag
|
from extras.models import Tag
|
||||||
from extras.utils import run_validators
|
from extras.utils import run_validators
|
||||||
@ -51,6 +52,10 @@ def update_object_types(sender, **kwargs):
|
|||||||
"""
|
"""
|
||||||
Create or update the corresponding ObjectType for each model within the migrated app.
|
Create or update the corresponding ObjectType for each model within the migrated app.
|
||||||
"""
|
"""
|
||||||
|
# Skip ObjectType operations if the table doesn't exist yet (during migrations)
|
||||||
|
if not objecttype_table_exists():
|
||||||
|
return
|
||||||
|
|
||||||
for model in sender.get_models():
|
for model in sender.get_models():
|
||||||
app_label, model_name = model._meta.label_lower.split('.')
|
app_label, model_name = model._meta.label_lower.split('.')
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ from netbox.registry import registry
|
|||||||
from netbox.signals import post_clean
|
from netbox.signals import post_clean
|
||||||
from netbox.utils import register_model_feature
|
from netbox.utils import register_model_feature
|
||||||
from utilities.json import CustomFieldJSONEncoder
|
from utilities.json import CustomFieldJSONEncoder
|
||||||
|
from utilities.object_types import objecttype_table_exists
|
||||||
from utilities.serialization import serialize_object
|
from utilities.serialization import serialize_object
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -670,6 +671,10 @@ def has_feature(model_or_ct, feature):
|
|||||||
"""
|
"""
|
||||||
Returns True if the model supports the specified feature.
|
Returns True if the model supports the specified feature.
|
||||||
"""
|
"""
|
||||||
|
# Check if ObjectType table exists before attempting queries
|
||||||
|
if not objecttype_table_exists():
|
||||||
|
return False
|
||||||
|
|
||||||
# If an ObjectType was passed, we can use it directly
|
# If an ObjectType was passed, we can use it directly
|
||||||
if type(model_or_ct) is ObjectType:
|
if type(model_or_ct) is ObjectType:
|
||||||
ot = model_or_ct
|
ot = model_or_ct
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from netaddr.core import AddrFormatError
|
|||||||
from core.models import ObjectType
|
from core.models import ObjectType
|
||||||
from extras.models import CachedValue, CustomField
|
from extras.models import CachedValue, CustomField
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
|
from utilities.object_types import objecttype_table_exists
|
||||||
from utilities.object_types import object_type_identifier
|
from utilities.object_types import object_type_identifier
|
||||||
from utilities.querysets import RestrictedPrefetch
|
from utilities.querysets import RestrictedPrefetch
|
||||||
from utilities.string import title
|
from utilities.string import title
|
||||||
@ -209,6 +210,10 @@ class CachedValueSearchBackend(SearchBackend):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Prefetch any associated custom fields
|
# Prefetch any associated custom fields
|
||||||
|
# Skip if ObjectType table doesn't exist yet (during migrations)
|
||||||
|
if not objecttype_table_exists():
|
||||||
|
return
|
||||||
|
|
||||||
object_type = ObjectType.objects.get_for_model(indexer.model)
|
object_type = ObjectType.objects.get_for_model(indexer.model)
|
||||||
custom_fields = CustomField.objects.filter(object_types=object_type).exclude(search_weight=0)
|
custom_fields = CustomField.objects.filter(object_types=object_type).exclude(search_weight=0)
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
|
from django.db import connection
|
||||||
|
|
||||||
from .string import title
|
from .string import title
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'object_type_identifier',
|
'object_type_identifier',
|
||||||
'object_type_name',
|
'object_type_name',
|
||||||
|
'objecttype_table_exists',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -27,3 +30,13 @@ def object_type_name(object_type, include_app=True):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Model does not exist
|
# Model does not exist
|
||||||
return f'{object_type.app_label} > {object_type.model}'
|
return f'{object_type.app_label} > {object_type.model}'
|
||||||
|
|
||||||
|
|
||||||
|
def objecttype_table_exists():
|
||||||
|
"""
|
||||||
|
Check if the core_objecttype table exists.
|
||||||
|
|
||||||
|
Returns True if the table exists, False otherwise.
|
||||||
|
Used to prevent ObjectType queries during migrations when the table doesn't exist yet.
|
||||||
|
"""
|
||||||
|
return 'core_objecttype' in connection.introspection.table_names()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user