From 7c105019d8ae9205051c302e7499b33a455f9176 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 9 Feb 2022 16:01:58 -0500 Subject: [PATCH] Closes #8600: Document built-in template tags & filters --- docs/plugins/development/templates.md | 40 +++++ netbox/extras/tables.py | 2 +- netbox/netbox/settings.py | 4 + netbox/netbox/tables/columns.py | 5 +- netbox/templates/circuits/provider.html | 4 +- netbox/templates/dcim/platform.html | 2 +- .../templates/extras/htmx/report_result.html | 2 +- .../templates/extras/htmx/script_result.html | 2 +- .../extras/inc/configcontext_data.html | 2 +- netbox/templates/extras/objectchange.html | 8 +- netbox/templates/extras/report.html | 2 +- netbox/templates/extras/report_list.html | 2 +- netbox/templates/extras/script.html | 2 +- netbox/templates/extras/script_list.html | 2 +- netbox/templates/extras/script_result.html | 2 +- netbox/templates/extras/webhook.html | 2 +- netbox/templates/inc/panels/comments.html | 2 +- .../templates/inc/panels/custom_fields.html | 4 +- .../{helpers => builtins}/badge.html | 0 .../{helpers => builtins}/checkmark.html | 0 .../templates/{helpers => builtins}/tag.html | 2 +- .../templatetags/builtins/__init__.py | 0 .../templatetags/builtins/filters.py | 167 ++++++++++++++++++ .../utilities/templatetags/builtins/tags.py | 54 ++++++ netbox/utilities/templatetags/helpers.py | 166 +---------------- 25 files changed, 289 insertions(+), 189 deletions(-) rename netbox/utilities/templates/{helpers => builtins}/badge.html (100%) rename netbox/utilities/templates/{helpers => builtins}/checkmark.html (100%) rename netbox/utilities/templates/{helpers => builtins}/tag.html (61%) create mode 100644 netbox/utilities/templatetags/builtins/__init__.py create mode 100644 netbox/utilities/templatetags/builtins/filters.py create mode 100644 netbox/utilities/templatetags/builtins/tags.py diff --git a/docs/plugins/development/templates.md b/docs/plugins/development/templates.md index 70228c623..61ffd3ce2 100644 --- a/docs/plugins/development/templates.md +++ b/docs/plugins/development/templates.md @@ -193,3 +193,43 @@ This template is used by the `BulkDeleteView` generic view to delete multiple ob | `form` | Yes | The bulk delete form class | | `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 | + +## 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 diff --git a/netbox/extras/tables.py b/netbox/extras/tables.py index 88a2dbe54..d37c44763 100644 --- a/netbox/extras/tables.py +++ b/netbox/extras/tables.py @@ -225,7 +225,7 @@ class ObjectJournalTable(NetBoxTable): ) kind = columns.ChoiceFieldColumn() comments = tables.TemplateColumn( - template_code='{% load helpers %}{{ value|render_markdown|truncatewords_html:50 }}' + template_code='{{ value|markdown|truncatewords_html:50 }}' ) class Meta(NetBoxTable.Meta): diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 4ca24fbf1..eaf1d3033 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -352,6 +352,10 @@ TEMPLATES = [ 'DIRS': [TEMPLATES_DIR], 'APP_DIRS': True, 'OPTIONS': { + 'builtins': [ + 'utilities.templatetags.builtins.filters', + 'utilities.templatetags.builtins.tags', + ], 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index cfc220536..43350acb0 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -290,7 +290,7 @@ class TagColumn(tables.TemplateColumn): template_code = """ {% load helpers %} {% for tag in value.all %} - {% tag tag url_name=url_name %} + {% tag tag url_name %} {% empty %} {% endfor %} @@ -414,9 +414,8 @@ class MarkdownColumn(tables.TemplateColumn): Render a Markdown string. """ template_code = """ - {% load helpers %} {% if value %} - {{ value|render_markdown }} + {{ value|markdown }} {% else %} — {% endif %} diff --git a/netbox/templates/circuits/provider.html b/netbox/templates/circuits/provider.html index 14fd00863..3fd275c7c 100644 --- a/netbox/templates/circuits/provider.html +++ b/netbox/templates/circuits/provider.html @@ -41,11 +41,11 @@ NOC Contact - {{ object.noc_contact|render_markdown|placeholder }} + {{ object.noc_contact|markdown|placeholder }} Admin Contact - {{ object.admin_contact|render_markdown|placeholder }} + {{ object.admin_contact|markdown|placeholder }} Circuits diff --git a/netbox/templates/dcim/platform.html b/netbox/templates/dcim/platform.html index d5642fd94..5180d47c5 100644 --- a/netbox/templates/dcim/platform.html +++ b/netbox/templates/dcim/platform.html @@ -73,7 +73,7 @@ NAPALM Arguments
-
{{ object.napalm_args|render_json }}
+
{{ object.napalm_args|json }}
{% include 'inc/panels/custom_fields.html' %} diff --git a/netbox/templates/extras/htmx/report_result.html b/netbox/templates/extras/htmx/report_result.html index b04f0c78c..9b3e9db5f 100644 --- a/netbox/templates/extras/htmx/report_result.html +++ b/netbox/templates/extras/htmx/report_result.html @@ -60,7 +60,7 @@ {% endif %} - {{ message|render_markdown }} + {{ message|markdown }} {% endfor %} {% endfor %} diff --git a/netbox/templates/extras/htmx/script_result.html b/netbox/templates/extras/htmx/script_result.html index 0336bdfaa..425f35898 100644 --- a/netbox/templates/extras/htmx/script_result.html +++ b/netbox/templates/extras/htmx/script_result.html @@ -22,7 +22,7 @@ {{ forloop.counter }} {% log_level log.status %} - {{ log.message|render_markdown }} + {{ log.message|markdown }} {% empty %} diff --git a/netbox/templates/extras/inc/configcontext_data.html b/netbox/templates/extras/inc/configcontext_data.html index 48ca18543..a19ec1c96 100644 --- a/netbox/templates/extras/inc/configcontext_data.html +++ b/netbox/templates/extras/inc/configcontext_data.html @@ -1,5 +1,5 @@ {% load helpers %}
-
{% if format == 'json' %}{{ data|render_json }}{% elif format == 'yaml' %}{{ data|render_yaml }}{% else %}{{ data }}{% endif %}
+
{% if format == 'json' %}{{ data|json }}{% elif format == 'yaml' %}{{ data|yaml }}{% else %}{{ data }}{% endif %}
diff --git a/netbox/templates/extras/objectchange.html b/netbox/templates/extras/objectchange.html index 501c9564f..1b18f1bd2 100644 --- a/netbox/templates/extras/objectchange.html +++ b/netbox/templates/extras/objectchange.html @@ -98,8 +98,8 @@ {% endif %} {% else %} -
{{ diff_removed|render_json }}
-
{{ diff_added|render_json }}
+
{{ diff_removed|json }}
+
{{ diff_added|json }}
{% endif %} @@ -114,7 +114,7 @@
{% if object.prechange_data %}
{% for k, v in object.prechange_data.items %}{% spaceless %}
-                    {{ k }}: {{ v|render_json }}
+                    {{ k }}: {{ v|json }}
                 {% endspaceless %}{% endfor %}
                 
