1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Fixed #35529 -- Added support for positional arguments in querystring template tag.

This commit is contained in:
Giannis Terzopoulos 2024-12-02 16:38:22 +01:00 committed by Natalia
parent 7e41a7a47d
commit 33ee6b23c7
5 changed files with 135 additions and 30 deletions

View File

@ -387,6 +387,7 @@ answer newbie questions, and generally made Django that much better:
Georg "Hugo" Bauer <gb@hugo.westfalen.de>
Georgi Stanojevski <glisha@gmail.com>
Gerardo Orozco <gerardo.orozco.mosqueda@gmail.com>
Giannis Terzopoulos <terzo.giannis@gmail.com>
Gil Gonçalves <lursty@gmail.com>
Girish Kumar <girishkumarkh@gmail.com>
Girish Sontakke <girishsontakke7@gmail.com>

View File

@ -4,12 +4,13 @@ import re
import sys
import warnings
from collections import namedtuple
from collections.abc import Iterable
from collections.abc import Iterable, Mapping
from datetime import datetime
from itertools import cycle as itertools_cycle
from itertools import groupby
from django.conf import settings
from django.http import QueryDict
from django.utils import timezone
from django.utils.html import conditional_escape, escape, format_html
from django.utils.lorem_ipsum import paragraphs, words
@ -1170,11 +1171,13 @@ def now(parser, token):
@register.simple_tag(name="querystring", takes_context=True)
def querystring(context, query_dict=None, **kwargs):
def querystring(context, *args, **kwargs):
"""
Add, remove, and change parameters of a ``QueryDict`` and return the result
as a query string. If the ``query_dict`` argument is not provided, default
to ``request.GET``.
Modify and return a query string with updated parameters.
Add, remove, or change parameters in a given `QueryDict` or `dict`,
returning the result as a query string. Any arguments provided will
override existing keys. If no arguments are given, `request.GET` is used.
For example::
@ -1188,21 +1191,44 @@ def querystring(context, query_dict=None, **kwargs):
{% querystring page=page_obj.next_page_number %}
A custom ``QueryDict`` can also be used::
A custom `QueryDict` can be used::
{% querystring my_query_dict foo=3 %}
Or a `dict` can be used::
{% querystring my_dict foo=3 %}
As can a mix of multiple args and kwargs::
{% querystring my_dict my_query_dict foo=3 bar=None %}
"""
if query_dict is None:
query_dict = context.request.GET
query_dict = query_dict.copy()
for key, value in kwargs.items():
if value is None:
if key in query_dict:
del query_dict[key]
elif isinstance(value, Iterable) and not isinstance(value, str):
query_dict.setlist(key, value)
else:
query_dict[key] = value
try:
query_dict = context.request.GET.copy()
except AttributeError:
if not args:
raise
query_dict = QueryDict(mutable=True)
for d in args + (kwargs,):
if d is None:
continue
if not isinstance(d, Mapping):
raise TemplateSyntaxError(
"querystring requires mappings for positional arguments (received %r "
"instead)." % d
)
for key, value in d.items():
if not isinstance(key, str):
raise TemplateSyntaxError(
"querystring requires strings for mapping keys (received %r "
"instead)." % key
)
if value is None:
query_dict.pop(key, None)
elif isinstance(value, Iterable) and not isinstance(value, str):
query_dict.setlist(key, value)
else:
query_dict[key] = value
if not query_dict:
return ""
query_string = query_dict.urlencode()

View File

@ -961,20 +961,20 @@ output (as a string) inside a variable. This is useful if you want to use
Outputs a URL-encoded formatted query string based on the provided parameters.
This tag requires a :class:`~django.http.QueryDict` instance, which defaults to
:attr:`request.GET <django.http.HttpRequest.GET>` if none is provided.
This tag requires a :class:`~django.http.QueryDict` or :class:`dict` instance,
which defaults to :attr:`request.GET <django.http.HttpRequest.GET>` if none is
provided.
If the :class:`~django.http.QueryDict` is empty and no additional parameters
are provided, an empty string is returned. A non-empty result includes a
leading ``"?"``.
A non-empty result includes a leading ``"?"``, whereas an empty result returns
an empty string.
.. admonition:: Using ``request.GET`` as default
To use ``request.GET`` as the default ``QueryDict`` instance, the
``django.template.context_processors.request`` context processor should be
enabled. If it's not enabled, you must either explicitly pass the
``request`` object into the template context, or provide a ``QueryDict``
instance to this tag.
``request`` object into the template context, or pass the ``request.GET``
``QueryDict`` instance to this tag.
Basic usage
~~~~~~~~~~~
@ -993,16 +993,24 @@ Outputs the current query string verbatim. So if the query string is
Outputs the current query string with the addition of the ``size`` parameter.
Following the previous example, the output would be ``?color=green&size=M``.
Custom QueryDict
~~~~~~~~~~~~~~~~
Customizing the default QueryDict
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: html+django
{% querystring my_query_dict %}
{% querystring my_query_dict my_dict %}
You can provide a custom ``QueryDict`` to be used instead of ``request.GET``.
So if ``my_query_dict`` is ``<QueryDict: {'color': ['blue']}>``, this outputs
``?color=blue``.
You can provide custom ``QueryDict`` or ``dict`` instances as positional
arguments to be used instead of ``request.GET``. Key values from later
arguments take precedence over previously seen keys. So if ``my_query_dict``
is ``<QueryDict: {'color': ['blue'], 'size': ['S']}>`` and ``my_dict`` is
``{'color': 'orange', 'fabric': 'silk'}``, this outputs
``?color=orange&size=S&fabric=silk``.
.. versionchanged:: 5.2
Support for multiple positional arguments and positional arguments of
type ``dict`` were added.
Setting items
~~~~~~~~~~~~~

