mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #8600: Document built-in template tags & filters
This commit is contained in:
@ -193,3 +193,43 @@ This template is used by the `BulkDeleteView` generic view to delete multiple ob
|
|||||||
| `form` | Yes | The bulk delete form class |
|
| `form` | Yes | The bulk delete form class |
|
||||||
| `table` | Yes | The table class used for rendering the list of objects |
|
| `table` | Yes | The table class used for rendering the list of objects |
|
||||||
| `return_url` | Yes | The URL to which the user is redirect after submitting the form |
|
| `return_url` | Yes | The URL to which the user is redirect after submitting the form |
|
||||||
|
|
||||||
|
## Tags
|
||||||
|
|
||||||
|
The following custom template tags are available in NetBox.
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
These are loaded automatically by the template backend: You do _not_ need to include a `{% load %}` tag in your template to activate them.
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.tags.badge
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.tags.checkmark
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.tags.tag
|
||||||
|
|
||||||
|
## Filters
|
||||||
|
|
||||||
|
The following custom template filters are available in NetBox.
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
These are loaded automatically by the template backend: You do _not_ need to include a `{% load %}` tag in your template to activate them.
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.filters.bettertitle
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.filters.content_type
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.filters.content_type_id
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.filters.meta
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.filters.placeholder
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.filters.render_json
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.filters.render_markdown
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.filters.render_yaml
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.filters.split
|
||||||
|
|
||||||
|
::: utilities.templatetags.builtins.filters.tzoffset
|
||||||
|
@ -225,7 +225,7 @@ class ObjectJournalTable(NetBoxTable):
|
|||||||
)
|
)
|
||||||
kind = columns.ChoiceFieldColumn()
|
kind = columns.ChoiceFieldColumn()
|
||||||
comments = tables.TemplateColumn(
|
comments = tables.TemplateColumn(
|
||||||
template_code='{% load helpers %}{{ value|render_markdown|truncatewords_html:50 }}'
|
template_code='{{ value|markdown|truncatewords_html:50 }}'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
|
@ -352,6 +352,10 @@ TEMPLATES = [
|
|||||||
'DIRS': [TEMPLATES_DIR],
|
'DIRS': [TEMPLATES_DIR],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
|
'builtins': [
|
||||||
|
'utilities.templatetags.builtins.filters',
|
||||||
|
'utilities.templatetags.builtins.tags',
|
||||||
|
],
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
'django.template.context_processors.debug',
|
'django.template.context_processors.debug',
|
||||||
'django.template.context_processors.request',
|
'django.template.context_processors.request',
|
||||||
|
@ -290,7 +290,7 @@ class TagColumn(tables.TemplateColumn):
|
|||||||
template_code = """
|
template_code = """
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% for tag in value.all %}
|
{% for tag in value.all %}
|
||||||
{% tag tag url_name=url_name %}
|
{% tag tag url_name %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -414,9 +414,8 @@ class MarkdownColumn(tables.TemplateColumn):
|
|||||||
Render a Markdown string.
|
Render a Markdown string.
|
||||||
"""
|
"""
|
||||||
template_code = """
|
template_code = """
|
||||||
{% load helpers %}
|
|
||||||
{% if value %}
|
{% if value %}
|
||||||
{{ value|render_markdown }}
|
{{ value|markdown }}
|
||||||
{% else %}
|
{% else %}
|
||||||
—
|
—
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -41,11 +41,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">NOC Contact</th>
|
<th scope="row">NOC Contact</th>
|
||||||
<td>{{ object.noc_contact|render_markdown|placeholder }}</td>
|
<td>{{ object.noc_contact|markdown|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Admin Contact</th>
|
<th scope="row">Admin Contact</th>
|
||||||
<td>{{ object.admin_contact|render_markdown|placeholder }}</td>
|
<td>{{ object.admin_contact|markdown|placeholder }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Circuits</th>
|
<th scope="row">Circuits</th>
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
NAPALM Arguments
|
NAPALM Arguments
|
||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<pre>{{ object.napalm_args|render_json }}</pre>
|
<pre>{{ object.napalm_args|json }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/custom_fields.html' %}
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
<span class="muted">—</span>
|
<span class="muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="rendered-markdown">{{ message|render_markdown }}</td>
|
<td class="rendered-markdown">{{ message|markdown }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ forloop.counter }}</td>
|
<td>{{ forloop.counter }}</td>
|
||||||
<td>{% log_level log.status %}</td>
|
<td>{% log_level log.status %}</td>
|
||||||
<td class="rendered-markdown">{{ log.message|render_markdown }}</td>
|
<td class="rendered-markdown">{{ log.message|markdown }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
<div class="rendered-context-data">
|
<div class="rendered-context-data">
|
||||||
<pre class="block">{% if format == 'json' %}{{ data|render_json }}{% elif format == 'yaml' %}{{ data|render_yaml }}{% else %}{{ data }}{% endif %}</pre>
|
<pre class="block">{% if format == 'json' %}{{ data|json }}{% elif format == 'yaml' %}{{ data|yaml }}{% else %}{{ data }}{% endif %}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
@ -98,8 +98,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<pre class="change-diff change-removed">{{ diff_removed|render_json }}</pre>
|
<pre class="change-diff change-removed">{{ diff_removed|json }}</pre>
|
||||||
<pre class="change-diff change-added">{{ diff_added|render_json }}</pre>
|
<pre class="change-diff change-added">{{ diff_added|json }}</pre>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -114,7 +114,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if object.prechange_data %}
|
{% if object.prechange_data %}
|
||||||
<pre class="change-data">{% for k, v in object.prechange_data.items %}{% spaceless %}
|
<pre class="change-data">{% for k, v in object.prechange_data.items %}{% spaceless %}
|
||||||
<span{% if k in diff_removed %} class="removed"{% endif %}>{{ k }}: {{ v|render_json }}</span>
|
<span{% if k in diff_removed %} class="removed"{% endif %}>{{ k }}: {{ v|json }}</span>
|
||||||
{% endspaceless %}{% endfor %}
|
{% endspaceless %}{% endfor %}
|
||||||
</pre>
|
</pre>
|
||||||
{% elif non_atomic_change %}
|
{% elif non_atomic_change %}
|
||||||
@ -133,7 +133,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if object.postchange_data %}
|
{% if object.postchange_data %}
|
||||||
<pre class="change-data">{% for k, v in object.postchange_data.items %}{% spaceless %}
|
<pre class="change-data">{% for k, v in object.postchange_data.items %}{% spaceless %}
|
||||||
<span{% if k in diff_added %} class="added"{% endif %}>{{ k }}: {{ v|render_json }}</span>
|
<span{% if k in diff_added %} class="added"{% endif %}>{{ k }}: {{ v|json }}</span>
|
||||||
{% endspaceless %}{% endfor %}
|
{% endspaceless %}{% endfor %}
|
||||||
</pre>
|
</pre>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
{% block subtitle %}
|
{% block subtitle %}
|
||||||
{% if report.description %}
|
{% if report.description %}
|
||||||
<div class="object-subtitle">
|
<div class="object-subtitle">
|
||||||
<div class="text-muted">{{ report.description|render_markdown }}</div>
|
<div class="text-muted">{{ report.description|markdown }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock subtitle %}
|
{% endblock subtitle %}
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{% include 'extras/inc/job_label.html' with result=report.result %}
|
{% include 'extras/inc/job_label.html' with result=report.result %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ report.description|render_markdown|placeholder }}</td>
|
<td>{{ report.description|markdown|placeholder }}</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
{% if report.result %}
|
{% if report.result %}
|
||||||
<a href="{% url 'extras:report_result' job_result_pk=report.result.pk %}">{{ report.result.created|annotated_date }}</a>
|
<a href="{% url 'extras:report_result' job_result_pk=report.result.pk %}">{{ report.result.created|annotated_date }}</a>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
{% block subtitle %}
|
{% block subtitle %}
|
||||||
<div class="object-subtitle">
|
<div class="object-subtitle">
|
||||||
<div class="text-muted">{{ script.Meta.description|render_markdown }}</div>
|
<div class="text-muted">{{ script.Meta.description|markdown }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock subtitle %}
|
{% endblock subtitle %}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
{% include 'extras/inc/job_label.html' with result=script.result %}
|
{% include 'extras/inc/job_label.html' with result=script.result %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ script.Meta.description|render_markdown|placeholder }}
|
{{ script.Meta.description|markdown|placeholder }}
|
||||||
</td>
|
</td>
|
||||||
{% if script.result %}
|
{% if script.result %}
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% block title %}{{ script }}{% endblock %}
|
{% block title %}{{ script }}{% endblock %}
|
||||||
|
|
||||||
{% block subtitle %}
|
{% block subtitle %}
|
||||||
{{ script.Meta.description|render_markdown }}
|
{{ script.Meta.description|markdown }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
|
@ -108,7 +108,7 @@
|
|||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if object.conditions %}
|
{% if object.conditions %}
|
||||||
<pre>{{ object.conditions|render_json }}</pre>
|
<pre>{{ object.conditions|json }}</pre>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-muted">None</p>
|
<p class="text-muted">None</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if object.comments %}
|
{% if object.comments %}
|
||||||
{{ object.comments|render_markdown }}
|
{{ object.comments|markdown }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">None</span>
|
<span class="text-muted">None</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if field.type == 'longtext' and value %}
|
{% if field.type == 'longtext' and value %}
|
||||||
{{ value|render_markdown }}
|
{{ value|markdown }}
|
||||||
{% elif field.type == 'boolean' and value == True %}
|
{% elif field.type == 'boolean' and value == True %}
|
||||||
{% checkmark value true="True" %}
|
{% checkmark value true="True" %}
|
||||||
{% elif field.type == 'boolean' and value == False %}
|
{% elif field.type == 'boolean' and value == False %}
|
||||||
@ -21,7 +21,7 @@
|
|||||||
{% elif field.type == 'url' and value %}
|
{% elif field.type == 'url' and value %}
|
||||||
<a href="{{ value }}">{{ value|truncatechars:70 }}</a>
|
<a href="{{ value }}">{{ value|truncatechars:70 }}</a>
|
||||||
{% elif field.type == 'json' and value %}
|
{% elif field.type == 'json' and value %}
|
||||||
<pre>{{ value|render_json }}</pre>
|
<pre>{{ value|json }}</pre>
|
||||||
{% elif field.type == 'multiselect' and value %}
|
{% elif field.type == 'multiselect' and value %}
|
||||||
{{ value|join:", " }}
|
{{ value|join:", " }}
|
||||||
{% elif field.type == 'object' and value %}
|
{% elif field.type == 'object' and value %}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% if url_name %}<a href="{% url url_name %}?tag={{ tag.slug }}">{% endif %}<span class="badge" style="color: {{ tag.color|fgcolor }}; background-color: #{{ tag.color }}">{{ tag }}</span>{% if url_name %}</a>{% endif %}
|
{% if viewname %}<a href="{% url viewname %}?tag={{ tag.slug }}">{% endif %}<span class="badge" style="color: {{ tag.color|fgcolor }}; background-color: #{{ tag.color }}">{{ tag }}</span>{% if viewname %}</a>{% endif %}
|
0
netbox/utilities/templatetags/builtins/__init__.py
Normal file
0
netbox/utilities/templatetags/builtins/__init__.py
Normal file
167
netbox/utilities/templatetags/builtins/filters.py
Normal file
167
netbox/utilities/templatetags/builtins/filters.py
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from django import template
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.utils.html import strip_tags
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from markdown import markdown
|
||||||
|
|
||||||
|
from netbox.config import get_config
|
||||||
|
from utilities.markdown import StrikethroughExtension
|
||||||
|
from utilities.utils import foreground_color
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# General
|
||||||
|
#
|
||||||
|
|
||||||
|
@register.filter()
|
||||||
|
def bettertitle(value):
|
||||||
|
"""
|
||||||
|
Alternative to the builtin title(). Ensures that the first letter of each word is uppercase but retains the
|
||||||
|
original case of all others.
|
||||||
|
"""
|
||||||
|
return ' '.join([w[0].upper() + w[1:] for w in value.split()])
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter()
|
||||||
|
def fgcolor(value, dark='000000', light='ffffff'):
|
||||||
|
"""
|
||||||
|
Return black (#000000) or white (#ffffff) given an arbitrary background color in RRGGBB format. The foreground
|
||||||
|
color with the better contrast is returned.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The background color
|
||||||
|
dark: The foreground color to use for light backgrounds
|
||||||
|
light: The foreground color to use for dark backgrounds
|
||||||
|
"""
|
||||||
|
value = value.lower().strip('#')
|
||||||
|
if not re.match('^[0-9a-f]{6}$', value):
|
||||||
|
return ''
|
||||||
|
return f'#{foreground_color(value, dark, light)}'
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter()
|
||||||
|
def meta(model, attr):
|
||||||
|
"""
|
||||||
|
Return the specified Meta attribute of a model. This is needed because Django does not permit templates
|
||||||
|
to access attributes which begin with an underscore (e.g. _meta).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model: A Django model class or instance
|
||||||
|
attr: The attribute name
|
||||||
|
"""
|
||||||
|
return getattr(model._meta, attr, '')
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter()
|
||||||
|
def placeholder(value):
|
||||||
|
"""
|
||||||
|
Render a muted placeholder if the value equates to False.
|
||||||
|
"""
|
||||||
|
if value not in ('', None):
|
||||||
|
return value
|
||||||
|
placeholder = '<span class="text-muted">—</span>'
|
||||||
|
return mark_safe(placeholder)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter()
|
||||||
|
def split(value, separator=','):
|
||||||
|
"""
|
||||||
|
Wrapper for Python's `split()` string method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: A string
|
||||||
|
separator: String on which the value will be split
|
||||||
|
"""
|
||||||
|
return value.split(separator)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter()
|
||||||
|
def tzoffset(value):
|
||||||
|
"""
|
||||||
|
Returns the hour offset of a given time zone using the current time.
|
||||||
|
"""
|
||||||
|
return datetime.datetime.now(value).strftime('%z')
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Content types
|
||||||
|
#
|
||||||
|
|
||||||
|
@register.filter()
|
||||||
|
def content_type(model):
|
||||||
|
"""
|
||||||
|
Return the ContentType for the given object.
|
||||||
|
"""
|
||||||
|
return ContentType.objects.get_for_model(model)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter()
|
||||||
|
def content_type_id(model):
|
||||||
|
"""
|
||||||
|
Return the ContentType ID for the given object.
|
||||||
|
"""
|
||||||
|
content_type = ContentType.objects.get_for_model(model)
|
||||||
|
if content_type:
|
||||||
|
return content_type.pk
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Rendering
|
||||||
|
#
|
||||||
|
|
||||||
|
@register.filter('markdown', is_safe=True)
|
||||||
|
def render_markdown(value):
|
||||||
|
"""
|
||||||
|
Render a string as Markdown. This filter is invoked as "markdown":
|
||||||
|
|
||||||
|
{{ md_source_text|markdown }}
|
||||||
|
"""
|
||||||
|
schemes = '|'.join(get_config().ALLOWED_URL_SCHEMES)
|
||||||
|
|
||||||
|
# Strip HTML tags
|
||||||
|
value = strip_tags(value)
|
||||||
|
|
||||||
|
# Sanitize Markdown links
|
||||||
|
pattern = fr'\[([^\]]+)\]\((?!({schemes})).*:(.+)\)'
|
||||||
|
value = re.sub(pattern, '[\\1](\\3)', value, flags=re.IGNORECASE)
|
||||||
|
|
||||||
|
# Sanitize Markdown reference links
|
||||||
|
pattern = fr'\[(.+)\]:\s*(?!({schemes}))\w*:(.+)'
|
||||||
|
value = re.sub(pattern, '[\\1]: \\3', value, flags=re.IGNORECASE)
|
||||||
|
|
||||||
|
# Render Markdown
|
||||||
|
html = markdown(value, extensions=['fenced_code', 'tables', StrikethroughExtension()])
|
||||||
|
|
||||||
|
# If the string is not empty wrap it in rendered-markdown to style tables
|
||||||
|
if html:
|
||||||
|
html = f'<div class="rendered-markdown">{html}</div>'
|
||||||
|
|
||||||
|
return mark_safe(html)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter('json')
|
||||||
|
def render_json(value):
|
||||||
|
"""
|
||||||
|
Render a dictionary as formatted JSON. This filter is invoked as "json":
|
||||||
|
|
||||||
|
{{ data_dict|json }}
|
||||||
|
"""
|
||||||
|
return json.dumps(value, ensure_ascii=False, indent=4, sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter('yaml')
|
||||||
|
def render_yaml(value):
|
||||||
|
"""
|
||||||
|
Render a dictionary as formatted YAML. This filter is invoked as "yaml":
|
||||||
|
|
||||||
|
{{ data_dict|yaml }}
|
||||||
|
"""
|
||||||
|
return yaml.dump(json.loads(json.dumps(value)))
|
54
netbox/utilities/templatetags/builtins/tags.py
Normal file
54
netbox/utilities/templatetags/builtins/tags.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag('builtins/tag.html')
|
||||||
|
def tag(value, viewname=None):
|
||||||
|
"""
|
||||||
|
Display a tag, optionally linked to a filtered list of objects.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: A Tag instance
|
||||||
|
viewname: If provided, the tag will be a hyperlink to the specified view's URL
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'tag': value,
|
||||||
|
'viewname': viewname,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag('builtins/badge.html')
|
||||||
|
def badge(value, bg_class='secondary', show_empty=False):
|
||||||
|
"""
|
||||||
|
Display the specified number as a badge.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The value to be displayed within the badge
|
||||||
|
bg_class: Bootstrap 5 background CSS name
|
||||||
|
show_empty: If true, display the badge even if value is None or zero
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'value': value,
|
||||||
|
'bg_class': bg_class,
|
||||||
|
'show_empty': show_empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag('builtins/checkmark.html')
|
||||||
|
def checkmark(value, show_false=True, true='Yes', false='No'):
|
||||||
|
"""
|
||||||
|
Display either a green checkmark or red X to indicate a boolean value.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: True or False
|
||||||
|
show_false: Show false values
|
||||||
|
true: Text label for true values
|
||||||
|
false: Text label for false values
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'value': bool(value),
|
||||||
|
'show_false': show_false,
|
||||||
|
'true_label': true,
|
||||||
|
'false_label': false,
|
||||||
|
}
|
@ -1,24 +1,17 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import json
|
|
||||||
import re
|
import re
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
|
|
||||||
import yaml
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.template.defaultfilters import date
|
from django.template.defaultfilters import date
|
||||||
from django.urls import NoReverseMatch, reverse
|
from django.urls import NoReverseMatch, reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.html import strip_tags
|
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from markdown import markdown
|
|
||||||
|
|
||||||
from netbox.config import get_config
|
|
||||||
from utilities.forms import get_selected_values, TableConfigForm
|
from utilities.forms import get_selected_values, TableConfigForm
|
||||||
from utilities.markdown import StrikethroughExtension
|
from utilities.utils import get_viewname
|
||||||
from utilities.utils import foreground_color, get_viewname
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@ -27,88 +20,6 @@ register = template.Library()
|
|||||||
# Filters
|
# Filters
|
||||||
#
|
#
|
||||||
|
|
||||||
@register.filter()
|
|
||||||
def placeholder(value):
|
|
||||||
"""
|
|
||||||
Render a muted placeholder if value equates to False.
|
|
||||||
"""
|
|
||||||
if value not in ('', None):
|
|
||||||
return value
|
|
||||||
placeholder = '<span class="text-muted">—</span>'
|
|
||||||
return mark_safe(placeholder)
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter(is_safe=True)
|
|
||||||
def render_markdown(value):
|
|
||||||
"""
|
|
||||||
Render text as Markdown
|
|
||||||
"""
|
|
||||||
schemes = '|'.join(get_config().ALLOWED_URL_SCHEMES)
|
|
||||||
|
|
||||||
# Strip HTML tags
|
|
||||||
value = strip_tags(value)
|
|
||||||
|
|
||||||
# Sanitize Markdown links
|
|
||||||
pattern = fr'\[([^\]]+)\]\((?!({schemes})).*:(.+)\)'
|
|
||||||
value = re.sub(pattern, '[\\1](\\3)', value, flags=re.IGNORECASE)
|
|
||||||
|
|
||||||
# Sanitize Markdown reference links
|
|
||||||
pattern = fr'\[(.+)\]:\s*(?!({schemes}))\w*:(.+)'
|
|
||||||
value = re.sub(pattern, '[\\1]: \\3', value, flags=re.IGNORECASE)
|
|
||||||
|
|
||||||
# Render Markdown
|
|
||||||
html = markdown(value, extensions=['fenced_code', 'tables', StrikethroughExtension()])
|
|
||||||
|
|
||||||
# If the string is not empty wrap it in rendered-markdown to style tables
|
|
||||||
if html:
|
|
||||||
html = f'<div class="rendered-markdown">{html}</div>'
|
|
||||||
|
|
||||||
return mark_safe(html)
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
|
||||||
def render_json(value):
|
|
||||||
"""
|
|
||||||
Render a dictionary as formatted JSON.
|
|
||||||
"""
|
|
||||||
return json.dumps(value, ensure_ascii=False, indent=4, sort_keys=True)
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
|
||||||
def render_yaml(value):
|
|
||||||
"""
|
|
||||||
Render a dictionary as formatted YAML.
|
|
||||||
"""
|
|
||||||
return yaml.dump(json.loads(json.dumps(value)))
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
|
||||||
def meta(obj, attr):
|
|
||||||
"""
|
|
||||||
Return the specified Meta attribute of a model. This is needed because Django does not permit templates
|
|
||||||
to access attributes which begin with an underscore (e.g. _meta).
|
|
||||||
"""
|
|
||||||
return getattr(obj._meta, attr, '')
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
|
||||||
def content_type(obj):
|
|
||||||
"""
|
|
||||||
Return the ContentType for the given object.
|
|
||||||
"""
|
|
||||||
return ContentType.objects.get_for_model(obj)
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
|
||||||
def content_type_id(obj):
|
|
||||||
"""
|
|
||||||
Return the ContentType ID for the given object.
|
|
||||||
"""
|
|
||||||
content_type = ContentType.objects.get_for_model(obj)
|
|
||||||
if content_type:
|
|
||||||
return content_type.pk
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
@register.filter()
|
||||||
def viewname(model, action):
|
def viewname(model, action):
|
||||||
@ -133,14 +44,6 @@ def validated_viewname(model, action):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
|
||||||
def bettertitle(value):
|
|
||||||
"""
|
|
||||||
Alternative to the builtin title(); uppercases words without replacing letters that are already uppercase.
|
|
||||||
"""
|
|
||||||
return ' '.join([w[0].upper() + w[1:] for w in value.split()])
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
@register.filter()
|
||||||
def humanize_speed(speed):
|
def humanize_speed(speed):
|
||||||
"""
|
"""
|
||||||
@ -191,14 +94,6 @@ def simplify_decimal(value):
|
|||||||
return str(value).rstrip('0').rstrip('.')
|
return str(value).rstrip('0').rstrip('.')
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
|
||||||
def tzoffset(value):
|
|
||||||
"""
|
|
||||||
Returns the hour offset of a given time zone using the current time.
|
|
||||||
"""
|
|
||||||
return datetime.datetime.now(value).strftime('%z')
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter(expects_localtime=True)
|
@register.filter(expects_localtime=True)
|
||||||
def annotated_date(date_value):
|
def annotated_date(date_value):
|
||||||
"""
|
"""
|
||||||
@ -229,17 +124,6 @@ def annotated_now():
|
|||||||
return annotated_date(datetime.datetime.now(tz=tzinfo))
|
return annotated_date(datetime.datetime.now(tz=tzinfo))
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
|
||||||
def fgcolor(value):
|
|
||||||
"""
|
|
||||||
Return black (#000000) or white (#ffffff) given an arbitrary background color in RRGGBB format.
|
|
||||||
"""
|
|
||||||
value = value.lower().strip('#')
|
|
||||||
if not re.match('^[0-9a-f]{6}$', value):
|
|
||||||
return ''
|
|
||||||
return f'#{foreground_color(value)}'
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
@register.filter()
|
||||||
def divide(x, y):
|
def divide(x, y):
|
||||||
"""
|
"""
|
||||||
@ -276,14 +160,6 @@ def has_perms(user, permissions_list):
|
|||||||
return user.has_perms(permissions_list)
|
return user.has_perms(permissions_list)
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
|
||||||
def split(string, sep=','):
|
|
||||||
"""
|
|
||||||
Split a string by the given value (default: comma)
|
|
||||||
"""
|
|
||||||
return string.split(sep)
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter()
|
@register.filter()
|
||||||
def as_range(n):
|
def as_range(n):
|
||||||
"""
|
"""
|
||||||
@ -403,46 +279,6 @@ def utilization_graph(utilization, warning_threshold=75, danger_threshold=90):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('helpers/tag.html')
|
|
||||||
def tag(tag, url_name=None):
|
|
||||||
"""
|
|
||||||
Display a tag, optionally linked to a filtered list of objects.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
'tag': tag,
|
|
||||||
'url_name': url_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('helpers/badge.html')
|
|
||||||
def badge(value, bg_class='secondary', show_empty=False):
|
|
||||||
"""
|
|
||||||
Display the specified number as a badge.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
'value': value,
|
|
||||||
'bg_class': bg_class,
|
|
||||||
'show_empty': show_empty,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('helpers/checkmark.html')
|
|
||||||
def checkmark(value, show_false=True, true='Yes', false='No'):
|
|
||||||
"""
|
|
||||||
Display either a green checkmark or red X to indicate a boolean value.
|
|
||||||
|
|
||||||
:param show_false: Display a red X if the value is False
|
|
||||||
:param true: Text label for true value
|
|
||||||
:param false: Text label for false value
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
'value': bool(value),
|
|
||||||
'show_false': show_false,
|
|
||||||
'true_label': true,
|
|
||||||
'false_label': false,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('helpers/table_config_form.html')
|
@register.inclusion_tag('helpers/table_config_form.html')
|
||||||
def table_config_form(table, table_name=None):
|
def table_config_form(table, table_name=None):
|
||||||
return {
|
return {
|
||||||
|
Reference in New Issue
Block a user