tests.py 48 KB

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