View File

@ -360,6 +360,9 @@ Templates
* The new :meth:`~django.template.Library.simple_block_tag` decorator enables
the creation of simple block tags, which can accept and use a section of the
template.
* The :ttag:`querystring` template tag now supports multiple positional
arguments, which must be mappings, such as :class:`~django.http.QueryDict`
or ``dict``.
Tests
~~~~~

View File

@ -1,5 +1,6 @@
from django.http import QueryDict
from django.template import RequestContext
from django.template.base import TemplateSyntaxError
from django.test import RequestFactory, SimpleTestCase
from ..utils import setup
@ -13,6 +14,10 @@ class QueryStringTagTests(SimpleTestCase):
output = self.engine.render_to_string(template_name, context)
self.assertEqual(output, expected)
def assertTemplateSyntaxError(self, template_name, context, expected):
with self.assertRaisesMessage(TemplateSyntaxError, expected):
self.engine.render_to_string(template_name, context)
@setup({"test_querystring_empty_get_params": "{% querystring %}"})
def test_querystring_empty_get_params(self):
context = RequestContext(self.request_factory.get("/"))
@ -65,6 +70,14 @@ class QueryStringTagTests(SimpleTestCase):
context = RequestContext(request)
self.assertRenderEqual("querystring_remove", context, expected="?a=1")
@setup({"querystring_remove_dict": "{% querystring my_dict a=1 %}"})
def test_querystring_remove_from_dict(self):
request = self.request_factory.get("/", {"test": "value"})
template = self.engine.get_template("querystring_remove_dict")
context = RequestContext(request, {"my_dict": {"test": None}})
output = template.render(context)
self.assertEqual(output, "?a=1")
@setup({"querystring_remove_nonexistent": "{% querystring nonexistent=None a=1 %}"})
def test_querystring_remove_nonexistent(self):
request = self.request_factory.get("/", {"x": "y", "a": "1"})
@ -73,6 +86,60 @@ class QueryStringTagTests(SimpleTestCase):
"querystring_remove_nonexistent", context, expected="?x=y&amp;a=1"
)
@setup({"querystring_same_arg": "{% querystring a=1 a=2 %}"})
def test_querystring_same_arg(self):
msg = "'querystring' received multiple values for keyword argument 'a'"
self.assertTemplateSyntaxError("querystring_same_arg", {}, msg)
@setup({"querystring_variable": "{% querystring a=a %}"})
def test_querystring_variable(self):
request = self.request_factory.get("/")
template = self.engine.get_template("querystring_variable")
context = RequestContext(request, {"a": 1})
output = template.render(context)
self.assertEqual(output, "?a=1")
@setup({"querystring_dict": "{% querystring my_dict %}"})
def test_querystring_dict(self):
context = {"my_dict": {"a": 1}}
output = self.engine.render_to_string("querystring_dict", context)
self.assertEqual(output, "?a=1")
@setup({"querystring_dict_list": "{% querystring my_dict %}"})
def test_querystring_dict_list_values(self):
context = {"my_dict": {"a": [1, 2]}}
output = self.engine.render_to_string("querystring_dict_list", context)
self.assertEqual(output, "?a=1&amp;a=2")
@setup({"querystring_non_string_dict_keys": "{% querystring my_dict %}"})
def test_querystring_non_string_dict_keys(self):
context = {"my_dict": {0: 1}}
msg = "querystring requires strings for mapping keys (received 0 instead)."
self.assertTemplateSyntaxError("querystring_non_string_dict_keys", context, msg)
@setup({"querystring_non_dict_args": "{% querystring somevar %}"})
def test_querystring_non_dict_args(self):
context = {"somevar": 0}
msg = (
"querystring requires mappings for positional arguments (received 0 "
"instead)."
)
self.assertTemplateSyntaxError("querystring_non_dict_args", context, msg)
@setup(
{
"querystring_multiple_args_override": (
"{% querystring my_dict my_query_dict x=3 %}"
)
}
)
def test_querystring_multiple_args_override(self):
context = {"my_dict": {"x": 0}, "my_query_dict": QueryDict("a=1&b=2")}
output = self.engine.render_to_string(
"querystring_multiple_args_override", context
)
self.assertEqual(output, "?x=3&amp;a=1&amp;b=2")
@setup({"querystring_list": "{% querystring a=my_list %}"})
def test_querystring_add_list(self):
request = self.request_factory.get("/")