2
0

tests.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. import datetime
  2. from operator import attrgetter
  3. from django.core.exceptions import FieldError
  4. from django.db import models
  5. from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
  6. from django.test.utils import isolate_apps
  7. from django.utils import translation
  8. from .models import (
  9. Article, ArticleIdea, ArticleTag, ArticleTranslation, Country, Friendship,
  10. Group, Membership, NewsArticle, Person,
  11. )
  12. # Note that these tests are testing internal implementation details.
  13. # ForeignObject is not part of public API.
  14. class MultiColumnFKTests(TestCase):
  15. @classmethod
  16. def setUpTestData(cls):
  17. # Creating countries
  18. cls.usa = Country.objects.create(name="United States of America")
  19. cls.soviet_union = Country.objects.create(name="Soviet Union")
  20. # Creating People
  21. cls.bob = Person.objects.create(name='Bob', person_country=cls.usa)
  22. cls.jim = Person.objects.create(name='Jim', person_country=cls.usa)
  23. cls.george = Person.objects.create(name='George', person_country=cls.usa)
  24. cls.jane = Person.objects.create(name='Jane', person_country=cls.soviet_union)
  25. cls.mark = Person.objects.create(name='Mark', person_country=cls.soviet_union)
  26. cls.sam = Person.objects.create(name='Sam', person_country=cls.soviet_union)
  27. # Creating Groups
  28. cls.kgb = Group.objects.create(name='KGB', group_country=cls.soviet_union)
  29. cls.cia = Group.objects.create(name='CIA', group_country=cls.usa)
  30. cls.republican = Group.objects.create(name='Republican', group_country=cls.usa)
  31. cls.democrat = Group.objects.create(name='Democrat', group_country=cls.usa)
  32. def test_get_succeeds_on_multicolumn_match(self):
  33. # Membership objects have access to their related Person if both
  34. # country_ids match between them
  35. membership = Membership.objects.create(
  36. membership_country_id=self.usa.id, person_id=self.bob.id, group_id=self.cia.id)
  37. person = membership.person
  38. self.assertEqual((person.id, person.name), (self.bob.id, "Bob"))
  39. def test_get_fails_on_multicolumn_mismatch(self):
  40. # Membership objects returns DoesNotExist error when there is no
  41. # Person with the same id and country_id
  42. membership = Membership.objects.create(
  43. membership_country_id=self.usa.id, person_id=self.jane.id, group_id=self.cia.id)
  44. with self.assertRaises(Person.DoesNotExist):
  45. getattr(membership, 'person')
  46. def test_reverse_query_returns_correct_result(self):
  47. # Creating a valid membership because it has the same country has the person
  48. Membership.objects.create(
  49. membership_country_id=self.usa.id, person_id=self.bob.id, group_id=self.cia.id)
  50. # Creating an invalid membership because it has a different country has the person
  51. Membership.objects.create(
  52. membership_country_id=self.soviet_union.id, person_id=self.bob.id,
  53. group_id=self.republican.id)
  54. with self.assertNumQueries(1):
  55. membership = self.bob.membership_set.get()
  56. self.assertEqual(membership.group_id, self.cia.id)
  57. self.assertIs(membership.person, self.bob)
  58. def test_query_filters_correctly(self):
  59. # Creating a to valid memberships
  60. Membership.objects.create(
  61. membership_country_id=self.usa.id, person_id=self.bob.id, group_id=self.cia.id)
  62. Membership.objects.create(
  63. membership_country_id=self.usa.id, person_id=self.jim.id,
  64. group_id=self.cia.id)
  65. # Creating an invalid membership
  66. Membership.objects.create(membership_country_id=self.soviet_union.id,
  67. person_id=self.george.id, group_id=self.cia.id)
  68. self.assertQuerysetEqual(
  69. Membership.objects.filter(person__name__contains='o'), [
  70. self.bob.id
  71. ],
  72. attrgetter("person_id")
  73. )
  74. def test_reverse_query_filters_correctly(self):
  75. timemark = datetime.datetime.utcnow()
  76. timedelta = datetime.timedelta(days=1)
  77. # Creating a to valid memberships
  78. Membership.objects.create(
  79. membership_country_id=self.usa.id, person_id=self.bob.id,
  80. group_id=self.cia.id, date_joined=timemark - timedelta)
  81. Membership.objects.create(
  82. membership_country_id=self.usa.id, person_id=self.jim.id,
  83. group_id=self.cia.id, date_joined=timemark + timedelta)
  84. # Creating an invalid membership
  85. Membership.objects.create(
  86. membership_country_id=self.soviet_union.id, person_id=self.george.id,
  87. group_id=self.cia.id, date_joined=timemark + timedelta)
  88. self.assertQuerysetEqual(
  89. Person.objects.filter(membership__date_joined__gte=timemark), [
  90. 'Jim'
  91. ],
  92. attrgetter('name')
  93. )
  94. def test_forward_in_lookup_filters_correctly(self):
  95. Membership.objects.create(membership_country_id=self.usa.id, person_id=self.bob.id,
  96. group_id=self.cia.id)
  97. Membership.objects.create(membership_country_id=self.usa.id, person_id=self.jim.id,
  98. group_id=self.cia.id)
  99. # Creating an invalid membership
  100. Membership.objects.create(
  101. membership_country_id=self.soviet_union.id, person_id=self.george.id,
  102. group_id=self.cia.id)
  103. self.assertQuerysetEqual(
  104. Membership.objects.filter(person__in=[self.george, self.jim]), [
  105. self.jim.id,
  106. ],
  107. attrgetter('person_id')
  108. )
  109. self.assertQuerysetEqual(
  110. Membership.objects.filter(person__in=Person.objects.filter(name='Jim')), [
  111. self.jim.id,
  112. ],
  113. attrgetter('person_id')
  114. )
  115. def test_double_nested_query(self):
  116. m1 = Membership.objects.create(membership_country_id=self.usa.id, person_id=self.bob.id,
  117. group_id=self.cia.id)
  118. m2 = Membership.objects.create(membership_country_id=self.usa.id, person_id=self.jim.id,
  119. group_id=self.cia.id)
  120. Friendship.objects.create(from_friend_country_id=self.usa.id, from_friend_id=self.bob.id,
  121. to_friend_country_id=self.usa.id, to_friend_id=self.jim.id)
  122. self.assertSequenceEqual(
  123. Membership.objects.filter(
  124. person__in=Person.objects.filter(
  125. from_friend__in=Friendship.objects.filter(to_friend__in=Person.objects.all())
  126. )
  127. ),
  128. [m1]
  129. )
  130. self.assertSequenceEqual(
  131. Membership.objects.exclude(
  132. person__in=Person.objects.filter(
  133. from_friend__in=Friendship.objects.filter(to_friend__in=Person.objects.all())
  134. )
  135. ),
  136. [m2]
  137. )
  138. def test_select_related_foreignkey_forward_works(self):
  139. Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia)
  140. Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat)
  141. with self.assertNumQueries(1):
  142. people = [m.person for m in Membership.objects.select_related('person').order_by('pk')]
  143. normal_people = [m.person for m in Membership.objects.all().order_by('pk')]
  144. self.assertEqual(people, normal_people)
  145. def test_prefetch_foreignkey_forward_works(self):
  146. Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia)
  147. Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat)
  148. with self.assertNumQueries(2):
  149. people = [
  150. m.person for m in Membership.objects.prefetch_related('person').order_by('pk')]
  151. normal_people = [m.person for m in Membership.objects.order_by('pk')]
  152. self.assertEqual(people, normal_people)
  153. def test_prefetch_foreignkey_reverse_works(self):
  154. Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia)
  155. Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat)
  156. with self.assertNumQueries(2):
  157. membership_sets = [
  158. list(p.membership_set.all())
  159. for p in Person.objects.prefetch_related('membership_set').order_by('pk')]
  160. with self.assertNumQueries(7):
  161. normal_membership_sets = [
  162. list(p.membership_set.all())
  163. for p in Person.objects.order_by('pk')
  164. ]
  165. self.assertEqual(membership_sets, normal_membership_sets)
  166. def test_m2m_through_forward_returns_valid_members(self):
  167. # We start out by making sure that the Group 'CIA' has no members.
  168. self.assertQuerysetEqual(
  169. self.cia.members.all(),
  170. []
  171. )
  172. Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia)
  173. Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.cia)
  174. # Let's check to make sure that it worked. Bob and Jim should be members of the CIA.
  175. self.assertQuerysetEqual(
  176. self.cia.members.all(), [
  177. 'Bob',
  178. 'Jim'
  179. ], attrgetter("name")
  180. )
  181. def test_m2m_through_reverse_returns_valid_members(self):
  182. # We start out by making sure that Bob is in no groups.
  183. self.assertQuerysetEqual(
  184. self.bob.groups.all(),
  185. []
  186. )
  187. Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia)
  188. Membership.objects.create(membership_country=self.usa, person=self.bob,
  189. group=self.republican)
  190. # Bob should be in the CIA and a Republican
  191. self.assertQuerysetEqual(
  192. self.bob.groups.all(), [
  193. 'CIA',
  194. 'Republican'
  195. ], attrgetter("name")
  196. )
  197. def test_m2m_through_forward_ignores_invalid_members(self):
  198. # We start out by making sure that the Group 'CIA' has no members.
  199. self.assertQuerysetEqual(
  200. self.cia.members.all(),
  201. []
  202. )
  203. # Something adds jane to group CIA but Jane is in Soviet Union which isn't CIA's country
  204. Membership.objects.create(membership_country=self.usa, person=self.jane, group=self.cia)
  205. # There should still be no members in CIA
  206. self.assertQuerysetEqual(
  207. self.cia.members.all(),
  208. []
  209. )
  210. def test_m2m_through_reverse_ignores_invalid_members(self):
  211. # We start out by making sure that Jane has no groups.
  212. self.assertQuerysetEqual(
  213. self.jane.groups.all(),
  214. []
  215. )
  216. # Something adds jane to group CIA but Jane is in Soviet Union which isn't CIA's country
  217. Membership.objects.create(membership_country=self.usa, person=self.jane, group=self.cia)
  218. # Jane should still not be in any groups
  219. self.assertQuerysetEqual(
  220. self.jane.groups.all(),
  221. []
  222. )
  223. def test_m2m_through_on_self_works(self):
  224. self.assertQuerysetEqual(
  225. self.jane.friends.all(),
  226. []
  227. )
  228. Friendship.objects.create(
  229. from_friend_country=self.jane.person_country, from_friend=self.jane,
  230. to_friend_country=self.george.person_country, to_friend=self.george)
  231. self.assertQuerysetEqual(
  232. self.jane.friends.all(),
  233. ['George'], attrgetter("name")
  234. )
  235. def test_m2m_through_on_self_ignores_mismatch_columns(self):
  236. self.assertQuerysetEqual(self.jane.friends.all(), [])
  237. # Note that we use ids instead of instances. This is because instances on ForeignObject
  238. # properties will set all related field off of the given instance
  239. Friendship.objects.create(
  240. from_friend_id=self.jane.id, to_friend_id=self.george.id,
  241. to_friend_country_id=self.jane.person_country_id,
  242. from_friend_country_id=self.george.person_country_id)
  243. self.assertQuerysetEqual(self.jane.friends.all(), [])
  244. def test_prefetch_related_m2m_forward_works(self):
  245. Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia)
  246. Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat)
  247. with self.assertNumQueries(2):
  248. members_lists = [list(g.members.all())
  249. for g in Group.objects.prefetch_related('members')]
  250. normal_members_lists = [list(g.members.all()) for g in Group.objects.all()]
  251. self.assertEqual(members_lists, normal_members_lists)
  252. def test_prefetch_related_m2m_reverse_works(self):
  253. Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia)
  254. Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat)
  255. with self.assertNumQueries(2):
  256. groups_lists = [list(p.groups.all()) for p in Person.objects.prefetch_related('groups')]
  257. normal_groups_lists = [list(p.groups.all()) for p in Person.objects.all()]
  258. self.assertEqual(groups_lists, normal_groups_lists)
  259. @translation.override('fi')
  260. def test_translations(self):
  261. a1 = Article.objects.create(pub_date=datetime.date.today())
  262. at1_fi = ArticleTranslation(article=a1, lang='fi', title='Otsikko', body='Diipadaapa')
  263. at1_fi.save()
  264. at2_en = ArticleTranslation(article=a1, lang='en', title='Title', body='Lalalalala')
  265. at2_en.save()
  266. self.assertEqual(Article.objects.get(pk=a1.pk).active_translation, at1_fi)
  267. with self.assertNumQueries(1):
  268. fetched = Article.objects.select_related('active_translation').get(
  269. active_translation__title='Otsikko')
  270. self.assertEqual(fetched.active_translation.title, 'Otsikko')
  271. a2 = Article.objects.create(pub_date=datetime.date.today())
  272. at2_fi = ArticleTranslation(article=a2, lang='fi', title='Atsikko', body='Diipadaapa',
  273. abstract='dipad')
  274. at2_fi.save()
  275. a3 = Article.objects.create(pub_date=datetime.date.today())
  276. at3_en = ArticleTranslation(article=a3, lang='en', title='A title', body='lalalalala',
  277. abstract='lala')
  278. at3_en.save()
  279. # Test model initialization with active_translation field.
  280. a3 = Article(id=a3.id, pub_date=a3.pub_date, active_translation=at3_en)
  281. a3.save()
  282. self.assertEqual(
  283. list(Article.objects.filter(active_translation__abstract=None)),
  284. [a1, a3])
  285. self.assertEqual(
  286. list(Article.objects.filter(active_translation__abstract=None,
  287. active_translation__pk__isnull=False)),
  288. [a1])
  289. with translation.override('en'):
  290. self.assertEqual(
  291. list(Article.objects.filter(active_translation__abstract=None)),
  292. [a1, a2])
  293. def test_foreign_key_raises_informative_does_not_exist(self):
  294. referrer = ArticleTranslation()
  295. with self.assertRaisesMessage(Article.DoesNotExist, 'ArticleTranslation has no article'):
  296. referrer.article
  297. def test_foreign_key_related_query_name(self):
  298. a1 = Article.objects.create(pub_date=datetime.date.today())
  299. ArticleTag.objects.create(article=a1, name="foo")
  300. self.assertEqual(Article.objects.filter(tag__name="foo").count(), 1)
  301. self.assertEqual(Article.objects.filter(tag__name="bar").count(), 0)
  302. msg = (
  303. "Cannot resolve keyword 'tags' into field. Choices are: "
  304. "active_translation, active_translation_q, articletranslation, "
  305. "id, idea_things, newsarticle, pub_date, tag"
  306. )
  307. with self.assertRaisesMessage(FieldError, msg):
  308. Article.objects.filter(tags__name="foo")
  309. def test_many_to_many_related_query_name(self):
  310. a1 = Article.objects.create(pub_date=datetime.date.today())
  311. i1 = ArticleIdea.objects.create(name="idea1")
  312. a1.ideas.add(i1)
  313. self.assertEqual(Article.objects.filter(idea_things__name="idea1").count(), 1)
  314. self.assertEqual(Article.objects.filter(idea_things__name="idea2").count(), 0)
  315. msg = (
  316. "Cannot resolve keyword 'ideas' into field. Choices are: "
  317. "active_translation, active_translation_q, articletranslation, "
  318. "id, idea_things, newsarticle, pub_date, tag"
  319. )
  320. with self.assertRaisesMessage(FieldError, msg):
  321. Article.objects.filter(ideas__name="idea1")
  322. @translation.override('fi')
  323. def test_inheritance(self):
  324. na = NewsArticle.objects.create(pub_date=datetime.date.today())
  325. ArticleTranslation.objects.create(
  326. article=na, lang="fi", title="foo", body="bar")
  327. self.assertSequenceEqual(
  328. NewsArticle.objects.select_related('active_translation'),
  329. [na]
  330. )
  331. with self.assertNumQueries(1):
  332. self.assertEqual(
  333. NewsArticle.objects.select_related(
  334. 'active_translation')[0].active_translation.title,
  335. "foo")
  336. @skipUnlessDBFeature('has_bulk_insert')
  337. def test_batch_create_foreign_object(self):
  338. objs = [Person(name="abcd_%s" % i, person_country=self.usa) for i in range(0, 5)]
  339. Person.objects.bulk_create(objs, 10)
  340. def test_isnull_lookup(self):
  341. Membership.objects.create(membership_country=self.usa, person=self.bob, group_id=None)
  342. Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia)
  343. self.assertQuerysetEqual(
  344. Membership.objects.filter(group__isnull=True),
  345. ['<Membership: Bob is a member of NULL>']
  346. )
  347. self.assertQuerysetEqual(
  348. Membership.objects.filter(group__isnull=False),
  349. ['<Membership: Bob is a member of CIA>']
  350. )
  351. class TestModelCheckTests(SimpleTestCase):
  352. @isolate_apps('foreign_object')
  353. def test_check_composite_foreign_object(self):
  354. class Parent(models.Model):
  355. a = models.PositiveIntegerField()
  356. b = models.PositiveIntegerField()
  357. class Meta:
  358. unique_together = (('a', 'b'),)
  359. class Child(models.Model):
  360. a = models.PositiveIntegerField()
  361. b = models.PositiveIntegerField()
  362. value = models.CharField(max_length=255)
  363. parent = models.ForeignObject(
  364. Parent,
  365. on_delete=models.SET_NULL,
  366. from_fields=('a', 'b'),
  367. to_fields=('a', 'b'),
  368. related_name='children',
  369. )
  370. self.assertEqual(Child._meta.get_field('parent').check(from_model=Child), [])
  371. @isolate_apps('foreign_object')
  372. def test_check_subset_composite_foreign_object(self):
  373. class Parent(models.Model):
  374. a = models.PositiveIntegerField()
  375. b = models.PositiveIntegerField()
  376. c = models.PositiveIntegerField()
  377. class Meta:
  378. unique_together = (('a', 'b'),)
  379. class Child(models.Model):
  380. a = models.PositiveIntegerField()
  381. b = models.PositiveIntegerField()
  382. c = models.PositiveIntegerField()
  383. d = models.CharField(max_length=255)
  384. parent = models.ForeignObject(
  385. Parent,
  386. on_delete=models.SET_NULL,
  387. from_fields=('a', 'b', 'c'),
  388. to_fields=('a', 'b', 'c'),
  389. related_name='children',
  390. )
  391. self.assertEqual(Child._meta.get_field('parent').check(from_model=Child), [])
  392. class TestExtraJoinFilterQ(TestCase):
  393. @translation.override('fi')
  394. def test_extra_join_filter_q(self):
  395. a = Article.objects.create(pub_date=datetime.datetime.today())
  396. ArticleTranslation.objects.create(article=a, lang='fi', title='title', body='body')
  397. qs = Article.objects.all()
  398. with self.assertNumQueries(2):
  399. self.assertEqual(qs[0].active_translation_q.title, 'title')
  400. qs = qs.select_related('active_translation_q')
  401. with self.assertNumQueries(1):
  402. self.assertEqual(qs[0].active_translation_q.title, 'title')