1. from datetime import datetime
    
  2. from operator import attrgetter
    
  3. 
    
  4. from django.db.models import Q
    
  5. from django.test import TestCase
    
  6. 
    
  7. from .models import Article
    
  8. 
    
  9. 
    
  10. class OrLookupsTests(TestCase):
    
  11.     @classmethod
    
  12.     def setUpTestData(cls):
    
  13.         cls.a1 = Article.objects.create(
    
  14.             headline="Hello", pub_date=datetime(2005, 11, 27)
    
  15.         ).pk
    
  16.         cls.a2 = Article.objects.create(
    
  17.             headline="Goodbye", pub_date=datetime(2005, 11, 28)
    
  18.         ).pk
    
  19.         cls.a3 = Article.objects.create(
    
  20.             headline="Hello and goodbye", pub_date=datetime(2005, 11, 29)
    
  21.         ).pk
    
  22. 
    
  23.     def test_filter_or(self):
    
  24.         self.assertQuerysetEqual(
    
  25.             (
    
  26.                 Article.objects.filter(headline__startswith="Hello")
    
  27.                 | Article.objects.filter(headline__startswith="Goodbye")
    
  28.             ),
    
  29.             ["Hello", "Goodbye", "Hello and goodbye"],
    
  30.             attrgetter("headline"),
    
  31.         )
    
  32. 
    
  33.         self.assertQuerysetEqual(
    
  34.             Article.objects.filter(headline__contains="Hello")
    
  35.             | Article.objects.filter(headline__contains="bye"),
    
  36.             ["Hello", "Goodbye", "Hello and goodbye"],
    
  37.             attrgetter("headline"),
    
  38.         )
    
  39. 
    
  40.         self.assertQuerysetEqual(
    
  41.             Article.objects.filter(headline__iexact="Hello")
    
  42.             | Article.objects.filter(headline__contains="ood"),
    
  43.             ["Hello", "Goodbye", "Hello and goodbye"],
    
  44.             attrgetter("headline"),
    
  45.         )
    
  46. 
    
  47.         self.assertQuerysetEqual(
    
  48.             Article.objects.filter(
    
  49.                 Q(headline__startswith="Hello") | Q(headline__startswith="Goodbye")
    
  50.             ),
    
  51.             ["Hello", "Goodbye", "Hello and goodbye"],
    
  52.             attrgetter("headline"),
    
  53.         )
    
  54. 
    
  55.     def test_stages(self):
    
  56.         # You can shorten this syntax with code like the following,  which is
    
  57.         # especially useful if building the query in stages:
    
  58.         articles = Article.objects.all()
    
  59.         self.assertQuerysetEqual(
    
  60.             articles.filter(headline__startswith="Hello")
    
  61.             & articles.filter(headline__startswith="Goodbye"),
    
  62.             [],
    
  63.         )
    
  64.         self.assertQuerysetEqual(
    
  65.             articles.filter(headline__startswith="Hello")
    
  66.             & articles.filter(headline__contains="bye"),
    
  67.             ["Hello and goodbye"],
    
  68.             attrgetter("headline"),
    
  69.         )
    
  70. 
    
  71.     def test_pk_q(self):
    
  72.         self.assertQuerysetEqual(
    
  73.             Article.objects.filter(Q(pk=self.a1) | Q(pk=self.a2)),
    
  74.             ["Hello", "Goodbye"],
    
  75.             attrgetter("headline"),
    
  76.         )
    
  77. 
    
  78.         self.assertQuerysetEqual(
    
  79.             Article.objects.filter(Q(pk=self.a1) | Q(pk=self.a2) | Q(pk=self.a3)),
    
  80.             ["Hello", "Goodbye", "Hello and goodbye"],
    
  81.             attrgetter("headline"),
    
  82.         )
    
  83. 
    
  84.     def test_pk_in(self):
    
  85.         self.assertQuerysetEqual(
    
  86.             Article.objects.filter(pk__in=[self.a1, self.a2, self.a3]),
    
  87.             ["Hello", "Goodbye", "Hello and goodbye"],
    
  88.             attrgetter("headline"),
    
  89.         )
    
  90. 
    
  91.         self.assertQuerysetEqual(
    
  92.             Article.objects.filter(pk__in=(self.a1, self.a2, self.a3)),
    
  93.             ["Hello", "Goodbye", "Hello and goodbye"],
    
  94.             attrgetter("headline"),
    
  95.         )
    
  96. 
    
  97.         self.assertQuerysetEqual(
    
  98.             Article.objects.filter(pk__in=[self.a1, self.a2, self.a3, 40000]),
    
  99.             ["Hello", "Goodbye", "Hello and goodbye"],
    
  100.             attrgetter("headline"),
    
  101.         )
    
  102. 
    
  103.     def test_q_repr(self):
    
  104.         or_expr = Q(baz=Article(headline="Foö"))
    
  105.         self.assertEqual(repr(or_expr), "<Q: (AND: ('baz', <Article: Foö>))>")
    
  106.         negated_or = ~Q(baz=Article(headline="Foö"))
    
  107.         self.assertEqual(repr(negated_or), "<Q: (NOT (AND: ('baz', <Article: Foö>)))>")
    
  108. 
    
  109.     def test_q_negated(self):
    
  110.         # Q objects can be negated
    
  111.         self.assertQuerysetEqual(
    
  112.             Article.objects.filter(Q(pk=self.a1) | ~Q(pk=self.a2)),
    
  113.             ["Hello", "Hello and goodbye"],
    
  114.             attrgetter("headline"),
    
  115.         )
    
  116. 
    
  117.         self.assertQuerysetEqual(
    
  118.             Article.objects.filter(~Q(pk=self.a1) & ~Q(pk=self.a2)),
    
  119.             ["Hello and goodbye"],
    
  120.             attrgetter("headline"),
    
  121.         )
    
  122.         # This allows for more complex queries than filter() and exclude()
    
  123.         # alone would allow
    
  124.         self.assertQuerysetEqual(
    
  125.             Article.objects.filter(Q(pk=self.a1) & (~Q(pk=self.a2) | Q(pk=self.a3))),
    
  126.             ["Hello"],
    
  127.             attrgetter("headline"),
    
  128.         )
    
  129. 
    
  130.     def test_complex_filter(self):
    
  131.         # The 'complex_filter' method supports framework features such as
    
  132.         # 'limit_choices_to' which normally take a single dictionary of lookup
    
  133.         # arguments but need to support arbitrary queries via Q objects too.
    
  134.         self.assertQuerysetEqual(
    
  135.             Article.objects.complex_filter({"pk": self.a1}),
    
  136.             ["Hello"],
    
  137.             attrgetter("headline"),
    
  138.         )
    
  139. 
    
  140.         self.assertQuerysetEqual(
    
  141.             Article.objects.complex_filter(Q(pk=self.a1) | Q(pk=self.a2)),
    
  142.             ["Hello", "Goodbye"],
    
  143.             attrgetter("headline"),
    
  144.         )
    
  145. 
    
  146.     def test_empty_in(self):
    
  147.         # Passing "in" an empty list returns no results ...
    
  148.         self.assertQuerysetEqual(Article.objects.filter(pk__in=[]), [])
    
  149.         # ... but can return results if we OR it with another query.
    
  150.         self.assertQuerysetEqual(
    
  151.             Article.objects.filter(Q(pk__in=[]) | Q(headline__icontains="goodbye")),
    
  152.             ["Goodbye", "Hello and goodbye"],
    
  153.             attrgetter("headline"),
    
  154.         )
    
  155. 
    
  156.     def test_q_and(self):
    
  157.         # Q arg objects are ANDed
    
  158.         self.assertQuerysetEqual(
    
  159.             Article.objects.filter(
    
  160.                 Q(headline__startswith="Hello"), Q(headline__contains="bye")
    
  161.             ),
    
  162.             ["Hello and goodbye"],
    
  163.             attrgetter("headline"),
    
  164.         )
    
  165.         # Q arg AND order is irrelevant
    
  166.         self.assertQuerysetEqual(
    
  167.             Article.objects.filter(
    
  168.                 Q(headline__contains="bye"), headline__startswith="Hello"
    
  169.             ),
    
  170.             ["Hello and goodbye"],
    
  171.             attrgetter("headline"),
    
  172.         )
    
  173. 
    
  174.         self.assertQuerysetEqual(
    
  175.             Article.objects.filter(
    
  176.                 Q(headline__startswith="Hello") & Q(headline__startswith="Goodbye")
    
  177.             ),
    
  178.             [],
    
  179.         )
    
  180. 
    
  181.     def test_q_exclude(self):
    
  182.         self.assertQuerysetEqual(
    
  183.             Article.objects.exclude(Q(headline__startswith="Hello")),
    
  184.             ["Goodbye"],
    
  185.             attrgetter("headline"),
    
  186.         )
    
  187. 
    
  188.     def test_other_arg_queries(self):
    
  189.         # Try some arg queries with operations other than filter.
    
  190.         self.assertEqual(
    
  191.             Article.objects.get(
    
  192.                 Q(headline__startswith="Hello"), Q(headline__contains="bye")
    
  193.             ).headline,
    
  194.             "Hello and goodbye",
    
  195.         )
    
  196. 
    
  197.         self.assertEqual(
    
  198.             Article.objects.filter(
    
  199.                 Q(headline__startswith="Hello") | Q(headline__contains="bye")
    
  200.             ).count(),
    
  201.             3,
    
  202.         )
    
  203. 
    
  204.         self.assertSequenceEqual(
    
  205.             Article.objects.filter(
    
  206.                 Q(headline__startswith="Hello"), Q(headline__contains="bye")
    
  207.             ).values(),
    
  208.             [
    
  209.                 {
    
  210.                     "headline": "Hello and goodbye",
    
  211.                     "id": self.a3,
    
  212.                     "pub_date": datetime(2005, 11, 29),
    
  213.                 },
    
  214.             ],
    
  215.         )
    
  216. 
    
  217.         self.assertEqual(
    
  218.             Article.objects.filter(Q(headline__startswith="Hello")).in_bulk(
    
  219.                 [self.a1, self.a2]
    
  220.             ),
    
  221.             {self.a1: Article.objects.get(pk=self.a1)},
    
  222.         )