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:
Jason Novinger 2025-10-02 10:02:10 -05:00
parent f23eb53312
commit 52d990463f
4 changed files with 28 additions and 0 deletions

View File

@ -13,6 +13,7 @@ from django_prometheus.models import model_deletes, model_inserts, model_updates
from core.choices import JobStatusChoices, ObjectChangeActionChoices
from core.events import *
from core.models import ObjectType
from utilities.object_types import objecttype_table_exists
from extras.events import enqueue_event
from extras.models import Tag
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.
"""
# Skip ObjectType operations if the table doesn't exist yet (during migrations)
if not objecttype_table_exists():
return
for model in sender.get_models():
app_label, model_name = model._meta.label_lower.split('.')

View File

@ -24,6 +24,7 @@ from netbox.registry import registry
from netbox.signals import post_clean
from netbox.utils import register_model_feature
from utilities.json import CustomFieldJSONEncoder
from utilities.object_types import objecttype_table_exists
from utilities.serialization import serialize_object
__all__ = (
@ -670,6 +671,10 @@ def has_feature(model_or_ct, 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 type(model_or_ct) is ObjectType:
ot = model_or_ct

View File

@ -15,6 +15,7 @@ from netaddr.core import AddrFormatError
from core.models import ObjectType
from extras.models import CachedValue, CustomField
from netbox.registry import registry
from utilities.object_types import objecttype_table_exists
from utilities.object_types import object_type_identifier
from utilities.querysets import RestrictedPrefetch
from utilities.string import title
@ -209,6 +210,10 @@ class CachedValueSearchBackend(SearchBackend):
break
# 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)
custom_fields = CustomField.objects.filter(object_types=object_type).exclude(search_weight=0)

View File

@ -1,8 +1,11 @@
from django.db import connection
from .string import title
__all__ = (
'object_type_identifier',
'object_type_name',
'objecttype_table_exists',
)
@ -27,3 +30,13 @@ def object_type_name(object_type, include_app=True):
except AttributeError:
# Model does not exist
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()