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>
|
Georg "Hugo" Bauer <gb@hugo.westfalen.de>
|
||||||
Georgi Stanojevski <glisha@gmail.com>
|
Georgi Stanojevski <glisha@gmail.com>
|
||||||
Gerardo Orozco <gerardo.orozco.mosqueda@gmail.com>
|
Gerardo Orozco <gerardo.orozco.mosqueda@gmail.com>
|
||||||
|
Giannis Terzopoulos <terzo.giannis@gmail.com>
|
||||||
Gil Gonçalves <lursty@gmail.com>
|
Gil Gonçalves <lursty@gmail.com>
|
||||||
Girish Kumar <girishkumarkh@gmail.com>
|
Girish Kumar <girishkumarkh@gmail.com>
|
||||||
Girish Sontakke <girishsontakke7@gmail.com>
|
Girish Sontakke <girishsontakke7@gmail.com>
|
||||||
|
@ -4,12 +4,13 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable, Mapping
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from itertools import cycle as itertools_cycle
|
from itertools import cycle as itertools_cycle
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.http import QueryDict
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.html import conditional_escape, escape, format_html
|
from django.utils.html import conditional_escape, escape, format_html
|
||||||
from django.utils.lorem_ipsum import paragraphs, words
|
from django.utils.lorem_ipsum import paragraphs, words
|
||||||
@ -1170,11 +1171,13 @@ def now(parser, token):
|
|||||||
|
|
||||||
|
|
||||||
@register.simple_tag(name="querystring", takes_context=True)
|
@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
|
Modify and return a query string with updated parameters.
|
||||||
as a query string. If the ``query_dict`` argument is not provided, default
|
|
||||||
to ``request.GET``.
|
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::
|
For example::
|
||||||
|
|
||||||
@ -1188,21 +1191,44 @@ def querystring(context, query_dict=None, **kwargs):
|
|||||||
|
|
||||||
{% querystring page=page_obj.next_page_number %}
|
{% 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 %}
|
{% 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:
|
try:
|
||||||
query_dict = context.request.GET
|
query_dict = context.request.GET.copy()
|
||||||
query_dict = query_dict.copy()
|
except AttributeError:
|
||||||
for key, value in kwargs.items():
|
if not args:
|
||||||
if value is None:
|
raise
|
||||||
if key in query_dict:
|
query_dict = QueryDict(mutable=True)
|
||||||
del query_dict[key]
|
for d in args + (kwargs,):
|
||||||
elif isinstance(value, Iterable) and not isinstance(value, str):
|
if d is None:
|
||||||
query_dict.setlist(key, value)
|
continue
|
||||||
else:
|
if not isinstance(d, Mapping):
|
||||||
query_dict[key] = value
|
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:
|
if not query_dict:
|
||||||
return ""
|
return ""
|
||||||
query_string = query_dict.urlencode()
|
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.
|
Outputs a URL-encoded formatted query string based on the provided parameters.
|
||||||
|
|
||||||
This tag requires a :class:`~django.http.QueryDict` instance, which defaults to
|
This tag requires a :class:`~django.http.QueryDict` or :class:`dict` instance,
|
||||||
:attr:`request.GET <django.http.HttpRequest.GET>` if none is provided.
|
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
|
A non-empty result includes a leading ``"?"``, whereas an empty result returns
|
||||||
are provided, an empty string is returned. A non-empty result includes a
|
an empty string.
|
||||||
leading ``"?"``.
|
|
||||||
|
|
||||||
.. admonition:: Using ``request.GET`` as default
|
.. admonition:: Using ``request.GET`` as default
|
||||||
|
|
||||||
To use ``request.GET`` as the default ``QueryDict`` instance, the
|
To use ``request.GET`` as the default ``QueryDict`` instance, the
|
||||||
``django.template.context_processors.request`` context processor should be
|
``django.template.context_processors.request`` context processor should be
|
||||||
enabled. If it's not enabled, you must either explicitly pass the
|
enabled. If it's not enabled, you must either explicitly pass the
|
||||||
``request`` object into the template context, or provide a ``QueryDict``
|
``request`` object into the template context, or pass the ``request.GET``
|
||||||
instance to this tag.
|
``QueryDict`` instance to this tag.
|
||||||
|
|
||||||
Basic usage
|
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.
|
Outputs the current query string with the addition of the ``size`` parameter.
|
||||||
Following the previous example, the output would be ``?color=green&size=M``.
|
Following the previous example, the output would be ``?color=green&size=M``.
|
||||||
|
|
||||||
Custom QueryDict
|
Customizing the default QueryDict
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. code-block:: html+django
|
.. 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``.
|
You can provide custom ``QueryDict`` or ``dict`` instances as positional
|
||||||
So if ``my_query_dict`` is ``<QueryDict: {'color': ['blue']}>``, this outputs
|
arguments to be used instead of ``request.GET``. Key values from later
|
||||||
``?color=blue``.
|
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
|
Setting items
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
@ -360,6 +360,9 @@ Templates
|
|||||||
* The new :meth:`~django.template.Library.simple_block_tag` decorator enables
|
* 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
|
the creation of simple block tags, which can accept and use a section of the
|
||||||
template.
|
template.
|
||||||
|
* The :ttag:`querystring` template tag now supports multiple positional
|
||||||
|
arguments, which must be mappings, such as :class:`~django.http.QueryDict`
|
||||||
|
or ``dict``.
|
||||||
|
|
||||||
Tests
|
Tests
|
||||||
~~~~~
|
~~~~~
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from django.http import QueryDict
|
from django.http import QueryDict
|
||||||
from django.template import RequestContext
|
from django.template import RequestContext
|
||||||
|
from django.template.base import TemplateSyntaxError
|
||||||
from django.test import RequestFactory, SimpleTestCase
|
from django.test import RequestFactory, SimpleTestCase
|
||||||
|
|
||||||
from ..utils import setup
|
from ..utils import setup
|
||||||
@ -13,6 +14,10 @@ class QueryStringTagTests(SimpleTestCase):
|
|||||||
output = self.engine.render_to_string(template_name, context)
|
output = self.engine.render_to_string(template_name, context)
|
||||||
self.assertEqual(output, expected)
|
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 %}"})
|
@setup({"test_querystring_empty_get_params": "{% querystring %}"})
|
||||||
def test_querystring_empty_get_params(self):
|
def test_querystring_empty_get_params(self):
|
||||||
context = RequestContext(self.request_factory.get("/"))
|
context = RequestContext(self.request_factory.get("/"))
|
||||||
@ -65,6 +70,14 @@ class QueryStringTagTests(SimpleTestCase):
|
|||||||
context = RequestContext(request)
|
context = RequestContext(request)
|
||||||
self.assertRenderEqual("querystring_remove", context, expected="?a=1")
|
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 %}"})
|
@setup({"querystring_remove_nonexistent": "{% querystring nonexistent=None a=1 %}"})
|
||||||
def test_querystring_remove_nonexistent(self):
|
def test_querystring_remove_nonexistent(self):
|
||||||
request = self.request_factory.get("/", {"x": "y", "a": "1"})
|
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"
|
"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 %}"})
|
@setup({"querystring_list": "{% querystring a=my_list %}"})
|
||||||
def test_querystring_add_list(self):
|
def test_querystring_add_list(self):
|
||||||
request = self.request_factory.get("/")
|
request = self.request_factory.get("/")
|
||||||
|
Loading…
Reference in New Issue
Block a user