From d5e6829eff548fe6146d672a6e62c453f5db3da9 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 20 Jan 2023 14:21:03 -0500 Subject: [PATCH 01/27] 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 ffa144d34bd..bf32f2d260c 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.4 (FUTURE) + +--- + ## v3.4.3 (2023-01-20) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 2de06dd10e5..4d74307a08f 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.3' +VERSION = '3.4.4-dev' # Hostname HOSTNAME = platform.node() From 5f7e3103052f163d99908396559432d8674995ed Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 20 Jan 2023 16:47:19 -0500 Subject: [PATCH 02/27] Fixes #11555: Avoid inadvertent interpretation of search query as regular expression under global search --- docs/release-notes/version-3.4.md | 6 +++++- netbox/utilities/utils.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index bf32f2d260c..234c8ee0ea6 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -2,6 +2,10 @@ ## v3.4.4 (FUTURE) +### Bug Fixes + +* [#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)) + --- ## v3.4.3 (2023-01-20) @@ -34,7 +38,7 @@ * [#11483](https://github.com/netbox-community/netbox/issues/11483) - Apply configured formatting to custom date fields * [#11488](https://github.com/netbox-community/netbox/issues/11488) - Add missing `description` fields to several REST API serializers * [#11497](https://github.com/netbox-community/netbox/issues/11497) - Enforce `run_script` permission when executing scripts via REST API -* [#11516](https://github.com/netbox-community/netbox/issues/11516) - Prevent text highlight utility from interpreting match as regex +* ~[#11516](https://github.com/netbox-community/netbox/issues/11516) - Prevent text highlight utility from interpreting match as regex~ * [#11522](https://github.com/netbox-community/netbox/issues/11522) - Correct tag links under contact & tenant list views * [#11544](https://github.com/netbox-community/netbox/issues/11544) - Catch ValidationError exception when filtering by invalid MAC address diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index b6f626eb479..23c2666df11 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -527,6 +527,7 @@ def highlight_string(value, highlight, trim_pre=None, trim_post=None, trim_place if type(highlight) is re.Pattern: pre, match, post = highlight.split(value, maxsplit=1) else: + highlight = re.escape(highlight) pre, match, post = re.split(fr'({highlight})', value, maxsplit=1, flags=re.IGNORECASE) except ValueError as e: # Match not found From a4d8169df8e1e57cbb24ffb60a1edd8259929802 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 20 Jan 2023 16:48:22 -0500 Subject: [PATCH 03/27] Changelog for #11537 --- 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 234c8ee0ea6..363a629ad2b 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -40,6 +40,7 @@ * [#11497](https://github.com/netbox-community/netbox/issues/11497) - Enforce `run_script` permission when executing scripts via REST API * ~[#11516](https://github.com/netbox-community/netbox/issues/11516) - Prevent text highlight utility from interpreting match as regex~ * [#11522](https://github.com/netbox-community/netbox/issues/11522) - Correct tag links under contact & tenant list views +* [#11537](https://github.com/netbox-community/netbox/issues/11537) - Remove obsolete "Connection" column from power feeds table * [#11544](https://github.com/netbox-community/netbox/issues/11544) - Catch ValidationError exception when filtering by invalid MAC address --- From eca624b13d4bd04b3b0600993f919c1a60f32722 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Mon, 23 Jan 2023 05:48:14 -0800 Subject: [PATCH 04/27] 11487 remove set null from read-only custom fields bulk edit (#11552) * 11487 remove set null from read-only custom fields bulk edit * 11487 removes unreleased sentry-sdk --- netbox/netbox/forms/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/forms/base.py b/netbox/netbox/forms/base.py index d69445e7882..b4ad39b5e2a 100644 --- a/netbox/netbox/forms/base.py +++ b/netbox/netbox/forms/base.py @@ -131,7 +131,7 @@ class NetBoxModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, forms.Form): def _extend_nullable_fields(self): nullable_custom_fields = [ - name for name, customfield in self.custom_fields.items() if not customfield.required + name for name, customfield in self.custom_fields.items() if (not customfield.required and customfield.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_READ_WRITE) ] self.nullable_fields = (*self.nullable_fields, *nullable_custom_fields) From 0f9a3039637dc4e02b8a6895004660cb3f4f517a Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 23 Jan 2023 10:21:11 -0500 Subject: [PATCH 05/27] Changelog for #11487 --- 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 363a629ad2b..db677c4965b 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -4,6 +4,7 @@ ### Bug Fixes +* [#11487](https://github.com/netbox-community/netbox/issues/11487) - Remove "set null" option from non-writable custom fields during bulk edit * [#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)) --- From 6a793087b44a36f754ff90306d301162a5fa5177 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 23 Jan 2023 10:23:49 -0500 Subject: [PATCH 06/27] Reference GitHub advisory reporting --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index b389dd2b3ea..c434b61102b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -24,7 +24,7 @@ If you believe you've uncovered a security vulnerability and wish to report it c Please note that we **DO NOT** accept reports generated by automated tooling which merely suggest that a file or file(s) _may_ be vulnerable under certain conditions, as these are most often innocuous. -If you believe that you've found a vulnerability which meets all of these conditions, please email a brief description of the suspected bug and instructions for reproduction to **security@netbox.dev**. For any security concerns regarding NetBox deployed via Docker, please see the [netbox-docker](https://github.com/netbox-community/netbox-docker) project. +If you believe that you've found a vulnerability which meets all of these conditions, please [submit a draft security advisory](https://github.com/netbox-community/netbox/security/advisories/new) on GitHub, or email a brief description of the suspected bug and instructions for reproduction to **security@netbox.dev**. For any security concerns regarding NetBox deployed via Docker, please see the [netbox-docker](https://github.com/netbox-community/netbox-docker) project. ### Bug Bounties From 39087d10eb1c779b2c37d8dae33eb5dbc9dd943b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 23 Jan 2023 10:44:42 -0500 Subject: [PATCH 07/27] Add NetBox Labs as a sponsor --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f44ce725f35..e14f31b56a6 100644 --- a/README.md +++ b/README.md @@ -67,15 +67,17 @@ complete list of requirements, see `requirements.txt`. The code is available

