Skip to content

Commit

Permalink
Closes #15042: Move model registration logic to AppConfigs
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremystretch committed Feb 20, 2024
1 parent 7abb2b2 commit 62a0b52
Show file tree
Hide file tree
Showing 14 changed files with 105 additions and 65 deletions.
5 changes: 5 additions & 0 deletions netbox/circuits/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ class CircuitsConfig(AppConfig):
verbose_name = "Circuits"

def ready(self):
from netbox.models.features import register_model
from . import signals, search

# Register models
for model in self.get_models():
register_model(model)
7 changes: 6 additions & 1 deletion netbox/core/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,10 @@ class CoreConfig(AppConfig):
name = "core"

def ready(self):
from core.api import schema # noqa
from netbox.models.features import register_model
from . import data_backends, search
from core.api import schema # noqa: E402

# Register models
for model in self.get_models():
register_model(model)
7 changes: 6 additions & 1 deletion netbox/dcim/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ class DCIMConfig(AppConfig):
verbose_name = "DCIM"

def ready(self):
from netbox.models.features import register_model
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
for model in self.get_models():
register_model(model)

# Register denormalized fields
denormalized.register(CableTermination, '_device', {
Expand Down
5 changes: 5 additions & 0 deletions netbox/extras/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ class ExtrasConfig(AppConfig):
name = "extras"

def ready(self):
from netbox.models.features import register_model
from . import dashboard, lookups, search, signals

# Register models
for model in self.get_models():
register_model(model)
20 changes: 0 additions & 20 deletions netbox/extras/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from taggit.managers import _TaggableManager

from netbox.registry import registry


def is_taggable(obj):
"""
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions netbox/ipam/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ class IPAMConfig(AppConfig):
verbose_name = "IPAM"

def ready(self):
from netbox.models.features import register_model
from . import signals, search

# Register models
for model in self.get_models():
register_model(model)
73 changes: 42 additions & 31 deletions netbox/netbox/models/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
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

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
Expand All @@ -37,6 +35,7 @@
'JournalingMixin',
'SyncedDataMixin',
'TagsMixin',
'register_model',
)


Expand Down Expand Up @@ -576,36 +575,48 @@ def sync_data(self):
})


@receiver(class_prepared)
def _register_features(sender, **kwargs):
def register_model(model, **kwargs):
"""
Register a model 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.
"""
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(sender, cls)
feature for feature, cls in FEATURES_MAP.items() if issubclass(model, cls)
}
register_features(sender, features)
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(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')
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'
)
6 changes: 6 additions & 0 deletions netbox/netbox/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ def _load_resource(self, name):
pass

def ready(self):
from netbox.models.features import register_model

# Register models
for model in self.get_models():
register_model(model)

plugin_name = self.name.rsplit('.', 1)[-1]

# Register search extensions (if defined)
Expand Down
4 changes: 4 additions & 0 deletions netbox/netbox/tests/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ def test_config(self):

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

Expand Down
5 changes: 5 additions & 0 deletions netbox/tenancy/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ class TenancyConfig(AppConfig):
name = 'tenancy'

def ready(self):
from netbox.models.features import register_model
from . import search

# Register models
for model in self.get_models():
register_model(model)
16 changes: 5 additions & 11 deletions netbox/users/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,9 @@ 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_model
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
for model in self.get_models():
register_model(model)
7 changes: 6 additions & 1 deletion netbox/virtualization/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ class VirtualizationConfig(AppConfig):
name = 'virtualization'

def ready(self):
from netbox.models.features import register_model
from utilities.counters import connect_counters
from . import search, signals
from .models import VirtualMachine
from utilities.counters import connect_counters

# Register models
for model in self.get_models():
register_model(model)

# Register denormalized fields
denormalized.register(VirtualMachine, 'cluster', {
Expand Down
5 changes: 5 additions & 0 deletions netbox/vpn/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ class VPNConfig(AppConfig):
verbose_name = 'VPN'

def ready(self):
from netbox.models.features import register_model
from . import search

# Register models
for model in self.get_models():
register_model(model)
5 changes: 5 additions & 0 deletions netbox/wireless/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ class WirelessConfig(AppConfig):
name = 'wireless'

def ready(self):
from netbox.models.features import register_model
from . import signals, search

# Register models
for model in self.get_models():
register_model(model)

0 comments on commit 62a0b52

Please sign in to comment.