{% elif non_atomic_change %} @@ -133,7 +133,7 @@
{% if object.postchange_data %}
{% for k, v in object.postchange_data.items %}{% spaceless %}
-                        {{ k }}: {{ v|render_json }}
+                        {{ k }}: {{ v|json }}
                         {% endspaceless %}{% endfor %}
                     
{% else %} diff --git a/netbox/templates/extras/report.html b/netbox/templates/extras/report.html index 68e888097..391de6614 100644 --- a/netbox/templates/extras/report.html +++ b/netbox/templates/extras/report.html @@ -15,7 +15,7 @@ {% block subtitle %} {% if report.description %}
-
{{ report.description|render_markdown }}
+
{{ report.description|markdown }}
{% endif %} {% endblock subtitle %} diff --git a/netbox/templates/extras/report_list.html b/netbox/templates/extras/report_list.html index 99d6da730..56b92c96d 100644 --- a/netbox/templates/extras/report_list.html +++ b/netbox/templates/extras/report_list.html @@ -40,7 +40,7 @@ {% include 'extras/inc/job_label.html' with result=report.result %} - {{ report.description|render_markdown|placeholder }} + {{ report.description|markdown|placeholder }} {% if report.result %} {{ report.result.created|annotated_date }} diff --git a/netbox/templates/extras/script.html b/netbox/templates/extras/script.html index 7d37b0cbb..99eade0a0 100644 --- a/netbox/templates/extras/script.html +++ b/netbox/templates/extras/script.html @@ -16,7 +16,7 @@ {% block subtitle %}
-
{{ script.Meta.description|render_markdown }}
+
{{ script.Meta.description|markdown }}
{% endblock subtitle %} diff --git a/netbox/templates/extras/script_list.html b/netbox/templates/extras/script_list.html index ccbdca705..7fc4b6d9a 100644 --- a/netbox/templates/extras/script_list.html +++ b/netbox/templates/extras/script_list.html @@ -40,7 +40,7 @@ {% include 'extras/inc/job_label.html' with result=script.result %} - {{ script.Meta.description|render_markdown|placeholder }} + {{ script.Meta.description|markdown|placeholder }} {% if script.result %} diff --git a/netbox/templates/extras/script_result.html b/netbox/templates/extras/script_result.html index 41368ecad..1c311ff26 100644 --- a/netbox/templates/extras/script_result.html +++ b/netbox/templates/extras/script_result.html @@ -4,7 +4,7 @@ {% block title %}{{ script }}{% endblock %} {% block subtitle %} - {{ script.Meta.description|render_markdown }} + {{ script.Meta.description|markdown }} {% endblock %} {% block header %} diff --git a/netbox/templates/extras/webhook.html b/netbox/templates/extras/webhook.html index 6891930bc..78aaaa105 100644 --- a/netbox/templates/extras/webhook.html +++ b/netbox/templates/extras/webhook.html @@ -108,7 +108,7 @@
{% if object.conditions %} -
{{ object.conditions|render_json }}
+
{{ object.conditions|json }}
{% else %}

