1. from django.core.exceptions import FieldError
    
  2. from django.db.models import (
    
  3.     BooleanField,
    
  4.     Exists,
    
  5.     ExpressionWrapper,
    
  6.     F,
    
  7.     OuterRef,
    
  8.     Q,
    
  9.     Value,
    
  10. )
    
  11. from django.db.models.expressions import RawSQL
    
  12. from django.db.models.functions import Lower
    
  13. from django.db.models.sql.where import NothingNode
    
  14. from django.test import SimpleTestCase, TestCase
    
  15. 
    
  16. from .models import Tag
    
  17. 
    
  18. 
    
  19. class QTests(SimpleTestCase):
    
  20.     def test_combine_and_empty(self):
    
  21.         q = Q(x=1)
    
  22.         self.assertEqual(q & Q(), q)
    
  23.         self.assertEqual(Q() & q, q)
    
  24. 
    
  25.         q = Q(x__in={}.keys())
    
  26.         self.assertEqual(q & Q(), q)
    
  27.         self.assertEqual(Q() & q, q)
    
  28. 
    
  29.     def test_combine_and_both_empty(self):
    
  30.         self.assertEqual(Q() & Q(), Q())
    
  31. 
    
  32.     def test_combine_or_empty(self):
    
  33.         q = Q(x=1)
    
  34.         self.assertEqual(q | Q(), q)
    
  35.         self.assertEqual(Q() | q, q)
    
  36. 
    
  37.         q = Q(x__in={}.keys())
    
  38.         self.assertEqual(q | Q(), q)
    
  39.         self.assertEqual(Q() | q, q)
    
  40. 
    
  41.     def test_combine_xor_empty(self):
    
  42.         q = Q(x=1)
    
  43.         self.assertEqual(q ^ Q(), q)
    
  44.         self.assertEqual(Q() ^ q, q)
    
  45. 
    
  46.         q = Q(x__in={}.keys())
    
  47.         self.assertEqual(q ^ Q(), q)
    
  48.         self.assertEqual(Q() ^ q, q)
    
  49. 
    
  50.     def test_combine_empty_copy(self):
    
  51.         base_q = Q(x=1)
    
  52.         tests = [
    
  53.             base_q | Q(),
    
  54.             Q() | base_q,
    
  55.             base_q & Q(),
    
  56.             Q() & base_q,
    
  57.             base_q ^ Q(),
    
  58.             Q() ^ base_q,
    
  59.         ]
    
  60.         for i, q in enumerate(tests):
    
  61.             with self.subTest(i=i):
    
  62.                 self.assertEqual(q, base_q)
    
  63.                 self.assertIsNot(q, base_q)
    
  64. 
    
  65.     def test_combine_or_both_empty(self):
    
  66.         self.assertEqual(Q() | Q(), Q())
    
  67. 
    
  68.     def test_combine_xor_both_empty(self):
    
  69.         self.assertEqual(Q() ^ Q(), Q())
    
  70. 
    
  71.     def test_combine_not_q_object(self):
    
  72.         obj = object()
    
  73.         q = Q(x=1)
    
  74.         with self.assertRaisesMessage(TypeError, str(obj)):
    
  75.             q | obj
    
  76.         with self.assertRaisesMessage(TypeError, str(obj)):
    
  77.             q & obj
    
  78.         with self.assertRaisesMessage(TypeError, str(obj)):
    
  79.             q ^ obj
    
  80. 
    
  81.     def test_combine_negated_boolean_expression(self):
    
  82.         tagged = Tag.objects.filter(category=OuterRef("pk"))
    
  83.         tests = [
    
  84.             Q() & ~Exists(tagged),
    
  85.             Q() | ~Exists(tagged),
    
  86.             Q() ^ ~Exists(tagged),
    
  87.         ]
    
  88.         for q in tests:
    
  89.             with self.subTest(q=q):
    
  90.                 self.assertIs(q.negated, True)
    
  91. 
    
  92.     def test_deconstruct(self):
    
  93.         q = Q(price__gt=F("discounted_price"))
    
  94.         path, args, kwargs = q.deconstruct()
    
  95.         self.assertEqual(path, "django.db.models.Q")
    
  96.         self.assertEqual(args, (("price__gt", F("discounted_price")),))
    
  97.         self.assertEqual(kwargs, {})
    
  98. 
    
  99.     def test_deconstruct_negated(self):
    
  100.         q = ~Q(price__gt=F("discounted_price"))
    
  101.         path, args, kwargs = q.deconstruct()
    
  102.         self.assertEqual(args, (("price__gt", F("discounted_price")),))
    
  103.         self.assertEqual(kwargs, {"_negated": True})
    
  104. 
    
  105.     def test_deconstruct_or(self):
    
  106.         q1 = Q(price__gt=F("discounted_price"))
    
  107.         q2 = Q(price=F("discounted_price"))
    
  108.         q = q1 | q2
    
  109.         path, args, kwargs = q.deconstruct()
    
  110.         self.assertEqual(
    
  111.             args,
    
  112.             (
    
  113.                 ("price__gt", F("discounted_price")),
    
  114.                 ("price", F("discounted_price")),
    
  115.             ),
    
  116.         )
    
  117.         self.assertEqual(kwargs, {"_connector": "OR"})
    
  118. 
    
  119.     def test_deconstruct_xor(self):
    
  120.         q1 = Q(price__gt=F("discounted_price"))
    
  121.         q2 = Q(price=F("discounted_price"))
    
  122.         q = q1 ^ q2
    
  123.         path, args, kwargs = q.deconstruct()
    
  124.         self.assertEqual(
    
  125.             args,
    
  126.             (
    
  127.                 ("price__gt", F("discounted_price")),
    
  128.                 ("price", F("discounted_price")),
    
  129.             ),
    
  130.         )
    
  131.         self.assertEqual(kwargs, {"_connector": "XOR"})
    
  132. 
    
  133.     def test_deconstruct_and(self):
    
  134.         q1 = Q(price__gt=F("discounted_price"))
    
  135.         q2 = Q(price=F("discounted_price"))
    
  136.         q = q1 & q2
    
  137.         path, args, kwargs = q.deconstruct()
    
  138.         self.assertEqual(
    
  139.             args,
    
  140.             (
    
  141.                 ("price__gt", F("discounted_price")),
    
  142.                 ("price", F("discounted_price")),
    
  143.             ),
    
  144.         )
    
  145.         self.assertEqual(kwargs, {})
    
  146. 
    
  147.     def test_deconstruct_multiple_kwargs(self):
    
  148.         q = Q(price__gt=F("discounted_price"), price=F("discounted_price"))
    
  149.         path, args, kwargs = q.deconstruct()
    
  150.         self.assertEqual(
    
  151.             args,
    
  152.             (
    
  153.                 ("price", F("discounted_price")),
    
  154.                 ("price__gt", F("discounted_price")),
    
  155.             ),
    
  156.         )
    
  157.         self.assertEqual(kwargs, {})
    
  158. 
    
  159.     def test_deconstruct_nested(self):
    
  160.         q = Q(Q(price__gt=F("discounted_price")))
    
  161.         path, args, kwargs = q.deconstruct()
    
  162.         self.assertEqual(args, (Q(price__gt=F("discounted_price")),))
    
  163.         self.assertEqual(kwargs, {})
    
  164. 
    
  165.     def test_deconstruct_boolean_expression(self):
    
  166.         expr = RawSQL("1 = 1", BooleanField())
    
  167.         q = Q(expr)
    
  168.         _, args, kwargs = q.deconstruct()
    
  169.         self.assertEqual(args, (expr,))
    
  170.         self.assertEqual(kwargs, {})
    
  171. 
    
  172.     def test_reconstruct(self):
    
  173.         q = Q(price__gt=F("discounted_price"))
    
  174.         path, args, kwargs = q.deconstruct()
    
  175.         self.assertEqual(Q(*args, **kwargs), q)
    
  176. 
    
  177.     def test_reconstruct_negated(self):
    
  178.         q = ~Q(price__gt=F("discounted_price"))
    
  179.         path, args, kwargs = q.deconstruct()
    
  180.         self.assertEqual(Q(*args, **kwargs), q)
    
  181. 
    
  182.     def test_reconstruct_or(self):
    
  183.         q1 = Q(price__gt=F("discounted_price"))
    
  184.         q2 = Q(price=F("discounted_price"))
    
  185.         q = q1 | q2
    
  186.         path, args, kwargs = q.deconstruct()
    
  187.         self.assertEqual(Q(*args, **kwargs), q)
    
  188. 
    
  189.     def test_reconstruct_xor(self):
    
  190.         q1 = Q(price__gt=F("discounted_price"))
    
  191.         q2 = Q(price=F("discounted_price"))
    
  192.         q = q1 ^ q2
    
  193.         path, args, kwargs = q.deconstruct()
    
  194.         self.assertEqual(Q(*args, **kwargs), q)
    
  195. 
    
  196.     def test_reconstruct_and(self):
    
  197.         q1 = Q(price__gt=F("discounted_price"))
    
  198.         q2 = Q(price=F("discounted_price"))
    
  199.         q = q1 & q2
    
  200.         path, args, kwargs = q.deconstruct()
    
  201.         self.assertEqual(Q(*args, **kwargs), q)
    
  202. 
    
  203.     def test_flatten(self):
    
  204.         q = Q()
    
  205.         self.assertEqual(list(q.flatten()), [q])
    
  206.         q = Q(NothingNode())
    
  207.         self.assertEqual(list(q.flatten()), [q, q.children[0]])
    
  208.         q = Q(
    
  209.             ExpressionWrapper(
    
  210.                 Q(RawSQL("id = 0", params=(), output_field=BooleanField()))
    
  211.                 | Q(price=Value("4.55"))
    
  212.                 | Q(name=Lower("category")),
    
  213.                 output_field=BooleanField(),
    
  214.             )
    
  215.         )
    
  216.         flatten = list(q.flatten())
    
  217.         self.assertEqual(len(flatten), 7)
    
  218. 
    
  219. 
    
  220. class QCheckTests(TestCase):
    
  221.     def test_basic(self):
    
  222.         q = Q(price__gt=20)
    
  223.         self.assertIs(q.check({"price": 30}), True)
    
  224.         self.assertIs(q.check({"price": 10}), False)
    
  225. 
    
  226.     def test_expression(self):
    
  227.         q = Q(name="test")
    
  228.         self.assertIs(q.check({"name": Lower(Value("TeSt"))}), True)
    
  229.         self.assertIs(q.check({"name": Value("other")}), False)
    
  230. 
    
  231.     def test_missing_field(self):
    
  232.         q = Q(description__startswith="prefix")
    
  233.         msg = "Cannot resolve keyword 'description' into field."
    
  234.         with self.assertRaisesMessage(FieldError, msg):
    
  235.             q.check({"name": "test"})
    
  236. 
    
  237.     def test_boolean_expression(self):
    
  238.         q = Q(ExpressionWrapper(Q(price__gt=20), output_field=BooleanField()))
    
  239.         self.assertIs(q.check({"price": 25}), True)
    
  240.         self.assertIs(q.check({"price": Value(10)}), False)
    
  241. 
    
  242.     def test_rawsql(self):
    
  243.         """
    
  244.         RawSQL expressions cause a database error because "price" cannot be
    
  245.         replaced by its value. In this case, Q.check() logs a warning and
    
  246.         return True.
    
  247.         """
    
  248.         q = Q(RawSQL("price > %s", params=(20,), output_field=BooleanField()))
    
  249.         with self.assertLogs("django.db.models", "WARNING") as cm:
    
  250.             self.assertIs(q.check({"price": 10}), True)
    
  251.         self.assertIn(
    
  252.             f"Got a database error calling check() on {q!r}: ",
    
  253.             cm.records[0].getMessage(),
    
  254.         )