From 5ceb6a60da2f862169ad0d9aca99b6c0daee910c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 6 Oct 2025 14:11:45 -0400 Subject: [PATCH] Fixes #20290: Avoid exceptions when upgrading to v4.4 from early releases due to missing ObjectTypes table --- netbox/core/models/object_types.py | 10 +++++++++- netbox/netbox/models/features.py | 11 ++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/netbox/core/models/object_types.py b/netbox/core/models/object_types.py index bb031b4eb..ec9e2fef8 100644 --- a/netbox/core/models/object_types.py +++ b/netbox/core/models/object_types.py @@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.indexes import GinIndex 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.utils.translation import gettext as _ @@ -66,6 +66,14 @@ class ObjectTypeManager(models.Manager): """ 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): model = model.__class__ opts = self._get_opts(model, for_concrete_model) diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index 09c2722ad..be58d647a 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -673,10 +673,15 @@ def has_feature(model_or_ct, feature): # If an ObjectType was passed, we can use it directly if type(model_or_ct) is ObjectType: 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: - model_class = model_or_ct.model_class() - ot = ObjectType.objects.get_for_model(model_class) if model_class else None + model = model_or_ct.model_class() + try: + test_func = registry['model_features'][feature] + except KeyError: + # Unknown feature + return False + return test_func(model) # For anything else, look up the ObjectType else: ot = ObjectType.objects.get_for_model(model_or_ct)