Fixed #23460 -- Added literal `%s` support to extra() QuerySets.

This commit is contained in:
Matt Robenolt 2014-09-10 07:23:58 +00:00 committed by Tim Graham
parent 17557d068c
commit ef5f9b6ae8
4 changed files with 26 additions and 6 deletions

View File

@ -1775,7 +1775,8 @@ class Query(object):
entry_params = []
pos = entry.find("%s")
while pos != -1:
entry_params.append(next(param_iter))
if pos == 0 or entry[pos - 1] != '%':
entry_params.append(next(param_iter))
pos = entry.find("%s", pos + 2)
select_pairs[name] = (entry, entry_params)
# This is order preserving, since self.extra_select is an OrderedDict.

View File

@ -1144,11 +1144,12 @@ of the arguments is required, but you should use at least one of them.
select=OrderedDict([('a', '%s'), ('b', '%s')]),
select_params=('one', 'two'))
The only thing to be careful about when using select parameters in
``extra()`` is to avoid using the substring ``"%%s"`` (that's *two*
percent characters before the ``s``) in the select strings. Django's
tracking of parameters looks for ``%s`` and an escaped ``%`` character
like this isn't detected. That will lead to incorrect results.
If you need to use a literal ``%s`` inside your select string, use
the sequence ``%%s``.
.. versionchanged:: 1.8
Prior to 1.8, you were unable to escape a literal ``%s``.
* ``where`` / ``tables``

View File

@ -281,6 +281,9 @@ Models
Django uses whenever objects are loaded using the ORM. The method allows
customizing model loading behavior.
* ``extra(select={...})`` now allows you to escape a literal ``%s`` sequence
using ``%%s``.
Signals
^^^^^^^

View File

@ -1655,6 +1655,21 @@ class Queries5Tests(TestCase):
['<Note: n1>', '<Note: n2>']
)
def test_extra_select_literal_percent_s(self):
# Allow %%s to escape select clauses
self.assertEqual(
Note.objects.extra(select={'foo': "'%%s'"})[0].foo,
'%s'
)
self.assertEqual(
Note.objects.extra(select={'foo': "'%%s bar %%s'"})[0].foo,
'%s bar %s'
)
self.assertEqual(
Note.objects.extra(select={'foo': "'bar %%s'"})[0].foo,
'bar %s'
)
class SelectRelatedTests(TestCase):
def test_tickets_3045_3288(self):