mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-14 04:19:36 -06:00
Refactor render() on Attr to split out context and reduce boilerplate
This commit is contained in:
parent
dfb08ff521
commit
4edaa48aa7
@ -1,5 +1,3 @@
|
|||||||
from abc import ABC, abstractmethod
|
|
||||||
|
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -12,23 +10,65 @@ from utilities.data import resolve_attr_path
|
|||||||
# Attributes
|
# Attributes
|
||||||
#
|
#
|
||||||
|
|
||||||
class Attr(ABC):
|
class ObjectAttribute:
|
||||||
|
"""
|
||||||
|
Base class for representing an attribute of an object.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
template_name: The name of the template to render
|
||||||
|
label: Human-friendly label for the rendered attribute
|
||||||
|
placeholder: HTML to render for empty/null values
|
||||||
|
"""
|
||||||
template_name = None
|
template_name = None
|
||||||
label = None
|
label = None
|
||||||
placeholder = mark_safe('<span class="text-muted">—</span>')
|
placeholder = mark_safe('<span class="text-muted">—</span>')
|
||||||
|
|
||||||
def __init__(self, accessor, label=None, template_name=None):
|
def __init__(self, accessor, label=None, template_name=None):
|
||||||
|
"""
|
||||||
|
Instantiate a new ObjectAttribute.
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
self.accessor = accessor
|
self.accessor = accessor
|
||||||
self.template_name = template_name or self.template_name
|
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
|
||||||
|
|
||||||
@abstractmethod
|
def get_value(self, obj):
|
||||||
def render(self, obj, context=None):
|
"""
|
||||||
pass
|
Return the value of the attribute.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
obj: The object for which the attribute is being rendered
|
||||||
|
"""
|
||||||
|
return resolve_attr_path(obj, self.accessor)
|
||||||
|
|
||||||
|
def get_context(self, obj, context):
|
||||||
|
"""
|
||||||
|
Return any additional template context used to render the attribute value.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
obj: The object for which the attribute is being rendered
|
||||||
|
context: The template context
|
||||||
|
"""
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def render(self, obj, context):
|
||||||
|
value = self.get_value(obj)
|
||||||
|
if value in (None, ''):
|
||||||
|
return self.placeholder
|
||||||
|
context = self.get_context(obj, context)
|
||||||
|
return render_to_string(self.template_name, {
|
||||||
|
**context,
|
||||||
|
'value': value,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class TextAttr(Attr):
|
class TextAttr(ObjectAttribute):
|
||||||
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):
|
||||||
@ -37,22 +77,21 @@ class TextAttr(Attr):
|
|||||||
self.format_string = format_string
|
self.format_string = format_string
|
||||||
self.copy_button = copy_button
|
self.copy_button = copy_button
|
||||||
|
|
||||||
def render(self, obj, context=None):
|
def get_value(self, obj):
|
||||||
context = context or {}
|
|
||||||
value = resolve_attr_path(obj, self.accessor)
|
value = resolve_attr_path(obj, self.accessor)
|
||||||
if value in (None, ''):
|
# Apply format string (if any)
|
||||||
return self.placeholder
|
if value and self.format_string:
|
||||||
if self.format_string:
|
|
||||||
value = self.format_string.format(value)
|
value = self.format_string.format(value)
|
||||||
return render_to_string(self.template_name, {
|
return value
|
||||||
**context,
|
|
||||||
'value': value,
|
def get_context(self, obj, context):
|
||||||
|
return {
|
||||||
'style': self.style,
|
'style': self.style,
|
||||||
'copy_button': self.copy_button,
|
'copy_button': self.copy_button,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class NumericAttr(Attr):
|
class NumericAttr(ObjectAttribute):
|
||||||
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):
|
||||||
@ -60,88 +99,57 @@ class NumericAttr(Attr):
|
|||||||
self.unit_accessor = unit_accessor
|
self.unit_accessor = unit_accessor
|
||||||
self.copy_button = copy_button
|
self.copy_button = copy_button
|
||||||
|
|
||||||
def render(self, obj, context=None):
|
def get_context(self, obj, context):
|
||||||
context = context or {}
|
|
||||||
value = resolve_attr_path(obj, self.accessor)
|
|
||||||
if value in (None, ''):
|
|
||||||
return self.placeholder
|
|
||||||
unit = resolve_attr_path(obj, self.unit_accessor) if self.unit_accessor else None
|
unit = resolve_attr_path(obj, self.unit_accessor) if self.unit_accessor else None
|
||||||
return render_to_string(self.template_name, {
|
return {
|
||||||
**context,
|
|
||||||
'value': value,
|
|
||||||
'unit': unit,
|
'unit': unit,
|
||||||
'copy_button': self.copy_button,
|
'copy_button': self.copy_button,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class ChoiceAttr(Attr):
|
class ChoiceAttr(ObjectAttribute):
|
||||||
template_name = 'ui/attrs/choice.html'
|
template_name = 'ui/attrs/choice.html'
|
||||||
|
|
||||||
def render(self, obj, context=None):
|
def get_value(self, obj):
|
||||||
context = context or {}
|
|
||||||
try:
|
try:
|
||||||
value = getattr(obj, f'get_{self.accessor}_display')()
|
return getattr(obj, f'get_{self.accessor}_display')()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
value = resolve_attr_path(obj, self.accessor)
|
return resolve_attr_path(obj, self.accessor)
|
||||||
if value in (None, ''):
|
|
||||||
return self.placeholder
|
def get_context(self, obj, context):
|
||||||
try:
|
try:
|
||||||
bg_color = getattr(obj, f'get_{self.accessor}_color')()
|
bg_color = getattr(obj, f'get_{self.accessor}_color')()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
bg_color = None
|
bg_color = None
|
||||||
return render_to_string(self.template_name, {
|
return {
|
||||||
**context,
|
|
||||||
'value': value,
|
|
||||||
'bg_color': bg_color,
|
'bg_color': bg_color,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class BooleanAttr(Attr):
|
class BooleanAttr(ObjectAttribute):
|
||||||
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):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.display_false = display_false
|
self.display_false = display_false
|
||||||
|
|
||||||
def render(self, obj, context=None):
|
def get_value(self, obj):
|
||||||
context = context or {}
|
value = super().get_value(obj)
|
||||||
value = resolve_attr_path(obj, self.accessor)
|
if value is False and self.display_false is False:
|
||||||
if value in (None, '') and not self.display_false:
|
return None
|
||||||
return self.placeholder
|
return value
|
||||||
return render_to_string(self.template_name, {
|
|
||||||
**context,
|
|
||||||
'value': value,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class ColorAttr(Attr):
|
class ColorAttr(ObjectAttribute):
|
||||||
template_name = 'ui/attrs/color.html'
|
template_name = 'ui/attrs/color.html'
|
||||||
label = _('Color')
|
label = _('Color')
|
||||||
|
|
||||||
def render(self, obj, context=None):
|
|
||||||
context = context or {}
|
|
||||||
value = resolve_attr_path(obj, self.accessor)
|
|
||||||
return render_to_string(self.template_name, {
|
|
||||||
**context,
|
|
||||||
'color': value,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
class ImageAttr(ObjectAttribute):
|
||||||
class ImageAttr(Attr):
|
|
||||||
template_name = 'ui/attrs/image.html'
|
template_name = 'ui/attrs/image.html'
|
||||||
|
|
||||||
def render(self, obj, context=None):
|
|
||||||
context = context or {}
|
|
||||||
value = resolve_attr_path(obj, self.accessor)
|
|
||||||
if value in (None, ''):
|
|
||||||
return self.placeholder
|
|
||||||
return render_to_string(self.template_name, {
|
|
||||||
**context,
|
|
||||||
'value': value,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
class ObjectAttr(ObjectAttribute):
|
||||||
class ObjectAttr(Attr):
|
|
||||||
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):
|
||||||
@ -149,22 +157,16 @@ class ObjectAttr(Attr):
|
|||||||
self.linkify = linkify
|
self.linkify = linkify
|
||||||
self.grouped_by = grouped_by
|
self.grouped_by = grouped_by
|
||||||
|
|
||||||
def render(self, obj, context=None):
|
def get_context(self, obj, context):
|
||||||
context = context or {}
|
value = self.get_value(obj)
|
||||||
value = resolve_attr_path(obj, self.accessor)
|
|
||||||
if value is None:
|
|
||||||
return self.placeholder
|
|
||||||
group = getattr(value, self.grouped_by, None) if self.grouped_by else None
|
group = getattr(value, self.grouped_by, None) if self.grouped_by else None
|
||||||
|
return {
|
||||||
return render_to_string(self.template_name, {
|
|
||||||
**context,
|
|
||||||
'object': value,
|
|
||||||
'group': group,
|
|
||||||
'linkify': self.linkify,
|
'linkify': self.linkify,
|
||||||
})
|
'group': group,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class NestedObjectAttr(Attr):
|
class NestedObjectAttr(ObjectAttribute):
|
||||||
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):
|
||||||
@ -172,22 +174,18 @@ class NestedObjectAttr(Attr):
|
|||||||
self.linkify = linkify
|
self.linkify = linkify
|
||||||
self.max_depth = max_depth
|
self.max_depth = max_depth
|
||||||
|
|
||||||
def render(self, obj, context=None):
|
def get_context(self, obj, context):
|
||||||
context = context or {}
|
value = self.get_value(obj)
|
||||||
value = resolve_attr_path(obj, self.accessor)
|
|
||||||
if value is None:
|
|
||||||
return self.placeholder
|
|
||||||
nodes = value.get_ancestors(include_self=True)
|
nodes = value.get_ancestors(include_self=True)
|
||||||
if self.max_depth:
|
if self.max_depth:
|
||||||
nodes = list(nodes)[-self.max_depth:]
|
nodes = list(nodes)[-self.max_depth:]
|
||||||
return render_to_string(self.template_name, {
|
return {
|
||||||
**context,
|
|
||||||
'nodes': nodes,
|
'nodes': nodes,
|
||||||
'linkify': self.linkify,
|
'linkify': self.linkify,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class AddressAttr(Attr):
|
class AddressAttr(ObjectAttribute):
|
||||||
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):
|
||||||
@ -199,19 +197,13 @@ class AddressAttr(Attr):
|
|||||||
else:
|
else:
|
||||||
self.map_url = None
|
self.map_url = None
|
||||||
|
|
||||||
def render(self, obj, context=None):
|
def get_context(self, obj, context):
|
||||||
context = context or {}
|
return {
|
||||||
value = resolve_attr_path(obj, self.accessor)
|
|
||||||
if value in (None, ''):
|
|
||||||
return self.placeholder
|
|
||||||
return render_to_string(self.template_name, {
|
|
||||||
**context,
|
|
||||||
'value': value,
|
|
||||||
'map_url': self.map_url,
|
'map_url': self.map_url,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
class GPSCoordinatesAttr(Attr):
|
class GPSCoordinatesAttr(ObjectAttribute):
|
||||||
template_name = 'ui/attrs/gps_coordinates.html'
|
template_name = 'ui/attrs/gps_coordinates.html'
|
||||||
label = _('GPS coordinates')
|
label = _('GPS coordinates')
|
||||||
|
|
||||||
@ -240,49 +232,22 @@ class GPSCoordinatesAttr(Attr):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class TimezoneAttr(Attr):
|
class TimezoneAttr(ObjectAttribute):
|
||||||
template_name = 'ui/attrs/timezone.html'
|
template_name = 'ui/attrs/timezone.html'
|
||||||
|
|
||||||
def render(self, obj, context=None):
|
|
||||||
context = context or {}
|
|
||||||
value = resolve_attr_path(obj, self.accessor)
|
|
||||||
if value in (None, ''):
|
|
||||||
return self.placeholder
|
|
||||||
return render_to_string(self.template_name, {
|
|
||||||
**context,
|
|
||||||
'value': value,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
class TemplatedAttr(ObjectAttribute):
|
||||||
class TemplatedAttr(Attr):
|
|
||||||
|
|
||||||
def __init__(self, *args, context=None, **kwargs):
|
def __init__(self, *args, context=None, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.context = context or {}
|
self.context = context or {}
|
||||||
|
|
||||||
def render(self, obj, context=None):
|
def get_context(self, obj, context):
|
||||||
context = context or {}
|
return {
|
||||||
value = resolve_attr_path(obj, self.accessor)
|
**self.context,
|
||||||
if value is None:
|
'object': obj,
|
||||||
return self.placeholder
|
}
|
||||||
return render_to_string(
|
|
||||||
self.template_name,
|
|
||||||
{
|
|
||||||
**context,
|
|
||||||
**self.context,
|
|
||||||
'object': obj,
|
|
||||||
'value': value,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UtilizationAttr(Attr):
|
class UtilizationAttr(ObjectAttribute):
|
||||||
template_name = 'ui/attrs/utilization.html'
|
template_name = 'ui/attrs/utilization.html'
|
||||||
|
|
||||||
def render(self, obj, context=None):
|
|
||||||
context = context or {}
|
|
||||||
value = resolve_attr_path(obj, self.accessor)
|
|
||||||
return render_to_string(self.template_name, {
|
|
||||||
**context,
|
|
||||||
'value': value,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -130,13 +130,13 @@ class ObjectAttributesPanelMeta(ABCMeta):
|
|||||||
|
|
||||||
# Add local declarations in the order they appear in the class body
|
# Add local declarations in the order they appear in the class body
|
||||||
for key, attr in namespace.items():
|
for key, attr in namespace.items():
|
||||||
if isinstance(attr, attrs.Attr):
|
if isinstance(attr, attrs.ObjectAttribute):
|
||||||
declared[key] = attr
|
declared[key] = attr
|
||||||
|
|
||||||
namespace['_attrs'] = declared
|
namespace['_attrs'] = declared
|
||||||
|
|
||||||
# Remove Attrs from the class namespace to keep things tidy
|
# Remove Attrs from the class namespace to keep things tidy
|
||||||
local_items = [key for key, attr in namespace.items() if isinstance(attr, attrs.Attr)]
|
local_items = [key for key, attr in namespace.items() if isinstance(attr, attrs.ObjectAttribute)]
|
||||||
for key in local_items:
|
for key in local_items:
|
||||||
namespace.pop(key)
|
namespace.pop(key)
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
<span class="badge color-label" style="background-color: #{{ color }}"> </span>
|
<span class="badge color-label" style="background-color: #{{ value }}"> </span>
|
||||||
|
|||||||
@ -5,10 +5,10 @@
|
|||||||
{% if linkify %}{{ group|linkify }}{% else %}{{ group }}{% endif %}
|
{% if linkify %}{{ group|linkify }}{% else %}{{ group }}{% endif %}
|
||||||
</li>
|
</li>
|
||||||
<li class="breadcrumb-item">
|
<li class="breadcrumb-item">
|
||||||
{% if linkify %}{{ object|linkify }}{% else %}{{ object }}{% endif %}
|
{% if linkify %}{{ value|linkify }}{% else %}{{ value }}{% endif %}
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
{% else %}
|
{% else %}
|
||||||
{# Display only the object #}
|
{# Display only the object #}
|
||||||
{% if linkify %}{{ object|linkify }}{% else %}{{ object }}{% endif %}
|
{% if linkify %}{{ value|linkify }}{% else %}{{ value }}{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user