Przeglądaj źródła

Closes #15042: Move model registration logic to AppConfigs (#15203)

* Closes #15042: Move model registration logic to AppConfigs

* Refactor register_model() to accept multiple models
Jeremy Stretch 2 lat temu
rodzic
commit
5f159795dd

+ 4 - 0
netbox/circuits/apps.py

@@ -6,4 +6,8 @@ class CircuitsConfig(AppConfig):
     verbose_name = "Circuits"
 
     def ready(self):
+        from netbox.models.features import register_models
         from . import signals, search
+
+        # Register models
+        register_models(*self.get_models())

+ 5 - 1
netbox/core/apps.py

@@ -16,5 +16,9 @@ class CoreConfig(AppConfig):
     name = "core"
 
     def ready(self):
+        from core.api import schema  # noqa
+        from netbox.models.features import register_models
         from . import data_backends, search
-        from core.api import schema  # noqa: E402
+
+        # Register models
+        register_models(*self.get_models())

+ 5 - 1
netbox/dcim/apps.py

@@ -8,9 +8,13 @@ class DCIMConfig(AppConfig):
     verbose_name = "DCIM"
 
     def ready(self):
+        from netbox.models.features import register_models
+        from utilities.counters import connect_counters
         from . import signals, search
         from .models import CableTermination, Device, DeviceType, VirtualChassis
