Sfoglia il codice sorgente

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 anni fa
parent
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.contrib.contenttypes.models import ContentType
 from django.core.cache import cache
 from django.core.cache import cache
 from django.db.models import Q
 from django.db.models import Q
-from django.http import QueryDict
 from django.template.loader import render_to_string
 from django.template.loader import render_to_string
 from django.urls import NoReverseMatch, resolve, reverse
 from django.urls import NoReverseMatch, resolve, reverse
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
@@ -19,7 +18,7 @@ from extras.utils import FeatureQuery
 from utilities.forms import BootstrapMixin
 from utilities.forms import BootstrapMixin
 from utilities.permissions import get_permission_for_model
 from utilities.permissions import get_permission_for_model
 from utilities.templatetags.builtins.filters import render_markdown
 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
 from .utils import register_widget
 
 
 __all__ = (
 __all__ = (
@@ -170,8 +169,7 @@ class ObjectCountsWidget(DashboardWidget):
                 qs = model.objects.restrict(request.user, 'view')
                 qs = model.objects.restrict(request.user, 'view')
                 # Apply any specified filters
                 # Apply any specified filters
                 if filters := self.config.get('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)
                     filterset = getattr(resolve(url).func.view_class, 'filterset', None)
                     qs = filterset(params, qs).qs
                     qs = filterset(params, qs).qs
                     url = f'{url}?{params.urlencode()}'
                     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.cache import cache
 from django.core.validators import ValidationError
 from django.core.validators import ValidationError
 from django.db import models
 from django.db import models
-from django.http import HttpResponse, QueryDict
+from django.http import HttpResponse
 from django.urls import reverse
 from django.urls import reverse
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.formats import date_format
 from django.utils.formats import date_format
@@ -26,7 +26,7 @@ from netbox.models.features import (
     CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin,
     CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin,
 )
 )
 from utilities.querysets import RestrictedQuerySet
 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__ = (
 __all__ = (
     'ConfigRevision',
     'ConfigRevision',
@@ -462,8 +462,7 @@ class SavedFilter(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
 
 
     @property
     @property
     def url_params(self):
     def url_params(self):
-        qd = QueryDict(mutable=True)
-        qd.update(self.parameters)
+        qd = dict_to_querydict(self.parameters)
         return qd.urlencode()
         return qd.urlencode()
 
 
 
 

+ 3 - 2
netbox/utilities/templatetags/builtins/tags.py

@@ -1,6 +1,8 @@
 from django import template
 from django import template
 from django.http import QueryDict
 from django.http import QueryDict
 
 
+from utilities.utils import dict_to_querydict
+
 __all__ = (
 __all__ = (
     'badge',
     'badge',
     'checkmark',
     '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`)
         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.
         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
     url_params['return_url'] = return_url or context['request'].path
     return {
     return {
         'viewname': viewname,
         '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 import Count, OuterRef, Subquery
 from django.db.models.functions import Coalesce
 from django.db.models.functions import Coalesce
 from django.http import QueryDict
 from django.http import QueryDict
-from django.utils.html import escape
 from django.utils import timezone
 from django.utils import timezone
+from django.utils.datastructures import MultiValueDict
+from django.utils.html import escape
 from django.utils.timezone import localtime
 from django.utils.timezone import localtime
 from jinja2.sandbox import SandboxedEnvironment
 from jinja2.sandbox import SandboxedEnvironment
 from mptt.models import MPTTModel
 from mptt.models import MPTTModel
@@ -231,6 +232,19 @@ def dict_to_filter_params(d, prefix=''):
     return params
     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):
 def normalize_querydict(querydict):
     """
     """
     Convert a QueryDict to a normal, mutable dictionary, preserving list values. For example,
     Convert a QueryDict to a normal, mutable dictionary, preserving list values. For example,