From 7efbfabc0bd7e8a5c5878ec60d4d14aefb6c38a0 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 17 Oct 2023 13:07:29 -0400 Subject: [PATCH 01/30] PRVB --- docs/release-notes/version-3.6.md | 4 ++++ netbox/netbox/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index a03dc548fbe..8f827b50e05 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -1,5 +1,9 @@ # NetBox v3.6 +## v3.6.5 (FUTURE) + +--- + ## v3.6.4 (2023-10-17) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index a5bbad85e20..111781b8a3b 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ # Environment setup # -VERSION = '3.6.4' +VERSION = '3.6.5-dev' # Hostname HOSTNAME = platform.node() From 2a0a7d45aaf9e0fbd2f3c3980c96a2396fbab032 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 18 Oct 2023 11:24:14 -0400 Subject: [PATCH 02/30] Add GitHub issue template for translations --- .github/ISSUE_TEMPLATE/translation.yaml | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/translation.yaml diff --git a/.github/ISSUE_TEMPLATE/translation.yaml b/.github/ISSUE_TEMPLATE/translation.yaml new file mode 100644 index 00000000000..001b6ac53bc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/translation.yaml @@ -0,0 +1,37 @@ +--- +name: 🌍 Translation +description: Request support for a new language in the user interface +labels: ["type: translation"] +body: + - type: markdown + attributes: + value: > + **NOTE:** This template is used only for proposing the addition of *new* languages. Please do + not use it to request changes to existing translations. + - type: input + attributes: + label: Language + description: What is the name of the language in English? + validations: + required: true + - type: input + attributes: + label: ISO 639-1 code + description: > + What is the two-letter [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) + assigned to the language? + validations: + required: true + - type: dropdown + attributes: + label: Volunteer + description: Are you a native speaker of this language and willing to contribute a translation map? + options: + - Yes + - No + validations: + required: true + - type: textarea + attributes: + label: Comments + description: Any other notes you would like to share From 809b04959085c8d6cdb757c0a0255e17a73e7723 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 18 Oct 2023 11:29:31 -0400 Subject: [PATCH 03/30] YAML fix --- .github/ISSUE_TEMPLATE/translation.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/translation.yaml b/.github/ISSUE_TEMPLATE/translation.yaml index 001b6ac53bc..d53db04fd26 100644 --- a/.github/ISSUE_TEMPLATE/translation.yaml +++ b/.github/ISSUE_TEMPLATE/translation.yaml @@ -27,8 +27,8 @@ body: label: Volunteer description: Are you a native speaker of this language and willing to contribute a translation map? options: - - Yes - - No + - "Yes" + - "No" validations: required: true - type: textarea From 5b2f29480a42e9c815ca1f50403c115cc89b6538 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 18 Oct 2023 11:57:21 -0400 Subject: [PATCH 04/30] Tweak translation issue form --- .github/ISSUE_TEMPLATE/translation.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/translation.yaml b/.github/ISSUE_TEMPLATE/translation.yaml index d53db04fd26..d07bc399d4b 100644 --- a/.github/ISSUE_TEMPLATE/translation.yaml +++ b/.github/ISSUE_TEMPLATE/translation.yaml @@ -25,7 +25,7 @@ body: - type: dropdown attributes: label: Volunteer - description: Are you a native speaker of this language and willing to contribute a translation map? + description: Are you a fluent speaker of this language **and** willing to contribute a translation map? options: - "Yes" - "No" From b3fb39349014998e499c4b5946a69260d46b0713 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Wed, 1 Nov 2023 13:30:10 -0700 Subject: [PATCH 05/30] 14033 raise validation error if A and B term go to same object (#14050) * 14033 raise validation error if A and B term go to same object * 14033 move check to cable model clean * 14033 fix tests --- netbox/dcim/models/cables.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index 751bca2719e..f240659dd33 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -180,6 +180,17 @@ def clean(self): if b_type not in COMPATIBLE_TERMINATION_TYPES.get(a_type): raise ValidationError(f"Incompatible termination types: {a_type} and {b_type}") + if a_type == b_type: + # can't directly use self.a_terminations here as possible they + # don't have pk yet + a_pks = set(obj.pk for obj in self.a_terminations if obj.pk) + b_pks = set(obj.pk for obj in self.b_terminations if obj.pk) + + if (a_pks & b_pks): + raise ValidationError( + _("A and B terminations cannot connect to the same object.") + ) + # Run clean() on any new CableTerminations for termination in self.a_terminations: CableTermination(cable=self, cable_end='A', termination=termination).clean() From 22e474ff96ce9b8659455ad48ee96e766bbf7025 Mon Sep 17 00:00:00 2001 From: Kenny Y <24802984+kenny-y-dev@users.noreply.github.com> Date: Wed, 1 Nov 2023 16:52:19 -0400 Subject: [PATCH 06/30] Update attr in conditions example --- docs/reference/conditions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/conditions.md b/docs/reference/conditions.md index 514006b0174..fc571c05e5a 100644 --- a/docs/reference/conditions.md +++ b/docs/reference/conditions.md @@ -116,7 +116,7 @@ Multiple conditions can be combined into nested sets using AND or OR logic. This ] }, { - "attr": "tags", + "attr": "tags.slug", "value": "exempt", "op": "contains" } From 66b9cdf1411270f9c9aa8b7d9a8d3cc530231899 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Wed, 8 Nov 2023 18:25:32 +0530 Subject: [PATCH 07/30] adds import button on the contact assignment table #13669 --- netbox/tenancy/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 76a86146c5a..d0a8c2b89d8 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -386,7 +386,7 @@ class ContactAssignmentListView(generic.ObjectListView): filterset = filtersets.ContactAssignmentFilterSet filterset_form = forms.ContactAssignmentFilterForm table = tables.ContactAssignmentTable - actions = ('export', 'bulk_edit', 'bulk_delete') + actions = ('export', 'bulk_edit', 'bulk_delete', 'import') @register_model_view(ContactAssignment, 'edit') From 60e98324c3fcfcb082489dce24bee4419c474caa Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Wed, 8 Nov 2023 21:36:11 +0530 Subject: [PATCH 08/30] adds inventory items to interface #13723 --- netbox/dcim/tables/devices.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 624eb579b13..32c0f58c446 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -625,6 +625,10 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi verbose_name=_('VRF'), linkify=True ) + inventory_items = tables.ManyToManyColumn( + linkify_item=True, + verbose_name=_('Inventory Items'), + ) tags = columns.TagColumn( url_name='dcim:interface_list' ) @@ -636,7 +640,7 @@ class Meta(DeviceComponentTable.Meta): 'speed', 'speed_formatted', 'duplex', 'mode', 'mac_address', 'wwn', 'poe_mode', 'poe_type', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description', 'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection', 'tags', 'vdcs', 'vrf', 'l2vpn', - 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'created', 'last_updated', + 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'inventory_items', 'created', 'last_updated', ) default_columns = ('pk', 'name', 'device', 'label', 'enabled', 'type', 'description') From b0f2de5bd7154e3ab2e403167e669a5ee84613d5 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Wed, 8 Nov 2023 23:18:39 +0530 Subject: [PATCH 09/30] order available columns #14219 --- netbox/netbox/tables/tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py index 52ff69aa96b..97ab4436231 100644 --- a/netbox/netbox/tables/tables.py +++ b/netbox/netbox/tables/tables.py @@ -119,7 +119,7 @@ def name(self): @property def available_columns(self): - return self._get_columns(visible=False) + return sorted(self._get_columns(visible=False)) @property def selected_columns(self): From 94858ac13f6aa1983d10269516af4cd74f0be11e Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Wed, 8 Nov 2023 22:42:58 +0530 Subject: [PATCH 10/30] adds parent to inventory item table #14113 --- netbox/dcim/tables/devices.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 32c0f58c446..bbf1f5d9630 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -937,6 +937,9 @@ class InventoryItemTable(DeviceComponentTable): discovered = columns.BooleanColumn( verbose_name=_('Discovered'), ) + parent = tables.LinkColumn( + verbose_name=_('Parent'), + ) tags = columns.TagColumn( url_name='dcim:inventoryitem_list' ) @@ -945,7 +948,7 @@ class InventoryItemTable(DeviceComponentTable): class Meta(NetBoxTable.Meta): model = models.InventoryItem fields = ( - 'pk', 'id', 'name', 'device', 'component', 'label', 'role', 'manufacturer', 'part_id', 'serial', + 'pk', 'id', 'name', 'device', 'parent', 'component', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'discovered', 'tags', 'created', 'last_updated', ) default_columns = ( From 6e8ee9db894e07154b43f7e19eb817fc93765212 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 9 Nov 2023 02:56:36 +0530 Subject: [PATCH 11/30] review changes #14113 --- netbox/dcim/tables/devices.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index bbf1f5d9630..bf5f2d0502d 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -937,7 +937,8 @@ class InventoryItemTable(DeviceComponentTable): discovered = columns.BooleanColumn( verbose_name=_('Discovered'), ) - parent = tables.LinkColumn( + parent = tables.Column( + linkify=True, verbose_name=_('Parent'), ) tags = columns.TagColumn( From ac4b46b5027efe2846872211ebce20fc83ff1d06 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Wed, 8 Nov 2023 22:35:00 +0530 Subject: [PATCH 12/30] adds site column to power feeds #13743 --- netbox/dcim/tables/power.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/tables/power.py b/netbox/dcim/tables/power.py index e4735bd5797..04aa9a2d6de 100644 --- a/netbox/dcim/tables/power.py +++ b/netbox/dcim/tables/power.py @@ -87,6 +87,10 @@ class PowerFeedTable(TenancyColumnsMixin, CableTerminationTable): linkify=True, verbose_name=_('Tenant') ) + site = tables.TemplateColumn( + template_code='''{% if record.rack %}{{ record.rack.site|linkify }}{% else %}—{% endif %}''', + verbose_name=_('Site'), + ) comments = columns.MarkdownColumn( verbose_name=_('Comments'), ) @@ -97,9 +101,9 @@ class PowerFeedTable(TenancyColumnsMixin, CableTerminationTable): class Meta(NetBoxTable.Meta): model = PowerFeed fields = ( - 'pk', 'id', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', - 'max_utilization', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'available_power', 'tenant', - 'tenant_group', 'description', 'comments', 'tags', 'created', 'last_updated', + 'pk', 'id', 'name', 'power_panel', 'site', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', + 'phase', 'max_utilization', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'available_power', + 'tenant', 'tenant_group', 'description', 'comments', 'tags', 'created', 'last_updated', ) default_columns = ( 'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', 'cable', From d2c727c0a2d1e5e56857fb74628e193a23cf3aa2 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 9 Nov 2023 02:54:56 +0530 Subject: [PATCH 13/30] review changes #13743 --- netbox/dcim/tables/power.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/tables/power.py b/netbox/dcim/tables/power.py index 04aa9a2d6de..40a58ad8161 100644 --- a/netbox/dcim/tables/power.py +++ b/netbox/dcim/tables/power.py @@ -87,8 +87,9 @@ class PowerFeedTable(TenancyColumnsMixin, CableTerminationTable): linkify=True, verbose_name=_('Tenant') ) - site = tables.TemplateColumn( - template_code='''{% if record.rack %}{{ record.rack.site|linkify }}{% else %}—{% endif %}''', + site = tables.Column( + accessor='rack__site', + linkify=True, verbose_name=_('Site'), ) comments = columns.MarkdownColumn( From 1203d761f4c741ea86e1dbe30b48266e49ef9a4d Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 9 Nov 2023 19:16:14 +0530 Subject: [PATCH 14/30] Adds mask length filters on ipaddress (#14218) * adds mask length filters on ipaddress #14101 * Change IPaddress mask_length filter to multi-value; extend tests --------- Co-authored-by: Jeremy Stretch --- netbox/ipam/filtersets.py | 23 ++++++++++++++--------- netbox/ipam/tests/test_filtersets.py | 12 ++++++++++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index bc918128651..d1177bdc1ae 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -266,7 +266,8 @@ class PrefixFilterSet(NetBoxModelFilterSet, TenancyFilterSet): ) mask_length = MultiValueNumberFilter( field_name='prefix', - lookup_expr='net_mask_length' + lookup_expr='net_mask_length', + label=_('Mask length') ) mask_length__gte = django_filters.NumberFilter( field_name='prefix', @@ -531,9 +532,18 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet): method='filter_address', label=_('Address'), ) - mask_length = django_filters.NumberFilter( - method='filter_mask_length', - label=_('Mask length'), + mask_length = MultiValueNumberFilter( + field_name='address', + lookup_expr='net_mask_length', + label=_('Mask length') + ) + mask_length__gte = django_filters.NumberFilter( + field_name='address', + lookup_expr='net_mask_length__gte' + ) + mask_length__lte = django_filters.NumberFilter( + field_name='address', + lookup_expr='net_mask_length__lte' ) vrf_id = django_filters.ModelMultipleChoiceFilter( queryset=VRF.objects.all(), @@ -677,11 +687,6 @@ def filter_address(self, queryset, name, value): except ValidationError: return queryset.none() - def filter_mask_length(self, queryset, name, value): - if not value: - return queryset - return queryset.filter(address__net_mask_length=value) - @extend_schema_field(OpenApiTypes.STR) def filter_present_in_vrf(self, queryset, name, vrf): if vrf is None: diff --git a/netbox/ipam/tests/test_filtersets.py b/netbox/ipam/tests/test_filtersets.py index 59635690644..95237605647 100644 --- a/netbox/ipam/tests/test_filtersets.py +++ b/netbox/ipam/tests/test_filtersets.py @@ -627,8 +627,12 @@ def test_children(self): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_mask_length(self): - params = {'mask_length': ['24']} + params = {'mask_length': [24]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + params = {'mask_length__gte': 32} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) + params = {'mask_length__lte': 24} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) def test_vrf(self): vrfs = VRF.objects.all()[:2] @@ -954,8 +958,12 @@ def test_filter_address(self): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) def test_mask_length(self): - params = {'mask_length': '24'} + params = {'mask_length': [24]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) + params = {'mask_length__gte': 64} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) + params = {'mask_length__lte': 25} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) def test_vrf(self): vrfs = VRF.objects.all()[:2] From 0603dd1be485a8202ade05c3faee824bbbeb99e8 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 9 Nov 2023 19:17:24 +0530 Subject: [PATCH 15/30] Adds inventory item children view (#14217) * adds inventory item children view #14112 * Use existing child_items relation --------- Co-authored-by: Jeremy Stretch --- netbox/dcim/views.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 7c75dd26e38..c67dfaade07 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2993,6 +2993,25 @@ class InventoryItemBulkDeleteView(generic.BulkDeleteView): template_name = 'dcim/inventoryitem_bulk_delete.html' +@register_model_view(InventoryItem, 'children') +class InventoryItemChildrenView(generic.ObjectChildrenView): + queryset = InventoryItem.objects.all() + child_model = InventoryItem + table = tables.InventoryItemTable + filterset = filtersets.InventoryItemFilterSet + template_name = 'generic/object_children.html' + tab = ViewTab( + label=_('Children'), + badge=lambda obj: obj.child_items.count(), + permission='dcim.view_inventoryitem', + hide_if_empty=True, + weight=5000 + ) + + def get_children(self, request, parent): + return parent.child_items.restrict(request.user, 'view') + + # # Inventory item roles # From dfef89ab88dc57fddeae98fdf427168dbce0a48b Mon Sep 17 00:00:00 2001 From: Chris Mills <1878544+cpmills1975@users.noreply.github.com> Date: Thu, 9 Nov 2023 01:17:09 +0000 Subject: [PATCH 16/30] Fix ordering on JobTable. #14223 --- netbox/core/tables/jobs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/netbox/core/tables/jobs.py b/netbox/core/tables/jobs.py index 32ca67f7f6d..f65964f77c8 100644 --- a/netbox/core/tables/jobs.py +++ b/netbox/core/tables/jobs.py @@ -19,7 +19,8 @@ class JobTable(NetBoxTable): ) object = tables.Column( verbose_name=_('Object'), - linkify=True + linkify=True, + orderable=False ) status = columns.ChoiceFieldColumn( verbose_name=_('Status'), From 95519b42a056320a5745086b243e8959e3168195 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 9 Nov 2023 19:43:46 +0530 Subject: [PATCH 17/30] Adds device and vm to service filter form (#14215) * adds device and vm to service filter form #13951 * Tweak labels --------- Co-authored-by: Jeremy Stretch --- netbox/ipam/forms/filtersets.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/netbox/ipam/forms/filtersets.py b/netbox/ipam/forms/filtersets.py index aae62ca7575..a8ca91901d3 100644 --- a/netbox/ipam/forms/filtersets.py +++ b/netbox/ipam/forms/filtersets.py @@ -523,6 +523,21 @@ class ServiceTemplateFilterForm(NetBoxModelFilterSetForm): class ServiceFilterForm(ServiceTemplateFilterForm): model = Service + fieldsets = ( + (None, ('q', 'filter_id', 'tag')), + (_('Attributes'), ('protocol', 'port')), + (_('Assignment'), ('device_id', 'virtual_machine_id')), + ) + device_id = DynamicModelMultipleChoiceField( + queryset=Device.objects.all(), + required=False, + label=_('Device'), + ) + virtual_machine_id = DynamicModelMultipleChoiceField( + queryset=VirtualMachine.objects.all(), + required=False, + label=_('Virtual Machine'), + ) tag = TagFilterField(model) From 5000564430cab3da2a752568b737829e2b536cfe Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 9 Nov 2023 09:19:49 -0500 Subject: [PATCH 18/30] Changelog for #13669, #13723, #13743, #13951, #14033, #14101, #14112, #14113, #14220, #14220 --- docs/release-notes/version-3.6.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index 8f827b50e05..41413016b7a 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -2,6 +2,22 @@ ## v3.6.5 (FUTURE) +### Enhancements + +* [#13669](https://github.com/netbox-community/netbox/issues/13669) - Add bulk import button to contact assignments list view +* [#13723](https://github.com/netbox-community/netbox/issues/13723) - Add inventory items column to interfaces table +* [#13743](https://github.com/netbox-community/netbox/issues/13743) - Add site column to power feeds table +* [#13951](https://github.com/netbox-community/netbox/issues/13951) - Add device & virtual machine fields to service filter form +* [#14101](https://github.com/netbox-community/netbox/issues/14101) - Add greater/less than mask length filters for IP addresses +* [#14112](https://github.com/netbox-community/netbox/issues/14112) - Add tab listing child items under inventory item view +* [#14113](https://github.com/netbox-community/netbox/issues/14113) - Add optional parent column to inventory items table +* [#14220](https://github.com/netbox-community/netbox/issues/14220) - Order available columns alphabetically in table configuration form + +### Bug Fixes + +* [#14033](https://github.com/netbox-community/netbox/issues/14033) - Avoid exception when attempting to connect both ends of a cable to the same object +* [#14223](https://github.com/netbox-community/netbox/issues/14223) - Disable ordering of jobs by assigned object + --- ## v3.6.4 (2023-10-17) From 6900097e2df13f676b3c55ec43e63332424fa4d3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 1 Nov 2023 16:24:14 -0400 Subject: [PATCH 19/30] Fixes #14117: Validate the number of front ports to be created --- netbox/dcim/forms/object_create.py | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py index abd7bd6f632..ea842508ff8 100644 --- a/netbox/dcim/forms/object_create.py +++ b/netbox/dcim/forms/object_create.py @@ -151,6 +151,23 @@ def __init__(self, *args, **kwargs): ) self.fields['rear_port'].choices = choices + def clean(self): + + # Check that the number of FrontPortTemplates to be created matches the selected number of RearPortTemplate + # positions + frontport_count = len(self.cleaned_data['name']) + rearport_count = len(self.cleaned_data['rear_port']) + if frontport_count != rearport_count: + raise forms.ValidationError({ + 'rear_port': _( + "The number of front port templates to be created ({frontport_count}) must match the selected " + "number of rear port positions ({rearport_count})." + ).format( + frontport_count=frontport_count, + rearport_count=rearport_count + ) + }) + def get_iterative_data(self, iteration): # Assign rear port and position from selected set @@ -291,6 +308,22 @@ def __init__(self, *args, **kwargs): ) self.fields['rear_port'].choices = choices + def clean(self): + + # Check that the number of FrontPorts to be created matches the selected number of RearPort positions + frontport_count = len(self.cleaned_data['name']) + rearport_count = len(self.cleaned_data['rear_port']) + if frontport_count != rearport_count: + raise forms.ValidationError({ + 'rear_port': _( + "The number of front ports to be created ({frontport_count}) must match the selected number of " + "rear port positions ({rearport_count})." + ).format( + frontport_count=frontport_count, + rearport_count=rearport_count + ) + }) + def get_iterative_data(self, iteration): # Assign rear port and position from selected set From 092f2b06abc64e1d9d9c89b61a39f7d67e23c2e8 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Thu, 9 Nov 2023 20:25:44 +0530 Subject: [PATCH 20/30] Enhance Virtual Machine and Device Platform Filter with Manufacturer Information (#14047) * Add manufacturer for filters in the virtual machine and device #12741 * reverse the filtersets of device and vm * revert the filtersets of vm * add advance selector in platform * remove manufacture from imports --- netbox/dcim/forms/model_forms.py | 3 ++- netbox/virtualization/forms/model_forms.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 93e21459873..219e1f6c3bf 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -442,7 +442,8 @@ class DeviceForm(TenancyForm, NetBoxModelForm): platform = DynamicModelChoiceField( label=_('Platform'), queryset=Platform.objects.all(), - required=False + required=False, + selector=True ) cluster = DynamicModelChoiceField( label=_('Cluster'), diff --git a/netbox/virtualization/forms/model_forms.py b/netbox/virtualization/forms/model_forms.py index 21dbc895a76..91f5b06ad1c 100644 --- a/netbox/virtualization/forms/model_forms.py +++ b/netbox/virtualization/forms/model_forms.py @@ -200,7 +200,8 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm): platform = DynamicModelChoiceField( label=_('Platform'), queryset=Platform.objects.all(), - required=False + required=False, + selector=True ) local_context_data = JSONField( required=False, From 6b89da2233b8f424e2cddc9c8bbe32a53cef4e74 Mon Sep 17 00:00:00 2001 From: Artem Kotik Date: Thu, 9 Nov 2023 22:56:43 +0800 Subject: [PATCH 21/30] Closes #13936: Add primary_ip4 and primary_ip6 filters to VirtualMachine and VirtualDeviceContext filtersets (#14203) * Add primary_ip4 and primary_ip6 filters for VirtualMachine and VirtualDeviceContext filtersets (#13936) * Add PrimaryIPFilterSet to __all__ --------- Co-authored-by: Artem I. Kotik Co-authored-by: Jeremy Stretch --- netbox/dcim/filtersets.py | 21 ++++++++---------- netbox/dcim/tests/test_filtersets.py | 20 +++++++++++++++++ netbox/extras/tests/test_views.py | 2 +- netbox/ipam/filtersets.py | 17 ++++++++++++++ netbox/virtualization/filtersets.py | 4 +++- .../virtualization/tests/test_filtersets.py | 22 +++++++++++++++++-- 6 files changed, 70 insertions(+), 16 deletions(-) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index d600667d700..b5bdaf26995 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -4,6 +4,7 @@ from extras.filtersets import LocalConfigContextFilterSet from extras.models import ConfigTemplate +from ipam.filtersets import PrimaryIPFilterSet from ipam.models import ASN, L2VPN, IPAddress, VRF from netbox.filtersets import ( BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet, @@ -817,7 +818,13 @@ class Meta: fields = ['id', 'name', 'slug', 'description'] -class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet, LocalConfigContextFilterSet): +class DeviceFilterSet( + NetBoxModelFilterSet, + TenancyFilterSet, + ContactModelFilterSet, + LocalConfigContextFilterSet, + PrimaryIPFilterSet, +): manufacturer_id = django_filters.ModelMultipleChoiceFilter( field_name='device_type__manufacturer', queryset=Manufacturer.objects.all(), @@ -993,16 +1000,6 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter method='_device_bays', label=_('Has device bays'), ) - primary_ip4_id = django_filters.ModelMultipleChoiceFilter( - field_name='primary_ip4', - queryset=IPAddress.objects.all(), - label=_('Primary IPv4 (ID)'), - ) - primary_ip6_id = django_filters.ModelMultipleChoiceFilter( - field_name='primary_ip6', - queryset=IPAddress.objects.all(), - label=_('Primary IPv6 (ID)'), - ) oob_ip_id = django_filters.ModelMultipleChoiceFilter( field_name='oob_ip', queryset=IPAddress.objects.all(), @@ -1069,7 +1066,7 @@ def _device_bays(self, queryset, name, value): return queryset.exclude(devicebays__isnull=value) -class VirtualDeviceContextFilterSet(NetBoxModelFilterSet, TenancyFilterSet): +class VirtualDeviceContextFilterSet(NetBoxModelFilterSet, TenancyFilterSet, PrimaryIPFilterSet): device_id = django_filters.ModelMultipleChoiceFilter( field_name='device', queryset=Device.objects.all(), diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 1f3b557b56c..8fbef126e6b 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -4712,12 +4712,18 @@ def setUpTestData(cls): addresses = ( IPAddress(assigned_object=interfaces[0], address='10.1.1.1/24'), IPAddress(assigned_object=interfaces[1], address='10.1.1.2/24'), + IPAddress(assigned_object=None, address='10.1.1.3/24'), + IPAddress(assigned_object=interfaces[0], address='2001:db8::1/64'), + IPAddress(assigned_object=interfaces[1], address='2001:db8::2/64'), + IPAddress(assigned_object=None, address='2001:db8::3/64'), ) IPAddress.objects.bulk_create(addresses) vdcs[0].primary_ip4 = addresses[0] + vdcs[0].primary_ip6 = addresses[3] vdcs[0].save() vdcs[1].primary_ip4 = addresses[1] + vdcs[1].primary_ip6 = addresses[4] vdcs[1].save() def test_device(self): @@ -4738,3 +4744,17 @@ def test_has_primary_ip(self): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) params = {'has_primary_ip': False} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) + + def test_primary_ip4(self): + addresses = IPAddress.objects.filter(address__family=4) + params = {'primary_ip4_id': [addresses[0].pk, addresses[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'primary_ip4_id': [addresses[2].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0) + + def test_primary_ip6(self): + addresses = IPAddress.objects.filter(address__family=6) + params = {'primary_ip6_id': [addresses[0].pk, addresses[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'primary_ip6_id': [addresses[2].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0) diff --git a/netbox/extras/tests/test_views.py b/netbox/extras/tests/test_views.py index 296ed9f4d5a..e034abff53b 100644 --- a/netbox/extras/tests/test_views.py +++ b/netbox/extras/tests/test_views.py @@ -457,7 +457,7 @@ def setUpTestData(cls): 'platforms': [], 'tenant_groups': [], 'tenants': [], - 'device_types': [devicetype.id,], + 'device_types': [devicetype.id], 'tags': [], 'data': '{"foo": 123}', } diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index d1177bdc1ae..ba944e3ada9 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -29,6 +29,7 @@ 'L2VPNFilterSet', 'L2VPNTerminationFilterSet', 'PrefixFilterSet', + 'PrimaryIPFilterSet', 'RIRFilterSet', 'RoleFilterSet', 'RouteTargetFilterSet', @@ -1232,3 +1233,19 @@ def filter_region(self, queryset, name, value): ) ) return qs + + +class PrimaryIPFilterSet(django_filters.FilterSet): + """ + An inheritable FilterSet for models which support primary IP assignment. + """ + primary_ip4_id = django_filters.ModelMultipleChoiceFilter( + field_name='primary_ip4', + queryset=IPAddress.objects.all(), + label=_('Primary IPv4 (ID)'), + ) + primary_ip6_id = django_filters.ModelMultipleChoiceFilter( + field_name='primary_ip6', + queryset=IPAddress.objects.all(), + label=_('Primary IPv6 (ID)'), + ) diff --git a/netbox/virtualization/filtersets.py b/netbox/virtualization/filtersets.py index 571dbe64b2d..b23808b317d 100644 --- a/netbox/virtualization/filtersets.py +++ b/netbox/virtualization/filtersets.py @@ -6,6 +6,7 @@ from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup from extras.filtersets import LocalConfigContextFilterSet from extras.models import ConfigTemplate +from ipam.filtersets import PrimaryIPFilterSet from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet from utilities.filters import MultiValueCharFilter, MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter @@ -114,7 +115,8 @@ class VirtualMachineFilterSet( NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet, - LocalConfigContextFilterSet + LocalConfigContextFilterSet, + PrimaryIPFilterSet, ): status = django_filters.MultipleChoiceFilter( choices=VirtualMachineStatusChoices, diff --git a/netbox/virtualization/tests/test_filtersets.py b/netbox/virtualization/tests/test_filtersets.py index d474af21a49..e6fe9029732 100644 --- a/netbox/virtualization/tests/test_filtersets.py +++ b/netbox/virtualization/tests/test_filtersets.py @@ -291,10 +291,14 @@ def setUpTestData(cls): ipaddresses = ( IPAddress(address='192.0.2.1/24', assigned_object=interfaces[0]), IPAddress(address='192.0.2.2/24', assigned_object=interfaces[1]), + IPAddress(address='192.0.2.3/24', assigned_object=None), + IPAddress(address='2001:db8::1/64', assigned_object=interfaces[0]), + IPAddress(address='2001:db8::2/64', assigned_object=interfaces[1]), + IPAddress(address='2001:db8::3/64', assigned_object=None), ) IPAddress.objects.bulk_create(ipaddresses) - VirtualMachine.objects.filter(pk=vms[0].pk).update(primary_ip4=ipaddresses[0]) - VirtualMachine.objects.filter(pk=vms[1].pk).update(primary_ip4=ipaddresses[1]) + VirtualMachine.objects.filter(pk=vms[0].pk).update(primary_ip4=ipaddresses[0], primary_ip6=ipaddresses[3]) + VirtualMachine.objects.filter(pk=vms[1].pk).update(primary_ip4=ipaddresses[1], primary_ip6=ipaddresses[4]) def test_name(self): params = {'name': ['Virtual Machine 1', 'Virtual Machine 2']} @@ -412,6 +416,20 @@ def test_tenant_group(self): params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_primary_ip4(self): + addresses = IPAddress.objects.filter(address__family=4) + params = {'primary_ip4_id': [addresses[0].pk, addresses[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'primary_ip4_id': [addresses[2].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0) + + def test_primary_ip6(self): + addresses = IPAddress.objects.filter(address__family=6) + params = {'primary_ip6_id': [addresses[0].pk, addresses[1].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + params = {'primary_ip6_id': [addresses[2].pk]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0) + class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = VMInterface.objects.all() From e5c38e0829705d44fd47764cf2b2eb92d9540da5 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 9 Nov 2023 10:55:55 -0500 Subject: [PATCH 22/30] Closes #13022: Add IP assignment support when bulk importing services (#14230) * issue 13022 resolved, ipaddress added into bulk_import form * validation of ip address for device and virtual machine * error message modified * error message modified * error message modified * Fix form validation * Extend bulk import test --------- Co-authored-by: yash-pal1 Co-authored-by: yash-pal1 --- netbox/ipam/forms/bulk_import.py | 20 +++++++++++++++++++- netbox/ipam/tests/test_views.py | 16 ++++++++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/netbox/ipam/forms/bulk_import.py b/netbox/ipam/forms/bulk_import.py index ac3c994681e..ed3ceec2b30 100644 --- a/netbox/ipam/forms/bulk_import.py +++ b/netbox/ipam/forms/bulk_import.py @@ -507,10 +507,28 @@ class ServiceImportForm(NetBoxModelImportForm): choices=ServiceProtocolChoices, help_text=_('IP protocol') ) + ipaddresses = CSVModelMultipleChoiceField( + queryset=IPAddress.objects.all(), + required=False, + to_field_name='address', + help_text=_('IP Address'), + ) class Meta: model = Service - fields = ('device', 'virtual_machine', 'name', 'protocol', 'ports', 'description', 'comments', 'tags') + fields = ( + 'device', 'virtual_machine', 'ipaddresses', 'name', 'protocol', 'ports', 'description', 'comments', 'tags', + ) + + def clean_ipaddresses(self): + parent = self.cleaned_data.get('device') or self.cleaned_data.get('virtual_machine') + for ip_address in self.cleaned_data['ipaddresses']: + if not ip_address.assigned_object or getattr(ip_address.assigned_object, 'parent_object') != parent: + raise forms.ValidationError( + _("{ip} is not assigned to this device/VM.").format(ip=ip_address) + ) + + return self.cleaned_data['ipaddresses'] class L2VPNImportForm(NetBoxModelImportForm): diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index afc97cc634c..a37584f0f13 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -4,6 +4,7 @@ from django.urls import reverse from netaddr import IPNetwork +from dcim.constants import InterfaceTypeChoices from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site, Interface from ipam.choices import * from ipam.models import * @@ -911,6 +912,7 @@ def setUpTestData(cls): devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1') role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') device = Device.objects.create(name='Device 1', site=site, device_type=devicetype, role=role) + interface = Interface.objects.create(device=device, name='Interface 1', type=InterfaceTypeChoices.TYPE_VIRTUAL) services = ( Service(device=device, name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[101]), @@ -919,6 +921,12 @@ def setUpTestData(cls): ) Service.objects.bulk_create(services) + ip_addresses = ( + IPAddress(assigned_object=interface, address='192.0.2.1/24'), + IPAddress(assigned_object=interface, address='192.0.2.2/24'), + ) + IPAddress.objects.bulk_create(ip_addresses) + tags = create_tags('Alpha', 'Bravo', 'Charlie') cls.form_data = { @@ -933,10 +941,10 @@ def setUpTestData(cls): } cls.csv_data = ( - "device,name,protocol,ports,description", - "Device 1,Service 1,tcp,1,First service", - "Device 1,Service 2,tcp,2,Second service", - "Device 1,Service 3,udp,3,Third service", + "device,name,protocol,ports,ipaddresses,description", + "Device 1,Service 1,tcp,1,192.0.2.1/24,First service", + "Device 1,Service 2,tcp,2,192.0.2.2/24,Second service", + "Device 1,Service 3,udp,3,,Third service", ) cls.csv_update_data = ( From 57bf2a2f00f2254750590aa6b752b7b2bd6cc3e6 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 9 Nov 2023 20:59:55 +0530 Subject: [PATCH 23/30] fix asn view under asn range #14195 --- netbox/ipam/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 7cf785521df..48ea637d909 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -220,7 +220,7 @@ class ASNRangeASNsView(generic.ObjectChildrenView): tab = ViewTab( label=_('ASNs'), badge=lambda x: x.get_child_asns().count(), - permission='ipam.view_asns', + permission='ipam.view_asn', weight=500 ) From ad95760eadb262f51b2430640ff8f43f4d2a846d Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 9 Nov 2023 21:12:21 +0530 Subject: [PATCH 24/30] adds contact group on contact assignment table #14221 --- netbox/tenancy/tables/contacts.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/netbox/tenancy/tables/contacts.py b/netbox/tenancy/tables/contacts.py index 2e752548196..a22c0456959 100644 --- a/netbox/tenancy/tables/contacts.py +++ b/netbox/tenancy/tables/contacts.py @@ -102,6 +102,11 @@ class ContactAssignmentTable(NetBoxTable): verbose_name=_('Role'), linkify=True ) + contact_group = tables.Column( + accessor=Accessor('contact__group'), + verbose_name=_('Group'), + linkify=True + ) contact_title = tables.Column( accessor=Accessor('contact__title'), verbose_name=_('Contact Title') @@ -137,7 +142,8 @@ class Meta(NetBoxTable.Meta): model = ContactAssignment fields = ( 'pk', 'content_type', 'object', 'contact', 'role', 'priority', 'contact_title', 'contact_phone', - 'contact_email', 'contact_address', 'contact_link', 'contact_description', 'tags', 'actions' + 'contact_email', 'contact_address', 'contact_link', 'contact_description', 'contact_group', 'tags', + 'actions' ) default_columns = ( 'pk', 'content_type', 'object', 'contact', 'role', 'priority', 'contact_email', 'contact_phone' From 217a9edb4c4848b818b2ca37de4e1b377aff9475 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 9 Nov 2023 23:43:03 +0530 Subject: [PATCH 25/30] handles the port in the ip #14085 --- netbox/utilities/request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/utilities/request.py b/netbox/utilities/request.py index 3b8e1eddefd..0f8ee9caee2 100644 --- a/netbox/utilities/request.py +++ b/netbox/utilities/request.py @@ -17,7 +17,7 @@ def get_client_ip(request, additional_headers=()): ) for header in HTTP_HEADERS: if header in request.META: - client_ip = request.META[header].split(',')[0] + client_ip = request.META[header].split(',')[0].partition(':')[0] try: return IPAddress(client_ip) except ValueError: From dd5e20aa1ad1ec46efeeabc5de71d4bba8e07037 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 9 Nov 2023 23:51:43 +0530 Subject: [PATCH 26/30] allow login and logout in maintenance mode #14166 --- netbox/netbox/settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 111781b8a3b..e3c716a1660 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -502,6 +502,9 @@ def _setting(name, default=None): MAINTENANCE_EXEMPT_PATHS = ( f'/{BASE_PATH}admin/', f'/{BASE_PATH}extras/config-revisions/', # Allow modifying the configuration + LOGIN_URL, + LOGIN_REDIRECT_URL, + LOGOUT_REDIRECT_URL ) SERIALIZATION_MODULES = { From e1bedb83501bb2c6b8b12968a48c87833f70a8bb Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 9 Nov 2023 21:57:58 +0530 Subject: [PATCH 27/30] restores config revision during cache clear #14182 --- netbox/core/management/commands/clearcache.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/netbox/core/management/commands/clearcache.py b/netbox/core/management/commands/clearcache.py index 22843c490ca..dd95013afb6 100644 --- a/netbox/core/management/commands/clearcache.py +++ b/netbox/core/management/commands/clearcache.py @@ -1,11 +1,20 @@ from django.core.cache import cache from django.core.management.base import BaseCommand +from extras.models import ConfigRevision + class Command(BaseCommand): """Command to clear the entire cache.""" help = 'Clears the cache.' def handle(self, *args, **kwargs): + # Fetch the current config revision from the cache + config_version = cache.get('config_version') + # Clear the cache cache.clear() self.stdout.write('Cache has been cleared.', ending="\n") + if config_version: + # Activate the current config revision + ConfigRevision.objects.get(id=config_version).activate() + self.stdout.write(f'Config revision ({config_version}) has been restored.', ending="\n") From 5c27d29b08535364df96d897b6271f49f74a7372 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 10 Nov 2023 01:39:16 +0530 Subject: [PATCH 28/30] Adds unit to the power port draw (#14208) * adds unit to the power port draw #13587 * review changes #13587 * moved units to header #13587 * Abbreviate unit for consistency with e.g. PowerFeedTable available_power column --------- Co-authored-by: Jeremy Stretch --- netbox/dcim/tables/devices.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index bf5f2d0502d..b72c37daa4d 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -466,6 +466,12 @@ class PowerPortTable(ModularDeviceComponentTable, PathEndpointTable): 'args': [Accessor('device_id')], } ) + maximum_draw = tables.Column( + verbose_name=_('Maximum draw (W)') + ) + allocated_draw = tables.Column( + verbose_name=_('Allocated draw (W)') + ) tags = columns.TagColumn( url_name='dcim:powerport_list' ) From 351aaf8397079d234001cd934c5121e4220ce9f0 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 9 Nov 2023 15:20:24 -0500 Subject: [PATCH 29/30] Changelog for #12741, #13022, #13587, #13936, #14085, #14117, #14166, #14182, #14195, #14221 --- docs/release-notes/version-3.6.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index 41413016b7a..dfa4abc50c7 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -4,18 +4,28 @@ ### Enhancements +* [#12741](https://github.com/netbox-community/netbox/issues/12741) - Add selector widget to platform field on device & virtual machine forms +* [#13022](https://github.com/netbox-community/netbox/issues/13022) - Introduce support for assigning IP addresses when bulk importing services +* [#13587](https://github.com/netbox-community/netbox/issues/13587) - Annotate units of measurement on power port table columns * [#13669](https://github.com/netbox-community/netbox/issues/13669) - Add bulk import button to contact assignments list view * [#13723](https://github.com/netbox-community/netbox/issues/13723) - Add inventory items column to interfaces table * [#13743](https://github.com/netbox-community/netbox/issues/13743) - Add site column to power feeds table +* [#13936](https://github.com/netbox-community/netbox/issues/13936) - Add primary IPv4 and IPv6 filters for virtual machines and VDCs * [#13951](https://github.com/netbox-community/netbox/issues/13951) - Add device & virtual machine fields to service filter form +* [#14085](https://github.com/netbox-community/netbox/issues/14085) - Strip trailing port number from value returned by `get_client_ip()` * [#14101](https://github.com/netbox-community/netbox/issues/14101) - Add greater/less than mask length filters for IP addresses * [#14112](https://github.com/netbox-community/netbox/issues/14112) - Add tab listing child items under inventory item view * [#14113](https://github.com/netbox-community/netbox/issues/14113) - Add optional parent column to inventory items table * [#14220](https://github.com/netbox-community/netbox/issues/14220) - Order available columns alphabetically in table configuration form +* [#14221](https://github.com/netbox-community/netbox/issues/14221) - Add contact group column on contact assignments table ### Bug Fixes * [#14033](https://github.com/netbox-community/netbox/issues/14033) - Avoid exception when attempting to connect both ends of a cable to the same object +* [#14117](https://github.com/netbox-community/netbox/issues/14117) - Check that enough rear port positions have been selected to accommodate the number of front ports being created +* [#14166](https://github.com/netbox-community/netbox/issues/14166) - Permit user login when maintenance mode is enabled +* [#14182](https://github.com/netbox-community/netbox/issues/14182) - Ensure the active configuration is restored upon clearing cache +* [#14195](https://github.com/netbox-community/netbox/issues/14195) - Correct permissions evaluation for ASN range child ASNs view * [#14223](https://github.com/netbox-community/netbox/issues/14223) - Disable ordering of jobs by assigned object --- From 41eae1bc19d1177ad853918119627f14ac4f399e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 9 Nov 2023 15:45:49 -0500 Subject: [PATCH 30/30] Release v3.6.5 --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- base_requirements.txt | 3 ++- docs/release-notes/version-3.6.md | 2 +- netbox/netbox/settings.py | 2 +- requirements.txt | 8 ++++---- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index f4afe3f9861..48c14a2daf0 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.6.4 + placeholder: v3.6.5 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 9bf991e6e70..0525659aee9 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.6.4 + placeholder: v3.6.5 validations: required: true - type: dropdown diff --git a/base_requirements.txt b/base_requirements.txt index 9863984ca61..6e3c5ba1916 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -53,7 +53,8 @@ django-tables2 # User-defined tags for objects # https://github.com/jazzband/django-taggit/blob/master/CHANGELOG.rst -django-taggit +# TODO: Upgrade to v5.0 for NetBox v3.7 beta +django-taggit<5.0 # A Django field for representing time zones # https://github.com/mfogel/django-timezone-field/ diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index dfa4abc50c7..646c2019ea1 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -1,6 +1,6 @@ # NetBox v3.6 -## v3.6.5 (FUTURE) +## v3.6.5 (2023-11-09) ### Enhancements diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index e3c716a1660..eac1c3c3777 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -25,7 +25,7 @@ # Environment setup # -VERSION = '3.6.5-dev' +VERSION = '3.6.5' # Hostname HOSTNAME = platform.node() diff --git a/requirements.txt b/requirements.txt index 9f9176ea214..16bafe62fe3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ bleach==6.1.0 -Django==4.2.6 +Django==4.2.7 django-cors-headers==4.3.0 django-debug-toolbar==4.2.0 django-filter==23.3 @@ -21,16 +21,16 @@ graphene-django==3.0.0 gunicorn==21.2.0 Jinja2==3.1.2 Markdown==3.3.7 -mkdocs-material==9.4.6 +mkdocs-material==9.4.8 mkdocstrings[python-legacy]==0.23.0 netaddr==0.9.0 Pillow==10.1.0 psycopg[binary,pool]==3.1.12 PyYAML==6.0.1 requests==2.31.0 -sentry-sdk==1.32.0 +sentry-sdk==1.34.0 social-auth-app-django==5.4.0 -social-auth-core[openidconnect]==4.4.2 +social-auth-core[openidconnect]==4.5.0 svgwrite==1.4.3 tablib==3.5.0 tzdata==2023.3