From fbc9fea0a5b0943da6f10df3551f071ce555a6ed Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 27 Jan 2023 16:44:10 -0500 Subject: [PATCH 01/21] Fixes #11267: Avoid catching ImportError exceptions when loading plugins (#11566) * Avoid catching ImportErrors when loading plugin URLs * Avoid catching ImportErrors when loading plugin resources --- netbox/extras/plugins/__init__.py | 70 ++++++++++++++++--------------- netbox/extras/plugins/urls.py | 13 +++--- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/netbox/extras/plugins/__init__.py b/netbox/extras/plugins/__init__.py index 7694a1fbe..0b2123a4e 100644 --- a/netbox/extras/plugins/__init__.py +++ b/netbox/extras/plugins/__init__.py @@ -1,4 +1,5 @@ import collections +from importlib.util import find_spec from django.apps import AppConfig from django.conf import settings @@ -21,6 +22,15 @@ registry['plugins'] = { 'template_extensions': collections.defaultdict(list), } +DEFAULT_RESOURCE_PATHS = { + 'search_indexes': 'search.indexes', + 'graphql_schema': 'graphql.schema', + 'menu': 'navigation.menu', + 'menu_items': 'navigation.menu_items', + 'template_extensions': 'template_content.template_extensions', + 'user_preferences': 'preferences.preferences', +} + # # Plugin AppConfig class @@ -58,58 +68,50 @@ class PluginConfig(AppConfig): # 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. - search_indexes = 'search.indexes' - graphql_schema = 'graphql.schema' - menu = 'navigation.menu' - menu_items = 'navigation.menu_items' - template_extensions = 'template_content.template_extensions' - user_preferences = 'preferences.preferences' + # Optional plugin resources + search_indexes = None + graphql_schema = None + menu = None + menu_items = None + template_extensions = None + user_preferences = None + + def _load_resource(self, name): + # Import from the configured path, if defined. + if getattr(self, name): + return import_string(f"{self.__module__}.{self.name}") + # Fall back to the resource's default path. Return None if the module has not been provided. + default_path = DEFAULT_RESOURCE_PATHS[name] + default_module = f'{self.__module__}.{default_path}'.rsplit('.', 1)[0] + if find_spec(default_module): + setattr(self, name, default_path) + return import_string(f"{self.__module__}.{default_path}") def ready(self): plugin_name = self.name.rsplit('.', 1)[-1] # Register search extensions (if defined) - try: - search_indexes = import_string(f"{self.__module__}.{self.search_indexes}") - for idx in search_indexes: - register_search(idx) - except ImportError: - pass + search_indexes = self._load_resource('search_indexes') or [] + for idx in search_indexes: + register_search(idx) # Register template content (if defined) - try: - template_extensions = import_string(f"{self.__module__}.{self.template_extensions}") + if template_extensions := self._load_resource('template_extensions'): register_template_extensions(template_extensions) - except ImportError: - pass # Register navigation menu and/or menu items (if defined) - try: - menu = import_string(f"{self.__module__}.{self.menu}") + if menu := self._load_resource('menu'): register_menu(menu) - except ImportError: - pass - try: - menu_items = import_string(f"{self.__module__}.{self.menu_items}") + if menu_items := self._load_resource('menu_items'): register_menu_items(self.verbose_name, menu_items) - except ImportError: - pass # Register GraphQL schema (if defined) - try: - graphql_schema = import_string(f"{self.__module__}.{self.graphql_schema}") + if graphql_schema := self._load_resource('graphql_schema'): register_graphql_schema(graphql_schema) - except ImportError: - pass # Register user preferences (if defined) - try: - user_preferences = import_string(f"{self.__module__}.{self.user_preferences}") + if user_preferences := self._load_resource('user_preferences'): register_user_preferences(plugin_name, user_preferences) - except ImportError: - pass @classmethod def validate(cls, user_config, netbox_version): diff --git a/netbox/extras/plugins/urls.py b/netbox/extras/plugins/urls.py index b4360dc9e..2f237f56a 100644 --- a/netbox/extras/plugins/urls.py +++ b/netbox/extras/plugins/urls.py @@ -1,9 +1,11 @@ +from importlib import import_module + from django.apps import apps from django.conf import settings from django.conf.urls import include from django.contrib.admin.views.decorators import staff_member_required from django.urls import path -from django.utils.module_loading import import_string +from django.utils.module_loading import import_string, module_has_submodule from . import views @@ -19,24 +21,21 @@ plugin_admin_patterns = [ # Register base/API URL patterns for each plugin for plugin_path in settings.PLUGINS: + plugin = import_module(plugin_path) plugin_name = plugin_path.split('.')[-1] app = apps.get_app_config(plugin_name) base_url = getattr(app, 'base_url') or app.label # Check if the plugin specifies any base URLs - try: + if module_has_submodule(plugin, 'urls'): urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns") plugin_patterns.append( path(f"{base_url}/", include((urlpatterns, app.label))) ) - except ImportError: - pass # Check if the plugin specifies any API URLs - try: + if module_has_submodule(plugin, 'api.urls'): urlpatterns = import_string(f"{plugin_path}.api.urls.urlpatterns") plugin_api_patterns.append( path(f"{base_url}/", include((urlpatterns, f"{app.label}-api"))) ) - except ImportError: - pass From 0da518e83de6500bb25572fb9c2618c198915d7f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 27 Jan 2023 16:45:20 -0500 Subject: [PATCH 02/21] Changelog for #11267 --- docs/release-notes/version-3.4.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 4c9992f20..612e5bb74 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -11,6 +11,7 @@ ### Bug Fixes +* [#11267](https://github.com/netbox-community/netbox/issues/11267) - Avoid catching ImportErrors when loading plugin resources * [#11487](https://github.com/netbox-community/netbox/issues/11487) - Remove "set null" option from non-writable custom fields during bulk edit * [#11491](https://github.com/netbox-community/netbox/issues/11491) - Show edit/delete buttons in user tokens table * [#11528](https://github.com/netbox-community/netbox/issues/11528) - Permit import of devices using uploaded file From 892fd95b5f3813b9d3365ebe100c842e88480a41 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 27 Jan 2023 16:46:49 -0500 Subject: [PATCH 03/21] Update NetBox Cloud link --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index d61465443..6a53403d6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -52,4 +52,4 @@ NetBox is built on the enormously popular [Django](http://www.djangoproject.com/ * Try out our [public demo](https://demo.netbox.dev/) if you want to jump right in * The [installation guide](./installation/index.md) will help you get your own deployment up and running * Or try the community [Docker image](https://github.com/netbox-community/netbox-docker) for a low-touch approach -* [NetBox Cloud](https://www.getnetbox.io/) is a hosted solution offered by NS1 +* [NetBox Cloud](https://netboxlabs.com/netbox-cloud) is a managed solution offered by [NetBox Labs](https://netboxlabs.com/) From e7ad6eeb7482c3b5cd7f9b64738da7a52e9d5ebe Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 27 Jan 2023 19:56:12 -0500 Subject: [PATCH 04/21] Fixes #11613: Correct plugin import logic fix from #11267 --- netbox/extras/plugins/__init__.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/netbox/extras/plugins/__init__.py b/netbox/extras/plugins/__init__.py index 0b2123a4e..b56113ca1 100644 --- a/netbox/extras/plugins/__init__.py +++ b/netbox/extras/plugins/__init__.py @@ -1,5 +1,5 @@ import collections -from importlib.util import find_spec +from importlib import import_module from django.apps import AppConfig from django.conf import settings @@ -80,12 +80,15 @@ class PluginConfig(AppConfig): # Import from the configured path, if defined. if getattr(self, name): return import_string(f"{self.__module__}.{self.name}") + # Fall back to the resource's default path. Return None if the module has not been provided. - default_path = DEFAULT_RESOURCE_PATHS[name] - default_module = f'{self.__module__}.{default_path}'.rsplit('.', 1)[0] - if find_spec(default_module): - setattr(self, name, default_path) - return import_string(f"{self.__module__}.{default_path}") + default_path = f'{self.__module__}.{DEFAULT_RESOURCE_PATHS[name]}' + default_module, resource_name = default_path.rsplit('.', 1) + try: + module = import_module(default_module) + return getattr(module, resource_name, None) + except ModuleNotFoundError: + pass def ready(self): plugin_name = self.name.rsplit('.', 1)[-1] From 46ede62f3fbb9938f5fe19f03cb1df991db69fee Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 30 Jan 2023 10:25:20 -0500 Subject: [PATCH 05/21] Fix rendering of example code --- docs/plugins/development/navigation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/development/navigation.md b/docs/plugins/development/navigation.md index 63402c747..5f4a8a0dc 100644 --- a/docs/plugins/development/navigation.md +++ b/docs/plugins/development/navigation.md @@ -51,7 +51,7 @@ menu_items = (item1, item2, item3) Each menu item represents a link and (optionally) a set of buttons comprising one entry in NetBox's navigation menu. Menu items are defined as PluginMenuItem instances. An example is shown below. -```python filename="navigation.py" +```python title="navigation.py" from extras.plugins import PluginMenuButton, PluginMenuItem from utilities.choices import ButtonColorChoices From 10e27cfa00031c1875e3fd0c81776e6e3a44ad88 Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 31 Jan 2023 09:56:09 -0800 Subject: [PATCH 06/21] 11620 fix interface poe type filter --- netbox/dcim/forms/filtersets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index c00e83672..4dd2f73eb 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -1170,7 +1170,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): label='PoE mode' ) poe_type = MultipleChoiceField( - choices=InterfacePoEModeChoices, + choices=InterfacePoETypeChoices, required=False, label='PoE type' ) From a137cd6cbefa4654cc8f69c367b3258d3ccdadaa Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 1 Feb 2023 10:33:45 -0500 Subject: [PATCH 07/21] Fixes #11635: Pre-populate assigned VRF when following "first available IP" link from prefix view --- docs/release-notes/version-3.4.md | 2 ++ netbox/templates/ipam/prefix.html | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 612e5bb74..3c92bc61d 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -17,6 +17,8 @@ * [#11528](https://github.com/netbox-community/netbox/issues/11528) - Permit import of devices using uploaded file * [#11555](https://github.com/netbox-community/netbox/issues/11555) - Avoid inadvertent interpretation of search query as regular expression under global search (previously [#11516](https://github.com/netbox-community/netbox/issues/11516)) * [#11562](https://github.com/netbox-community/netbox/issues/11562) - Correct ordering of virtual chassis interfaces with duplicate names +* [#11620](https://github.com/netbox-community/netbox/issues/11620) - Correct available filter choices for interface PoE type +* [#11635](https://github.com/netbox-community/netbox/issues/11635) - Pre-populate assigned VRF when following "first available IP" link from prefix view --- diff --git a/netbox/templates/ipam/prefix.html b/netbox/templates/ipam/prefix.html index a0baf3325..6d986aed5 100644 --- a/netbox/templates/ipam/prefix.html +++ b/netbox/templates/ipam/prefix.html @@ -133,7 +133,7 @@ {% with first_available_ip=object.get_first_available_ip %} {% if first_available_ip %} {% if perms.ipam.add_ipaddress %} - {{ first_available_ip }} + {{ first_available_ip }} {% else %} {{ first_available_ip }} {% endif %} From fb2771370cdf7f1d17db8e0f3985785333372d79 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 2 Feb 2023 06:33:57 -0800 Subject: [PATCH 08/21] handled scripts error when only interval is used --- netbox/extras/forms/scripts.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/netbox/extras/forms/scripts.py b/netbox/extras/forms/scripts.py index 79dc8c869..8216c5413 100644 --- a/netbox/extras/forms/scripts.py +++ b/netbox/extras/forms/scripts.py @@ -45,12 +45,16 @@ class ScriptForm(BootstrapMixin, forms.Form): self.fields['_interval'] = interval self.fields['_commit'] = commit - def clean__schedule_at(self): + def clean(self): scheduled_time = self.cleaned_data['_schedule_at'] - if scheduled_time and scheduled_time < timezone.now(): + if scheduled_time and scheduled_time < local_now(): raise forms.ValidationError(_('Scheduled time must be in the future.')) - return scheduled_time + # When interval is used without schedule at, raise an exception + if self.cleaned_data['_interval'] and not scheduled_time: + raise forms.ValidationError(_('Scheduled time must be set when recurs is used.')) + + return self.cleaned_data @property def requires_input(self): From 98a2f3e4979fa473f5d508dae5f50913177a88c9 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 2 Feb 2023 14:18:32 -0500 Subject: [PATCH 09/21] Refresh the README --- README.md | 119 ++++++++++++++++++------------------------------------ 1 file changed, 39 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index e14f31b56..053aa8461 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,59 @@
NetBox logo + + The premiere source of truth powering network automation
+![Master branch build status](https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=master) + NetBox is the leading solution for modeling and documenting modern networks. By combining the traditional disciplines of IP address management (IPAM) and datacenter infrastructure management (DCIM) with powerful APIs and extensions, NetBox provides the ideal "source of truth" to power network automation. -Available as open source software under the Apache 2.0 license, NetBox is -employed by thousands of organizations around the world. +Available as open source software under the Apache 2.0 license, NetBox serves +as the cornerstone for network automation in thousands of organizations. -![Master branch build status](https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=master) - -[![Timeline graph](https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_timeline.svg)](https://github.com/netbox-community/netbox/commits) -[![Issue status graph](https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_issues.svg)](https://github.com/netbox-community/netbox/issues) -[![Pull request status graph](https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_prs.svg)](https://github.com/netbox-community/netbox/pulls) -[![Top contributors](https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_users.svg)](https://github.com/netbox-community/netbox/graphs/contributors) -
Stats via [Repography](https://repography.com) - -## About NetBox +* **Physical infrasucture:** Accurately model the physical world, from global regions down to individual racks of gear. Then connect everything - network, console, and power! +* **Modern IPAM:** All the standard IPAM functionality you expect, plus VRF import/export tracking, VLAN management, and overlay support. +* **Data circuits:** Confidently manage the delivery of crtical circuits from various service providers, modeled seamlessly alongside your own infrastructure. +* **Power tracking:** Map the distribution of power from upstream sources to individual feeds and outlets. +* **Organization:** Manage tenant and contact assignments natively. +* **Powerful search:** Easily find anything you need using a single global search function. +* **Comprehensive logging:** Leverage both automatic change logging and user-submitted journal entries to track your network's growth over time. +* **Endless customization:** Custom fields, custom links, tags, export templates, custom validation, reports, scripts, and more! +* **Flexible permissions:** An advanced permissions systems enables very flexible delegation of permissions. +* **Integrations:** Easily connect NetBox to your other tooling via its REST & GraphQL APIs. +* **Plugins:** Not finding what you need in the core application? Try one of many community plugins - or build your own! ![Screenshot of NetBox UI](docs/media/screenshots/netbox-ui.png "NetBox UI") -Myriad infrastructure components can be modeled in NetBox, including: +## Getting Started -* Hierarchical regions, site groups, sites, and locations -* Racks, devices, and device components -* Cables and wireless connections -* Power distribution -* Data circuits and providers -* Virtual machines and clusters -* IP prefixes, ranges, and addresses -* VRFs and route targets -* L2VPN and overlays -* FHRP groups (VRRP, HSRP, etc.) -* AS numbers -* VLANs and scoped VLAN groups -* Organizational tenants and contacts +* Just want to explore? Check out [our public demo](https://demo.netbox.dev/) right now! +* The [official documentation](https://docs.netbox.dev) offers a comprehensive introduction. +* Choose your deployment: [self-hosted](https://github.com/netbox-community/netbox), [Docker](https://github.com/netbox-community/netbox-docker), or [NetBox Cloud](https://netboxlabs.com/netbox-cloud/). +* Check out [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions) for even more projects to get the most out of NetBox! -In addition to its extensive built-in models and functionality, NetBox can be -customized and extended through the use of: +## Get Involved -* Custom fields -* Custom links -* Configuration contexts -* Custom model validation rules -* Reports -* Custom scripts -* Export templates -* Conditional webhooks -* Plugins -* Single sign-on (SSO) authentication -* NAPALM integration -* Detailed change logging +* Follow [@NetBoxOfficial](https://twitter.com/NetBoxOfficial) on Twitter! +* Join the conversation on [the discussion forum](https://github.com/netbox-community/netbox/discussions) and [Slack](https://netdev.chat/)! +* Already a power user? You can [suggest a feature](https://github.com/netbox-community/netbox/issues/new?assignees=&labels=type%3A+feature&template=feature_request.yaml) or [report a bug](https://github.com/netbox-community/netbox/issues/new?assignees=&labels=type%3A+bug&template=bug_report.yaml) on GitHub. +* Contributions from the community are encouraged and appreciated! Check out our [contributing guide](CONTRIBUTING.md) to get started. -NetBox also features a complete REST API as well as a GraphQL API for easily -integrating with other tools and systems. - -The complete documentation for NetBox can be found at [docs.netbox.dev](https://docs.netbox.dev/). -A public demo instance is available at [demo.netbox.dev](https://demo.netbox.dev). - -NetBox runs as a web application atop the [Django](https://www.djangoproject.com/) -Python framework with a [PostgreSQL](https://www.postgresql.org/) database. For a -complete list of requirements, see `requirements.txt`. The code is available -[on GitHub](https://github.com/netbox-community/netbox). +## Project Stats + +
+ Timeline graph + Issues graph + Pull requests graph + Top contributors +
Stats via Repography +
+ +## Sponsors
-

Thank you to our sponsors!

[![NetBox Labs](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/netbox_labs.png)](https://netboxlabs.com)            @@ -76,34 +64,10 @@ complete list of requirements, see `requirements.txt`. The code is available [![Sentry](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/sentry.png)](https://sentry.io)            [![Equinix Metal](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/equinix.png)](https://metal.equinix.com) -            - [![Stellar Technologies](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/stellar.png)](https://stellar.tech)
-### Discussion - -* [GitHub Discussions](https://github.com/netbox-community/netbox/discussions) - Discussion forum hosted by GitHub; ideal for Q&A and other structured discussions -* [Slack](https://netdev.chat/) - Real-time chat hosted by the NetDev Community; best for unstructured discussion or just hanging out - -### Installation - -Please see [the documentation](https://docs.netbox.dev/) for -instructions on installing NetBox. To upgrade NetBox, please download the -[latest release](https://github.com/netbox-community/netbox/releases) and -run `upgrade.sh`. - -### Providing Feedback - -The best platform for general feedback, assistance, and other discussion is our -[GitHub discussions](https://github.com/netbox-community/netbox/discussions). -To report a bug or request a specific feature, please open a GitHub issue using -the [appropriate template](https://github.com/netbox-community/netbox/issues/new/choose). - -If you are interested in contributing to the development of NetBox, please read -our [contributing guide](CONTRIBUTING.md) prior to beginning any work. - -### Screenshots +## Screenshots ![Screenshot of main page (dark mode)](docs/media/screenshots/home-dark.png "Main page (dark mode)") @@ -112,8 +76,3 @@ our [contributing guide](CONTRIBUTING.md) prior to beginning any work. ![Screenshot of prefixes hierarchy](docs/media/screenshots/prefixes-list.png "Prefixes hierarchy") ![Screenshot of cable trace](docs/media/screenshots/cable-trace.png "Cable tracing") - -### Related projects - -Please see [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions) -for a list of relevant community projects. From 95b2acb6031b94bb88bca846f5352ce307def44f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 2 Feb 2023 14:59:16 -0500 Subject: [PATCH 10/21] Fixes #11650: Display error message when attempting to create device component with duplicate name --- docs/release-notes/version-3.4.md | 2 ++ netbox/netbox/views/generic/object_views.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 3c92bc61d..130b14544 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -17,8 +17,10 @@ * [#11528](https://github.com/netbox-community/netbox/issues/11528) - Permit import of devices using uploaded file * [#11555](https://github.com/netbox-community/netbox/issues/11555) - Avoid inadvertent interpretation of search query as regular expression under global search (previously [#11516](https://github.com/netbox-community/netbox/issues/11516)) * [#11562](https://github.com/netbox-community/netbox/issues/11562) - Correct ordering of virtual chassis interfaces with duplicate names +* [#11574](https://github.com/netbox-community/netbox/issues/11574) - Fix exception when attempting to schedule reports/scripts * [#11620](https://github.com/netbox-community/netbox/issues/11620) - Correct available filter choices for interface PoE type * [#11635](https://github.com/netbox-community/netbox/issues/11635) - Pre-populate assigned VRF when following "first available IP" link from prefix view +* [#11650](https://github.com/netbox-community/netbox/issues/11650) - Display error message when attempting to create device component with duplicate name --- diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index 795f4ad56..d855490d1 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -453,6 +453,9 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView): if component_form.is_valid(): new_components.append(component_form) + else: + form.errors.update(component_form.errors) + break if not form.errors and not component_form.errors: try: From 699edd049c5b7426e01e1471b7f36d560ed182ce Mon Sep 17 00:00:00 2001 From: Maximilian Wilhelm Date: Thu, 2 Feb 2023 21:22:55 +0100 Subject: [PATCH 11/21] Closes #11152: Add support to abort custom script gracefully (#11621) Signed-off-by: Maximilian Wilhelm --- docs/customization/custom-scripts.md | 13 +++++++++++++ netbox/extras/scripts.py | 10 +++++++++- netbox/utilities/exceptions.py | 7 +++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/customization/custom-scripts.md b/docs/customization/custom-scripts.md index 456bcf472..af1e9b5b6 100644 --- a/docs/customization/custom-scripts.md +++ b/docs/customization/custom-scripts.md @@ -142,6 +142,19 @@ obj.full_clean() obj.save() ``` +## Error handling + +Sometimes things go wrong and a script will run into an `Exception`. If that happens and an uncaught exception is raised by the custom script, the execution is aborted and a full stack trace is reported. + +Although this is helpful for debugging, in some situations it might be required to cleanly abort the execution of a custom script (e.g. because of invalid input data) and thereby make sure no changes are performed on the database. In this case the script can throw an `AbortScript` exception, which will prevent the stack trace from being reported, but still terminating the script's execution and reporting a given error message. + +```python +from utilities.exceptions import AbortScript + +if some_error: + raise AbortScript("Some meaningful error message") +``` + ## Variable Reference ### Default Options diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index 998d727a4..77c96de56 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -21,7 +21,7 @@ from extras.models import JobResult from extras.signals import clear_webhooks from ipam.formfields import IPAddressFormField, IPNetworkFormField from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator -from utilities.exceptions import AbortTransaction +from utilities.exceptions import AbortScript, AbortTransaction from utilities.forms import add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField from .context_managers import change_logging from .forms import ScriptForm @@ -470,6 +470,14 @@ def run_script(data, request, commit=True, *args, **kwargs): except AbortTransaction: script.log_info("Database changes have been reverted automatically.") clear_webhooks.send(request) + except AbortScript as e: + script.log_failure( + f"Script aborted with error: {e}" + ) + script.log_info("Database changes have been reverted due to error.") + logger.error(f"Script aborted with error: {e}") + job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) + clear_webhooks.send(request) except Exception as e: stacktrace = traceback.format_exc() script.log_failure( diff --git a/netbox/utilities/exceptions.py b/netbox/utilities/exceptions.py index 657e90745..d7418d0cb 100644 --- a/netbox/utilities/exceptions.py +++ b/netbox/utilities/exceptions.py @@ -24,6 +24,13 @@ class AbortRequest(Exception): self.message = message +class AbortScript(Exception): + """ + Raised to cleanly abort a script. + """ + pass + + class PermissionsViolation(Exception): """ Raised when an operation was prevented because it would violate the From 37d0135cab92339652239775da4c94c67fe914e1 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 2 Feb 2023 15:24:54 -0500 Subject: [PATCH 12/21] Release v3.4.4 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- base_requirements.txt | 2 +- docs/release-notes/version-3.4.md | 3 ++- netbox/netbox/settings.py | 2 +- requirements.txt | 6 +++--- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 80810f2ba..9ed740fff 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.4.3 + placeholder: v3.4.4 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 975fc025a..8e4ab54a5 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.4.3 + placeholder: v3.4.4 validations: required: true - type: dropdown diff --git a/base_requirements.txt b/base_requirements.txt index 3e4811ece..7292c676b 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -1,6 +1,6 @@ # HTML sanitizer # https://github.com/mozilla/bleach -bleach +bleach<6.0 # The Python web framework on which NetBox is built # https://github.com/django/django diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 130b14544..1581ce681 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -1,10 +1,11 @@ # NetBox v3.4 -## v3.4.4 (FUTURE) +## v3.4.4 (2023-02-02) ### Enhancements * [#10762](https://github.com/netbox-community/netbox/issues/10762) - Permit selection custom fields to have only one choice +* [#11152](https://github.com/netbox-community/netbox/issues/11152) - Introduce AbortScript exception to elegantly abort scripts * [#11554](https://github.com/netbox-community/netbox/issues/11554) - Add module types count to manufacturers list * [#11585](https://github.com/netbox-community/netbox/issues/11585) - Add IP address filters for services * [#11598](https://github.com/netbox-community/netbox/issues/11598) - Add buttons to easily switch between rack list and elevations views diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 4d74307a0..8517efca1 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -24,7 +24,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.4.4-dev' +VERSION = '3.4.4' # Hostname HOSTNAME = platform.node() diff --git a/requirements.txt b/requirements.txt index 3ab7faace..3cb2529a8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ bleach==5.0.1 -Django==4.1.5 +Django==4.1.6 django-cors-headers==3.13.0 django-debug-toolbar==3.8.1 django-filter==22.1 @@ -19,13 +19,13 @@ graphene-django==3.0.0 gunicorn==20.1.0 Jinja2==3.1.2 Markdown==3.3.7 -mkdocs-material==9.0.6 +mkdocs-material==9.0.10 mkdocstrings[python-legacy]==0.20.0 netaddr==0.8.0 Pillow==9.4.0 psycopg2-binary==2.9.5 PyYAML==6.0 -sentry-sdk==1.13.0 +sentry-sdk==1.14.0 social-auth-app-django==5.0.0 social-auth-core[openidconnect]==4.3.0 svgwrite==1.4.3 From 7ebfa4c1d1890fafb6393d97e88aa76229368f67 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 2 Feb 2023 15:41:24 -0500 Subject: [PATCH 13/21] PRVB --- docs/release-notes/version-3.4.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 1581ce681..15b84436d 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -1,5 +1,9 @@ # NetBox v3.4 +## v3.4.5 (FUTURE) + +--- + ## v3.4.4 (2023-02-02) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 8517efca1..cda6ee643 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -24,7 +24,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.4.4' +VERSION = '3.4.5-dev' # Hostname HOSTNAME = platform.node() From 5e1bb20f3208d34321fa218f901f71aaa6f439d5 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 7 Feb 2023 16:49:07 -0500 Subject: [PATCH 14/21] Display login message as success --- netbox/users/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/users/views.py b/netbox/users/views.py index 832a4e592..a82620914 100644 --- a/netbox/users/views.py +++ b/netbox/users/views.py @@ -96,7 +96,7 @@ class LoginView(View): # Authenticate user auth_login(request, form.get_user()) logger.info(f"User {request.user} successfully authenticated") - messages.info(request, f"Logged in as {request.user}.") + messages.success(request, f"Logged in as {request.user}.") # Ensure the user has a UserConfig defined. (This should normally be handled by # create_userconfig() on user creation.) From edbd597bf2687ef31d1fbaa394b2080bd57f95a9 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 7 Feb 2023 16:52:54 -0500 Subject: [PATCH 15/21] Update housekeeping command docs --- docs/administration/housekeeping.md | 1 + netbox/extras/management/commands/housekeeping.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/administration/housekeeping.md b/docs/administration/housekeeping.md index da1a5443b..fcc3aa04e 100644 --- a/docs/administration/housekeeping.md +++ b/docs/administration/housekeeping.md @@ -5,6 +5,7 @@ NetBox includes a `housekeeping` management command that should be run nightly. * Clearing expired authentication sessions from the database * Deleting changelog records older than the configured [retention time](../configuration/miscellaneous.md#changelog_retention) * Deleting job result records older than the configured [retention time](../configuration/miscellaneous.md#jobresult_retention) +* Check for new NetBox releases (if [`RELEASE_CHECK_URL`](../configuration/miscellaneous.md#release_check_url) is set) This command can be invoked directly, or by using the shell script provided at `/opt/netbox/contrib/netbox-housekeeping.sh`. This script can be linked from your cron scheduler's daily jobs directory (e.g. `/etc/cron.daily`) or referenced directly within the cron configuration file. diff --git a/netbox/extras/management/commands/housekeeping.py b/netbox/extras/management/commands/housekeeping.py index 42690568d..172e26bf2 100644 --- a/netbox/extras/management/commands/housekeeping.py +++ b/netbox/extras/management/commands/housekeeping.py @@ -37,7 +37,7 @@ class Command(BaseCommand): f"clearing sessions; skipping." ) - # Delete expired ObjectRecords + # Delete expired ObjectChanges if options['verbosity']: self.stdout.write("[*] Checking for expired changelog records") if config.CHANGELOG_RETENTION: From 3f28d6aef3b40726ee6c9fade941c31272271d70 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 7 Feb 2023 16:55:50 -0500 Subject: [PATCH 16/21] Add step for creating search index --- docs/development/adding-models.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/development/adding-models.md b/docs/development/adding-models.md index aef11d666..7de897a97 100644 --- a/docs/development/adding-models.md +++ b/docs/development/adding-models.md @@ -54,15 +54,19 @@ Each model should have a corresponding FilterSet class defined. This is used to Create a table class for the model in `tables.py` by subclassing `utilities.tables.BaseTable`. Under the table's `Meta` class, be sure to list both the fields and default columns. -## 9. Create the object template +## 9. Create a SearchIndex subclass + +If this model will be included in global search results, create a subclass of `netbox.search.SearchIndex` for it and specify the fields to be indexed. + +## 10. Create the object template Create the HTML template for the object view. (The other views each typically employ a generic template.) This template should extend `generic/object.html`. -## 10. Add the model to the navigation menu +## 11. Add the model to the navigation menu Add the relevant navigation menu items in `netbox/netbox/navigation/menu.py`. -## 11. REST API components +## 12. REST API components Create the following for each model: @@ -71,13 +75,13 @@ Create the following for each model: * API view in `api/views.py` * Endpoint route in `api/urls.py` -## 12. GraphQL API components +## 13. GraphQL API components Create a Graphene object type for the model in `graphql/types.py` by subclassing the appropriate class from `netbox.graphql.types`. Also extend the schema class defined in `graphql/schema.py` with the individual object and object list fields per the established convention. -## 13. Add tests +## 14. Add tests Add tests for the following: @@ -85,7 +89,7 @@ Add tests for the following: * API views * Filter sets -## 14. Documentation +## 15. Documentation Create a new documentation page for the model in `docs/models//.md`. Include this file under the "features" documentation where appropriate. From 56c7a238a4905d3220b63d8b33b07ca52df7efd1 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 7 Feb 2023 17:24:26 -0500 Subject: [PATCH 17/21] Fixes #11683: Fix CSV header attribute detection when auto-detecting import format --- docs/release-notes/version-3.4.md | 4 ++++ netbox/netbox/views/generic/bulk_views.py | 4 ++-- netbox/utilities/forms/forms.py | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 15b84436d..b23251a70 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -2,6 +2,10 @@ ## v3.4.5 (FUTURE) +### Bug Fixes + +* [#11683](https://github.com/netbox-community/netbox/issues/11683) - Fix CSV header attribute detection when auto-detecting import format + --- ## v3.4.4 (2023-02-02) diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index ab3e8f100..6060475d8 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -384,8 +384,8 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView): 'data': record, 'instance': instance, } - if form.cleaned_data['format'] == ImportFormatChoices.CSV: - model_form_kwargs['headers'] = form._csv_headers + if hasattr(form, '_csv_headers'): + model_form_kwargs['headers'] = form._csv_headers # Add CSV headers model_form = self.model_form(**model_form_kwargs) # When updating, omit all form fields other than those specified in the record. (No diff --git a/netbox/utilities/forms/forms.py b/netbox/utilities/forms/forms.py index 99d03f2a6..9884ffac5 100644 --- a/netbox/utilities/forms/forms.py +++ b/netbox/utilities/forms/forms.py @@ -197,6 +197,8 @@ class ImportForm(BootstrapMixin, forms.Form): self.cleaned_data['data'] = self._clean_json(data) elif format == ImportFormatChoices.YAML: self.cleaned_data['data'] = self._clean_yaml(data) + else: + raise forms.ValidationError(f"Unknown data format: {format}") def _detect_format(self, data): """ From 91705aa9fdfb4b730aaad3b438f65b853ff00107 Mon Sep 17 00:00:00 2001 From: kkthxbye <400797+kkthxbye-code@users.noreply.github.com> Date: Wed, 8 Feb 2023 20:36:20 +0100 Subject: [PATCH 18/21] Fixes #11032 - Replication fields broken in custom validation (#11698) * Fixes #11032 - Replication fields broken in custom validation * Use getattr instead of hasattr to make sure custom validation is triggered as normal --------- Co-authored-by: kkthxbye-code <> --- netbox/netbox/models/features.py | 4 ++++ netbox/netbox/views/generic/object_views.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index 8e5af0ab5..f041d016d 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -257,6 +257,10 @@ class CustomValidationMixin(models.Model): def clean(self): super().clean() + # If the instance is a base for replications, skip custom validation + if getattr(self, '_replicated_base', False): + return + # Send the post_clean signal post_clean.send(sender=self.__class__, instance=self) diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index d855490d1..475cca9d3 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -436,6 +436,10 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView): form = self.initialize_form(request) instance = self.alter_object(self.queryset.model(), request) + # Note that the form instance is a replicated field base + # This is needed to avoid running custom validators multiple times + form.instance._replicated_base = hasattr(self.form, "replication_fields") + if form.is_valid(): new_components = [] data = deepcopy(request.POST) From 3c970c331ceb7fef0f78d05c6e500c177ab6d037 Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Wed, 8 Feb 2023 09:33:06 +0100 Subject: [PATCH 19/21] Fixes #11582: Fix missing VC form errors ### Fixes: #11582 Not sure if this is the correct fix or not. The reason that the custom field errors were not shown is that messages.html only shows non_field_errors if the form passed to the context is named form. This is probably an issue in more places, but not sure how to make it generic. A change to messages.html would also need to support formsets. Any input appreciated @jeremystretch or @arthanson --- netbox/templates/dcim/virtualchassis_add_member.html | 2 ++ netbox/templates/dcim/virtualchassis_edit.html | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/netbox/templates/dcim/virtualchassis_add_member.html b/netbox/templates/dcim/virtualchassis_add_member.html index 17ffd64d9..bc2ba2f55 100644 --- a/netbox/templates/dcim/virtualchassis_add_member.html +++ b/netbox/templates/dcim/virtualchassis_add_member.html @@ -5,6 +5,8 @@ {% block content %}
+ {% render_errors membership_form %} + {% csrf_token %}
Add New Member
diff --git a/netbox/templates/dcim/virtualchassis_edit.html b/netbox/templates/dcim/virtualchassis_edit.html index f98a9fe64..433837cf5 100644 --- a/netbox/templates/dcim/virtualchassis_edit.html +++ b/netbox/templates/dcim/virtualchassis_edit.html @@ -8,6 +8,10 @@
+ {% for form in formset %} + {% render_errors form %} + {% endfor %} + {% csrf_token %} {{ pk_form.pk }} {{ formset.management_form }} From f9237285fdfa1d412e2ff8de8a4ee7475d634c74 Mon Sep 17 00:00:00 2001 From: kkthxbye-code Date: Wed, 8 Feb 2023 10:09:07 +0100 Subject: [PATCH 20/21] Fixes #11601 - Add partial lookup to IPRangeFilterSet --- netbox/ipam/filtersets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index d069eed27..c312b02ff 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -441,9 +441,9 @@ class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet): def search(self, queryset, name, value): if not value.strip(): return queryset - qs_filter = Q(description__icontains=value) + qs_filter = Q(description__icontains=value) | Q(start_address__contains=value) | Q(end_address__contains=value) try: - ipaddress = str(netaddr.IPNetwork(value.strip()).cidr) + ipaddress = str(netaddr.IPNetwork(value.strip())) qs_filter |= Q(start_address=ipaddress) qs_filter |= Q(end_address=ipaddress) except (AddrFormatError, ValueError): From b5da383a179be36312c4def9fc6de29bde0a3df0 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 8 Feb 2023 14:56:14 -0500 Subject: [PATCH 21/21] Changelog for #11032, #11582, #11601 --- docs/release-notes/version-3.4.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index b23251a70..b8ebe4a33 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -4,6 +4,9 @@ ### Bug Fixes +* [#11032](https://github.com/netbox-community/netbox/issues/11032) - Fix false custom validation errors during component creation +* [#11582](https://github.com/netbox-community/netbox/issues/11582) - Ensure form validation errors are displayed when adding virtual chassis members +* [#11601](https://github.com/netbox-community/netbox/issues/11601) - Fix partial matching of start/end addresses for IP range search * [#11683](https://github.com/netbox-community/netbox/issues/11683) - Fix CSV header attribute detection when auto-detecting import format ---