tests.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. from __future__ import unicode_literals
  2. from django.core.exceptions import FieldError
  3. from django.db.models import F
  4. from django.db import transaction
  5. from django.test import TestCase
  6. from django.utils import six
  7. from .models import Company, Employee
  8. class ExpressionsTests(TestCase):
  9. def test_filter(self):
  10. Company.objects.create(
  11. name="Example Inc.", num_employees=2300, num_chairs=5,
  12. ceo=Employee.objects.create(firstname="Joe", lastname="Smith")
  13. )
  14. Company.objects.create(
  15. name="Foobar Ltd.", num_employees=3, num_chairs=4,
  16. ceo=Employee.objects.create(firstname="Frank", lastname="Meyer")
  17. )
  18. Company.objects.create(
  19. name="Test GmbH", num_employees=32, num_chairs=1,
  20. ceo=Employee.objects.create(firstname="Max", lastname="Mustermann")
  21. )
  22. company_query = Company.objects.values(
  23. "name", "num_employees", "num_chairs"
  24. ).order_by(
  25. "name", "num_employees", "num_chairs"
  26. )
  27. # We can filter for companies where the number of employees is greater
  28. # than the number of chairs.
  29. self.assertQuerysetEqual(
  30. company_query.filter(num_employees__gt=F("num_chairs")), [
  31. {
  32. "num_chairs": 5,
  33. "name": "Example Inc.",
  34. "num_employees": 2300,
  35. },
  36. {
  37. "num_chairs": 1,
  38. "name": "Test GmbH",
  39. "num_employees": 32
  40. },
  41. ],
  42. lambda o: o
  43. )
  44. # We can set one field to have the value of another field
  45. # Make sure we have enough chairs
  46. company_query.update(num_chairs=F("num_employees"))
  47. self.assertQuerysetEqual(
  48. company_query, [
  49. {
  50. "num_chairs": 2300,
  51. "name": "Example Inc.",
  52. "num_employees": 2300
  53. },
  54. {
  55. "num_chairs": 3,
  56. "name": "Foobar Ltd.",
  57. "num_employees": 3
  58. },
  59. {
  60. "num_chairs": 32,
  61. "name": "Test GmbH",
  62. "num_employees": 32
  63. }
  64. ],
  65. lambda o: o
  66. )
  67. # We can perform arithmetic operations in expressions
  68. # Make sure we have 2 spare chairs
  69. company_query.update(num_chairs=F("num_employees")+2)
  70. self.assertQuerysetEqual(
  71. company_query, [
  72. {
  73. 'num_chairs': 2302,
  74. 'name': 'Example Inc.',
  75. 'num_employees': 2300
  76. },
  77. {
  78. 'num_chairs': 5,
  79. 'name': 'Foobar Ltd.',
  80. 'num_employees': 3
  81. },
  82. {
  83. 'num_chairs': 34,
  84. 'name': 'Test GmbH',
  85. 'num_employees': 32
  86. }
  87. ],
  88. lambda o: o,
  89. )
  90. # Law of order of operations is followed
  91. company_query.update(
  92. num_chairs=F('num_employees') + 2 * F('num_employees')
  93. )
  94. self.assertQuerysetEqual(
  95. company_query, [
  96. {
  97. 'num_chairs': 6900,
  98. 'name': 'Example Inc.',
  99. 'num_employees': 2300
  100. },
  101. {
  102. 'num_chairs': 9,
  103. 'name': 'Foobar Ltd.',
  104. 'num_employees': 3
  105. },
  106. {
  107. 'num_chairs': 96,
  108. 'name': 'Test GmbH',
  109. 'num_employees': 32
  110. }
  111. ],
  112. lambda o: o,
  113. )
  114. # Law of order of operations can be overridden by parentheses
  115. company_query.update(
  116. num_chairs=((F('num_employees') + 2) * F('num_employees'))
  117. )
  118. self.assertQuerysetEqual(
  119. company_query, [
  120. {
  121. 'num_chairs': 5294600,
  122. 'name': 'Example Inc.',
  123. 'num_employees': 2300
  124. },
  125. {
  126. 'num_chairs': 15,
  127. 'name': 'Foobar Ltd.',
  128. 'num_employees': 3
  129. },
  130. {
  131. 'num_chairs': 1088,
  132. 'name': 'Test GmbH',
  133. 'num_employees': 32
  134. }
  135. ],
  136. lambda o: o,
  137. )
  138. # The relation of a foreign key can become copied over to an other
  139. # foreign key.
  140. self.assertEqual(
  141. Company.objects.update(point_of_contact=F('ceo')),
  142. 3
  143. )
  144. self.assertQuerysetEqual(
  145. Company.objects.all(), [
  146. "Joe Smith",
  147. "Frank Meyer",
  148. "Max Mustermann",
  149. ],
  150. lambda c: six.text_type(c.point_of_contact),
  151. ordered=False
  152. )
  153. c = Company.objects.all()[0]
  154. c.point_of_contact = Employee.objects.create(firstname="Guido", lastname="van Rossum")
  155. c.save()
  156. # F Expressions can also span joins
  157. self.assertQuerysetEqual(
  158. Company.objects.filter(ceo__firstname=F("point_of_contact__firstname")), [
  159. "Foobar Ltd.",
  160. "Test GmbH",
  161. ],
  162. lambda c: c.name,
  163. ordered=False
  164. )
  165. Company.objects.exclude(
  166. ceo__firstname=F("point_of_contact__firstname")
  167. ).update(name="foo")
  168. self.assertEqual(
  169. Company.objects.exclude(
  170. ceo__firstname=F('point_of_contact__firstname')
  171. ).get().name,
  172. "foo",
  173. )
  174. with transaction.atomic():
  175. with self.assertRaises(FieldError):
  176. Company.objects.exclude(
  177. ceo__firstname=F('point_of_contact__firstname')
  178. ).update(name=F('point_of_contact__lastname'))
  179. # F expressions can be used to update attributes on single objects
  180. test_gmbh = Company.objects.get(name="Test GmbH")
  181. self.assertEqual(test_gmbh.num_employees, 32)
  182. test_gmbh.num_employees = F("num_employees") + 4
  183. test_gmbh.save()
  184. test_gmbh = Company.objects.get(pk=test_gmbh.pk)
  185. self.assertEqual(test_gmbh.num_employees, 36)
  186. # F expressions cannot be used to update attributes which are foreign
  187. # keys, or attributes which involve joins.
  188. test_gmbh.point_of_contact = None
  189. test_gmbh.save()
  190. self.assertTrue(test_gmbh.point_of_contact is None)
  191. def test():
  192. test_gmbh.point_of_contact = F("ceo")
  193. self.assertRaises(ValueError, test)
  194. test_gmbh.point_of_contact = test_gmbh.ceo
  195. test_gmbh.save()
  196. test_gmbh.name = F("ceo__last_name")
  197. self.assertRaises(FieldError, test_gmbh.save)
  198. # F expressions cannot be used to update attributes on objects which do
  199. # not yet exist in the database
  200. acme = Company(
  201. name="The Acme Widget Co.", num_employees=12, num_chairs=5,
  202. ceo=test_gmbh.ceo
  203. )
  204. acme.num_employees = F("num_employees") + 16
  205. self.assertRaises(TypeError, acme.save)
  206. def test_ticket_18375_join_reuse(self):
  207. # Test that reverse multijoin F() references and the lookup target
  208. # the same join. Pre #18375 the F() join was generated first, and the
  209. # lookup couldn't reuse that join.
  210. qs = Employee.objects.filter(
  211. company_ceo_set__num_chairs=F('company_ceo_set__num_employees'))
  212. self.assertEqual(str(qs.query).count('JOIN'), 1)
  213. def test_ticket_18375_kwarg_ordering(self):
  214. # The next query was dict-randomization dependent - if the "gte=1"
  215. # was seen first, then the F() will reuse the join generated by the
  216. # gte lookup, if F() was seen first, then it generated a join the
  217. # other lookups could not reuse.
  218. qs = Employee.objects.filter(
  219. company_ceo_set__num_chairs=F('company_ceo_set__num_employees'),
  220. company_ceo_set__num_chairs__gte=1)
  221. self.assertEqual(str(qs.query).count('JOIN'), 1)
  222. def test_ticket_18375_kwarg_ordering_2(self):
  223. # Another similar case for F() than above. Now we have the same join
  224. # in two filter kwargs, one in the lhs lookup, one in F. Here pre
  225. # #18375 the amount of joins generated was random if dict
  226. # randomization was enabled, that is the generated query dependend
  227. # on which clause was seen first.
  228. qs = Employee.objects.filter(
  229. company_ceo_set__num_employees=F('pk'),
  230. pk=F('company_ceo_set__num_employees')
  231. )
  232. self.assertEqual(str(qs.query).count('JOIN'), 1)
  233. def test_ticket_18375_chained_filters(self):
  234. # Test that F() expressions do not reuse joins from previous filter.
  235. qs = Employee.objects.filter(
  236. company_ceo_set__num_employees=F('pk')
  237. ).filter(
  238. company_ceo_set__num_employees=F('company_ceo_set__num_employees')
  239. )
  240. self.assertEqual(str(qs.query).count('JOIN'), 2)