Reference ObjectType records instead of registry for feature support

This commit is contained in:
Jeremy Stretch 2025-07-22 11:31:53 -04:00
parent 749eed8f02
commit 0250271001
5 changed files with 33 additions and 22 deletions

View File

@ -9,9 +9,9 @@ from django.utils.translation import gettext as _
from django_rq import get_queue from django_rq import get_queue
from core.events import * from core.events import *
from core.models import ObjectType
from netbox.config import get_config from netbox.config import get_config
from netbox.constants import RQ_QUEUE_DEFAULT from netbox.constants import RQ_QUEUE_DEFAULT
from netbox.registry import registry
from users.models import User from users.models import User
from utilities.api import get_serializer_for_model from utilities.api import get_serializer_for_model
from utilities.rqworker import get_rq_retry from utilities.rqworker import get_rq_retry
@ -55,11 +55,12 @@ def enqueue_event(queue, instance, user, request_id, event_type):
Enqueue a serialized representation of a created/updated/deleted object for the processing of Enqueue a serialized representation of a created/updated/deleted object for the processing of
events once the request has completed. events once the request has completed.
""" """
# Determine whether this type of object supports event rules # Bail if this type of object does not support event rules
if 'event_rules' not in ObjectType.objects.get_for_model(instance).features:
return
app_label = instance._meta.app_label app_label = instance._meta.app_label
model_name = instance._meta.model_name model_name = instance._meta.model_name
if model_name not in registry['model_features']['event_rules'].get(app_label, []):
return
assert instance.pk is not None assert instance.pk is not None
key = f'{app_label}.{model_name}:{instance.pk}' key = f'{app_label}.{model_name}:{instance.pk}'

View File

