Remove the custom querystring template tag (closes #19091)

Removes the deprecated querystring template tag from utilities/templatetags/
helpers.py and updates all 30 call sites across templates to use Django's
built-in querystring tag (available since Django 5.1). The request argument
is dropped since the built-in tag reads from the template context automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeremy Stretch 2026-05-07 08:41:01 -04:00
parent 14f3d9c791
commit b277d92654
12 changed files with 40 additions and 51 deletions

View File

@ -0,0 +1,13 @@
# NetBox v4.7
## v4.7.0 (FUTURE)
### Breaking Changes
### Enhancements
### Other Changes
* [#19091](https://github.com/netbox-community/netbox/issues/19091) - Remove the custom `querystring` template tag (use Django's built-in tag instead)
### REST API Changes

View File

@ -8,7 +8,7 @@
{% block controls %}
<div class="col-auto d-print-none">
<div class="btn-list">
<a href="{% url 'dcim:rack_list' %}{% querystring request %}" class="btn btn-primary">
<a href="{% url 'dcim:rack_list' %}{% querystring %}" class="btn btn-primary">
<i class="mdi mdi-format-list-checkbox"></i> {% trans "View List" %}
</a>
<select class="btn btn-outline-secondary no-ts rack-view" aria-label="{% trans "Select rack view" %}">
@ -17,8 +17,8 @@
<option value="labels-only">{% trans "Labels only" %}</option>
</select>
<div class="btn-group" role="group">
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='front' %}" class="btn btn-outline-secondary{% if rack_face == 'front' %} active{% endif %}">{% trans "Front" %}</a>
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='rear' %}" class="btn btn-outline-secondary{% if rack_face == 'rear' %} active{% endif %}">{% trans "Rear" %}</a>
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring face='front' %}" class="btn btn-outline-secondary{% if rack_face == 'front' %} active{% endif %}">{% trans "Front" %}</a>
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring face='rear' %}" class="btn btn-outline-secondary{% if rack_face == 'rear' %} active{% endif %}">{% trans "Rear" %}</a>
</div>
<div class="dropdown">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@ -26,7 +26,7 @@
</button>
<ul class="dropdown-menu dropdown-menu-end">
{% for sort_key, sort_display_name in sort_choices.items %}
<li><a class="dropdown-item{% if sort == sort_key %} active{% endif %}" href="{% url 'dcim:rack_elevation_list' %}{% querystring request sort=sort_key %}">{{ sort_display_name }}</a></li>
<li><a class="dropdown-item{% if sort == sort_key %} active{% endif %}" href="{% url 'dcim:rack_elevation_list' %}{% querystring sort=sort_key %}">{{ sort_display_name }}</a></li>
{% endfor %}
</ul>
</div>

View File

@ -4,7 +4,7 @@
{% load i18n %}
{% block extra_controls %}
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request %}" class="btn btn-primary">
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring %}" class="btn btn-primary">
<i class="mdi mdi-view-day-outline"></i> {% trans "View Elevations" %}
</a>
{% endblock %}

View File

@ -8,7 +8,7 @@
{% block tabs %}
<ul class="nav nav-tabs">
<li class="nav-item">
<a href="{{ add_url }}{% querystring request %}" class="nav-link">
<a href="{{ add_url }}{% querystring %}" class="nav-link">
{% trans "Create" %}
</a>
</li>

View File

