From 0c8d45f679806b21939f60700a8a70d47064ef0e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 4 Sep 2020 15:57:28 -0400 Subject: [PATCH 01/26] Post-release version bump --- netbox/netbox/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index c48db1493..8a8c47c75 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -16,7 +16,7 @@ from django.core.validators import URLValidator # Environment setup # -VERSION = '2.9.3' +VERSION = '2.9.4-dev' # Hostname HOSTNAME = platform.node() From 695e9ec5d7ce3915cf6caf8f4131b5dc1d116747 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 9 Sep 2020 09:52:57 -0400 Subject: [PATCH 02/26] Fixes #5111: Allow use of tuples when specifying ObjectVar query_params --- docs/release-notes/version-2.9.md | 8 ++++++++ netbox/utilities/forms/widgets.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index a3d500094..3d68b10ba 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -1,5 +1,13 @@ # NetBox v2.9 +## v2.9.4 (FUTURE) + +### Bug Fixes + +* [#5111](https://github.com/netbox-community/netbox/issues/5111) - Allow use of tuples when specifying ObjectVar `query_params` + +--- + ## v2.9.3 (2020-09-04) ### Enhancements diff --git a/netbox/utilities/forms/widgets.py b/netbox/utilities/forms/widgets.py index 9996f7d11..cd6fb0fbb 100644 --- a/netbox/utilities/forms/widgets.py +++ b/netbox/utilities/forms/widgets.py @@ -141,7 +141,7 @@ class APISelect(SelectWithDisabled): key = f'data-query-param-{name}' values = json.loads(self.attrs.get(key, '[]')) - if type(value) is list: + if type(value) in (list, tuple): values.extend([str(v) for v in value]) else: values.append(str(value)) From 150965046269b5d0f2145f7bf8447ba2e35e6ed1 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 9 Sep 2020 10:00:50 -0400 Subject: [PATCH 03/26] Closes #5107: Add note about dropping backward compatibility for old REDIS configuration format --- docs/release-notes/version-2.9.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 3d68b10ba..dbafa21ac 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -129,6 +129,7 @@ Two new REST API endpoints have been added to facilitate the retrieval and manip * If using NetBox's built-in remote authentication backend, update `REMOTE_AUTH_BACKEND` to `'netbox.authentication.RemoteUserBackend'`, as the authentication class has moved. * If using LDAP authentication, set `REMOTE_AUTH_BACKEND` to `'netbox.authentication.LDAPBackend'`. (LDAP configuration parameters in `ldap_config.py` remain unchanged.) * `REMOTE_AUTH_DEFAULT_PERMISSIONS` now takes a dictionary rather than a list. This is a mapping of permission names to a dictionary of constraining attributes, or `None`. For example, `['dcim.add_site', 'dcim.change_site']` would become `{'dcim.add_site': None, 'dcim.change_site': None}`. +* Backward compatibility for the old `webhooks` Redis queue name has been dropped. Ensure that your `REDIS` configuration parameter specifies both the `tasks` and `caching` databases. ### REST API Changes From c891f43b144c82e8d7891b57ca113d5f8f1f156a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 9 Sep 2020 10:29:17 -0400 Subject: [PATCH 04/26] Fixes #5050: Fix potential failure on 0016_replicate_interfaces schema migration from old release --- docs/release-notes/version-2.9.md | 1 + netbox/virtualization/migrations/0016_replicate_interfaces.py | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index dbafa21ac..a38e945f6 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -4,6 +4,7 @@ ### Bug Fixes +* [#5050](https://github.com/netbox-community/netbox/issues/5050) - Fix potential failure on `0016_replicate_interfaces` schema migration from old release * [#5111](https://github.com/netbox-community/netbox/issues/5111) - Allow use of tuples when specifying ObjectVar `query_params` --- diff --git a/netbox/virtualization/migrations/0016_replicate_interfaces.py b/netbox/virtualization/migrations/0016_replicate_interfaces.py index a9c474083..585f5b80c 100644 --- a/netbox/virtualization/migrations/0016_replicate_interfaces.py +++ b/netbox/virtualization/migrations/0016_replicate_interfaces.py @@ -83,6 +83,7 @@ def replicate_interfaces(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ + ('dcim', '0082_3569_interface_fields'), ('ipam', '0037_ipaddress_assignment'), ('virtualization', '0015_vminterface'), ] From 47a6fc19cab1a3f53f80855e1f6fa9288fc8cbaf Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 9 Sep 2020 11:47:21 -0400 Subject: [PATCH 05/26] Fixes #5109: Fix representation of custom choice field values for webhook data --- docs/release-notes/version-2.9.md | 1 + netbox/extras/api/customfields.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index a38e945f6..19789149b 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -5,6 +5,7 @@ ### Bug Fixes * [#5050](https://github.com/netbox-community/netbox/issues/5050) - Fix potential failure on `0016_replicate_interfaces` schema migration from old release +* [#5109](https://github.com/netbox-community/netbox/issues/5109) - Fix representation of custom choice field values for webhook data * [#5111](https://github.com/netbox-community/netbox/issues/5111) - Allow use of tuples when specifying ObjectVar `query_params` --- diff --git a/netbox/extras/api/customfields.py b/netbox/extras/api/customfields.py index f096fb4a6..ba59e529a 100644 --- a/netbox/extras/api/customfields.py +++ b/netbox/extras/api/customfields.py @@ -158,7 +158,7 @@ class CustomFieldModelSerializer(ValidatedModelSerializer): instance.custom_fields = {} for field in custom_fields: value = instance.cf.get(field.name) - if field.type == CustomFieldTypeChoices.TYPE_SELECT and value: + if field.type == CustomFieldTypeChoices.TYPE_SELECT and type(value) is CustomFieldChoice: instance.custom_fields[field.name] = CustomFieldChoiceSerializer(value).data else: instance.custom_fields[field.name] = value From 4466458076b4cfd1b3d97a234d7f34ef9e191e8c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 9 Sep 2020 13:43:10 -0400 Subject: [PATCH 06/26] Fixes #5118: Specifying an empty list of tags should clear assigned tags (REST API) --- docs/release-notes/version-2.9.md | 1 + netbox/extras/api/serializers.py | 16 +++++++++++----- netbox/extras/tests/test_tags.py | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 19789149b..33f372f47 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -7,6 +7,7 @@ * [#5050](https://github.com/netbox-community/netbox/issues/5050) - Fix potential failure on `0016_replicate_interfaces` schema migration from old release * [#5109](https://github.com/netbox-community/netbox/issues/5109) - Fix representation of custom choice field values for webhook data * [#5111](https://github.com/netbox-community/netbox/issues/5111) - Allow use of tuples when specifying ObjectVar `query_params` +* [#5118](https://github.com/netbox-community/netbox/issues/5118) - Specifying an empty list of tags should clear assigned tags (REST API) --- diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index aa8f6ba69..3cc2d1991 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -101,24 +101,30 @@ class TaggedObjectSerializer(serializers.Serializer): tags = NestedTagSerializer(many=True, required=False) def create(self, validated_data): - tags = validated_data.pop('tags', []) + tags = validated_data.pop('tags', None) instance = super().create(validated_data) - return self._save_tags(instance, tags) + if tags is not None: + return self._save_tags(instance, tags) + return instance def update(self, instance, validated_data): - tags = validated_data.pop('tags', []) + tags = validated_data.pop('tags', None) # Cache tags on instance for change logging - instance._tags = tags + instance._tags = tags or [] instance = super().update(instance, validated_data) - return self._save_tags(instance, tags) + if tags is not None: + return self._save_tags(instance, tags) + return instance def _save_tags(self, instance, tags): if tags: instance.tags.set(*[t.name for t in tags]) + else: + instance.tags.clear() return instance diff --git a/netbox/extras/tests/test_tags.py b/netbox/extras/tests/test_tags.py index 694cd77d9..39aae49dc 100644 --- a/netbox/extras/tests/test_tags.py +++ b/netbox/extras/tests/test_tags.py @@ -59,3 +59,21 @@ class TaggedItemTest(APITestCase): sorted([t.name for t in site.tags.all()]), sorted(["Foo", "Bar", "New Tag"]) ) + + def test_clear_tagged_item(self): + site = Site.objects.create( + name='Test Site', + slug='test-site' + ) + site.tags.add("Foo", "Bar", "Baz") + data = { + 'tags': [] + } + self.add_permissions('dcim.change_site') + url = reverse('dcim-api:site-detail', kwargs={'pk': site.pk}) + + response = self.client.patch(url, data, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(len(response.data['tags']), 0) + site = Site.objects.get(pk=response.data['id']) + self.assertEqual(len(site.tags.all()), 0) From c5e82a389545ae5516577e452ddd9cb32fc0a185 Mon Sep 17 00:00:00 2001 From: John Anderson Date: Thu, 10 Sep 2020 17:43:41 -0400 Subject: [PATCH 07/26] fixes #5108 - correct the runreport management command to work with JobResults model --- .../extras/management/commands/runreport.py | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/netbox/extras/management/commands/runreport.py b/netbox/extras/management/commands/runreport.py index efc789021..b751118e0 100644 --- a/netbox/extras/management/commands/runreport.py +++ b/netbox/extras/management/commands/runreport.py @@ -1,7 +1,12 @@ +import time + +from django.contrib.contenttypes.models import ContentType from django.core.management.base import BaseCommand from django.utils import timezone -from extras.reports import get_reports +from extras.choices import JobResultStatusChoices +from extras.models import JobResult +from extras.reports import get_reports, run_report class Command(BaseCommand): @@ -24,11 +29,29 @@ class Command(BaseCommand): self.stdout.write( "[{:%H:%M:%S}] Running {}...".format(timezone.now(), report.full_name) ) - report.run() + + report_content_type = ContentType.objects.get(app_label='extras', model='report') + job_result = JobResult.enqueue_job( + run_report, + report.full_name, + report_content_type, + None + ) + + # Wait on the job to finish + while job_result.status not in JobResultStatusChoices.TERMINAL_STATE_CHOICES: + time.sleep(1) + job_result = JobResult.objects.get(pk=job_result.pk) # Report on success/failure - status = self.style.ERROR('FAILED') if report.failed else self.style.SUCCESS('SUCCESS') - for test_name, attrs in report.result.data.items(): + if job_result.status == JobResultStatusChoices.STATUS_FAILED: + status = self.style.ERROR('FAILED') + elif job_result == JobResultStatusChoices.STATUS_ERRORED: + status = self.style.ERROR('ERRORED') + else: + status = self.style.SUCCESS('SUCCESS') + + for test_name, attrs in job_result.data.items(): self.stdout.write( "\t{}: {} success, {} info, {} warning, {} failure".format( test_name, attrs['success'], attrs['info'], attrs['warning'], attrs['failure'] @@ -37,6 +60,9 @@ class Command(BaseCommand): self.stdout.write( "[{:%H:%M:%S}] {}: {}".format(timezone.now(), report.full_name, status) ) + self.stdout.write( + "[{:%H:%M:%S}] {}: Duration {}".format(timezone.now(), report.full_name, job_result.duration) + ) # Wrap things up self.stdout.write( From b1b63513e79d6b253492edbadab5e92c217f4e41 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 14 Sep 2020 10:41:04 -0400 Subject: [PATCH 08/26] Changelog for #5108 --- docs/release-notes/version-2.9.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 33f372f47..d469e4c0f 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -6,6 +6,7 @@ * [#5050](https://github.com/netbox-community/netbox/issues/5050) - Fix potential failure on `0016_replicate_interfaces` schema migration from old release * [#5109](https://github.com/netbox-community/netbox/issues/5109) - Fix representation of custom choice field values for webhook data +* [#5108](https://github.com/netbox-community/netbox/issues/5108) - Fix execution of reports via CLI * [#5111](https://github.com/netbox-community/netbox/issues/5111) - Allow use of tuples when specifying ObjectVar `query_params` * [#5118](https://github.com/netbox-community/netbox/issues/5118) - Specifying an empty list of tags should clear assigned tags (REST API) From df6ad680ce300733157cb100f7627d45b1cb4db4 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 14 Sep 2020 14:22:21 -0400 Subject: [PATCH 09/26] Closes #1755: Toggle order in which rack elevations are displayed --- docs/release-notes/version-2.9.md | 4 ++++ netbox/dcim/views.py | 6 ++++++ netbox/templates/dcim/rack_elevation_list.html | 12 +++++++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index d469e4c0f..093dc34a7 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -2,6 +2,10 @@ ## v2.9.4 (FUTURE) +### Enhancements + +* [#1755](https://github.com/netbox-community/netbox/issues/1755) - Toggle order in which rack elevations are displayed + ### Bug Fixes * [#5050](https://github.com/netbox-community/netbox/issues/5050) - Fix potential failure on `0016_replicate_interfaces` schema migration from old release diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 9f4dcc90e..ab84ba64b 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -310,6 +310,11 @@ class RackElevationListView(ObjectListView): racks = filters.RackFilterSet(request.GET, self.queryset).qs total_count = racks.count() + # Determine ordering + reverse = bool(request.GET.get('reverse', False)) + if reverse: + racks = racks.reverse() + # Pagination per_page = request.GET.get('per_page', settings.PAGINATE_COUNT) page_number = request.GET.get('page', 1) @@ -330,6 +335,7 @@ class RackElevationListView(ObjectListView): 'paginator': paginator, 'page': page, 'total_count': total_count, + 'reverse': reverse, 'rack_face': rack_face, 'filter_form': forms.RackElevationFilterForm(request.GET), }) diff --git a/netbox/templates/dcim/rack_elevation_list.html b/netbox/templates/dcim/rack_elevation_list.html index e4949eb17..c39bcae75 100644 --- a/netbox/templates/dcim/rack_elevation_list.html +++ b/netbox/templates/dcim/rack_elevation_list.html @@ -3,12 +3,18 @@ {% load static %} {% block content %} -
+