@ -1,15 +1,16 @@
from django.apps import apps from collections import defaultdict
from django.conf import settings from django.conf import settings
from django.core.validators import ValidationError from django.core.validators import ValidationError
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from core.models import ObjectType
from extras.models.mixins import RenderTemplateMixin from extras.models.mixins import RenderTemplateMixin
from extras.querysets import ConfigContextQuerySet from extras.querysets import ConfigContextQuerySet
from netbox.models import ChangeLoggedModel from netbox.models import ChangeLoggedModel
from netbox.models.features import CloningMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin from netbox.models.features import CloningMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin
from netbox.registry import registry
from utilities.data import deepmerge from utilities.data import deepmerge
__all__ = ( __all__ = (
@ -239,15 +240,12 @@ class ConfigTemplate(
sync_data.alters_data = True sync_data.alters_data = True
def get_context(self, context=None, queryset=None): def get_context(self, context=None, queryset=None):
_context = dict() _context = defaultdict(dict)
for app, model_names in registry['models'].items():
_context.setdefault(app, {}) # Populate all public models for reference within the template
for model_name in model_names: for object_type in ObjectType.objects.public():
try: if model := object_type.model_class():
model = apps.get_registered_model(app, model_name) _context[object_type.app_label][model.__name__] = model
_context[app][model.__name__] = model
except LookupError:
pass
# Apply the provided context data, if any # Apply the provided context data, if any
if context is not None: if context is not None:

View File

@ -8,7 +8,6 @@ from core.signals import job_end, job_start
from extras.events import process_event_rules from extras.events import process_event_rules
from extras.models import EventRule, Notification, Subscription from extras.models import EventRule, Notification, Subscription
from netbox.config import get_config from netbox.config import get_config
from netbox.registry import registry
from netbox.signals import post_clean from netbox.signals import post_clean
from utilities.exceptions import AbortRequest from utilities.exceptions import AbortRequest
from .models import CustomField, TaggedItem from .models import CustomField, TaggedItem
@ -150,17 +149,24 @@ def notify_object_changed(sender, instance, **kwargs):
event_type = OBJECT_DELETED event_type = OBJECT_DELETED
# Skip unsupported object types # Skip unsupported object types
ct = ContentType.objects.get_for_model(instance) object_type = ObjectType.objects.get_for_model(instance)
if ct.model not in registry['model_features']['notifications'].get(ct.app_label, []): if 'notifications' not in object_type.features:
return return
# Find all subscribed Users # Find all subscribed Users
subscribed_users = Subscription.objects.filter(object_type=ct, object_id=instance.pk).values_list('user', flat=True) subscribed_users = Subscription.objects.filter(
object_type=object_type,
object_id=instance.pk
).values_list('user', flat=True)
if not subscribed_users: if not subscribed_users:
return return
# Delete any existing Notifications for the object # Delete any existing Notifications for the object
Notification.objects.filter(object_type=ct, object_id=instance.pk, user__in=subscribed_users).delete() Notification.objects.filter(
object_type=object_type,
object_id=instance.pk,
user__in=subscribed_users
).delete()
# Create Notifications for Subscribers # Create Notifications for Subscribers
Notification.objects.bulk_create([ Notification.objects.bulk_create([

View File

@ -32,6 +32,7 @@ __all__ = (
'CustomValidationMixin', 'CustomValidationMixin',
'EventRulesMixin', 'EventRulesMixin',
'ExportTemplatesMixin', 'ExportTemplatesMixin',
'FEATURES_MAP',
'ImageAttachmentsMixin', 'ImageAttachmentsMixin',
'JobsMixin', 'JobsMixin',
'JournalingMixin', 'JournalingMixin',
@ -633,6 +634,7 @@ FEATURES_MAP = {
'tags': TagsMixin, 'tags': TagsMixin,
} }
# TODO: Remove in NetBox v4.5
registry['model_features'].update({ registry['model_features'].update({
feature: defaultdict(set) for feature in FEATURES_MAP.keys() feature: defaultdict(set) for feature in FEATURES_MAP.keys()
}) })
@ -653,10 +655,12 @@ def register_models(*models):
for model in models: for model in models:
app_label, model_name = model._meta.label_lower.split('.') app_label, model_name = model._meta.label_lower.split('.')
# TODO: Remove in NetBox v4.5
# Register public models # Register public models
if not getattr(model, '_netbox_private', False): if not getattr(model, '_netbox_private', False):
registry['models'][app_label].add(model_name) registry['models'][app_label].add(model_name)
# TODO: Remove in NetBox v4.5
# Record each applicable feature for the model in the registry # Record each applicable feature for the model in the registry
features = { features = {
feature for feature, cls in FEATURES_MAP.items() if issubclass(model, cls) feature for feature, cls in FEATURES_MAP.items() if issubclass(model, cls)

View File

@ -6,6 +6,7 @@ from django.test import Client, TestCase, override_settings
from django.urls import reverse from django.urls import reverse
from core.choices import JobIntervalChoices from core.choices import JobIntervalChoices
from core.models import ObjectType
from netbox.tests.dummy_plugin import config as dummy_config from netbox.tests.dummy_plugin import config as dummy_config
from netbox.tests.dummy_plugin.data_backends import DummyBackend from netbox.tests.dummy_plugin.data_backends import DummyBackend
from netbox.tests.dummy_plugin.jobs import DummySystemJob from netbox.tests.dummy_plugin.jobs import DummySystemJob
@ -23,8 +24,9 @@ class PluginTest(TestCase):
self.assertIn('netbox.tests.dummy_plugin.DummyPluginConfig', settings.INSTALLED_APPS) self.assertIn('netbox.tests.dummy_plugin.DummyPluginConfig', settings.INSTALLED_APPS)
def test_model_registration(self): def test_model_registration(self):
self.assertIn('dummy_plugin', registry['models']) self.assertIsNone(
self.assertIn('dummymodel', registry['models']['dummy_plugin']) ObjectType.objects.filter(app_label='dummy_plugin', model='dummymodel')
)
def test_models(self): def test_models(self):
from netbox.tests.dummy_plugin.models import DummyModel from netbox.tests.dummy_plugin.models import DummyModel