diff --git a/netbox/dcim/ui/panels.py b/netbox/dcim/ui/panels.py index 56a557acb..c7aa36a23 100644 --- a/netbox/dcim/ui/panels.py +++ b/netbox/dcim/ui/panels.py @@ -1,10 +1,16 @@ from django.utils.translation import gettext_lazy as _ -from netbox.ui import attrs -from netbox.ui.components import ObjectPanel +from netbox.ui import attrs, components -class DevicePanel(ObjectPanel): +class LocationPanel(components.NestedGroupObjectPanel): + site = attrs.ObjectAttr('site', label=_('Site'), linkify=True, grouped_by='group') + status = attrs.ChoiceAttr('status', label=_('Status')) + tenant = attrs.ObjectAttr('tenant', label=_('Tenant'), linkify=True, grouped_by='group') + facility = attrs.TextAttr('facility', label=_('Facility')) + + +class DevicePanel(components.ObjectPanel): region = attrs.NestedObjectAttr('site.region', label=_('Region'), linkify=True) site = attrs.ObjectAttr('site', label=_('Site'), linkify=True, grouped_by='group') location = attrs.NestedObjectAttr('location', label=_('Location'), linkify=True) @@ -25,7 +31,7 @@ class DevicePanel(ObjectPanel): config_template = attrs.ObjectAttr('config_template', label=_('Config template'), linkify=True) -class DeviceManagementPanel(ObjectPanel): +class DeviceManagementPanel(components.ObjectPanel): status = attrs.ChoiceAttr('status', label=_('Status')) role = attrs.NestedObjectAttr('role', label=_('Role'), linkify=True, max_depth=3) platform = attrs.NestedObjectAttr('platform', label=_('Platform'), linkify=True, max_depth=3) @@ -46,7 +52,7 @@ class DeviceManagementPanel(ObjectPanel): ) -class SitePanel(ObjectPanel): +class SitePanel(components.ObjectPanel): region = attrs.NestedObjectAttr('region', label=_('Region'), linkify=True) group = attrs.NestedObjectAttr('group', label=_('Group'), linkify=True) status = attrs.ChoiceAttr('status', label=_('Status')) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index e32a59a0b..eb0e42ee1 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -571,6 +571,7 @@ class LocationView(GetRelatedModelsMixin, generic.ObjectView): locations = instance.get_descendants(include_self=True) location_content_type = ContentType.objects.get_for_model(instance) return { + 'location_panel': panels.LocationPanel(instance, _('Location')), 'related_models': self.get_related_models( request, locations, diff --git a/netbox/netbox/ui/components.py b/netbox/netbox/ui/components.py index 206086f17..156eb0304 100644 --- a/netbox/netbox/ui/components.py +++ b/netbox/netbox/ui/components.py @@ -21,13 +21,29 @@ class Component(ABC): class ObjectDetailsPanelMeta(ABCMeta): - def __new__(mcls, name, bases, attrs): - # Collect all declared attributes - attrs['_attrs'] = {} - for key, val in list(attrs.items()): - if isinstance(val, Attr): - attrs['_attrs'][key] = val - return super().__new__(mcls, name, bases, attrs) + def __new__(mcls, name, bases, namespace, **kwargs): + declared = {} + + # Walk MRO parents (excluding `object`) for declared attributes + for base in reversed([b for b in bases if hasattr(b, "_attrs")]): + for key, attr in getattr(base, '_attrs', {}).items(): + if key not in declared: + declared[key] = attr + + # Add local declarations in the order they appear in the class body + for key, attr in namespace.items(): + if isinstance(attr, Attr): + declared[key] = attr + + namespace['_attrs'] = declared + + # Remove Attrs from the class namespace to keep things tidy + local_items = [key for key, attr in namespace.items() if isinstance(attr, Attr)] + for key in local_items: + namespace.pop(key) + + cls = super().__new__(mcls, name, bases, namespace, **kwargs) + return cls class ObjectPanel(Component, metaclass=ObjectDetailsPanelMeta): @@ -56,7 +72,7 @@ class ObjectPanel(Component, metaclass=ObjectDetailsPanelMeta): return self.render() -class NestedGroupObjectPanel(ObjectPanel): +class NestedGroupObjectPanel(ObjectPanel, metaclass=ObjectDetailsPanelMeta): name = attrs.TextAttr('name', label=_('Name')) description = attrs.TextAttr('description', label=_('Description')) parent = attrs.NestedObjectAttr('parent', label=_('Parent'), linkify=True) diff --git a/netbox/templates/dcim/location.html b/netbox/templates/dcim/location.html index dfd0c32b3..861a2adef 100644 --- a/netbox/templates/dcim/location.html +++ b/netbox/templates/dcim/location.html @@ -22,44 +22,7 @@ {% block content %}
-
-

{% trans "Location" %}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Name" %}{{ object.name }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Site" %}{{ object.site|linkify }}
{% trans "Parent" %}{{ object.parent|linkify|placeholder }}
{% trans "Status" %}{% badge object.get_status_display bg_color=object.get_status_color %}
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|placeholder }} -
{% trans "Facility" %}{{ object.facility|placeholder }}
-
+ {{ location_panel }} {% include 'inc/panels/tags.html' %} {% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/comments.html' %}