{% block title %}Rack Elevations{% endblock %}

From 03b207d1542e8ede4fd29035bb41b640f4d018d9 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 16 Sep 2020 11:10:30 -0400 Subject: [PATCH 10/26] Fixes #5105: Validation should fail when reassigning a primary IP from device to VM --- docs/release-notes/version-2.9.md | 1 + netbox/ipam/forms.py | 13 ++++--------- netbox/ipam/models.py | 22 +++++----------------- 3 files changed, 10 insertions(+), 26 deletions(-) diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 093dc34a7..6d4f56ed8 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -9,6 +9,7 @@ ### Bug Fixes * [#5050](https://github.com/netbox-community/netbox/issues/5050) - Fix potential failure on `0016_replicate_interfaces` schema migration from old release +* [#5105](https://github.com/netbox-community/netbox/issues/5105) - Validation should fail when reassigning a primary IP from device to VM * [#5109](https://github.com/netbox-community/netbox/issues/5109) - Fix representation of custom choice field values for webhook data * [#5108](https://github.com/netbox-community/netbox/issues/5108) - Fix execution of reports via CLI * [#5111](https://github.com/netbox-community/netbox/issues/5111) - Allow use of tuples when specifying ObjectVar `query_params` diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 1e8e9038a..75a4caf10 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -641,11 +641,11 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel self.initial['primary_for_parent'] = True def clean(self): - super().clean() # Cannot select both a device interface and a VM interface if self.cleaned_data.get('interface') and self.cleaned_data.get('vminterface'): raise forms.ValidationError("Cannot select both a device interface and a virtual machine interface") + self.instance.assigned_object = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface') # Primary IP assignment is only available if an interface has been assigned. interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface') @@ -655,26 +655,21 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldModel ) def save(self, *args, **kwargs): - - # Set assigned object - interface = self.cleaned_data.get('interface') or self.cleaned_data.get('vminterface') - if interface: - self.instance.assigned_object = interface - ipaddress = super().save(*args, **kwargs) # Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine. + interface = self.instance.assigned_object if interface and self.cleaned_data['primary_for_parent']: if ipaddress.address.version == 4: interface.parent.primary_ip4 = ipaddress else: - interface.primary_ip6 = ipaddress + interface.parent.primary_ip6 = ipaddress interface.parent.save() elif interface and ipaddress.address.version == 4 and interface.parent.primary_ip4 == ipaddress: interface.parent.primary_ip4 = None interface.parent.save() elif interface and ipaddress.address.version == 6 and interface.parent.primary_ip6 == ipaddress: - interface.parent.primary_ip4 = None + interface.parent.primary_ip6 = None interface.parent.save() return ipaddress diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 832e09330..c7955bcfa 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -726,30 +726,18 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): }) # Check for primary IP assignment that doesn't match the assigned device/VM - if self.pk and type(self.assigned_object) is Interface: + if self.pk: device = Device.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first() if device: - if self.assigned_object is None: + if getattr(self.assigned_object, 'device', None) != device: raise ValidationError({ - 'interface': f"IP address is primary for device {device} but not assigned to an interface" + 'interface': f"IP address is primary for device {device} but not assigned to it!" }) - elif self.assigned_object.device != device: - raise ValidationError({ - 'interface': f"IP address is primary for device {device} but assigned to " - f"{self.assigned_object.device} ({self.assigned_object})" - }) - elif self.pk and type(self.assigned_object) is VMInterface: vm = VirtualMachine.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first() if vm: - if self.assigned_object is None: + if getattr(self.assigned_object, 'virtual_machine', None) != vm: raise ValidationError({ - 'vminterface': f"IP address is primary for virtual machine {vm} but not assigned to an " - f"interface" - }) - elif self.assigned_object.virtual_machine != vm: - raise ValidationError({ - 'vminterface': f"IP address is primary for virtual machine {vm} but assigned to " - f"{self.assigned_object.virtual_machine} ({self.assigned_object})" + 'vminterface': f"IP address is primary for virtual machine {vm} but not assigned to it!" }) # Validate IP status selection From 9d30712fb26503092e2ecd261c348f908213580b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 16 Sep 2020 11:20:00 -0400 Subject: [PATCH 11/26] Changelog for #5133 (fixed in #5105) --- docs/release-notes/version-2.9.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 6d4f56ed8..55c66cb6a 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -14,6 +14,7 @@ * [#5108](https://github.com/netbox-community/netbox/issues/5108) - Fix execution of reports via CLI * [#5111](https://github.com/netbox-community/netbox/issues/5111) - Allow use of tuples when specifying ObjectVar `query_params` * [#5118](https://github.com/netbox-community/netbox/issues/5118) - Specifying an empty list of tags should clear assigned tags (REST API) +* [#5133](https://github.com/netbox-community/netbox/issues/5133) - Fix disassociation of an IP address from a VM interface --- From 4d9da4a1f8c261e4aa6dae04a3cbcfb20a443c1f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 16 Sep 2020 12:44:49 -0400 Subject: [PATCH 12/26] Closes #5134: Display full hierarchy in breadcrumbs for sites/racks --- docs/release-notes/version-2.9.md | 1 + netbox/templates/dcim/device.html | 15 +++++++-------- netbox/templates/dcim/rack.html | 11 ++++++++++- netbox/templates/dcim/site.html | 4 ++-- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 55c66cb6a..fd7a99ff0 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -5,6 +5,7 @@ ### Enhancements * [#1755](https://github.com/netbox-community/netbox/issues/1755) - Toggle order in which rack elevations are displayed +* [#5134](https://github.com/netbox-community/netbox/issues/5134) - Display full hierarchy in breadcrumbs for sites/racks ### Bug Fixes diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index e97893c30..34d28179b 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -11,11 +11,8 @@
@@ -87,7 +93,10 @@ Group {% if rack.group %} - {{ rack.group }} + {% for group in rack.group.get_ancestors %} + {{ group }} + {% endfor %} + {{ rack.group }} {% else %} None {% endif %} diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html index d6c21bf92..f5823f721 100644 --- a/netbox/templates/dcim/site.html +++ b/netbox/templates/dcim/site.html @@ -12,7 +12,7 @@