@ -19,11 +19,11 @@
{% if page.has_previous %}
<li class="page-item">
{% if htmx %}
<a href="#" hx-get="{{ table.htmx_url }}{% querystring request page=page.previous_page_number %}" class="page-link">
<a href="#" hx-get="{{ table.htmx_url }}{% querystring page=page.previous_page_number %}" class="page-link">
<i class="mdi mdi-chevron-left"></i>
</a>
{% else %}
<a href="{% querystring request page=page.previous_page_number %}" class="page-link">
<a href="{% querystring page=page.previous_page_number %}" class="page-link">
<i class="mdi mdi-chevron-left"></i>
</a>
{% endif %}
@ -35,11 +35,11 @@
{% for p in page.smart_pages %}
<li class="page-item{% if page.number == p %} active" aria-current="page{% endif %}">
{% if p and htmx %}
<a href="#" hx-get="{{ table.htmx_url }}{% querystring request page=p %}" class="page-link">
<a href="#" hx-get="{{ table.htmx_url }}{% querystring page=p %}" class="page-link">
{{ p }}
</a>
{% elif p %}
<a href="{% querystring request page=p %}" class="page-link {% if page.number == p %} active{% endif %}">
<a href="{% querystring page=p %}" class="page-link {% if page.number == p %} active{% endif %}">
{{ p }}
</a>
{% else %}
@ -53,11 +53,11 @@
{% if page.has_next %}
<li class="page-item">
{% if htmx %}
<a href="#" hx-get="{{ table.htmx_url }}{% querystring request page=page.next_page_number %}" class="page-link">
<a href="#" hx-get="{{ table.htmx_url }}{% querystring page=page.next_page_number %}" class="page-link">
<i class="mdi mdi-chevron-right"></i>
</a>
{% else %}
<a href="{% querystring request page=page.next_page_number %}" class="page-link">
<a href="{% querystring page=page.next_page_number %}" class="page-link">
<i class="mdi mdi-chevron-right"></i>
</a>
{% endif %}
@ -89,9 +89,9 @@
{% for n in page.paginator.get_page_lengths %}
<li>
{% if htmx %}
<a href="#" hx-get="{{ table.htmx_url }}{% querystring request per_page=n %}" class="dropdown-item">{{ n }}</a>
<a href="#" hx-get="{{ table.htmx_url }}{% querystring per_page=n %}" class="dropdown-item">{{ n }}</a>
{% else %}
<a href="{% querystring request per_page=n %}" class="dropdown-item">{{ n }}</a>
<a href="{% querystring per_page=n %}" class="dropdown-item">{{ n }}</a>
{% endif %}
</li>
{% endfor %}

View File

@ -3,19 +3,19 @@
<ul class="nav nav-tabs">
<li class="nav-item">
<a href="{% url 'ipam:ipaddress_add' %}{% querystring request %}" class="nav-link {% if active_tab == 'add' %}active{% endif %}">
<a href="{% url 'ipam:ipaddress_add' %}{% querystring %}" class="nav-link {% if active_tab == 'add' %}active{% endif %}">
{% if object.pk %}{% trans "Edit" %}{% else %}{% trans "Create" %}{% endif %}
</a>
</li>
{% if 'interface' in request.GET or 'vminterface' in request.GET %}
<li class="nav-item">
<a href="{% url 'ipam:ipaddress_assign' %}{% querystring request %}" class="nav-link {% if active_tab == 'assign' %}active{% endif %}">
<a href="{% url 'ipam:ipaddress_assign' %}{% querystring %}" class="nav-link {% if active_tab == 'assign' %}active{% endif %}">
{% trans "Assign IP" %}
</a>
</li>
{% elif not object.pk %}
<li class="nav-item">
<a href="{% url 'ipam:ipaddress_bulk_add' %}{% querystring request %}" class="nav-link {% if active_tab == 'bulk_add' %}active{% endif %}">
<a href="{% url 'ipam:ipaddress_bulk_add' %}{% querystring %}" class="nav-link {% if active_tab == 'bulk_add' %}active{% endif %}">
{% trans "Bulk Create" %}
</a>
</li>

View File

@ -8,11 +8,11 @@
<ul class="dropdown-menu" aria-labelledby="max_depth">
{% if request.GET.depth__lte %}
<li>
<a class="dropdown-item" href="{{ request.path }}{% querystring request depth__lte=None page=1 %}">{% trans "Clear" %}</a>
<a class="dropdown-item" href="{{ request.path }}{% querystring depth__lte=None page=1 %}">{% trans "Clear" %}</a>
</li>
{% endif %}
{% for i in 16|as_range %}
<li><a class="dropdown-item" href="{{ request.path }}{% querystring request depth__lte=i page=1 %}">
<li><a class="dropdown-item" href="{{ request.path }}{% querystring depth__lte=i page=1 %}">
{{ i }} {% if request.GET.depth__lte == i %}<i class="mdi mdi-check-bold"></i>{% endif %}
</a></li>
{% endfor %}

View File

@ -8,11 +8,11 @@
<ul class="dropdown-menu" aria-labelledby="max_length">
{% if request.GET.mask_length__lte %}
<li>
<a class="dropdown-item" href="{{ request.path }}{% querystring request mask_length__lte=None page=1 %}">{% trans "Clear" %}</a>
<a class="dropdown-item" href="{{ request.path }}{% querystring mask_length__lte=None page=1 %}">{% trans "Clear" %}</a>
</li>
{% endif %}
{% for i in "4,8,12,16,20,24,28,32,40,48,56,64"|split %}
<li><a class="dropdown-item" href="{{ request.path }}{% querystring request mask_length__lte=i page=1 %}">
<li><a class="dropdown-item" href="{{ request.path }}{% querystring mask_length__lte=i page=1 %}">
{{ i }} {% if request.GET.mask_length__lte == i %}<i class="mdi mdi-check-bold"></i>{% endif %}
</a></li>
{% endfor %}