None

{% endif %} diff --git a/netbox/templates/inc/panels/comments.html b/netbox/templates/inc/panels/comments.html index 3219a25a5..8ccbf8949 100644 --- a/netbox/templates/inc/panels/comments.html +++ b/netbox/templates/inc/panels/comments.html @@ -6,7 +6,7 @@
{% if object.comments %} - {{ object.comments|render_markdown }} + {{ object.comments|markdown }} {% else %} None {% endif %} diff --git a/netbox/templates/inc/panels/custom_fields.html b/netbox/templates/inc/panels/custom_fields.html index c8838fa80..eea94fdc3 100644 --- a/netbox/templates/inc/panels/custom_fields.html +++ b/netbox/templates/inc/panels/custom_fields.html @@ -13,7 +13,7 @@ {% if field.type == 'longtext' and value %} - {{ value|render_markdown }} + {{ value|markdown }} {% elif field.type == 'boolean' and value == True %} {% checkmark value true="True" %} {% elif field.type == 'boolean' and value == False %} @@ -21,7 +21,7 @@ {% elif field.type == 'url' and value %} {{ value|truncatechars:70 }} {% elif field.type == 'json' and value %} -
{{ value|render_json }}
+
{{ value|json }}
{% elif field.type == 'multiselect' and value %} {{ value|join:", " }} {% elif field.type == 'object' and value %} diff --git a/netbox/utilities/templates/helpers/badge.html b/netbox/utilities/templates/builtins/badge.html similarity index 100% rename from netbox/utilities/templates/helpers/badge.html rename to netbox/utilities/templates/builtins/badge.html diff --git a/netbox/utilities/templates/helpers/checkmark.html b/netbox/utilities/templates/builtins/checkmark.html similarity index 100% rename from netbox/utilities/templates/helpers/checkmark.html rename to netbox/utilities/templates/builtins/checkmark.html diff --git a/netbox/utilities/templates/helpers/tag.html b/netbox/utilities/templates/builtins/tag.html similarity index 61% rename from netbox/utilities/templates/helpers/tag.html rename to netbox/utilities/templates/builtins/tag.html index addb2380b..d63b6afa6 100644 --- a/netbox/utilities/templates/helpers/tag.html +++ b/netbox/utilities/templates/builtins/tag.html @@ -1,3 +1,3 @@ {% load helpers %} -{% if url_name %}{% endif %}{{ tag }}{% if url_name %}{% endif %} +{% if viewname %}{% endif %}{{ tag }}{% if viewname %}{% endif %} diff --git a/netbox/utilities/templatetags/builtins/__init__.py b/netbox/utilities/templatetags/builtins/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/netbox/utilities/templatetags/builtins/filters.py b/netbox/utilities/templatetags/builtins/filters.py new file mode 100644 index 000000000..afb40a308 --- /dev/null +++ b/netbox/utilities/templatetags/builtins/filters.py @@ -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 = '' + 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'
{html}
' + + 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))) diff --git a/netbox/utilities/templatetags/builtins/tags.py b/netbox/utilities/templatetags/builtins/tags.py new file mode 100644 index 000000000..a72a87d56 --- /dev/null +++ b/netbox/utilities/templatetags/builtins/tags.py @@ -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, + } diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index 89c218b9b..053eee728 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -1,24 +1,17 @@ import datetime import decimal -import json import re from typing import Dict, Any -import yaml from django import template from django.conf import settings -from django.contrib.contenttypes.models import ContentType from django.template.defaultfilters import date from django.urls import NoReverseMatch, reverse from django.utils import timezone -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.forms import get_selected_values, TableConfigForm -from utilities.markdown import StrikethroughExtension -from utilities.utils import foreground_color, get_viewname +from utilities.utils import get_viewname register = template.Library() @@ -27,88 +20,6 @@ register = template.Library() # Filters # -@register.filter() -def placeholder(value): - """ - Render a muted placeholder if value equates to False. - """ - if value not in ('', None): - return value - placeholder = '' - 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'
{html}
' - - 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() def viewname(model, action): @@ -133,14 +44,6 @@ def validated_viewname(model, action): 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() def humanize_speed(speed): """ @@ -191,14 +94,6 @@ def simplify_decimal(value): 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) def annotated_date(date_value): """ @@ -229,17 +124,6 @@ def annotated_now(): 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() def divide(x, y): """ @@ -276,14 +160,6 @@ def has_perms(user, 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() 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') def table_config_form(table, table_name=None): return {