From 5cc55d1e993d4d21655a7f536dd1ce299755784b Mon Sep 17 00:00:00 2001 From: Patrick Hurrelmann Date: Tue, 27 Sep 2022 17:24:19 +0200 Subject: [PATCH 1/7] Fixes: #10465 Format all remaining displayed rackunits with floatformat (#10481) * Fixes: #10465 Try to finish #10268 and format all remaining displayed rackunits with floatformat * #10465: PEP8 fix Co-authored-by: Patrick Hurrelmann Co-authored-by: jeremystretch --- netbox/dcim/tables/devicetypes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index bc596a297..c48e93ca7 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -92,6 +92,9 @@ class DeviceTypeTable(NetBoxTable): template_code=DEVICE_WEIGHT, order_by=('_abs_weight', 'weight_unit') ) + u_height = columns.TemplateColumn( + template_code='{{ value|floatformat }}' + ) class Meta(NetBoxTable.Meta): model = DeviceType From ac7db3cc88dcb8159df3256c64b5f8dda642050e Mon Sep 17 00:00:00 2001 From: Jonathan Senecal Date: Tue, 20 Sep 2022 15:50:33 -0400 Subject: [PATCH 2/7] Tidy-up imports and typing (cherry picked from commit adee5cf6a8856ceda0170a4382cec8fd784be93b) --- netbox/netbox/settings.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index cfd4d231c..a0e8f5ffa 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -1,18 +1,17 @@ import hashlib import importlib -import logging import os import platform -import re -import socket import sys import warnings from urllib.parse import urlsplit +import django import sentry_sdk from django.contrib.messages import constants as messages from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.validators import URLValidator +from django.utils.encoding import force_str from sentry_sdk.integrations.django import DjangoIntegration from netbox.config import PARAMS @@ -20,9 +19,7 @@ from netbox.config import PARAMS # Monkey patch to fix Django 4.0 support for graphene-django (see # https://github.com/graphql-python/graphene-django/issues/1284) # TODO: Remove this when graphene-django 2.16 becomes available -import django -from django.utils.encoding import force_str -django.utils.encoding.force_text = force_str +django.utils.encoding.force_text = force_str # type: ignore # @@ -186,7 +183,7 @@ if STORAGE_BACKEND is not None: if STORAGE_BACKEND.startswith('storages.'): try: - import storages.utils + import storages.utils # type: ignore except ModuleNotFoundError as e: if getattr(e, 'name') == 'storages': raise ImproperlyConfigured( From dc522a0135df41f0905c114ad40ffc5026287f47 Mon Sep 17 00:00:00 2001 From: Jonathan Senecal Date: Tue, 20 Sep 2022 17:55:44 -0400 Subject: [PATCH 3/7] Initial implementation - Allows to specify a list of django-apps to be "installed" alongside the plugin. (cherry picked from commit 6c7296200d756d2acbba3a589a7759f3a690cc48) --- netbox/extras/plugins/__init__.py | 3 +++ netbox/netbox/settings.py | 34 +++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/netbox/extras/plugins/__init__.py b/netbox/extras/plugins/__init__.py index ef1106aea..3efa9aaa7 100644 --- a/netbox/extras/plugins/__init__.py +++ b/netbox/extras/plugins/__init__.py @@ -55,6 +55,9 @@ class PluginConfig(AppConfig): # Django-rq queues dedicated to the plugin queues = [] + # Django apps to append to INSTALLED_APPS when plugin requires them. + django_apps = [] + # Default integration paths. Plugin authors can override these to customize the paths to # integrated components. graphql_schema = 'graphql.schema' diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index a0e8f5ffa..ed225da52 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -1,5 +1,6 @@ import hashlib import importlib +import importlib.util import os import platform import sys @@ -12,6 +13,7 @@ from django.contrib.messages import constants as messages from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.validators import URLValidator from django.utils.encoding import force_str +from extras.plugins import PluginConfig from sentry_sdk.integrations.django import DjangoIntegration from netbox.config import PARAMS @@ -660,14 +662,42 @@ for plugin_name in PLUGINS: # Determine plugin config and add to INSTALLED_APPS. try: - plugin_config = plugin.config - INSTALLED_APPS.append("{}.{}".format(plugin_config.__module__, plugin_config.__name__)) + plugin_config: PluginConfig = plugin.config except AttributeError: raise ImproperlyConfigured( "Plugin {} does not provide a 'config' variable. This should be defined in the plugin's __init__.py file " "and point to the PluginConfig subclass.".format(plugin_name) ) + plugin_module = "{}.{}".format(plugin_config.__module__, plugin_config.__name__) # type: ignore + # Gather additionnal apps to load alongside this plugin + plugin_apps = plugin_config.django_apps + if plugin_name in plugin_apps: + plugin_apps.pop(plugin_name) + if plugin_module not in plugin_apps: + plugin_apps.append(plugin_module) + + # Test if we can import all modules (or its parent, for PluginConfigs and AppConfigs) + for app in plugin_apps: + if "." in app: + parts = app.split(".") + spec = importlib.util.find_spec(".".join(parts[:-1])) + else: + spec = importlib.util.find_spec(app) + if spec is None: + raise ImproperlyConfigured( + f"Plugin {plugin_name} provides a 'config' variable which contains invalid 'plugin_apps'. " + f"The module {app}, from this list, cannot be imported. Check that the additionnal app has been " + "installed within the correct Python environment." + ) + + + INSTALLED_APPS.extend(plugin_apps) + + # Preserve uniqueness of the INSTALLED_APPS list, we keep the last occurence + sorted_apps = reversed(list(dict.fromkeys(reversed(INSTALLED_APPS)))) + INSTALLED_APPS = list(sorted_apps) + # Validate user-provided configuration settings and assign defaults if plugin_name not in PLUGINS_CONFIG: PLUGINS_CONFIG[plugin_name] = {} From 5c1417c4c76cd09942695fcf71abc37d06689962 Mon Sep 17 00:00:00 2001 From: Jonathan Senecal Date: Wed, 28 Sep 2022 18:11:10 -0400 Subject: [PATCH 4/7] PEP8 fixes --- netbox/netbox/settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index ed225da52..0af5eaa1b 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -21,7 +21,7 @@ from netbox.config import PARAMS # Monkey patch to fix Django 4.0 support for graphene-django (see # https://github.com/graphql-python/graphene-django/issues/1284) # TODO: Remove this when graphene-django 2.16 becomes available -django.utils.encoding.force_text = force_str # type: ignore +django.utils.encoding.force_text = force_str # type: ignore # @@ -669,7 +669,8 @@ for plugin_name in PLUGINS: "and point to the PluginConfig subclass.".format(plugin_name) ) - plugin_module = "{}.{}".format(plugin_config.__module__, plugin_config.__name__) # type: ignore + plugin_module = "{}.{}".format(plugin_config.__module__, plugin_config.__name__) # type: ignore + # Gather additionnal apps to load alongside this plugin plugin_apps = plugin_config.django_apps if plugin_name in plugin_apps: @@ -691,7 +692,6 @@ for plugin_name in PLUGINS: "installed within the correct Python environment." ) - INSTALLED_APPS.extend(plugin_apps) # Preserve uniqueness of the INSTALLED_APPS list, we keep the last occurence From d4a7af8a896f9962a252af43863f559c5340c1d2 Mon Sep 17 00:00:00 2001 From: Jonathan Senecal Date: Thu, 29 Sep 2022 17:12:18 -0400 Subject: [PATCH 5/7] Update plugins development docs --- docs/plugins/development/index.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/plugins/development/index.md b/docs/plugins/development/index.md index 98db9e0bb..7c2334713 100644 --- a/docs/plugins/development/index.md +++ b/docs/plugins/development/index.md @@ -14,6 +14,7 @@ Plugins can do a lot, including: * Provide their own "pages" (views) in the web user interface * Inject template content and navigation links * Extend NetBox's REST and GraphQL APIs +* Load additionnal Django Apps * Add custom request/response middleware However, keep in mind that each piece of functionality is entirely optional. For example, if your plugin merely adds a piece of middleware or an API endpoint for existing data, there's no need to define any new models. @@ -82,6 +83,7 @@ class FooBarConfig(PluginConfig): default_settings = { 'baz': True } + django_apps = ["foo", "bar", "baz"] config = FooBarConfig ``` @@ -101,6 +103,7 @@ NetBox looks for the `config` variable within a plugin's `__init__.py` to load i | `base_url` | Base path to use for plugin URLs (optional). If not specified, the project's `name` will be used. | | `required_settings` | A list of any configuration parameters that **must** be defined by the user | | `default_settings` | A dictionary of configuration parameters and their default values | +| `django_apps` | A list of additionnal apps to load alongside the plugin | | `min_version` | Minimum version of NetBox with which the plugin is compatible | | `max_version` | Maximum version of NetBox with which the plugin is compatible | | `middleware` | A list of middleware classes to append after NetBox's build-in middleware | @@ -112,6 +115,15 @@ NetBox looks for the `config` variable within a plugin's `__init__.py` to load i All required settings must be configured by the user. If a configuration parameter is listed in both `required_settings` and `default_settings`, the default setting will be ignored. +#### Important notes about `django_apps` + +Loading additional apps may cause more harm than good and could lead to make identifying problems within NetBox itself more difficult. The `django_apps` attribute is intented to be used only for advanced use-cases that require a deeper Django integration. + +Apps from this list are inserted *before* the plugin's `PluginConfig` in the same order. Adding the plugin's `PluginConfig` module to this list changes this behavior and allows for apps to be loaded *after* the plugin. + +Any additionnal app must be installed within the the same Python environment as NetBox or `ImproperlyConfigured` exceptions will be raised when loading the plugin. + + ## Create setup.py `setup.py` is the [setup script](https://docs.python.org/3.8/distutils/setupscript.html) used to package and install our plugin once it's finished. The primary function of this script is to call the setuptools library's `setup()` function to create a Python distribution package. We can pass a number of keyword arguments to control the package creation as well as to provide metadata about the plugin. An example `setup.py` is below: From 0607295081b0f56bcfb3b6ab60925446b95baeaf Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 30 Sep 2022 16:49:49 -0400 Subject: [PATCH 6/7] Docs cleanup --- docs/plugins/development/index.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/plugins/development/index.md b/docs/plugins/development/index.md index 7c2334713..d5aea0591 100644 --- a/docs/plugins/development/index.md +++ b/docs/plugins/development/index.md @@ -14,7 +14,7 @@ Plugins can do a lot, including: * Provide their own "pages" (views) in the web user interface * Inject template content and navigation links * Extend NetBox's REST and GraphQL APIs -* Load additionnal Django Apps +* Load additional Django apps * Add custom request/response middleware However, keep in mind that each piece of functionality is entirely optional. For example, if your plugin merely adds a piece of middleware or an API endpoint for existing data, there's no need to define any new models. @@ -103,7 +103,7 @@ NetBox looks for the `config` variable within a plugin's `__init__.py` to load i | `base_url` | Base path to use for plugin URLs (optional). If not specified, the project's `name` will be used. | | `required_settings` | A list of any configuration parameters that **must** be defined by the user | | `default_settings` | A dictionary of configuration parameters and their default values | -| `django_apps` | A list of additionnal apps to load alongside the plugin | +| `django_apps` | A list of additional Django apps to load alongside the plugin | | `min_version` | Minimum version of NetBox with which the plugin is compatible | | `max_version` | Maximum version of NetBox with which the plugin is compatible | | `middleware` | A list of middleware classes to append after NetBox's build-in middleware | @@ -115,14 +115,13 @@ NetBox looks for the `config` variable within a plugin's `__init__.py` to load i All required settings must be configured by the user. If a configuration parameter is listed in both `required_settings` and `default_settings`, the default setting will be ignored. -#### Important notes about `django_apps` +#### Important Notes About `django_apps` -Loading additional apps may cause more harm than good and could lead to make identifying problems within NetBox itself more difficult. The `django_apps` attribute is intented to be used only for advanced use-cases that require a deeper Django integration. +Loading additional apps may cause more harm than good and could make identifying problems within NetBox itself more difficult. The `django_apps` attribute is intended only for advanced use cases that require a deeper Django integration. -Apps from this list are inserted *before* the plugin's `PluginConfig` in the same order. Adding the plugin's `PluginConfig` module to this list changes this behavior and allows for apps to be loaded *after* the plugin. - -Any additionnal app must be installed within the the same Python environment as NetBox or `ImproperlyConfigured` exceptions will be raised when loading the plugin. +Apps from this list are inserted *before* the plugin's `PluginConfig` in the order defined. Adding the plugin's `PluginConfig` module to this list changes this behavior and allows for apps to be loaded *after* the plugin. +Any additional apps must be installed within the same Python environment as NetBox or `ImproperlyConfigured` exceptions will be raised when loading the plugin. ## Create setup.py From f7860138c79402c30364254ff747c87d743a311b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 30 Sep 2022 17:01:37 -0400 Subject: [PATCH 7/7] Rename plugin_apps to django_apps for clarity --- netbox/netbox/settings.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 0af5eaa1b..a2a6f57a6 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -671,15 +671,15 @@ for plugin_name in PLUGINS: plugin_module = "{}.{}".format(plugin_config.__module__, plugin_config.__name__) # type: ignore - # Gather additionnal apps to load alongside this plugin - plugin_apps = plugin_config.django_apps - if plugin_name in plugin_apps: - plugin_apps.pop(plugin_name) - if plugin_module not in plugin_apps: - plugin_apps.append(plugin_module) + # Gather additional apps to load alongside this plugin + django_apps = plugin_config.django_apps + if plugin_name in django_apps: + django_apps.pop(plugin_name) + if plugin_module not in django_apps: + django_apps.append(plugin_module) # Test if we can import all modules (or its parent, for PluginConfigs and AppConfigs) - for app in plugin_apps: + for app in django_apps: if "." in app: parts = app.split(".") spec = importlib.util.find_spec(".".join(parts[:-1])) @@ -687,12 +687,12 @@ for plugin_name in PLUGINS: spec = importlib.util.find_spec(app) if spec is None: raise ImproperlyConfigured( - f"Plugin {plugin_name} provides a 'config' variable which contains invalid 'plugin_apps'. " - f"The module {app}, from this list, cannot be imported. Check that the additionnal app has been " + f"Failed to load django_apps specified by plugin {plugin_name}: {django_apps} " + f"The module {app} cannot be imported. Check that the necessary package has been " "installed within the correct Python environment." ) - INSTALLED_APPS.extend(plugin_apps) + INSTALLED_APPS.extend(django_apps) # Preserve uniqueness of the INSTALLED_APPS list, we keep the last occurence sorted_apps = reversed(list(dict.fromkeys(reversed(INSTALLED_APPS))))