-        from utilities.counters import connect_counters
+
+        # Register models
+        register_models(*self.get_models())
 
         # Register denormalized fields
         denormalized.register(CableTermination, '_device', {

+ 4 - 0
netbox/extras/apps.py

@@ -5,4 +5,8 @@ class ExtrasConfig(AppConfig):
     name = "extras"
 
     def ready(self):
+        from netbox.models.features import register_models
         from . import dashboard, lookups, search, signals
+
+        # Register models
+        register_models(*self.get_models())

+ 0 - 20
netbox/extras/utils.py

@@ -1,7 +1,5 @@
 from taggit.managers import _TaggableManager
 
-from netbox.registry import registry
-
 
 def is_taggable(obj):
     """
@@ -29,24 +27,6 @@ def image_upload(instance, filename):
     return '{}{}_{}_{}'.format(path, instance.content_type.name, instance.object_id, filename)
 
 
-def register_features(model, features):
-    """
-    Register model features in the application registry.
-    """
-    app_label, model_name = model._meta.label_lower.split('.')
-    for feature in features:
-        try:
-            registry['model_features'][feature][app_label].add(model_name)
-        except KeyError:
-            raise KeyError(
-                f"{feature} is not a valid model feature! Valid keys are: {registry['model_features'].keys()}"
-            )
-
-    # Register public models
-    if not getattr(model, '_netbox_private', False):
-        registry['models'][app_label].add(model_name)
-
-
 def is_script(obj):
     """
     Returns True if the object is a Script or Report.

+ 4 - 0
netbox/ipam/apps.py

@@ -6,4 +6,8 @@ class IPAMConfig(AppConfig):
     verbose_name = "IPAM"
 
     def ready(self):
+        from netbox.models.features import register_models
         from . import signals, search
+
+        # Register models
+        register_models(*self.get_models())

+ 48 - 36
netbox/netbox/models/features.py

@@ -5,8 +5,6 @@ from functools import cached_property
 from django.contrib.contenttypes.fields import GenericRelation
 from django.core.validators import ValidationError
 from django.db import models
-from django.db.models.signals import class_prepared
-from django.dispatch import receiver
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 from taggit.managers import TaggableManager
@@ -14,7 +12,7 @@ from taggit.managers import TaggableManager
 from core.choices import JobStatusChoices
 from core.models import ContentType
 from extras.choices import *
-from extras.utils import is_taggable, register_features
+from extras.utils import is_taggable
 from netbox.config import get_config
 from netbox.registry import registry
 from netbox.signals import post_clean
@@ -37,6 +35,7 @@ __all__ = (
     'JournalingMixin',
     'SyncedDataMixin',
     'TagsMixin',
+    'register_models',
 )
 
 
@@ -576,36 +575,49 @@ registry['model_features'].update({
 })
 
 
-@receiver(class_prepared)
-def _register_features(sender, **kwargs):
-    # Record each applicable feature for the model in the registry
-    features = {
-        feature for feature, cls in FEATURES_MAP.items() if issubclass(sender, cls)
-    }
-    register_features(sender, features)
-
-    # Register applicable feature views for the model
-    if issubclass(sender, JournalingMixin):
-        register_model_view(
-            sender,
-            'journal',
-            kwargs={'model': sender}
-        )('netbox.views.generic.ObjectJournalView')
-    if issubclass(sender, ChangeLoggingMixin):
-        register_model_view(
-            sender,
-            'changelog',
-            kwargs={'model': sender}
-        )('netbox.views.generic.ObjectChangeLogView')
-    if issubclass(sender, JobsMixin):
-        register_model_view(
-            sender,
-            'jobs',
-            kwargs={'model': sender}
-        )('netbox.views.generic.ObjectJobsView')
-    if issubclass(sender, SyncedDataMixin):
-        register_model_view(
-            sender,
-            'sync',
-            kwargs={'model': sender}
-        )('netbox.views.generic.ObjectSyncDataView')
+def register_models(*models):
+    """
+    Register one or more models in NetBox. This entails:
+
+     - Determining whether the model is considered "public" (available for reference by other models)
+     - Registering which features the model supports (e.g. bookmarks, custom fields, etc.)
+     - Registering any feature-specific views for the model (e.g. ObjectJournalView instances)
+
+    register_model() should be called for each relevant model under the ready() of an app's AppConfig class.
+    """
+    for model in models:
+        app_label, model_name = model._meta.label_lower.split('.')
+
+        # Register public models
+        if not getattr(model, '_netbox_private', False):
+            registry['models'][app_label].add(model_name)
+
+        # Record each applicable feature for the model in the registry
+        features = {
+            feature for feature, cls in FEATURES_MAP.items() if issubclass(model, cls)
+        }
+        for feature in features:
+            try:
+                registry['model_features'][feature][app_label].add(model_name)
+            except KeyError:
+                raise KeyError(
+                    f"{feature} is not a valid model feature! Valid keys are: {registry['model_features'].keys()}"
+                )
+
+        # Register applicable feature views for the model
+        if issubclass(model, JournalingMixin):
+            register_model_view(model, 'journal', kwargs={'model': model})(
+                'netbox.views.generic.ObjectJournalView'
+            )
+        if issubclass(model, ChangeLoggingMixin):
+            register_model_view(model, 'changelog', kwargs={'model': model})(
+                'netbox.views.generic.ObjectChangeLogView'
+            )
+        if issubclass(model, JobsMixin):
+            register_model_view(model, 'jobs', kwargs={'model': model})(
+                'netbox.views.generic.ObjectJobsView'
+            )
+        if issubclass(model, SyncedDataMixin):
+            register_model_view(model, 'sync', kwargs={'model': model})(
+                'netbox.views.generic.ObjectSyncDataView'
+            )

+ 5 - 0
netbox/netbox/plugins/__init__.py

@@ -94,6 +94,11 @@ class PluginConfig(AppConfig):
             pass
 
     def ready(self):
+        from netbox.models.features import register_models
+
+        # Register models
+        register_models(*self.get_models())
+
         plugin_name = self.name.rsplit('.', 1)[-1]
 
         # Register search extensions (if defined)

+ 4 - 0
netbox/netbox/tests/test_plugins.py

@@ -20,6 +20,10 @@ class PluginTest(TestCase):
 
         self.assertIn('netbox.tests.dummy_plugin.DummyPluginConfig', settings.INSTALLED_APPS)
 
+    def test_model_registration(self):
+        self.assertIn('dummy_plugin', registry['models'])
+        self.assertIn('dummymodel', registry['models']['dummy_plugin'])
+
     def test_models(self):
         from netbox.tests.dummy_plugin.models import DummyModel
 

+ 4 - 0
netbox/tenancy/apps.py

@@ -5,4 +5,8 @@ class TenancyConfig(AppConfig):
     name = 'tenancy'
 
     def ready(self):
+        from netbox.models.features import register_models
         from . import search
+
+        # Register models
+        register_models(*self.get_models())

+ 4 - 11
netbox/users/apps.py

@@ -5,15 +5,8 @@ class UsersConfig(AppConfig):
     name = 'users'
 
     def ready(self):
-        import users.signals
-        from .models import NetBoxGroup, ObjectPermission, Token, User, UserConfig
-        from netbox.models.features import _register_features
+        from netbox.models.features import register_models
+        from . import signals
 
-        # have to register these manually as the signal handler for class_prepared does
-        # not get registered until after these models are loaded. Any models defined in
-        # users.models should be registered here.
-        _register_features(NetBoxGroup)
-        _register_features(ObjectPermission)
-        _register_features(Token)
-        _register_features(User)
-        _register_features(UserConfig)
+        # Register models
+        register_models(*self.get_models())

+ 5 - 1
netbox/virtualization/apps.py

@@ -7,9 +7,13 @@ class VirtualizationConfig(AppConfig):
     name = 'virtualization'
 
     def ready(self):
+        from netbox.models.features import register_models
+        from utilities.counters import connect_counters
         from . import search, signals
         from .models import VirtualMachine
-        from utilities.counters import connect_counters
+
+        # Register models
+        register_models(*self.get_models())
 
         # Register denormalized fields
         denormalized.register(VirtualMachine, 'cluster', {

+ 4 - 0
netbox/vpn/apps.py

@@ -6,4 +6,8 @@ class VPNConfig(AppConfig):
     verbose_name = 'VPN'
 
     def ready(self):
+        from netbox.models.features import register_models
         from . import search
+
+        # Register models
+        register_models(*self.get_models())

+ 4 - 0
netbox/wireless/apps.py

@@ -5,4 +5,8 @@ class WirelessConfig(AppConfig):
     name = 'wireless'
 
     def ready(self):
+        from netbox.models.features import register_models
         from . import signals, search
+
+        # Register models
+        register_models(*self.get_models())