Fixes #20290: Avoid exceptions when upgrading to v4.4 from early releases due to missing ObjectTypes table
Some checks are pending
CI / build (20.x, 3.10) (push) Waiting to run
CI / build (20.x, 3.11) (push) Waiting to run
CI / build (20.x, 3.12) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, actions) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
CodeQL / Analyze (${{ matrix.language }}) (none, python) (push) Waiting to run

This commit is contained in:
Jeremy Stretch 2025-10-06 14:11:45 -04:00
parent 33d4759871
commit 5ceb6a60da
2 changed files with 17 additions and 4 deletions

View File

@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.indexes import GinIndex
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import models from django.db import connection, models
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -66,6 +66,14 @@ class ObjectTypeManager(models.Manager):
""" """
from netbox.models.features import get_model_features, model_is_public from netbox.models.features import get_model_features, model_is_public
# TODO: Remove this in NetBox v5.0
# If the ObjectType table has not yet been provisioned (e.g. because we're in a pre-v4.4 migration),
# fall back to ContentType.
if 'core_objecttype' not in connection.introspection.table_names():
ct = ContentType.objects.get_for_model(model, for_concrete_model=for_concrete_model)
ct.features = get_model_features(ct.model_class())
return ct
if not inspect.isclass(model): if not inspect.isclass(model):
model = model.__class__ model = model.__class__
opts = self._get_opts(model, for_concrete_model) opts = self._get_opts(model, for_concrete_model)

View File

@ -673,10 +673,15 @@ def has_feature(model_or_ct, feature):
# 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
# If a ContentType was passed, resolve its model class # If a ContentType was passed, resolve its model class and run the associated feature test
elif type(model_or_ct) is ContentType: elif type(model_or_ct) is ContentType:
model_class = model_or_ct.model_class() model = model_or_ct.model_class()
ot = ObjectType.objects.get_for_model(model_class) if model_class else None try:
test_func = registry['model_features'][feature]
except KeyError:
# Unknown feature
return False
return test_func(model)
# For anything else, look up the ObjectType # For anything else, look up the ObjectType
else: else:
ot = ObjectType.objects.get_for_model(model_or_ct) ot = ObjectType.objects.get_for_model(model_or_ct)