From 290ffd408a1120263b062988154f406cba46ac26 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 26 Jun 2023 14:21:26 -0400 Subject: [PATCH] Fixes #12977: Fix URL parameters for object count dashboard widgets (#12991) * Fixes #12977: Introduce dict_to_querydict() to ensure proper handling of QueryDicts * Remove unused import --- netbox/extras/dashboard/widgets.py | 6 ++---- netbox/extras/models/models.py | 7 +++---- netbox/utilities/templatetags/builtins/tags.py | 5 +++-- netbox/utilities/utils.py | 16 +++++++++++++++- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index b3a4d090c..31e7cb2d3 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -10,7 +10,6 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.cache import cache from django.db.models import Q -from django.http import QueryDict from django.template.loader import render_to_string from django.urls import NoReverseMatch, resolve, reverse from django.utils.translation import gettext as _ @@ -19,7 +18,7 @@ from extras.utils import FeatureQuery from utilities.forms import BootstrapMixin from utilities.permissions import get_permission_for_model from utilities.templatetags.builtins.filters import render_markdown -from utilities.utils import content_type_identifier, content_type_name, get_viewname +from utilities.utils import content_type_identifier, content_type_name, dict_to_querydict, get_viewname from .utils import register_widget __all__ = ( @@ -170,8 +169,7 @@ class ObjectCountsWidget(DashboardWidget): qs = model.objects.restrict(request.user, 'view') # Apply any specified filters if filters := self.config.get('filters'): - params = QueryDict(mutable=True) - params.update(filters) + params = dict_to_querydict(filters) filterset = getattr(resolve(url).func.view_class, 'filterset', None) qs = filterset(params, qs).qs url = f'{url}?{params.urlencode()}' diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index b7034645f..4b4b6fbfd 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -9,7 +9,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.cache import cache from django.core.validators import ValidationError from django.db import models -from django.http import HttpResponse, QueryDict +from django.http import HttpResponse from django.urls import reverse from django.utils import timezone from django.utils.formats import date_format @@ -26,7 +26,7 @@ from netbox.models.features import ( CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin, ) from utilities.querysets import RestrictedQuerySet -from utilities.utils import clean_html, render_jinja2 +from utilities.utils import clean_html, dict_to_querydict, render_jinja2 __all__ = ( 'ConfigRevision', @@ -462,8 +462,7 @@ class SavedFilter(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel): @property def url_params(self): - qd = QueryDict(mutable=True) - qd.update(self.parameters) + qd = dict_to_querydict(self.parameters) return qd.urlencode() diff --git a/netbox/utilities/templatetags/builtins/tags.py b/netbox/utilities/templatetags/builtins/tags.py index dc86586e7..f9fe5f4e3 100644 --- a/netbox/utilities/templatetags/builtins/tags.py +++ b/netbox/utilities/templatetags/builtins/tags.py @@ -1,6 +1,8 @@ from django import template from django.http import QueryDict +from utilities.utils import dict_to_querydict + __all__ = ( 'badge', 'checkmark', @@ -87,8 +89,7 @@ def htmx_table(context, viewname, return_url=None, **kwargs): viewname: The name of the view to use for the HTMX request (e.g. `dcim:site_list`) return_url: The URL to pass as the `return_url`. If not provided, the current request's path will be used. """ - url_params = QueryDict(mutable=True) - url_params.update(kwargs) + url_params = dict_to_querydict(kwargs) url_params['return_url'] = return_url or context['request'].path return { 'viewname': viewname, diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 4b4a2631a..114397dae 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -11,8 +11,9 @@ from django.core import serializers from django.db.models import Count, OuterRef, Subquery from django.db.models.functions import Coalesce from django.http import QueryDict -from django.utils.html import escape from django.utils import timezone +from django.utils.datastructures import MultiValueDict +from django.utils.html import escape from django.utils.timezone import localtime from jinja2.sandbox import SandboxedEnvironment from mptt.models import MPTTModel @@ -231,6 +232,19 @@ def dict_to_filter_params(d, prefix=''): return params +def dict_to_querydict(d, mutable=True): + """ + Create a QueryDict instance from a regular Python dictionary. + """ + qd = QueryDict(mutable=True) + for k, v in d.items(): + item = MultiValueDict({k: v}) if isinstance(v, (list, tuple, set)) else {k: v} + qd.update(item) + if not mutable: + qd._mutable = False + return qd + + def normalize_querydict(querydict): """ Convert a QueryDict to a normal, mutable dictionary, preserving list values. For example,