diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index 2a3f72ee0..1ca162090 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -16,8 +16,9 @@ from core.models import ObjectType from extras.choices import BookmarkOrderingChoices from netbox.choices import ButtonColorChoices from utilities.permissions import get_permission_for_model +from utilities.querydict import dict_to_querydict from utilities.templatetags.builtins.filters import render_markdown -from utilities.utils import content_type_identifier, content_type_name, dict_to_querydict +from utilities.utils import content_type_identifier, content_type_name from utilities.views import get_viewname from .utils import register_widget diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 0c136e562..16f10b485 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -23,9 +23,9 @@ from netbox.models.features import ( CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin, ) from utilities.html import clean_html +from utilities.querydict import dict_to_querydict from utilities.querysets import RestrictedQuerySet from utilities.jinja2 import render_jinja2 -from utilities.utils import dict_to_querydict __all__ = ( 'Bookmark', diff --git a/netbox/extras/views.py b/netbox/extras/views.py index aa6e5718d..140610ac4 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -21,10 +21,11 @@ from netbox.views.generic.mixins import TableMixin from utilities.data import shallow_compare_dict from utilities.forms import ConfirmationForm, get_field_value from utilities.paginator import EnhancedPaginator, get_paginate_count +from utilities.querydict import normalize_querydict from utilities.request import copy_safe_request from utilities.rqworker import get_workers_for_queue from utilities.templatetags.builtins.filters import render_markdown -from utilities.utils import count_related, normalize_querydict +from utilities.utils import count_related from utilities.views import ContentTypePermissionRequiredMixin, get_viewname, register_model_view from . import filtersets, forms, tables from .models import * diff --git a/netbox/netbox/views/generic/object_views.py b/netbox/netbox/views/generic/object_views.py index db18ae859..1bd6e89d8 100644 --- a/netbox/netbox/views/generic/object_views.py +++ b/netbox/netbox/views/generic/object_views.py @@ -18,7 +18,8 @@ from utilities.error_handlers import handle_protectederror from utilities.exceptions import AbortRequest, PermissionsViolation from utilities.forms import ConfirmationForm, restrict_form_fields from utilities.permissions import get_permission_for_model -from utilities.utils import normalize_querydict, prepare_cloned_fields +from utilities.querydict import normalize_querydict +from utilities.utils import prepare_cloned_fields from utilities.views import GetReturnURLMixin, get_viewname from .base import BaseObjectView from .mixins import ActionsMixin, TableMixin diff --git a/netbox/utilities/querydict.py b/netbox/utilities/querydict.py new file mode 100644 index 000000000..190b296d6 --- /dev/null +++ b/netbox/utilities/querydict.py @@ -0,0 +1,38 @@ +from django.http import QueryDict +from django.utils.datastructures import MultiValueDict + +__all__ = ( + 'dict_to_querydict', + 'normalize_querydict', +) + + +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, + + QueryDict('foo=1&bar=2&bar=3&baz=') + + becomes: + + {'foo': '1', 'bar': ['2', '3'], 'baz': ''} + + This function is necessary because QueryDict does not provide any built-in mechanism which preserves multiple + values. + """ + return { + k: v if len(v) > 1 else v[0] for k, v in querydict.lists() + } diff --git a/netbox/utilities/templatetags/builtins/tags.py b/netbox/utilities/templatetags/builtins/tags.py index dc5d75f48..b386e13ca 100644 --- a/netbox/utilities/templatetags/builtins/tags.py +++ b/netbox/utilities/templatetags/builtins/tags.py @@ -1,8 +1,7 @@ from django import template -from django.http import QueryDict from extras.choices import CustomFieldTypeChoices -from utilities.utils import dict_to_querydict +from utilities.querydict import dict_to_querydict __all__ = ( 'badge', diff --git a/netbox/utilities/tests/test_utils.py b/netbox/utilities/tests/test_utils.py index 5bdd24108..289fa3a3a 100644 --- a/netbox/utilities/tests/test_utils.py +++ b/netbox/utilities/tests/test_utils.py @@ -2,7 +2,8 @@ from django.http import QueryDict from django.test import TestCase from utilities.data import deepmerge -from utilities.utils import dict_to_filter_params, normalize_querydict +from utilities.querydict import normalize_querydict +from utilities.utils import dict_to_filter_params class DictToFilterParamsTest(TestCase): diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index a7da5d542..62195e4c7 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -4,7 +4,6 @@ from django.db.models import Count, ManyToOneRel, OuterRef, Subquery from django.db.models.functions import Coalesce from django.http import QueryDict from django.utils import timezone -from django.utils.datastructures import MultiValueDict from django.utils.timezone import localtime from .string import title @@ -70,37 +69,6 @@ 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, - - QueryDict('foo=1&bar=2&bar=3&baz=') - - becomes: - - {'foo': '1', 'bar': ['2', '3'], 'baz': ''} - - This function is necessary because QueryDict does not provide any built-in mechanism which preserves multiple - values. - """ - return { - k: v if len(v) > 1 else v[0] for k, v in querydict.lists() - } - - def prepare_cloned_fields(instance): """ Generate a QueryDict comprising attributes from an object's clone() method.