Thank you to our sponsors!

+ [![NetBox Labs](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/netbox_labs.png)](https://netboxlabs.com) +            [![DigitalOcean](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/digitalocean.png)](https://try.digitalocean.com/developer-cloud)            - [![Equinix Metal](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/equinix.png)](https://metal.equinix.com/) -            - [![NS1](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/ns1.png)](https://ns1.com/) + [![NS1](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/ns1.png)](https://ns1.com)
- [![Sentry](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/sentry.png)](https://sentry.io/) + [![Sentry](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/sentry.png)](https://sentry.io)            - [![Stellar Technologies](https://raw.githubusercontent.com/wiki/netbox-community/netbox/images/sponsors/stellar.png)](https://stellar.tech/) + [![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)
From b79a2976f7f288617505043a4216ca01487da3d7 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 24 Jan 2023 14:40:09 -0500 Subject: [PATCH 08/27] Closes #10888, #10889: Add supplementary notes to installation docs --- docs/installation/3-netbox.md | 5 ++++- docs/installation/4-gunicorn.md | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/installation/3-netbox.md b/docs/installation/3-netbox.md index 68a582e7f5b..26a2bf917b2 100644 --- a/docs/installation/3-netbox.md +++ b/docs/installation/3-netbox.md @@ -272,7 +272,10 @@ See the [housekeeping documentation](../administration/housekeeping.md) for furt ## Test the Application -At this point, we should be able to run NetBox's development server for testing. We can check by starting a development instance: +At this point, we should be able to run NetBox's development server for testing. We can check by starting a development instance locally. + +!!! tip + Check that the Python virtual environment is still active before attempting to run the server. ```no-highlight python3 manage.py runserver 0.0.0.0:8000 --insecure diff --git a/docs/installation/4-gunicorn.md b/docs/installation/4-gunicorn.md index 21d1f1211dd..1183a91237f 100644 --- a/docs/installation/4-gunicorn.md +++ b/docs/installation/4-gunicorn.md @@ -14,7 +14,10 @@ While the provided configuration should suffice for most initial installations, ## systemd Setup -We'll use systemd to control both gunicorn and NetBox's background worker process. First, copy `contrib/netbox.service` and `contrib/netbox-rq.service` to the `/etc/systemd/system/` directory and reload the systemd daemon: +We'll use systemd to control both gunicorn and NetBox's background worker process. First, copy `contrib/netbox.service` and `contrib/netbox-rq.service` to the `/etc/systemd/system/` directory and reload the systemd daemon. + +!!! warning "Check user & group assignment" + The stock service configuration files packaged with NetBox assume that the service will run with the `netbox` user and group names. If these differ on your installation, be sure to update the service files accordingly. ```no-highlight sudo cp -v /opt/netbox/contrib/*.service /etc/systemd/system/ From d5ccda355fc7be6ee8e87fce0ca1c951a3e2f973 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 24 Jan 2023 15:44:02 -0500 Subject: [PATCH 09/27] Fixes #11562: Correct ordering of virtual chassis interfaces with duplicate names --- docs/release-notes/version-3.4.md | 1 + netbox/dcim/tables/devices.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index db677c4965b..b7ce5380b04 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -6,6 +6,7 @@ * [#11487](https://github.com/netbox-community/netbox/issues/11487) - Remove "set null" option from non-writable custom fields during bulk edit * [#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 --- diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 7a2ea50babb..7303091566e 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -580,7 +580,6 @@ class DeviceInterfaceTable(InterfaceTable): 'wireless_lans', 'link_peer', 'connection', 'tags', 'vdcs', 'vrf', 'l2vpn', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'actions', ) - order_by = ('name',) default_columns = ( 'pk', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mtu', 'mode', 'description', 'ip_addresses', 'cable', 'connection', From b8de9c0875cedb1a6decc48c61bee1b183e4af95 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 25 Jan 2023 09:55:45 -0500 Subject: [PATCH 10/27] Fixes #11528: Permit import of devices using uploaded file --- netbox/dcim/forms/bulk_import.py | 77 ++++++++----------- netbox/dcim/urls.py | 1 - netbox/dcim/views.py | 17 ++-- netbox/templates/dcim/device_import.html | 5 -- .../templates/dcim/device_import_child.html | 5 -- .../dcim/inc/device_import_header.html | 8 -- 6 files changed, 37 insertions(+), 76 deletions(-) delete mode 100644 netbox/templates/dcim/device_import.html delete mode 100644 netbox/templates/dcim/device_import_child.html delete mode 100644 netbox/templates/dcim/inc/device_import_header.html diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index bdbaf9f1898..3f016899e91 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -18,7 +18,6 @@ from .common import ModuleCommonForm __all__ = ( 'CableImportForm', - 'ChildDeviceImportForm', 'ConsolePortImportForm', 'ConsoleServerPortImportForm', 'DeviceBayImportForm', @@ -413,6 +412,18 @@ class DeviceImportForm(BaseDeviceImportForm): required=False, help_text=_('Mounted rack face') ) + parent = CSVModelChoiceField( + queryset=Device.objects.all(), + to_field_name='name', + required=False, + help_text=_('Parent device (for child devices)') + ) + device_bay = CSVModelChoiceField( + queryset=DeviceBay.objects.all(), + to_field_name='name', + required=False, + help_text=_('Device bay in which this device is installed (for child devices)') + ) airflow = CSVChoiceField( choices=DeviceAirflowChoices, required=False, @@ -422,8 +433,8 @@ class DeviceImportForm(BaseDeviceImportForm): class Meta(BaseDeviceImportForm.Meta): fields = [ 'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status', - 'site', 'location', 'rack', 'position', 'face', 'airflow', 'virtual_chassis', 'vc_position', 'vc_priority', - 'cluster', 'description', 'comments', 'tags', + 'site', 'location', 'rack', 'position', 'face', 'parent', 'device_bay', 'airflow', 'virtual_chassis', + 'vc_position', 'vc_priority', 'cluster', 'description', 'comments', 'tags', ] def __init__(self, data=None, *args, **kwargs): @@ -434,6 +445,7 @@ class DeviceImportForm(BaseDeviceImportForm): # Limit location queryset by assigned site params = {f"site__{self.fields['site'].to_field_name}": data.get('site')} self.fields['location'].queryset = self.fields['location'].queryset.filter(**params) + self.fields['parent'].queryset = self.fields['parent'].queryset.filter(**params) # Limit rack queryset by assigned site and group params = { @@ -442,6 +454,23 @@ class DeviceImportForm(BaseDeviceImportForm): } self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params) + # Limit device bay queryset by parent device + if parent := data.get('parent'): + params = {f"device__{self.fields['parent'].to_field_name}": parent} + self.fields['device_bay'].queryset = self.fields['device_bay'].queryset.filter(**params) + + def clean(self): + super().clean() + + # Inherit site and rack from parent device + if parent := self.cleaned_data.get('parent'): + self.instance.site = parent.site + self.instance.rack = parent.rack + + # Set parent_bay reverse relationship + if device_bay := self.cleaned_data.get('device_bay'): + self.instance.parent_bay = device_bay + class ModuleImportForm(ModuleCommonForm, NetBoxModelImportForm): device = CSVModelChoiceField( @@ -495,48 +524,6 @@ class ModuleImportForm(ModuleCommonForm, NetBoxModelImportForm): return self.cleaned_data['replicate_components'] -class ChildDeviceImportForm(BaseDeviceImportForm): - parent = CSVModelChoiceField( - queryset=Device.objects.all(), - to_field_name='name', - help_text=_('Parent device') - ) - device_bay = CSVModelChoiceField( - queryset=DeviceBay.objects.all(), - to_field_name='name', - help_text=_('Device bay in which this device is installed') - ) - - class Meta(BaseDeviceImportForm.Meta): - fields = [ - 'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status', - 'parent', 'device_bay', 'virtual_chassis', 'vc_position', 'vc_priority', 'cluster', 'comments', 'tags' - ] - - def __init__(self, data=None, *args, **kwargs): - super().__init__(data, *args, **kwargs) - - if data: - - # Limit device bay queryset by parent device - params = {f"device__{self.fields['parent'].to_field_name}": data.get('parent')} - self.fields['device_bay'].queryset = self.fields['device_bay'].queryset.filter(**params) - - def clean(self): - super().clean() - - # Set parent_bay reverse relationship - device_bay = self.cleaned_data.get('device_bay') - if device_bay: - self.instance.parent_bay = device_bay - - # Inherit site and rack from parent device - parent = self.cleaned_data.get('parent') - if parent: - self.instance.site = parent.site - self.instance.rack = parent.rack - - # # Device components # diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 6772f96adc3..c71a0aff136 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -177,7 +177,6 @@ urlpatterns = [ path('devices/', views.DeviceListView.as_view(), name='device_list'), path('devices/add/', views.DeviceEditView.as_view(), name='device_add'), path('devices/import/', views.DeviceBulkImportView.as_view(), name='device_import'), - path('devices/import/child-devices/', views.ChildDeviceBulkImportView.as_view(), name='device_import_child'), path('devices/edit/', views.DeviceBulkEditView.as_view(), name='device_bulk_edit'), path('devices/rename/', views.DeviceBulkRenameView.as_view(), name='device_bulk_rename'), path('devices/delete/', views.DeviceBulkDeleteView.as_view(), name='device_bulk_delete'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 46d12937b03..0643ac7390d 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2090,22 +2090,15 @@ class DeviceBulkImportView(generic.BulkImportView): queryset = Device.objects.all() model_form = forms.DeviceImportForm table = tables.DeviceImportTable - template_name = 'dcim/device_import.html' - - -class ChildDeviceBulkImportView(generic.BulkImportView): - queryset = Device.objects.all() - model_form = forms.ChildDeviceImportForm - table = tables.DeviceImportTable - template_name = 'dcim/device_import_child.html' def save_object(self, object_form, request): obj = object_form.save() - # Save the reverse relation to the parent device bay - device_bay = obj.parent_bay - device_bay.installed_device = obj - device_bay.save() + # For child devices, save the reverse relation to the parent device bay + if getattr(obj, 'parent_bay', None): + device_bay = obj.parent_bay + device_bay.installed_device = obj + device_bay.save() return obj diff --git a/netbox/templates/dcim/device_import.html b/netbox/templates/dcim/device_import.html deleted file mode 100644 index b30de60c2e3..00000000000 --- a/netbox/templates/dcim/device_import.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends 'generic/bulk_import.html' %} - -{% block tabs %} - {% include 'dcim/inc/device_import_header.html' %} -{% endblock %} diff --git a/netbox/templates/dcim/device_import_child.html b/netbox/templates/dcim/device_import_child.html deleted file mode 100644 index d0dc72b61a7..00000000000 --- a/netbox/templates/dcim/device_import_child.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends 'generic/bulk_import.html' %} - -{% block tabs %} - {% include 'dcim/inc/device_import_header.html' with active_tab='child_import' %} -{% endblock %} diff --git a/netbox/templates/dcim/inc/device_import_header.html b/netbox/templates/dcim/inc/device_import_header.html deleted file mode 100644 index 97e849c2a39..00000000000 --- a/netbox/templates/dcim/inc/device_import_header.html +++ /dev/null @@ -1,8 +0,0 @@ - From 6f74c5ec03305b7f597a839d35b2ee0c0e8f75ad Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 25 Jan 2023 10:09:37 -0500 Subject: [PATCH 11/27] Fixes #11528: Show edit/delete buttons in user tokens table --- docs/release-notes/version-3.4.md | 2 ++ netbox/users/tables.py | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index b7ce5380b04..b7ca6b27cc4 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -5,6 +5,8 @@ ### Bug Fixes * [#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 * [#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 diff --git a/netbox/users/tables.py b/netbox/users/tables.py index 8fbe9e8b3b1..0f14848875d 100644 --- a/netbox/users/tables.py +++ b/netbox/users/tables.py @@ -19,6 +19,14 @@ COPY_BUTTON = """ """ +class TokenActionsColumn(columns.ActionsColumn): + # Subclass ActionsColumn to disregard permissions for edit & delete buttons + actions = { + 'edit': columns.ActionsItem('Edit', 'pencil', None, 'warning'), + 'delete': columns.ActionsItem('Delete', 'trash-can-outline', None, 'danger'), + } + + class TokenTable(NetBoxTable): key = columns.TemplateColumn( template_code=TOKEN @@ -32,7 +40,7 @@ class TokenTable(NetBoxTable): allowed_ips = columns.TemplateColumn( template_code=ALLOWED_IPS ) - actions = columns.ActionsColumn( + actions = TokenActionsColumn( actions=('edit', 'delete'), extra_buttons=COPY_BUTTON ) From 55b1549895c31185d2525eee150918b5c063685c Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 25 Jan 2023 10:15:11 -0500 Subject: [PATCH 12/27] Closes #10762: Permit selection custom fields to have only one choice --- docs/release-notes/version-3.4.md | 4 ++++ netbox/extras/models/customfields.py | 9 ++++++--- netbox/extras/tests/test_api.py | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index b7ca6b27cc4..5134c99722a 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -2,6 +2,10 @@ ## v3.4.4 (FUTURE) +### Enhancements + +* [#10762](https://github.com/netbox-community/netbox/issues/10762) - Permit selection custom fields to have only one choice + ### Bug Fixes * [#11487](https://github.com/netbox-community/netbox/issues/11487) - Remove "set null" option from non-writable custom fields during bulk edit diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 14b033bcd5b..4842c065445 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -273,10 +273,13 @@ class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLogge 'choices': "Choices may be set only for custom selection fields." }) - # A selection field must have at least two choices defined - if self.type == CustomFieldTypeChoices.TYPE_SELECT and self.choices and len(self.choices) < 2: + # Selection fields must have at least one choice defined + if self.type in ( + CustomFieldTypeChoices.TYPE_SELECT, + CustomFieldTypeChoices.TYPE_MULTISELECT + ) and not self.choices: raise ValidationError({ - 'choices': "Selection fields must specify at least two choices." + 'choices': "Selection fields must specify at least one choice." }) # A selection field's default (if any) must be present in its available choices diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index 29e725507d3..81a607eecdf 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -101,6 +101,7 @@ class CustomFieldTest(APIViewTestCases.APIViewTestCase): 'content_types': ['dcim.site'], 'name': 'cf6', 'type': 'select', + 'choices': ['A', 'B', 'C'] }, ] bulk_update_data = { From 9cb75e98340473bed2b0bf40e86e1a5d1ab22553 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 25 Jan 2023 21:25:25 -0500 Subject: [PATCH 13/27] Closes #11585: Add IP address filters for services --- docs/release-notes/version-3.4.md | 1 + netbox/ipam/filtersets.py | 12 ++++++++++++ netbox/ipam/tests/test_filtersets.py | 23 +++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 5134c99722a..4fedddab29f 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -5,6 +5,7 @@ ### Enhancements * [#10762](https://github.com/netbox-community/netbox/issues/10762) - Permit selection custom fields to have only one choice +* [#11585](https://github.com/netbox-community/netbox/issues/11585) - Add IP address filters for services ### Bug Fixes diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index c30064ff17d..d069eed27c2 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -923,6 +923,18 @@ class ServiceFilterSet(NetBoxModelFilterSet): to_field_name='name', label=_('Virtual machine (name)'), ) + ipaddress_id = django_filters.ModelMultipleChoiceFilter( + field_name='ipaddresses', + queryset=IPAddress.objects.all(), + label=_('IP address (ID)'), + ) + ipaddress = django_filters.ModelMultipleChoiceFilter( + field_name='ipaddresses__address', + queryset=IPAddress.objects.all(), + to_field_name='address', + label=_('IP address'), + ) + port = NumericArrayFilter( field_name='ports', lookup_expr='contains' diff --git a/netbox/ipam/tests/test_filtersets.py b/netbox/ipam/tests/test_filtersets.py index a2b06080a51..711009a7edb 100644 --- a/netbox/ipam/tests/test_filtersets.py +++ b/netbox/ipam/tests/test_filtersets.py @@ -1420,6 +1420,19 @@ class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests): ) Device.objects.bulk_create(devices) + interface = Interface.objects.create( + device=devices[0], + name='eth0', + type=InterfaceTypeChoices.TYPE_VIRTUAL + ) + interface_ct = ContentType.objects.get_for_model(Interface).pk + ip_addresses = ( + IPAddress(address='192.0.2.1/24', assigned_object_type_id=interface_ct, assigned_object_id=interface.pk), + IPAddress(address='192.0.2.2/24', assigned_object_type_id=interface_ct, assigned_object_id=interface.pk), + IPAddress(address='192.0.2.3/24', assigned_object_type_id=interface_ct, assigned_object_id=interface.pk), + ) + IPAddress.objects.bulk_create(ip_addresses) + clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1') cluster = Cluster.objects.create(type=clustertype, name='Cluster 1') @@ -1439,6 +1452,9 @@ class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests): Service(virtual_machine=virtual_machines[2], name='Service 6', protocol=ServiceProtocolChoices.PROTOCOL_UDP, ports=[2003]), ) Service.objects.bulk_create(services) + services[0].ipaddresses.add(ip_addresses[0]) + services[1].ipaddresses.add(ip_addresses[1]) + services[2].ipaddresses.add(ip_addresses[2]) def test_name(self): params = {'name': ['Service 1', 'Service 2']} @@ -1470,6 +1486,13 @@ class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'virtual_machine': [vms[0].name, vms[1].name]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_ipaddress(self): + ips = IPAddress.objects.all()[:2] + params = {'ipaddress_id': [ips[0].pk, ips[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'ipaddress': [str(ips[0].address), str(ips[1].address)]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + class L2VPNTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = L2VPN.objects.all() From 22a9df82e6d4e3a3085982538bfb82c5a49b9d6b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 26 Jan 2023 08:46:25 -0500 Subject: [PATCH 14/27] Closes #11554: Add module types count to manufacturers list --- docs/release-notes/version-3.4.md | 1 + netbox/dcim/tables/devicetypes.py | 20 +++++++++++++++----- netbox/dcim/views.py | 1 + 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index 4fedddab29f..d4f238b3c24 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -5,6 +5,7 @@ ### Enhancements * [#10762](https://github.com/netbox-community/netbox/issues/10762) - Permit selection custom fields to have only one choice +* [#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 ### Bug Fixes diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index 42d9c787975..c452c3efb5a 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -34,10 +34,19 @@ class ManufacturerTable(ContactsColumnMixin, NetBoxTable): url_params={'manufacturer_id': 'pk'}, verbose_name='Device Types' ) - inventoryitem_count = tables.Column( + moduletype_count = columns.LinkedCountColumn( + viewname='dcim:moduletype_list', + url_params={'manufacturer_id': 'pk'}, + verbose_name='Module Types' + ) + inventoryitem_count = columns.LinkedCountColumn( + viewname='dcim:inventoryitem_list', + url_params={'manufacturer_id': 'pk'}, verbose_name='Inventory Items' ) - platform_count = tables.Column( + platform_count = columns.LinkedCountColumn( + viewname='dcim:platform_list', + url_params={'manufacturer_id': 'pk'}, verbose_name='Platforms' ) slug = tables.Column() @@ -48,11 +57,12 @@ class ManufacturerTable(ContactsColumnMixin, NetBoxTable): class Meta(NetBoxTable.Meta): model = models.Manufacturer fields = ( - 'pk', 'id', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug', - 'tags', 'contacts', 'actions', 'created', 'last_updated', + 'pk', 'id', 'name', 'devicetype_count', 'moduletype_count', 'inventoryitem_count', 'platform_count', + 'description', 'slug', 'tags', 'contacts', 'actions', 'created', 'last_updated', ) default_columns = ( - 'pk', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug', + 'pk', 'name', 'devicetype_count', 'moduletype_count', 'inventoryitem_count', 'platform_count', + 'description', 'slug', ) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 0643ac7390d..80b369b6df6 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -842,6 +842,7 @@ class RackReservationBulkDeleteView(generic.BulkDeleteView): class ManufacturerListView(generic.ObjectListView): queryset = Manufacturer.objects.annotate( devicetype_count=count_related(DeviceType, 'manufacturer'), + moduletype_count=count_related(ModuleType, 'manufacturer'), inventoryitem_count=count_related(InventoryItem, 'manufacturer'), platform_count=count_related(Platform, 'manufacturer') ) From ccc108a2179606426b4e1004abc741ab20b3d9bd Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 26 Jan 2023 10:53:59 -0500 Subject: [PATCH 15/27] Closes #11598: Add buttons to easily switch between rack list and elevations views --- docs/release-notes/version-3.4.md | 1 + netbox/dcim/views.py | 1 + .../templates/dcim/rack_elevation_list.html | 51 ++++++++++--------- netbox/templates/dcim/rack_list.html | 9 ++++ netbox/templates/generic/object_list.html | 7 ++- 5 files changed, 41 insertions(+), 28 deletions(-) create mode 100644 netbox/templates/dcim/rack_list.html diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index d4f238b3c24..4c9992f20ea 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -7,6 +7,7 @@ * [#10762](https://github.com/netbox-community/netbox/issues/10762) - Permit selection custom fields to have only one choice * [#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 ### Bug Fixes diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 80b369b6df6..9b49e799c8f 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -642,6 +642,7 @@ class RackListView(generic.ObjectListView): filterset = filtersets.RackFilterSet filterset_form = forms.RackFilterForm table = tables.RackTable + template_name = 'dcim/rack_list.html' class RackElevationListView(generic.ObjectListView): diff --git a/netbox/templates/dcim/rack_elevation_list.html b/netbox/templates/dcim/rack_elevation_list.html index c9d9a248afb..bd02c9f74b5 100644 --- a/netbox/templates/dcim/rack_elevation_list.html +++ b/netbox/templates/dcim/rack_elevation_list.html @@ -5,31 +5,34 @@ {% block title %}Rack Elevations{% endblock %} {% block controls %} -
-
-
- -
-
- Front - Rear -
- -
+
+
+ + View List + +
+ +
+
+ Front + Rear +
+
+
{% endblock %} {% block content-wrapper %} diff --git a/netbox/templates/dcim/rack_list.html b/netbox/templates/dcim/rack_list.html new file mode 100644 index 00000000000..897625af6fc --- /dev/null +++ b/netbox/templates/dcim/rack_list.html @@ -0,0 +1,9 @@ +{% extends 'generic/object_list.html' %} +{% load helpers %} +{% load static %} + +{% block extra_controls %} + + View Elevations + +{% endblock %} \ No newline at end of file diff --git a/netbox/templates/generic/object_list.html b/netbox/templates/generic/object_list.html index 8b3e317c0d5..e269e9da622 100644 --- a/netbox/templates/generic/object_list.html +++ b/netbox/templates/generic/object_list.html @@ -26,16 +26,15 @@ Context:
{% plugin_list_buttons model %} - {% block extra_controls %}{% endblock %} {% if 'add' in actions %} - {% add_button model %} + {% add_button model %} {% endif %} {% if 'import' in actions %} - {% import_button model %} + {% import_button model %} {% endif %} {% if 'export' in actions %} - {% export_button model %} + {% export_button model %} {% endif %}
From fbc9fea0a5b0943da6f10df3551f071ce555a6ed Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 27 Jan 2023 16:44:10 -0500 Subject: [PATCH 16/27] 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 7694a1fbe5c..0b2123a4e4f 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 b4360dc9e2d..2f237f56a1f 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 17/27] 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 4c9992f20ea..612e5bb7402 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 18/27] 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 d6146544357..6a53403d6e9 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 19/27] 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 0b2123a4e4f..b56113ca19f 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 20/27] 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 63402c7472f..5f4a8a0dc0a 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 21/27] 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 c00e83672ff..4dd2f73ebf7 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 22/27] 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 612e5bb7402..3c92bc61d7c 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 a0baf332555..6d986aed546 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 23/27] 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 79dc8c8691b..8216c5413cc 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 24/27] Refresh the README --- README.md | 119 ++++++++++++++++++------------------------------------ 1 file changed, 39 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index e14f31b56a6..053aa84619e 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 25/27] 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 3c92bc61d7c..130b1454469 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 795f4ad56cb..d855490d18c 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 26/27] 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 456bcf472fb..af1e9b5b697 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 998d727a4e2..77c96de5614 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 657e90745ed..d7418d0cb5f 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 27/27] 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 80810f2ba5e..9ed740fff17 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 975fc025a3d..8e4ab54a5b6 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 3e4811ece1a..7292c676bd3 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 130b1454469..1581ce681ef 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 4d74307a08f..8517efca190 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 3ab7faacedb..3cb2529a8d3 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