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:
parent
7e41a7a47d
commit
33ee6b23c7
1
AUTHORS
1
AUTHORS
@ -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>
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
~~~~~~~~~~~~~
|
||||
|
@ -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
|
||||
~~~~~
|
||||
|
@ -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&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&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&a=1&b=2")
|
||||
|
||||
@setup({"querystring_list": "{% querystring a=my_list %}"})
|
||||
def test_querystring_add_list(self):
|
||||
request = self.request_factory.get("/")
|
||||
|
Loading…
Reference in New Issue
Block a user