mirror of
https://github.com/netbox-community/netbox.git
synced 2026-02-04 14:26:25 -06:00
Enable panel inheritance; add location panel
This commit is contained in:
@@ -1,10 +1,16 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.ui import attrs
|
from netbox.ui import attrs, components
|
||||||
from netbox.ui.components import ObjectPanel
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
region = attrs.NestedObjectAttr('site.region', label=_('Region'), linkify=True)
|
||||||
site = attrs.ObjectAttr('site', label=_('Site'), linkify=True, grouped_by='group')
|
site = attrs.ObjectAttr('site', label=_('Site'), linkify=True, grouped_by='group')
|
||||||
location = attrs.NestedObjectAttr('location', label=_('Location'), linkify=True)
|
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)
|
config_template = attrs.ObjectAttr('config_template', label=_('Config template'), linkify=True)
|
||||||
|
|
||||||
|
|
||||||
class DeviceManagementPanel(ObjectPanel):
|
class DeviceManagementPanel(components.ObjectPanel):
|
||||||
status = attrs.ChoiceAttr('status', label=_('Status'))
|
status = attrs.ChoiceAttr('status', label=_('Status'))
|
||||||
role = attrs.NestedObjectAttr('role', label=_('Role'), linkify=True, max_depth=3)
|
role = attrs.NestedObjectAttr('role', label=_('Role'), linkify=True, max_depth=3)
|
||||||
platform = attrs.NestedObjectAttr('platform', label=_('Platform'), 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)
|
region = attrs.NestedObjectAttr('region', label=_('Region'), linkify=True)
|
||||||
group = attrs.NestedObjectAttr('group', label=_('Group'), linkify=True)
|
group = attrs.NestedObjectAttr('group', label=_('Group'), linkify=True)
|
||||||
status = attrs.ChoiceAttr('status', label=_('Status'))
|
status = attrs.ChoiceAttr('status', label=_('Status'))
|
||||||
|
|||||||
@@ -571,6 +571,7 @@ class LocationView(GetRelatedModelsMixin, generic.ObjectView):
|
|||||||
locations = instance.get_descendants(include_self=True)
|
locations = instance.get_descendants(include_self=True)
|
||||||
location_content_type = ContentType.objects.get_for_model(instance)
|
location_content_type = ContentType.objects.get_for_model(instance)
|
||||||
return {
|
return {
|
||||||
|
'location_panel': panels.LocationPanel(instance, _('Location')),
|
||||||
'related_models': self.get_related_models(
|
'related_models': self.get_related_models(
|
||||||
request,
|
request,
|
||||||
locations,
|
locations,
|
||||||
|
|||||||
@@ -21,13 +21,29 @@ class Component(ABC):
|
|||||||
|
|
||||||
class ObjectDetailsPanelMeta(ABCMeta):
|
class ObjectDetailsPanelMeta(ABCMeta):
|
||||||
|
|
||||||
def __new__(mcls, name, bases, attrs):
|
def __new__(mcls, name, bases, namespace, **kwargs):
|
||||||
# Collect all declared attributes
|
declared = {}
|
||||||
attrs['_attrs'] = {}
|
|
||||||
for key, val in list(attrs.items()):
|
# Walk MRO parents (excluding `object`) for declared attributes
|
||||||
if isinstance(val, Attr):
|
for base in reversed([b for b in bases if hasattr(b, "_attrs")]):
|
||||||
attrs['_attrs'][key] = val
|
for key, attr in getattr(base, '_attrs', {}).items():
|
||||||
return super().__new__(mcls, name, bases, attrs)
|
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):
|
class ObjectPanel(Component, metaclass=ObjectDetailsPanelMeta):
|
||||||
@@ -56,7 +72,7 @@ class ObjectPanel(Component, metaclass=ObjectDetailsPanelMeta):
|
|||||||
return self.render()
|
return self.render()
|
||||||
|
|
||||||
|
|
||||||
class NestedGroupObjectPanel(ObjectPanel):
|
class NestedGroupObjectPanel(ObjectPanel, metaclass=ObjectDetailsPanelMeta):
|
||||||
name = attrs.TextAttr('name', label=_('Name'))
|
name = attrs.TextAttr('name', label=_('Name'))
|
||||||
description = attrs.TextAttr('description', label=_('Description'))
|
description = attrs.TextAttr('description', label=_('Description'))
|
||||||
parent = attrs.NestedObjectAttr('parent', label=_('Parent'), linkify=True)
|
parent = attrs.NestedObjectAttr('parent', label=_('Parent'), linkify=True)
|
||||||
|
|||||||
@@ -22,44 +22,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col col-12 col-md-6">
|
<div class="col col-12 col-md-6">
|
||||||
<div class="card">
|
{{ location_panel }}
|
||||||
<h2 class="card-header">{% trans "Location" %}</h2>
|
|
||||||
<table class="table table-hover attr-table">
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Name" %}</th>
|
|
||||||
<td>{{ object.name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Description" %}</th>
|
|
||||||
<td>{{ object.description|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Site" %}</th>
|
|
||||||
<td>{{ object.site|linkify }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Parent" %}</th>
|
|
||||||
<td>{{ object.parent|linkify|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Status" %}</th>
|
|
||||||
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Tenant" %}</th>
|
|
||||||
<td>
|
|
||||||
{% if object.tenant.group %}
|
|
||||||
{{ object.tenant.group|linkify }} /
|
|
||||||
{% endif %}
|
|
||||||
{{ object.tenant|linkify|placeholder }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{% trans "Facility" %}</th>
|
|
||||||
<td>{{ object.facility|placeholder }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
{% include 'inc/panels/comments.html' %}
|
{% include 'inc/panels/comments.html' %}
|
||||||
|
|||||||
Reference in New Issue
Block a user