Version in base suite: 1.11.23-1~deb10u1
Base version: python-django_1.11.23-1~deb10u1
Target version: python-django_1.11.27-1~deb10u1
Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/python-django/python-django_1.11.23-1~deb10u1.dsc
Target file: /srv/ftp-master.debian.org/policy/pool/main/p/python-django/python-django_1.11.27-1~deb10u1.dsc
Django.egg-info/PKG-INFO | 4 -
Django.egg-info/SOURCES.txt | 4 +
PKG-INFO | 4 -
debian/changelog | 21 +++++++
django/__init__.py | 2
django/contrib/auth/forms.py | 30 ++++++++--
django/contrib/postgres/fields/hstore.py | 2
django/contrib/postgres/fields/jsonb.py | 2
django/contrib/postgres/lookups.py | 2
django/forms/widgets.py | 2
docs/releases/1.11.24.txt | 15 +++++
docs/releases/1.11.25.txt | 14 ++++
docs/releases/1.11.26.txt | 15 +++++
docs/releases/1.11.27.txt | 31 ++++++++++
docs/releases/index.txt | 4 +
docs/releases/security.txt | 55 ++++++++++++++++++
tests/auth_tests/test_forms.py | 42 ++++++++++++++
tests/forms_tests/widget_tests/test_checkboxinput.py | 5 +
tests/postgres_tests/test_array.py | 11 +++
tests/postgres_tests/test_hstore.py | 15 +++++
tests/postgres_tests/test_json.py | 56 ++++++++++++++++++-
21 files changed, 323 insertions(+), 13 deletions(-)
diff -Nru python-django-1.11.23/Django.egg-info/PKG-INFO python-django-1.11.27/Django.egg-info/PKG-INFO
--- python-django-1.11.23/Django.egg-info/PKG-INFO 2019-08-01 08:44:01.000000000 +0000
+++ python-django-1.11.27/Django.egg-info/PKG-INFO 2019-12-18 08:33:12.000000000 +0000
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: Django
-Version: 1.11.23
+Version: 1.11.27
Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
Home-page: https://www.djangoproject.com/
Author: Django Software Foundation
@@ -27,5 +27,5 @@
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Provides-Extra: argon2
Provides-Extra: bcrypt
+Provides-Extra: argon2
diff -Nru python-django-1.11.23/Django.egg-info/SOURCES.txt python-django-1.11.27/Django.egg-info/SOURCES.txt
--- python-django-1.11.23/Django.egg-info/SOURCES.txt 2019-08-01 08:44:02.000000000 +0000
+++ python-django-1.11.27/Django.egg-info/SOURCES.txt 2019-12-18 08:33:13.000000000 +0000
@@ -3551,6 +3551,10 @@
docs/releases/1.11.21.txt
docs/releases/1.11.22.txt
docs/releases/1.11.23.txt
+docs/releases/1.11.24.txt
+docs/releases/1.11.25.txt
+docs/releases/1.11.26.txt
+docs/releases/1.11.27.txt
docs/releases/1.11.3.txt
docs/releases/1.11.4.txt
docs/releases/1.11.5.txt
diff -Nru python-django-1.11.23/PKG-INFO python-django-1.11.27/PKG-INFO
--- python-django-1.11.23/PKG-INFO 2019-08-01 08:44:08.000000000 +0000
+++ python-django-1.11.27/PKG-INFO 2019-12-18 08:33:15.000000000 +0000
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: Django
-Version: 1.11.23
+Version: 1.11.27
Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
Home-page: https://www.djangoproject.com/
Author: Django Software Foundation
@@ -27,5 +27,5 @@
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Provides-Extra: argon2
Provides-Extra: bcrypt
+Provides-Extra: argon2
diff -Nru python-django-1.11.23/debian/changelog python-django-1.11.27/debian/changelog
--- python-django-1.11.23/debian/changelog 2019-08-08 15:00:04.000000000 +0000
+++ python-django-1.11.27/debian/changelog 2020-01-06 15:35:55.000000000 +0000
@@ -1,3 +1,24 @@
+python-django (1:1.11.27-1~deb10u1) buster-security; urgency=high
+
+ * New upstream security release. (Closes: #946937)
+
+
+ - CVE-2019-19844: Potential account hijack via password reset form.
+
+ By submitting a suitably crafted email address making use of Unicode
+ characters, that compared equal to an existing user email when
+ lower-cased for comparison, an attacker could be sent a password reset
+ token for the matched account.
+
+ In order to avoid this vulnerability, password reset requests now compare
+ the submitted email using the stricter, recommended algorithm for
+ case-insensitive comparison of two identifiers from Unicode Technical
+ Report 36, section 2.11.2(B)(2). Upon a match, the email containing the
+ reset token will be sent to the email address on record rather than the
+ submitted address.
+
+ -- Chris Lamb Mon, 06 Jan 2020 15:35:55 +0000
+
python-django (1:1.11.23-1~deb10u1) buster-security; urgency=high
* New upstream security release.
diff -Nru python-django-1.11.23/django/__init__.py python-django-1.11.27/django/__init__.py
--- python-django-1.11.23/django/__init__.py 2019-08-01 08:43:22.000000000 +0000
+++ python-django-1.11.27/django/__init__.py 2019-12-18 08:32:18.000000000 +0000
@@ -2,7 +2,7 @@
from django.utils.version import get_version
-VERSION = (1, 11, 23, 'final', 0)
+VERSION = (1, 11, 27, 'final', 0)
__version__ = get_version(VERSION)
diff -Nru python-django-1.11.23/django/contrib/auth/forms.py python-django-1.11.27/django/contrib/auth/forms.py
--- python-django-1.11.23/django/contrib/auth/forms.py 2019-08-01 08:35:35.000000000 +0000
+++ python-django-1.11.27/django/contrib/auth/forms.py 2019-12-18 08:31:53.000000000 +0000
@@ -16,12 +16,27 @@
from django.template import loader
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
+from django.utils.six import PY3
from django.utils.text import capfirst
from django.utils.translation import ugettext, ugettext_lazy as _
UserModel = get_user_model()
+def _unicode_ci_compare(s1, s2):
+ """
+ Perform case-insensitive comparison of two identifiers, using the
+ recommended algorithm from Unicode Technical Report 36, section
+ 2.11.2(B)(2).
+ """
+ normalized1 = unicodedata.normalize('NFKC', s1)
+ normalized2 = unicodedata.normalize('NFKC', s2)
+ if PY3:
+ return normalized1.casefold() == normalized2.casefold()
+ # lower() is the best alternative available on Python 2.
+ return normalized1.lower() == normalized2.lower()
+
+
class ReadOnlyPasswordHashWidget(forms.Widget):
template_name = 'auth/widgets/read_only_password_hash.html'
@@ -249,11 +264,16 @@
that prevent inactive users and users with unusable passwords from
resetting their password.
"""
+ email_field_name = UserModel.get_email_field_name()
active_users = UserModel._default_manager.filter(**{
- '%s__iexact' % UserModel.get_email_field_name(): email,
+ '%s__iexact' % email_field_name: email,
'is_active': True,
})
- return (u for u in active_users if u.has_usable_password())
+ return (
+ u for u in active_users
+ if u.has_usable_password() and
+ _unicode_ci_compare(email, getattr(u, email_field_name))
+ )
def save(self, domain_override=None,
subject_template_name='registration/password_reset_subject.txt',
@@ -266,6 +286,7 @@
user.
"""
email = self.cleaned_data["email"]
+ email_field_name = UserModel.get_email_field_name()
for user in self.get_users(email):
if not domain_override:
current_site = get_current_site(request)
@@ -273,8 +294,9 @@
domain = current_site.domain
else:
site_name = domain = domain_override
+ user_email = getattr(user, email_field_name)
context = {
- 'email': email,
+ 'email': user_email,
'domain': domain,
'site_name': site_name,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
@@ -286,7 +308,7 @@
context.update(extra_email_context)
self.send_mail(
subject_template_name, email_template_name, context, from_email,
- email, html_email_template_name=html_email_template_name,
+ user_email, html_email_template_name=html_email_template_name,
)
diff -Nru python-django-1.11.23/django/contrib/postgres/fields/hstore.py python-django-1.11.27/django/contrib/postgres/fields/hstore.py
--- python-django-1.11.23/django/contrib/postgres/fields/hstore.py 2019-08-01 08:35:36.000000000 +0000
+++ python-django-1.11.27/django/contrib/postgres/fields/hstore.py 2019-12-18 08:31:54.000000000 +0000
@@ -86,7 +86,7 @@
def as_sql(self, compiler, connection):
lhs, params = compiler.compile(self.lhs)
- return '(%s -> %%s)' % lhs, [self.key_name] + params
+ return '(%s -> %%s)' % lhs, tuple(params) + (self.key_name,)
class KeyTransformFactory(object):
diff -Nru python-django-1.11.23/django/contrib/postgres/fields/jsonb.py python-django-1.11.27/django/contrib/postgres/fields/jsonb.py
--- python-django-1.11.23/django/contrib/postgres/fields/jsonb.py 2019-08-01 08:35:36.000000000 +0000
+++ python-django-1.11.27/django/contrib/postgres/fields/jsonb.py 2019-12-18 08:31:54.000000000 +0000
@@ -107,7 +107,7 @@
lookup = int(self.key_name)
except ValueError:
lookup = self.key_name
- return '(%s %s %%s)' % (lhs, self.operator), [lookup] + params
+ return '(%s %s %%s)' % (lhs, self.operator), tuple(params) + (lookup,)
class KeyTextTransform(KeyTransform):
diff -Nru python-django-1.11.23/django/contrib/postgres/lookups.py python-django-1.11.27/django/contrib/postgres/lookups.py
--- python-django-1.11.23/django/contrib/postgres/lookups.py 2019-08-01 08:35:36.000000000 +0000
+++ python-django-1.11.27/django/contrib/postgres/lookups.py 2019-12-18 08:31:54.000000000 +0000
@@ -8,7 +8,7 @@
def as_sql(self, qn, connection):
lhs, lhs_params = self.process_lhs(qn, connection)
rhs, rhs_params = self.process_rhs(qn, connection)
- params = lhs_params + rhs_params
+ params = tuple(lhs_params) + tuple(rhs_params)
return '%s %s %s' % (lhs, self.operator, rhs), params
diff -Nru python-django-1.11.23/django/forms/widgets.py python-django-1.11.27/django/forms/widgets.py
--- python-django-1.11.23/django/forms/widgets.py 2019-08-01 08:35:36.000000000 +0000
+++ python-django-1.11.27/django/forms/widgets.py 2019-12-18 08:31:54.000000000 +0000
@@ -508,6 +508,8 @@
if self.check_test(value):
if attrs is None:
attrs = {}
+ else:
+ attrs = attrs.copy()
attrs['checked'] = True
return super(CheckboxInput, self).get_context(name, value, attrs)
diff -Nru python-django-1.11.23/docs/releases/1.11.24.txt python-django-1.11.27/docs/releases/1.11.24.txt
--- python-django-1.11.23/docs/releases/1.11.24.txt 1970-01-01 00:00:00.000000000 +0000
+++ python-django-1.11.27/docs/releases/1.11.24.txt 2019-11-18 08:15:26.000000000 +0000
@@ -0,0 +1,15 @@
+============================
+Django 1.11.24 release notes
+============================
+
+*September 2, 2019*
+
+Django 1.11.24 fixes a regression in 1.11.23.
+
+Bugfixes
+========
+
+* Fixed crash of ``KeyTransform()`` for
+ :class:`~django.contrib.postgres.fields.JSONField` and
+ :class:`~django.contrib.postgres.fields.HStoreField` when using on
+ expressions with params (:ticket:`30672`).
diff -Nru python-django-1.11.23/docs/releases/1.11.25.txt python-django-1.11.27/docs/releases/1.11.25.txt
--- python-django-1.11.23/docs/releases/1.11.25.txt 1970-01-01 00:00:00.000000000 +0000
+++ python-django-1.11.27/docs/releases/1.11.25.txt 2019-11-18 08:15:26.000000000 +0000
@@ -0,0 +1,14 @@
+============================
+Django 1.11.25 release notes
+============================
+
+*October 1, 2019*
+
+Django 1.11.25 fixes a regression in 1.11.23.
+
+Bugfixes
+========
+
+* Fixed a crash when filtering with a ``Subquery()`` annotation of a queryset
+ containing :class:`~django.contrib.postgres.fields.JSONField` or
+ :class:`~django.contrib.postgres.fields.HStoreField` (:ticket:`30769`).
diff -Nru python-django-1.11.23/docs/releases/1.11.26.txt python-django-1.11.27/docs/releases/1.11.26.txt
--- python-django-1.11.23/docs/releases/1.11.26.txt 1970-01-01 00:00:00.000000000 +0000
+++ python-django-1.11.27/docs/releases/1.11.26.txt 2019-11-18 08:32:10.000000000 +0000
@@ -0,0 +1,15 @@
+============================
+Django 1.11.26 release notes
+============================
+
+*November 4, 2019*
+
+Django 1.11.26 fixes a regression in 1.11.25.
+
+Bugfixes
+========
+
+* Fixed a crash when using a ``contains``, ``contained_by``, ``has_key``,
+ ``has_keys``, or ``has_any_keys`` lookup on
+ :class:`~django.contrib.postgres.fields.JSONField`, if the right or left hand
+ side of an expression is a key transform (:ticket:`30826`).
diff -Nru python-django-1.11.23/docs/releases/1.11.27.txt python-django-1.11.27/docs/releases/1.11.27.txt
--- python-django-1.11.23/docs/releases/1.11.27.txt 1970-01-01 00:00:00.000000000 +0000
+++ python-django-1.11.27/docs/releases/1.11.27.txt 2019-12-18 08:17:17.000000000 +0000
@@ -0,0 +1,31 @@
+============================
+Django 1.11.27 release notes
+============================
+
+*December 18, 2019*
+
+Django 1.11.27 fixes a security issue and a data loss bug in 1.11.26.
+
+CVE-2019-19844: Potential account hijack via password reset form
+================================================================
+
+By submitting a suitably crafted email address making use of Unicode
+characters, that compared equal to an existing user email when lower-cased for
+comparison, an attacker could be sent a password reset token for the matched
+account.
+
+In order to avoid this vulnerability, password reset requests now compare the
+submitted email using the stricter, recommended algorithm for case-insensitive
+comparison of two identifiers from `Unicode Technical Report 36, section
+2.11.2(B)(2)`__. Upon a match, the email containing the reset token will be
+sent to the email address on record rather than the submitted address.
+
+.. __: https://www.unicode.org/reports/tr36/#Recommendations_General
+
+Bugfixes
+========
+
+* Fixed a data loss possibility in
+ :class:`~django.contrib.postgres.forms.SplitArrayField`. When using with
+ ``ArrayField(BooleanField())``, all values after the first ``True`` value
+ were marked as checked instead of preserving passed values (:ticket:`31073`).
diff -Nru python-django-1.11.23/docs/releases/index.txt python-django-1.11.27/docs/releases/index.txt
--- python-django-1.11.23/docs/releases/index.txt 2019-08-01 08:36:08.000000000 +0000
+++ python-django-1.11.27/docs/releases/index.txt 2019-12-18 08:31:54.000000000 +0000
@@ -26,6 +26,10 @@
.. toctree::
:maxdepth: 1
+ 1.11.27
+ 1.11.26
+ 1.11.25
+ 1.11.24
1.11.23
1.11.22
1.11.21
diff -Nru python-django-1.11.23/docs/releases/security.txt python-django-1.11.27/docs/releases/security.txt
--- python-django-1.11.23/docs/releases/security.txt 2019-07-29 09:16:02.000000000 +0000
+++ python-django-1.11.27/docs/releases/security.txt 2019-12-18 08:31:54.000000000 +0000
@@ -974,3 +974,58 @@
* Django 2.2 :commit:`(patch) <77706a3e4766da5d5fb75c4db22a0a59a28e6cd6>`
* Django 2.1 :commit:`(patch) <1e40f427bb8d0fb37cc9f830096a97c36c97af6f>`
* Django 1.11 :commit:`(patch) <32124fc41e75074141b05f10fc55a4f01ff7f050>`
+
+August 1, 2019 - :cve:`2019-14232`
+----------------------------------
+
+Denial-of-service possibility in ``django.utils.text.Truncator``. `Full
+description `__
+
+Versions affected
+~~~~~~~~~~~~~~~~~
+
+* Django 2.2 :commit:`(patch) `
+* Django 2.1 :commit:`(patch) `
+* Django 1.11 :commit:`(patch) <42a66e969023c00536256469f0e8b8a099ef109d>`
+
+August 1, 2019 - :cve:`2019-14233`
+----------------------------------
+
+Denial-of-service possibility in ``strip_tags()``. `Full description
+`__
+
+Versions affected
+~~~~~~~~~~~~~~~~~
+
+* Django 2.2 :commit:`(patch) `
+* Django 2.1 :commit:`(patch) <5ff8e791148bd451180124d76a55cb2b2b9556eb>`
+* Django 1.11 :commit:`(patch) <52479acce792ad80bb0f915f20b835f919993c72>`
+
+
+August 1, 2019 - :cve:`2019-14234`
+----------------------------------
+
+SQL injection possibility in key and index lookups for
+``JSONField``/``HStoreField``. `Full description
+`__
+
+Versions affected
+~~~~~~~~~~~~~~~~~
+
+* Django 2.2 :commit:`(patch) <4f5b58f5cd3c57fee9972ab074f8dc6895d8f387>`
+* Django 2.1 :commit:`(patch) `
+* Django 1.11 :commit:`(patch) `
+
+August 1, 2019 - :cve:`2019-14235`
+----------------------------------
+
+Potential memory exhaustion in ``django.utils.encoding.uri_to_iri()``. `Full
+description
+`__
+
+Versions affected
+~~~~~~~~~~~~~~~~~
+
+* Django 2.2 :commit:`(patch) `
+* Django 2.1 :commit:`(patch) <5d50a2e5fa36ad23ab532fc54cf4073de84b3306>`
+* Django 1.11 :commit:`(patch) <869b34e9b3be3a4cfcb3a145f218ffd3f5e3fd79>`
diff -Nru python-django-1.11.23/tests/auth_tests/test_forms.py python-django-1.11.27/tests/auth_tests/test_forms.py
--- python-django-1.11.23/tests/auth_tests/test_forms.py 2019-08-01 08:35:37.000000000 +0000
+++ python-django-1.11.27/tests/auth_tests/test_forms.py 2019-12-18 08:31:54.000000000 +0000
@@ -694,6 +694,48 @@
self.assertFalse(form.is_valid())
self.assertEqual(form['email'].errors, [_('Enter a valid email address.')])
+ def test_user_email_unicode_collision(self):
+ User.objects.create_user('mike123', 'mike@example.org', 'test123')
+ User.objects.create_user('mike456', 'mıke@example.org', 'test123')
+ data = {'email': 'mıke@example.org'}
+ form = PasswordResetForm(data)
+ if six.PY2:
+ self.assertFalse(form.is_valid())
+ else:
+ self.assertTrue(form.is_valid())
+ form.save()
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].to, ['mıke@example.org'])
+
+ def test_user_email_domain_unicode_collision(self):
+ User.objects.create_user('mike123', 'mike@ixample.org', 'test123')
+ User.objects.create_user('mike456', 'mike@ıxample.org', 'test123')
+ data = {'email': 'mike@ıxample.org'}
+ form = PasswordResetForm(data)
+ self.assertTrue(form.is_valid())
+ form.save()
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].to, ['mike@ıxample.org'])
+
+ def test_user_email_unicode_collision_nonexistent(self):
+ User.objects.create_user('mike123', 'mike@example.org', 'test123')
+ data = {'email': 'mıke@example.org'}
+ form = PasswordResetForm(data)
+ if six.PY2:
+ self.assertFalse(form.is_valid())
+ else:
+ self.assertTrue(form.is_valid())
+ form.save()
+ self.assertEqual(len(mail.outbox), 0)
+
+ def test_user_email_domain_unicode_collision_nonexistent(self):
+ User.objects.create_user('mike123', 'mike@ixample.org', 'test123')
+ data = {'email': 'mike@ıxample.org'}
+ form = PasswordResetForm(data)
+ self.assertTrue(form.is_valid())
+ form.save()
+ self.assertEqual(len(mail.outbox), 0)
+
def test_nonexistent_email(self):
"""
Test nonexistent email address. This should not fail because it would
diff -Nru python-django-1.11.23/tests/forms_tests/widget_tests/test_checkboxinput.py python-django-1.11.27/tests/forms_tests/widget_tests/test_checkboxinput.py
--- python-django-1.11.23/tests/forms_tests/widget_tests/test_checkboxinput.py 2019-08-01 08:35:37.000000000 +0000
+++ python-django-1.11.27/tests/forms_tests/widget_tests/test_checkboxinput.py 2019-12-18 08:31:54.000000000 +0000
@@ -89,3 +89,8 @@
def test_value_omitted_from_data(self):
self.assertIs(self.widget.value_omitted_from_data({'field': 'value'}, {}, 'field'), False)
self.assertIs(self.widget.value_omitted_from_data({}, {}, 'field'), False)
+
+ def test_get_context_does_not_mutate_attrs(self):
+ attrs = {'checked': False}
+ self.widget.get_context('name', True, attrs)
+ self.assertIs(attrs['checked'], False)
diff -Nru python-django-1.11.23/tests/postgres_tests/test_array.py python-django-1.11.27/tests/postgres_tests/test_array.py
--- python-django-1.11.23/tests/postgres_tests/test_array.py 2019-08-01 08:35:37.000000000 +0000
+++ python-django-1.11.27/tests/postgres_tests/test_array.py 2019-12-18 08:31:54.000000000 +0000
@@ -826,6 +826,17 @@
}
)
+ def test_checkbox_get_context_attrs(self):
+ context = SplitArrayWidget(
+ forms.CheckboxInput(),
+ size=2,
+ ).get_context('name', [True, False])
+ self.assertEqual(context['widget']['value'], '[True, False]')
+ self.assertEqual(
+ [subwidget['attrs'] for subwidget in context['widget']['subwidgets']],
+ [{'checked': True}, {}]
+ )
+
def test_render(self):
self.check_html(
SplitArrayWidget(forms.TextInput(), size=2), 'array', None,
diff -Nru python-django-1.11.23/tests/postgres_tests/test_hstore.py python-django-1.11.27/tests/postgres_tests/test_hstore.py
--- python-django-1.11.23/tests/postgres_tests/test_hstore.py 2019-08-01 08:35:37.000000000 +0000
+++ python-django-1.11.27/tests/postgres_tests/test_hstore.py 2019-12-18 08:31:54.000000000 +0000
@@ -5,6 +5,7 @@
from django.core import exceptions, serializers
from django.db import connection
+from django.db.models.expressions import OuterRef, RawSQL, Subquery
from django.forms import Form
from django.test.utils import CaptureQueriesContext, modify_settings
@@ -14,6 +15,7 @@
try:
from django.contrib.postgres import forms
from django.contrib.postgres.fields import HStoreField
+ from django.contrib.postgres.fields.hstore import KeyTransform
from django.contrib.postgres.validators import KeysValidator
except ImportError:
pass
@@ -121,6 +123,13 @@
self.objs[:2]
)
+ def test_key_transform_raw_expression(self):
+ expr = RawSQL('%s::hstore', ['x => b, y => c'])
+ self.assertSequenceEqual(
+ HStoreModel.objects.filter(field__a=KeyTransform('x', expr)),
+ self.objs[:2]
+ )
+
def test_keys(self):
self.assertSequenceEqual(
HStoreModel.objects.filter(field__keys=['a']),
@@ -180,6 +189,12 @@
queries[0]['sql'],
)
+ def test_obj_subquery_lookup(self):
+ qs = HStoreModel.objects.annotate(
+ value=Subquery(HStoreModel.objects.filter(pk=OuterRef('pk')).values('field')),
+ ).filter(value__a='b')
+ self.assertSequenceEqual(qs, self.objs[:2])
+
class TestSerialization(HStoreTestCase):
test_data = ('[{"fields": {"field": "{\\"a\\": \\"b\\"}"}, '
diff -Nru python-django-1.11.23/tests/postgres_tests/test_json.py python-django-1.11.27/tests/postgres_tests/test_json.py
--- python-django-1.11.23/tests/postgres_tests/test_json.py 2019-08-01 08:35:37.000000000 +0000
+++ python-django-1.11.27/tests/postgres_tests/test_json.py 2019-12-18 08:31:54.000000000 +0000
@@ -7,6 +7,9 @@
from django.core import exceptions, serializers
from django.core.serializers.json import DjangoJSONEncoder
from django.db import connection
+from django.db.models import F, OuterRef, Subquery
+from django.db.models.expressions import RawSQL
+from django.db.models.functions import Cast
from django.forms import CharField, Form, widgets
from django.test import skipUnlessDBFeature
from django.test.utils import CaptureQueriesContext
@@ -18,6 +21,9 @@
try:
from django.contrib.postgres import forms
from django.contrib.postgres.fields import JSONField
+ from django.contrib.postgres.fields.jsonb import (
+ KeyTextTransform, KeyTransform
+ )
except ImportError:
pass
@@ -126,7 +132,12 @@
'k': True,
'l': False,
}),
- JSONModel.objects.create(field={'foo': 'bar'}),
+ JSONModel.objects.create(field={
+ 'foo': 'bar',
+ 'baz': {'a': 'b', 'c': 'd'},
+ 'bar': ['foo', 'bar'],
+ 'bax': {'foo': 'bar'},
+ }),
]
def test_exact(self):
@@ -147,6 +158,24 @@
[self.objs[0]]
)
+ def test_key_transform_raw_expression(self):
+ expr = RawSQL('%s::jsonb', ['{"x": "bar"}'])
+ self.assertSequenceEqual(
+ JSONModel.objects.filter(field__foo=KeyTransform('x', expr)),
+ [self.objs[-1]],
+ )
+
+ def test_key_transform_expression(self):
+ self.assertSequenceEqual(
+ JSONModel.objects.filter(field__d__0__isnull=False).annotate(
+ key=KeyTransform('d', 'field'),
+ ).annotate(
+ chain=KeyTransform('0', 'key'),
+ expr=KeyTransform('0', Cast('key', JSONField())),
+ ).filter(chain=F('expr')),
+ [self.objs[8]],
+ )
+
def test_isnull_key(self):
# key__isnull works the same as has_key='key'.
self.assertSequenceEqual(
@@ -200,6 +229,12 @@
[self.objs[7], self.objs[8]]
)
+ def test_obj_subquery_lookup(self):
+ qs = JSONModel.objects.annotate(
+ value=Subquery(JSONModel.objects.filter(pk=OuterRef('pk')).values('field')),
+ ).filter(value__a='b')
+ self.assertSequenceEqual(qs, [self.objs[7], self.objs[8]])
+
def test_deep_lookup_objs(self):
self.assertSequenceEqual(
JSONModel.objects.filter(field__k__l='m'),
@@ -277,6 +312,25 @@
queries[0]['sql'],
)
+ def test_lookups_with_key_transform(self):
+ tests = (
+ ('field__d__contains', 'e'),
+ ('field__baz__contained_by', {'a': 'b', 'c': 'd', 'e': 'f'}),
+ ('field__baz__has_key', 'c'),
+ ('field__baz__has_keys', ['a', 'c']),
+ ('field__baz__has_any_keys', ['a', 'x']),
+ ('field__contains', KeyTransform('bax', 'field')),
+ (
+ 'field__contained_by',
+ KeyTransform('x', RawSQL('%s::jsonb', ['{"x": {"a": "b", "c": 1, "d": "e"}}'])),
+ ),
+ ('field__has_key', KeyTextTransform('foo', 'field')),
+ )
+ for lookup, value in tests:
+ self.assertTrue(JSONModel.objects.filter(
+ **{lookup: value}
+ ).exists())
+
@skipUnlessDBFeature('has_jsonb_datatype')
class TestSerialization(PostgreSQLTestCase):