tests.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. import datetime
  2. import sys
  3. import unittest
  4. from django.conf import settings
  5. from django.contrib.admindocs import utils
  6. from django.contrib.admindocs.views import get_return_data_type
  7. from django.contrib.auth.models import User
  8. from django.contrib.sites.models import Site
  9. from django.test import TestCase, modify_settings, override_settings
  10. from django.test.utils import captured_stderr
  11. from django.urls import reverse
  12. from .models import Company, Person
  13. class TestDataMixin(object):
  14. @classmethod
  15. def setUpTestData(cls):
  16. # password = "secret"
  17. User.objects.create(
  18. pk=100, username='super', first_name='Super', last_name='User', email='super@example.com',
  19. password='sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158', is_active=True, is_superuser=True,
  20. is_staff=True, last_login=datetime.datetime(2007, 5, 30, 13, 20, 10),
  21. date_joined=datetime.datetime(2007, 5, 30, 13, 20, 10)
  22. )
  23. @override_settings(
  24. PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'],
  25. ROOT_URLCONF='admin_docs.urls')
  26. @modify_settings(INSTALLED_APPS={'append': 'django.contrib.admindocs'})
  27. class AdminDocsTestCase(TestCase):
  28. pass
  29. class MiscTests(AdminDocsTestCase):
  30. def setUp(self):
  31. User.objects.create_superuser('super', None, 'secret')
  32. self.client.login(username='super', password='secret')
  33. @modify_settings(INSTALLED_APPS={'remove': 'django.contrib.sites'})
  34. @override_settings(SITE_ID=None) # will restore SITE_ID after the test
  35. def test_no_sites_framework(self):
  36. """
  37. Without the sites framework, should not access SITE_ID or Site
  38. objects. Deleting settings is fine here as UserSettingsHolder is used.
  39. """
  40. Site.objects.all().delete()
  41. del settings.SITE_ID
  42. self.client.get('/admindocs/views/') # should not raise
  43. @unittest.skipUnless(utils.docutils_is_available, "no docutils installed.")
  44. class AdminDocViewTests(TestDataMixin, AdminDocsTestCase):
  45. def setUp(self):
  46. self.client.login(username='super', password='secret')
  47. def test_index(self):
  48. self.client.logout()
  49. response = self.client.get(reverse('django-admindocs-docroot'), follow=True)
  50. # Should display the login screen
  51. self.assertContains(response,
  52. '<input type="hidden" name="next" value="/admindocs/" />', html=True)
  53. self.client.login(username='super', password='secret')
  54. response = self.client.get(reverse('django-admindocs-docroot'))
  55. self.assertContains(response, '<h1>Documentation</h1>', html=True)
  56. self.assertContains(response,
  57. '<h1 id="site-name"><a href="/admin/">Django '
  58. 'administration</a></h1>')
  59. def test_bookmarklets(self):
  60. response = self.client.get(reverse('django-admindocs-bookmarklets'))
  61. self.assertContains(response, '/admindocs/views/')
  62. def test_templatetag_index(self):
  63. response = self.client.get(reverse('django-admindocs-tags'))
  64. self.assertContains(response, '<h3 id="built_in-extends">extends</h3>', html=True)
  65. def test_templatefilter_index(self):
  66. response = self.client.get(reverse('django-admindocs-filters'))
  67. self.assertContains(response, '<h3 id="built_in-first">first</h3>', html=True)
  68. def test_view_index(self):
  69. response = self.client.get(reverse('django-admindocs-views-index'))
  70. self.assertContains(response,
  71. '<h3><a href="/admindocs/views/django.contrib.admindocs.views.BaseAdminDocsView/">/admindocs/</a></h3>',
  72. html=True)
  73. self.assertContains(response, 'Views by namespace test')
  74. self.assertContains(response, 'Name: <code>test:func</code>.')
  75. def test_view_detail(self):
  76. response = self.client.get(
  77. reverse('django-admindocs-views-detail',
  78. args=['django.contrib.admindocs.views.BaseAdminDocsView']))
  79. # View docstring
  80. self.assertContains(response, 'Base view for admindocs views.')
  81. def test_view_detail_illegal_import(self):
  82. """
  83. #23601 - Ensure the view exists in the URLconf.
  84. """
  85. response = self.client.get(
  86. reverse('django-admindocs-views-detail',
  87. args=['urlpatterns_reverse.nonimported_module.view']))
  88. self.assertEqual(response.status_code, 404)
  89. self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules)
  90. def test_model_index(self):
  91. response = self.client.get(reverse('django-admindocs-models-index'))
  92. self.assertContains(
  93. response,
  94. '<h2 id="app-auth">Authentication and Authorization (django.contrib.auth)</h2>',
  95. html=True
  96. )
  97. def test_template_detail(self):
  98. response = self.client.get(reverse('django-admindocs-templates',
  99. args=['admin_doc/template_detail.html']))
  100. self.assertContains(response,
  101. '<h1>Template: "admin_doc/template_detail.html"</h1>', html=True)
  102. def test_missing_docutils(self):
  103. utils.docutils_is_available = False
  104. try:
  105. response = self.client.get(reverse('django-admindocs-docroot'))
  106. self.assertContains(response,
  107. '<h3>The admin documentation system requires Python\'s '
  108. '<a href="http://docutils.sf.net/">docutils</a> library.</h3>',
  109. html=True)
  110. self.assertContains(response,
  111. '<h1 id="site-name"><a href="/admin/">Django '
  112. 'administration</a></h1>')
  113. finally:
  114. utils.docutils_is_available = True
  115. @override_settings(TEMPLATES=[{
  116. 'NAME': 'ONE',
  117. 'BACKEND': 'django.template.backends.django.DjangoTemplates',
  118. 'APP_DIRS': True,
  119. }, {
  120. 'NAME': 'TWO',
  121. 'BACKEND': 'django.template.backends.django.DjangoTemplates',
  122. 'APP_DIRS': True,
  123. }])
  124. @unittest.skipUnless(utils.docutils_is_available, "no docutils installed.")
  125. class AdminDocViewWithMultipleEngines(AdminDocViewTests):
  126. def test_templatefilter_index(self):
  127. # Overridden because non-trivial TEMPLATES settings aren't supported
  128. # but the page shouldn't crash (#24125).
  129. response = self.client.get(reverse('django-admindocs-filters'))
  130. self.assertContains(response, '<title>Template filters</title>', html=True)
  131. def test_templatetag_index(self):
  132. # Overridden because non-trivial TEMPLATES settings aren't supported
  133. # but the page shouldn't crash (#24125).
  134. response = self.client.get(reverse('django-admindocs-tags'))
  135. self.assertContains(response, '<title>Template tags</title>', html=True)
  136. class XViewMiddlewareTest(TestDataMixin, AdminDocsTestCase):
  137. def test_xview_func(self):
  138. user = User.objects.get(username='super')
  139. response = self.client.head('/xview/func/')
  140. self.assertNotIn('X-View', response)
  141. self.client.login(username='super', password='secret')
  142. response = self.client.head('/xview/func/')
  143. self.assertIn('X-View', response)
  144. self.assertEqual(response['X-View'], 'admin_docs.views.xview')
  145. user.is_staff = False
  146. user.save()
  147. response = self.client.head('/xview/func/')
  148. self.assertNotIn('X-View', response)
  149. user.is_staff = True
  150. user.is_active = False
  151. user.save()
  152. response = self.client.head('/xview/func/')
  153. self.assertNotIn('X-View', response)
  154. def test_xview_class(self):
  155. user = User.objects.get(username='super')
  156. response = self.client.head('/xview/class/')
  157. self.assertNotIn('X-View', response)
  158. self.client.login(username='super', password='secret')
  159. response = self.client.head('/xview/class/')
  160. self.assertIn('X-View', response)
  161. self.assertEqual(response['X-View'], 'admin_docs.views.XViewClass')
  162. user.is_staff = False
  163. user.save()
  164. response = self.client.head('/xview/class/')
  165. self.assertNotIn('X-View', response)
  166. user.is_staff = True
  167. user.is_active = False
  168. user.save()
  169. response = self.client.head('/xview/class/')
  170. self.assertNotIn('X-View', response)
  171. @unittest.skipUnless(utils.docutils_is_available, "no docutils installed.")
  172. class DefaultRoleTest(AdminDocsTestCase):
  173. def test_parse_rst(self):
  174. """
  175. ``django.contrib.admindocs.utils.parse_rst`` should use
  176. ``cmsreference`` as the default role.
  177. """
  178. markup = ('<p><a class="reference external" href="/admindocs/%s">'
  179. 'title</a></p>\n')
  180. self.assertEqual(utils.parse_rst('`title`', 'model'),
  181. markup % 'models/title/')
  182. self.assertEqual(utils.parse_rst('`title`', 'view'),
  183. markup % 'views/title/')
  184. self.assertEqual(utils.parse_rst('`title`', 'template'),
  185. markup % 'templates/title/')
  186. self.assertEqual(utils.parse_rst('`title`', 'filter'),
  187. markup % 'filters/#title')
  188. self.assertEqual(utils.parse_rst('`title`', 'tag'),
  189. markup % 'tags/#title')
  190. def test_publish_parts(self):
  191. """
  192. Django shouldn't break the default role for interpreted text
  193. when ``publish_parts`` is used directly, by setting it to
  194. ``cmsreference``. See #6681.
  195. """
  196. import docutils
  197. self.assertNotEqual(docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE,
  198. 'cmsreference')
  199. source = 'reST, `interpreted text`, default role.'
  200. markup = '<p>reST, <cite>interpreted text</cite>, default role.</p>\n'
  201. parts = docutils.core.publish_parts(source=source, writer_name="html4css1")
  202. self.assertEqual(parts['fragment'], markup)
  203. @unittest.skipUnless(utils.docutils_is_available, "no docutils installed.")
  204. class TestModelDetailView(TestDataMixin, AdminDocsTestCase):
  205. """
  206. Tests that various details render correctly
  207. """
  208. def setUp(self):
  209. self.client.login(username='super', password='secret')
  210. with captured_stderr() as self.docutils_stderr:
  211. self.response = self.client.get(reverse('django-admindocs-models-detail', args=['admin_docs', 'Person']))
  212. def test_method_excludes(self):
  213. """
  214. Methods that begin with strings defined in
  215. ``django.contrib.admindocs.views.MODEL_METHODS_EXCLUDE``
  216. should not get displayed in the admin docs.
  217. """
  218. self.assertContains(self.response, "<td>get_full_name</td>")
  219. self.assertNotContains(self.response, "<td>_get_full_name</td>")
  220. self.assertNotContains(self.response, "<td>add_image</td>")
  221. self.assertNotContains(self.response, "<td>delete_image</td>")
  222. self.assertNotContains(self.response, "<td>set_status</td>")
  223. self.assertNotContains(self.response, "<td>save_changes</td>")
  224. def test_methods_with_arguments(self):
  225. """
  226. Methods that take arguments should also displayed.
  227. """
  228. self.assertContains(self.response, "<h3>Methods with arguments</h3>")
  229. self.assertContains(self.response, "<td>rename_company</td>")
  230. self.assertContains(self.response, "<td>dummy_function</td>")
  231. self.assertContains(self.response, "<td>suffix_company_name</td>")
  232. def test_methods_with_arguments_display_arguments(self):
  233. """
  234. Methods with arguments should have their arguments displayed.
  235. """
  236. self.assertContains(self.response, "<td>new_name</td>")
  237. def test_methods_with_arguments_display_arguments_default_value(self):
  238. """
  239. Methods with keyword arguments should have their arguments displayed.
  240. """
  241. self.assertContains(self.response, "<td>suffix=&#39;ltd&#39;</td>")
  242. def test_methods_with_multiple_arguments_display_arguments(self):
  243. """
  244. Methods with multiple arguments should have all their arguments
  245. displayed, but omitting 'self'.
  246. """
  247. self.assertContains(self.response, "<td>baz, rox, *some_args, **some_kwargs</td>")
  248. def test_method_data_types(self):
  249. """
  250. We should be able to get a basic idea of the type returned
  251. by a method
  252. """
  253. company = Company.objects.create(name="Django")
  254. person = Person.objects.create(
  255. first_name="Human",
  256. last_name="User",
  257. company=company
  258. )
  259. self.assertEqual(
  260. get_return_data_type(person.get_status_count.__name__),
  261. 'Integer'
  262. )
  263. self.assertEqual(
  264. get_return_data_type(person.get_groups_list.__name__),
  265. 'List'
  266. )
  267. def test_descriptions_render_correctly(self):
  268. """
  269. The ``description`` field should render correctly for each type of field
  270. """
  271. # help text in fields
  272. self.assertContains(self.response, "<td>first name - The person's first name</td>")
  273. self.assertContains(self.response, "<td>last name - The person's last name</td>")
  274. # method docstrings
  275. self.assertContains(self.response, "<p>Get the full name of the person</p>")
  276. link = '<a class="reference external" href="/admindocs/models/%s/">%s</a>'
  277. markup = '<p>the related %s object</p>'
  278. company_markup = markup % (link % ("admin_docs.company", "admin_docs.Company"))
  279. # foreign keys
  280. self.assertContains(self.response, company_markup)
  281. # foreign keys with help text
  282. self.assertContains(self.response, "%s\n - place of work" % company_markup)
  283. # many to many fields
  284. self.assertContains(
  285. self.response,
  286. "number of related %s objects" % (link % ("admin_docs.group", "admin_docs.Group"))
  287. )
  288. self.assertContains(
  289. self.response,
  290. "all related %s objects" % (link % ("admin_docs.group", "admin_docs.Group"))
  291. )
  292. # "raw" and "include" directives are disabled
  293. self.assertContains(self.response, '<p>&quot;raw&quot; directive disabled.</p>',)
  294. self.assertContains(self.response, '.. raw:: html\n :file: admin_docs/evilfile.txt')
  295. self.assertContains(self.response, '<p>&quot;include&quot; directive disabled.</p>',)
  296. self.assertContains(self.response, '.. include:: admin_docs/evilfile.txt')
  297. out = self.docutils_stderr.getvalue()
  298. self.assertIn('"raw" directive disabled', out)
  299. self.assertIn('"include" directive disabled', out)
  300. def test_model_with_many_to_one(self):
  301. link = '<a class="reference external" href="/admindocs/models/%s/">%s</a>'
  302. response = self.client.get(
  303. reverse('django-admindocs-models-detail', args=['admin_docs', 'company'])
  304. )
  305. self.assertContains(
  306. response,
  307. "number of related %s objects" % (link % ("admin_docs.person", "admin_docs.Person"))
  308. )
  309. self.assertContains(
  310. response,
  311. "all related %s objects" % (link % ("admin_docs.person", "admin_docs.Person"))
  312. )
  313. def test_model_with_no_backward_relations_render_only_relevant_fields(self):
  314. """
  315. A model with ``related_name`` of `+` should not show backward relationship
  316. links in admin docs
  317. """
  318. response = self.client.get(
  319. reverse('django-admindocs-models-detail',
  320. args=['admin_docs', 'family']))
  321. fields = response.context_data.get('fields')
  322. self.assertEqual(len(fields), 2)
  323. def test_model_docstring_renders_correctly(self):
  324. summary = (
  325. '<h2 class="subhead"><p>Stores information about a person, related to <a class="reference external" '
  326. 'href="/admindocs/models/myapp.company/">myapp.Company</a>.</p></h2>'
  327. )
  328. subheading = '<p><strong>Notes</strong></p>'
  329. body = '<p>Use <tt class="docutils literal">save_changes()</tt> when saving this object.</p>'
  330. model_body = (
  331. '<dl class="docutils"><dt><tt class="'
  332. 'docutils literal">company</tt></dt><dd>Field storing <a class="'
  333. 'reference external" href="/admindocs/models/myapp.company/">'
  334. 'myapp.Company</a> where the person works.</dd></dl>'
  335. )
  336. self.assertContains(self.response, 'DESCRIPTION')
  337. self.assertContains(self.response, summary, html=True)
  338. self.assertContains(self.response, subheading, html=True)
  339. self.assertContains(self.response, body, html=True)
  340. self.assertContains(self.response, model_body, html=True)
  341. def test_model_detail_title(self):
  342. self.assertContains(self.response, '<h1>admin_docs.Person</h1>', html=True)
  343. @unittest.skipUnless(utils.docutils_is_available, "no docutils installed.")
  344. class TestUtils(AdminDocsTestCase):
  345. """
  346. This __doc__ output is required for testing. I copied this example from
  347. `admindocs` documentation. (TITLE)
  348. Display an individual :model:`myapp.MyModel`.
  349. **Context**
  350. ``RequestContext``
  351. ``mymodel``
  352. An instance of :model:`myapp.MyModel`.
  353. **Template:**
  354. :template:`myapp/my_template.html` (DESCRIPTION)
  355. some_metadata: some data
  356. """
  357. def setUp(self):
  358. self.docstring = self.__doc__
  359. def test_trim_docstring(self):
  360. trim_docstring_output = utils.trim_docstring(self.docstring)
  361. trimmed_docstring = (
  362. 'This __doc__ output is required for testing. I copied this '
  363. 'example from\n`admindocs` documentation. (TITLE)\n\n'
  364. 'Display an individual :model:`myapp.MyModel`.\n\n'
  365. '**Context**\n\n``RequestContext``\n\n``mymodel``\n'
  366. ' An instance of :model:`myapp.MyModel`.\n\n'
  367. '**Template:**\n\n:template:`myapp/my_template.html` '
  368. '(DESCRIPTION)\n\nsome_metadata: some data'
  369. )
  370. self.assertEqual(trim_docstring_output, trimmed_docstring)
  371. def test_parse_docstring(self):
  372. title, description, metadata = utils.parse_docstring(self.docstring)
  373. docstring_title = (
  374. 'This __doc__ output is required for testing. I copied this example from\n'
  375. '`admindocs` documentation. (TITLE)'
  376. )
  377. docstring_description = (
  378. 'Display an individual :model:`myapp.MyModel`.\n\n'
  379. '**Context**\n\n``RequestContext``\n\n``mymodel``\n'
  380. ' An instance of :model:`myapp.MyModel`.\n\n'
  381. '**Template:**\n\n:template:`myapp/my_template.html` '
  382. '(DESCRIPTION)'
  383. )
  384. self.assertEqual(title, docstring_title)
  385. self.assertEqual(description, docstring_description)
  386. self.assertEqual(metadata, {'some_metadata': 'some data'})
  387. def test_title_output(self):
  388. title, description, metadata = utils.parse_docstring(self.docstring)
  389. title_output = utils.parse_rst(title, 'model', 'model:admindocs')
  390. self.assertIn('TITLE', title_output)
  391. title_rendered = (
  392. '<p>This __doc__ output is required for testing. I copied this '
  393. 'example from\n<a class="reference external" '
  394. 'href="/admindocs/models/admindocs/">admindocs</a> documentation. '
  395. '(TITLE)</p>\n'
  396. )
  397. self.assertHTMLEqual(title_output, title_rendered)
  398. def test_description_output(self):
  399. title, description, metadata = utils.parse_docstring(self.docstring)
  400. description_output = utils.parse_rst(description, 'model', 'model:admindocs')
  401. description_rendered = (
  402. '<p>Display an individual <a class="reference external" '
  403. 'href="/admindocs/models/myapp.mymodel/">myapp.MyModel</a>.</p>\n'
  404. '<p><strong>Context</strong></p>\n<p><tt class="docutils literal">'
  405. 'RequestContext</tt></p>\n<dl class="docutils">\n<dt><tt class="'
  406. 'docutils literal">mymodel</tt></dt>\n<dd>An instance of <a class="'
  407. 'reference external" href="/admindocs/models/myapp.mymodel/">'
  408. 'myapp.MyModel</a>.</dd>\n</dl>\n<p><strong>Template:</strong></p>'
  409. '\n<p><a class="reference external" href="/admindocs/templates/'
  410. 'myapp/my_template.html/">myapp/my_template.html</a> (DESCRIPTION)'
  411. '</p>\n'
  412. )
  413. self.assertHTMLEqual(description_output, description_rendered)
  414. def test_initial_header_level(self):
  415. header = 'should be h3...\n\nHeader\n------\n'
  416. output = utils.parse_rst(header, 'header')
  417. self.assertIn('<h3>Header</h3>', output)