From 34d9ecb7f3f3acd42852485596ce2783bd1c9750 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 24 Jul 2025 08:14:09 -0400 Subject: [PATCH] ObjectTypeManager should not inherit from ContentTypeManager --- .../migrations/0017_concrete_objecttype.py | 6 +-- netbox/core/models/contenttypes.py | 40 ++++++++++--------- netbox/core/signals.py | 12 +----- netbox/netbox/models/features.py | 15 ++++++- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/netbox/core/migrations/0017_concrete_objecttype.py b/netbox/core/migrations/0017_concrete_objecttype.py index 42b624376..ec8c3a751 100644 --- a/netbox/core/migrations/0017_concrete_objecttype.py +++ b/netbox/core/migrations/0017_concrete_objecttype.py @@ -2,8 +2,6 @@ import django.contrib.postgres.fields import django.db.models.deletion from django.db import migrations, models -import core.models.contenttypes - def populate_object_types(apps, schema_editor): """ @@ -69,9 +67,7 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'object types', }, bases=('contenttypes.contenttype',), - managers=[ - ('objects', core.models.contenttypes.ObjectTypeManager()), - ], + managers=[], ), # Create an ObjectType record for each ContentType migrations.RunPython( diff --git a/netbox/core/models/contenttypes.py b/netbox/core/models/contenttypes.py index 9418172e1..40a02ea2c 100644 --- a/netbox/core/models/contenttypes.py +++ b/netbox/core/models/contenttypes.py @@ -1,4 +1,4 @@ -from django.contrib.contenttypes.models import ContentType, ContentTypeManager +from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ObjectDoesNotExist from django.db import models @@ -29,7 +29,7 @@ class ObjectTypeQuerySet(models.QuerySet): return super().create(**kwargs) -class ObjectTypeManager(ContentTypeManager): +class ObjectTypeManager(models.Manager): def get_queryset(self): return ObjectTypeQuerySet(self.model, using=self._db) @@ -37,33 +37,35 @@ class ObjectTypeManager(ContentTypeManager): def create(self, **kwargs): return self.get_queryset().create(**kwargs) - def get_for_model(self, model, for_concrete_model=True): - """ - Return the ContentType object for a given model, creating the - ContentType if necessary. Lookups are cached so that subsequent lookups - for the same model don't hit the database. - """ - opts = self._get_opts(model, for_concrete_model) - try: - return self._get_from_cache(opts) - except KeyError: - pass + def _get_opts(self, model, for_concrete_model): + if for_concrete_model: + model = model._meta.concrete_model + return model._meta + + def get_by_natural_key(self, app_label, model): + return self.get(app_label=app_label, model=model) + + def get_for_id(self, id): + return self.get(pk=id) + + def get_for_model(self, model, for_concrete_model=True): + from netbox.models.features import get_model_features, model_is_public + opts = self._get_opts(model, for_concrete_model) - # The ContentType entry was not found in the cache, therefore we - # proceed to load or create it. try: # Start with get() and not get_or_create() in order to use # the db_for_read (see #20401). - ct = self.get(app_label=opts.app_label, model=opts.model_name) + ot = self.get(app_label=opts.app_label, model=opts.model_name) except self.model.DoesNotExist: # Not found in the database; we proceed to create it. This time # use get_or_create to take care of any race conditions. - ct, __ = self.get_or_create( + ot, __ = self.get_or_create( app_label=opts.app_label, model=opts.model_name, + public=model_is_public(model), + features=get_model_features(model.__class__), ) - self._add_to_cache(self.db, ct) - return ct + return ot def public(self): """ diff --git a/netbox/core/signals.py b/netbox/core/signals.py index 50a360421..1c91e1d48 100644 --- a/netbox/core/signals.py +++ b/netbox/core/signals.py @@ -16,7 +16,7 @@ from extras.events import enqueue_event from extras.utils import run_validators from netbox.config import get_config from netbox.context import current_request, events_queue -from netbox.models.features import ChangeLoggingMixin, FEATURES_MAP +from netbox.models.features import ChangeLoggingMixin, get_model_features, model_is_public from utilities.exceptions import AbortRequest from .models import ConfigRevision, DataSource, ObjectChange @@ -40,16 +40,6 @@ post_sync = Signal() clear_events = Signal() -def model_is_public(model): - return not getattr(model, '_netbox_private', False) - - -def get_model_features(model): - return [ - feature for feature, cls in FEATURES_MAP.items() if issubclass(model, cls) - ] - - # # Object types # diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index ca6cbcbd0..db8bdb094 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -40,7 +40,9 @@ __all__ = ( 'NotificationsMixin', 'SyncedDataMixin', 'TagsMixin', + 'get_model_features', 'has_feature', + 'model_is_public', 'register_models', ) @@ -642,13 +644,24 @@ registry['model_features'].update({ }) +def model_is_public(model): + return not getattr(model, '_netbox_private', False) + + +def get_model_features(model): + return [ + feature for feature, cls in FEATURES_MAP.items() if issubclass(model, cls) + ] + + def has_feature(model, feature): """ Returns True if the model supports the specified feature. """ if type(model) is ContentType: model = model.model_class() - return feature in ObjectType.objects.get_for_model(model).features + ot = ObjectType.objects.get_for_model(model) + return feature in ot.features def register_models(*models):