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

* Closes #15042: Move model registration logic to AppConfigs

* Refactor register_model() to accept multiple models
This commit is contained in:
Jeremy Stretch 2024-02-21 14:22:13 -05:00 committed by GitHub
parent 7abb2b2ab5
commit 5f159795dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 99 additions and 69 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,5 @@
from taggit.managers import _TaggableManager from taggit.managers import _TaggableManager
from netbox.registry import registry
def is_taggable(obj): def is_taggable(obj):
""" """
@ -29,24 +27,6 @@ def image_upload(instance, filename):
return '{}{}_{}_{}'.format(path, instance.content_type.name, instance.object_id, 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): def is_script(obj):
""" """
Returns True if the object is a Script or Report. Returns True if the object is a Script or Report.

View File

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

View File

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

View File

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

View File

@ -20,6 +20,10 @@ 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):
self.assertIn('dummy_plugin', registry['models'])
self.assertIn('dummymodel', registry['models']['dummy_plugin'])
def test_models(self): def test_models(self):
from netbox.tests.dummy_plugin.models import DummyModel from netbox.tests.dummy_plugin.models import DummyModel

View File

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

View File

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

View File

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

View File

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

View File

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