tests.py 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108
  1. # -*- coding: utf-8 -*-
  2. """
  3. Unit tests for reverse URL lookups.
  4. """
  5. from __future__ import unicode_literals
  6. import sys
  7. import unittest
  8. from admin_scripts.tests import AdminScriptTestCase
  9. from django.conf import settings
  10. from django.conf.urls import include, url
  11. from django.contrib.auth.models import User
  12. from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
  13. from django.http import (
  14. HttpRequest, HttpResponsePermanentRedirect, HttpResponseRedirect,
  15. )
  16. from django.shortcuts import redirect
  17. from django.test import (
  18. SimpleTestCase, TestCase, ignore_warnings, override_settings,
  19. )
  20. from django.test.utils import override_script_prefix
  21. from django.urls import (
  22. NoReverseMatch, RegexURLPattern, RegexURLResolver, Resolver404,
  23. ResolverMatch, get_callable, get_resolver, resolve, reverse, reverse_lazy,
  24. )
  25. from django.utils import six
  26. from django.utils.deprecation import RemovedInDjango20Warning
  27. from . import middleware, urlconf_outer, views
  28. from .utils import URLObject
  29. from .views import empty_view
  30. resolve_test_data = (
  31. # These entries are in the format: (path, url_name, app_name, namespace, view_name, func, args, kwargs)
  32. # Simple case
  33. ('/normal/42/37/', 'normal-view', '', '', 'normal-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}),
  34. (
  35. '/view_class/42/37/', 'view-class', '', '', 'view-class', views.view_class_instance, tuple(),
  36. {'arg1': '42', 'arg2': '37'}
  37. ),
  38. (
  39. '/included/normal/42/37/', 'inc-normal-view', '', '', 'inc-normal-view', views.empty_view, tuple(),
  40. {'arg1': '42', 'arg2': '37'}
  41. ),
  42. (
  43. '/included/view_class/42/37/', 'inc-view-class', '', '', 'inc-view-class', views.view_class_instance, tuple(),
  44. {'arg1': '42', 'arg2': '37'}
  45. ),
  46. # Unnamed args are dropped if you have *any* kwargs in a pattern
  47. ('/mixed_args/42/37/', 'mixed-args', '', '', 'mixed-args', views.empty_view, tuple(), {'arg2': '37'}),
  48. (
  49. '/included/mixed_args/42/37/', 'inc-mixed-args', '', '', 'inc-mixed-args', views.empty_view, tuple(),
  50. {'arg2': '37'}
  51. ),
  52. (
  53. '/included/12/mixed_args/42/37/', 'inc-mixed-args', '', '', 'inc-mixed-args', views.empty_view, tuple(),
  54. {'arg2': '37'}
  55. ),
  56. # Unnamed views should have None as the url_name. Regression data for #21157.
  57. (
  58. '/unnamed/normal/42/37/', None, '', '', 'urlpatterns_reverse.views.empty_view', views.empty_view, tuple(),
  59. {'arg1': '42', 'arg2': '37'}
  60. ),
  61. (
  62. '/unnamed/view_class/42/37/', None, '', '', 'urlpatterns_reverse.views.ViewClass', views.view_class_instance,
  63. tuple(), {'arg1': '42', 'arg2': '37'}
  64. ),
  65. # If you have no kwargs, you get an args list.
  66. ('/no_kwargs/42/37/', 'no-kwargs', '', '', 'no-kwargs', views.empty_view, ('42', '37'), {}),
  67. ('/included/no_kwargs/42/37/', 'inc-no-kwargs', '', '', 'inc-no-kwargs', views.empty_view, ('42', '37'), {}),
  68. (
  69. '/included/12/no_kwargs/42/37/', 'inc-no-kwargs', '', '', 'inc-no-kwargs', views.empty_view,
  70. ('12', '42', '37'), {}
  71. ),
  72. # Namespaces
  73. (
  74. '/test1/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns1', 'test-ns1:urlobject-view',
  75. views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}
  76. ),
  77. (
  78. '/included/test3/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns3', 'test-ns3:urlobject-view',
  79. views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}
  80. ),
  81. (
  82. '/ns-included1/normal/42/37/', 'inc-normal-view', '', 'inc-ns1', 'inc-ns1:inc-normal-view', views.empty_view,
  83. tuple(), {'arg1': '42', 'arg2': '37'}
  84. ),
  85. (
  86. '/included/test3/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns3', 'test-ns3:urlobject-view',
  87. views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}
  88. ),
  89. (
  90. '/default/inner/42/37/', 'urlobject-view', 'testapp', 'testapp', 'testapp:urlobject-view', views.empty_view,
  91. tuple(), {'arg1': '42', 'arg2': '37'}
  92. ),
  93. (
  94. '/other2/inner/42/37/', 'urlobject-view', 'nodefault', 'other-ns2', 'other-ns2:urlobject-view',
  95. views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}
  96. ),
  97. (
  98. '/other1/inner/42/37/', 'urlobject-view', 'nodefault', 'other-ns1', 'other-ns1:urlobject-view',
  99. views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}
  100. ),
  101. # Nested namespaces
  102. (
  103. '/ns-included1/test3/inner/42/37/', 'urlobject-view', 'testapp', 'inc-ns1:test-ns3',
  104. 'inc-ns1:test-ns3:urlobject-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}
  105. ),
  106. (
  107. '/ns-included1/ns-included4/ns-included2/test3/inner/42/37/', 'urlobject-view', 'testapp',
  108. 'inc-ns1:inc-ns4:inc-ns2:test-ns3', 'inc-ns1:inc-ns4:inc-ns2:test-ns3:urlobject-view', views.empty_view,
  109. tuple(), {'arg1': '42', 'arg2': '37'}
  110. ),
  111. (
  112. '/app-included/test3/inner/42/37/', 'urlobject-view', 'inc-app:testapp', 'inc-app:test-ns3',
  113. 'inc-app:test-ns3:urlobject-view', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}
  114. ),
  115. (
  116. '/app-included/ns-included4/ns-included2/test3/inner/42/37/', 'urlobject-view', 'inc-app:testapp',
  117. 'inc-app:inc-ns4:inc-ns2:test-ns3', 'inc-app:inc-ns4:inc-ns2:test-ns3:urlobject-view', views.empty_view,
  118. tuple(), {'arg1': '42', 'arg2': '37'}
  119. ),
  120. # Namespaces capturing variables
  121. ('/inc70/', 'inner-nothing', '', 'inc-ns5', 'inc-ns5:inner-nothing', views.empty_view, tuple(), {'outer': '70'}),
  122. (
  123. '/inc78/extra/foobar/', 'inner-extra', '', 'inc-ns5', 'inc-ns5:inner-extra', views.empty_view, tuple(),
  124. {'outer': '78', 'extra': 'foobar'}
  125. ),
  126. )
  127. test_data = (
  128. ('places', '/places/3/', [3], {}),
  129. ('places', '/places/3/', ['3'], {}),
  130. ('places', NoReverseMatch, ['a'], {}),
  131. ('places', NoReverseMatch, [], {}),
  132. ('places?', '/place/', [], {}),
  133. ('places+', '/places/', [], {}),
  134. ('places*', '/place/', [], {}),
  135. ('places2?', '/', [], {}),
  136. ('places2+', '/places/', [], {}),
  137. ('places2*', '/', [], {}),
  138. ('places3', '/places/4/', [4], {}),
  139. ('places3', '/places/harlem/', ['harlem'], {}),
  140. ('places3', NoReverseMatch, ['harlem64'], {}),
  141. ('places4', '/places/3/', [], {'id': 3}),
  142. ('people', NoReverseMatch, [], {}),
  143. ('people', '/people/adrian/', ['adrian'], {}),
  144. ('people', '/people/adrian/', [], {'name': 'adrian'}),
  145. ('people', NoReverseMatch, ['name with spaces'], {}),
  146. ('people', NoReverseMatch, [], {'name': 'name with spaces'}),
  147. ('people2', '/people/name/', [], {}),
  148. ('people2a', '/people/name/fred/', ['fred'], {}),
  149. ('people_backref', '/people/nate-nate/', ['nate'], {}),
  150. ('people_backref', '/people/nate-nate/', [], {'name': 'nate'}),
  151. ('optional', '/optional/fred/', [], {'name': 'fred'}),
  152. ('optional', '/optional/fred/', ['fred'], {}),
  153. ('named_optional', '/optional/1/', [1], {}),
  154. ('named_optional', '/optional/1/', [], {'arg1': 1}),
  155. ('named_optional', '/optional/1/2/', [1, 2], {}),
  156. ('named_optional', '/optional/1/2/', [], {'arg1': 1, 'arg2': 2}),
  157. ('named_optional_terminated', '/optional/1/2/', [1, 2], {}),
  158. ('named_optional_terminated', '/optional/1/2/', [], {'arg1': 1, 'arg2': 2}),
  159. ('hardcoded', '/hardcoded/', [], {}),
  160. ('hardcoded2', '/hardcoded/doc.pdf', [], {}),
  161. ('people3', '/people/il/adrian/', [], {'state': 'il', 'name': 'adrian'}),
  162. ('people3', NoReverseMatch, [], {'state': 'il'}),
  163. ('people3', NoReverseMatch, [], {'name': 'adrian'}),
  164. ('people4', NoReverseMatch, [], {'state': 'il', 'name': 'adrian'}),
  165. ('people6', '/people/il/test/adrian/', ['il/test', 'adrian'], {}),
  166. ('people6', '/people//adrian/', ['adrian'], {}),
  167. ('range', '/character_set/a/', [], {}),
  168. ('range2', '/character_set/x/', [], {}),
  169. ('price', '/price/$10/', ['10'], {}),
  170. ('price2', '/price/$10/', ['10'], {}),
  171. ('price3', '/price/$10/', ['10'], {}),
  172. ('product', '/product/chocolate+($2.00)/', [], {'price': '2.00', 'product': 'chocolate'}),
  173. ('headlines', '/headlines/2007.5.21/', [], dict(year=2007, month=5, day=21)),
  174. (
  175. 'windows', r'/windows_path/C:%5CDocuments%20and%20Settings%5Cspam/', [],
  176. dict(drive_name='C', path=r'Documents and Settings\spam')
  177. ),
  178. ('special', r'/special_chars/~@+%5C$*%7C/', [r'~@+\$*|'], {}),
  179. ('special', r'/special_chars/some%20resource/', [r'some resource'], {}),
  180. ('special', r'/special_chars/10%25%20complete/', [r'10% complete'], {}),
  181. ('special', r'/special_chars/some%20resource/', [], {'chars': r'some resource'}),
  182. ('special', r'/special_chars/10%25%20complete/', [], {'chars': r'10% complete'}),
  183. ('special', NoReverseMatch, [''], {}),
  184. ('mixed', '/john/0/', [], {'name': 'john'}),
  185. ('repeats', '/repeats/a/', [], {}),
  186. ('repeats2', '/repeats/aa/', [], {}),
  187. ('repeats3', '/repeats/aa/', [], {}),
  188. ('insensitive', '/CaseInsensitive/fred', ['fred'], {}),
  189. ('test', '/test/1', [], {}),
  190. ('test2', '/test/2', [], {}),
  191. ('inner-nothing', '/outer/42/', [], {'outer': '42'}),
  192. ('inner-nothing', '/outer/42/', ['42'], {}),
  193. ('inner-nothing', NoReverseMatch, ['foo'], {}),
  194. ('inner-extra', '/outer/42/extra/inner/', [], {'extra': 'inner', 'outer': '42'}),
  195. ('inner-extra', '/outer/42/extra/inner/', ['42', 'inner'], {}),
  196. ('inner-extra', NoReverseMatch, ['fred', 'inner'], {}),
  197. ('inner-no-kwargs', '/outer-no-kwargs/42/inner-no-kwargs/1/', ['42', '1'], {}),
  198. ('disjunction', NoReverseMatch, ['foo'], {}),
  199. ('inner-disjunction', NoReverseMatch, ['10', '11'], {}),
  200. ('extra-places', '/e-places/10/', ['10'], {}),
  201. ('extra-people', '/e-people/fred/', ['fred'], {}),
  202. ('extra-people', '/e-people/fred/', [], {'name': 'fred'}),
  203. ('part', '/part/one/', [], {'value': 'one'}),
  204. ('part', '/prefix/xx/part/one/', [], {'value': 'one', 'prefix': 'xx'}),
  205. ('part2', '/part2/one/', [], {'value': 'one'}),
  206. ('part2', '/part2/', [], {}),
  207. ('part2', '/prefix/xx/part2/one/', [], {'value': 'one', 'prefix': 'xx'}),
  208. ('part2', '/prefix/xx/part2/', [], {'prefix': 'xx'}),
  209. # Tests for nested groups. Nested capturing groups will only work if you
  210. # *only* supply the correct outer group.
  211. ('nested-noncapture', '/nested/noncapture/opt', [], {'p': 'opt'}),
  212. ('nested-capture', '/nested/capture/opt/', ['opt/'], {}),
  213. ('nested-capture', NoReverseMatch, [], {'p': 'opt'}),
  214. ('nested-mixedcapture', '/nested/capture/mixed/opt', ['opt'], {}),
  215. ('nested-mixedcapture', NoReverseMatch, [], {'p': 'opt'}),
  216. ('nested-namedcapture', '/nested/capture/named/opt/', [], {'outer': 'opt/'}),
  217. ('nested-namedcapture', NoReverseMatch, [], {'outer': 'opt/', 'inner': 'opt'}),
  218. ('nested-namedcapture', NoReverseMatch, [], {'inner': 'opt'}),
  219. ('non_path_include', '/includes/non_path_include/', [], {}),
  220. # Tests for #13154
  221. ('defaults', '/defaults_view1/3/', [], {'arg1': 3, 'arg2': 1}),
  222. ('defaults', '/defaults_view2/3/', [], {'arg1': 3, 'arg2': 2}),
  223. ('defaults', NoReverseMatch, [], {'arg1': 3, 'arg2': 3}),
  224. ('defaults', NoReverseMatch, [], {'arg2': 1}),
  225. # Security tests
  226. ('security', '/%2Fexample.com/security/', ['/example.com'], {}),
  227. )
  228. @override_settings(ROOT_URLCONF='urlpatterns_reverse.no_urls')
  229. class NoURLPatternsTests(SimpleTestCase):
  230. def test_no_urls_exception(self):
  231. """
  232. RegexURLResolver should raise an exception when no urlpatterns exist.
  233. """
  234. resolver = RegexURLResolver(r'^$', settings.ROOT_URLCONF)
  235. with self.assertRaisesMessage(
  236. ImproperlyConfigured,
  237. "The included URLconf 'urlpatterns_reverse.no_urls' does not "
  238. "appear to have any patterns in it. If you see valid patterns in "
  239. "the file then the issue is probably caused by a circular import."
  240. ):
  241. getattr(resolver, 'url_patterns')
  242. @override_settings(ROOT_URLCONF='urlpatterns_reverse.urls')
  243. class URLPatternReverse(SimpleTestCase):
  244. def test_urlpattern_reverse(self):
  245. for name, expected, args, kwargs in test_data:
  246. try:
  247. got = reverse(name, args=args, kwargs=kwargs)
  248. except NoReverseMatch:
  249. self.assertEqual(expected, NoReverseMatch)
  250. else:
  251. self.assertEqual(got, expected)
  252. def test_reverse_none(self):
  253. # Reversing None should raise an error, not return the last un-named view.
  254. with self.assertRaises(NoReverseMatch):
  255. reverse(None)
  256. @override_script_prefix('/{{invalid}}/')
  257. def test_prefix_braces(self):
  258. self.assertEqual(
  259. '/%7B%7Binvalid%7D%7D/includes/non_path_include/',
  260. reverse('non_path_include')
  261. )
  262. def test_prefix_parenthesis(self):
  263. # Parentheses are allowed and should not cause errors or be escaped
  264. with override_script_prefix('/bogus)/'):
  265. self.assertEqual(
  266. '/bogus)/includes/non_path_include/',
  267. reverse('non_path_include')
  268. )
  269. with override_script_prefix('/(bogus)/'):
  270. self.assertEqual(
  271. '/(bogus)/includes/non_path_include/',
  272. reverse('non_path_include')
  273. )
  274. @override_script_prefix('/bump%20map/')
  275. def test_prefix_format_char(self):
  276. self.assertEqual(
  277. '/bump%2520map/includes/non_path_include/',
  278. reverse('non_path_include')
  279. )
  280. @override_script_prefix('/%7Eme/')
  281. def test_non_urlsafe_prefix_with_args(self):
  282. # Regression for #20022, adjusted for #24013 because ~ is an unreserved
  283. # character. Tests whether % is escaped.
  284. self.assertEqual('/%257Eme/places/1/', reverse('places', args=[1]))
  285. def test_patterns_reported(self):
  286. # Regression for #17076
  287. try:
  288. # this url exists, but requires an argument
  289. reverse("people", args=[])
  290. except NoReverseMatch as e:
  291. pattern_description = r"1 pattern(s) tried: ['people/(?P<name>\\w+)/$']"
  292. self.assertIn(pattern_description, str(e))
  293. else:
  294. # we can't use .assertRaises, since we want to inspect the
  295. # exception
  296. self.fail("Expected a NoReverseMatch, but none occurred.")
  297. @override_script_prefix('/script:name/')
  298. def test_script_name_escaping(self):
  299. self.assertEqual(
  300. reverse('optional', args=['foo:bar']),
  301. '/script:name/optional/foo:bar/'
  302. )
  303. def test_reverse_returns_unicode(self):
  304. name, expected, args, kwargs = test_data[0]
  305. self.assertIsInstance(
  306. reverse(name, args=args, kwargs=kwargs),
  307. six.text_type
  308. )
  309. class ResolverTests(unittest.TestCase):
  310. @ignore_warnings(category=RemovedInDjango20Warning)
  311. def test_resolver_repr(self):
  312. """
  313. Test repr of RegexURLResolver, especially when urlconf_name is a list
  314. (#17892).
  315. """
  316. # Pick a resolver from a namespaced URLconf
  317. resolver = get_resolver('urlpatterns_reverse.namespace_urls')
  318. sub_resolver = resolver.namespace_dict['test-ns1'][1]
  319. self.assertIn('<RegexURLPattern list>', repr(sub_resolver))
  320. def test_reverse_lazy_object_coercion_by_resolve(self):
  321. """
  322. Verifies lazy object returned by reverse_lazy is coerced to
  323. text by resolve(). Previous to #21043, this would raise a TypeError.
  324. """
  325. urls = 'urlpatterns_reverse.named_urls'
  326. proxy_url = reverse_lazy('named-url1', urlconf=urls)
  327. resolver = get_resolver(urls)
  328. try:
  329. resolver.resolve(proxy_url)
  330. except TypeError:
  331. self.fail('Failed to coerce lazy object to text')
  332. def test_non_regex(self):
  333. """
  334. Verifies that we raise a Resolver404 if what we are resolving doesn't
  335. meet the basic requirements of a path to match - i.e., at the very
  336. least, it matches the root pattern '^/'. We must never return None
  337. from resolve, or we will get a TypeError further down the line.
  338. Regression for #10834.
  339. """
  340. with self.assertRaises(Resolver404):
  341. resolve('')
  342. with self.assertRaises(Resolver404):
  343. resolve('a')
  344. with self.assertRaises(Resolver404):
  345. resolve('\\')
  346. with self.assertRaises(Resolver404):
  347. resolve('.')
  348. def test_404_tried_urls_have_names(self):
  349. """
  350. Verifies that the list of URLs that come back from a Resolver404
  351. exception contains a list in the right format for printing out in
  352. the DEBUG 404 page with both the patterns and URL names, if available.
  353. """
  354. urls = 'urlpatterns_reverse.named_urls'
  355. # this list matches the expected URL types and names returned when
  356. # you try to resolve a non-existent URL in the first level of included
  357. # URLs in named_urls.py (e.g., '/included/non-existent-url')
  358. url_types_names = [
  359. [{'type': RegexURLPattern, 'name': 'named-url1'}],
  360. [{'type': RegexURLPattern, 'name': 'named-url2'}],
  361. [{'type': RegexURLPattern, 'name': None}],
  362. [{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': 'named-url3'}],
  363. [{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': 'named-url4'}],
  364. [{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': None}],
  365. [{'type': RegexURLResolver}, {'type': RegexURLResolver}],
  366. ]
  367. try:
  368. resolve('/included/non-existent-url', urlconf=urls)
  369. self.fail('resolve did not raise a 404')
  370. except Resolver404 as e:
  371. # make sure we at least matched the root ('/') url resolver:
  372. self.assertIn('tried', e.args[0])
  373. tried = e.args[0]['tried']
  374. self.assertEqual(
  375. len(e.args[0]['tried']),
  376. len(url_types_names),
  377. 'Wrong number of tried URLs returned. Expected %s, got %s.' % (
  378. len(url_types_names), len(e.args[0]['tried'])
  379. )
  380. )
  381. for tried, expected in zip(e.args[0]['tried'], url_types_names):
  382. for t, e in zip(tried, expected):
  383. self.assertIsInstance(t, e['type']), str('%s is not an instance of %s') % (t, e['type'])
  384. if 'name' in e:
  385. if not e['name']:
  386. self.assertIsNone(t.name, 'Expected no URL name but found %s.' % t.name)
  387. else:
  388. self.assertEqual(
  389. t.name,
  390. e['name'],
  391. 'Wrong URL name. Expected "%s", got "%s".' % (e['name'], t.name)
  392. )
  393. @override_settings(ROOT_URLCONF='urlpatterns_reverse.reverse_lazy_urls')
  394. class ReverseLazyTest(TestCase):
  395. def test_redirect_with_lazy_reverse(self):
  396. response = self.client.get('/redirect/')
  397. self.assertRedirects(response, "/redirected_to/", status_code=302)
  398. def test_user_permission_with_lazy_reverse(self):
  399. alfred = User.objects.create_user('alfred', 'alfred@example.com', password='testpw')
  400. response = self.client.get('/login_required_view/')
  401. self.assertRedirects(response, "/login/?next=/login_required_view/", status_code=302)
  402. self.client.force_login(alfred)
  403. response = self.client.get('/login_required_view/')
  404. self.assertEqual(response.status_code, 200)
  405. def test_inserting_reverse_lazy_into_string(self):
  406. self.assertEqual(
  407. 'Some URL: %s' % reverse_lazy('some-login-page'),
  408. 'Some URL: /login/'
  409. )
  410. if six.PY2:
  411. self.assertEqual(
  412. b'Some URL: %s' % reverse_lazy('some-login-page'),
  413. 'Some URL: /login/'
  414. )
  415. class ReverseLazySettingsTest(AdminScriptTestCase):
  416. """
  417. Test that reverse_lazy can be used in settings without causing a circular
  418. import error.
  419. """
  420. def setUp(self):
  421. self.write_settings('settings.py', extra="""
  422. from django.urls import reverse_lazy
  423. LOGIN_URL = reverse_lazy('login')""")
  424. def tearDown(self):
  425. self.remove_settings('settings.py')
  426. def test_lazy_in_settings(self):
  427. out, err = self.run_manage(['check'])
  428. self.assertNoOutput(err)
  429. @override_settings(ROOT_URLCONF='urlpatterns_reverse.urls')
  430. class ReverseShortcutTests(SimpleTestCase):
  431. def test_redirect_to_object(self):
  432. # We don't really need a model; just something with a get_absolute_url
  433. class FakeObj(object):
  434. def get_absolute_url(self):
  435. return "/hi-there/"
  436. res = redirect(FakeObj())
  437. self.assertIsInstance(res, HttpResponseRedirect)
  438. self.assertEqual(res.url, '/hi-there/')
  439. res = redirect(FakeObj(), permanent=True)
  440. self.assertIsInstance(res, HttpResponsePermanentRedirect)
  441. self.assertEqual(res.url, '/hi-there/')
  442. def test_redirect_to_view_name(self):
  443. res = redirect('hardcoded2')
  444. self.assertEqual(res.url, '/hardcoded/doc.pdf')
  445. res = redirect('places', 1)
  446. self.assertEqual(res.url, '/places/1/')
  447. res = redirect('headlines', year='2008', month='02', day='17')
  448. self.assertEqual(res.url, '/headlines/2008.02.17/')
  449. with self.assertRaises(NoReverseMatch):
  450. redirect('not-a-view')
  451. def test_redirect_to_url(self):
  452. res = redirect('/foo/')
  453. self.assertEqual(res.url, '/foo/')
  454. res = redirect('http://example.com/')
  455. self.assertEqual(res.url, 'http://example.com/')
  456. # Assert that we can redirect using UTF-8 strings
  457. res = redirect('/æøå/abc/')
  458. self.assertEqual(res.url, '/%C3%A6%C3%B8%C3%A5/abc/')
  459. # Assert that no imports are attempted when dealing with a relative path
  460. # (previously, the below would resolve in a UnicodeEncodeError from __import__ )
  461. res = redirect('/æøå.abc/')
  462. self.assertEqual(res.url, '/%C3%A6%C3%B8%C3%A5.abc/')
  463. res = redirect('os.path')
  464. self.assertEqual(res.url, 'os.path')
  465. def test_no_illegal_imports(self):
  466. # modules that are not listed in urlpatterns should not be importable
  467. redirect("urlpatterns_reverse.nonimported_module.view")
  468. self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules)
  469. def test_reverse_by_path_nested(self):
  470. # Views added to urlpatterns using include() should be reversible.
  471. from .views import nested_view
  472. self.assertEqual(reverse(nested_view), '/includes/nested_path/')
  473. def test_redirect_view_object(self):
  474. from .views import absolute_kwargs_view
  475. res = redirect(absolute_kwargs_view)
  476. self.assertEqual(res.url, '/absolute_arg_view/')
  477. with self.assertRaises(NoReverseMatch):
  478. redirect(absolute_kwargs_view, wrong_argument=None)
  479. @override_settings(ROOT_URLCONF='urlpatterns_reverse.namespace_urls')
  480. @ignore_warnings(category=RemovedInDjango20Warning)
  481. class NamespaceTests(SimpleTestCase):
  482. def test_ambiguous_object(self):
  483. "Names deployed via dynamic URL objects that require namespaces can't be resolved"
  484. with self.assertRaises(NoReverseMatch):
  485. reverse('urlobject-view')
  486. with self.assertRaises(NoReverseMatch):
  487. reverse('urlobject-view', args=[37, 42])
  488. with self.assertRaises(NoReverseMatch):
  489. reverse('urlobject-view', kwargs={'arg1': 42, 'arg2': 37})
  490. def test_ambiguous_urlpattern(self):
  491. "Names deployed via dynamic URL objects that require namespaces can't be resolved"
  492. with self.assertRaises(NoReverseMatch):
  493. reverse('inner-nothing')
  494. with self.assertRaises(NoReverseMatch):
  495. reverse('inner-nothing', args=[37, 42])
  496. with self.assertRaises(NoReverseMatch):
  497. reverse('inner-nothing', kwargs={'arg1': 42, 'arg2': 37})
  498. def test_non_existent_namespace(self):
  499. "Non-existent namespaces raise errors"
  500. with self.assertRaises(NoReverseMatch):
  501. reverse('blahblah:urlobject-view')
  502. with self.assertRaises(NoReverseMatch):
  503. reverse('test-ns1:blahblah:urlobject-view')
  504. def test_normal_name(self):
  505. "Normal lookups work as expected"
  506. self.assertEqual('/normal/', reverse('normal-view'))
  507. self.assertEqual('/normal/37/42/', reverse('normal-view', args=[37, 42]))
  508. self.assertEqual('/normal/42/37/', reverse('normal-view', kwargs={'arg1': 42, 'arg2': 37}))
  509. self.assertEqual('/+%5C$*/', reverse('special-view'))
  510. def test_simple_included_name(self):
  511. "Normal lookups work on names included from other patterns"
  512. self.assertEqual('/included/normal/', reverse('inc-normal-view'))
  513. self.assertEqual('/included/normal/37/42/', reverse('inc-normal-view', args=[37, 42]))
  514. self.assertEqual('/included/normal/42/37/', reverse('inc-normal-view', kwargs={'arg1': 42, 'arg2': 37}))
  515. self.assertEqual('/included/+%5C$*/', reverse('inc-special-view'))
  516. def test_namespace_object(self):
  517. "Dynamic URL objects can be found using a namespace"
  518. self.assertEqual('/test1/inner/', reverse('test-ns1:urlobject-view'))
  519. self.assertEqual('/test1/inner/37/42/', reverse('test-ns1:urlobject-view', args=[37, 42]))
  520. self.assertEqual('/test1/inner/42/37/', reverse('test-ns1:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}))
  521. self.assertEqual('/test1/inner/+%5C$*/', reverse('test-ns1:urlobject-special-view'))
  522. def test_app_object(self):
  523. "Dynamic URL objects can return a (pattern, app_name) 2-tuple, and include() can set the namespace"
  524. self.assertEqual('/newapp1/inner/', reverse('new-ns1:urlobject-view'))
  525. self.assertEqual('/newapp1/inner/37/42/', reverse('new-ns1:urlobject-view', args=[37, 42]))
  526. self.assertEqual('/newapp1/inner/42/37/', reverse('new-ns1:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}))
  527. self.assertEqual('/newapp1/inner/+%5C$*/', reverse('new-ns1:urlobject-special-view'))
  528. def test_app_object_default_namespace(self):
  529. "Namespace defaults to app_name when including a (pattern, app_name) 2-tuple"
  530. self.assertEqual('/new-default/inner/', reverse('newapp:urlobject-view'))
  531. self.assertEqual('/new-default/inner/37/42/', reverse('newapp:urlobject-view', args=[37, 42]))
  532. self.assertEqual(
  533. '/new-default/inner/42/37/', reverse('newapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37})
  534. )
  535. self.assertEqual('/new-default/inner/+%5C$*/', reverse('newapp:urlobject-special-view'))
  536. def test_embedded_namespace_object(self):
  537. "Namespaces can be installed anywhere in the URL pattern tree"
  538. self.assertEqual('/included/test3/inner/', reverse('test-ns3:urlobject-view'))
  539. self.assertEqual('/included/test3/inner/37/42/', reverse('test-ns3:urlobject-view', args=[37, 42]))
  540. self.assertEqual(
  541. '/included/test3/inner/42/37/', reverse('test-ns3:urlobject-view', kwargs={'arg1': 42, 'arg2': 37})
  542. )
  543. self.assertEqual('/included/test3/inner/+%5C$*/', reverse('test-ns3:urlobject-special-view'))
  544. def test_namespace_pattern(self):
  545. "Namespaces can be applied to include()'d urlpatterns"
  546. self.assertEqual('/ns-included1/normal/', reverse('inc-ns1:inc-normal-view'))
  547. self.assertEqual('/ns-included1/normal/37/42/', reverse('inc-ns1:inc-normal-view', args=[37, 42]))
  548. self.assertEqual(
  549. '/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1': 42, 'arg2': 37})
  550. )
  551. self.assertEqual('/ns-included1/+%5C$*/', reverse('inc-ns1:inc-special-view'))
  552. def test_app_name_pattern(self):
  553. "Namespaces can be applied to include()'d urlpatterns that set an app_name attribute"
  554. self.assertEqual('/app-included1/normal/', reverse('app-ns1:inc-normal-view'))
  555. self.assertEqual('/app-included1/normal/37/42/', reverse('app-ns1:inc-normal-view', args=[37, 42]))
  556. self.assertEqual(
  557. '/app-included1/normal/42/37/', reverse('app-ns1:inc-normal-view', kwargs={'arg1': 42, 'arg2': 37})
  558. )
  559. self.assertEqual('/app-included1/+%5C$*/', reverse('app-ns1:inc-special-view'))
  560. def test_namespace_pattern_with_variable_prefix(self):
  561. "When using an include with namespaces when there is a regex variable in front of it"
  562. self.assertEqual('/ns-outer/42/normal/', reverse('inc-outer:inc-normal-view', kwargs={'outer': 42}))
  563. self.assertEqual('/ns-outer/42/normal/', reverse('inc-outer:inc-normal-view', args=[42]))
  564. self.assertEqual(
  565. '/ns-outer/42/normal/37/4/',
  566. reverse('inc-outer:inc-normal-view', kwargs={'outer': 42, 'arg1': 37, 'arg2': 4})
  567. )
  568. self.assertEqual('/ns-outer/42/normal/37/4/', reverse('inc-outer:inc-normal-view', args=[42, 37, 4]))
  569. self.assertEqual('/ns-outer/42/+%5C$*/', reverse('inc-outer:inc-special-view', kwargs={'outer': 42}))
  570. self.assertEqual('/ns-outer/42/+%5C$*/', reverse('inc-outer:inc-special-view', args=[42]))
  571. def test_multiple_namespace_pattern(self):
  572. "Namespaces can be embedded"
  573. self.assertEqual('/ns-included1/test3/inner/', reverse('inc-ns1:test-ns3:urlobject-view'))
  574. self.assertEqual('/ns-included1/test3/inner/37/42/', reverse('inc-ns1:test-ns3:urlobject-view', args=[37, 42]))
  575. self.assertEqual(
  576. '/ns-included1/test3/inner/42/37/',
  577. reverse('inc-ns1:test-ns3:urlobject-view', kwargs={'arg1': 42, 'arg2': 37})
  578. )
  579. self.assertEqual('/ns-included1/test3/inner/+%5C$*/', reverse('inc-ns1:test-ns3:urlobject-special-view'))
  580. def test_nested_namespace_pattern(self):
  581. "Namespaces can be nested"
  582. self.assertEqual(
  583. '/ns-included1/ns-included4/ns-included1/test3/inner/',
  584. reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view')
  585. )
  586. self.assertEqual(
  587. '/ns-included1/ns-included4/ns-included1/test3/inner/37/42/',
  588. reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view', args=[37, 42])
  589. )
  590. self.assertEqual(
  591. '/ns-included1/ns-included4/ns-included1/test3/inner/42/37/',
  592. reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view', kwargs={'arg1': 42, 'arg2': 37})
  593. )
  594. self.assertEqual(
  595. '/ns-included1/ns-included4/ns-included1/test3/inner/+%5C$*/',
  596. reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-special-view')
  597. )
  598. def test_app_lookup_object(self):
  599. "A default application namespace can be used for lookup"
  600. self.assertEqual('/default/inner/', reverse('testapp:urlobject-view'))
  601. self.assertEqual('/default/inner/37/42/', reverse('testapp:urlobject-view', args=[37, 42]))
  602. self.assertEqual('/default/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}))
  603. self.assertEqual('/default/inner/+%5C$*/', reverse('testapp:urlobject-special-view'))
  604. def test_app_lookup_object_with_default(self):
  605. "A default application namespace is sensitive to the 'current' app can be used for lookup"
  606. self.assertEqual('/included/test3/inner/', reverse('testapp:urlobject-view', current_app='test-ns3'))
  607. self.assertEqual(
  608. '/included/test3/inner/37/42/',
  609. reverse('testapp:urlobject-view', args=[37, 42], current_app='test-ns3')
  610. )
  611. self.assertEqual(
  612. '/included/test3/inner/42/37/',
  613. reverse('testapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}, current_app='test-ns3')
  614. )
  615. self.assertEqual(
  616. '/included/test3/inner/+%5C$*/', reverse('testapp:urlobject-special-view', current_app='test-ns3')
  617. )
  618. def test_app_lookup_object_without_default(self):
  619. "An application namespace without a default is sensitive to the 'current' app can be used for lookup"
  620. self.assertEqual('/other2/inner/', reverse('nodefault:urlobject-view'))
  621. self.assertEqual('/other2/inner/37/42/', reverse('nodefault:urlobject-view', args=[37, 42]))
  622. self.assertEqual('/other2/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}))
  623. self.assertEqual('/other2/inner/+%5C$*/', reverse('nodefault:urlobject-special-view'))
  624. self.assertEqual('/other1/inner/', reverse('nodefault:urlobject-view', current_app='other-ns1'))
  625. self.assertEqual(
  626. '/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37, 42], current_app='other-ns1')
  627. )
  628. self.assertEqual(
  629. '/other1/inner/42/37/',
  630. reverse('nodefault:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}, current_app='other-ns1')
  631. )
  632. self.assertEqual('/other1/inner/+%5C$*/', reverse('nodefault:urlobject-special-view', current_app='other-ns1'))
  633. def test_special_chars_namespace(self):
  634. self.assertEqual('/+%5C$*/included/normal/', reverse('special:inc-normal-view'))
  635. self.assertEqual('/+%5C$*/included/normal/37/42/', reverse('special:inc-normal-view', args=[37, 42]))
  636. self.assertEqual(
  637. '/+%5C$*/included/normal/42/37/',
  638. reverse('special:inc-normal-view', kwargs={'arg1': 42, 'arg2': 37})
  639. )
  640. self.assertEqual('/+%5C$*/included/+%5C$*/', reverse('special:inc-special-view'))
  641. def test_namespaces_with_variables(self):
  642. "Namespace prefixes can capture variables: see #15900"
  643. self.assertEqual('/inc70/', reverse('inc-ns5:inner-nothing', kwargs={'outer': '70'}))
  644. self.assertEqual(
  645. '/inc78/extra/foobar/', reverse('inc-ns5:inner-extra', kwargs={'outer': '78', 'extra': 'foobar'})
  646. )
  647. self.assertEqual('/inc70/', reverse('inc-ns5:inner-nothing', args=['70']))
  648. self.assertEqual('/inc78/extra/foobar/', reverse('inc-ns5:inner-extra', args=['78', 'foobar']))
  649. def test_nested_app_lookup(self):
  650. "A nested current_app should be split in individual namespaces (#24904)"
  651. self.assertEqual('/ns-included1/test4/inner/', reverse('inc-ns1:testapp:urlobject-view'))
  652. self.assertEqual('/ns-included1/test4/inner/37/42/', reverse('inc-ns1:testapp:urlobject-view', args=[37, 42]))
  653. self.assertEqual(
  654. '/ns-included1/test4/inner/42/37/',
  655. reverse('inc-ns1:testapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37})
  656. )
  657. self.assertEqual('/ns-included1/test4/inner/+%5C$*/', reverse('inc-ns1:testapp:urlobject-special-view'))
  658. self.assertEqual(
  659. '/ns-included1/test3/inner/',
  660. reverse('inc-ns1:testapp:urlobject-view', current_app='inc-ns1:test-ns3')
  661. )
  662. self.assertEqual(
  663. '/ns-included1/test3/inner/37/42/',
  664. reverse('inc-ns1:testapp:urlobject-view', args=[37, 42], current_app='inc-ns1:test-ns3')
  665. )
  666. self.assertEqual(
  667. '/ns-included1/test3/inner/42/37/',
  668. reverse('inc-ns1:testapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37}, current_app='inc-ns1:test-ns3')
  669. )
  670. self.assertEqual(
  671. '/ns-included1/test3/inner/+%5C$*/',
  672. reverse('inc-ns1:testapp:urlobject-special-view', current_app='inc-ns1:test-ns3')
  673. )
  674. def test_current_app_no_partial_match(self):
  675. "current_app should either match the whole path or shouldn't be used"
  676. self.assertEqual(
  677. '/ns-included1/test4/inner/',
  678. reverse('inc-ns1:testapp:urlobject-view', current_app='non-existent:test-ns3')
  679. )
  680. self.assertEqual(
  681. '/ns-included1/test4/inner/37/42/',
  682. reverse('inc-ns1:testapp:urlobject-view', args=[37, 42], current_app='non-existent:test-ns3')
  683. )
  684. self.assertEqual(
  685. '/ns-included1/test4/inner/42/37/',
  686. reverse('inc-ns1:testapp:urlobject-view', kwargs={'arg1': 42, 'arg2': 37},
  687. current_app='non-existent:test-ns3')
  688. )
  689. self.assertEqual(
  690. '/ns-included1/test4/inner/+%5C$*/',
  691. reverse('inc-ns1:testapp:urlobject-special-view', current_app='non-existent:test-ns3')
  692. )
  693. @override_settings(ROOT_URLCONF=urlconf_outer.__name__)
  694. class RequestURLconfTests(SimpleTestCase):
  695. def test_urlconf(self):
  696. response = self.client.get('/test/me/')
  697. self.assertEqual(response.status_code, 200)
  698. self.assertEqual(response.content, b'outer:/test/me/,inner:/inner_urlconf/second_test/')
  699. response = self.client.get('/inner_urlconf/second_test/')
  700. self.assertEqual(response.status_code, 200)
  701. response = self.client.get('/second_test/')
  702. self.assertEqual(response.status_code, 404)
  703. @override_settings(
  704. MIDDLEWARE=[
  705. '%s.ChangeURLconfMiddleware' % middleware.__name__,
  706. ]
  707. )
  708. def test_urlconf_overridden(self):
  709. response = self.client.get('/test/me/')
  710. self.assertEqual(response.status_code, 404)
  711. response = self.client.get('/inner_urlconf/second_test/')
  712. self.assertEqual(response.status_code, 404)
  713. response = self.client.get('/second_test/')
  714. self.assertEqual(response.status_code, 200)
  715. self.assertEqual(response.content, b'outer:,inner:/second_test/')
  716. @override_settings(
  717. MIDDLEWARE=[
  718. '%s.NullChangeURLconfMiddleware' % middleware.__name__,
  719. ]
  720. )
  721. def test_urlconf_overridden_with_null(self):
  722. """
  723. Overriding request.urlconf with None will fall back to the default
  724. URLconf.
  725. """
  726. response = self.client.get('/test/me/')
  727. self.assertEqual(response.status_code, 200)
  728. self.assertEqual(response.content, b'outer:/test/me/,inner:/inner_urlconf/second_test/')
  729. response = self.client.get('/inner_urlconf/second_test/')
  730. self.assertEqual(response.status_code, 200)
  731. response = self.client.get('/second_test/')
  732. self.assertEqual(response.status_code, 404)
  733. @override_settings(
  734. MIDDLEWARE=[
  735. '%s.ChangeURLconfMiddleware' % middleware.__name__,
  736. '%s.ReverseInnerInResponseMiddleware' % middleware.__name__,
  737. ]
  738. )
  739. def test_reverse_inner_in_response_middleware(self):
  740. """
  741. Test reversing an URL from the *overridden* URLconf from inside
  742. a response middleware.
  743. """
  744. response = self.client.get('/second_test/')
  745. self.assertEqual(response.status_code, 200)
  746. self.assertEqual(response.content, b'/second_test/')
  747. @override_settings(
  748. MIDDLEWARE=[
  749. '%s.ChangeURLconfMiddleware' % middleware.__name__,
  750. '%s.ReverseOuterInResponseMiddleware' % middleware.__name__,
  751. ]
  752. )
  753. def test_reverse_outer_in_response_middleware(self):
  754. """
  755. Test reversing an URL from the *default* URLconf from inside
  756. a response middleware.
  757. """
  758. message = "Reverse for 'outer' with arguments '()' and keyword arguments '{}' not found."
  759. with self.assertRaisesMessage(NoReverseMatch, message):
  760. self.client.get('/second_test/')
  761. @override_settings(
  762. MIDDLEWARE=[
  763. '%s.ChangeURLconfMiddleware' % middleware.__name__,
  764. '%s.ReverseInnerInStreaming' % middleware.__name__,
  765. ]
  766. )
  767. def test_reverse_inner_in_streaming(self):
  768. """
  769. Test reversing an URL from the *overridden* URLconf from inside
  770. a streaming response.
  771. """
  772. response = self.client.get('/second_test/')
  773. self.assertEqual(response.status_code, 200)
  774. self.assertEqual(b''.join(response), b'/second_test/')
  775. @override_settings(
  776. MIDDLEWARE=[
  777. '%s.ChangeURLconfMiddleware' % middleware.__name__,
  778. '%s.ReverseOuterInStreaming' % middleware.__name__,
  779. ]
  780. )
  781. def test_reverse_outer_in_streaming(self):
  782. """
  783. Test reversing an URL from the *default* URLconf from inside
  784. a streaming response.
  785. """
  786. message = "Reverse for 'outer' with arguments '()' and keyword arguments '{}' not found."
  787. with self.assertRaisesMessage(NoReverseMatch, message):
  788. self.client.get('/second_test/')
  789. b''.join(self.client.get('/second_test/'))
  790. class ErrorHandlerResolutionTests(SimpleTestCase):
  791. """Tests for handler400, handler404 and handler500"""
  792. def setUp(self):
  793. urlconf = 'urlpatterns_reverse.urls_error_handlers'
  794. urlconf_callables = 'urlpatterns_reverse.urls_error_handlers_callables'
  795. self.resolver = RegexURLResolver(r'^$', urlconf)
  796. self.callable_resolver = RegexURLResolver(r'^$', urlconf_callables)
  797. def test_named_handlers(self):
  798. handler = (empty_view, {})
  799. self.assertEqual(self.resolver.resolve_error_handler(400), handler)
  800. self.assertEqual(self.resolver.resolve_error_handler(404), handler)
  801. self.assertEqual(self.resolver.resolve_error_handler(500), handler)
  802. def test_callable_handlers(self):
  803. handler = (empty_view, {})
  804. self.assertEqual(self.callable_resolver.resolve_error_handler(400), handler)
  805. self.assertEqual(self.callable_resolver.resolve_error_handler(404), handler)
  806. self.assertEqual(self.callable_resolver.resolve_error_handler(500), handler)
  807. @override_settings(ROOT_URLCONF='urlpatterns_reverse.urls_without_full_import')
  808. class DefaultErrorHandlerTests(SimpleTestCase):
  809. def test_default_handler(self):
  810. "If the urls.py doesn't specify handlers, the defaults are used"
  811. try:
  812. response = self.client.get('/test/')
  813. self.assertEqual(response.status_code, 404)
  814. except AttributeError:
  815. self.fail("Shouldn't get an AttributeError due to undefined 404 handler")
  816. try:
  817. with self.assertRaises(ValueError):
  818. self.client.get('/bad_view/')
  819. except AttributeError:
  820. self.fail("Shouldn't get an AttributeError due to undefined 500 handler")
  821. @override_settings(ROOT_URLCONF=None)
  822. class NoRootUrlConfTests(SimpleTestCase):
  823. """Tests for handler404 and handler500 if ROOT_URLCONF is None"""
  824. def test_no_handler_exception(self):
  825. with self.assertRaises(ImproperlyConfigured):
  826. self.client.get('/test/me/')
  827. @override_settings(ROOT_URLCONF='urlpatterns_reverse.namespace_urls')
  828. class ResolverMatchTests(SimpleTestCase):
  829. @ignore_warnings(category=RemovedInDjango20Warning)
  830. def test_urlpattern_resolve(self):
  831. for path, url_name, app_name, namespace, view_name, func, args, kwargs in resolve_test_data:
  832. # Test legacy support for extracting "function, args, kwargs"
  833. match_func, match_args, match_kwargs = resolve(path)
  834. self.assertEqual(match_func, func)
  835. self.assertEqual(match_args, args)
  836. self.assertEqual(match_kwargs, kwargs)
  837. # Test ResolverMatch capabilities.
  838. match = resolve(path)
  839. self.assertEqual(match.__class__, ResolverMatch)
  840. self.assertEqual(match.url_name, url_name)
  841. self.assertEqual(match.app_name, app_name)
  842. self.assertEqual(match.namespace, namespace)
  843. self.assertEqual(match.view_name, view_name)
  844. self.assertEqual(match.func, func)
  845. self.assertEqual(match.args, args)
  846. self.assertEqual(match.kwargs, kwargs)
  847. # ... and for legacy purposes:
  848. self.assertEqual(match[0], func)
  849. self.assertEqual(match[1], args)
  850. self.assertEqual(match[2], kwargs)
  851. @ignore_warnings(category=RemovedInDjango20Warning)
  852. def test_resolver_match_on_request(self):
  853. response = self.client.get('/resolver_match/')
  854. resolver_match = response.resolver_match
  855. self.assertEqual(resolver_match.url_name, 'test-resolver-match')
  856. def test_resolver_match_on_request_before_resolution(self):
  857. request = HttpRequest()
  858. self.assertIsNone(request.resolver_match)
  859. @override_settings(ROOT_URLCONF='urlpatterns_reverse.erroneous_urls')
  860. class ErroneousViewTests(SimpleTestCase):
  861. def test_noncallable_view(self):
  862. # View is not a callable (explicit import; arbitrary Python object)
  863. with self.assertRaisesMessage(TypeError, 'view must be a callable'):
  864. url(r'uncallable-object/$', views.uncallable)
  865. def test_invalid_regex(self):
  866. # Regex contains an error (refs #6170)
  867. msg = '(regex_error/$" is not a valid regular expression'
  868. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  869. reverse(views.empty_view)
  870. class ViewLoadingTests(SimpleTestCase):
  871. def test_view_loading(self):
  872. self.assertEqual(get_callable('urlpatterns_reverse.views.empty_view'), empty_view)
  873. # passing a callable should return the callable
  874. self.assertEqual(get_callable(empty_view), empty_view)
  875. def test_exceptions(self):
  876. # A missing view (identified by an AttributeError) should raise
  877. # ViewDoesNotExist, ...
  878. with six.assertRaisesRegex(self, ViewDoesNotExist, ".*View does not exist in.*"):
  879. get_callable('urlpatterns_reverse.views.i_should_not_exist')
  880. # ... but if the AttributeError is caused by something else don't
  881. # swallow it.
  882. with self.assertRaises(AttributeError):
  883. get_callable('urlpatterns_reverse.views_broken.i_am_broken')
  884. class IncludeTests(SimpleTestCase):
  885. url_patterns = [
  886. url(r'^inner/$', views.empty_view, name='urlobject-view'),
  887. url(r'^inner/(?P<arg1>[0-9]+)/(?P<arg2>[0-9]+)/$', views.empty_view, name='urlobject-view'),
  888. url(r'^inner/\+\\\$\*/$', views.empty_view, name='urlobject-special-view'),
  889. ]
  890. app_urls = URLObject('inc-app')
  891. def test_include_app_name_but_no_namespace(self):
  892. msg = "Must specify a namespace if specifying app_name."
  893. with self.assertRaisesMessage(ValueError, msg):
  894. include(self.url_patterns, app_name='bar')
  895. def test_include_urls(self):
  896. self.assertEqual(include(self.url_patterns), (self.url_patterns, None, None))
  897. @ignore_warnings(category=RemovedInDjango20Warning)
  898. def test_include_namespace(self):
  899. # no app_name -> deprecated
  900. self.assertEqual(include(self.url_patterns, 'namespace'), (self.url_patterns, None, 'namespace'))
  901. @ignore_warnings(category=RemovedInDjango20Warning)
  902. def test_include_namespace_app_name(self):
  903. # app_name argument to include -> deprecated
  904. self.assertEqual(
  905. include(self.url_patterns, 'namespace', 'app_name'),
  906. (self.url_patterns, 'app_name', 'namespace')
  907. )
  908. @ignore_warnings(category=RemovedInDjango20Warning)
  909. def test_include_3_tuple(self):
  910. # 3-tuple -> deprecated
  911. self.assertEqual(
  912. include((self.url_patterns, 'app_name', 'namespace')),
  913. (self.url_patterns, 'app_name', 'namespace')
  914. )
  915. def test_include_2_tuple(self):
  916. self.assertEqual(
  917. include((self.url_patterns, 'app_name')),
  918. (self.url_patterns, 'app_name', 'app_name')
  919. )
  920. def test_include_2_tuple_namespace(self):
  921. self.assertEqual(
  922. include((self.url_patterns, 'app_name'), namespace='namespace'),
  923. (self.url_patterns, 'app_name', 'namespace')
  924. )
  925. def test_include_app_name(self):
  926. self.assertEqual(
  927. include(self.app_urls),
  928. (self.app_urls, 'inc-app', 'inc-app')
  929. )
  930. def test_include_app_name_namespace(self):
  931. self.assertEqual(
  932. include(self.app_urls, 'namespace'),
  933. (self.app_urls, 'inc-app', 'namespace')
  934. )
  935. @override_settings(ROOT_URLCONF='urlpatterns_reverse.urls')
  936. class LookaheadTests(SimpleTestCase):
  937. def test_valid_resolve(self):
  938. test_urls = [
  939. '/lookahead-/a-city/',
  940. '/lookbehind-/a-city/',
  941. '/lookahead+/a-city/',
  942. '/lookbehind+/a-city/',
  943. ]
  944. for test_url in test_urls:
  945. match = resolve(test_url)
  946. self.assertEqual(match.kwargs, {'city': 'a-city'})
  947. def test_invalid_resolve(self):
  948. test_urls = [
  949. '/lookahead-/not-a-city/',
  950. '/lookbehind-/not-a-city/',
  951. '/lookahead+/other-city/',
  952. '/lookbehind+/other-city/',
  953. ]
  954. for test_url in test_urls:
  955. with self.assertRaises(Resolver404):
  956. resolve(test_url)
  957. def test_valid_reverse(self):
  958. url = reverse('lookahead-positive', kwargs={'city': 'a-city'})
  959. self.assertEqual(url, '/lookahead+/a-city/')
  960. url = reverse('lookahead-negative', kwargs={'city': 'a-city'})
  961. self.assertEqual(url, '/lookahead-/a-city/')
  962. url = reverse('lookbehind-positive', kwargs={'city': 'a-city'})
  963. self.assertEqual(url, '/lookbehind+/a-city/')
  964. url = reverse('lookbehind-negative', kwargs={'city': 'a-city'})
  965. self.assertEqual(url, '/lookbehind-/a-city/')
  966. def test_invalid_reverse(self):
  967. with self.assertRaises(NoReverseMatch):
  968. reverse('lookahead-positive', kwargs={'city': 'other-city'})
  969. with self.assertRaises(NoReverseMatch):
  970. reverse('lookahead-negative', kwargs={'city': 'not-a-city'})
  971. with self.assertRaises(NoReverseMatch):
  972. reverse('lookbehind-positive', kwargs={'city': 'other-city'})
  973. with self.assertRaises(NoReverseMatch):
  974. reverse('lookbehind-negative', kwargs={'city': 'not-a-city'})