View File

@ -3,13 +3,13 @@
<ul class="nav nav-tabs">
<li class="nav-item">
<a href="{% if object.pk %}{% url 'ipam:prefix_edit' pk=object.pk %}{% else %}{% url 'ipam:prefix_add' %}{% querystring request %}{% endif %}" class="nav-link {% if active_tab == 'add' %}active{% endif %}">
<a href="{% if object.pk %}{% url 'ipam:prefix_edit' pk=object.pk %}{% else %}{% url 'ipam:prefix_add' %}{% querystring %}{% endif %}" class="nav-link {% if active_tab == 'add' %}active{% endif %}">
{% if object.pk %}{% trans "Prefix" %}{% else %}{% trans "Create" %}{% endif %}
</a>
</li>
{% if not object.pk %}
<li class="nav-item">
<a href="{% url 'ipam:prefix_bulk_add' %}{% querystring request %}" class="nav-link {% if active_tab == 'bulk_add' %}active{% endif %}">
<a href="{% url 'ipam:prefix_bulk_add' %}{% querystring %}" class="nav-link {% if active_tab == 'bulk_add' %}active{% endif %}">
{% trans "Bulk Create" %}
</a>
</li>

View File

@ -3,13 +3,13 @@
{% if show_assigned or show_available is not None %}
<div class="btn-group" role="group">
<a href="{{ request.path }}{% querystring request show_assigned='true' show_available='false' %}" class="btn {% if show_assigned and not show_available %}btn-secondary active{% else %}btn-outline-secondary{% endif %}">
<a href="{{ request.path }}{% querystring show_assigned='true' show_available='false' %}" class="btn {% if show_assigned and not show_available %}btn-secondary active{% else %}btn-outline-secondary{% endif %}">
{% trans "Show Assigned" %}
</a>
<a href="{{ request.path }}{% querystring request show_assigned='false' show_available='true' %}" class="btn {% if show_available and not show_assigned %}btn-secondary active{% else %}btn-outline-secondary{% endif %}">
<a href="{{ request.path }}{% querystring show_assigned='false' show_available='true' %}" class="btn {% if show_available and not show_assigned %}btn-secondary active{% else %}btn-outline-secondary{% endif %}">
{% trans "Show Available" %}
</a>
<a href="{{ request.path }}{% querystring request show_assigned='true' show_available='true' %}" class="btn {% if show_available and show_assigned %}btn-secondary active{% else %}btn-outline-secondary{% endif %}">
<a href="{{ request.path }}{% querystring show_assigned='true' show_available='true' %}" class="btn {% if show_available and show_assigned %}btn-secondary active{% else %}btn-outline-secondary{% endif %}">
{% trans "Show All" %}
</a>
</div>

View File

@ -12,7 +12,7 @@
{% endblock %}
{% block form %}
<form action="{% querystring request %}" method="post" class="form form-horizontal">
<form action="{% querystring %}" method="post" class="form form-horizontal">
{% csrf_token %}
{% for field in form.hidden_fields %}
{{ field }}

View File

@ -1,5 +1,4 @@
import json
import warnings
from typing import Any
from urllib.parse import quote
@ -28,7 +27,6 @@ __all__ = (
'kg_to_pounds',
'meters_to_feet',
'percentage',
'querystring',
'startswith',
'status_from_tag',
'table_config_form',
@ -395,28 +393,6 @@ def icon_from_status(status: str = "info") -> str:
# Tags
#
@register.simple_tag()
def querystring(request, **kwargs):
"""
Append or update the page number in a querystring.
"""
warnings.warn(
'The querystring template tag is deprecated and will be removed in a future release. Use '
'the built-in Django querystring tag instead.',
category=FutureWarning,
)
querydict = request.GET.copy()
for k, v in kwargs.items():
if v is not None:
querydict[k] = str(v)
elif k in querydict:
querydict.pop(k)
querystring = querydict.urlencode(safe='/')
if querystring:
return '?' + querystring
return ''
@register.inclusion_tag('helpers/utilization_graph.html')
def utilization_graph(utilization, warning_threshold=75, danger_threshold=90):
"""