From e96cfadd22fbb4b207a04f207734ed1e6c97261a Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 5 May 2023 12:58:09 -0400 Subject: [PATCH 01/43] PRVB --- docs/release-notes/version-3.5.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 203c439c2..67d2b498a 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -1,5 +1,9 @@ # NetBox v3.5 +## v3.5.2 (FUTURE) + +--- + ## v3.5.1 (2023-05-05) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index dddceca9b..3f3f96736 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.5.1' +VERSION = '3.5.2-dev' # Hostname HOSTNAME = platform.node() From 12bef7623ccc3673394c6905e163976e626f1130 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 5 May 2023 22:05:52 +0530 Subject: [PATCH 02/43] disables map button when MAP_URL is none #12498 --- netbox/templates/dcim/site.html | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html index d6de8f3cb..91fdba7be 100644 --- a/netbox/templates/dcim/site.html +++ b/netbox/templates/dcim/site.html @@ -87,11 +87,13 @@ Physical Address {% if object.physical_address %} - + {% if config.MAPS_URL %} + + {% endif %} {{ object.physical_address|linebreaksbr }} {% else %} {{ ''|placeholder }} @@ -106,11 +108,13 @@ GPS Coordinates {% if object.latitude and object.longitude %} - + {% if config.MAPS_URL %} + + {% endif %} {{ object.latitude }}, {{ object.longitude }} {% else %} {{ ''|placeholder }} From 896b19eaa31757baf18035bdcb35bdf2635eedc4 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 5 May 2023 19:53:16 +0530 Subject: [PATCH 03/43] adds parent device and bay position to table #12223 --- netbox/dcim/forms/model_forms.py | 2 +- netbox/dcim/tables/devices.py | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index d756036f4..219216045 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -1214,7 +1214,7 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form): installed_device = forms.ModelChoiceField( queryset=Device.objects.all(), label=_('Child Device'), - help_text=_("Child devices must first be created and assigned to the site/rack of the parent device.") + help_text=_("Child devices must first be created and assigned to the site and rack of the parent device.") ) def __init__(self, device_bay, *args, **kwargs): diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 056d05c9a..db2655d27 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -216,6 +216,16 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable): config_template = tables.Column( linkify=True ) + parent_device = tables.Column( + verbose_name='Parent Device', + linkify=True, + accessor='parent_bay__device' + ) + device_bay_position = tables.Column( + verbose_name='Position (Device Bay)', + accessor='parent_bay', + linkify=True + ) comments = columns.MarkdownColumn() tags = columns.TagColumn( url_name='dcim:device_list' @@ -225,9 +235,10 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable): model = models.Device fields = ( 'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type', - 'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'position', 'face', - 'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', - 'vc_priority', 'description', 'config_template', 'comments', 'contacts', 'tags', 'created', 'last_updated', + 'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'parent_device', + 'device_bay_position', 'position', 'face', 'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', + 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template', 'comments', 'contacts', + 'tags', 'created', 'last_updated', ) default_columns = ( 'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type', From da781b8d28280e0686cf807f9f2f7548ee74ef17 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 5 May 2023 15:25:08 -0400 Subject: [PATCH 04/43] Changelog for #12223, #12498 --- docs/release-notes/version-3.5.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 67d2b498a..8bdfb1578 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -2,6 +2,11 @@ ## v3.5.2 (FUTURE) +### Enhancements + +* [#12223](https://github.com/netbox-community/netbox/issues/12223) - Add columns for parent device bay and position to devices list +* [#12498](https://github.com/netbox-community/netbox/issues/12498) - Hide map button if `MAPS_URL` is empty + --- ## v3.5.1 (2023-05-05) From 9eeca06115b61cbed61a9ef536e200ddb8a4be10 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 5 May 2023 15:26:22 -0400 Subject: [PATCH 05/43] #12498: Annotate option to set MAPS_URL=None --- docs/configuration/miscellaneous.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index 14f0b9151..c3fbb40aa 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -129,7 +129,7 @@ Setting this to True will display a "maintenance mode" banner at the top of ever Default: `https://maps.google.com/?q=` (Google Maps) -This specifies the URL to use when presenting a map of a physical location by street address or GPS coordinates. The URL must accept either a free-form street address or a comma-separated pair of numeric coordinates appended to it. +This specifies the URL to use when presenting a map of a physical location by street address or GPS coordinates. The URL must accept either a free-form street address or a comma-separated pair of numeric coordinates appended to it. Set this to `None` to disable the "map it" button within the UI. --- From 259d0e96f28635501e94eda5addfff1945d534ca Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Mon, 8 May 2023 18:26:28 +0530 Subject: [PATCH 06/43] Adds dimensions card to device view (#12509) * adds dimensions card to device view #12286 * Update netbox/templates/dcim/device.html Co-authored-by: Jeremy Stretch --------- Co-authored-by: Jeremy Stretch --- netbox/templates/dcim/device.html | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 2c79ab006..13d5f2a94 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -300,6 +300,29 @@ {% include 'inc/panels/contacts.html' %} {% include 'inc/panels/image_attachments.html' %} +
+
Dimensions
+
+ + + + + + + + + +
Height + {{ object.device_type.u_height }}U +
Weight + {% if object.device_type.weight %} + {{ object.device_type.weight|floatformat }} {{ object.device_type.get_weight_unit_display }} + {% else %} + {{ ''|placeholder }} + {% endif %} +
+
+
{% if object.rack and object.position %}
From cb6852bf7add10289bc17a6ae9389303922af573 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 9 May 2023 01:32:16 +0530 Subject: [PATCH 07/43] adds CXP (100GE) #12323 --- netbox/dcim/choices.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 8ad8a0eb7..d32f5aaee 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -807,6 +807,7 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_100GE_CFP = '100gbase-x-cfp' TYPE_100GE_CFP2 = '100gbase-x-cfp2' TYPE_100GE_CFP4 = '100gbase-x-cfp4' + TYPE_100GE_CXP = '100gbase-x-cxp' TYPE_100GE_CPAK = '100gbase-x-cpak' TYPE_100GE_QSFP28 = '100gbase-x-qsfp28' TYPE_200GE_CFP2 = '200gbase-x-cfp2' @@ -952,6 +953,7 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_100GE_CFP2, 'CFP2 (100GE)'), (TYPE_200GE_CFP2, 'CFP2 (200GE)'), (TYPE_100GE_CFP4, 'CFP4 (100GE)'), + (TYPE_100GE_CXP, 'CXP (100GE)'), (TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'), (TYPE_100GE_QSFP28, 'QSFP28 (100GE)'), (TYPE_200GE_QSFP56, 'QSFP56 (200GE)'), From 1af3ba949689e1d5452a3290f023b024821b130a Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 9 May 2023 18:01:50 +0530 Subject: [PATCH 08/43] Adds full_clean in examples (#12527) * adds full_clean in examples #11689 * removes extra info --- docs/administration/netbox-shell.md | 8 ++------ docs/customization/custom-scripts.md | 2 ++ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/administration/netbox-shell.md b/docs/administration/netbox-shell.md index 1a10c5c3e..21cef01b2 100644 --- a/docs/administration/netbox-shell.md +++ b/docs/administration/netbox-shell.md @@ -153,15 +153,10 @@ New objects can be created by instantiating the desired model, defining values f ``` >>> lab1 = Site.objects.get(pk=7) >>> myvlan = VLAN(vid=123, name='MyNewVLAN', site=lab1) +>>> myvlan.full_clean() >>> myvlan.save() ``` -Alternatively, the above can be performed as a single operation. (Note, however, that `save()` does _not_ return the new instance for reuse.) - -``` ->>> VLAN(vid=123, name='MyNewVLAN', site=Site.objects.get(pk=7)).save() -``` - To modify an existing object, we retrieve it, update the desired field(s), and call `save()` again. ``` @@ -169,6 +164,7 @@ To modify an existing object, we retrieve it, update the desired field(s), and c >>> vlan.name 'MyNewVLAN' >>> vlan.name = 'BetterName' +>>> vlan.full_clean() >>> vlan.save() >>> VLAN.objects.get(pk=1280).name 'BetterName' diff --git a/docs/customization/custom-scripts.md b/docs/customization/custom-scripts.md index 074da34cd..e20b09ae6 100644 --- a/docs/customization/custom-scripts.md +++ b/docs/customization/custom-scripts.md @@ -378,6 +378,7 @@ class NewBranchScript(Script): slug=slugify(data['site_name']), status=SiteStatusChoices.STATUS_PLANNED ) + site.full_clean() site.save() self.log_success(f"Created new site: {site}") @@ -391,6 +392,7 @@ class NewBranchScript(Script): status=DeviceStatusChoices.STATUS_PLANNED, device_role=switch_role ) + switch.full_clean() switch.save() self.log_success(f"Created new switch: {switch}") From 2b2c559a370f32aabb463c4e1faacaf07d82f342 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 9 May 2023 01:07:31 +0530 Subject: [PATCH 09/43] updates ldap doc for centos #12447 --- docs/installation/6-ldap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/6-ldap.md b/docs/installation/6-ldap.md index ffba6889b..1cd3e1f0a 100644 --- a/docs/installation/6-ldap.md +++ b/docs/installation/6-ldap.md @@ -15,7 +15,7 @@ sudo apt install -y libldap2-dev libsasl2-dev libssl-dev On CentOS: ```no-highlight -sudo yum install -y openldap-devel +sudo yum install -y openldap-devel python3-devel ``` ### Install django-auth-ldap From e1b7a3aeb609e22587da4002d933ceadc3aad7c4 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 9 May 2023 19:05:00 +0530 Subject: [PATCH 10/43] Replaced device type weight with device total weight (#12522) * replaced device type weight with device total weight #12286 * replaced device type weight with device total weight #12286 * Update netbox/templates/dcim/device.html Co-authored-by: Jeremy Stretch --------- Co-authored-by: Jeremy Stretch --- netbox/templates/dcim/device.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 13d5f2a94..aa1b80cf7 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -313,8 +313,8 @@ Weight - {% if object.device_type.weight %} - {{ object.device_type.weight|floatformat }} {{ object.device_type.get_weight_unit_display }} + {% if object.total_weight %} + {{ object.total_weight|floatformat }} Kilograms {% else %} {{ ''|placeholder }} {% endif %} From c55c14ea4c48ddb5d16b0411d56c919cec34e4f1 Mon Sep 17 00:00:00 2001 From: Dillon Henschen Date: Tue, 9 May 2023 09:59:42 -0400 Subject: [PATCH 11/43] Closes #11670: Add ability to optionally import DeviceType and ModuleType weight (#12512) * 11670: Add optional weight to DeviceType import This is 1 of 2 commits to address issue #11670 To maintain consistency, the import design of the DeviceType weight follows the same pattern used for importing weight and weight units in DCIM Racks. * Closes #11670: Add weight to ModuleType import This is commit 2 of 2 to address and close #11670. To maintain consistency, the import design of the ModuleType weight follows the same pattern used for importing weight and weight units in DCIM Racks. * Merge tests; misc cleanup --------- Co-authored-by: jeremystretch --- netbox/dcim/forms/bulk_import.py | 22 ++++++++++++++++++++-- netbox/dcim/models/devices.py | 4 ++++ netbox/dcim/tests/test_views.py | 24 +++++++++++++++++++++--- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index 8b7bd47ea..cdb59e9eb 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -292,12 +292,21 @@ class DeviceTypeImportForm(NetBoxModelImportForm): required=False, help_text=_('The default platform for devices of this type (optional)') ) + weight = forms.DecimalField( + required=False, + help_text=_('Device weight'), + ) + weight_unit = CSVChoiceField( + choices=WeightUnitChoices, + required=False, + help_text=_('Unit for device weight') + ) class Meta: model = DeviceType fields = [ 'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', - 'subdevice_role', 'airflow', 'description', 'comments', + 'subdevice_role', 'airflow', 'description', 'weight', 'weight_unit', 'comments', ] @@ -306,10 +315,19 @@ class ModuleTypeImportForm(NetBoxModelImportForm): queryset=Manufacturer.objects.all(), to_field_name='name' ) + weight = forms.DecimalField( + required=False, + help_text=_('Module weight'), + ) + weight_unit = CSVChoiceField( + choices=WeightUnitChoices, + required=False, + help_text=_('Unit for module weight') + ) class Meta: model = ModuleType - fields = ['manufacturer', 'model', 'part_number', 'description', 'comments'] + fields = ['manufacturer', 'model', 'part_number', 'description', 'weight', 'weight_unit', 'comments'] class DeviceRoleImportForm(NetBoxModelImportForm): diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 02c68c10a..85a5d6870 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -184,6 +184,8 @@ class DeviceType(PrimaryModel, WeightMixin): 'subdevice_role': self.subdevice_role, 'airflow': self.airflow, 'comments': self.comments, + 'weight': float(self.weight) if self.weight is not None else None, + 'weight_unit': self.weight_unit, } # Component templates @@ -361,6 +363,8 @@ class ModuleType(PrimaryModel, WeightMixin): 'model': self.model, 'part_number': self.part_number, 'comments': self.comments, + 'weight': float(self.weight) if self.weight is not None else None, + 'weight_unit': self.weight_unit, } # Component templates diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index bae5a8e0b..44e6ef2a9 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -681,11 +681,15 @@ class DeviceTypeTestCase( """ IMPORT_DATA = """ manufacturer: Generic -default_platform: Platform model: TEST-1000 slug: test-1000 +default_platform: Platform u_height: 2 +is_full_depth: false +airflow: front-to-rear subdevice_role: parent +weight: 10 +weight_unit: kg comments: Test comment console-ports: - name: Console Port 1 @@ -794,8 +798,16 @@ inventory-items: self.assertHttpStatus(response, 200) device_type = DeviceType.objects.get(model='TEST-1000') - self.assertEqual(device_type.comments, 'Test comment') + self.assertEqual(device_type.manufacturer.pk, manufacturer.pk) self.assertEqual(device_type.default_platform.pk, platform.pk) + self.assertEqual(device_type.slug, 'test-1000') + self.assertEqual(device_type.u_height, 2) + self.assertFalse(device_type.is_full_depth) + self.assertEqual(device_type.airflow, DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR) + self.assertEqual(device_type.subdevice_role, SubdeviceRoleChoices.ROLE_PARENT) + self.assertEqual(device_type.weight, 10) + self.assertEqual(device_type.weight_unit, WeightUnitChoices.UNIT_KILOGRAM) + self.assertEqual(device_type.comments, 'Test comment') # Verify all of the components were created self.assertEqual(device_type.consoleporttemplates.count(), 3) @@ -1019,6 +1031,8 @@ class ModuleTypeTestCase( IMPORT_DATA = """ manufacturer: Generic model: TEST-1000 +weight: 10 +weight_unit: lb comments: Test comment console-ports: - name: Console Port 1 @@ -1082,7 +1096,8 @@ front-ports: """ # Create the manufacturer - Manufacturer(name='Generic', slug='generic').save() + manufacturer = Manufacturer(name='Generic', slug='generic') + manufacturer.save() # Add all required permissions to the test user self.add_permissions( @@ -1105,6 +1120,9 @@ front-ports: self.assertHttpStatus(response, 200) module_type = ModuleType.objects.get(model='TEST-1000') + self.assertEqual(module_type.manufacturer.pk, manufacturer.pk) + self.assertEqual(module_type.weight, 10) + self.assertEqual(module_type.weight_unit, WeightUnitChoices.UNIT_POUND) self.assertEqual(module_type.comments, 'Test comment') # Verify all the components were created From 4e49f4a434b109aede79b618b7dd8d3d6865c17f Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 9 May 2023 19:50:02 +0530 Subject: [PATCH 12/43] Adds tooltip on custom field (#12505) * adds tooltip on custom field #12131 * adds description field check * fixed field name * updated code to match the panel * added escape filter on description --- .../templates/circuits/inc/circuit_termination.html | 13 ++++++++++--- netbox/templates/inc/panels/custom_fields.html | 11 +++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/netbox/templates/circuits/inc/circuit_termination.html b/netbox/templates/circuits/inc/circuit_termination.html index 22c204afc..b26a09205 100644 --- a/netbox/templates/circuits/inc/circuit_termination.html +++ b/netbox/templates/circuits/inc/circuit_termination.html @@ -132,9 +132,16 @@ {% for field, value in fields.items %} - - {{ field }} - + {{ field }} + {% if field.description %} + + {% endif %} + {% customfield_value field value %} diff --git a/netbox/templates/inc/panels/custom_fields.html b/netbox/templates/inc/panels/custom_fields.html index 45843eea5..7f5f4cc27 100644 --- a/netbox/templates/inc/panels/custom_fields.html +++ b/netbox/templates/inc/panels/custom_fields.html @@ -12,8 +12,15 @@ {% for field, value in fields.items %} -
- {{ field }} + {{ field }} + {% if field.description %} + + {% endif %} {% customfield_value field value %} From 57156f0e9442b60d1665eb1839ef806ddfd55726 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 9 May 2023 19:51:23 +0530 Subject: [PATCH 13/43] Adds stroke to the reservation (#12506) * adds stroke to the reservation #11900 * fixed right side border * Tweak reserved stroke style & add constants for colors --------- Co-authored-by: jeremystretch --- netbox/dcim/svg/racks.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/netbox/dcim/svg/racks.py b/netbox/dcim/svg/racks.py index 62878cef9..9c317ea16 100644 --- a/netbox/dcim/svg/racks.py +++ b/netbox/dcim/svg/racks.py @@ -22,6 +22,11 @@ __all__ = ( 'RackElevationSVG', ) +GRADIENT_RESERVED = '#b0b0ff' +GRADIENT_OCCUPIED = '#d7d7d7' +GRADIENT_BLOCKED = '#ffc0c0' +STROKE_RESERVED = '#4d4dff' + def get_device_name(device): if device.virtual_chassis: @@ -132,9 +137,9 @@ class RackElevationSVG: drawing.defs.add(drawing.style(css_file.read())) # Add gradients - RackElevationSVG._add_gradient(drawing, 'reserved', '#b0b0ff') - RackElevationSVG._add_gradient(drawing, 'occupied', '#d7d7d7') - RackElevationSVG._add_gradient(drawing, 'blocked', '#ffc0c0') + RackElevationSVG._add_gradient(drawing, 'reserved', GRADIENT_RESERVED) + RackElevationSVG._add_gradient(drawing, 'occupied', GRADIENT_OCCUPIED) + RackElevationSVG._add_gradient(drawing, 'blocked', GRADIENT_BLOCKED) return drawing @@ -246,13 +251,13 @@ class RackElevationSVG: coords = self._get_device_coords(segment[0], u_height) coords = (coords[0] + self.unit_width + RACK_ELEVATION_BORDER_WIDTH * 2, coords[1]) size = ( - self.margin_width, + self.margin_width - 3, u_height * self.unit_height ) link = Hyperlink(href=f'{self.base_url}{reservation.get_absolute_url()}', target='_parent') link.set_desc(f'Reservation #{reservation.pk}: {reservation.description}') link.add( - Rect(coords, size, class_='reservation') + Rect(coords, size, class_='reservation', stroke=STROKE_RESERVED, stroke_width=2) ) self.drawing.add(link) From 6b19f15a7b605ff87aefd6905a930a452ceffb9b Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 9 May 2023 22:19:13 +0530 Subject: [PATCH 14/43] Moves related ips to a tab (#12502) * moves related ips to a tab #12233 * Refactor IP address templates to use a base template --------- Co-authored-by: jeremystretch --- netbox/ipam/models/ip.py | 8 ++++++ netbox/ipam/views.py | 28 ++++++++++++------- netbox/templates/ipam/ipaddress.html | 8 ------ netbox/templates/ipam/ipaddress/base.html | 8 ++++++ .../ipam/ipaddress/ip_addresses.html | 19 +++++++++++++ 5 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 netbox/templates/ipam/ipaddress/base.html create mode 100644 netbox/templates/ipam/ipaddress/ip_addresses.html diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 28901ab8e..015f9220c 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -783,6 +783,14 @@ class IPAddress(PrimaryModel): if available_ips: return next(iter(available_ips)) + def get_related_ips(self): + """ + Return all IPAddresses belonging to the same VRF. + """ + return IPAddress.objects.exclude(address=str(self.address)).filter( + vrf=self.vrf, address__net_contained_or_equal=str(self.address) + ) + def clean(self): super().clean() diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 93d0dc8bb..6b19b502d 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -755,19 +755,9 @@ class IPAddressView(generic.ObjectView): # Limit to a maximum of 10 duplicates displayed here duplicate_ips_table = tables.IPAddressTable(duplicate_ips[:10], orderable=False) - # Related IP table - related_ips = IPAddress.objects.restrict(request.user, 'view').exclude( - address=str(instance.address) - ).filter( - vrf=instance.vrf, address__net_contained_or_equal=str(instance.address) - ) - related_ips_table = tables.IPAddressTable(related_ips, orderable=False) - related_ips_table.configure(request) - return { 'parent_prefixes_table': parent_prefixes_table, 'duplicate_ips_table': duplicate_ips_table, - 'related_ips_table': related_ips_table, } @@ -872,6 +862,24 @@ class IPAddressBulkDeleteView(generic.BulkDeleteView): table = tables.IPAddressTable +@register_model_view(IPAddress, 'related_ips', path='related-ip-addresses') +class IPAddressRelatedIPsView(generic.ObjectChildrenView): + queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant') + child_model = IPAddress + table = tables.IPAddressTable + filterset = filtersets.IPAddressFilterSet + template_name = 'ipam/ipaddress/ip_addresses.html' + tab = ViewTab( + label=_('Related IPs'), + badge=lambda x: x.get_related_ips().count(), + weight=500, + hide_if_empty=True, + ) + + def get_children(self, request, parent): + return parent.get_related_ips().restrict(request.user, 'view') + + # # VLAN groups # diff --git a/netbox/templates/ipam/ipaddress.html b/netbox/templates/ipam/ipaddress.html index c649f1dad..e58ac736f 100644 --- a/netbox/templates/ipam/ipaddress.html +++ b/netbox/templates/ipam/ipaddress.html @@ -3,13 +3,6 @@ {% load plugins %} {% load render_table from django_tables2 %} -{% block breadcrumbs %} - {{ block.super }} - {% if object.vrf %} - - {% endif %} -{% endblock %} - {% block content %}
@@ -116,7 +109,6 @@ {% if duplicate_ips_table.rows %} {% include 'inc/panel_table.html' with table=duplicate_ips_table heading='Duplicate IPs' panel_class='danger' %} {% endif %} - {% include 'inc/panel_table.html' with table=related_ips_table heading='Related IPs' %}
Services
{{ object.vrf }} + {% endif %} +{% endblock %} diff --git a/netbox/templates/ipam/ipaddress/ip_addresses.html b/netbox/templates/ipam/ipaddress/ip_addresses.html new file mode 100644 index 000000000..7034329aa --- /dev/null +++ b/netbox/templates/ipam/ipaddress/ip_addresses.html @@ -0,0 +1,19 @@ +{% extends 'ipam/ipaddress/base.html' %} +{% load helpers %} + +{% block content %} + {% include 'inc/table_controls_htmx.html' with table_modal="IPAddressTable_config" %} +
+ {% csrf_token %} +
+
+ {% include 'htmx/table.html' %} +
+
+
+{% endblock content %} + +{% block modals %} + {{ block.super }} + {% table_config_form table %} +{% endblock modals %} From 2d0ac213c70b7483f406d63c09945ac69c8ed290 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 10 May 2023 09:39:25 -0400 Subject: [PATCH 15/43] Changelog for #11670, #11900, #12131, #12233, #12286, #12323 --- docs/release-notes/version-3.5.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 8bdfb1578..b9094a658 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -4,7 +4,13 @@ ### Enhancements +* [#11670](https://github.com/netbox-community/netbox/issues/11670) - Enable setting device type & module type weight via bulk import +* [#11900](https://github.com/netbox-community/netbox/issues/11900) - Add an outline to the reservation markers on rack elevations +* [#12131](https://github.com/netbox-community/netbox/issues/12131) - Show custom field description as an icon tooltip under object views * [#12223](https://github.com/netbox-community/netbox/issues/12223) - Add columns for parent device bay and position to devices list +* [#12233](https://github.com/netbox-community/netbox/issues/12233) - Move related IP addresses table to a separate tab +* [#12286](https://github.com/netbox-community/netbox/issues/12286) - Show height and total weight under device view +* [#12323](https://github.com/netbox-community/netbox/issues/12323) - Add 100GE CXP interface type * [#12498](https://github.com/netbox-community/netbox/issues/12498) - Hide map button if `MAPS_URL` is empty --- From b7f028fba39026ca92f439c2ba64b0dfa587d429 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 10 May 2023 10:44:01 -0400 Subject: [PATCH 16/43] Fixes #12550: Fix rear port selection widget under front port creation form --- docs/release-notes/version-3.5.md | 4 ++++ netbox/dcim/forms/object_create.py | 1 + netbox/utilities/forms/mixins.py | 8 ++++---- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index b9094a658..36dc97bb9 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -13,6 +13,10 @@ * [#12323](https://github.com/netbox-community/netbox/issues/12323) - Add 100GE CXP interface type * [#12498](https://github.com/netbox-community/netbox/issues/12498) - Hide map button if `MAPS_URL` is empty +### Bug Fixes + +* [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form + --- ## v3.5.1 (2023-05-05) diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py index 236077421..d4c9e6ec3 100644 --- a/netbox/dcim/forms/object_create.py +++ b/netbox/dcim/forms/object_create.py @@ -242,6 +242,7 @@ class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm): choices=[], label=_('Rear ports'), help_text=_('Select one rear port assignment for each front port being created.'), + widget=forms.SelectMultiple(attrs={'size': 6}) ) # Override fieldsets from FrontPortForm to omit rear_port_position diff --git a/netbox/utilities/forms/mixins.py b/netbox/utilities/forms/mixins.py index dc9c3eb80..7cdb9731e 100644 --- a/netbox/utilities/forms/mixins.py +++ b/netbox/utilities/forms/mixins.py @@ -32,11 +32,11 @@ class BootstrapMixin: elif isinstance(field.widget, forms.CheckboxInput): field.widget.attrs['class'] = f'{css} form-check-input' - elif isinstance(field.widget, forms.SelectMultiple): - if 'size' not in field.widget.attrs: - field.widget.attrs['class'] = f'{css} netbox-static-select' + elif isinstance(field.widget, forms.SelectMultiple) and 'size' in field.widget.attrs: + # Use native Bootstrap class for multi-line - - - - - - - - - {% for contact in contacts %} - - - - - - - - - {% endfor %} -
NameRolePriorityPhoneEmail
{{ contact.contact|linkify }}{{ contact.role|placeholder }}{{ contact.get_priority_display|placeholder }} - {% if contact.contact.phone %} - {{ contact.contact.phone }} - {% else %} - {{ ''|placeholder }} - {% endif %} - - {% if contact.contact.email %} - {{ contact.contact.email }} - {% else %} - {{ ''|placeholder }} - {% endif %} - - {% if perms.tenancy.change_contactassignment %} - - - - {% endif %} - {% if perms.tenancy.delete_contactassignment %} - - - - {% endif %} -
- {% else %} -
None
- {% endif %} - {% endwith %} -
- {% if perms.tenancy.add_contactassignment %} - - {% endif %} -
diff --git a/netbox/templates/ipam/l2vpn.html b/netbox/templates/ipam/l2vpn.html index 87050eb26..8896dd6c2 100644 --- a/netbox/templates/ipam/l2vpn.html +++ b/netbox/templates/ipam/l2vpn.html @@ -37,7 +37,6 @@ {% plugin_left_page object %}
- {% include 'inc/panels/contacts.html' %} {% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/comments.html' %} {% plugin_right_page object %} diff --git a/netbox/templates/tenancy/object_contacts.html b/netbox/templates/tenancy/object_contacts.html new file mode 100644 index 000000000..aca63a379 --- /dev/null +++ b/netbox/templates/tenancy/object_contacts.html @@ -0,0 +1,27 @@ +{% extends base_template %} +{% load helpers %} + +{% block extra_controls %} + {% if perms.tenancy.add_contactassignment %} + + Add a contact + + {% endif %} +{% endblock %} + +{% block content %} + {% include 'inc/table_controls_htmx.html' with table_modal="ContactTable_config" %} +
+ {% csrf_token %} +
+
+ {% include 'htmx/table.html' %} +
+
+
+{% endblock content %} + +{% block modals %} + {{ block.super }} + {% table_config_form table %} +{% endblock modals %} diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html index da48f1ef5..34abe5c01 100644 --- a/netbox/templates/tenancy/tenant.html +++ b/netbox/templates/tenancy/tenant.html @@ -30,7 +30,6 @@ {% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} {% include 'inc/panels/comments.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_left_page object %}
diff --git a/netbox/templates/virtualization/cluster.html b/netbox/templates/virtualization/cluster.html index 3dfef108b..508bca547 100644 --- a/netbox/templates/virtualization/cluster.html +++ b/netbox/templates/virtualization/cluster.html @@ -84,7 +84,6 @@
{% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/virtualization/clustergroup.html b/netbox/templates/virtualization/clustergroup.html index 510433068..2496ad085 100644 --- a/netbox/templates/virtualization/clustergroup.html +++ b/netbox/templates/virtualization/clustergroup.html @@ -37,7 +37,6 @@
{% include 'inc/panels/related_objects.html' %} {% include 'inc/panels/custom_fields.html' %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %}
diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 5098a2f8f..51fd8aa80 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -158,7 +158,6 @@ {% endif %} - {% include 'inc/panels/contacts.html' %} {% plugin_right_page object %} diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index b9ada8640..5f8a7e314 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -7,17 +7,40 @@ from dcim.models import Cable, Device, Location, Rack, RackReservation, Site, Vi from ipam.models import Aggregate, ASN, IPAddress, IPRange, L2VPN, Prefix, VLAN, VRF from netbox.views import generic from utilities.utils import count_related -from utilities.views import register_model_view +from utilities.views import register_model_view, ViewTab from virtualization.models import VirtualMachine, Cluster from wireless.models import WirelessLAN, WirelessLink from . import filtersets, forms, tables from .models import * +class ObjectContactsView(generic.ObjectChildrenView): + child_model = Contact + table = tables.ContactTable + filterset = filtersets.ContactFilterSet + template_name = 'tenancy/object_contacts.html' + tab = ViewTab( + label=_('Contacts'), + badge=lambda obj: obj.contacts.count(), + permission='tenancy.view_contact', + weight=5000 + ) + + def get_children(self, request, parent): + return Contact.objects.annotate( + assignment_count=count_related(ContactAssignment, 'contact') + ).restrict(request.user, 'view').filter(assignments__object_id=parent.pk) + + def get_extra_context(self, request, instance): + return { + 'base_template': f'{instance._meta.app_label}/{instance._meta.model_name}.html', + } + # # Tenant groups # + class TenantGroupListView(generic.ObjectListView): queryset = TenantGroup.objects.add_related_count( TenantGroup.objects.all(), @@ -165,6 +188,11 @@ class TenantBulkDeleteView(generic.BulkDeleteView): table = tables.TenantTable +@register_model_view(Tenant, 'contacts') +class TenantContactsView(ObjectContactsView): + queryset = Tenant.objects.all() + + # # Contact groups # @@ -342,11 +370,11 @@ class ContactBulkDeleteView(generic.BulkDeleteView): filterset = filtersets.ContactFilterSet table = tables.ContactTable - # # Contact assignments # + class ContactAssignmentListView(generic.ObjectListView): queryset = ContactAssignment.objects.all() filterset = filtersets.ContactAssignmentFilterSet diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 9014aa9dd..4a501e14e 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -9,9 +9,10 @@ from dcim.filtersets import DeviceFilterSet from dcim.models import Device from dcim.tables import DeviceTable from extras.views import ObjectConfigContextView -from ipam.models import IPAddress, Service +from ipam.models import IPAddress from ipam.tables import InterfaceVLANTable from netbox.views import generic +from tenancy.views import ObjectContactsView from utilities.utils import count_related from utilities.views import ViewTab, register_model_view from . import filtersets, forms, tables @@ -140,6 +141,11 @@ class ClusterGroupBulkDeleteView(generic.BulkDeleteView): table = tables.ClusterGroupTable +@register_model_view(ClusterGroup, 'contacts') +class ClusterGroupContactsView(ObjectContactsView): + queryset = ClusterGroup.objects.all() + + # # Clusters # @@ -312,6 +318,11 @@ class ClusterRemoveDevicesView(generic.ObjectEditView): }) +@register_model_view(Cluster, 'contacts') +class ClusterContactsView(ObjectContactsView): + queryset = Cluster.objects.all() + + # # Virtual machines # @@ -390,6 +401,11 @@ class VirtualMachineBulkDeleteView(generic.BulkDeleteView): table = tables.VirtualMachineTable +@register_model_view(VirtualMachine, 'contacts') +class VirtualMachineContactsView(ObjectContactsView): + queryset = VirtualMachine.objects.all() + + # # VM interfaces # From cc0c985fecffefac621cf414f560a52655c21335 Mon Sep 17 00:00:00 2001 From: Jon Schewe Date: Fri, 12 May 2023 09:35:09 -0500 Subject: [PATCH 21/43] Feature/remote group autocreate (#12394) * Add REMOTE_AUTH_AUTOCREATE_GROUPS When REMOTE_AUTH_AUTOCREATE_GROUPS is True, Netbox will create groups referenced in the REMOTE_AUTH_GROUP_HEADER that don't exist in the database. Closes #7671 * Fix naming of parameter Apply the fix requested by kkthxbye-code in https://github.com/netbox-community/netbox/pull/8603 --------- Co-authored-by: Lars Kellogg-Stedman --- netbox/netbox/authentication.py | 7 +++- netbox/netbox/settings.py | 1 + netbox/netbox/tests/test_authentication.py | 44 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication.py index 798cb80e2..61dfe2fdb 100644 --- a/netbox/netbox/authentication.py +++ b/netbox/netbox/authentication.py @@ -156,8 +156,11 @@ class RemoteUserBackend(_RemoteUserBackend): try: group_list.append(Group.objects.get(name=name)) except Group.DoesNotExist: - logging.error( - f"Could not assign group {name} to remotely-authenticated user {user}: Group not found") + if settings.REMOTE_AUTH_AUTO_CREATE_GROUPS: + group_list.append(Group.objects.create(name=name)) + else: + logging.error( + f"Could not assign group {name} to remotely-authenticated user {user}: Group not found") if group_list: user.groups.set(group_list) logger.debug( diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 3f3f96736..b56ae46c4 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -122,6 +122,7 @@ PLUGINS_CONFIG = getattr(configuration, 'PLUGINS_CONFIG', {}) QUEUE_MAPPINGS = getattr(configuration, 'QUEUE_MAPPINGS', {}) RELEASE_CHECK_URL = getattr(configuration, 'RELEASE_CHECK_URL', None) REMOTE_AUTH_AUTO_CREATE_USER = getattr(configuration, 'REMOTE_AUTH_AUTO_CREATE_USER', False) +REMOTE_AUTH_AUTO_CREATE_GROUPS = getattr(configuration, 'REMOTE_AUTH_AUTO_CREATE_GROUPS', False) REMOTE_AUTH_BACKEND = getattr(configuration, 'REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend') REMOTE_AUTH_DEFAULT_GROUPS = getattr(configuration, 'REMOTE_AUTH_DEFAULT_GROUPS', []) REMOTE_AUTH_DEFAULT_PERMISSIONS = getattr(configuration, 'REMOTE_AUTH_DEFAULT_PERMISSIONS', {}) diff --git a/netbox/netbox/tests/test_authentication.py b/netbox/netbox/tests/test_authentication.py index 790cb4bd8..4e46996b5 100644 --- a/netbox/netbox/tests/test_authentication.py +++ b/netbox/netbox/tests/test_authentication.py @@ -310,6 +310,50 @@ class ExternalAuthenticationTestCase(TestCase): list(new_user.groups.all()) ) + @override_settings( + REMOTE_AUTH_ENABLED=True, + REMOTE_AUTH_AUTO_CREATE_USER=True, + REMOTE_AUTH_GROUP_SYNC_ENABLED=True, + REMOTE_AUTH_AUTO_CREATE_GROUPS=True, + LOGIN_REQUIRED=True, + ) + def test_remote_auth_remote_groups_autocreate(self): + """ + Test enabling remote authentication with group sync and autocreate + enabled with the default configuration. + """ + headers = { + "HTTP_REMOTE_USER": "remoteuser2", + "HTTP_REMOTE_USER_GROUP": "Group 1|Group 2", + } + + self.assertTrue(settings.REMOTE_AUTH_ENABLED) + self.assertTrue(settings.REMOTE_AUTH_AUTO_CREATE_USER) + self.assertTrue(settings.REMOTE_AUTH_AUTO_CREATE_GROUPS) + self.assertTrue(settings.REMOTE_AUTH_GROUP_SYNC_ENABLED) + self.assertEqual(settings.REMOTE_AUTH_HEADER, "HTTP_REMOTE_USER") + self.assertEqual(settings.REMOTE_AUTH_GROUP_HEADER, "HTTP_REMOTE_USER_GROUP") + self.assertEqual(settings.REMOTE_AUTH_GROUP_SEPARATOR, "|") + + groups = ( + Group(name="Group 1"), + Group(name="Group 2"), + ) + + response = self.client.get(reverse("home"), follow=True, **headers) + self.assertEqual(response.status_code, 200) + + new_user = User.objects.get(username="remoteuser2") + self.assertEqual( + int(self.client.session.get("_auth_user_id")), + new_user.pk, + msg="Authentication failed", + ) + self.assertListEqual( + [group.name for group in groups], + [group.name for group in list(new_user.groups.all())], + ) + @override_settings( REMOTE_AUTH_ENABLED=True, REMOTE_AUTH_AUTO_CREATE_USER=True, From 9b80ec22bad4b555c0851e6338840d14027b4933 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 12 May 2023 20:20:51 +0530 Subject: [PATCH 22/43] Adds db read-only middleware (#12490) * adds db read-only middleware #11233 * fixed attribute error * replaces getattr with get_config --- netbox/netbox/middleware.py | 48 ++++++++++++++++++++++++++++++++++--- netbox/netbox/settings.py | 1 + 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/netbox/netbox/middleware.py b/netbox/netbox/middleware.py index f9faa9c5d..461c018b9 100644 --- a/netbox/netbox/middleware.py +++ b/netbox/netbox/middleware.py @@ -3,19 +3,21 @@ import uuid from urllib import parse from django.conf import settings -from django.contrib import auth +from django.contrib import auth, messages from django.contrib.auth.middleware import RemoteUserMiddleware as RemoteUserMiddleware_ from django.core.exceptions import ImproperlyConfigured -from django.db import ProgrammingError +from django.db import connection, ProgrammingError +from django.db.utils import InternalError from django.http import Http404, HttpResponseRedirect from extras.context_managers import change_logging -from netbox.config import clear_config +from netbox.config import clear_config, get_config from netbox.views import handler_500 from utilities.api import is_api_request, rest_api_server_error __all__ = ( 'CoreMiddleware', + 'MaintenanceModeMiddleware', 'RemoteUserMiddleware', ) @@ -166,3 +168,43 @@ class RemoteUserMiddleware(RemoteUserMiddleware_): groups = [] logger.debug(f"Groups are {groups}") return groups + + +class MaintenanceModeMiddleware: + """ + Middleware that checks if the application is in maintenance mode + and restricts write-related operations to the database. + """ + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if get_config().MAINTENANCE_MODE: + self._prevent_db_write_operations() + + return self.get_response(request) + + @staticmethod + def _prevent_db_write_operations(): + """ + Prevent any write-related database operations. + """ + with connection.cursor() as cursor: + cursor.execute( + 'SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;' + ) + + def process_exception(self, request, exception): + """ + Prevent any write-related database operations if an exception is raised. + """ + if isinstance(exception, InternalError): + error_message = 'NetBox is currently operating in maintenance mode and is unable to perform write ' \ + 'operations. Please try again later.' + + if is_api_request(request): + return rest_api_server_error(request, error=error_message) + + messages.error(request, error_message) + return HttpResponseRedirect(request.path_info) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index b56ae46c4..575755d2b 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -383,6 +383,7 @@ MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'netbox.middleware.RemoteUserMiddleware', 'netbox.middleware.CoreMiddleware', + 'netbox.middleware.MaintenanceModeMiddleware', 'django_prometheus.middleware.PrometheusAfterMiddleware', ] From ff874a24dd62071d85be9d40106414a121bdc91b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 12 May 2023 10:56:36 -0400 Subject: [PATCH 23/43] #7671: Document REMOTE_AUTH_AUTO_CREATE_GROUPS config parameter --- docs/configuration/remote-authentication.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/configuration/remote-authentication.md b/docs/configuration/remote-authentication.md index fd95adef5..fb789bd98 100644 --- a/docs/configuration/remote-authentication.md +++ b/docs/configuration/remote-authentication.md @@ -4,6 +4,14 @@ The configuration parameters listed here control remote authentication for NetBo --- +## REMOTE_AUTH_AUTO_CREATE_GROUPS + +Default: `False` + +If true, NetBox will automatically create groups specified in the `REMOTE_AUTH_GROUP_HEADER` header if they don't already exist. (Requires `REMOTE_AUTH_ENABLED`.) + +--- + ## REMOTE_AUTH_AUTO_CREATE_USER Default: `False` From 567285d36ae61b5151feaeb585b1d7c36d7b76fe Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 12 May 2023 11:00:33 -0400 Subject: [PATCH 24/43] Changelog for #7671, #10686, #11233, #11559, #12554 --- docs/release-notes/version-3.5.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 36dc97bb9..feb2f177e 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -4,6 +4,9 @@ ### Enhancements +* [#7671](https://github.com/netbox-community/netbox/issues/7671) - Introduce `REMOTE_AUTH_AUTO_CREATE_GROUPS` config parameter to enable the automatic creation of new groups when remote authentication is in use +* [#11233](https://github.com/netbox-community/netbox/issues/11233) - Intercept and cleanly report errors upon attempted database writes when maintenance mode is enabled +* [#11599](https://github.com/netbox-community/netbox/issues/11599) - Move contacts panels to separate tabs under object views * [#11670](https://github.com/netbox-community/netbox/issues/11670) - Enable setting device type & module type weight via bulk import * [#11900](https://github.com/netbox-community/netbox/issues/11900) - Add an outline to the reservation markers on rack elevations * [#12131](https://github.com/netbox-community/netbox/issues/12131) - Show custom field description as an icon tooltip under object views @@ -12,9 +15,11 @@ * [#12286](https://github.com/netbox-community/netbox/issues/12286) - Show height and total weight under device view * [#12323](https://github.com/netbox-community/netbox/issues/12323) - Add 100GE CXP interface type * [#12498](https://github.com/netbox-community/netbox/issues/12498) - Hide map button if `MAPS_URL` is empty +* [#12554](https://github.com/netbox-community/netbox/issues/12554) - Allow customization or disabling of the maintenance mode banner ### Bug Fixes +* [#10686](https://github.com/netbox-community/netbox/issues/10686) - Enable specifying termination object by virtual chassis master when importing cables * [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form --- From 39fd64b2effe4b5ee0ea8b41132624f458fb7ab8 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 12 May 2023 11:08:32 -0400 Subject: [PATCH 25/43] Fixes #12570: Disable ordering of synchronized object tables by the synced attribute --- docs/release-notes/version-3.5.md | 1 + netbox/extras/tables/tables.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index feb2f177e..39e159268 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -21,6 +21,7 @@ * [#10686](https://github.com/netbox-community/netbox/issues/10686) - Enable specifying termination object by virtual chassis master when importing cables * [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form +* [#12570](https://github.com/netbox-community/netbox/issues/12570) - Disable ordering of synchronized object tables by the "synced" attribute --- diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index e6d014302..8d046b85d 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -73,6 +73,7 @@ class ExportTemplateTable(NetBoxTable): linkify=True ) is_synced = columns.BooleanColumn( + orderable=False, verbose_name='Synced' ) @@ -218,6 +219,7 @@ class ConfigContextTable(NetBoxTable): verbose_name='Active' ) is_synced = columns.BooleanColumn( + orderable=False, verbose_name='Synced' ) @@ -242,6 +244,7 @@ class ConfigTemplateTable(NetBoxTable): linkify=True ) is_synced = columns.BooleanColumn( + orderable=False, verbose_name='Synced' ) tags = columns.TagColumn( From 21f4761335570d06c76294d88905211e4cf2eb20 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Fri, 12 May 2023 13:08:57 -0700 Subject: [PATCH 26/43] 12468 disallow double underscores in custom field names (#12523) * 12468 disallow double underscores in custom field names * 12468 disallow double underscores in custom field names * 12468 review changes * 12468 correct migration * 12468 use inverse match * 12468 use inverse match * Add test for invalid custom field names --------- Co-authored-by: jeremystretch --- .../0066_customfield_name_validation.py | 18 +++++++++++++++++- netbox/extras/models/customfields.py | 6 ++++++ netbox/extras/tests/test_customfields.py | 11 +++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/netbox/extras/migrations/0066_customfield_name_validation.py b/netbox/extras/migrations/0066_customfield_name_validation.py index 7a768c10c..3d2c51399 100644 --- a/netbox/extras/migrations/0066_customfield_name_validation.py +++ b/netbox/extras/migrations/0066_customfield_name_validation.py @@ -13,6 +13,22 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='customfield', name='name', - field=models.CharField(max_length=50, unique=True, validators=[django.core.validators.RegexValidator(flags=re.RegexFlag['IGNORECASE'], message='Only alphanumeric characters and underscores are allowed.', regex='^[a-z0-9_]+$')]), + field=models.CharField( + max_length=50, + unique=True, + validators=[ + django.core.validators.RegexValidator( + flags=re.RegexFlag['IGNORECASE'], + message='Only alphanumeric characters and underscores are allowed.', + regex='^[a-z0-9_]+$', + ), + django.core.validators.RegexValidator( + flags=re.RegexFlag['IGNORECASE'], + inverse_match=True, + message='Double underscores are not permitted in custom field names.', + regex=r'__', + ), + ], + ), ), ] diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 439d15edc..be3540f08 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -85,6 +85,12 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): message="Only alphanumeric characters and underscores are allowed.", flags=re.IGNORECASE ), + RegexValidator( + regex=r'__', + message="Double underscores are not permitted in custom field names.", + flags=re.IGNORECASE, + inverse_match=True + ), ) ) label = models.CharField( diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index 6a3a3d074..3fd0dc83e 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -29,6 +29,17 @@ class CustomFieldTest(TestCase): cls.object_type = ContentType.objects.get_for_model(Site) + def test_invalid_name(self): + """ + Try creating a CustomField with an invalid name. + """ + with self.assertRaises(ValidationError): + # Invalid character + CustomField(name='?', type=CustomFieldTypeChoices.TYPE_TEXT).full_clean() + with self.assertRaises(ValidationError): + # Double underscores not permitted + CustomField(name='foo__bar', type=CustomFieldTypeChoices.TYPE_TEXT).full_clean() + def test_text_field(self): value = 'Foobar!' From e40e9cb4064b7128e4a590fa3a99d5ff0a45723c Mon Sep 17 00:00:00 2001 From: Austin de Coup-Crank <94914780+decoupca@users.noreply.github.com> Date: Fri, 12 May 2023 15:10:12 -0500 Subject: [PATCH 27/43] Closes #11017: increase maximum power draw (#12587) * Convert power draw/max draw to PositiveIntegerField * Closes #11017: Increase maximum power draw * Rename migration file for clarity --------- Co-authored-by: jeremystretch --- .../0172_larger_power_draw_values.py | 42 +++++++++++++++++++ .../dcim/models/device_component_templates.py | 4 +- netbox/dcim/models/device_components.py | 4 +- 3 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 netbox/dcim/migrations/0172_larger_power_draw_values.py diff --git a/netbox/dcim/migrations/0172_larger_power_draw_values.py b/netbox/dcim/migrations/0172_larger_power_draw_values.py new file mode 100644 index 000000000..729daf836 --- /dev/null +++ b/netbox/dcim/migrations/0172_larger_power_draw_values.py @@ -0,0 +1,42 @@ +# Generated by Django 4.1.9 on 2023-05-12 18:46 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0171_cabletermination_change_logging'), + ] + + operations = [ + migrations.AlterField( + model_name='powerport', + name='allocated_draw', + field=models.PositiveIntegerField( + blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)] + ), + ), + migrations.AlterField( + model_name='powerport', + name='maximum_draw', + field=models.PositiveIntegerField( + blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)] + ), + ), + migrations.AlterField( + model_name='powerporttemplate', + name='allocated_draw', + field=models.PositiveIntegerField( + blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)] + ), + ), + migrations.AlterField( + model_name='powerporttemplate', + name='maximum_draw', + field=models.PositiveIntegerField( + blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)] + ), + ), + ] diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index b0dc1677f..6a89655b2 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -232,13 +232,13 @@ class PowerPortTemplate(ModularComponentTemplateModel): choices=PowerPortTypeChoices, blank=True ) - maximum_draw = models.PositiveSmallIntegerField( + maximum_draw = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], help_text=_("Maximum power draw (watts)") ) - allocated_draw = models.PositiveSmallIntegerField( + allocated_draw = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 645fc5c5c..9f6837b92 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -329,13 +329,13 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint): blank=True, help_text=_('Physical port type') ) - maximum_draw = models.PositiveSmallIntegerField( + maximum_draw = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], help_text=_("Maximum power draw (watts)") ) - allocated_draw = models.PositiveSmallIntegerField( + allocated_draw = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], From 0f44f7eb20ebf10dd0418d42c57c6511986a262b Mon Sep 17 00:00:00 2001 From: Devon Mar Date: Sun, 14 May 2023 01:08:25 -0700 Subject: [PATCH 28/43] Use .font-monospace instead of .text-monospace --- netbox/templates/dcim/interface.html | 4 ++-- netbox/templates/virtualization/vminterface.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index db0fd7dfd..11f262eeb 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -123,11 +123,11 @@ - + - + diff --git a/netbox/templates/virtualization/vminterface.html b/netbox/templates/virtualization/vminterface.html index a7d4d92ba..82071bdaa 100644 --- a/netbox/templates/virtualization/vminterface.html +++ b/netbox/templates/virtualization/vminterface.html @@ -59,7 +59,7 @@ - + From c65b2a080f693d858078faabfbdfb3b1967cfa2a Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 15 May 2023 09:13:11 -0400 Subject: [PATCH 29/43] Changelog for #11017, #12468 --- docs/release-notes/version-3.5.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 39e159268..b101a7912 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -5,6 +5,7 @@ ### Enhancements * [#7671](https://github.com/netbox-community/netbox/issues/7671) - Introduce `REMOTE_AUTH_AUTO_CREATE_GROUPS` config parameter to enable the automatic creation of new groups when remote authentication is in use +* [#11017](https://github.com/netbox-community/netbox/issues/11017) - Increase the maximum values for allocated and maximum power draws * [#11233](https://github.com/netbox-community/netbox/issues/11233) - Intercept and cleanly report errors upon attempted database writes when maintenance mode is enabled * [#11599](https://github.com/netbox-community/netbox/issues/11599) - Move contacts panels to separate tabs under object views * [#11670](https://github.com/netbox-community/netbox/issues/11670) - Enable setting device type & module type weight via bulk import @@ -20,6 +21,7 @@ ### Bug Fixes * [#10686](https://github.com/netbox-community/netbox/issues/10686) - Enable specifying termination object by virtual chassis master when importing cables +* [#12468](https://github.com/netbox-community/netbox/issues/12468) - Custom field names should not permit double underscores * [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form * [#12570](https://github.com/netbox-community/netbox/issues/12570) - Disable ordering of synchronized object tables by the "synced" attribute From 0ad88e24312c256ef0a3189c71009d5b8d97cdf5 Mon Sep 17 00:00:00 2001 From: kkthxbye <400797+kkthxbye-code@users.noreply.github.com> Date: Tue, 16 May 2023 08:19:57 +0200 Subject: [PATCH 30/43] Changed docs to reflect the new URL for the dynamic API documentation --- docs/integrations/rest-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/rest-api.md b/docs/integrations/rest-api.md index af2f86e4c..cf3d11126 100644 --- a/docs/integrations/rest-api.md +++ b/docs/integrations/rest-api.md @@ -63,7 +63,7 @@ Each attribute of the IP address is expressed as an attribute of the JSON object ## Interactive Documentation -Comprehensive, interactive documentation of all REST API endpoints is available on a running NetBox instance at `/api/docs/`. This interface provides a convenient sandbox for researching and experimenting with specific endpoints and request types. The API itself can also be explored using a web browser by navigating to its root at `/api/`. +Comprehensive, interactive documentation of all REST API endpoints is available on a running NetBox instance at `/api/schema/swagger-ui/`. This interface provides a convenient sandbox for researching and experimenting with specific endpoints and request types. The API itself can also be explored using a web browser by navigating to its root at `/api/`. ## Endpoint Hierarchy From d5be59ef67c4a48698543ee9fe0c3d53254578bd Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 16 May 2023 08:39:05 -0400 Subject: [PATCH 31/43] Update README --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 480f0f856..81217797f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@
+ The :ballot_box_with_check: 2023 NetBox Community Survey is now open! +

Please take a few minutes to tell us about your NetBox deployment.

+ NetBox logo - - The premiere source of truth powering network automation +

The premiere source of truth powering network automation

+ CI status +

-![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, From eeb15ab5d1531b6502734c5592755f498d2bcf7c Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 15 May 2023 14:22:45 -0700 Subject: [PATCH 32/43] 12594 add config context to object count / list widget --- netbox/extras/dashboard/widgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index 69d1cc36d..dc68e1388 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -35,7 +35,8 @@ def get_content_type_labels(): return [ (content_type_identifier(ct), content_type_name(ct)) for ct in ContentType.objects.filter( - FeatureQuery('export_templates').get_query() | Q(app_label='extras', model='objectchange') + FeatureQuery('export_templates').get_query() | Q(app_label='extras', model='objectchange') | + Q(app_label='extras', model='configcontext') ).order_by('app_label', 'model') ] From 0df6a5793aefd64172d4e62f02e9d592dc715330 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 16 May 2023 08:35:21 -0700 Subject: [PATCH 33/43] Adds maintenance exempt paths (#12592) * adds maintenance exempt paths #11233 * adds maintenance exempt paths #11233 * Rename method & remove login/logout from exempt paths --------- Co-authored-by: jeremystretch --- netbox/netbox/middleware.py | 14 +++++++++----- netbox/netbox/settings.py | 5 +++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/netbox/netbox/middleware.py b/netbox/netbox/middleware.py index 461c018b9..76b3e42a8 100644 --- a/netbox/netbox/middleware.py +++ b/netbox/netbox/middleware.py @@ -181,19 +181,23 @@ class MaintenanceModeMiddleware: def __call__(self, request): if get_config().MAINTENANCE_MODE: - self._prevent_db_write_operations() + self._set_session_type( + allow_write=request.path_info.startswith(settings.MAINTENANCE_EXEMPT_PATHS) + ) return self.get_response(request) @staticmethod - def _prevent_db_write_operations(): + def _set_session_type(allow_write): """ Prevent any write-related database operations. + + Args: + allow_write (bool): If True, write operations will be permitted. """ with connection.cursor() as cursor: - cursor.execute( - 'SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;' - ) + mode = 'READ WRITE' if allow_write else 'READ ONLY' + cursor.execute(f'SET SESSION CHARACTERISTICS AS TRANSACTION {mode};') def process_exception(self, request, exception): """ diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 575755d2b..4d2dd8a81 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -478,6 +478,11 @@ AUTH_EXEMPT_PATHS = ( f'/{BASE_PATH}metrics', ) +# All URLs starting with a string listed here are exempt from maintenance mode enforcement +MAINTENANCE_EXEMPT_PATHS = ( + f'/{BASE_PATH}admin/', +) + SERIALIZATION_MODULES = { 'json': 'utilities.serializers.json', } From 2204735e9fbe93b1c77ba047a85845b9fdaffc41 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Tue, 16 May 2023 11:10:44 -0700 Subject: [PATCH 34/43] Adds rq retry options (#12588) * adds rq retry options #12327 * Clean up docs; disable retries of failed jobs by default * Pass a Retry object only if RQ_RETRY_MAX is non-zero --------- Co-authored-by: jeremystretch --- docs/configuration/miscellaneous.md | 22 ++++++++++++++++++++++ netbox/core/models/jobs.py | 5 +++-- netbox/extras/webhooks.py | 4 +++- netbox/netbox/settings.py | 2 ++ netbox/utilities/rqworker.py | 14 +++++++++++++- 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index e3728acab..fd410a9d4 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -204,3 +204,25 @@ This parameter defines the URL of the repository that will be checked for new Ne Default: `300` The maximum execution time of a background task (such as running a custom script), in seconds. + +--- + +## RQ_RETRY_INTERVAL + +!!! note + This parameter was added in NetBox v3.5. + +Default: `60` + +This parameter controls how frequently a failed job is retried, up to the maximum number of times specified by `RQ_RETRY_MAX`. This must be either an integer specifying the number of seconds to wait between successive attempts, or a list of such values. For example, `[60, 300, 3600]` will retry the task after 1 minute, 5 minutes, and 1 hour. + +--- + +## RQ_RETRY_MAX + +!!! note + This parameter was added in NetBox v3.5. + +Default: `0` (retries disabled) + +The maximum number of times a background task will be retried before being marked as failed. diff --git a/netbox/core/models/jobs.py b/netbox/core/models/jobs.py index 1d0eecd21..a91e75e61 100644 --- a/netbox/core/models/jobs.py +++ b/netbox/core/models/jobs.py @@ -16,7 +16,7 @@ from extras.utils import FeatureQuery from netbox.config import get_config from netbox.constants import RQ_QUEUE_DEFAULT from utilities.querysets import RestrictedQuerySet -from utilities.rqworker import get_queue_for_model +from utilities.rqworker import get_queue_for_model, get_rq_retry __all__ = ( 'Job', @@ -219,5 +219,6 @@ class Job(models.Model): event=event, data=self.data, timestamp=str(timezone.now()), - username=self.user.username + username=self.user.username, + retry=get_rq_retry() ) diff --git a/netbox/extras/webhooks.py b/netbox/extras/webhooks.py index 23702949a..1fc869ee8 100644 --- a/netbox/extras/webhooks.py +++ b/netbox/extras/webhooks.py @@ -9,6 +9,7 @@ from netbox.config import get_config from netbox.constants import RQ_QUEUE_DEFAULT from netbox.registry import registry from utilities.api import get_serializer_for_model +from utilities.rqworker import get_rq_retry from utilities.utils import serialize_object from .choices import * from .models import Webhook @@ -116,5 +117,6 @@ def flush_webhooks(queue): snapshots=data['snapshots'], timestamp=str(timezone.now()), username=data['username'], - request_id=data['request_id'] + request_id=data['request_id'], + retry=get_rq_retry() ) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 4d2dd8a81..d6d5c84ca 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -140,6 +140,8 @@ REMOTE_AUTH_STAFF_USERS = getattr(configuration, 'REMOTE_AUTH_STAFF_USERS', []) REMOTE_AUTH_GROUP_SEPARATOR = getattr(configuration, 'REMOTE_AUTH_GROUP_SEPARATOR', '|') REPORTS_ROOT = getattr(configuration, 'REPORTS_ROOT', os.path.join(BASE_DIR, 'reports')).rstrip('/') RQ_DEFAULT_TIMEOUT = getattr(configuration, 'RQ_DEFAULT_TIMEOUT', 300) +RQ_RETRY_INTERVAL = getattr(configuration, 'RQ_RETRY_INTERVAL', 60) +RQ_RETRY_MAX = getattr(configuration, 'RQ_RETRY_MAX', 0) SCRIPTS_ROOT = getattr(configuration, 'SCRIPTS_ROOT', os.path.join(BASE_DIR, 'scripts')).rstrip('/') SEARCH_BACKEND = getattr(configuration, 'SEARCH_BACKEND', 'netbox.search.backends.CachedValueSearchBackend') SECURE_SSL_REDIRECT = getattr(configuration, 'SECURE_SSL_REDIRECT', False) diff --git a/netbox/utilities/rqworker.py b/netbox/utilities/rqworker.py index 5866dfee0..61f594767 100644 --- a/netbox/utilities/rqworker.py +++ b/netbox/utilities/rqworker.py @@ -1,11 +1,12 @@ from django_rq.queues import get_connection -from rq import Worker +from rq import Retry, Worker from netbox.config import get_config from netbox.constants import RQ_QUEUE_DEFAULT __all__ = ( 'get_queue_for_model', + 'get_rq_retry', 'get_workers_for_queue', ) @@ -22,3 +23,14 @@ def get_workers_for_queue(queue_name): Returns True if a worker process is currently servicing the specified queue. """ return Worker.count(get_connection(queue_name)) + + +def get_rq_retry(): + """ + If RQ_RETRY_MAX is defined and greater than zero, instantiate and return a Retry object to be + used when queuing a job. Otherwise, return None. + """ + retry_max = get_config().RQ_RETRY_MAX + retry_interval = get_config().RQ_RETRY_INTERVAL + if retry_max: + return Retry(max=retry_max, interval=retry_interval) From 92c49669f96d8f6427f7ec4e4ea83712362f5680 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 18 May 2023 10:40:41 -0700 Subject: [PATCH 35/43] 12548 add prefetch_related for l2vpn and vdcs to interface api --- netbox/dcim/api/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 0d9fcdc5f..a62315b57 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -493,7 +493,8 @@ class PowerOutletViewSet(PathEndpointMixin, NetBoxModelViewSet): class InterfaceViewSet(PathEndpointMixin, NetBoxModelViewSet): queryset = Interface.objects.prefetch_related( 'device', 'module__module_bay', 'parent', 'bridge', 'lag', '_path', 'cable__terminations', 'wireless_lans', - 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', 'fhrp_group_assignments', 'tags' + 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', 'fhrp_group_assignments', 'tags', 'l2vpn_terminations', + 'vdcs', ) serializer_class = serializers.InterfaceSerializer filterset_class = filtersets.InterfaceFilterSet From 23b21246f078989b6cc8a51eaac54efde001700a Mon Sep 17 00:00:00 2001 From: neope <58367950+apellini@users.noreply.github.com> Date: Thu, 18 May 2023 20:21:32 +0200 Subject: [PATCH 36/43] Adding CDFP and CFP8 400GE connectors (#12646) * Adding CDFP and CFP8 400GE connectors * Update choices.py typo on CFP8 --- netbox/dcim/choices.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index d32f5aaee..4586f2111 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -814,6 +814,8 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_200GE_QSFP56 = '200gbase-x-qsfp56' TYPE_400GE_QSFP_DD = '400gbase-x-qsfpdd' TYPE_400GE_OSFP = '400gbase-x-osfp' + TYPE_400GE_CDFP = '400gbase-x-cdfp' + TYPE_400GE_CFP8 = '400gbase-x-cfp8' TYPE_800GE_QSFP_DD = '800gbase-x-qsfpdd' TYPE_800GE_OSFP = '800gbase-x-osfp' @@ -959,6 +961,8 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_200GE_QSFP56, 'QSFP56 (200GE)'), (TYPE_400GE_QSFP_DD, 'QSFP-DD (400GE)'), (TYPE_400GE_OSFP, 'OSFP (400GE)'), + (TYPE_400GE_CDFP, 'CDFP (400GE)'), + (TYPE_400GE_CFP8, 'CPF8 (400GE)'), (TYPE_800GE_QSFP_DD, 'QSFP-DD (800GE)'), (TYPE_800GE_OSFP, 'OSFP (800GE)'), ) From 311dce0b5f2adaf124f5a8d4a3fb2ccdf0b46f9d Mon Sep 17 00:00:00 2001 From: Austin de Coup-Crank Date: Mon, 15 May 2023 10:20:35 -0500 Subject: [PATCH 37/43] Closes #12605: Add LX.5 port type --- netbox/dcim/choices.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 4586f2111..c256761c7 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1227,6 +1227,10 @@ class PortTypeChoices(ChoiceSet): TYPE_LSH_PC = 'lsh-pc' TYPE_LSH_UPC = 'lsh-upc' TYPE_LSH_APC = 'lsh-apc' + TYPE_LX5 = 'lx5' + TYPE_LX5_PC = 'lx5-pc' + TYPE_LX5_UPC = 'lx5-upc' + TYPE_LX5_APC = 'lx5-apc' TYPE_SPLICE = 'splice' TYPE_CS = 'cs' TYPE_SN = 'sn' @@ -1273,6 +1277,10 @@ class PortTypeChoices(ChoiceSet): (TYPE_LSH_PC, 'LSH/PC'), (TYPE_LSH_UPC, 'LSH/UPC'), (TYPE_LSH_APC, 'LSH/APC'), + (TYPE_LX5, 'LX.5'), + (TYPE_LX5_PC, 'LX.5/PC'), + (TYPE_LX5_UPC, 'LX.5/UPC'), + (TYPE_LX5_APC, 'LX.5/APC'), (TYPE_MPO, 'MPO'), (TYPE_MTRJ, 'MTRJ'), (TYPE_SC, 'SC'), From c8d9a3b4eb58221b3806113718e50b29dcfe947c Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 18 May 2023 14:34:13 -0400 Subject: [PATCH 38/43] Changelog for #12327, #12548, #12594, #12605, #12629 --- docs/release-notes/version-3.5.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index b101a7912..4a77934b5 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -15,8 +15,12 @@ * [#12233](https://github.com/netbox-community/netbox/issues/12233) - Move related IP addresses table to a separate tab * [#12286](https://github.com/netbox-community/netbox/issues/12286) - Show height and total weight under device view * [#12323](https://github.com/netbox-community/netbox/issues/12323) - Add 100GE CXP interface type +* [#12327](https://github.com/netbox-community/netbox/issues/12327) - Introduce the ability to automatically retry failed background jobs * [#12498](https://github.com/netbox-community/netbox/issues/12498) - Hide map button if `MAPS_URL` is empty +* [#12548](https://github.com/netbox-community/netbox/issues/12548) - Optimize REST API performance when retrieving interfaces with L2VPN assignments * [#12554](https://github.com/netbox-community/netbox/issues/12554) - Allow customization or disabling of the maintenance mode banner +* [#12605](https://github.com/netbox-community/netbox/issues/12605) - Add LX.5 port types +* [#12629](https://github.com/netbox-community/netbox/issues/12629) - Add 400GE CDFP and CFP8 interface types ### Bug Fixes @@ -24,6 +28,7 @@ * [#12468](https://github.com/netbox-community/netbox/issues/12468) - Custom field names should not permit double underscores * [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form * [#12570](https://github.com/netbox-community/netbox/issues/12570) - Disable ordering of synchronized object tables by the "synced" attribute +* [#12594](https://github.com/netbox-community/netbox/issues/12594) - Enable selecting config context as object type in object counts dashboard widget --- From fa3bedb947f6cb166bf8f1a6b6d0e57a875006c1 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 22 May 2023 13:07:40 -0400 Subject: [PATCH 39/43] Fixes #12642: Fix bulk tenant assignment via cluster import form --- docs/release-notes/version-3.5.md | 1 + netbox/virtualization/forms/bulk_import.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 4a77934b5..79d9d12b7 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -29,6 +29,7 @@ * [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form * [#12570](https://github.com/netbox-community/netbox/issues/12570) - Disable ordering of synchronized object tables by the "synced" attribute * [#12594](https://github.com/netbox-community/netbox/issues/12594) - Enable selecting config context as object type in object counts dashboard widget +* [#12642](https://github.com/netbox-community/netbox/issues/12642) - Fix bulk tenant assignment via cluster import form --- diff --git a/netbox/virtualization/forms/bulk_import.py b/netbox/virtualization/forms/bulk_import.py index a229bd935..15651f2ae 100644 --- a/netbox/virtualization/forms/bulk_import.py +++ b/netbox/virtualization/forms/bulk_import.py @@ -65,7 +65,7 @@ class ClusterImportForm(NetBoxModelImportForm): class Meta: model = Cluster - fields = ('name', 'type', 'group', 'status', 'site', 'description', 'comments', 'tags') + fields = ('name', 'type', 'group', 'status', 'site', 'tenant', 'description', 'comments', 'tags') class VirtualMachineImportForm(NetBoxModelImportForm): From 80fc8db514486448f406e1f695b18ac255f54997 Mon Sep 17 00:00:00 2001 From: "Daniel W. Anner" Date: Mon, 22 May 2023 14:22:33 +0000 Subject: [PATCH 40/43] Adding interface type `200gbase-x-qsfpdd` --- netbox/dcim/choices.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index c256761c7..cc388b750 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -812,6 +812,7 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_100GE_QSFP28 = '100gbase-x-qsfp28' TYPE_200GE_CFP2 = '200gbase-x-cfp2' TYPE_200GE_QSFP56 = '200gbase-x-qsfp56' + TYPE_200GE_QSFP_DD = '200gbase-x-qsfpdd' TYPE_400GE_QSFP_DD = '400gbase-x-qsfpdd' TYPE_400GE_OSFP = '400gbase-x-osfp' TYPE_400GE_CDFP = '400gbase-x-cdfp' @@ -959,6 +960,7 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_100GE_CPAK, 'Cisco CPAK (100GE)'), (TYPE_100GE_QSFP28, 'QSFP28 (100GE)'), (TYPE_200GE_QSFP56, 'QSFP56 (200GE)'), + (TYPE_200GE_QSFP_DD, 'QSFP-DD (200GE)'), (TYPE_400GE_QSFP_DD, 'QSFP-DD (400GE)'), (TYPE_400GE_OSFP, 'OSFP (400GE)'), (TYPE_400GE_CDFP, 'CDFP (400GE)'), From 078893e0341f433ec975eb595acedfaf1c537955 Mon Sep 17 00:00:00 2001 From: Dillon Henschen Date: Mon, 22 May 2023 15:37:31 -0400 Subject: [PATCH 41/43] Closes #11619: Include VLANs with a null site in query during bulk interface edit for Devices > DEVICE COMPONENTS > Interfaces (#12659) * Closes #11619: Allow VLANs without a site during multi-port edits This commit allows users to be able to select VLANs without a site assignment during bulk interfaces edits under Devices > DEVICE COMPONENTS > Interfaces. Prior to this commit, only VLANs that were assigned the same site as the device were available for selection. * Replace 'null' with FILTERS_NULL_CHOICE_VALUE constant --------- Co-authored-by: jeremystretch --- netbox/dcim/forms/bulk_edit.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 6ed483c79..bc9693afb 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -1,4 +1,5 @@ from django import forms +from django.conf import settings from django.contrib.auth.models import User from django.utils.translation import gettext as _ from timezone_field import TimeZoneFormField @@ -1292,8 +1293,13 @@ class InterfaceBulkEditForm( break if site is not None: - self.fields['untagged_vlan'].widget.add_query_param('site_id', site.pk) - self.fields['tagged_vlans'].widget.add_query_param('site_id', site.pk) + # Query for VLANs assigned to the same site and VLANs with no site assigned (null). + self.fields['untagged_vlan'].widget.add_query_param( + 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE] + ) + self.fields['tagged_vlans'].widget.add_query_param( + 'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE] + ) self.fields['parent'].choices = () self.fields['parent'].widget.attrs['disabled'] = True From 005e3fd6926aa12ebd8e5c22ce6c1538bea19ad0 Mon Sep 17 00:00:00 2001 From: Austin de Coup-Crank <94914780+decoupca@users.noreply.github.com> Date: Mon, 22 May 2023 15:16:17 -0500 Subject: [PATCH 42/43] Closes #9068: validate addresses assigned to interfaces (#12618) * Begin logic * Closes #9068: Disallow assigning bcast/networks to interfaces * Allow net IDs in /31, /32, /127, /128 * linting error * Implement requested changes * Condensed the "if" logic a bit --------- Co-authored-by: Austin de Coup-Crank Co-authored-by: jeremystretch --- netbox/ipam/forms/model_forms.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index cf8117bf7..ac75e2cc3 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -351,6 +351,18 @@ class IPAddressForm(TenancyForm, NetBoxModelForm): 'primary_for_parent', "Only IP addresses assigned to an interface can be designated as primary IPs." ) + # Do not allow assigning a network ID or broadcast address to an interface. + if interface and (address := self.cleaned_data.get('address')): + if address.ip == address.network: + msg = f"{address} is a network ID, which may not be assigned to an interface." + if address.version == 4 and address.prefixlen not in (31, 32): + raise ValidationError(msg) + if address.version == 6 and address.prefixlen not in (127, 128): + raise ValidationError(msg) + if address.ip == address.broadcast: + msg = f"{address} is a broadcast address, which may not be assigned to an interface." + raise ValidationError(msg) + def save(self, *args, **kwargs): ipaddress = super().save(*args, **kwargs) From fbc7811f56040aee5d7dd0f6be568665172d5ba2 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 22 May 2023 16:24:30 -0400 Subject: [PATCH 43/43] Release v3.5.2 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- docs/release-notes/version-3.5.md | 5 ++++- netbox/netbox/settings.py | 2 +- requirements.txt | 14 +++++++------- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index be2aacff5..1a0e68929 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.5.1 + placeholder: v3.5.2 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index fcb3516b4..526057454 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.5.1 + placeholder: v3.5.2 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.5.md b/docs/release-notes/version-3.5.md index 79d9d12b7..24d1ce2dc 100644 --- a/docs/release-notes/version-3.5.md +++ b/docs/release-notes/version-3.5.md @@ -1,10 +1,11 @@ # NetBox v3.5 -## v3.5.2 (FUTURE) +## v3.5.2 (2023-05-22) ### Enhancements * [#7671](https://github.com/netbox-community/netbox/issues/7671) - Introduce `REMOTE_AUTH_AUTO_CREATE_GROUPS` config parameter to enable the automatic creation of new groups when remote authentication is in use +* [#9068](https://github.com/netbox-community/netbox/issues/9068) - Disallow the assignment of network/broadcast IP addresses to interfaces * [#11017](https://github.com/netbox-community/netbox/issues/11017) - Increase the maximum values for allocated and maximum power draws * [#11233](https://github.com/netbox-community/netbox/issues/11233) - Intercept and cleanly report errors upon attempted database writes when maintenance mode is enabled * [#11599](https://github.com/netbox-community/netbox/issues/11599) - Move contacts panels to separate tabs under object views @@ -21,10 +22,12 @@ * [#12554](https://github.com/netbox-community/netbox/issues/12554) - Allow customization or disabling of the maintenance mode banner * [#12605](https://github.com/netbox-community/netbox/issues/12605) - Add LX.5 port types * [#12629](https://github.com/netbox-community/netbox/issues/12629) - Add 400GE CDFP and CFP8 interface types +* [#12678](https://github.com/netbox-community/netbox/issues/12678) - Add 200GE QSFP-DD interface type ### Bug Fixes * [#10686](https://github.com/netbox-community/netbox/issues/10686) - Enable specifying termination object by virtual chassis master when importing cables +* [#11619](https://github.com/netbox-community/netbox/issues/11619) - Enable assigning VLANs without a site to interfaces during bulk edit * [#12468](https://github.com/netbox-community/netbox/issues/12468) - Custom field names should not permit double underscores * [#12550](https://github.com/netbox-community/netbox/issues/12550) - Fix rear port selection widget under front port creation form * [#12570](https://github.com/netbox-community/netbox/issues/12570) - Disable ordering of synchronized object tables by the "synced" attribute diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index d6d5c84ca..a5923c9f0 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW # Environment setup # -VERSION = '3.5.2-dev' +VERSION = '3.5.2' # Hostname HOSTNAME = platform.node() diff --git a/requirements.txt b/requirements.txt index c3d9c8c38..6757f3260 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ bleach==6.0.0 -boto3==1.26.127 +boto3==1.26.138 Django==4.1.9 -django-cors-headers==3.14.0 -django-debug-toolbar==4.0.0 +django-cors-headers==4.0.0 +django-debug-toolbar==4.1.0 django-filter==23.2 django-graphiql-debug-toolbar==0.2.0 django-mptt==0.14 @@ -10,7 +10,7 @@ django-pglocks==1.0.4 django-prometheus==2.3.1 django-redis==5.2.0 django-rich==1.5.0 -django-rq==2.8.0 +django-rq==2.8.1 django-tables2==2.5.3 django-taggit==4.0.0 django-timezone-field==5.0 @@ -19,17 +19,17 @@ drf-spectacular==0.26.2 drf-spectacular-sidecar==2023.5.1 dulwich==0.21.5 feedparser==6.0.10 -graphene-django==3.0.0 +graphene-django==3.0.2 gunicorn==20.1.0 Jinja2==3.1.2 Markdown==3.3.7 -mkdocs-material==9.1.9 +mkdocs-material==9.1.14 mkdocstrings[python-legacy]==0.21.2 netaddr==0.8.0 Pillow==9.5.0 psycopg2-binary==2.9.6 PyYAML==6.0 -sentry-sdk==1.22.1 +sentry-sdk==1.23.1 social-auth-app-django==5.2.0 social-auth-core[openidconnect]==4.4.2 svgwrite==1.4.3
MAC Address{{ object.mac_address|placeholder }}{{ object.mac_address|placeholder }}
WWN{{ object.wwn|placeholder }}{{ object.wwn|placeholder }}
VRF
MAC Address{{ object.mac_address|placeholder }}{{ object.mac_address|placeholder }}
802.1Q Mode