From 451a0067c70f13741cb52e5b9ee3dad5cb1d9d58 Mon Sep 17 00:00:00 2001 From: Gabor SOMOGYVARI Date: Fri, 22 Jul 2022 10:42:20 +0200 Subject: [PATCH 01/18] Closes #9825: Add Contacts to VM table view --- netbox/virtualization/tables/virtualmachines.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/netbox/virtualization/tables/virtualmachines.py b/netbox/virtualization/tables/virtualmachines.py index 8cff96227..d2be07876 100644 --- a/netbox/virtualization/tables/virtualmachines.py +++ b/netbox/virtualization/tables/virtualmachines.py @@ -48,6 +48,9 @@ class VirtualMachineTable(TenancyColumnsMixin, NetBoxTable): order_by=('primary_ip4', 'primary_ip6'), verbose_name='IP Address' ) + contacts = columns.ManyToManyColumn( + linkify_item=True + ) tags = columns.TagColumn( url_name='virtualization:virtualmachine_list' ) @@ -56,7 +59,7 @@ class VirtualMachineTable(TenancyColumnsMixin, NetBoxTable): model = VirtualMachine fields = ( 'pk', 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'tenant_group', 'platform', 'vcpus', 'memory', 'disk', - 'primary_ip4', 'primary_ip6', 'primary_ip', 'comments', 'tags', 'created', 'last_updated', + 'primary_ip4', 'primary_ip6', 'primary_ip', 'comments', 'contacts', 'tags', 'created', 'last_updated', ) default_columns = ( 'pk', 'name', 'status', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip', From 6cee12b1530597bf308ba39c661e456bd1802835 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 27 Jul 2022 15:40:25 -0400 Subject: [PATCH 02/18] Fix formatting of webhook conditions form field --- netbox/extras/forms/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/extras/forms/models.py b/netbox/extras/forms/models.py index 112911f42..82575de21 100644 --- a/netbox/extras/forms/models.py +++ b/netbox/extras/forms/models.py @@ -133,6 +133,7 @@ class WebhookForm(BootstrapMixin, forms.ModelForm): 'http_method': StaticSelect(), 'additional_headers': forms.Textarea(attrs={'class': 'font-monospace'}), 'body_template': forms.Textarea(attrs={'class': 'font-monospace'}), + 'conditions': forms.Textarea(attrs={'class': 'font-monospace'}), } From 498b655cb726b5bb1be442739923ae3746a8da41 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 27 Jul 2022 16:50:31 -0400 Subject: [PATCH 03/18] Changelog and cleanup for #9825 --- docs/release-notes/version-3.2.md | 4 ++++ netbox/virtualization/tables/virtualmachines.py | 11 ++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index c36344912..14d73d1eb 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -2,6 +2,10 @@ ## v3.2.8 (FUTURE) +### Enhancements + +* [#9825](https://github.com/netbox-community/netbox/issues/9825) - Add contacts column to virtual machines table + --- ## v3.2.7 (2022-07-20) diff --git a/netbox/virtualization/tables/virtualmachines.py b/netbox/virtualization/tables/virtualmachines.py index d2be07876..cace51ccc 100644 --- a/netbox/virtualization/tables/virtualmachines.py +++ b/netbox/virtualization/tables/virtualmachines.py @@ -58,8 +58,9 @@ class VirtualMachineTable(TenancyColumnsMixin, NetBoxTable): class Meta(NetBoxTable.Meta): model = VirtualMachine fields = ( - 'pk', 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'tenant_group', 'platform', 'vcpus', 'memory', 'disk', - 'primary_ip4', 'primary_ip6', 'primary_ip', 'comments', 'contacts', 'tags', 'created', 'last_updated', + 'pk', 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'tenant_group', 'platform', 'vcpus', 'memory', + 'disk', 'primary_ip4', 'primary_ip6', 'primary_ip', 'comments', 'contacts', 'tags', 'created', + 'last_updated', ) default_columns = ( 'pk', 'name', 'status', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip', @@ -80,9 +81,6 @@ class VMInterfaceTable(BaseInterfaceTable): vrf = tables.Column( linkify=True ) - contacts = columns.ManyToManyColumn( - linkify_item=True - ) tags = columns.TagColumn( url_name='virtualization:vminterface_list' ) @@ -91,8 +89,7 @@ class VMInterfaceTable(BaseInterfaceTable): model = VMInterface fields = ( 'pk', 'id', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags', - 'vrf', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'contacts', 'created', - 'last_updated', + 'vrf', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'created', 'last_updated', ) default_columns = ('pk', 'name', 'virtual_machine', 'enabled', 'description') From 62d1510c55f4afb59b8fd0a09c38405b71a29899 Mon Sep 17 00:00:00 2001 From: atownson <52260120+atownson@users.noreply.github.com> Date: Thu, 28 Jul 2022 10:23:04 -0500 Subject: [PATCH 04/18] Closes netbox-community#9762 Added nat_outside to IPAddressTable class --- netbox/ipam/tables/ip.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index bec05eeff..087d0de73 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -369,6 +369,11 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable): orderable=False, verbose_name='NAT (Inside)' ) + nat_outside = tables.Column( + linkify=True, + orderable=False, + verbose_name='NAT (Outside)' + ) assigned = columns.BooleanColumn( accessor='assigned_object_id', linkify=True, @@ -381,7 +386,7 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable): class Meta(NetBoxTable.Meta): model = IPAddress fields = ( - 'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'nat_inside', 'assigned', 'dns_name', 'description', + 'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'nat_inside', 'nat_outside', 'assigned', 'dns_name', 'description', 'tags', 'created', 'last_updated', ) default_columns = ( From 07620db027823fe55e49155f6adb4d2c80cace76 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 28 Jul 2022 12:45:27 -0400 Subject: [PATCH 05/18] Changelog for #9762 --- docs/release-notes/version-3.2.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 14d73d1eb..c0c99bb73 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -4,6 +4,7 @@ ### Enhancements +* [#9762](https://github.com/netbox-community/netbox/issues/9762) - Add `nat_outside` column to the IPAddress table * [#9825](https://github.com/netbox-community/netbox/issues/9825) - Add contacts column to virtual machines table --- From 890efa5400b0325fc32ccbd4946a554a78566447 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Fri, 29 Jul 2022 11:55:26 -0500 Subject: [PATCH 06/18] Fixes #9062 - Add/edit {module} substitution to help text for component template name --- docs/release-notes/version-3.2.md | 1 + netbox/dcim/forms/object_create.py | 8 ++++++++ netbox/dcim/models/device_component_templates.py | 5 ++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index c0c99bb73..ce8552b0b 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -4,6 +4,7 @@ ### Enhancements +* [#9062](https://github.com/netbox-community/netbox/issues/9062) - Add/edit {module} substitution to help text for component template name * [#9762](https://github.com/netbox-community/netbox/issues/9762) - Add `nat_outside` column to the IPAddress table * [#9825](https://github.com/netbox-community/netbox/issues/9825) - Add contacts column to virtual machines table diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py index 8c9ddab19..d2c941b34 100644 --- a/netbox/dcim/forms/object_create.py +++ b/netbox/dcim/forms/object_create.py @@ -64,6 +64,14 @@ class ModularComponentTemplateCreateForm(ComponentCreateForm): """ Creation form for component templates that can be assigned to either a DeviceType *or* a ModuleType. """ + name_pattern = ExpandableNameField( + label='Name', + help_text=""" + Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range + are not supported. Example: [ge,xe]-0/0/[0-9]. {module} is accepted as a substitution for + the module bay position. + """ + ) device_type = DynamicModelChoiceField( queryset=DeviceType.objects.all(), required=False diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 92658d310..ac0738b3f 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -39,7 +39,10 @@ class ComponentTemplateModel(WebhooksMixin, ChangeLoggedModel): related_name='%(class)ss' ) name = models.CharField( - max_length=64 + max_length=64, + help_text=""" + {module} is accepted as a substitution for the module bay position when attached to a module type. + """ ) _name = NaturalOrderingField( target_field='name', From 5ab03b7e926506a7ae949982bb252d62133fb7eb Mon Sep 17 00:00:00 2001 From: atownson <52260120+atownson@users.noreply.github.com> Date: Mon, 1 Aug 2022 08:01:18 -0500 Subject: [PATCH 07/18] Closes #9637: Add existing fields to the Rack Reservation user interface pages (#9870) * Closes netbox-community#9637 Added site_group to RackReservationFrom class fieldsets Added location to RackReservationTable class --- netbox/dcim/forms/filtersets.py | 2 +- netbox/dcim/forms/models.py | 2 +- netbox/dcim/tables/racks.py | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 38221b371..9a9f91ffb 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -287,7 +287,7 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): fieldsets = ( (None, ('q', 'tag')), ('User', ('user_id',)), - ('Rack', ('region_id', 'site_group_id', 'site_id', 'location_id')), + ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) region_id = DynamicModelMultipleChoiceField( diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index 043af751d..fb09b9871 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -321,7 +321,7 @@ class RackReservationForm(TenancyForm, NetBoxModelForm): ) fieldsets = ( - ('Reservation', ('region', 'site', 'location', 'rack', 'units', 'user', 'description', 'tags')), + ('Reservation', ('region', 'site_group', 'site', 'location', 'rack', 'units', 'user', 'description', 'tags')), ('Tenancy', ('tenant_group', 'tenant')), ) diff --git a/netbox/dcim/tables/racks.py b/netbox/dcim/tables/racks.py index 5412e2297..d83f25a5f 100644 --- a/netbox/dcim/tables/racks.py +++ b/netbox/dcim/tables/racks.py @@ -109,6 +109,10 @@ class RackReservationTable(TenancyColumnsMixin, NetBoxTable): accessor=Accessor('rack__site'), linkify=True ) + location = tables.Column( + accessor=Accessor('rack__location'), + linkify=True + ) rack = tables.Column( linkify=True ) @@ -123,7 +127,7 @@ class RackReservationTable(TenancyColumnsMixin, NetBoxTable): class Meta(NetBoxTable.Meta): model = RackReservation fields = ( - 'pk', 'id', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'tenant_group', 'description', 'tags', + 'pk', 'id', 'reservation', 'site', 'location', 'rack', 'unit_list', 'user', 'created', 'tenant', 'tenant_group', 'description', 'tags', 'actions', 'created', 'last_updated', ) default_columns = ('pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'description') From 728ad5162466fa805a428553a621bd78134ba61b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 1 Aug 2022 09:12:15 -0400 Subject: [PATCH 08/18] Changelog & cleanup for #9637 --- docs/release-notes/version-3.2.md | 1 + netbox/dcim/forms/filtersets.py | 35 +++++++++++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index ce8552b0b..57a6dffe1 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -5,6 +5,7 @@ ### Enhancements * [#9062](https://github.com/netbox-community/netbox/issues/9062) - Add/edit {module} substitution to help text for component template name +* [#9637](https://github.com/netbox-community/netbox/issues/9637) - Add site group field to rack reservation form * [#9762](https://github.com/netbox-community/netbox/issues/9762) - Add `nat_outside` column to the IPAddress table * [#9825](https://github.com/netbox-community/netbox/issues/9825) - Add contacts column to virtual machines table diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 9a9f91ffb..12905aec9 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -287,7 +287,7 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): fieldsets = ( (None, ('q', 'tag')), ('User', ('user_id',)), - ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')), + ('Rack', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')), ('Tenant', ('tenant_group_id', 'tenant_id')), ) region_id = DynamicModelMultipleChoiceField( @@ -295,25 +295,38 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm): required=False, label=_('Region') ) - site_id = DynamicModelMultipleChoiceField( - queryset=Site.objects.all(), - required=False, - query_params={ - 'region_id': '$region_id' - }, - label=_('Site') - ) site_group_id = DynamicModelMultipleChoiceField( queryset=SiteGroup.objects.all(), required=False, label=_('Site group') ) - location_id = DynamicModelMultipleChoiceField( - queryset=Location.objects.prefetch_related('site'), + site_id = DynamicModelMultipleChoiceField( + queryset=Site.objects.all(), required=False, + query_params={ + 'region_id': '$region_id', + 'group_id': '$site_group_id', + }, + label=_('Site') + ) + location_id = DynamicModelMultipleChoiceField( + queryset=Location.objects.all(), + required=False, + query_params={ + 'site_id': '$site_id', + }, label=_('Location'), null_option='None' ) + rack_id = DynamicModelMultipleChoiceField( + queryset=Rack.objects.all(), + required=False, + query_params={ + 'site_id': '$site_id', + 'location_id': '$location_id', + }, + label=_('Rack') + ) user_id = DynamicModelMultipleChoiceField( queryset=User.objects.all(), required=False, From 84f2225f42f16ca64777172135eba2689db958f3 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 1 Aug 2022 09:16:58 -0400 Subject: [PATCH 09/18] PEP8 cleanup --- netbox/dcim/tests/test_models.py | 4 ++-- netbox/extras/models/customfields.py | 2 +- netbox/extras/tests/test_customfields.py | 2 +- netbox/extras/tests/test_registry.py | 2 +- netbox/ipam/forms/models.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 8566f969b..03438a441 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -194,14 +194,14 @@ class RackTestCase(TestCase): # Validate inventory (front face) rack1_inventory_front = self.rack.get_rack_units(face=DeviceFaceChoices.FACE_FRONT) self.assertEqual(rack1_inventory_front[-10]['device'], device1) - del(rack1_inventory_front[-10]) + del rack1_inventory_front[-10] for u in rack1_inventory_front: self.assertIsNone(u['device']) # Validate inventory (rear face) rack1_inventory_rear = self.rack.get_rack_units(face=DeviceFaceChoices.FACE_REAR) self.assertEqual(rack1_inventory_rear[-10]['device'], device1) - del(rack1_inventory_rear[-10]) + del rack1_inventory_rear[-10] for u in rack1_inventory_rear: self.assertIsNone(u['device']) diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 6a8c1dacf..b7d77e550 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -169,7 +169,7 @@ class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel): model = ct.model_class() instances = model.objects.filter(**{f'custom_field_data__{self.name}__isnull': False}) for instance in instances: - del(instance.custom_field_data[self.name]) + del instance.custom_field_data[self.name] model.objects.bulk_update(instances, ['custom_field_data'], batch_size=100) def rename_object_data(self, old_name, new_name): diff --git a/netbox/extras/tests/test_customfields.py b/netbox/extras/tests/test_customfields.py index 8dcb53b09..946999bc2 100644 --- a/netbox/extras/tests/test_customfields.py +++ b/netbox/extras/tests/test_customfields.py @@ -992,7 +992,7 @@ class CustomFieldModelTest(TestCase): with self.assertRaises(ValidationError): site.clean() - del(site.cf['bar']) + del site.cf['bar'] site.clean() def test_missing_required_field(self): diff --git a/netbox/extras/tests/test_registry.py b/netbox/extras/tests/test_registry.py index 53ba6584a..38a6b9f83 100644 --- a/netbox/extras/tests/test_registry.py +++ b/netbox/extras/tests/test_registry.py @@ -30,4 +30,4 @@ class RegistryTest(TestCase): reg['foo'] = 123 with self.assertRaises(TypeError): - del(reg['foo']) + del reg['foo'] diff --git a/netbox/ipam/forms/models.py b/netbox/ipam/forms/models.py index e86abc672..d3421f22b 100644 --- a/netbox/ipam/forms/models.py +++ b/netbox/ipam/forms/models.py @@ -848,7 +848,7 @@ class ServiceCreateForm(ServiceForm): # Fields which may be populated from a ServiceTemplate are not required for field in ('name', 'protocol', 'ports'): self.fields[field].required = False - del(self.fields[field].widget.attrs['required']) + del self.fields[field].widget.attrs['required'] def clean(self): if self.cleaned_data['service_template']: From 9fe5f09742b8d9ed0a0ddc8ea57a7ad21884726b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 1 Aug 2022 09:32:52 -0400 Subject: [PATCH 10/18] Fixes #9891: Ensure consistent ordering for tags during object serialization --- docs/release-notes/version-3.2.md | 6 +++++- netbox/utilities/utils.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 57a6dffe1..ed08ad891 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -4,11 +4,15 @@ ### Enhancements -* [#9062](https://github.com/netbox-community/netbox/issues/9062) - Add/edit {module} substitution to help text for component template name +* [#9062](https://github.com/netbox-community/netbox/issues/9062) - Add/edit {module} substitution to help text for component template name * [#9637](https://github.com/netbox-community/netbox/issues/9637) - Add site group field to rack reservation form * [#9762](https://github.com/netbox-community/netbox/issues/9762) - Add `nat_outside` column to the IPAddress table * [#9825](https://github.com/netbox-community/netbox/issues/9825) - Add contacts column to virtual machines table +### Bug Fixes + +* [#9891](https://github.com/netbox-community/netbox/issues/9891) - Ensure consistent ordering for tags during object serialization + --- ## v3.2.7 (2022-07-20) diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 2b939471c..da7cdde94 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -148,7 +148,7 @@ def serialize_object(obj, extra=None): # Include any tags. Check for tags cached on the instance; fall back to using the manager. if is_taggable(obj): tags = getattr(obj, '_tags', None) or obj.tags.all() - data['tags'] = [tag.name for tag in tags] + data['tags'] = sorted([tag.name for tag in tags]) # Append any extra data if extra is not None: From 8a075bcff9b3a75ae86cd21eeb3921108594802f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 1 Aug 2022 09:47:18 -0400 Subject: [PATCH 11/18] Fixes #9884: Prevent querying assigned VRF on prefix object init --- docs/release-notes/version-3.2.md | 1 + netbox/ipam/models/ip.py | 2 +- netbox/ipam/signals.py | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index ed08ad891..caaf079b2 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -11,6 +11,7 @@ ### Bug Fixes +* [#9884](https://github.com/netbox-community/netbox/issues/9884) - Prevent querying assigned VRF on prefix object init * [#9891](https://github.com/netbox-community/netbox/issues/9891) - Ensure consistent ordering for tags during object serialization --- diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index a3b8fb2c1..d1538953a 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -373,7 +373,7 @@ class Prefix(GetAvailablePrefixesMixin, NetBoxModel): # Cache the original prefix and VRF so we can check if they have changed on post_save self._prefix = self.prefix - self._vrf = self.vrf + self._vrf_id = self.vrf_id def __str__(self): return str(self.prefix) diff --git a/netbox/ipam/signals.py b/netbox/ipam/signals.py index 3e8b86050..8555f5e67 100644 --- a/netbox/ipam/signals.py +++ b/netbox/ipam/signals.py @@ -30,14 +30,14 @@ def update_children_depth(prefix): def handle_prefix_saved(instance, created, **kwargs): # Prefix has changed (or new instance has been created) - if created or instance.vrf != instance._vrf or instance.prefix != instance._prefix: + if created or instance.vrf_id != instance._vrf_id or instance.prefix != instance._prefix: update_parents_children(instance) update_children_depth(instance) # If this is not a new prefix, clean up parent/children of previous prefix if not created: - old_prefix = Prefix(vrf=instance._vrf, prefix=instance._prefix) + old_prefix = Prefix(vrf_id=instance._vrf_id, prefix=instance._prefix) update_parents_children(old_prefix) update_children_depth(old_prefix) From 1bbf5d214b3c58d97c0c600ca2cf4c929f588e5e Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 1 Aug 2022 10:23:18 -0400 Subject: [PATCH 12/18] Closes #9881: Increase granularity in utilization graph values --- docs/release-notes/version-3.2.md | 1 + netbox/utilities/templates/helpers/utilization_graph.html | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index caaf079b2..b366d7c8f 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -8,6 +8,7 @@ * [#9637](https://github.com/netbox-community/netbox/issues/9637) - Add site group field to rack reservation form * [#9762](https://github.com/netbox-community/netbox/issues/9762) - Add `nat_outside` column to the IPAddress table * [#9825](https://github.com/netbox-community/netbox/issues/9825) - Add contacts column to virtual machines table +* [#9881](https://github.com/netbox-community/netbox/issues/9881) - Increase granularity in utilization graph values ### Bug Fixes diff --git a/netbox/utilities/templates/helpers/utilization_graph.html b/netbox/utilities/templates/helpers/utilization_graph.html index e6829befc..08e4e6f8a 100644 --- a/netbox/utilities/templates/helpers/utilization_graph.html +++ b/netbox/utilities/templates/helpers/utilization_graph.html @@ -12,10 +12,10 @@ class="progress-bar {{ bar_class }}" style="width: {{ utilization }}%;" > - {% if utilization >= 25 %}{{ utilization|floatformat:0 }}%{% endif %} + {% if utilization >= 25 %}{{ utilization|floatformat }}%{% endif %} {% if utilization < 25 %} - {{ utilization|floatformat:0 }}% + {{ utilization|floatformat }}% {% endif %} {% endif %} From 9646f88384360855fdc000156920eb3365d68d81 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 1 Aug 2022 10:28:21 -0400 Subject: [PATCH 13/18] Fixes #9885: Fix child prefix counts when editing/deleting aggregates in bulk --- docs/release-notes/version-3.2.md | 1 + netbox/ipam/views.py | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index b366d7c8f..8a57f4644 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -13,6 +13,7 @@ ### Bug Fixes * [#9884](https://github.com/netbox-community/netbox/issues/9884) - Prevent querying assigned VRF on prefix object init +* [#9885](https://github.com/netbox-community/netbox/issues/9885) - Fix child prefix counts when editing/deleting aggregates in bulk * [#9891](https://github.com/netbox-community/netbox/issues/9891) - Ensure consistent ordering for tags during object serialization --- diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 706670cad..9ae7cd4d7 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -333,14 +333,18 @@ class AggregateBulkImportView(generic.BulkImportView): class AggregateBulkEditView(generic.BulkEditView): - queryset = Aggregate.objects.prefetch_related('rir') + queryset = Aggregate.objects.annotate( + child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ()) + ) filterset = filtersets.AggregateFilterSet table = tables.AggregateTable form = forms.AggregateBulkEditForm class AggregateBulkDeleteView(generic.BulkDeleteView): - queryset = Aggregate.objects.prefetch_related('rir') + queryset = Aggregate.objects.annotate( + child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ()) + ) filterset = filtersets.AggregateFilterSet table = tables.AggregateTable From 3af989763e41fefdfbee1806c7e4c163e148db9b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 1 Aug 2022 10:32:05 -0400 Subject: [PATCH 14/18] Closes #9883: Linkify location column in power panels table --- docs/release-notes/version-3.2.md | 1 + netbox/dcim/tables/power.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 8a57f4644..e4c7d9268 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -9,6 +9,7 @@ * [#9762](https://github.com/netbox-community/netbox/issues/9762) - Add `nat_outside` column to the IPAddress table * [#9825](https://github.com/netbox-community/netbox/issues/9825) - Add contacts column to virtual machines table * [#9881](https://github.com/netbox-community/netbox/issues/9881) - Increase granularity in utilization graph values +* [#9883](https://github.com/netbox-community/netbox/issues/9883) - Linkify location column in power panels table ### Bug Fixes diff --git a/netbox/dcim/tables/power.py b/netbox/dcim/tables/power.py index 92c4bb0aa..6696d516a 100644 --- a/netbox/dcim/tables/power.py +++ b/netbox/dcim/tables/power.py @@ -21,6 +21,9 @@ class PowerPanelTable(NetBoxTable): site = tables.Column( linkify=True ) + location = tables.Column( + linkify=True + ) powerfeed_count = columns.LinkedCountColumn( viewname='dcim:powerfeed_list', url_params={'power_panel_id': 'pk'}, @@ -35,7 +38,9 @@ class PowerPanelTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = PowerPanel - fields = ('pk', 'id', 'name', 'site', 'location', 'powerfeed_count', 'contacts', 'tags', 'created', 'last_updated',) + fields = ( + 'pk', 'id', 'name', 'site', 'location', 'powerfeed_count', 'contacts', 'tags', 'created', 'last_updated', + ) default_columns = ('pk', 'name', 'site', 'location', 'powerfeed_count') From efa449faff99205a351553770e7e5186b487b754 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 1 Aug 2022 10:36:53 -0400 Subject: [PATCH 15/18] Closes #9882: Add manufacturer column to modules table --- docs/release-notes/version-3.2.md | 1 + netbox/dcim/tables/modules.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index e4c7d9268..64e229129 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -9,6 +9,7 @@ * [#9762](https://github.com/netbox-community/netbox/issues/9762) - Add `nat_outside` column to the IPAddress table * [#9825](https://github.com/netbox-community/netbox/issues/9825) - Add contacts column to virtual machines table * [#9881](https://github.com/netbox-community/netbox/issues/9881) - Increase granularity in utilization graph values +* [#9882](https://github.com/netbox-community/netbox/issues/9882) - Add manufacturer column to modules table * [#9883](https://github.com/netbox-community/netbox/issues/9883) - Linkify location column in power panels table ### Bug Fixes diff --git a/netbox/dcim/tables/modules.py b/netbox/dcim/tables/modules.py index 5b009e42e..e40d7bd80 100644 --- a/netbox/dcim/tables/modules.py +++ b/netbox/dcim/tables/modules.py @@ -14,6 +14,9 @@ class ModuleTypeTable(NetBoxTable): linkify=True, verbose_name='Module Type' ) + manufacturer = tables.Column( + linkify=True + ) instance_count = columns.LinkedCountColumn( viewname='dcim:module_list', url_params={'module_type_id': 'pk'}, @@ -41,6 +44,10 @@ class ModuleTable(NetBoxTable): module_bay = tables.Column( linkify=True ) + manufacturer = tables.Column( + accessor=tables.A('module_type__manufacturer'), + linkify=True + ) module_type = tables.Column( linkify=True ) @@ -52,8 +59,9 @@ class ModuleTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = Module fields = ( - 'pk', 'id', 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'comments', 'tags', + 'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', 'comments', + 'tags', ) default_columns = ( - 'pk', 'id', 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', + 'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', ) From 984d15d7fb637f7e5a73fa2138637d11a76f524b Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 1 Aug 2022 11:39:07 -0400 Subject: [PATCH 16/18] Closes #9893: Move YAML serialization logic for component templates under the individual models --- .../dcim/models/device_component_templates.py | 79 ++++++++ netbox/dcim/models/devices.py | 179 ++++-------------- 2 files changed, 112 insertions(+), 146 deletions(-) diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index ac0738b3f..f596cf28b 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -160,6 +160,14 @@ class ConsolePortTemplate(ModularComponentTemplateModel): **kwargs ) + def to_yaml(self): + return { + 'name': self.name, + 'type': self.type, + 'label': self.label, + 'description': self.description, + } + class ConsoleServerPortTemplate(ModularComponentTemplateModel): """ @@ -188,6 +196,14 @@ class ConsoleServerPortTemplate(ModularComponentTemplateModel): **kwargs ) + def to_yaml(self): + return { + 'name': self.name, + 'type': self.type, + 'label': self.label, + 'description': self.description, + } + class PowerPortTemplate(ModularComponentTemplateModel): """ @@ -239,6 +255,16 @@ class PowerPortTemplate(ModularComponentTemplateModel): 'allocated_draw': f"Allocated draw cannot exceed the maximum draw ({self.maximum_draw}W)." }) + def to_yaml(self): + return { + 'name': self.name, + 'type': self.type, + 'maximum_draw': self.maximum_draw, + 'allocated_draw': self.allocated_draw, + 'label': self.label, + 'description': self.description, + } + class PowerOutletTemplate(ModularComponentTemplateModel): """ @@ -301,6 +327,16 @@ class PowerOutletTemplate(ModularComponentTemplateModel): **kwargs ) + def to_yaml(self): + return { + 'name': self.name, + 'type': self.type, + 'power_port': self.power_port.name if self.power_port else None, + 'feed_leg': self.feed_leg, + 'label': self.label, + 'description': self.description, + } + class InterfaceTemplate(ModularComponentTemplateModel): """ @@ -340,6 +376,15 @@ class InterfaceTemplate(ModularComponentTemplateModel): **kwargs ) + def to_yaml(self): + return { + 'name': self.name, + 'type': self.type, + 'mgmt_only': self.mgmt_only, + 'label': self.label, + 'description': self.description, + } + class FrontPortTemplate(ModularComponentTemplateModel): """ @@ -413,6 +458,16 @@ class FrontPortTemplate(ModularComponentTemplateModel): **kwargs ) + def to_yaml(self): + return { + 'name': self.name, + 'type': self.type, + 'rear_port': self.rear_port.name, + 'rear_port_position': self.rear_port_position, + 'label': self.label, + 'description': self.description, + } + class RearPortTemplate(ModularComponentTemplateModel): """ @@ -452,6 +507,15 @@ class RearPortTemplate(ModularComponentTemplateModel): **kwargs ) + def to_yaml(self): + return { + 'name': self.name, + 'type': self.type, + 'positions': self.positions, + 'label': self.label, + 'description': self.description, + } + class ModuleBayTemplate(ComponentTemplateModel): """ @@ -477,6 +541,14 @@ class ModuleBayTemplate(ComponentTemplateModel): position=self.position ) + def to_yaml(self): + return { + 'name': self.name, + 'label': self.label, + 'position': self.position, + 'description': self.description, + } + class DeviceBayTemplate(ComponentTemplateModel): """ @@ -501,6 +573,13 @@ class DeviceBayTemplate(ComponentTemplateModel): f"Subdevice role of device type ({self.device_type}) must be set to \"parent\" to allow device bays." ) + def to_yaml(self): + return { + 'name': self.name, + 'label': self.label, + 'description': self.description, + } + class InventoryItemTemplate(MPTTModel, ComponentTemplateModel): """ diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index e88af2d05..91227f1cf 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import yaml from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError @@ -161,115 +159,54 @@ class DeviceType(NetBoxModel): return reverse('dcim:devicetype', args=[self.pk]) def to_yaml(self): - data = OrderedDict(( - ('manufacturer', self.manufacturer.name), - ('model', self.model), - ('slug', self.slug), - ('part_number', self.part_number), - ('u_height', self.u_height), - ('is_full_depth', self.is_full_depth), - ('subdevice_role', self.subdevice_role), - ('airflow', self.airflow), - ('comments', self.comments), - )) + data = { + 'manufacturer': self.manufacturer.name, + 'model': self.model, + 'slug': self.slug, + 'part_number': self.part_number, + 'u_height': self.u_height, + 'is_full_depth': self.is_full_depth, + 'subdevice_role': self.subdevice_role, + 'airflow': self.airflow, + 'comments': self.comments, + } # Component templates if self.consoleporttemplates.exists(): data['console-ports'] = [ - { - 'name': c.name, - 'type': c.type, - 'label': c.label, - 'description': c.description, - } - for c in self.consoleporttemplates.all() + c.to_yaml() for c in self.consoleporttemplates.all() ] if self.consoleserverporttemplates.exists(): data['console-server-ports'] = [ - { - 'name': c.name, - 'type': c.type, - 'label': c.label, - 'description': c.description, - } - for c in self.consoleserverporttemplates.all() + c.to_yaml() for c in self.consoleserverporttemplates.all() ] if self.powerporttemplates.exists(): data['power-ports'] = [ - { - 'name': c.name, - 'type': c.type, - 'maximum_draw': c.maximum_draw, - 'allocated_draw': c.allocated_draw, - 'label': c.label, - 'description': c.description, - } - for c in self.powerporttemplates.all() + c.to_yaml() for c in self.powerporttemplates.all() ] if self.poweroutlettemplates.exists(): data['power-outlets'] = [ - { - 'name': c.name, - 'type': c.type, - 'power_port': c.power_port.name if c.power_port else None, - 'feed_leg': c.feed_leg, - 'label': c.label, - 'description': c.description, - } - for c in self.poweroutlettemplates.all() + c.to_yaml() for c in self.poweroutlettemplates.all() ] if self.interfacetemplates.exists(): data['interfaces'] = [ - { - 'name': c.name, - 'type': c.type, - 'mgmt_only': c.mgmt_only, - 'label': c.label, - 'description': c.description, - } - for c in self.interfacetemplates.all() + c.to_yaml() for c in self.interfacetemplates.all() ] if self.frontporttemplates.exists(): data['front-ports'] = [ - { - 'name': c.name, - 'type': c.type, - 'rear_port': c.rear_port.name, - 'rear_port_position': c.rear_port_position, - 'label': c.label, - 'description': c.description, - } - for c in self.frontporttemplates.all() + c.to_yaml() for c in self.frontporttemplates.all() ] if self.rearporttemplates.exists(): data['rear-ports'] = [ - { - 'name': c.name, - 'type': c.type, - 'positions': c.positions, - 'label': c.label, - 'description': c.description, - } - for c in self.rearporttemplates.all() + c.to_yaml() for c in self.rearporttemplates.all() ] if self.modulebaytemplates.exists(): data['module-bays'] = [ - { - 'name': c.name, - 'label': c.label, - 'position': c.position, - 'description': c.description, - } - for c in self.modulebaytemplates.all() + c.to_yaml() for c in self.modulebaytemplates.all() ] if self.devicebaytemplates.exists(): data['device-bays'] = [ - { - 'name': c.name, - 'label': c.label, - 'description': c.description, - } - for c in self.devicebaytemplates.all() + c.to_yaml() for c in self.devicebaytemplates.all() ] return yaml.dump(dict(data), sort_keys=False) @@ -395,91 +332,41 @@ class ModuleType(NetBoxModel): return reverse('dcim:moduletype', args=[self.pk]) def to_yaml(self): - data = OrderedDict(( - ('manufacturer', self.manufacturer.name), - ('model', self.model), - ('part_number', self.part_number), - ('comments', self.comments), - )) + data = { + 'manufacturer': self.manufacturer.name, + 'model': self.model, + 'part_number': self.part_number, + 'comments': self.comments, + } # Component templates if self.consoleporttemplates.exists(): data['console-ports'] = [ - { - 'name': c.name, - 'type': c.type, - 'label': c.label, - 'description': c.description, - } - for c in self.consoleporttemplates.all() + c.to_yaml() for c in self.consoleporttemplates.all() ] if self.consoleserverporttemplates.exists(): data['console-server-ports'] = [ - { - 'name': c.name, - 'type': c.type, - 'label': c.label, - 'description': c.description, - } - for c in self.consoleserverporttemplates.all() + c.to_yaml() for c in self.consoleserverporttemplates.all() ] if self.powerporttemplates.exists(): data['power-ports'] = [ - { - 'name': c.name, - 'type': c.type, - 'maximum_draw': c.maximum_draw, - 'allocated_draw': c.allocated_draw, - 'label': c.label, - 'description': c.description, - } - for c in self.powerporttemplates.all() + c.to_yaml() for c in self.powerporttemplates.all() ] if self.poweroutlettemplates.exists(): data['power-outlets'] = [ - { - 'name': c.name, - 'type': c.type, - 'power_port': c.power_port.name if c.power_port else None, - 'feed_leg': c.feed_leg, - 'label': c.label, - 'description': c.description, - } - for c in self.poweroutlettemplates.all() + c.to_yaml() for c in self.poweroutlettemplates.all() ] if self.interfacetemplates.exists(): data['interfaces'] = [ - { - 'name': c.name, - 'type': c.type, - 'mgmt_only': c.mgmt_only, - 'label': c.label, - 'description': c.description, - } - for c in self.interfacetemplates.all() + c.to_yaml() for c in self.interfacetemplates.all() ] if self.frontporttemplates.exists(): data['front-ports'] = [ - { - 'name': c.name, - 'type': c.type, - 'rear_port': c.rear_port.name, - 'rear_port_position': c.rear_port_position, - 'label': c.label, - 'description': c.description, - } - for c in self.frontporttemplates.all() + c.to_yaml() for c in self.frontporttemplates.all() ] if self.rearporttemplates.exists(): data['rear-ports'] = [ - { - 'name': c.name, - 'type': c.type, - 'positions': c.positions, - 'label': c.label, - 'description': c.description, - } - for c in self.rearporttemplates.all() + c.to_yaml() for c in self.rearporttemplates.all() ] return yaml.dump(dict(data), sort_keys=False) From d4d73674fced54570db111574dae0a080f89ce77 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 1 Aug 2022 11:54:39 -0400 Subject: [PATCH 17/18] Fixes #9871: Fix utilization graph value alignments --- docs/release-notes/version-3.2.md | 1 + .../templates/helpers/utilization_graph.html | 34 ++++++++----------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 64e229129..5e538890c 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -14,6 +14,7 @@ ### Bug Fixes +* [#9871](https://github.com/netbox-community/netbox/issues/9871) - Fix utilization graph value alignments * [#9884](https://github.com/netbox-community/netbox/issues/9884) - Prevent querying assigned VRF on prefix object init * [#9885](https://github.com/netbox-community/netbox/issues/9885) - Fix child prefix counts when editing/deleting aggregates in bulk * [#9891](https://github.com/netbox-community/netbox/issues/9891) - Ensure consistent ordering for tags during object serialization diff --git a/netbox/utilities/templates/helpers/utilization_graph.html b/netbox/utilities/templates/helpers/utilization_graph.html index 08e4e6f8a..2a1856abf 100644 --- a/netbox/utilities/templates/helpers/utilization_graph.html +++ b/netbox/utilities/templates/helpers/utilization_graph.html @@ -1,21 +1,15 @@ -{% if utilization == 0 %} -
- {{ utilization }}% +
+
+ {% if utilization >= 25 %}{{ utilization|floatformat:1 }}%{% endif %}
-{% else %} -
-
- {% if utilization >= 25 %}{{ utilization|floatformat }}%{% endif %} -
- {% if utilization < 25 %} - {{ utilization|floatformat }}% - {% endif %} -
-{% endif %} + {% if utilization < 25 %} + {{ utilization|floatformat:1 }}% + {% endif %} +
From ff3fcb8134c229033453afadcc1e315c52f3fd20 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 1 Aug 2022 12:38:12 -0400 Subject: [PATCH 18/18] #9871: Tweak display of utilization graph value --- netbox/utilities/templates/helpers/utilization_graph.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netbox/utilities/templates/helpers/utilization_graph.html b/netbox/utilities/templates/helpers/utilization_graph.html index 2a1856abf..967ac8a87 100644 --- a/netbox/utilities/templates/helpers/utilization_graph.html +++ b/netbox/utilities/templates/helpers/utilization_graph.html @@ -7,9 +7,9 @@ class="progress-bar {{ bar_class }}" style="width: {{ utilization }}%;" > - {% if utilization >= 25 %}{{ utilization|floatformat:1 }}%{% endif %} + {% if utilization >= 35 %}{{ utilization|floatformat:1 }}%{% endif %}
- {% if utilization < 25 %} + {% if utilization < 35 %} {{ utilization|floatformat:1 }}% {% endif %}