tests.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. from __future__ import unicode_literals
  2. from django.contrib import admin
  3. from django.contrib.auth.models import User as AuthUser
  4. from django.contrib.contenttypes.models import ContentType
  5. from django.core import checks, management
  6. from django.db import DEFAULT_DB_ALIAS, models
  7. from django.db.models import signals
  8. from django.test import TestCase, override_settings
  9. from django.test.utils import isolate_apps
  10. from django.urls import reverse
  11. from .admin import admin as force_admin_model_registration # NOQA
  12. from .models import (
  13. Abstract, BaseUser, Bug, Country, Improvement, Issue, LowerStatusPerson,
  14. MyPerson, MyPersonProxy, OtherPerson, Person, ProxyBug, ProxyImprovement,
  15. ProxyProxyBug, ProxyTrackerUser, State, StateProxy, StatusPerson,
  16. TrackerUser, User, UserProxy, UserProxyProxy,
  17. )
  18. class ProxyModelTests(TestCase):
  19. def test_same_manager_queries(self):
  20. """
  21. The MyPerson model should be generating the same database queries as
  22. the Person model (when the same manager is used in each case).
  23. """
  24. my_person_sql = MyPerson.other.all().query.get_compiler(
  25. DEFAULT_DB_ALIAS).as_sql()
  26. person_sql = Person.objects.order_by("name").query.get_compiler(
  27. DEFAULT_DB_ALIAS).as_sql()
  28. self.assertEqual(my_person_sql, person_sql)
  29. def test_inheritance_new_table(self):
  30. """
  31. The StatusPerson models should have its own table (it's using ORM-level
  32. inheritance).
  33. """
  34. sp_sql = StatusPerson.objects.all().query.get_compiler(
  35. DEFAULT_DB_ALIAS).as_sql()
  36. p_sql = Person.objects.all().query.get_compiler(
  37. DEFAULT_DB_ALIAS).as_sql()
  38. self.assertNotEqual(sp_sql, p_sql)
  39. def test_basic_proxy(self):
  40. """
  41. Creating a Person makes them accessible through the MyPerson proxy.
  42. """
  43. person = Person.objects.create(name="Foo McBar")
  44. self.assertEqual(len(Person.objects.all()), 1)
  45. self.assertEqual(len(MyPerson.objects.all()), 1)
  46. self.assertEqual(MyPerson.objects.get(name="Foo McBar").id, person.id)
  47. self.assertFalse(MyPerson.objects.get(id=person.id).has_special_name())
  48. def test_no_proxy(self):
  49. """
  50. Person is not proxied by StatusPerson subclass.
  51. """
  52. Person.objects.create(name="Foo McBar")
  53. self.assertEqual(list(StatusPerson.objects.all()), [])
  54. def test_basic_proxy_reverse(self):
  55. """
  56. A new MyPerson also shows up as a standard Person.
  57. """
  58. MyPerson.objects.create(name="Bazza del Frob")
  59. self.assertEqual(len(MyPerson.objects.all()), 1)
  60. self.assertEqual(len(Person.objects.all()), 1)
  61. LowerStatusPerson.objects.create(status="low", name="homer")
  62. lsps = [lsp.name for lsp in LowerStatusPerson.objects.all()]
  63. self.assertEqual(lsps, ["homer"])
  64. def test_correct_type_proxy_of_proxy(self):
  65. """
  66. Correct type when querying a proxy of proxy
  67. """
  68. Person.objects.create(name="Foo McBar")
  69. MyPerson.objects.create(name="Bazza del Frob")
  70. LowerStatusPerson.objects.create(status="low", name="homer")
  71. pp = sorted(mpp.name for mpp in MyPersonProxy.objects.all())
  72. self.assertEqual(pp, ['Bazza del Frob', 'Foo McBar', 'homer'])
  73. def test_proxy_included_in_ancestors(self):
  74. """
  75. Proxy models are included in the ancestors for a model's DoesNotExist
  76. and MultipleObjectsReturned
  77. """
  78. Person.objects.create(name="Foo McBar")
  79. MyPerson.objects.create(name="Bazza del Frob")
  80. LowerStatusPerson.objects.create(status="low", name="homer")
  81. max_id = Person.objects.aggregate(max_id=models.Max('id'))['max_id']
  82. with self.assertRaises(Person.DoesNotExist):
  83. MyPersonProxy.objects.get(name='Zathras')
  84. with self.assertRaises(Person.MultipleObjectsReturned):
  85. MyPersonProxy.objects.get(id__lt=max_id + 1)
  86. with self.assertRaises(Person.DoesNotExist):
  87. StatusPerson.objects.get(name='Zathras')
  88. StatusPerson.objects.create(name='Bazza Jr.')
  89. StatusPerson.objects.create(name='Foo Jr.')
  90. max_id = Person.objects.aggregate(max_id=models.Max('id'))['max_id']
  91. with self.assertRaises(Person.MultipleObjectsReturned):
  92. StatusPerson.objects.get(id__lt=max_id + 1)
  93. def test_abc(self):
  94. """
  95. All base classes must be non-abstract
  96. """
  97. def build_abc():
  98. class NoAbstract(Abstract):
  99. class Meta:
  100. proxy = True
  101. with self.assertRaises(TypeError):
  102. build_abc()
  103. @isolate_apps('proxy_models')
  104. def test_no_cbc(self):
  105. """
  106. The proxy must actually have one concrete base class
  107. """
  108. def build_no_cbc():
  109. class TooManyBases(Person, Abstract):
  110. class Meta:
  111. proxy = True
  112. with self.assertRaises(TypeError):
  113. build_no_cbc()
  114. @isolate_apps('proxy_models')
  115. def test_no_base_classes(self):
  116. def build_no_base_classes():
  117. class NoBaseClasses(models.Model):
  118. class Meta:
  119. proxy = True
  120. with self.assertRaises(TypeError):
  121. build_no_base_classes()
  122. @isolate_apps('proxy_models')
  123. def test_new_fields(self):
  124. class NoNewFields(Person):
  125. newfield = models.BooleanField()
  126. class Meta:
  127. proxy = True
  128. errors = NoNewFields.check()
  129. expected = [
  130. checks.Error(
  131. "Proxy model 'NoNewFields' contains model fields.",
  132. id='models.E017',
  133. )
  134. ]
  135. self.assertEqual(errors, expected)
  136. @override_settings(TEST_SWAPPABLE_MODEL='proxy_models.AlternateModel')
  137. @isolate_apps('proxy_models')
  138. def test_swappable(self):
  139. class SwappableModel(models.Model):
  140. class Meta:
  141. swappable = 'TEST_SWAPPABLE_MODEL'
  142. class AlternateModel(models.Model):
  143. pass
  144. # You can't proxy a swapped model
  145. with self.assertRaises(TypeError):
  146. class ProxyModel(SwappableModel):
  147. class Meta:
  148. proxy = True
  149. def test_myperson_manager(self):
  150. Person.objects.create(name="fred")
  151. Person.objects.create(name="wilma")
  152. Person.objects.create(name="barney")
  153. resp = [p.name for p in MyPerson.objects.all()]
  154. self.assertEqual(resp, ['barney', 'fred'])
  155. resp = [p.name for p in MyPerson._default_manager.all()]
  156. self.assertEqual(resp, ['barney', 'fred'])
  157. def test_otherperson_manager(self):
  158. Person.objects.create(name="fred")
  159. Person.objects.create(name="wilma")
  160. Person.objects.create(name="barney")
  161. resp = [p.name for p in OtherPerson.objects.all()]
  162. self.assertEqual(resp, ['barney', 'wilma'])
  163. resp = [p.name for p in OtherPerson.excluder.all()]
  164. self.assertEqual(resp, ['barney', 'fred'])
  165. resp = [p.name for p in OtherPerson._default_manager.all()]
  166. self.assertEqual(resp, ['barney', 'wilma'])
  167. def test_permissions_created(self):
  168. from django.contrib.auth.models import Permission
  169. try:
  170. Permission.objects.get(name="May display users information")
  171. except Permission.DoesNotExist:
  172. self.fail("The permission 'May display users information' has not been created")
  173. def test_proxy_model_signals(self):
  174. """
  175. Test save signals for proxy models
  176. """
  177. output = []
  178. def make_handler(model, event):
  179. def _handler(*args, **kwargs):
  180. output.append('%s %s save' % (model, event))
  181. return _handler
  182. h1 = make_handler('MyPerson', 'pre')
  183. h2 = make_handler('MyPerson', 'post')
  184. h3 = make_handler('Person', 'pre')
  185. h4 = make_handler('Person', 'post')
  186. signals.pre_save.connect(h1, sender=MyPerson)
  187. signals.post_save.connect(h2, sender=MyPerson)
  188. signals.pre_save.connect(h3, sender=Person)
  189. signals.post_save.connect(h4, sender=Person)
  190. MyPerson.objects.create(name="dino")
  191. self.assertEqual(output, [
  192. 'MyPerson pre save',
  193. 'MyPerson post save'
  194. ])
  195. output = []
  196. h5 = make_handler('MyPersonProxy', 'pre')
  197. h6 = make_handler('MyPersonProxy', 'post')
  198. signals.pre_save.connect(h5, sender=MyPersonProxy)
  199. signals.post_save.connect(h6, sender=MyPersonProxy)
  200. MyPersonProxy.objects.create(name="pebbles")
  201. self.assertEqual(output, [
  202. 'MyPersonProxy pre save',
  203. 'MyPersonProxy post save'
  204. ])
  205. signals.pre_save.disconnect(h1, sender=MyPerson)
  206. signals.post_save.disconnect(h2, sender=MyPerson)
  207. signals.pre_save.disconnect(h3, sender=Person)
  208. signals.post_save.disconnect(h4, sender=Person)
  209. signals.pre_save.disconnect(h5, sender=MyPersonProxy)
  210. signals.post_save.disconnect(h6, sender=MyPersonProxy)
  211. def test_content_type(self):
  212. ctype = ContentType.objects.get_for_model
  213. self.assertIs(ctype(Person), ctype(OtherPerson))
  214. def test_user_userproxy_userproxyproxy(self):
  215. User.objects.create(name='Bruce')
  216. resp = [u.name for u in User.objects.all()]
  217. self.assertEqual(resp, ['Bruce'])
  218. resp = [u.name for u in UserProxy.objects.all()]
  219. self.assertEqual(resp, ['Bruce'])
  220. resp = [u.name for u in UserProxyProxy.objects.all()]
  221. self.assertEqual(resp, ['Bruce'])
  222. def test_proxy_for_model(self):
  223. self.assertEqual(UserProxy, UserProxyProxy._meta.proxy_for_model)
  224. def test_concrete_model(self):
  225. self.assertEqual(User, UserProxyProxy._meta.concrete_model)
  226. def test_proxy_delete(self):
  227. """
  228. Proxy objects can be deleted
  229. """
  230. User.objects.create(name='Bruce')
  231. u2 = UserProxy.objects.create(name='George')
  232. resp = [u.name for u in UserProxy.objects.all()]
  233. self.assertEqual(resp, ['Bruce', 'George'])
  234. u2.delete()
  235. resp = [u.name for u in UserProxy.objects.all()]
  236. self.assertEqual(resp, ['Bruce'])
  237. def test_select_related(self):
  238. """
  239. We can still use `select_related()` to include related models in our
  240. querysets.
  241. """
  242. country = Country.objects.create(name='Australia')
  243. State.objects.create(name='New South Wales', country=country)
  244. resp = [s.name for s in State.objects.select_related()]
  245. self.assertEqual(resp, ['New South Wales'])
  246. resp = [s.name for s in StateProxy.objects.select_related()]
  247. self.assertEqual(resp, ['New South Wales'])
  248. self.assertEqual(StateProxy.objects.get(name='New South Wales').name,
  249. 'New South Wales')
  250. resp = StateProxy.objects.select_related().get(name='New South Wales')
  251. self.assertEqual(resp.name, 'New South Wales')
  252. def test_filter_proxy_relation_reverse(self):
  253. tu = TrackerUser.objects.create(name='Contributor', status='contrib')
  254. ptu = ProxyTrackerUser.objects.get()
  255. issue = Issue.objects.create(assignee=tu)
  256. self.assertEqual(tu.issues.get(), issue)
  257. self.assertEqual(ptu.issues.get(), issue)
  258. self.assertQuerysetEqual(
  259. TrackerUser.objects.filter(issues=issue),
  260. [tu], lambda x: x
  261. )
  262. self.assertQuerysetEqual(
  263. ProxyTrackerUser.objects.filter(issues=issue),
  264. [ptu], lambda x: x
  265. )
  266. def test_proxy_bug(self):
  267. contributor = ProxyTrackerUser.objects.create(name='Contributor',
  268. status='contrib')
  269. someone = BaseUser.objects.create(name='Someone')
  270. Bug.objects.create(summary='fix this', version='1.1beta',
  271. assignee=contributor, reporter=someone)
  272. pcontributor = ProxyTrackerUser.objects.create(name='OtherContributor',
  273. status='proxy')
  274. Improvement.objects.create(summary='improve that', version='1.1beta',
  275. assignee=contributor, reporter=pcontributor,
  276. associated_bug=ProxyProxyBug.objects.all()[0])
  277. # Related field filter on proxy
  278. resp = ProxyBug.objects.get(version__icontains='beta')
  279. self.assertEqual(repr(resp), '<ProxyBug: ProxyBug:fix this>')
  280. # Select related + filter on proxy
  281. resp = ProxyBug.objects.select_related().get(version__icontains='beta')
  282. self.assertEqual(repr(resp), '<ProxyBug: ProxyBug:fix this>')
  283. # Proxy of proxy, select_related + filter
  284. resp = ProxyProxyBug.objects.select_related().get(
  285. version__icontains='beta'
  286. )
  287. self.assertEqual(repr(resp), '<ProxyProxyBug: ProxyProxyBug:fix this>')
  288. # Select related + filter on a related proxy field
  289. resp = ProxyImprovement.objects.select_related().get(
  290. reporter__name__icontains='butor'
  291. )
  292. self.assertEqual(
  293. repr(resp),
  294. '<ProxyImprovement: ProxyImprovement:improve that>'
  295. )
  296. # Select related + filter on a related proxy of proxy field
  297. resp = ProxyImprovement.objects.select_related().get(
  298. associated_bug__summary__icontains='fix'
  299. )
  300. self.assertEqual(
  301. repr(resp),
  302. '<ProxyImprovement: ProxyImprovement:improve that>'
  303. )
  304. def test_proxy_load_from_fixture(self):
  305. management.call_command('loaddata', 'mypeople.json', verbosity=0)
  306. p = MyPerson.objects.get(pk=100)
  307. self.assertEqual(p.name, 'Elvis Presley')
  308. def test_eq(self):
  309. self.assertEqual(MyPerson(id=100), Person(id=100))
  310. @override_settings(ROOT_URLCONF='proxy_models.urls')
  311. class ProxyModelAdminTests(TestCase):
  312. @classmethod
  313. def setUpTestData(cls):
  314. cls.superuser = AuthUser.objects.create(is_superuser=True, is_staff=True)
  315. cls.tu1 = ProxyTrackerUser.objects.create(name='Django Pony', status='emperor')
  316. cls.i1 = Issue.objects.create(summary="Pony's Issue", assignee=cls.tu1)
  317. def test_cascade_delete_proxy_model_admin_warning(self):
  318. """
  319. Test if admin gives warning about cascade deleting models referenced
  320. to concrete model by deleting proxy object.
  321. """
  322. tracker_user = TrackerUser.objects.all()[0]
  323. base_user = BaseUser.objects.all()[0]
  324. issue = Issue.objects.all()[0]
  325. with self.assertNumQueries(7):
  326. collector = admin.utils.NestedObjects('default')
  327. collector.collect(ProxyTrackerUser.objects.all())
  328. self.assertIn(tracker_user, collector.edges.get(None, ()))
  329. self.assertIn(base_user, collector.edges.get(None, ()))
  330. self.assertIn(issue, collector.edges.get(tracker_user, ()))
  331. def test_delete_str_in_model_admin(self):
  332. """
  333. Test if the admin delete page shows the correct string representation
  334. for a proxy model.
  335. """
  336. user = TrackerUser.objects.get(name='Django Pony')
  337. proxy = ProxyTrackerUser.objects.get(name='Django Pony')
  338. user_str = 'Tracker user: <a href="%s">%s</a>' % (
  339. reverse('admin_proxy:proxy_models_trackeruser_change', args=(user.pk,)), user
  340. )
  341. proxy_str = 'Proxy tracker user: <a href="%s">%s</a>' % (
  342. reverse('admin_proxy:proxy_models_proxytrackeruser_change', args=(proxy.pk,)), proxy
  343. )
  344. self.client.force_login(self.superuser)
  345. response = self.client.get(reverse('admin_proxy:proxy_models_trackeruser_delete', args=(user.pk,)))
  346. delete_str = response.context['deleted_objects'][0]
  347. self.assertEqual(delete_str, user_str)
  348. response = self.client.get(reverse('admin_proxy:proxy_models_proxytrackeruser_delete', args=(proxy.pk,)))
  349. delete_str = response.context['deleted_objects'][0]
  350. self.assertEqual(delete_str, proxy_str)