mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge pull request #8159 from netbox-community/6782-custom-link-columns
Closes #6782: Custom link columns
This commit is contained in:
@ -55,3 +55,7 @@ The link will only appear when viewing a device with a manufacturer name of "Cis
|
||||
## Link Groups
|
||||
|
||||
Group names can be specified to organize links into groups. Links with the same group name will render as a dropdown menu beneath a single button bearing the name of the group.
|
||||
|
||||
## Table Columns
|
||||
|
||||
Custom links can also be included in object tables by selecting the desired links from the table configuration form. When displayed, each link will render as a hyperlink for its corresponding object. When exported (e.g. as CSV data), each link render only its URL.
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [#6782](https://github.com/netbox-community/netbox/issues/6782) - Enable the inclusion of custom links in tables
|
||||
* [#8100](https://github.com/netbox-community/netbox/issues/8100) - Add "other" choice for FHRP group protocol
|
||||
|
||||
### Bug Fixes
|
||||
|
@ -229,6 +229,24 @@ class CustomLink(ChangeLoggedModel):
|
||||
def get_absolute_url(self):
|
||||
return reverse('extras:customlink', args=[self.pk])
|
||||
|
||||
def render(self, context):
|
||||
"""
|
||||
Render the CustomLink given the provided context, and return the text, link, and link_target.
|
||||
|
||||
:param context: The context passed to Jinja2
|
||||
"""
|
||||
text = render_jinja2(self.link_text, context)
|
||||
if not text:
|
||||
return {}
|
||||
link = render_jinja2(self.link_url, context)
|
||||
link_target = ' target="_blank"' if self.new_window else ''
|
||||
|
||||
return {
|
||||
'text': text,
|
||||
'link': link,
|
||||
'link_target': link_target,
|
||||
}
|
||||
|
||||
|
||||
@extras_features('webhooks', 'export_templates')
|
||||
class ExportTemplate(ChangeLoggedModel):
|
||||
|
@ -62,16 +62,14 @@ def custom_links(context, obj):
|
||||
# Add non-grouped links
|
||||
else:
|
||||
try:
|
||||
text_rendered = render_jinja2(cl.link_text, link_context)
|
||||
if text_rendered:
|
||||
link_rendered = render_jinja2(cl.link_url, link_context)
|
||||
link_target = ' target="_blank"' if cl.new_window else ''
|
||||
rendered = cl.render(link_context)
|
||||
if rendered:
|
||||
template_code += LINK_BUTTON.format(
|
||||
link_rendered, link_target, cl.button_class, text_rendered
|
||||
rendered['link'], rendered['link_target'], cl.button_class, rendered['text']
|
||||
)
|
||||
except Exception as e:
|
||||
template_code += '<a class="btn btn-sm btn-outline-dark" disabled="disabled" title="{}">' \
|
||||
'<i class="mdi mdi-alert"></i> {}</a>\n'.format(e, cl.name)
|
||||
template_code += f'<a class="btn btn-sm btn-outline-dark" disabled="disabled" title="{e}">' \
|
||||
f'<i class="mdi mdi-alert"></i> {cl.name}</a>\n'
|
||||
|
||||
# Add grouped links to template
|
||||
for group, links in group_names.items():
|
||||
@ -80,17 +78,15 @@ def custom_links(context, obj):
|
||||
|
||||
for cl in links:
|
||||
try:
|
||||
text_rendered = render_jinja2(cl.link_text, link_context)
|
||||
if text_rendered:
|
||||
link_target = ' target="_blank"' if cl.new_window else ''
|
||||
link_rendered = render_jinja2(cl.link_url, link_context)
|
||||
rendered = cl.render(link_context)
|
||||
if rendered:
|
||||
links_rendered.append(
|
||||
GROUP_LINK.format(link_rendered, link_target, text_rendered)
|
||||
GROUP_LINK.format(rendered['link'], rendered['link_target'], rendered['text'])
|
||||
)
|
||||
except Exception as e:
|
||||
links_rendered.append(
|
||||
'<li><a class="dropdown-item" disabled="disabled" title="{}"><span class="text-muted">'
|
||||
'<i class="mdi mdi-alert"></i> {}</span></a></li>'.format(e, cl.name)
|
||||
f'<li><a class="dropdown-item" disabled="disabled" title="{e}"><span class="text-muted">'
|
||||
f'<i class="mdi mdi-alert"></i> {cl.name}</span></a></li>'
|
||||
)
|
||||
|
||||
if links_rendered:
|
||||
|
@ -12,7 +12,7 @@ from django_tables2.data import TableQuerysetData
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from extras.choices import CustomFieldTypeChoices
|
||||
from extras.models import CustomField
|
||||
from extras.models import CustomField, CustomLink
|
||||
from .utils import content_type_identifier, content_type_name
|
||||
from .paginator import EnhancedPaginator, get_paginate_count
|
||||
|
||||
@ -34,15 +34,18 @@ class BaseTable(tables.Table):
|
||||
}
|
||||
|
||||
def __init__(self, *args, user=None, extra_columns=None, **kwargs):
|
||||
if extra_columns is None:
|
||||
extra_columns = []
|
||||
|
||||
# Add custom field columns
|
||||
obj_type = ContentType.objects.get_for_model(self._meta.model)
|
||||
cf_columns = [
|
||||
(f'cf_{cf.name}', CustomFieldColumn(cf)) for cf in CustomField.objects.filter(content_types=obj_type)
|
||||
]
|
||||
if extra_columns is not None:
|
||||
extra_columns.extend(cf_columns)
|
||||
else:
|
||||
extra_columns = cf_columns
|
||||
cl_columns = [
|
||||
(f'cl_{cl.name}', CustomLinkColumn(cl)) for cl in CustomLink.objects.filter(content_type=obj_type)
|
||||
]
|
||||
extra_columns.extend([*cf_columns, *cl_columns])
|
||||
|
||||
super().__init__(*args, extra_columns=extra_columns, **kwargs)
|
||||
|
||||
@ -418,6 +421,37 @@ class CustomFieldColumn(tables.Column):
|
||||
return self.default
|
||||
|
||||
|
||||
class CustomLinkColumn(tables.Column):
|
||||
"""
|
||||
Render a custom links as a table column.
|
||||
"""
|
||||
def __init__(self, customlink, *args, **kwargs):
|
||||
self.customlink = customlink
|
||||
kwargs['accessor'] = Accessor('pk')
|
||||
if 'verbose_name' not in kwargs:
|
||||
kwargs['verbose_name'] = customlink.name
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def render(self, record):
|
||||
try:
|
||||
rendered = self.customlink.render({'obj': record})
|
||||
if rendered:
|
||||
return mark_safe(f'<a href="{rendered["link"]}"{rendered["link_target"]}>{rendered["text"]}</a>')
|
||||
except Exception as e:
|
||||
return mark_safe(f'<span class="text-danger" title="{e}"><i class="mdi mdi-alert"></i> Error</span>')
|
||||
return ''
|
||||
|
||||
def value(self, record):
|
||||
try:
|
||||
rendered = self.customlink.render({'obj': record})
|
||||
if rendered:
|
||||
return rendered['link']
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
class MPTTColumn(tables.TemplateColumn):
|
||||
"""
|
||||
Display a nested hierarchy for MPTT-enabled models.
|
||||
|
Reference in New Issue
Block a user