tests.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. from __future__ import unicode_literals
  2. from datetime import datetime
  3. from decimal import Decimal
  4. from django import forms
  5. from django.conf import settings
  6. from django.contrib import admin
  7. from django.contrib.admin import helpers
  8. from django.contrib.admin.utils import (
  9. NestedObjects, display_for_field, flatten, flatten_fieldsets,
  10. label_for_field, lookup_field, quote,
  11. )
  12. from django.db import DEFAULT_DB_ALIAS, models
  13. from django.test import SimpleTestCase, TestCase, override_settings
  14. from django.utils import six
  15. from django.utils.formats import localize
  16. from django.utils.safestring import mark_safe
  17. from .models import (
  18. Article, Car, Count, Event, EventGuide, Location, Site, Vehicle,
  19. )
  20. class NestedObjectsTests(TestCase):
  21. """
  22. Tests for ``NestedObject`` utility collection.
  23. """
  24. def setUp(self):
  25. self.n = NestedObjects(using=DEFAULT_DB_ALIAS)
  26. self.objs = [Count.objects.create(num=i) for i in range(5)]
  27. def _check(self, target):
  28. self.assertEqual(self.n.nested(lambda obj: obj.num), target)
  29. def _connect(self, i, j):
  30. self.objs[i].parent = self.objs[j]
  31. self.objs[i].save()
  32. def _collect(self, *indices):
  33. self.n.collect([self.objs[i] for i in indices])
  34. def test_unrelated_roots(self):
  35. self._connect(2, 1)
  36. self._collect(0)
  37. self._collect(1)
  38. self._check([0, 1, [2]])
  39. def test_siblings(self):
  40. self._connect(1, 0)
  41. self._connect(2, 0)
  42. self._collect(0)
  43. self._check([0, [1, 2]])
  44. def test_non_added_parent(self):
  45. self._connect(0, 1)
  46. self._collect(0)
  47. self._check([0])
  48. def test_cyclic(self):
  49. self._connect(0, 2)
  50. self._connect(1, 0)
  51. self._connect(2, 1)
  52. self._collect(0)
  53. self._check([0, [1, [2]]])
  54. def test_queries(self):
  55. self._connect(1, 0)
  56. self._connect(2, 0)
  57. # 1 query to fetch all children of 0 (1 and 2)
  58. # 1 query to fetch all children of 1 and 2 (none)
  59. # Should not require additional queries to populate the nested graph.
  60. self.assertNumQueries(2, self._collect, 0)
  61. def test_on_delete_do_nothing(self):
  62. """
  63. Check that the nested collector doesn't query for DO_NOTHING objects.
  64. """
  65. n = NestedObjects(using=DEFAULT_DB_ALIAS)
  66. objs = [Event.objects.create()]
  67. EventGuide.objects.create(event=objs[0])
  68. with self.assertNumQueries(2):
  69. # One for Location, one for Guest, and no query for EventGuide
  70. n.collect(objs)
  71. def test_relation_on_abstract(self):
  72. """
  73. #21846 -- Check that `NestedObjects.collect()` doesn't trip
  74. (AttributeError) on the special notation for relations on abstract
  75. models (related_name that contains %(app_label)s and/or %(class)s).
  76. """
  77. n = NestedObjects(using=DEFAULT_DB_ALIAS)
  78. Car.objects.create()
  79. n.collect([Vehicle.objects.first()])
  80. class UtilsTests(SimpleTestCase):
  81. empty_value = '-empty-'
  82. def test_values_from_lookup_field(self):
  83. """
  84. Regression test for #12654: lookup_field
  85. """
  86. SITE_NAME = 'example.com'
  87. TITLE_TEXT = 'Some title'
  88. CREATED_DATE = datetime.min
  89. ADMIN_METHOD = 'admin method'
  90. SIMPLE_FUNCTION = 'function'
  91. INSTANCE_ATTRIBUTE = 'attr'
  92. class MockModelAdmin(object):
  93. def get_admin_value(self, obj):
  94. return ADMIN_METHOD
  95. simple_function = lambda obj: SIMPLE_FUNCTION
  96. site_obj = Site(domain=SITE_NAME)
  97. article = Article(
  98. site=site_obj,
  99. title=TITLE_TEXT,
  100. created=CREATED_DATE,
  101. )
  102. article.non_field = INSTANCE_ATTRIBUTE
  103. verifications = (
  104. ('site', SITE_NAME),
  105. ('created', localize(CREATED_DATE)),
  106. ('title', TITLE_TEXT),
  107. ('get_admin_value', ADMIN_METHOD),
  108. (simple_function, SIMPLE_FUNCTION),
  109. ('test_from_model', article.test_from_model()),
  110. ('non_field', INSTANCE_ATTRIBUTE)
  111. )
  112. mock_admin = MockModelAdmin()
  113. for name, value in verifications:
  114. field, attr, resolved_value = lookup_field(name, article, mock_admin)
  115. if field is not None:
  116. resolved_value = display_for_field(resolved_value, field, self.empty_value)
  117. self.assertEqual(value, resolved_value)
  118. def test_null_display_for_field(self):
  119. """
  120. Regression test for #12550: display_for_field should handle None
  121. value.
  122. """
  123. display_value = display_for_field(None, models.CharField(), self.empty_value)
  124. self.assertEqual(display_value, self.empty_value)
  125. display_value = display_for_field(None, models.CharField(
  126. choices=(
  127. (None, "test_none"),
  128. )
  129. ), self.empty_value)
  130. self.assertEqual(display_value, "test_none")
  131. display_value = display_for_field(None, models.DateField(), self.empty_value)
  132. self.assertEqual(display_value, self.empty_value)
  133. display_value = display_for_field(None, models.TimeField(), self.empty_value)
  134. self.assertEqual(display_value, self.empty_value)
  135. # Regression test for #13071: NullBooleanField has special
  136. # handling.
  137. display_value = display_for_field(None, models.NullBooleanField(), self.empty_value)
  138. expected = '<img src="%sadmin/img/icon-unknown.gif" alt="None" />' % settings.STATIC_URL
  139. self.assertHTMLEqual(display_value, expected)
  140. display_value = display_for_field(None, models.DecimalField(), self.empty_value)
  141. self.assertEqual(display_value, self.empty_value)
  142. display_value = display_for_field(None, models.FloatField(), self.empty_value)
  143. self.assertEqual(display_value, self.empty_value)
  144. def test_number_formats_display_for_field(self):
  145. display_value = display_for_field(12345.6789, models.FloatField(), self.empty_value)
  146. self.assertEqual(display_value, '12345.6789')
  147. display_value = display_for_field(Decimal('12345.6789'), models.DecimalField(), self.empty_value)
  148. self.assertEqual(display_value, '12345.6789')
  149. display_value = display_for_field(12345, models.IntegerField(), self.empty_value)
  150. self.assertEqual(display_value, '12345')
  151. @override_settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True)
  152. def test_number_formats_with_thousand_seperator_display_for_field(self):
  153. display_value = display_for_field(12345.6789, models.FloatField(), self.empty_value)
  154. self.assertEqual(display_value, '12,345.6789')
  155. display_value = display_for_field(Decimal('12345.6789'), models.DecimalField(), self.empty_value)
  156. self.assertEqual(display_value, '12,345.6789')
  157. display_value = display_for_field(12345, models.IntegerField(), self.empty_value)
  158. self.assertEqual(display_value, '12,345')
  159. def test_label_for_field(self):
  160. """
  161. Tests for label_for_field
  162. """
  163. self.assertEqual(
  164. label_for_field("title", Article),
  165. "title"
  166. )
  167. self.assertEqual(
  168. label_for_field("title2", Article),
  169. "another name"
  170. )
  171. self.assertEqual(
  172. label_for_field("title2", Article, return_attr=True),
  173. ("another name", None)
  174. )
  175. self.assertEqual(
  176. label_for_field("__unicode__", Article),
  177. "article"
  178. )
  179. self.assertEqual(
  180. label_for_field("__str__", Article),
  181. str("article")
  182. )
  183. self.assertRaises(
  184. AttributeError,
  185. lambda: label_for_field("unknown", Article)
  186. )
  187. def test_callable(obj):
  188. return "nothing"
  189. self.assertEqual(
  190. label_for_field(test_callable, Article),
  191. "Test callable"
  192. )
  193. self.assertEqual(
  194. label_for_field(test_callable, Article, return_attr=True),
  195. ("Test callable", test_callable)
  196. )
  197. self.assertEqual(
  198. label_for_field("test_from_model", Article),
  199. "Test from model"
  200. )
  201. self.assertEqual(
  202. label_for_field("test_from_model", Article, return_attr=True),
  203. ("Test from model", Article.test_from_model)
  204. )
  205. self.assertEqual(
  206. label_for_field("test_from_model_with_override", Article),
  207. "not What you Expect"
  208. )
  209. self.assertEqual(
  210. label_for_field(lambda x: "nothing", Article),
  211. "--"
  212. )
  213. class MockModelAdmin(object):
  214. def test_from_model(self, obj):
  215. return "nothing"
  216. test_from_model.short_description = "not Really the Model"
  217. self.assertEqual(
  218. label_for_field("test_from_model", Article, model_admin=MockModelAdmin),
  219. "not Really the Model"
  220. )
  221. self.assertEqual(
  222. label_for_field("test_from_model", Article,
  223. model_admin=MockModelAdmin,
  224. return_attr=True),
  225. ("not Really the Model", MockModelAdmin.test_from_model)
  226. )
  227. def test_label_for_property(self):
  228. # NOTE: cannot use @property decorator, because of
  229. # AttributeError: 'property' object has no attribute 'short_description'
  230. class MockModelAdmin(object):
  231. def my_property(self):
  232. return "this if from property"
  233. my_property.short_description = 'property short description'
  234. test_from_property = property(my_property)
  235. self.assertEqual(
  236. label_for_field("test_from_property", Article, model_admin=MockModelAdmin),
  237. 'property short description'
  238. )
  239. def test_related_name(self):
  240. """
  241. Regression test for #13963
  242. """
  243. self.assertEqual(
  244. label_for_field('location', Event, return_attr=True),
  245. ('location', None),
  246. )
  247. self.assertEqual(
  248. label_for_field('event', Location, return_attr=True),
  249. ('awesome event', None),
  250. )
  251. self.assertEqual(
  252. label_for_field('guest', Event, return_attr=True),
  253. ('awesome guest', None),
  254. )
  255. def test_logentry_unicode(self):
  256. """
  257. Regression test for #15661
  258. """
  259. log_entry = admin.models.LogEntry()
  260. log_entry.action_flag = admin.models.ADDITION
  261. self.assertTrue(
  262. six.text_type(log_entry).startswith('Added ')
  263. )
  264. log_entry.action_flag = admin.models.CHANGE
  265. self.assertTrue(
  266. six.text_type(log_entry).startswith('Changed ')
  267. )
  268. log_entry.action_flag = admin.models.DELETION
  269. self.assertTrue(
  270. six.text_type(log_entry).startswith('Deleted ')
  271. )
  272. # Make sure custom action_flags works
  273. log_entry.action_flag = 4
  274. self.assertEqual(six.text_type(log_entry), 'LogEntry Object')
  275. def test_safestring_in_field_label(self):
  276. # safestring should not be escaped
  277. class MyForm(forms.Form):
  278. text = forms.CharField(label=mark_safe('<i>text</i>'))
  279. cb = forms.BooleanField(label=mark_safe('<i>cb</i>'))
  280. form = MyForm()
  281. self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
  282. '<label for="id_text" class="required inline"><i>text</i>:</label>')
  283. self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
  284. '<label for="id_cb" class="vCheckboxLabel required inline"><i>cb</i></label>')
  285. # normal strings needs to be escaped
  286. class MyForm(forms.Form):
  287. text = forms.CharField(label='&text')
  288. cb = forms.BooleanField(label='&cb')
  289. form = MyForm()
  290. self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
  291. '<label for="id_text" class="required inline">&amp;text:</label>')
  292. self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
  293. '<label for="id_cb" class="vCheckboxLabel required inline">&amp;cb</label>')
  294. def test_flatten(self):
  295. flat_all = ['url', 'title', 'content', 'sites']
  296. inputs = (
  297. ((), []),
  298. (('url', 'title', ('content', 'sites')), flat_all),
  299. (('url', 'title', 'content', 'sites'), flat_all),
  300. ((('url', 'title'), ('content', 'sites')), flat_all)
  301. )
  302. for orig, expected in inputs:
  303. self.assertEqual(flatten(orig), expected)
  304. def test_flatten_fieldsets(self):
  305. """
  306. Regression test for #18051
  307. """
  308. fieldsets = (
  309. (None, {
  310. 'fields': ('url', 'title', ('content', 'sites'))
  311. }),
  312. )
  313. self.assertEqual(flatten_fieldsets(fieldsets), ['url', 'title', 'content', 'sites'])
  314. fieldsets = (
  315. (None, {
  316. 'fields': ('url', 'title', ['content', 'sites'])
  317. }),
  318. )
  319. self.assertEqual(flatten_fieldsets(fieldsets), ['url', 'title', 'content', 'sites'])
  320. def test_quote(self):
  321. self.assertEqual(quote('something\nor\nother'), 'something_0Aor_0Aother')