mirror of
https://github.com/django/django.git
synced 2025-01-03 06:55:47 +00:00
Fixed #10941 -- Added {% query_string %} template tag.
This commit is contained in:
parent
718b32c691
commit
e67d3580ed
@ -10,6 +10,7 @@ from itertools import groupby
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
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.itercompat import is_iterable
|
||||||
from django.utils.lorem_ipsum import paragraphs, words
|
from django.utils.lorem_ipsum import paragraphs, words
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
@ -1167,6 +1168,46 @@ def now(parser, token):
|
|||||||
return NowNode(format_string, asvar)
|
return NowNode(format_string, asvar)
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag(takes_context=True)
|
||||||
|
def query_string(context, query_dict=None, **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``.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
{% query_string foo=3 %}
|
||||||
|
|
||||||
|
To remove a key::
|
||||||
|
|
||||||
|
{% query_string foo=None %}
|
||||||
|
|
||||||
|
To use with pagination::
|
||||||
|
|
||||||
|
{% query_string page=page_obj.next_page_number %}
|
||||||
|
|
||||||
|
A custom ``QueryDict`` can also be used::
|
||||||
|
|
||||||
|
{% query_string my_query_dict foo=3 %}
|
||||||
|
"""
|
||||||
|
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 is_iterable(value) 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()
|
||||||
|
return f"?{query_string}"
|
||||||
|
|
||||||
|
|
||||||
@register.tag
|
@register.tag
|
||||||
def regroup(parser, token):
|
def regroup(parser, token):
|
||||||
"""
|
"""
|
||||||
|
@ -953,6 +953,78 @@ output (as a string) inside a variable. This is useful if you want to use
|
|||||||
{% now "Y" as current_year %}
|
{% now "Y" as current_year %}
|
||||||
{% blocktranslate %}Copyright {{ current_year }}{% endblocktranslate %}
|
{% blocktranslate %}Copyright {{ current_year }}{% endblocktranslate %}
|
||||||
|
|
||||||
|
.. templatetag:: query_string
|
||||||
|
|
||||||
|
``query_string``
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. versionadded:: 5.1
|
||||||
|
|
||||||
|
Outputs the query string from a given :class:`~django.http.QueryDict` instance,
|
||||||
|
if provided, or ``request.GET`` if not and the
|
||||||
|
``django.template.context_processors.request`` context processor is enabled.
|
||||||
|
If the ``QueryDict`` is empty, then the output will be an empty string.
|
||||||
|
Otherwise, the query string will be returned with a leading ``"?"``.
|
||||||
|
|
||||||
|
If not using the ``django.template.context_processors.request`` context
|
||||||
|
processor, you must pass either the ``request`` into the template context or a
|
||||||
|
``QueryDict`` instance into this tag.
|
||||||
|
|
||||||
|
The following example outputs the current query string verbatim. So if the
|
||||||
|
query string is ``?color=green&size=M``, the output would be
|
||||||
|
``?color=green&size=M``:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{% query_string %}
|
||||||
|
|
||||||
|
You can also pass in a custom ``QueryDict`` that will be used instead of
|
||||||
|
``request.GET``:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{% query_string my_query_dict %}
|
||||||
|
|
||||||
|
Each keyword argument will be added to the query string, replacing any existing
|
||||||
|
value for that key. With the query string ``?color=blue``, the following would
|
||||||
|
result in ``?color=red&size=S``:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{% query_string color="red" size="S" %}
|
||||||
|
|
||||||
|
It is possible to remove parameters by passing ``None`` as a value. With the
|
||||||
|
query string ``?color=blue&size=M``, the following would result in ``?size=M``:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{% query_string color=None %}
|
||||||
|
|
||||||
|
If the given parameter is a list, the value will remain as a list. For example,
|
||||||
|
if ``my_list`` is set to ``["red", "blue"]``, the following would result in
|
||||||
|
``?color=red&color=blue``:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{% query_string color=my_list %}
|
||||||
|
|
||||||
|
A common example of using this tag is to preserve the current query string when
|
||||||
|
displaying a page of results, while adding a link to the next and previous
|
||||||
|
pages of results. For example, if the paginator is currently on page 3, and
|
||||||
|
the current query string is ``?color=blue&size=M&page=3``, the following code
|
||||||
|
would output ``?color=blue&size=M&page=4``:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{% query_string page=page.next_page_number %}
|
||||||
|
|
||||||
|
You can also store the value in a variable, for example, if you need multiple
|
||||||
|
links to the same page with syntax such as:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{% query_string page=page.next_page_number as next_page %}
|
||||||
|
|
||||||
.. templatetag:: regroup
|
.. templatetag:: regroup
|
||||||
|
|
||||||
``regroup``
|
``regroup``
|
||||||
|
@ -198,6 +198,11 @@ Templates
|
|||||||
be made available on the ``Template`` instance. Such data may be used, for
|
be made available on the ``Template`` instance. Such data may be used, for
|
||||||
example, by the template loader, or other template clients.
|
example, by the template loader, or other template clients.
|
||||||
|
|
||||||
|
* The new :ttag:`{% query_string %} <query_string>` template tag allows
|
||||||
|
changing a :class:`~django.http.QueryDict` instance for use in links, for
|
||||||
|
example, to generate a link to the next page while keeping any filtering
|
||||||
|
options in place.
|
||||||
|
|
||||||
Tests
|
Tests
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
|
100
tests/template_tests/syntax_tests/test_query_string.py
Normal file
100
tests/template_tests/syntax_tests/test_query_string.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
from django.http import QueryDict
|
||||||
|
from django.template import RequestContext
|
||||||
|
from django.test import RequestFactory, SimpleTestCase
|
||||||
|
|
||||||
|
from ..utils import setup
|
||||||
|
|
||||||
|
|
||||||
|
class QueryStringTagTests(SimpleTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.request_factory = RequestFactory()
|
||||||
|
|
||||||
|
@setup({"query_string_empty": "{% query_string %}"})
|
||||||
|
def test_query_string_empty(self):
|
||||||
|
request = self.request_factory.get("/")
|
||||||
|
template = self.engine.get_template("query_string_empty")
|
||||||
|
context = RequestContext(request)
|
||||||
|
output = template.render(context)
|
||||||
|
self.assertEqual(output, "")
|
||||||
|
|
||||||
|
@setup({"query_string_non_empty": "{% query_string %}"})
|
||||||
|
def test_query_string_non_empty(self):
|
||||||
|
request = self.request_factory.get("/", {"a": "b"})
|
||||||
|
template = self.engine.get_template("query_string_non_empty")
|
||||||
|
context = RequestContext(request)
|
||||||
|
output = template.render(context)
|
||||||
|
self.assertEqual(output, "?a=b")
|
||||||
|
|
||||||
|
@setup({"query_string_multiple": "{% query_string %}"})
|
||||||
|
def test_query_string_multiple(self):
|
||||||
|
request = self.request_factory.get("/", {"x": "y", "a": "b"})
|
||||||
|
template = self.engine.get_template("query_string_multiple")
|
||||||
|
context = RequestContext(request)
|
||||||
|
output = template.render(context)
|
||||||
|
self.assertEqual(output, "?x=y&a=b")
|
||||||
|
|
||||||
|
@setup({"query_string_replace": "{% query_string a=1 %}"})
|
||||||
|
def test_query_string_replace(self):
|
||||||
|
request = self.request_factory.get("/", {"x": "y", "a": "b"})
|
||||||
|
template = self.engine.get_template("query_string_replace")
|
||||||
|
context = RequestContext(request)
|
||||||
|
output = template.render(context)
|
||||||
|
self.assertEqual(output, "?x=y&a=1")
|
||||||
|
|
||||||
|
@setup({"query_string_add": "{% query_string test_new='something' %}"})
|
||||||
|
def test_query_string_add(self):
|
||||||
|
request = self.request_factory.get("/", {"a": "b"})
|
||||||
|
template = self.engine.get_template("query_string_add")
|
||||||
|
context = RequestContext(request)
|
||||||
|
output = template.render(context)
|
||||||
|
self.assertEqual(output, "?a=b&test_new=something")
|
||||||
|
|
||||||
|
@setup({"query_string_remove": "{% query_string test=None a=1 %}"})
|
||||||
|
def test_query_string_remove(self):
|
||||||
|
request = self.request_factory.get("/", {"test": "value", "a": "1"})
|
||||||
|
template = self.engine.get_template("query_string_remove")
|
||||||
|
context = RequestContext(request)
|
||||||
|
output = template.render(context)
|
||||||
|
self.assertEqual(output, "?a=1")
|
||||||
|
|
||||||
|
@setup(
|
||||||
|
{"query_string_remove_nonexistent": "{% query_string nonexistent=None a=1 %}"}
|
||||||
|
)
|
||||||
|
def test_query_string_remove_nonexistent(self):
|
||||||
|
request = self.request_factory.get("/", {"x": "y", "a": "1"})
|
||||||
|
template = self.engine.get_template("query_string_remove_nonexistent")
|
||||||
|
context = RequestContext(request)
|
||||||
|
output = template.render(context)
|
||||||
|
self.assertEqual(output, "?x=y&a=1")
|
||||||
|
|
||||||
|
@setup({"query_string_list": "{% query_string a=my_list %}"})
|
||||||
|
def test_query_string_add_list(self):
|
||||||
|
request = self.request_factory.get("/")
|
||||||
|
template = self.engine.get_template("query_string_list")
|
||||||
|
context = RequestContext(request, {"my_list": [2, 3]})
|
||||||
|
output = template.render(context)
|
||||||
|
self.assertEqual(output, "?a=2&a=3")
|
||||||
|
|
||||||
|
@setup({"query_string_query_dict": "{% query_string request.GET a=2 %}"})
|
||||||
|
def test_query_string_with_explicit_query_dict(self):
|
||||||
|
request = self.request_factory.get("/", {"a": 1})
|
||||||
|
output = self.engine.render_to_string(
|
||||||
|
"query_string_query_dict", {"request": request}
|
||||||
|
)
|
||||||
|
self.assertEqual(output, "?a=2")
|
||||||
|
|
||||||
|
@setup(
|
||||||
|
{"query_string_query_dict_no_request": "{% query_string my_query_dict a=2 %}"}
|
||||||
|
)
|
||||||
|
def test_query_string_with_explicit_query_dict_and_no_request(self):
|
||||||
|
context = {"my_query_dict": QueryDict("a=1&b=2")}
|
||||||
|
output = self.engine.render_to_string(
|
||||||
|
"query_string_query_dict_no_request", context
|
||||||
|
)
|
||||||
|
self.assertEqual(output, "?a=2&b=2")
|
||||||
|
|
||||||
|
@setup({"query_string_no_request_no_query_dict": "{% query_string %}"})
|
||||||
|
def test_query_string_without_request_or_explicit_query_dict(self):
|
||||||
|
msg = "'Context' object has no attribute 'request'"
|
||||||
|
with self.assertRaisesMessage(AttributeError, msg):
|
||||||
|
self.engine.render_to_string("query_string_no_request_no_query_dict")
|
Loading…
Reference in New Issue
Block a user