mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-16 13:29:37 -06:00
Misc cleanup
This commit is contained in:
parent
6fc04bd1fe
commit
a024012abd
@ -7,7 +7,7 @@ class SitePanel(panels.ObjectAttributesPanel):
|
|||||||
region = attrs.NestedObjectAttr('region', linkify=True)
|
region = attrs.NestedObjectAttr('region', linkify=True)
|
||||||
group = attrs.NestedObjectAttr('group', linkify=True)
|
group = attrs.NestedObjectAttr('group', linkify=True)
|
||||||
status = attrs.ChoiceAttr('status')
|
status = attrs.ChoiceAttr('status')
|
||||||
tenant = attrs.ObjectAttr('tenant', linkify=True, grouped_by='group')
|
tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
|
||||||
facility = attrs.TextAttr('facility')
|
facility = attrs.TextAttr('facility')
|
||||||
description = attrs.TextAttr('description')
|
description = attrs.TextAttr('description')
|
||||||
timezone = attrs.TimezoneAttr('time_zone')
|
timezone = attrs.TimezoneAttr('time_zone')
|
||||||
@ -17,9 +17,9 @@ class SitePanel(panels.ObjectAttributesPanel):
|
|||||||
|
|
||||||
|
|
||||||
class LocationPanel(panels.NestedGroupObjectPanel):
|
class LocationPanel(panels.NestedGroupObjectPanel):
|
||||||
site = attrs.ObjectAttr('site', linkify=True, grouped_by='group')
|
site = attrs.RelatedObjectAttr('site', linkify=True, grouped_by='group')
|
||||||
status = attrs.ChoiceAttr('status')
|
status = attrs.ChoiceAttr('status')
|
||||||
tenant = attrs.ObjectAttr('tenant', linkify=True, grouped_by='group')
|
tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
|
||||||
facility = attrs.TextAttr('facility')
|
facility = attrs.TextAttr('facility')
|
||||||
|
|
||||||
|
|
||||||
@ -40,13 +40,13 @@ class RackNumberingPanel(panels.ObjectAttributesPanel):
|
|||||||
|
|
||||||
class RackPanel(panels.ObjectAttributesPanel):
|
class RackPanel(panels.ObjectAttributesPanel):
|
||||||
region = attrs.NestedObjectAttr('site.region', linkify=True)
|
region = attrs.NestedObjectAttr('site.region', linkify=True)
|
||||||
site = attrs.ObjectAttr('site', linkify=True, grouped_by='group')
|
site = attrs.RelatedObjectAttr('site', linkify=True, grouped_by='group')
|
||||||
location = attrs.NestedObjectAttr('location', linkify=True)
|
location = attrs.NestedObjectAttr('location', linkify=True)
|
||||||
facility = attrs.TextAttr('facility')
|
facility = attrs.TextAttr('facility')
|
||||||
tenant = attrs.ObjectAttr('tenant', linkify=True, grouped_by='group')
|
tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
|
||||||
status = attrs.ChoiceAttr('status')
|
status = attrs.ChoiceAttr('status')
|
||||||
rack_type = attrs.ObjectAttr('rack_type', linkify=True, grouped_by='manufacturer')
|
rack_type = attrs.RelatedObjectAttr('rack_type', linkify=True, grouped_by='manufacturer')
|
||||||
role = attrs.ObjectAttr('role', linkify=True)
|
role = attrs.RelatedObjectAttr('role', linkify=True)
|
||||||
description = attrs.TextAttr('description')
|
description = attrs.TextAttr('description')
|
||||||
serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True)
|
serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True)
|
||||||
asset_tag = attrs.TextAttr('asset_tag', style='font-monospace', copy_button=True)
|
asset_tag = attrs.TextAttr('asset_tag', style='font-monospace', copy_button=True)
|
||||||
@ -66,26 +66,26 @@ class RackRolePanel(panels.OrganizationalObjectPanel):
|
|||||||
|
|
||||||
|
|
||||||
class RackTypePanel(panels.ObjectAttributesPanel):
|
class RackTypePanel(panels.ObjectAttributesPanel):
|
||||||
manufacturer = attrs.ObjectAttr('manufacturer', linkify=True)
|
manufacturer = attrs.RelatedObjectAttr('manufacturer', linkify=True)
|
||||||
model = attrs.TextAttr('model')
|
model = attrs.TextAttr('model')
|
||||||
description = attrs.TextAttr('description')
|
description = attrs.TextAttr('description')
|
||||||
|
|
||||||
|
|
||||||
class DevicePanel(panels.ObjectAttributesPanel):
|
class DevicePanel(panels.ObjectAttributesPanel):
|
||||||
region = attrs.NestedObjectAttr('site.region', linkify=True)
|
region = attrs.NestedObjectAttr('site.region', linkify=True)
|
||||||
site = attrs.ObjectAttr('site', linkify=True, grouped_by='group')
|
site = attrs.RelatedObjectAttr('site', linkify=True, grouped_by='group')
|
||||||
location = attrs.NestedObjectAttr('location', linkify=True)
|
location = attrs.NestedObjectAttr('location', linkify=True)
|
||||||
rack = attrs.TemplatedAttr('rack', template_name='dcim/device/attrs/rack.html')
|
rack = attrs.TemplatedAttr('rack', template_name='dcim/device/attrs/rack.html')
|
||||||
virtual_chassis = attrs.ObjectAttr('virtual_chassis', linkify=True)
|
virtual_chassis = attrs.RelatedObjectAttr('virtual_chassis', linkify=True)
|
||||||
parent_device = attrs.TemplatedAttr('parent_bay', template_name='dcim/device/attrs/parent_device.html')
|
parent_device = attrs.TemplatedAttr('parent_bay', template_name='dcim/device/attrs/parent_device.html')
|
||||||
gps_coordinates = attrs.GPSCoordinatesAttr()
|
gps_coordinates = attrs.GPSCoordinatesAttr()
|
||||||
tenant = attrs.ObjectAttr('tenant', linkify=True, grouped_by='group')
|
tenant = attrs.RelatedObjectAttr('tenant', linkify=True, grouped_by='group')
|
||||||
device_type = attrs.ObjectAttr('device_type', linkify=True, grouped_by='manufacturer')
|
device_type = attrs.RelatedObjectAttr('device_type', linkify=True, grouped_by='manufacturer')
|
||||||
description = attrs.TextAttr('description')
|
description = attrs.TextAttr('description')
|
||||||
airflow = attrs.ChoiceAttr('airflow')
|
airflow = attrs.ChoiceAttr('airflow')
|
||||||
serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True)
|
serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True)
|
||||||
asset_tag = attrs.TextAttr('asset_tag', style='font-monospace', copy_button=True)
|
asset_tag = attrs.TextAttr('asset_tag', style='font-monospace', copy_button=True)
|
||||||
config_template = attrs.ObjectAttr('config_template', linkify=True)
|
config_template = attrs.RelatedObjectAttr('config_template', linkify=True)
|
||||||
|
|
||||||
|
|
||||||
class DeviceManagementPanel(panels.ObjectAttributesPanel):
|
class DeviceManagementPanel(panels.ObjectAttributesPanel):
|
||||||
@ -109,7 +109,7 @@ class DeviceManagementPanel(panels.ObjectAttributesPanel):
|
|||||||
label=_('Out-of-band IP'),
|
label=_('Out-of-band IP'),
|
||||||
template_name='dcim/device/attrs/ipaddress.html',
|
template_name='dcim/device/attrs/ipaddress.html',
|
||||||
)
|
)
|
||||||
cluster = attrs.ObjectAttr('cluster', linkify=True)
|
cluster = attrs.RelatedObjectAttr('cluster', linkify=True)
|
||||||
|
|
||||||
|
|
||||||
class DeviceDimensionsPanel(panels.ObjectAttributesPanel):
|
class DeviceDimensionsPanel(panels.ObjectAttributesPanel):
|
||||||
@ -120,10 +120,10 @@ class DeviceDimensionsPanel(panels.ObjectAttributesPanel):
|
|||||||
|
|
||||||
|
|
||||||
class DeviceTypePanel(panels.ObjectAttributesPanel):
|
class DeviceTypePanel(panels.ObjectAttributesPanel):
|
||||||
manufacturer = attrs.ObjectAttr('manufacturer', linkify=True)
|
manufacturer = attrs.RelatedObjectAttr('manufacturer', linkify=True)
|
||||||
model = attrs.TextAttr('model')
|
model = attrs.TextAttr('model')
|
||||||
part_number = attrs.TextAttr('part_number')
|
part_number = attrs.TextAttr('part_number')
|
||||||
default_platform = attrs.ObjectAttr('default_platform', linkify=True)
|
default_platform = attrs.RelatedObjectAttr('default_platform', linkify=True)
|
||||||
description = attrs.TextAttr('description')
|
description = attrs.TextAttr('description')
|
||||||
height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height'))
|
height = attrs.TextAttr('u_height', format_string='{}U', label=_('Height'))
|
||||||
exclude_from_utilization = attrs.BooleanAttr('exclude_from_utilization')
|
exclude_from_utilization = attrs.BooleanAttr('exclude_from_utilization')
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from django.template.loader import render_to_string
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.ui import actions, panels
|
from netbox.ui import actions, panels
|
||||||
|
from utilities.data import resolve_attr_path
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CustomFieldsPanel',
|
'CustomFieldsPanel',
|
||||||
@ -13,13 +14,13 @@ __all__ = (
|
|||||||
|
|
||||||
class CustomFieldsPanel(panels.ObjectPanel):
|
class CustomFieldsPanel(panels.ObjectPanel):
|
||||||
"""
|
"""
|
||||||
Render a panel showing the value of all custom fields defined on the object.
|
A panel showing the value of all custom fields defined on an object.
|
||||||
"""
|
"""
|
||||||
template_name = 'extras/panels/custom_fields.html'
|
template_name = 'extras/panels/custom_fields.html'
|
||||||
title = _('Custom Fields')
|
title = _('Custom Fields')
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
obj = context['object']
|
obj = resolve_attr_path(context, self.accessor)
|
||||||
return {
|
return {
|
||||||
**super().get_context(context),
|
**super().get_context(context),
|
||||||
'custom_fields': obj.get_custom_fields_by_group(),
|
'custom_fields': obj.get_custom_fields_by_group(),
|
||||||
@ -35,7 +36,7 @@ class CustomFieldsPanel(panels.ObjectPanel):
|
|||||||
|
|
||||||
class ImageAttachmentsPanel(panels.ObjectsTablePanel):
|
class ImageAttachmentsPanel(panels.ObjectsTablePanel):
|
||||||
"""
|
"""
|
||||||
Render a table listing all images attached to the object.
|
A panel showing all images attached to the object.
|
||||||
"""
|
"""
|
||||||
actions = [
|
actions = [
|
||||||
actions.AddObject(
|
actions.AddObject(
|
||||||
@ -55,7 +56,7 @@ class ImageAttachmentsPanel(panels.ObjectsTablePanel):
|
|||||||
|
|
||||||
class TagsPanel(panels.ObjectPanel):
|
class TagsPanel(panels.ObjectPanel):
|
||||||
"""
|
"""
|
||||||
Render a panel showing the tags assigned to the object.
|
A panel showing the tags assigned to the object.
|
||||||
"""
|
"""
|
||||||
template_name = 'extras/panels/tags.html'
|
template_name = 'extras/panels/tags.html'
|
||||||
title = _('Tags')
|
title = _('Tags')
|
||||||
@ -63,5 +64,5 @@ class TagsPanel(panels.ObjectPanel):
|
|||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
return {
|
return {
|
||||||
**super().get_context(context),
|
**super().get_context(context),
|
||||||
'object': context['object'],
|
'object': resolve_attr_path(context, self.accessor),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,25 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from utilities.data import resolve_attr_path
|
from utilities.data import resolve_attr_path
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'AddressAttr',
|
||||||
|
'BooleanAttr',
|
||||||
|
'ColorAttr',
|
||||||
|
'ChoiceAttr',
|
||||||
|
'GPSCoordinatesAttr',
|
||||||
|
'ImageAttr',
|
||||||
|
'NestedObjectAttr',
|
||||||
|
'NumericAttr',
|
||||||
|
'ObjectAttribute',
|
||||||
|
'RelatedObjectAttr',
|
||||||
|
'TemplatedAttr',
|
||||||
|
'TextAttr',
|
||||||
|
'TimezoneAttr',
|
||||||
|
'UtilizationAttr',
|
||||||
|
)
|
||||||
|
|
||||||
|
PLACEHOLDER_HTML = '<span class="text-muted">—</span>'
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Attributes
|
# Attributes
|
||||||
@ -21,20 +40,17 @@ class ObjectAttribute:
|
|||||||
"""
|
"""
|
||||||
template_name = None
|
template_name = None
|
||||||
label = None
|
label = None
|
||||||
placeholder = mark_safe('<span class="text-muted">—</span>')
|
placeholder = mark_safe(PLACEHOLDER_HTML)
|
||||||
|
|
||||||
def __init__(self, accessor, label=None, template_name=None):
|
def __init__(self, accessor, label=None):
|
||||||
"""
|
"""
|
||||||
Instantiate a new ObjectAttribute.
|
Instantiate a new ObjectAttribute.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||||
label: Human-friendly label for the rendered attribute
|
label: Human-friendly label for the rendered attribute
|
||||||
template_name: The name of the template to render
|
|
||||||
"""
|
"""
|
||||||
self.accessor = accessor
|
self.accessor = accessor
|
||||||
if template_name is not None:
|
|
||||||
self.template_name = template_name
|
|
||||||
if label is not None:
|
if label is not None:
|
||||||
self.label = label
|
self.label = label
|
||||||
|
|
||||||
@ -53,25 +69,42 @@ class ObjectAttribute:
|
|||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
obj: The object for which the attribute is being rendered
|
obj: The object for which the attribute is being rendered
|
||||||
context: The template context
|
context: The root template context
|
||||||
"""
|
"""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def render(self, obj, context):
|
def render(self, obj, context):
|
||||||
value = self.get_value(obj)
|
value = self.get_value(obj)
|
||||||
|
|
||||||
|
# If the value is empty, render a placeholder
|
||||||
if value in (None, ''):
|
if value in (None, ''):
|
||||||
return self.placeholder
|
return self.placeholder
|
||||||
context = self.get_context(obj, context)
|
|
||||||
return render_to_string(self.template_name, {
|
return render_to_string(self.template_name, {
|
||||||
**context,
|
**self.get_context(obj, context),
|
||||||
|
'name': context['name'],
|
||||||
'value': value,
|
'value': value,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class TextAttr(ObjectAttribute):
|
class TextAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
A text attribute.
|
||||||
|
"""
|
||||||
template_name = 'ui/attrs/text.html'
|
template_name = 'ui/attrs/text.html'
|
||||||
|
|
||||||
def __init__(self, *args, style=None, format_string=None, copy_button=False, **kwargs):
|
def __init__(self, *args, style=None, format_string=None, copy_button=False, **kwargs):
|
||||||
|
"""
|
||||||
|
Instantiate a new TextAttr.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||||
|
label: Human-friendly label for the rendered attribute
|
||||||
|
template_name: The name of the template to render
|
||||||
|
style: CSS class to apply to the rendered attribute
|
||||||
|
format_string: If specified, the value will be formatted using this string when rendering
|
||||||
|
copy_button: Set to True to include a copy-to-clipboard button
|
||||||
|
"""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.style = style
|
self.style = style
|
||||||
self.format_string = format_string
|
self.format_string = format_string
|
||||||
@ -81,7 +114,7 @@ class TextAttr(ObjectAttribute):
|
|||||||
value = resolve_attr_path(obj, self.accessor)
|
value = resolve_attr_path(obj, self.accessor)
|
||||||
# Apply format string (if any)
|
# Apply format string (if any)
|
||||||
if value and self.format_string:
|
if value and self.format_string:
|
||||||
value = self.format_string.format(value)
|
return self.format_string.format(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def get_context(self, obj, context):
|
def get_context(self, obj, context):
|
||||||
@ -92,9 +125,22 @@ class TextAttr(ObjectAttribute):
|
|||||||
|
|
||||||
|
|
||||||
class NumericAttr(ObjectAttribute):
|
class NumericAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
An integer or float attribute.
|
||||||
|
"""
|
||||||
template_name = 'ui/attrs/numeric.html'
|
template_name = 'ui/attrs/numeric.html'
|
||||||
|
|
||||||
def __init__(self, *args, unit_accessor=None, copy_button=False, **kwargs):
|
def __init__(self, *args, unit_accessor=None, copy_button=False, **kwargs):
|
||||||
|
"""
|
||||||
|
Instantiate a new NumericAttr.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||||
|
unit_accessor: Accessor for the unit of measurement to display alongside the value (if any)
|
||||||
|
copy_button: Set to True to include a copy-to-clipboard button
|
||||||
|
label: Human-friendly label for the rendered attribute
|
||||||
|
template_name: The name of the template to render
|
||||||
|
"""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.unit_accessor = unit_accessor
|
self.unit_accessor = unit_accessor
|
||||||
self.copy_button = copy_button
|
self.copy_button = copy_button
|
||||||
@ -108,6 +154,12 @@ class NumericAttr(ObjectAttribute):
|
|||||||
|
|
||||||
|
|
||||||
class ChoiceAttr(ObjectAttribute):
|
class ChoiceAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
A selection from a set of choices.
|
||||||
|
|
||||||
|
The class calls get_FOO_display() on the object to retrieve the human-friendly choice label. If a get_FOO_color()
|
||||||
|
method exists on the object, it will be used to render a background color for the attribute value.
|
||||||
|
"""
|
||||||
template_name = 'ui/attrs/choice.html'
|
template_name = 'ui/attrs/choice.html'
|
||||||
|
|
||||||
def get_value(self, obj):
|
def get_value(self, obj):
|
||||||
@ -127,9 +179,21 @@ class ChoiceAttr(ObjectAttribute):
|
|||||||
|
|
||||||
|
|
||||||
class BooleanAttr(ObjectAttribute):
|
class BooleanAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
A boolean attribute.
|
||||||
|
"""
|
||||||
template_name = 'ui/attrs/boolean.html'
|
template_name = 'ui/attrs/boolean.html'
|
||||||
|
|
||||||
def __init__(self, *args, display_false=True, **kwargs):
|
def __init__(self, *args, display_false=True, **kwargs):
|
||||||
|
"""
|
||||||
|
Instantiate a new BooleanAttr.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||||
|
display_false: If False, a placeholder will be rendered instead of the "False" indication
|
||||||
|
label: Human-friendly label for the rendered attribute
|
||||||
|
template_name: The name of the template to render
|
||||||
|
"""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.display_false = display_false
|
self.display_false = display_false
|
||||||
|
|
||||||
@ -141,18 +205,38 @@ class BooleanAttr(ObjectAttribute):
|
|||||||
|
|
||||||
|
|
||||||
class ColorAttr(ObjectAttribute):
|
class ColorAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
An RGB color value.
|
||||||
|
"""
|
||||||
template_name = 'ui/attrs/color.html'
|
template_name = 'ui/attrs/color.html'
|
||||||
label = _('Color')
|
label = _('Color')
|
||||||
|
|
||||||
|
|
||||||
class ImageAttr(ObjectAttribute):
|
class ImageAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
An attribute representing an image field on the model. Displays the uploaded image.
|
||||||
|
"""
|
||||||
template_name = 'ui/attrs/image.html'
|
template_name = 'ui/attrs/image.html'
|
||||||
|
|
||||||
|
|
||||||
class ObjectAttr(ObjectAttribute):
|
class RelatedObjectAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
An attribute representing a related object.
|
||||||
|
"""
|
||||||
template_name = 'ui/attrs/object.html'
|
template_name = 'ui/attrs/object.html'
|
||||||
|
|
||||||
def __init__(self, *args, linkify=None, grouped_by=None, **kwargs):
|
def __init__(self, *args, linkify=None, grouped_by=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Instantiate a new RelatedObjectAttr.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||||
|
linkify: If True, the rendered value will be hyperlinked to the related object's detail view
|
||||||
|
grouped_by: A second-order object to annotate alongside the related object; for example, an attribute
|
||||||
|
representing the dcim.Site model might specify grouped_by="region"
|
||||||
|
label: Human-friendly label for the rendered attribute
|
||||||
|
template_name: The name of the template to render
|
||||||
|
"""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.linkify = linkify
|
self.linkify = linkify
|
||||||
self.grouped_by = grouped_by
|
self.grouped_by = grouped_by
|
||||||
@ -167,9 +251,23 @@ class ObjectAttr(ObjectAttribute):
|
|||||||
|
|
||||||
|
|
||||||
class NestedObjectAttr(ObjectAttribute):
|
class NestedObjectAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
An attribute representing a related nested object. Similar to `RelatedObjectAttr`, but includes the ancestors of the
|
||||||
|
related object in the rendered output.
|
||||||
|
"""
|
||||||
template_name = 'ui/attrs/nested_object.html'
|
template_name = 'ui/attrs/nested_object.html'
|
||||||
|
|
||||||
def __init__(self, *args, linkify=None, max_depth=None, **kwargs):
|
def __init__(self, *args, linkify=None, max_depth=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Instantiate a new NestedObjectAttr. Shows a related object as well as its ancestors.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||||
|
linkify: If True, the rendered value will be hyperlinked to the related object's detail view
|
||||||
|
max_depth: Maximum number of ancestors to display (default: all)
|
||||||
|
label: Human-friendly label for the rendered attribute
|
||||||
|
template_name: The name of the template to render
|
||||||
|
"""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.linkify = linkify
|
self.linkify = linkify
|
||||||
self.max_depth = max_depth
|
self.max_depth = max_depth
|
||||||
@ -186,9 +284,21 @@ class NestedObjectAttr(ObjectAttribute):
|
|||||||
|
|
||||||
|
|
||||||
class AddressAttr(ObjectAttribute):
|
class AddressAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
A physical or mailing address.
|
||||||
|
"""
|
||||||
template_name = 'ui/attrs/address.html'
|
template_name = 'ui/attrs/address.html'
|
||||||
|
|
||||||
def __init__(self, *args, map_url=True, **kwargs):
|
def __init__(self, *args, map_url=True, **kwargs):
|
||||||
|
"""
|
||||||
|
Instantiate a new AddressAttr.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||||
|
map_url: If true, the address will render as a hyperlink using settings.MAPS_URL
|
||||||
|
label: Human-friendly label for the rendered attribute
|
||||||
|
template_name: The name of the template to render
|
||||||
|
"""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if map_url is True:
|
if map_url is True:
|
||||||
self.map_url = get_config().MAPS_URL
|
self.map_url = get_config().MAPS_URL
|
||||||
@ -204,10 +314,23 @@ class AddressAttr(ObjectAttribute):
|
|||||||
|
|
||||||
|
|
||||||
class GPSCoordinatesAttr(ObjectAttribute):
|
class GPSCoordinatesAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
A GPS coordinates pair comprising latitude and longitude values.
|
||||||
|
"""
|
||||||
template_name = 'ui/attrs/gps_coordinates.html'
|
template_name = 'ui/attrs/gps_coordinates.html'
|
||||||
label = _('GPS coordinates')
|
label = _('GPS coordinates')
|
||||||
|
|
||||||
def __init__(self, latitude_attr='latitude', longitude_attr='longitude', map_url=True, **kwargs):
|
def __init__(self, latitude_attr='latitude', longitude_attr='longitude', map_url=True, **kwargs):
|
||||||
|
"""
|
||||||
|
Instantiate a new GPSCoordinatesAttr.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
latitude_attr: The name of the field containing the latitude value
|
||||||
|
longitude_attr: The name of the field containing the longitude value
|
||||||
|
map_url: If true, the address will render as a hyperlink using settings.MAPS_URL
|
||||||
|
label: Human-friendly label for the rendered attribute
|
||||||
|
template_name: The name of the template to render
|
||||||
|
"""
|
||||||
super().__init__(accessor=None, **kwargs)
|
super().__init__(accessor=None, **kwargs)
|
||||||
self.latitude_attr = latitude_attr
|
self.latitude_attr = latitude_attr
|
||||||
self.longitude_attr = longitude_attr
|
self.longitude_attr = longitude_attr
|
||||||
@ -233,13 +356,29 @@ class GPSCoordinatesAttr(ObjectAttribute):
|
|||||||
|
|
||||||
|
|
||||||
class TimezoneAttr(ObjectAttribute):
|
class TimezoneAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
A timezone value. Includes the numeric offset from UTC.
|
||||||
|
"""
|
||||||
template_name = 'ui/attrs/timezone.html'
|
template_name = 'ui/attrs/timezone.html'
|
||||||
|
|
||||||
|
|
||||||
class TemplatedAttr(ObjectAttribute):
|
class TemplatedAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
Renders an attribute using a custom template.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, template_name, context=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Instantiate a new TemplatedAttr.
|
||||||
|
|
||||||
def __init__(self, *args, context=None, **kwargs):
|
Parameters:
|
||||||
|
accessor: The dotted path to the attribute being rendered (e.g. "site.region.name")
|
||||||
|
template_name: The name of the template to render
|
||||||
|
context: Additional context to pass to the template when rendering
|
||||||
|
label: Human-friendly label for the rendered attribute
|
||||||
|
template_name: The name of the template to render
|
||||||
|
"""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
self.template_name = template_name
|
||||||
self.context = context or {}
|
self.context = context or {}
|
||||||
|
|
||||||
def get_context(self, obj, context):
|
def get_context(self, obj, context):
|
||||||
@ -250,4 +389,7 @@ class TemplatedAttr(ObjectAttribute):
|
|||||||
|
|
||||||
|
|
||||||
class UtilizationAttr(ObjectAttribute):
|
class UtilizationAttr(ObjectAttribute):
|
||||||
|
"""
|
||||||
|
Renders the value of an attribute as a utilization graph.
|
||||||
|
"""
|
||||||
template_name = 'ui/attrs/utilization.html'
|
template_name = 'ui/attrs/utilization.html'
|
||||||
|
|||||||
@ -13,7 +13,9 @@ __all__ = (
|
|||||||
#
|
#
|
||||||
|
|
||||||
class Layout:
|
class Layout:
|
||||||
|
"""
|
||||||
|
A collection of rows and columns comprising the layout of content within the user interface.
|
||||||
|
"""
|
||||||
def __init__(self, *rows):
|
def __init__(self, *rows):
|
||||||
for i, row in enumerate(rows):
|
for i, row in enumerate(rows):
|
||||||
if type(row) is not Row:
|
if type(row) is not Row:
|
||||||
@ -22,7 +24,9 @@ class Layout:
|
|||||||
|
|
||||||
|
|
||||||
class Row:
|
class Row:
|
||||||
|
"""
|
||||||
|
A collection of columns arranged horizontally.
|
||||||
|
"""
|
||||||
def __init__(self, *columns):
|
def __init__(self, *columns):
|
||||||
for i, column in enumerate(columns):
|
for i, column in enumerate(columns):
|
||||||
if type(column) is not Column:
|
if type(column) is not Column:
|
||||||
@ -31,7 +35,9 @@ class Row:
|
|||||||
|
|
||||||
|
|
||||||
class Column:
|
class Column:
|
||||||
|
"""
|
||||||
|
A collection of panels arranged vertically.
|
||||||
|
"""
|
||||||
def __init__(self, *panels):
|
def __init__(self, *panels):
|
||||||
for i, panel in enumerate(panels):
|
for i, panel in enumerate(panels):
|
||||||
if not isinstance(panel, Panel):
|
if not isinstance(panel, Panel):
|
||||||
@ -40,12 +46,18 @@ class Column:
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Standard layouts
|
# Common layouts
|
||||||
#
|
#
|
||||||
|
|
||||||
class SimpleLayout(Layout):
|
class SimpleLayout(Layout):
|
||||||
"""
|
"""
|
||||||
A layout with one row of two columns and a second row with one column. Includes registered plugin content.
|
A layout with one row of two columns and a second row with one column. Includes registered plugin content.
|
||||||
|
|
||||||
|
+------+------+
|
||||||
|
| col1 | col2 |
|
||||||
|
+------+------+
|
||||||
|
| col3 |
|
||||||
|
+-------------+
|
||||||
"""
|
"""
|
||||||
def __init__(self, left_panels=None, right_panels=None, bottom_panels=None):
|
def __init__(self, left_panels=None, right_panels=None, bottom_panels=None):
|
||||||
left_panels = left_panels or []
|
left_panels = left_panels or []
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
from abc import ABC, ABCMeta
|
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -20,9 +18,9 @@ __all__ = (
|
|||||||
'ObjectPanel',
|
'ObjectPanel',
|
||||||
'ObjectsTablePanel',
|
'ObjectsTablePanel',
|
||||||
'OrganizationalObjectPanel',
|
'OrganizationalObjectPanel',
|
||||||
'RelatedObjectsPanel',
|
|
||||||
'Panel',
|
'Panel',
|
||||||
'PluginContentPanel',
|
'PluginContentPanel',
|
||||||
|
'RelatedObjectsPanel',
|
||||||
'TemplatePanel',
|
'TemplatePanel',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,14 +29,13 @@ __all__ = (
|
|||||||
# Base classes
|
# Base classes
|
||||||
#
|
#
|
||||||
|
|
||||||
class Panel(ABC):
|
class Panel:
|
||||||
"""
|
"""
|
||||||
A block of content rendered within an HTML template.
|
A block of content rendered within an HTML template.
|
||||||
|
|
||||||
Attributes:
|
Panels are arranged within rows and columns, (generally) render as discrete "cards" within the user interface. Each
|
||||||
template_name: The name of the template to render
|
panel has a title and may have one or more actions associated with it, which will be rendered as hyperlinks in the
|
||||||
title: The human-friendly title of the panel
|
top right corner of the card.
|
||||||
actions: A list of PanelActions to include in the panel header
|
|
||||||
"""
|
"""
|
||||||
template_name = None
|
template_name = None
|
||||||
title = None
|
title = None
|
||||||
@ -50,7 +47,7 @@ class Panel(ABC):
|
|||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
title: The human-friendly title of the panel
|
title: The human-friendly title of the panel
|
||||||
actions: A list of PanelActions to include in the panel header
|
actions: An iterable of PanelActions to include in the panel header
|
||||||
"""
|
"""
|
||||||
if title is not None:
|
if title is not None:
|
||||||
self.title = title
|
self.title = title
|
||||||
@ -95,7 +92,7 @@ class ObjectPanel(Panel):
|
|||||||
Instantiate a new ObjectPanel.
|
Instantiate a new ObjectPanel.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
accessor: The name of the attribute on the object (default: "object")
|
accessor: The dotted path in context data to the object being rendered (default: "object")
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
@ -103,12 +100,6 @@ class ObjectPanel(Panel):
|
|||||||
self.accessor = accessor
|
self.accessor = accessor
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
"""
|
|
||||||
Return the context data to be used when rendering the panel.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
context: The template context
|
|
||||||
"""
|
|
||||||
obj = resolve_attr_path(context, self.accessor)
|
obj = resolve_attr_path(context, self.accessor)
|
||||||
return {
|
return {
|
||||||
**super().get_context(context),
|
**super().get_context(context),
|
||||||
@ -117,7 +108,7 @@ class ObjectPanel(Panel):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ObjectAttributesPanelMeta(ABCMeta):
|
class ObjectAttributesPanelMeta(type):
|
||||||
|
|
||||||
def __new__(mcls, name, bases, namespace, **kwargs):
|
def __new__(mcls, name, bases, namespace, **kwargs):
|
||||||
declared = {}
|
declared = {}
|
||||||
@ -148,9 +139,8 @@ class ObjectAttributesPanel(ObjectPanel, metaclass=ObjectAttributesPanelMeta):
|
|||||||
"""
|
"""
|
||||||
A panel which displays selected attributes of an object.
|
A panel which displays selected attributes of an object.
|
||||||
|
|
||||||
Attributes:
|
Attributes are added to the panel by declaring ObjectAttribute instances in the class body (similar to fields on
|
||||||
template_name: The name of the template to render
|
a Django form). Attributes are displayed in the order they are declared.
|
||||||
accessor: The name of the attribute on the object
|
|
||||||
"""
|
"""
|
||||||
template_name = 'ui/panels/object_attributes.html'
|
template_name = 'ui/panels/object_attributes.html'
|
||||||
|
|
||||||
@ -159,7 +149,6 @@ class ObjectAttributesPanel(ObjectPanel, metaclass=ObjectAttributesPanelMeta):
|
|||||||
Instantiate a new ObjectPanel.
|
Instantiate a new ObjectPanel.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
accessor: The name of the attribute on the object
|
|
||||||
only: If specified, only attributes in this list will be displayed
|
only: If specified, only attributes in this list will be displayed
|
||||||
exclude: If specified, attributes in this list will be excluded from display
|
exclude: If specified, attributes in this list will be excluded from display
|
||||||
"""
|
"""
|
||||||
@ -181,12 +170,6 @@ class ObjectAttributesPanel(ObjectPanel, metaclass=ObjectAttributesPanelMeta):
|
|||||||
return label
|
return label
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
"""
|
|
||||||
Return the context data to be used when rendering the panel.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
context: The template context
|
|
||||||
"""
|
|
||||||
# Determine which attributes to display in the panel based on only/exclude args
|
# Determine which attributes to display in the panel based on only/exclude args
|
||||||
attr_names = set(self._attrs.keys())
|
attr_names = set(self._attrs.keys())
|
||||||
if self.only:
|
if self.only:
|
||||||
@ -209,7 +192,7 @@ class ObjectAttributesPanel(ObjectPanel, metaclass=ObjectAttributesPanelMeta):
|
|||||||
|
|
||||||
class OrganizationalObjectPanel(ObjectAttributesPanel, metaclass=ObjectAttributesPanelMeta):
|
class OrganizationalObjectPanel(ObjectAttributesPanel, metaclass=ObjectAttributesPanelMeta):
|
||||||
"""
|
"""
|
||||||
An ObjectPanel with attributes common to OrganizationalModels.
|
An ObjectPanel with attributes common to OrganizationalModels. Includes name and description.
|
||||||
"""
|
"""
|
||||||
name = attrs.TextAttr('name', label=_('Name'))
|
name = attrs.TextAttr('name', label=_('Name'))
|
||||||
description = attrs.TextAttr('description', label=_('Description'))
|
description = attrs.TextAttr('description', label=_('Description'))
|
||||||
@ -217,7 +200,7 @@ class OrganizationalObjectPanel(ObjectAttributesPanel, metaclass=ObjectAttribute
|
|||||||
|
|
||||||
class NestedGroupObjectPanel(ObjectAttributesPanel, metaclass=ObjectAttributesPanelMeta):
|
class NestedGroupObjectPanel(ObjectAttributesPanel, metaclass=ObjectAttributesPanelMeta):
|
||||||
"""
|
"""
|
||||||
An ObjectPanel with attributes common to NestedGroupObjects.
|
An ObjectPanel with attributes common to NestedGroupObjects. Includes the parent object.
|
||||||
"""
|
"""
|
||||||
parent = attrs.NestedObjectAttr('parent', label=_('Parent'), linkify=True)
|
parent = attrs.NestedObjectAttr('parent', label=_('Parent'), linkify=True)
|
||||||
|
|
||||||
@ -234,18 +217,12 @@ class CommentsPanel(ObjectPanel):
|
|||||||
Instantiate a new CommentsPanel.
|
Instantiate a new CommentsPanel.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
field_name: The name of the comment field on the object
|
field_name: The name of the comment field on the object (default: "comments")
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.field_name = field_name
|
self.field_name = field_name
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
"""
|
|
||||||
Return the context data to be used when rendering the panel.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
context: The template context
|
|
||||||
"""
|
|
||||||
return {
|
return {
|
||||||
**super().get_context(context),
|
**super().get_context(context),
|
||||||
'comments': getattr(context['object'], self.field_name),
|
'comments': getattr(context['object'], self.field_name),
|
||||||
@ -270,17 +247,9 @@ class JSONPanel(ObjectPanel):
|
|||||||
self.field_name = field_name
|
self.field_name = field_name
|
||||||
|
|
||||||
if copy_button:
|
if copy_button:
|
||||||
self.actions.append(
|
self.actions.append(CopyContent(f'panel_{field_name}'))
|
||||||
CopyContent(f'panel_{field_name}'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
"""
|
|
||||||
Return the context data to be used when rendering the panel.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
context: The template context
|
|
||||||
"""
|
|
||||||
return {
|
return {
|
||||||
**super().get_context(context),
|
**super().get_context(context),
|
||||||
'data': getattr(context['object'], self.field_name),
|
'data': getattr(context['object'], self.field_name),
|
||||||
@ -300,12 +269,6 @@ class RelatedObjectsPanel(Panel):
|
|||||||
title = _('Related Objects')
|
title = _('Related Objects')
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
"""
|
|
||||||
Return the context data to be used when rendering the panel.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
context: The template context
|
|
||||||
"""
|
|
||||||
return {
|
return {
|
||||||
**super().get_context(context),
|
**super().get_context(context),
|
||||||
'related_models': context.get('related_models'),
|
'related_models': context.get('related_models'),
|
||||||
@ -343,12 +306,6 @@ class ObjectsTablePanel(Panel):
|
|||||||
self.title = title(self.model._meta.verbose_name_plural)
|
self.title = title(self.model._meta.verbose_name_plural)
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
"""
|
|
||||||
Return the context data to be used when rendering the panel.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
context: The template context
|
|
||||||
"""
|
|
||||||
url_params = {
|
url_params = {
|
||||||
k: v(context) if callable(v) else v for k, v in self.filters.items()
|
k: v(context) if callable(v) else v for k, v in self.filters.items()
|
||||||
}
|
}
|
||||||
@ -363,7 +320,7 @@ class ObjectsTablePanel(Panel):
|
|||||||
|
|
||||||
class TemplatePanel(Panel):
|
class TemplatePanel(Panel):
|
||||||
"""
|
"""
|
||||||
A panel which renders content using an HTML template.
|
A panel which renders custom content using an HTML template.
|
||||||
"""
|
"""
|
||||||
def __init__(self, template_name, **kwargs):
|
def __init__(self, template_name, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -385,7 +342,7 @@ class PluginContentPanel(Panel):
|
|||||||
A panel which displays embedded plugin content.
|
A panel which displays embedded plugin content.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
method: The name of the plugin method to render (e.g. left_page)
|
method: The name of the plugin method to render (e.g. "left_page")
|
||||||
"""
|
"""
|
||||||
def __init__(self, method, **kwargs):
|
def __init__(self, method, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
{# TODO: Add copy-to-clipboard button #}
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<a href="{{ value.get_absolute_url }}"{% if name %} id="attr_{{ name }}"{% endif %}>{{ value.address.ip }}</a>
|
<a href="{{ value.get_absolute_url }}"{% if name %} id="attr_{{ name }}"{% endif %}>{{ value.address.ip }}</a>
|
||||||
{% if value.nat_inside %}
|
{% if value.nat_inside %}
|
||||||
|
|||||||
@ -184,4 +184,7 @@ def static_with_params(path, **params):
|
|||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def render(context, component):
|
def render(context, component):
|
||||||
|
"""
|
||||||
|
Render a UI component (e.g. a Panel) by calling its render() method and passing the current template context.
|
||||||
|
"""
|
||||||
return mark_safe(component.render(context))
|
return mark_safe(component.render(context))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user