Przeglądaj źródła

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
Jeremy Stretch 2 lat temu
rodzic
commit
290ffd408a

+ 2 - 4
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()}'

+ 3 - 4
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()
 
 

+ 3 - 2
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,

+ 15 - 1
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,