```
+
+````
+```
+var s = "Code block";
+alert(s);
+```
+````
+
+```
+var s = "Code block";
+alert(s);
+```
+
+
+
+## Tables
+
+```no-highlight
+Colons can be used to align columns.
+
+| Tables | Are | Cool |
+| ------------- |:-------------:| -----:|
+| col 3 is | right-aligned | $1600 |
+| col 2 is | centered | $12 |
+| zebra stripes | are neat | $1 |
+
+There must be at least 3 dashes separating each header cell.
+The outer pipes (|) are optional, and you don't need to make the
+raw Markdown line up prettily. You can also use inline Markdown.
+
+Markdown | Less | Pretty
+--- | --- | ---
+*Still* | `renders` | **nicely**
+1 | 2 | 3
+```
+
+Colons can be used to align columns.
+
+| Tables | Are | Cool |
+| ------------- |:-------------:| -----:|
+| col 3 is | right-aligned | $1600 |
+| col 2 is | centered | $12 |
+| zebra stripes | are neat | $1 |
+
+There must be at least 3 dashes separating each header cell. The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown.
+
+Markdown | Less | Pretty
+--- | --- | ---
+*Still* | `renders` | **nicely**
+1 | 2 | 3
+
+
+
+## Blockquotes
+
+```no-highlight
+> Blockquotes are very handy in email to emulate reply text.
+> This line is part of the same quote.
+
+Quote break.
+
+> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
+```
+
+> Blockquotes are very handy in email to emulate reply text.
+> This line is part of the same quote.
+
+Quote break.
+
+> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
+
+
+
+## Inline HTML
+
+You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
+
+```no-highlight
+{{ object.validation_regex }}
{% else %}
- —
+ {{ ''|placeholder }}
{% endif %}
diff --git a/netbox/templates/extras/htmx/report_result.html b/netbox/templates/extras/htmx/report_result.html
index 9b3e9db5f..c20bf5fe2 100644
--- a/netbox/templates/extras/htmx/report_result.html
+++ b/netbox/templates/extras/htmx/report_result.html
@@ -57,7 +57,7 @@
{% elif obj %}
{{ obj }}
{% else %}
- —
+ {{ ''|placeholder }}
{% endif %}
{{ field.to_field_name }}
{% else %}
- —
+ {{ ''|placeholder }}
{% endif %}
Prefixes
+IP Ranges
+IP addresses
diff --git a/netbox/templates/users/profile.html b/netbox/templates/users/profile.html index 112603126..913784c94 100644 --- a/netbox/templates/users/profile.html +++ b/netbox/templates/users/profile.html @@ -21,7 +21,7 @@ {% if request.user.first_name or request.user.last_name %} {{ request.user.first_name }} {{ request.user.last_name }} {% else %} - — + {{ ''|placeholder }} {% endif %} diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 2831a452a..f62da6fed 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -49,7 +49,7 @@ (NAT: {{ object.primary_ip4.nat_outside.address.ip }}) {% endif %} {% else %} - — + {{ ''|placeholder }} {% endif %} @@ -64,7 +64,7 @@ (NAT: {{ object.primary_ip6.nat_outside.address.ip }}) {% endif %} {% else %} - — + {{ ''|placeholder }} {% endif %} @@ -123,7 +123,7 @@ {% if object.memory %} {{ object.memory|humanize_megabytes }} {% else %} - — + {{ ''|placeholder }} {% endif %} @@ -133,7 +133,7 @@ {% if object.disk %} {{ object.disk }} GB {% else %} - — + {{ ''|placeholder }} {% endif %} diff --git a/netbox/templates/wireless/inc/wirelesslink_interface.html b/netbox/templates/wireless/inc/wirelesslink_interface.html index db4f84f0a..7732816a7 100644 --- a/netbox/templates/wireless/inc/wirelesslink_interface.html +++ b/netbox/templates/wireless/inc/wirelesslink_interface.html @@ -33,7 +33,7 @@ {% if interface.rf_channel_frequency %} {{ interface.rf_channel_frequency|simplify_decimal }} MHz {% else %} - — + {{ ''|placeholder }} {% endif %} @@ -43,7 +43,7 @@ {% if interface.rf_channel_width %} {{ interface.rf_channel_width|simplify_decimal }} MHz {% else %} - — + {{ ''|placeholder }} {% endif %} diff --git a/netbox/tenancy/tables/contacts.py b/netbox/tenancy/tables/contacts.py index 17abc5a5b..234dc2ad7 100644 --- a/netbox/tenancy/tables/contacts.py +++ b/netbox/tenancy/tables/contacts.py @@ -18,7 +18,7 @@ class ContactGroupTable(NetBoxTable): ) contact_count = columns.LinkedCountColumn( viewname='tenancy:contact_list', - url_params={'role_id': 'pk'}, + url_params={'group_id': 'pk'}, verbose_name='Contacts' ) tags = columns.TagColumn( diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 58ad98e8f..f6f95b123 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -3,7 +3,7 @@ from django.shortcuts import get_object_or_404 from circuits.models import Circuit from dcim.models import Cable, Device, Location, Rack, RackReservation, Site -from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF, ASN +from ipam.models import Aggregate, IPAddress, IPRange, Prefix, VLAN, VRF, ASN from netbox.views import generic from utilities.utils import count_related from virtualization.models import VirtualMachine, Cluster @@ -104,8 +104,9 @@ class TenantView(generic.ObjectView): 'location_count': Location.objects.restrict(request.user, 'view').filter(tenant=instance).count(), 'device_count': Device.objects.restrict(request.user, 'view').filter(tenant=instance).count(), 'vrf_count': VRF.objects.restrict(request.user, 'view').filter(tenant=instance).count(), - 'prefix_count': Prefix.objects.restrict(request.user, 'view').filter(tenant=instance).count(), 'aggregate_count': Aggregate.objects.restrict(request.user, 'view').filter(tenant=instance).count(), + 'prefix_count': Prefix.objects.restrict(request.user, 'view').filter(tenant=instance).count(), + 'iprange_count': IPRange.objects.restrict(request.user, 'view').filter(tenant=instance).count(), 'ipaddress_count': IPAddress.objects.restrict(request.user, 'view').filter(tenant=instance).count(), 'vlan_count': VLAN.objects.restrict(request.user, 'view').filter(tenant=instance).count(), 'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(tenant=instance).count(), diff --git a/netbox/utilities/forms/fields/fields.py b/netbox/utilities/forms/fields/fields.py index 0d09d2ac7..9168189a1 100644 --- a/netbox/utilities/forms/fields/fields.py +++ b/netbox/utilities/forms/fields/fields.py @@ -3,6 +3,7 @@ import json from django import forms from django.db.models import Count from django.forms.fields import JSONField as _JSONField, InvalidJSONInput +from django.templatetags.static import static from netaddr import AddrFormatError, EUI from utilities.forms import widgets @@ -26,10 +27,9 @@ class CommentField(forms.CharField): A textarea with support for Markdown rendering. Exists mostly just to add a standard `help_text`. """ widget = forms.Textarea - # TODO: Port Markdown cheat sheet to internal documentation - help_text = """ + help_text = f""" - + Markdown syntax is supported """ diff --git a/netbox/utilities/forms/utils.py b/netbox/utilities/forms/utils.py index 9a4b011e0..a6f037e0b 100644 --- a/netbox/utilities/forms/utils.py +++ b/netbox/utilities/forms/utils.py @@ -1,7 +1,6 @@ import re from django import forms -from django.conf import settings from django.forms.models import fields_for_model from utilities.choices import unpack_grouped_choices diff --git a/netbox/utilities/templatetags/builtins/filters.py b/netbox/utilities/templatetags/builtins/filters.py index 44ad5ac47..738dc0e00 100644 --- a/netbox/utilities/templatetags/builtins/filters.py +++ b/netbox/utilities/templatetags/builtins/filters.py @@ -11,7 +11,7 @@ from markdown import markdown from netbox.config import get_config from utilities.markdown import StrikethroughExtension -from utilities.utils import foreground_color +from utilities.utils import clean_html, foreground_color register = template.Library() @@ -144,18 +144,6 @@ def render_markdown(value): {{ md_source_text|markdown }} """ - schemes = '|'.join(get_config().ALLOWED_URL_SCHEMES) - - # Strip HTML tags - value = strip_tags(value) - - # Sanitize Markdown links - pattern = fr'\[([^\]]+)\]\(\s*(?!({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=['def_list', 'fenced_code', 'tables', StrikethroughExtension()]) @@ -164,6 +152,11 @@ def render_markdown(value): if html: html = f'