tests.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import datetime
  4. from django.apps.registry import Apps, apps
  5. from django.contrib.contenttypes import management
  6. from django.contrib.contenttypes.fields import (
  7. GenericForeignKey, GenericRelation,
  8. )
  9. from django.contrib.contenttypes.models import ContentType
  10. from django.contrib.sites.models import Site
  11. from django.core import checks
  12. from django.db import connections, models
  13. from django.test import SimpleTestCase, TestCase, override_settings
  14. from django.test.utils import captured_stdout, isolate_apps
  15. from django.utils.encoding import force_str, force_text
  16. from .models import Article, Author, SchemeIncludedURL
  17. @override_settings(ROOT_URLCONF='contenttypes_tests.urls')
  18. class ContentTypesViewsTests(TestCase):
  19. @classmethod
  20. def setUpTestData(cls):
  21. # don't use the manager because we want to ensure the site exists
  22. # with pk=1, regardless of whether or not it already exists.
  23. cls.site1 = Site(pk=1, domain='testserver', name='testserver')
  24. cls.site1.save()
  25. cls.author1 = Author.objects.create(name='Boris')
  26. cls.article1 = Article.objects.create(
  27. title='Old Article', slug='old_article', author=cls.author1,
  28. date_created=datetime.datetime(2001, 1, 1, 21, 22, 23)
  29. )
  30. cls.article2 = Article.objects.create(
  31. title='Current Article', slug='current_article', author=cls.author1,
  32. date_created=datetime.datetime(2007, 9, 17, 21, 22, 23)
  33. )
  34. cls.article3 = Article.objects.create(
  35. title='Future Article', slug='future_article', author=cls.author1,
  36. date_created=datetime.datetime(3000, 1, 1, 21, 22, 23)
  37. )
  38. cls.scheme1 = SchemeIncludedURL.objects.create(url='http://test_scheme_included_http/')
  39. cls.scheme2 = SchemeIncludedURL.objects.create(url='https://test_scheme_included_https/')
  40. cls.scheme3 = SchemeIncludedURL.objects.create(url='//test_default_scheme_kept/')
  41. def test_shortcut_with_absolute_url(self):
  42. "Can view a shortcut for an Author object that has a get_absolute_url method"
  43. for obj in Author.objects.all():
  44. short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, obj.pk)
  45. response = self.client.get(short_url)
  46. self.assertRedirects(response, 'http://testserver%s' % obj.get_absolute_url(),
  47. status_code=302, target_status_code=404)
  48. def test_shortcut_with_absolute_url_including_scheme(self):
  49. """
  50. Can view a shortcut when object's get_absolute_url returns a full URL
  51. the tested URLs are: "http://...", "https://..." and "//..."
  52. """
  53. for obj in SchemeIncludedURL.objects.all():
  54. short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(SchemeIncludedURL).id, obj.pk)
  55. response = self.client.get(short_url)
  56. self.assertRedirects(response, obj.get_absolute_url(),
  57. status_code=302,
  58. fetch_redirect_response=False)
  59. def test_shortcut_no_absolute_url(self):
  60. "Shortcuts for an object that has no get_absolute_url method raises 404"
  61. for obj in Article.objects.all():
  62. short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Article).id, obj.pk)
  63. response = self.client.get(short_url)
  64. self.assertEqual(response.status_code, 404)
  65. def test_wrong_type_pk(self):
  66. short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, 'nobody/expects')
  67. response = self.client.get(short_url)
  68. self.assertEqual(response.status_code, 404)
  69. def test_shortcut_bad_pk(self):
  70. short_url = '/shortcut/%s/%s/' % (ContentType.objects.get_for_model(Author).id, '42424242')
  71. response = self.client.get(short_url)
  72. self.assertEqual(response.status_code, 404)
  73. def test_nonint_content_type(self):
  74. an_author = Author.objects.all()[0]
  75. short_url = '/shortcut/%s/%s/' % ('spam', an_author.pk)
  76. response = self.client.get(short_url)
  77. self.assertEqual(response.status_code, 404)
  78. def test_bad_content_type(self):
  79. an_author = Author.objects.all()[0]
  80. short_url = '/shortcut/%s/%s/' % (42424242, an_author.pk)
  81. response = self.client.get(short_url)
  82. self.assertEqual(response.status_code, 404)
  83. def test_create_contenttype_on_the_spot(self):
  84. """
  85. Make sure ContentTypeManager.get_for_model creates the corresponding
  86. content type if it doesn't exist in the database (for some reason).
  87. """
  88. class ModelCreatedOnTheFly(models.Model):
  89. name = models.CharField()
  90. class Meta:
  91. verbose_name = 'a model created on the fly'
  92. app_label = 'my_great_app'
  93. apps = Apps()
  94. ct = ContentType.objects.get_for_model(ModelCreatedOnTheFly)
  95. self.assertEqual(ct.app_label, 'my_great_app')
  96. self.assertEqual(ct.model, 'modelcreatedonthefly')
  97. self.assertEqual(force_text(ct), 'modelcreatedonthefly')
  98. @override_settings(SILENCED_SYSTEM_CHECKS=['fields.W342']) # ForeignKey(unique=True)
  99. @isolate_apps('contenttypes_tests', attr_name='apps')
  100. class GenericForeignKeyTests(SimpleTestCase):
  101. def test_str(self):
  102. class Model(models.Model):
  103. field = GenericForeignKey()
  104. expected = "contenttypes_tests.Model.field"
  105. actual = force_str(Model.field)
  106. self.assertEqual(expected, actual)
  107. def test_missing_content_type_field(self):
  108. class TaggedItem(models.Model):
  109. # no content_type field
  110. object_id = models.PositiveIntegerField()
  111. content_object = GenericForeignKey()
  112. errors = TaggedItem.content_object.check()
  113. expected = [
  114. checks.Error(
  115. "The GenericForeignKey content type references the non-existent field 'TaggedItem.content_type'.",
  116. obj=TaggedItem.content_object,
  117. id='contenttypes.E002',
  118. )
  119. ]
  120. self.assertEqual(errors, expected)
  121. def test_invalid_content_type_field(self):
  122. class Model(models.Model):
  123. content_type = models.IntegerField() # should be ForeignKey
  124. object_id = models.PositiveIntegerField()
  125. content_object = GenericForeignKey(
  126. 'content_type', 'object_id')
  127. errors = Model.content_object.check()
  128. expected = [
  129. checks.Error(
  130. "'Model.content_type' is not a ForeignKey.",
  131. hint=(
  132. "GenericForeignKeys must use a ForeignKey to "
  133. "'contenttypes.ContentType' as the 'content_type' field."
  134. ),
  135. obj=Model.content_object,
  136. id='contenttypes.E003',
  137. )
  138. ]
  139. self.assertEqual(errors, expected)
  140. def test_content_type_field_pointing_to_wrong_model(self):
  141. class Model(models.Model):
  142. content_type = models.ForeignKey('self', models.CASCADE) # should point to ContentType
  143. object_id = models.PositiveIntegerField()
  144. content_object = GenericForeignKey(
  145. 'content_type', 'object_id')
  146. errors = Model.content_object.check()
  147. expected = [
  148. checks.Error(
  149. "'Model.content_type' is not a ForeignKey to 'contenttypes.ContentType'.",
  150. hint=(
  151. "GenericForeignKeys must use a ForeignKey to "
  152. "'contenttypes.ContentType' as the 'content_type' field."
  153. ),
  154. obj=Model.content_object,
  155. id='contenttypes.E004',
  156. )
  157. ]
  158. self.assertEqual(errors, expected)
  159. def test_missing_object_id_field(self):
  160. class TaggedItem(models.Model):
  161. content_type = models.ForeignKey(ContentType, models.CASCADE)
  162. # missing object_id field
  163. content_object = GenericForeignKey()
  164. errors = TaggedItem.content_object.check()
  165. expected = [
  166. checks.Error(
  167. "The GenericForeignKey object ID references the non-existent field 'object_id'.",
  168. obj=TaggedItem.content_object,
  169. id='contenttypes.E001',
  170. )
  171. ]
  172. self.assertEqual(errors, expected)
  173. def test_field_name_ending_with_underscore(self):
  174. class Model(models.Model):
  175. content_type = models.ForeignKey(ContentType, models.CASCADE)
  176. object_id = models.PositiveIntegerField()
  177. content_object_ = GenericForeignKey(
  178. 'content_type', 'object_id')
  179. errors = Model.content_object_.check()
  180. expected = [
  181. checks.Error(
  182. 'Field names must not end with an underscore.',
  183. obj=Model.content_object_,
  184. id='fields.E001',
  185. )
  186. ]
  187. self.assertEqual(errors, expected)
  188. @override_settings(INSTALLED_APPS=['django.contrib.auth', 'django.contrib.contenttypes', 'contenttypes_tests'])
  189. def test_generic_foreign_key_checks_are_performed(self):
  190. class MyGenericForeignKey(GenericForeignKey):
  191. def check(self, **kwargs):
  192. return ['performed!']
  193. class Model(models.Model):
  194. content_object = MyGenericForeignKey()
  195. errors = checks.run_checks(app_configs=self.apps.get_app_configs())
  196. self.assertEqual(errors, ['performed!'])
  197. @isolate_apps('contenttypes_tests')
  198. class GenericRelationshipTests(SimpleTestCase):
  199. def test_valid_generic_relationship(self):
  200. class TaggedItem(models.Model):
  201. content_type = models.ForeignKey(ContentType, models.CASCADE)
  202. object_id = models.PositiveIntegerField()
  203. content_object = GenericForeignKey()
  204. class Bookmark(models.Model):
  205. tags = GenericRelation('TaggedItem')
  206. errors = Bookmark.tags.field.check()
  207. self.assertEqual(errors, [])
  208. def test_valid_generic_relationship_with_explicit_fields(self):
  209. class TaggedItem(models.Model):
  210. custom_content_type = models.ForeignKey(ContentType, models.CASCADE)
  211. custom_object_id = models.PositiveIntegerField()
  212. content_object = GenericForeignKey(
  213. 'custom_content_type', 'custom_object_id')
  214. class Bookmark(models.Model):
  215. tags = GenericRelation('TaggedItem',
  216. content_type_field='custom_content_type',
  217. object_id_field='custom_object_id')
  218. errors = Bookmark.tags.field.check()
  219. self.assertEqual(errors, [])
  220. def test_pointing_to_missing_model(self):
  221. class Model(models.Model):
  222. rel = GenericRelation('MissingModel')
  223. errors = Model.rel.field.check()
  224. expected = [
  225. checks.Error(
  226. "Field defines a relation with model 'MissingModel', "
  227. "which is either not installed, or is abstract.",
  228. obj=Model.rel.field,
  229. id='fields.E300',
  230. )
  231. ]
  232. self.assertEqual(errors, expected)
  233. def test_valid_self_referential_generic_relationship(self):
  234. class Model(models.Model):
  235. rel = GenericRelation('Model')
  236. content_type = models.ForeignKey(ContentType, models.CASCADE)
  237. object_id = models.PositiveIntegerField()
  238. content_object = GenericForeignKey(
  239. 'content_type', 'object_id')
  240. errors = Model.rel.field.check()
  241. self.assertEqual(errors, [])
  242. def test_missing_generic_foreign_key(self):
  243. class TaggedItem(models.Model):
  244. content_type = models.ForeignKey(ContentType, models.CASCADE)
  245. object_id = models.PositiveIntegerField()
  246. class Bookmark(models.Model):
  247. tags = GenericRelation('TaggedItem')
  248. errors = Bookmark.tags.field.check()
  249. expected = [
  250. checks.Error(
  251. "The GenericRelation defines a relation with the model "
  252. "'contenttypes_tests.TaggedItem', but that model does not have a "
  253. "GenericForeignKey.",
  254. obj=Bookmark.tags.field,
  255. id='contenttypes.E004',
  256. )
  257. ]
  258. self.assertEqual(errors, expected)
  259. @override_settings(TEST_SWAPPED_MODEL='contenttypes_tests.Replacement')
  260. def test_pointing_to_swapped_model(self):
  261. class Replacement(models.Model):
  262. pass
  263. class SwappedModel(models.Model):
  264. content_type = models.ForeignKey(ContentType, models.CASCADE)
  265. object_id = models.PositiveIntegerField()
  266. content_object = GenericForeignKey()
  267. class Meta:
  268. swappable = 'TEST_SWAPPED_MODEL'
  269. class Model(models.Model):
  270. rel = GenericRelation('SwappedModel')
  271. errors = Model.rel.field.check()
  272. expected = [
  273. checks.Error(
  274. "Field defines a relation with the model "
  275. "'contenttypes_tests.SwappedModel', "
  276. "which has been swapped out.",
  277. hint="Update the relation to point at 'settings.TEST_SWAPPED_MODEL'.",
  278. obj=Model.rel.field,
  279. id='fields.E301',
  280. )
  281. ]
  282. self.assertEqual(errors, expected)
  283. def test_field_name_ending_with_underscore(self):
  284. class TaggedItem(models.Model):
  285. content_type = models.ForeignKey(ContentType, models.CASCADE)
  286. object_id = models.PositiveIntegerField()
  287. content_object = GenericForeignKey()
  288. class InvalidBookmark(models.Model):
  289. tags_ = GenericRelation('TaggedItem')
  290. errors = InvalidBookmark.tags_.field.check()
  291. expected = [
  292. checks.Error(
  293. 'Field names must not end with an underscore.',
  294. obj=InvalidBookmark.tags_.field,
  295. id='fields.E001',
  296. )
  297. ]
  298. self.assertEqual(errors, expected)
  299. class UpdateContentTypesTests(TestCase):
  300. def setUp(self):
  301. self.before_count = ContentType.objects.count()
  302. ContentType.objects.create(app_label='contenttypes_tests', model='Fake')
  303. self.app_config = apps.get_app_config('contenttypes_tests')
  304. def test_interactive_true(self):
  305. """
  306. interactive mode of update_contenttypes() (the default) should delete
  307. stale contenttypes.
  308. """
  309. management.input = lambda x: force_str("yes")
  310. with captured_stdout() as stdout:
  311. management.update_contenttypes(self.app_config)
  312. self.assertIn("Deleting stale content type", stdout.getvalue())
  313. self.assertEqual(ContentType.objects.count(), self.before_count)
  314. def test_interactive_false(self):
  315. """
  316. non-interactive mode of update_contenttypes() shouldn't delete stale
  317. content types.
  318. """
  319. with captured_stdout() as stdout:
  320. management.update_contenttypes(self.app_config, interactive=False)
  321. self.assertIn("Stale content types remain.", stdout.getvalue())
  322. self.assertEqual(ContentType.objects.count(), self.before_count + 1)
  323. class TestRouter(object):
  324. def db_for_read(self, model, **hints):
  325. return 'other'
  326. def db_for_write(self, model, **hints):
  327. return 'default'
  328. @override_settings(DATABASE_ROUTERS=[TestRouter()])
  329. class ContentTypesMultidbTestCase(TestCase):
  330. def setUp(self):
  331. # Whenever a test starts executing, only the "default" database is
  332. # connected. We explicitly connect to the "other" database here. If we
  333. # don't do it, then it will be implicitly connected later when we query
  334. # it, but in that case some database backends may automatically perform
  335. # extra queries upon connecting (notably mysql executes
  336. # "SET SQL_AUTO_IS_NULL = 0"), which will affect assertNumQueries().
  337. connections['other'].ensure_connection()
  338. def test_multidb(self):
  339. """
  340. Test that, when using multiple databases, we use the db_for_read (see
  341. #20401).
  342. """
  343. ContentType.objects.clear_cache()
  344. with self.assertNumQueries(0, using='default'), \
  345. self.assertNumQueries(1, using='other'):
  346. ContentType.objects.get_for_model(Author)