diff --git a/README.md b/README.md index 7dfaf71..777b827 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ A NetBox plugin that dynamically reloads plugins without requiring a server rest - Dynamically registers plugin models that were missed during server startup - Refreshes custom field form definitions to include newly registered models +- Refreshes tag form definitions to include newly registered models - Helps solve integration issues between NetBox and other plugins - No configuration required - works out of the box @@ -14,6 +15,9 @@ A NetBox plugin that dynamically reloads plugins without requiring a server rest | NetBox Version | Plugin Version | |----------------|---------------| | 4.2.x | 0.0.2 | +| 4.3.x | 4.3.x | + +**Version Format**: X.X.Y where X.X = NetBox version (e.g., 4.3) and Y = plugin version increment ## Installation @@ -54,9 +58,10 @@ When NetBox starts, Plugin Reloader: 1. Scans all enabled plugins for models that aren't properly registered in NetBox's feature registry 2. Registers any missed models with NetBox's registration system -3. Refreshes form field definitions to ensure they include all registered models +3. Refreshes custom field form definitions to ensure they include all registered models +4. Refreshes tag form definitions to ensure they include all registered models -This helps resolve issues where plugins might not fully integrate with NetBox due to load order problems without requiring a server restart. +This helps resolve issues where plugins might not fully integrate with NetBox due to load order problems without requiring a server restart. The reloader specifically updates custom field choices and tag choices to include newly registered plugin models. ## Contributing @@ -71,4 +76,4 @@ This project is licensed under the MIT License - see the LICENSE file for detail Jan Krupa ## Links -- Based on https://github.com/netbox-community/netbox/discussions/17836 +- Based on https://github.com/netbox-community/netbox/discussions/17836 \ No newline at end of file diff --git a/netbox_plugin_reloader/__init__.py b/netbox_plugin_reloader/__init__.py index adfa386..c71daf7 100644 --- a/netbox_plugin_reloader/__init__.py +++ b/netbox_plugin_reloader/__init__.py @@ -3,6 +3,7 @@ """ from netbox.plugins import PluginConfig + from netbox_plugin_reloader.version import __version__ @@ -19,149 +20,97 @@ class NetboxPluginReloaderConfig(PluginConfig): description = "Dynamically reload NetBox plugins without server restart" version = __version__ base_url = "netbox-plugin-reloader" - - # Plugin configuration - default_settings = {} - required_settings = [] - - # NetBox version compatibility min_version = "4.3.0" max_version = "4.3.99" def ready(self): """ - Plugin initialization logic executed when Django loads the application. - - This method handles the dynamic registration of plugin models and - refreshes form fields to ensure all plugins are properly loaded. + Initializes the plugin when the Django application loads. + + Registers any plugin models missed during startup and refreshes form fields to include newly registered models for custom fields and tags. """ - # Initialize parent plugin functionality super().ready() - # Import dependencies from core.models import ObjectType from django.apps import apps from django.conf import settings from django.utils.translation import gettext_lazy as _ - from extras.forms.model_forms import CustomFieldForm + from extras.forms.model_forms import CustomFieldForm, TagForm from netbox.models.features import FEATURES_MAP, register_models from netbox.registry import registry from utilities.forms.fields import ContentTypeMultipleChoiceField - # Step 1: Register any plugin models missed during initial application startup - self._register_missing_plugin_models( - plugin_list=settings.PLUGINS, - app_registry=apps, - netbox_registry=registry, - feature_mixins_map=FEATURES_MAP, - model_register_function=register_models, - ) + # Register missing plugin models + self._register_missing_plugin_models(settings.PLUGINS, apps, registry, FEATURES_MAP, register_models) - # Step 2: Ensure form fields for plugins are properly initialized - self._refresh_custom_field_form( - form_class=CustomFieldForm, - field_class=ContentTypeMultipleChoiceField, - object_type_class=ObjectType, - translation_function=_, - ) + # Refresh form fields + self._refresh_form_field(CustomFieldForm, "custom_fields", ObjectType, ContentTypeMultipleChoiceField, _) + self._refresh_form_field(TagForm, "tags", ObjectType, ContentTypeMultipleChoiceField, _) def _register_missing_plugin_models( - self, - plugin_list, - app_registry, - netbox_registry, - feature_mixins_map, - model_register_function, + self, plugin_list, app_registry, netbox_registry, feature_mixins_map, model_register_function ): """ - Register plugin models that weren't properly registered during application startup. - - This method scans all enabled plugins, identifies models that haven't been - registered in NetBox's feature registry, and registers them. - - Args: - plugin_list: List of enabled plugin names from settings - app_registry: Django application registry - netbox_registry: NetBox's internal registry for tracking features - feature_mixins_map: Dictionary mapping feature names to mixin classes - model_register_function: Function used to register models with NetBox + Registers plugin models that were not registered during initial application startup. + + Iterates through the provided list of plugin names, identifies models that are missing from the NetBox feature registry, and registers them using the supplied registration function. Prints errors encountered during processing and reports the number of models registered if any were missed. """ unregistered_models = [] - # For each enabled plugin for plugin_name in plugin_list: try: - # Get the Django app configuration for this plugin plugin_app_config = app_registry.get_app_config(plugin_name) app_label = plugin_app_config.label - # Check each model in the plugin for model_class in plugin_app_config.get_models(): model_name = model_class._meta.model_name - - # Only register models that aren't already in the registry - if not self._is_model_registered( - app_label=app_label, - model_name=model_name, - registry=netbox_registry, - feature_mixins_map=feature_mixins_map, - ): + if not self._is_model_registered(app_label, model_name, netbox_registry, feature_mixins_map): unregistered_models.append(model_class) except Exception as e: - # Safely handle errors with specific plugins print(f"Error processing plugin {plugin_name}: {e}") - # Register the collected models if any were found if unregistered_models: model_register_function(*unregistered_models) print(f"Plugin Reloader: Registered {len(unregistered_models)} previously missed models") def _is_model_registered(self, app_label, model_name, registry, feature_mixins_map): """ - Check if a model is already registered in any NetBox feature registry. - - Args: - app_label: Django application label (e.g., 'dcim', 'ipam') - model_name: Model name without the app label - registry: NetBox registry containing feature registrations - feature_mixins_map: Dictionary mapping feature names to mixin classes - + Determines whether a model is registered under any NetBox feature. + Returns: - bool: True if model is registered in any feature, False otherwise + True if the specified model is present in any feature registry; otherwise, False. """ - # Check each available feature registry - for feature_name in feature_mixins_map.keys(): - feature_registry = registry["model_features"][feature_name] - - # If the app_label exists and the model is registered under it - if app_label in feature_registry and model_name in feature_registry[app_label]: - return True - - # Model not found in any feature registry - return False + return any( + app_label in registry["model_features"][feature_name] + and model_name in registry["model_features"][feature_name][app_label] + for feature_name in feature_mixins_map.keys() + ) - def _refresh_custom_field_form(self, form_class, field_class, object_type_class, translation_function): + def _refresh_form_field(self, form_class, feature_name, object_type_class, field_class, translation_function): """ - Refresh form field definitions for custom fields. - - This ensures that plugin models are properly included in form field choices - after they've been registered. - + Updates a form class's object_types field to reflect models supporting a specific NetBox feature. + Args: - form_class: The CustomFieldForm class to update - field_class: Field class to use for the object_types field - object_type_class: The ObjectType model class - translation_function: Function for internationalizing strings + form_class: The form class to update. + feature_name: The NetBox feature name (e.g., "custom_fields", "tags"). + object_type_class: The ContentType-like class used to query object types. + field_class: The form field class to instantiate. + translation_function: Function used to translate field labels and help texts. """ - # Create a field that includes all models with custom_fields feature + field_labels = { + "custom_fields": ("Object types", "The type(s) of object that have this custom field"), + "tags": ("Object types", "The type(s) of object that can have this tag"), + } + + label, help_text = field_labels[feature_name] + object_types_field = field_class( - label=translation_function("Object types"), - queryset=object_type_class.objects.with_feature("custom_fields"), - help_text=translation_function("The type(s) of object that have this custom field"), + label=translation_function(label), + queryset=object_type_class.objects.with_feature(feature_name), + help_text=translation_function(help_text), ) - # Update the form definition form_class.base_fields["object_types"] = object_types_field diff --git a/netbox_plugin_reloader/version.py b/netbox_plugin_reloader/version.py index 2831498..8af6e9c 100644 --- a/netbox_plugin_reloader/version.py +++ b/netbox_plugin_reloader/version.py @@ -1,3 +1,3 @@ """Version information.""" -__version__ = "4.3.0" +__version__ = "4.3.1" diff --git a/setup.py b/setup.py index 6ce1905..eaac276 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,6 @@ def get_version(rel_path): 'Framework :: Django :: 5.0', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Operating System :: OS Independent', ],