tests.py 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875
  1. """
  2. Unit tests for reverse URL lookups.
  3. """
  4. import pickle
  5. import sys
  6. import threading
  7. from admin_scripts.tests import AdminScriptTestCase
  8. from django.conf import settings
  9. from django.contrib.auth.models import User
  10. from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
  11. from django.http import (
  12. HttpRequest,
  13. HttpResponsePermanentRedirect,
  14. HttpResponseRedirect,
  15. QueryDict,
  16. )
  17. from django.shortcuts import redirect
  18. from django.test import RequestFactory, SimpleTestCase, TestCase, override_settings
  19. from django.test.utils import override_script_prefix
  20. from django.urls import (
  21. NoReverseMatch,
  22. Resolver404,
  23. ResolverMatch,
  24. URLPattern,
  25. URLResolver,
  26. get_callable,
  27. get_resolver,
  28. get_urlconf,
  29. include,
  30. path,
  31. re_path,
  32. resolve,
  33. reverse,
  34. reverse_lazy,
  35. )
  36. from django.urls.resolvers import RegexPattern
  37. from . import middleware, urlconf_outer, views
  38. from .utils import URLObject
  39. from .views import empty_view
  40. resolve_test_data = (
  41. # These entries are in the format:
  42. # (path, url_name, app_name, namespace, view_name, func, args, kwargs)
  43. # Simple case
  44. (
  45. "/normal/42/37/",
  46. "normal-view",
  47. "",
  48. "",
  49. "normal-view",
  50. views.empty_view,
  51. (),
  52. {"arg1": "42", "arg2": "37"},
  53. ),
  54. (
  55. "/view_class/42/37/",
  56. "view-class",
  57. "",
  58. "",
  59. "view-class",
  60. views.view_class_instance,
  61. (),
  62. {"arg1": "42", "arg2": "37"},
  63. ),
  64. (
  65. "/included/normal/42/37/",
  66. "inc-normal-view",
  67. "included_namespace_urls",
  68. "included_namespace_urls",
  69. "included_namespace_urls:inc-normal-view",
  70. views.empty_view,
  71. (),
  72. {"arg1": "42", "arg2": "37"},
  73. ),
  74. (
  75. "/included/view_class/42/37/",
  76. "inc-view-class",
  77. "included_namespace_urls",
  78. "included_namespace_urls",
  79. "included_namespace_urls:inc-view-class",
  80. views.view_class_instance,
  81. (),
  82. {"arg1": "42", "arg2": "37"},
  83. ),
  84. # Unnamed args are dropped if you have *any* kwargs in a pattern
  85. (
  86. "/mixed_args/42/37/",
  87. "mixed-args",
  88. "",
  89. "",
  90. "mixed-args",
  91. views.empty_view,
  92. (),
  93. {"extra": True, "arg2": "37"},
  94. ),
  95. (
  96. "/included/mixed_args/42/37/",
  97. "inc-mixed-args",
  98. "included_namespace_urls",
  99. "included_namespace_urls",
  100. "included_namespace_urls:inc-mixed-args",
  101. views.empty_view,
  102. (),
  103. {"arg2": "37"},
  104. ),
  105. (
  106. "/included/12/mixed_args/42/37/",
  107. "inc-mixed-args",
  108. "included_namespace_urls",
  109. "included_namespace_urls",
  110. "included_namespace_urls:inc-mixed-args",
  111. views.empty_view,
  112. (),
  113. {"arg2": "37"},
  114. ),
  115. # Unnamed views should have None as the url_name. Regression data for #21157.
  116. (
  117. "/unnamed/normal/42/37/",
  118. None,
  119. "",
  120. "",
  121. "urlpatterns_reverse.views.empty_view",
  122. views.empty_view,
  123. (),
  124. {"arg1": "42", "arg2": "37"},
  125. ),
  126. (
  127. "/unnamed/view_class/42/37/",
  128. None,
  129. "",
  130. "",
  131. "urlpatterns_reverse.views.ViewClass",
  132. views.view_class_instance,
  133. (),
  134. {"arg1": "42", "arg2": "37"},
  135. ),
  136. # If you have no kwargs, you get an args list.
  137. (
  138. "/no_kwargs/42/37/",
  139. "no-kwargs",
  140. "",
  141. "",
  142. "no-kwargs",
  143. views.empty_view,
  144. ("42", "37"),
  145. {},
  146. ),
  147. (
  148. "/included/no_kwargs/42/37/",
  149. "inc-no-kwargs",
  150. "included_namespace_urls",
  151. "included_namespace_urls",
  152. "included_namespace_urls:inc-no-kwargs",
  153. views.empty_view,
  154. ("42", "37"),
  155. {},
  156. ),
  157. (
  158. "/included/12/no_kwargs/42/37/",
  159. "inc-no-kwargs",
  160. "included_namespace_urls",
  161. "included_namespace_urls",
  162. "included_namespace_urls:inc-no-kwargs",
  163. views.empty_view,
  164. ("12", "42", "37"),
  165. {},
  166. ),
  167. # Namespaces
  168. (
  169. "/test1/inner/42/37/",
  170. "urlobject-view",
  171. "testapp",
  172. "test-ns1",
  173. "test-ns1:urlobject-view",
  174. views.empty_view,
  175. (),
  176. {"arg1": "42", "arg2": "37"},
  177. ),
  178. (
  179. "/included/test3/inner/42/37/",
  180. "urlobject-view",
  181. "included_namespace_urls:testapp",
  182. "included_namespace_urls:test-ns3",
  183. "included_namespace_urls:test-ns3:urlobject-view",
  184. views.empty_view,
  185. (),
  186. {"arg1": "42", "arg2": "37"},
  187. ),
  188. (
  189. "/ns-included1/normal/42/37/",
  190. "inc-normal-view",
  191. "included_namespace_urls",
  192. "inc-ns1",
  193. "inc-ns1:inc-normal-view",
  194. views.empty_view,
  195. (),
  196. {"arg1": "42", "arg2": "37"},
  197. ),
  198. (
  199. "/included/test3/inner/42/37/",
  200. "urlobject-view",
  201. "included_namespace_urls:testapp",
  202. "included_namespace_urls:test-ns3",
  203. "included_namespace_urls:test-ns3:urlobject-view",
  204. views.empty_view,
  205. (),
  206. {"arg1": "42", "arg2": "37"},
  207. ),
  208. (
  209. "/default/inner/42/37/",
  210. "urlobject-view",
  211. "testapp",
  212. "testapp",
  213. "testapp:urlobject-view",
  214. views.empty_view,
  215. (),
  216. {"arg1": "42", "arg2": "37"},
  217. ),
  218. (
  219. "/other2/inner/42/37/",
  220. "urlobject-view",
  221. "nodefault",
  222. "other-ns2",
  223. "other-ns2:urlobject-view",
  224. views.empty_view,
  225. (),
  226. {"arg1": "42", "arg2": "37"},
  227. ),
  228. (
  229. "/other1/inner/42/37/",
  230. "urlobject-view",
  231. "nodefault",
  232. "other-ns1",
  233. "other-ns1:urlobject-view",
  234. views.empty_view,
  235. (),
  236. {"arg1": "42", "arg2": "37"},
  237. ),
  238. # Nested namespaces
  239. (
  240. "/ns-included1/test3/inner/42/37/",
  241. "urlobject-view",
  242. "included_namespace_urls:testapp",
  243. "inc-ns1:test-ns3",
  244. "inc-ns1:test-ns3:urlobject-view",
  245. views.empty_view,
  246. (),
  247. {"arg1": "42", "arg2": "37"},
  248. ),
  249. (
  250. "/ns-included1/ns-included4/ns-included2/test3/inner/42/37/",
  251. "urlobject-view",
  252. "included_namespace_urls:namespace_urls:included_namespace_urls:testapp",
  253. "inc-ns1:inc-ns4:inc-ns2:test-ns3",
  254. "inc-ns1:inc-ns4:inc-ns2:test-ns3:urlobject-view",
  255. views.empty_view,
  256. (),
  257. {"arg1": "42", "arg2": "37"},
  258. ),
  259. (
  260. "/app-included/test3/inner/42/37/",
  261. "urlobject-view",
  262. "included_namespace_urls:testapp",
  263. "inc-app:test-ns3",
  264. "inc-app:test-ns3:urlobject-view",
  265. views.empty_view,
  266. (),
  267. {"arg1": "42", "arg2": "37"},
  268. ),
  269. (
  270. "/app-included/ns-included4/ns-included2/test3/inner/42/37/",
  271. "urlobject-view",
  272. "included_namespace_urls:namespace_urls:included_namespace_urls:testapp",
  273. "inc-app:inc-ns4:inc-ns2:test-ns3",
  274. "inc-app:inc-ns4:inc-ns2:test-ns3:urlobject-view",
  275. views.empty_view,
  276. (),
  277. {"arg1": "42", "arg2": "37"},
  278. ),
  279. # Namespaces capturing variables
  280. (
  281. "/inc70/",
  282. "inner-nothing",
  283. "included_urls",
  284. "inc-ns5",
  285. "inc-ns5:inner-nothing",
  286. views.empty_view,
  287. (),
  288. {"outer": "70"},
  289. ),
  290. (
  291. "/inc78/extra/foobar/",
  292. "inner-extra",
  293. "included_urls",
  294. "inc-ns5",
  295. "inc-ns5:inner-extra",
  296. views.empty_view,
  297. (),
  298. {"outer": "78", "extra": "foobar"},
  299. ),
  300. )
  301. test_data = (
  302. ("places", "/places/3/", [3], {}),
  303. ("places", "/places/3/", ["3"], {}),
  304. ("places", NoReverseMatch, ["a"], {}),
  305. ("places", NoReverseMatch, [], {}),
  306. ("places?", "/place/", [], {}),
  307. ("places+", "/places/", [], {}),
  308. ("places*", "/place/", [], {}),
  309. ("places2?", "/", [], {}),
  310. ("places2+", "/places/", [], {}),
  311. ("places2*", "/", [], {}),
  312. ("places3", "/places/4/", [4], {}),
  313. ("places3", "/places/harlem/", ["harlem"], {}),
  314. ("places3", NoReverseMatch, ["harlem64"], {}),
  315. ("places4", "/places/3/", [], {"id": 3}),
  316. ("people", NoReverseMatch, [], {}),
  317. ("people", "/people/adrian/", ["adrian"], {}),
  318. ("people", "/people/adrian/", [], {"name": "adrian"}),
  319. ("people", NoReverseMatch, ["name with spaces"], {}),
  320. ("people", NoReverseMatch, [], {"name": "name with spaces"}),
  321. ("people2", "/people/name/", [], {}),
  322. ("people2a", "/people/name/fred/", ["fred"], {}),
  323. ("people_backref", "/people/nate-nate/", ["nate"], {}),
  324. ("people_backref", "/people/nate-nate/", [], {"name": "nate"}),
  325. ("optional", "/optional/fred/", [], {"name": "fred"}),
  326. ("optional", "/optional/fred/", ["fred"], {}),
  327. ("named_optional", "/optional/1/", [1], {}),
  328. ("named_optional", "/optional/1/", [], {"arg1": 1}),
  329. ("named_optional", "/optional/1/2/", [1, 2], {}),
  330. ("named_optional", "/optional/1/2/", [], {"arg1": 1, "arg2": 2}),
  331. ("named_optional_terminated", "/optional/1/", [1], {}),
  332. ("named_optional_terminated", "/optional/1/", [], {"arg1": 1}),
  333. ("named_optional_terminated", "/optional/1/2/", [1, 2], {}),
  334. ("named_optional_terminated", "/optional/1/2/", [], {"arg1": 1, "arg2": 2}),
  335. ("hardcoded", "/hardcoded/", [], {}),
  336. ("hardcoded2", "/hardcoded/doc.pdf", [], {}),
  337. ("people3", "/people/il/adrian/", [], {"state": "il", "name": "adrian"}),
  338. ("people3", NoReverseMatch, [], {"state": "il"}),
  339. ("people3", NoReverseMatch, [], {"name": "adrian"}),
  340. ("people4", NoReverseMatch, [], {"state": "il", "name": "adrian"}),
  341. ("people6", "/people/il/test/adrian/", ["il/test", "adrian"], {}),
  342. ("people6", "/people//adrian/", ["adrian"], {}),
  343. ("range", "/character_set/a/", [], {}),
  344. ("range2", "/character_set/x/", [], {}),
  345. ("price", "/price/$10/", ["10"], {}),
  346. ("price2", "/price/$10/", ["10"], {}),
  347. ("price3", "/price/$10/", ["10"], {}),
  348. (
  349. "product",
  350. "/product/chocolate+($2.00)/",
  351. [],
  352. {"price": "2.00", "product": "chocolate"},
  353. ),
  354. ("headlines", "/headlines/2007.5.21/", [], {"year": 2007, "month": 5, "day": 21}),
  355. (
  356. "windows",
  357. r"/windows_path/C:%5CDocuments%20and%20Settings%5Cspam/",
  358. [],
  359. {"drive_name": "C", "path": r"Documents and Settings\spam"},
  360. ),
  361. ("special", r"/special_chars/~@+%5C$*%7C/", [r"~@+\$*|"], {}),
  362. ("special", r"/special_chars/some%20resource/", [r"some resource"], {}),
  363. ("special", r"/special_chars/10%25%20complete/", [r"10% complete"], {}),
  364. ("special", r"/special_chars/some%20resource/", [], {"chars": r"some resource"}),
  365. ("special", r"/special_chars/10%25%20complete/", [], {"chars": r"10% complete"}),
  366. ("special", NoReverseMatch, [""], {}),
  367. ("mixed", "/john/0/", [], {"name": "john"}),
  368. ("repeats", "/repeats/a/", [], {}),
  369. ("repeats2", "/repeats/aa/", [], {}),
  370. ("repeats3", "/repeats/aa/", [], {}),
  371. ("test", "/test/1", [], {}),
  372. ("inner-nothing", "/outer/42/", [], {"outer": "42"}),
  373. ("inner-nothing", "/outer/42/", ["42"], {}),
  374. ("inner-nothing", NoReverseMatch, ["foo"], {}),
  375. ("inner-extra", "/outer/42/extra/inner/", [], {"extra": "inner", "outer": "42"}),
  376. ("inner-extra", "/outer/42/extra/inner/", ["42", "inner"], {}),
  377. ("inner-extra", NoReverseMatch, ["fred", "inner"], {}),
  378. ("inner-no-kwargs", "/outer-no-kwargs/42/inner-no-kwargs/1/", ["42", "1"], {}),
  379. ("disjunction", NoReverseMatch, ["foo"], {}),
  380. ("inner-disjunction", NoReverseMatch, ["10", "11"], {}),
  381. ("extra-places", "/e-places/10/", ["10"], {}),
  382. ("extra-people", "/e-people/fred/", ["fred"], {}),
  383. ("extra-people", "/e-people/fred/", [], {"name": "fred"}),
  384. ("part", "/part/one/", [], {"value": "one"}),
  385. ("part", "/prefix/xx/part/one/", [], {"value": "one", "prefix": "xx"}),
  386. ("part2", "/part2/one/", [], {"value": "one"}),
  387. ("part2", "/part2/", [], {}),
  388. ("part2", "/prefix/xx/part2/one/", [], {"value": "one", "prefix": "xx"}),
  389. ("part2", "/prefix/xx/part2/", [], {"prefix": "xx"}),
  390. # Tests for nested groups. Nested capturing groups will only work if you
  391. # *only* supply the correct outer group.
  392. ("nested-noncapture", "/nested/noncapture/opt", [], {"p": "opt"}),
  393. ("nested-capture", "/nested/capture/opt/", ["opt/"], {}),
  394. ("nested-capture", NoReverseMatch, [], {"p": "opt"}),
  395. ("nested-mixedcapture", "/nested/capture/mixed/opt", ["opt"], {}),
  396. ("nested-mixedcapture", NoReverseMatch, [], {"p": "opt"}),
  397. ("nested-namedcapture", "/nested/capture/named/opt/", [], {"outer": "opt/"}),
  398. ("nested-namedcapture", NoReverseMatch, [], {"outer": "opt/", "inner": "opt"}),
  399. ("nested-namedcapture", NoReverseMatch, [], {"inner": "opt"}),
  400. ("non_path_include", "/includes/non_path_include/", [], {}),
  401. # Tests for #13154
  402. ("defaults", "/defaults_view1/3/", [], {"arg1": 3, "arg2": 1}),
  403. ("defaults", "/defaults_view2/3/", [], {"arg1": 3, "arg2": 2}),
  404. ("defaults", NoReverseMatch, [], {"arg1": 3, "arg2": 3}),
  405. ("defaults", NoReverseMatch, [], {"arg2": 1}),
  406. # Security tests
  407. ("security", "/%2Fexample.com/security/", ["/example.com"], {}),
  408. )
  409. @override_settings(ROOT_URLCONF="urlpatterns_reverse.no_urls")
  410. class NoURLPatternsTests(SimpleTestCase):
  411. def test_no_urls_exception(self):
  412. """
  413. URLResolver should raise an exception when no urlpatterns exist.
  414. """
  415. resolver = URLResolver(RegexPattern(r"^$"), settings.ROOT_URLCONF)
  416. with self.assertRaisesMessage(
  417. ImproperlyConfigured,
  418. "The included URLconf 'urlpatterns_reverse.no_urls' does not "
  419. "appear to have any patterns in it. If you see the 'urlpatterns' "
  420. "variable with valid patterns in the file then the issue is "
  421. "probably caused by a circular import.",
  422. ):
  423. getattr(resolver, "url_patterns")
  424. @override_settings(ROOT_URLCONF="urlpatterns_reverse.urls")
  425. class URLPatternReverse(SimpleTestCase):
  426. def test_urlpattern_reverse(self):
  427. for name, expected, args, kwargs in test_data:
  428. with self.subTest(name=name, args=args, kwargs=kwargs):
  429. try:
  430. got = reverse(name, args=args, kwargs=kwargs)
  431. except NoReverseMatch:
  432. self.assertEqual(NoReverseMatch, expected)
  433. else:
  434. self.assertEqual(got, expected)
  435. def test_reverse_none(self):
  436. # Reversing None should raise an error, not return the last un-named view.
  437. with self.assertRaises(NoReverseMatch):
  438. reverse(None)
  439. def test_mixing_args_and_kwargs(self):
  440. msg = "Don't mix *args and **kwargs in call to reverse()!"
  441. with self.assertRaisesMessage(ValueError, msg):
  442. reverse("name", args=["a"], kwargs={"b": "c"})
  443. @override_script_prefix("/{{invalid}}/")
  444. def test_prefix_braces(self):
  445. self.assertEqual(
  446. "/%7B%7Binvalid%7D%7D/includes/non_path_include/",
  447. reverse("non_path_include"),
  448. )
  449. def test_prefix_parenthesis(self):
  450. # Parentheses are allowed and should not cause errors or be escaped
  451. with override_script_prefix("/bogus)/"):
  452. self.assertEqual(
  453. "/bogus)/includes/non_path_include/", reverse("non_path_include")
  454. )
  455. with override_script_prefix("/(bogus)/"):
  456. self.assertEqual(
  457. "/(bogus)/includes/non_path_include/", reverse("non_path_include")
  458. )
  459. @override_script_prefix("/bump%20map/")
  460. def test_prefix_format_char(self):
  461. self.assertEqual(
  462. "/bump%2520map/includes/non_path_include/", reverse("non_path_include")
  463. )
  464. @override_script_prefix("/%7Eme/")
  465. def test_non_urlsafe_prefix_with_args(self):
  466. # Regression for #20022, adjusted for #24013 because ~ is an unreserved
  467. # character. Tests whether % is escaped.
  468. self.assertEqual("/%257Eme/places/1/", reverse("places", args=[1]))
  469. def test_patterns_reported(self):
  470. # Regression for #17076
  471. with self.assertRaisesMessage(
  472. NoReverseMatch, r"1 pattern(s) tried: ['people/(?P<name>\\w+)/$']"
  473. ):
  474. # this url exists, but requires an argument
  475. reverse("people", args=[])
  476. @override_script_prefix("/script:name/")
  477. def test_script_name_escaping(self):
  478. self.assertEqual(
  479. reverse("optional", args=["foo:bar"]), "/script:name/optional/foo:bar/"
  480. )
  481. def test_view_not_found_message(self):
  482. msg = (
  483. "Reverse for 'nonexistent-view' not found. 'nonexistent-view' "
  484. "is not a valid view function or pattern name."
  485. )
  486. with self.assertRaisesMessage(NoReverseMatch, msg):
  487. reverse("nonexistent-view")
  488. def test_no_args_message(self):
  489. msg = "Reverse for 'places' with no arguments not found. 1 pattern(s) tried:"
  490. with self.assertRaisesMessage(NoReverseMatch, msg):
  491. reverse("places")
  492. def test_illegal_args_message(self):
  493. msg = (
  494. "Reverse for 'places' with arguments '(1, 2)' not found. 1 pattern(s) "
  495. "tried:"
  496. )
  497. with self.assertRaisesMessage(NoReverseMatch, msg):
  498. reverse("places", args=(1, 2))
  499. def test_illegal_kwargs_message(self):
  500. msg = (
  501. "Reverse for 'places' with keyword arguments '{'arg1': 2}' not found. 1 "
  502. "pattern(s) tried:"
  503. )
  504. with self.assertRaisesMessage(NoReverseMatch, msg):
  505. reverse("places", kwargs={"arg1": 2})
  506. def test_view_func_from_cbv(self):
  507. expected = "/hello/world/"
  508. url = reverse(views.view_func_from_cbv, kwargs={"name": "world"})
  509. self.assertEqual(url, expected)
  510. def test_view_func_from_cbv_no_expected_kwarg(self):
  511. with self.assertRaises(NoReverseMatch):
  512. reverse(views.view_func_from_cbv)
  513. def test_reverse_with_query(self):
  514. self.assertEqual(
  515. reverse("test", query={"hello": "world", "foo": 123}),
  516. "/test/1?hello=world&foo=123",
  517. )
  518. def test_reverse_with_query_sequences(self):
  519. cases = [
  520. [("hello", "world"), ("foo", 123), ("foo", 456)],
  521. (("hello", "world"), ("foo", 123), ("foo", 456)),
  522. {"hello": "world", "foo": (123, 456)},
  523. ]
  524. for query in cases:
  525. with self.subTest(query=query):
  526. self.assertEqual(
  527. reverse("test", query=query), "/test/1?hello=world&foo=123&foo=456"
  528. )
  529. def test_reverse_with_fragment(self):
  530. self.assertEqual(reverse("test", fragment="tab-1"), "/test/1#tab-1")
  531. def test_reverse_with_fragment_not_encoded(self):
  532. self.assertEqual(
  533. reverse("test", fragment="tab 1 is the best!"), "/test/1#tab 1 is the best!"
  534. )
  535. def test_reverse_with_query_and_fragment(self):
  536. self.assertEqual(
  537. reverse("test", query={"hello": "world", "foo": 123}, fragment="tab-1"),
  538. "/test/1?hello=world&foo=123#tab-1",
  539. )
  540. def test_reverse_with_empty_fragment(self):
  541. self.assertEqual(reverse("test", fragment=None), "/test/1")
  542. self.assertEqual(reverse("test", fragment=""), "/test/1#")
  543. def test_reverse_with_invalid_fragment(self):
  544. cases = [0, False, {}, [], set(), ()]
  545. for fragment in cases:
  546. with self.subTest(fragment=fragment):
  547. with self.assertRaises(TypeError):
  548. reverse("test", fragment=fragment)
  549. def test_reverse_with_empty_query(self):
  550. cases = [None, "", {}, [], set(), (), QueryDict()]
  551. for query in cases:
  552. with self.subTest(query=query):
  553. self.assertEqual(reverse("test", query=query), "/test/1")
  554. def test_reverse_with_invalid_query(self):
  555. cases = [0, False, [1, 3, 5], {1, 2, 3}]
  556. for query in cases:
  557. with self.subTest(query=query):
  558. with self.assertRaises(TypeError):
  559. print(reverse("test", query=query))
  560. def test_reverse_encodes_query_string(self):
  561. self.assertEqual(
  562. reverse(
  563. "test",
  564. query={
  565. "hello world": "django project",
  566. "foo": [123, 456],
  567. "@invalid": ["?", "!", "a b"],
  568. },
  569. ),
  570. "/test/1?hello+world=django+project&foo=123&foo=456"
  571. "&%40invalid=%3F&%40invalid=%21&%40invalid=a+b",
  572. )
  573. def test_reverse_with_query_from_querydict(self):
  574. query_string = "a=1&b=2&b=3&c=4"
  575. query_dict = QueryDict(query_string)
  576. self.assertEqual(reverse("test", query=query_dict), f"/test/1?{query_string}")
  577. class ResolverTests(SimpleTestCase):
  578. def test_resolver_repr(self):
  579. """
  580. Test repr of URLResolver, especially when urlconf_name is a list
  581. (#17892).
  582. """
  583. # Pick a resolver from a namespaced URLconf
  584. resolver = get_resolver("urlpatterns_reverse.namespace_urls")
  585. sub_resolver = resolver.namespace_dict["test-ns1"][1]
  586. self.assertIn("<URLPattern list>", repr(sub_resolver))
  587. def test_reverse_lazy_object_coercion_by_resolve(self):
  588. """
  589. Verifies lazy object returned by reverse_lazy is coerced to
  590. text by resolve(). Previous to #21043, this would raise a TypeError.
  591. """
  592. urls = "urlpatterns_reverse.named_urls"
  593. proxy_url = reverse_lazy("named-url1", urlconf=urls)
  594. resolver = get_resolver(urls)
  595. resolver.resolve(proxy_url)
  596. def test_resolver_reverse(self):
  597. resolver = get_resolver("urlpatterns_reverse.named_urls")
  598. test_urls = [
  599. # (name, args, kwargs, expected)
  600. ("named-url1", (), {}, ""),
  601. ("named-url2", ("arg",), {}, "extra/arg/"),
  602. ("named-url2", (), {"extra": "arg"}, "extra/arg/"),
  603. ]
  604. for name, args, kwargs, expected in test_urls:
  605. with self.subTest(name=name, args=args, kwargs=kwargs):
  606. self.assertEqual(resolver.reverse(name, *args, **kwargs), expected)
  607. def test_resolver_reverse_conflict(self):
  608. """
  609. URL pattern name arguments don't need to be unique. The last registered
  610. pattern takes precedence for conflicting names.
  611. """
  612. resolver = get_resolver("urlpatterns_reverse.named_urls_conflict")
  613. test_urls = [
  614. # (name, args, kwargs, expected)
  615. # Without arguments, the last URL in urlpatterns has precedence.
  616. ("name-conflict", (), {}, "conflict/"),
  617. # With an arg, the last URL in urlpatterns has precedence.
  618. ("name-conflict", ("arg",), {}, "conflict-last/arg/"),
  619. # With a kwarg, other URL patterns can be reversed.
  620. ("name-conflict", (), {"first": "arg"}, "conflict-first/arg/"),
  621. ("name-conflict", (), {"middle": "arg"}, "conflict-middle/arg/"),
  622. ("name-conflict", (), {"last": "arg"}, "conflict-last/arg/"),
  623. # The number and order of the arguments don't interfere with reversing.
  624. ("name-conflict", ("arg", "arg"), {}, "conflict/arg/arg/"),
  625. ]
  626. for name, args, kwargs, expected in test_urls:
  627. with self.subTest(name=name, args=args, kwargs=kwargs):
  628. self.assertEqual(resolver.reverse(name, *args, **kwargs), expected)
  629. def test_non_regex(self):
  630. """
  631. A Resolver404 is raised if resolving doesn't meet the basic
  632. requirements of a path to match - i.e., at the very least, it matches
  633. the root pattern '^/'. Never return None from resolve() to prevent a
  634. TypeError from occurring later (#10834).
  635. """
  636. test_urls = ["", "a", "\\", "."]
  637. for path_ in test_urls:
  638. with self.subTest(path=path_):
  639. with self.assertRaises(Resolver404):
  640. resolve(path_)
  641. def test_404_tried_urls_have_names(self):
  642. """
  643. The list of URLs that come back from a Resolver404 exception contains
  644. a list in the right format for printing out in the DEBUG 404 page with
  645. both the patterns and URL names, if available.
  646. """
  647. urls = "urlpatterns_reverse.named_urls"
  648. # this list matches the expected URL types and names returned when
  649. # you try to resolve a nonexistent URL in the first level of included
  650. # URLs in named_urls.py (e.g., '/included/nonexistent-url')
  651. url_types_names = [
  652. [{"type": URLPattern, "name": "named-url1"}],
  653. [{"type": URLPattern, "name": "named-url2"}],
  654. [{"type": URLPattern, "name": None}],
  655. [{"type": URLResolver}, {"type": URLPattern, "name": "named-url3"}],
  656. [{"type": URLResolver}, {"type": URLPattern, "name": "named-url4"}],
  657. [{"type": URLResolver}, {"type": URLPattern, "name": None}],
  658. [{"type": URLResolver}, {"type": URLResolver}],
  659. ]
  660. with self.assertRaisesMessage(Resolver404, "tried") as cm:
  661. resolve("/included/nonexistent-url", urlconf=urls)
  662. e = cm.exception
  663. # make sure we at least matched the root ('/') url resolver:
  664. self.assertIn("tried", e.args[0])
  665. self.assertEqual(
  666. len(e.args[0]["tried"]),
  667. len(url_types_names),
  668. "Wrong number of tried URLs returned. Expected %s, got %s."
  669. % (len(url_types_names), len(e.args[0]["tried"])),
  670. )
  671. for tried, expected in zip(e.args[0]["tried"], url_types_names):
  672. for t, e in zip(tried, expected):
  673. with self.subTest(t):
  674. self.assertIsInstance(
  675. t, e["type"]
  676. ), "%s is not an instance of %s" % (t, e["type"])
  677. if "name" in e:
  678. if not e["name"]:
  679. self.assertIsNone(
  680. t.name, "Expected no URL name but found %s." % t.name
  681. )
  682. else:
  683. self.assertEqual(
  684. t.name,
  685. e["name"],
  686. 'Wrong URL name. Expected "%s", got "%s".'
  687. % (e["name"], t.name),
  688. )
  689. def test_namespaced_view_detail(self):
  690. resolver = get_resolver("urlpatterns_reverse.nested_urls")
  691. self.assertTrue(resolver._is_callback("urlpatterns_reverse.nested_urls.view1"))
  692. self.assertTrue(resolver._is_callback("urlpatterns_reverse.nested_urls.view2"))
  693. self.assertTrue(resolver._is_callback("urlpatterns_reverse.nested_urls.View3"))
  694. self.assertFalse(resolver._is_callback("urlpatterns_reverse.nested_urls.blub"))
  695. def test_view_detail_as_method(self):
  696. # Views which have a class name as part of their path.
  697. resolver = get_resolver("urlpatterns_reverse.method_view_urls")
  698. self.assertTrue(
  699. resolver._is_callback(
  700. "urlpatterns_reverse.method_view_urls.ViewContainer.method_view"
  701. )
  702. )
  703. self.assertTrue(
  704. resolver._is_callback(
  705. "urlpatterns_reverse.method_view_urls.ViewContainer.classmethod_view"
  706. )
  707. )
  708. def test_populate_concurrency(self):
  709. """
  710. URLResolver._populate() can be called concurrently, but not more
  711. than once per thread (#26888).
  712. """
  713. resolver = URLResolver(RegexPattern(r"^/"), "urlpatterns_reverse.urls")
  714. resolver._local.populating = True
  715. thread = threading.Thread(target=resolver._populate)
  716. thread.start()
  717. thread.join()
  718. self.assertNotEqual(resolver._reverse_dict, {})
  719. @override_settings(ROOT_URLCONF="urlpatterns_reverse.reverse_lazy_urls")
  720. class ReverseLazyTest(TestCase):
  721. def test_redirect_with_lazy_reverse(self):
  722. response = self.client.get("/redirect/")
  723. self.assertRedirects(response, "/redirected_to/", status_code=302)
  724. def test_user_permission_with_lazy_reverse(self):
  725. alfred = User.objects.create_user(
  726. "alfred", "alfred@example.com", password="testpw"
  727. )
  728. response = self.client.get("/login_required_view/")
  729. self.assertRedirects(
  730. response, "/login/?next=/login_required_view/", status_code=302
  731. )
  732. self.client.force_login(alfred)
  733. response = self.client.get("/login_required_view/")
  734. self.assertEqual(response.status_code, 200)
  735. def test_inserting_reverse_lazy_into_string(self):
  736. self.assertEqual(
  737. "Some URL: %s" % reverse_lazy("some-login-page"), "Some URL: /login/"
  738. )
  739. def test_build_absolute_uri(self):
  740. factory = RequestFactory()
  741. request = factory.get("/")
  742. self.assertEqual(
  743. request.build_absolute_uri(reverse_lazy("some-login-page")),
  744. "http://testserver/login/",
  745. )
  746. class ReverseLazySettingsTest(AdminScriptTestCase):
  747. """
  748. reverse_lazy can be used in settings without causing a circular
  749. import error.
  750. """
  751. def setUp(self):
  752. super().setUp()
  753. self.write_settings(
  754. "settings.py",
  755. extra=(
  756. "from django.urls import reverse_lazy\n"
  757. "LOGIN_URL = reverse_lazy('login')"
  758. ),
  759. )
  760. def test_lazy_in_settings(self):
  761. out, err = self.run_manage(["check"])
  762. self.assertNoOutput(err)
  763. @override_settings(ROOT_URLCONF="urlpatterns_reverse.urls")
  764. class ReverseShortcutTests(SimpleTestCase):
  765. def test_redirect_to_object(self):
  766. # We don't really need a model; just something with a get_absolute_url
  767. class FakeObj:
  768. def get_absolute_url(self):
  769. return "/hi-there/"
  770. res = redirect(FakeObj())
  771. self.assertIsInstance(res, HttpResponseRedirect)
  772. self.assertEqual(res.url, "/hi-there/")
  773. res = redirect(FakeObj(), permanent=True)
  774. self.assertIsInstance(res, HttpResponsePermanentRedirect)
  775. self.assertEqual(res.url, "/hi-there/")
  776. def test_redirect_to_view_name(self):
  777. res = redirect("hardcoded2")
  778. self.assertEqual(res.url, "/hardcoded/doc.pdf")
  779. res = redirect("places", 1)
  780. self.assertEqual(res.url, "/places/1/")
  781. res = redirect("headlines", year="2008", month="02", day="17")
  782. self.assertEqual(res.url, "/headlines/2008.02.17/")
  783. with self.assertRaises(NoReverseMatch):
  784. redirect("not-a-view")
  785. def test_redirect_to_url(self):
  786. res = redirect("/foo/")
  787. self.assertEqual(res.url, "/foo/")
  788. res = redirect("http://example.com/")
  789. self.assertEqual(res.url, "http://example.com/")
  790. # Assert that we can redirect using UTF-8 strings
  791. res = redirect("/æøå/abc/")
  792. self.assertEqual(res.url, "/%C3%A6%C3%B8%C3%A5/abc/")
  793. # Assert that no imports are attempted when dealing with a relative path
  794. # (previously, the below would resolve in a UnicodeEncodeError from __import__ )
  795. res = redirect("/æøå.abc/")
  796. self.assertEqual(res.url, "/%C3%A6%C3%B8%C3%A5.abc/")
  797. res = redirect("os.path")
  798. self.assertEqual(res.url, "os.path")
  799. def test_no_illegal_imports(self):
  800. # modules that are not listed in urlpatterns should not be importable
  801. redirect("urlpatterns_reverse.nonimported_module.view")
  802. self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules)
  803. def test_reverse_by_path_nested(self):
  804. # Views added to urlpatterns using include() should be reversible.
  805. from .views import nested_view
  806. self.assertEqual(reverse(nested_view), "/includes/nested_path/")
  807. def test_redirect_view_object(self):
  808. from .views import absolute_kwargs_view
  809. res = redirect(absolute_kwargs_view)
  810. self.assertEqual(res.url, "/absolute_arg_view/")
  811. with self.assertRaises(NoReverseMatch):
  812. redirect(absolute_kwargs_view, wrong_argument=None)
  813. @override_settings(ROOT_URLCONF="urlpatterns_reverse.namespace_urls")
  814. class NamespaceTests(SimpleTestCase):
  815. def test_ambiguous_object(self):
  816. """
  817. Names deployed via dynamic URL objects that require namespaces can't
  818. be resolved.
  819. """
  820. test_urls = [
  821. ("urlobject-view", [], {}),
  822. ("urlobject-view", [37, 42], {}),
  823. ("urlobject-view", [], {"arg1": 42, "arg2": 37}),
  824. ]
  825. for name, args, kwargs in test_urls:
  826. with self.subTest(name=name, args=args, kwargs=kwargs):
  827. with self.assertRaises(NoReverseMatch):
  828. reverse(name, args=args, kwargs=kwargs)
  829. def test_ambiguous_urlpattern(self):
  830. """
  831. Names deployed via dynamic URL objects that require namespaces can't
  832. be resolved.
  833. """
  834. test_urls = [
  835. ("inner-nothing", [], {}),
  836. ("inner-nothing", [37, 42], {}),
  837. ("inner-nothing", [], {"arg1": 42, "arg2": 37}),
  838. ]
  839. for name, args, kwargs in test_urls:
  840. with self.subTest(name=name, args=args, kwargs=kwargs):
  841. with self.assertRaises(NoReverseMatch):
  842. reverse(name, args=args, kwargs=kwargs)
  843. def test_non_existent_namespace(self):
  844. """Nonexistent namespaces raise errors."""
  845. test_urls = [
  846. "blahblah:urlobject-view",
  847. "test-ns1:blahblah:urlobject-view",
  848. ]
  849. for name in test_urls:
  850. with self.subTest(name=name):
  851. with self.assertRaises(NoReverseMatch):
  852. reverse(name)
  853. def test_normal_name(self):
  854. """Normal lookups work as expected."""
  855. test_urls = [
  856. ("normal-view", [], {}, "/normal/"),
  857. ("normal-view", [37, 42], {}, "/normal/37/42/"),
  858. ("normal-view", [], {"arg1": 42, "arg2": 37}, "/normal/42/37/"),
  859. ("special-view", [], {}, "/+%5C$*/"),
  860. ]
  861. for name, args, kwargs, expected in test_urls:
  862. with self.subTest(name=name, args=args, kwargs=kwargs):
  863. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  864. def test_simple_included_name(self):
  865. """Normal lookups work on names included from other patterns."""
  866. test_urls = [
  867. ("included_namespace_urls:inc-normal-view", [], {}, "/included/normal/"),
  868. (
  869. "included_namespace_urls:inc-normal-view",
  870. [37, 42],
  871. {},
  872. "/included/normal/37/42/",
  873. ),
  874. (
  875. "included_namespace_urls:inc-normal-view",
  876. [],
  877. {"arg1": 42, "arg2": 37},
  878. "/included/normal/42/37/",
  879. ),
  880. ("included_namespace_urls:inc-special-view", [], {}, "/included/+%5C$*/"),
  881. ]
  882. for name, args, kwargs, expected in test_urls:
  883. with self.subTest(name=name, args=args, kwargs=kwargs):
  884. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  885. def test_namespace_object(self):
  886. """Dynamic URL objects can be found using a namespace."""
  887. test_urls = [
  888. ("test-ns1:urlobject-view", [], {}, "/test1/inner/"),
  889. ("test-ns1:urlobject-view", [37, 42], {}, "/test1/inner/37/42/"),
  890. (
  891. "test-ns1:urlobject-view",
  892. [],
  893. {"arg1": 42, "arg2": 37},
  894. "/test1/inner/42/37/",
  895. ),
  896. ("test-ns1:urlobject-special-view", [], {}, "/test1/inner/+%5C$*/"),
  897. ]
  898. for name, args, kwargs, expected in test_urls:
  899. with self.subTest(name=name, args=args, kwargs=kwargs):
  900. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  901. def test_app_object(self):
  902. """
  903. Dynamic URL objects can return a (pattern, app_name) 2-tuple, and
  904. include() can set the namespace.
  905. """
  906. test_urls = [
  907. ("new-ns1:urlobject-view", [], {}, "/newapp1/inner/"),
  908. ("new-ns1:urlobject-view", [37, 42], {}, "/newapp1/inner/37/42/"),
  909. (
  910. "new-ns1:urlobject-view",
  911. [],
  912. {"arg1": 42, "arg2": 37},
  913. "/newapp1/inner/42/37/",
  914. ),
  915. ("new-ns1:urlobject-special-view", [], {}, "/newapp1/inner/+%5C$*/"),
  916. ]
  917. for name, args, kwargs, expected in test_urls:
  918. with self.subTest(name=name, args=args, kwargs=kwargs):
  919. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  920. def test_app_object_default_namespace(self):
  921. """
  922. Namespace defaults to app_name when including a (pattern, app_name)
  923. 2-tuple.
  924. """
  925. test_urls = [
  926. ("newapp:urlobject-view", [], {}, "/new-default/inner/"),
  927. ("newapp:urlobject-view", [37, 42], {}, "/new-default/inner/37/42/"),
  928. (
  929. "newapp:urlobject-view",
  930. [],
  931. {"arg1": 42, "arg2": 37},
  932. "/new-default/inner/42/37/",
  933. ),
  934. ("newapp:urlobject-special-view", [], {}, "/new-default/inner/+%5C$*/"),
  935. ]
  936. for name, args, kwargs, expected in test_urls:
  937. with self.subTest(name=name, args=args, kwargs=kwargs):
  938. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  939. def test_embedded_namespace_object(self):
  940. """Namespaces can be installed anywhere in the URL pattern tree."""
  941. test_urls = [
  942. (
  943. "included_namespace_urls:test-ns3:urlobject-view",
  944. [],
  945. {},
  946. "/included/test3/inner/",
  947. ),
  948. (
  949. "included_namespace_urls:test-ns3:urlobject-view",
  950. [37, 42],
  951. {},
  952. "/included/test3/inner/37/42/",
  953. ),
  954. (
  955. "included_namespace_urls:test-ns3:urlobject-view",
  956. [],
  957. {"arg1": 42, "arg2": 37},
  958. "/included/test3/inner/42/37/",
  959. ),
  960. (
  961. "included_namespace_urls:test-ns3:urlobject-special-view",
  962. [],
  963. {},
  964. "/included/test3/inner/+%5C$*/",
  965. ),
  966. ]
  967. for name, args, kwargs, expected in test_urls:
  968. with self.subTest(name=name, args=args, kwargs=kwargs):
  969. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  970. def test_namespace_pattern(self):
  971. """Namespaces can be applied to include()'d urlpatterns."""
  972. test_urls = [
  973. ("inc-ns1:inc-normal-view", [], {}, "/ns-included1/normal/"),
  974. ("inc-ns1:inc-normal-view", [37, 42], {}, "/ns-included1/normal/37/42/"),
  975. (
  976. "inc-ns1:inc-normal-view",
  977. [],
  978. {"arg1": 42, "arg2": 37},
  979. "/ns-included1/normal/42/37/",
  980. ),
  981. ("inc-ns1:inc-special-view", [], {}, "/ns-included1/+%5C$*/"),
  982. ]
  983. for name, args, kwargs, expected in test_urls:
  984. with self.subTest(name=name, args=args, kwargs=kwargs):
  985. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  986. def test_app_name_pattern(self):
  987. """
  988. Namespaces can be applied to include()'d urlpatterns that set an
  989. app_name attribute.
  990. """
  991. test_urls = [
  992. ("app-ns1:inc-normal-view", [], {}, "/app-included1/normal/"),
  993. ("app-ns1:inc-normal-view", [37, 42], {}, "/app-included1/normal/37/42/"),
  994. (
  995. "app-ns1:inc-normal-view",
  996. [],
  997. {"arg1": 42, "arg2": 37},
  998. "/app-included1/normal/42/37/",
  999. ),
  1000. ("app-ns1:inc-special-view", [], {}, "/app-included1/+%5C$*/"),
  1001. ]
  1002. for name, args, kwargs, expected in test_urls:
  1003. with self.subTest(name=name, args=args, kwargs=kwargs):
  1004. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  1005. def test_namespace_pattern_with_variable_prefix(self):
  1006. """
  1007. Using include() with namespaces when there is a regex variable in front
  1008. of it.
  1009. """
  1010. test_urls = [
  1011. ("inc-outer:inc-normal-view", [], {"outer": 42}, "/ns-outer/42/normal/"),
  1012. ("inc-outer:inc-normal-view", [42], {}, "/ns-outer/42/normal/"),
  1013. (
  1014. "inc-outer:inc-normal-view",
  1015. [],
  1016. {"arg1": 37, "arg2": 4, "outer": 42},
  1017. "/ns-outer/42/normal/37/4/",
  1018. ),
  1019. ("inc-outer:inc-normal-view", [42, 37, 4], {}, "/ns-outer/42/normal/37/4/"),
  1020. ("inc-outer:inc-special-view", [], {"outer": 42}, "/ns-outer/42/+%5C$*/"),
  1021. ("inc-outer:inc-special-view", [42], {}, "/ns-outer/42/+%5C$*/"),
  1022. ]
  1023. for name, args, kwargs, expected in test_urls:
  1024. with self.subTest(name=name, args=args, kwargs=kwargs):
  1025. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  1026. def test_multiple_namespace_pattern(self):
  1027. """Namespaces can be embedded."""
  1028. test_urls = [
  1029. ("inc-ns1:test-ns3:urlobject-view", [], {}, "/ns-included1/test3/inner/"),
  1030. (
  1031. "inc-ns1:test-ns3:urlobject-view",
  1032. [37, 42],
  1033. {},
  1034. "/ns-included1/test3/inner/37/42/",
  1035. ),
  1036. (
  1037. "inc-ns1:test-ns3:urlobject-view",
  1038. [],
  1039. {"arg1": 42, "arg2": 37},
  1040. "/ns-included1/test3/inner/42/37/",
  1041. ),
  1042. (
  1043. "inc-ns1:test-ns3:urlobject-special-view",
  1044. [],
  1045. {},
  1046. "/ns-included1/test3/inner/+%5C$*/",
  1047. ),
  1048. ]
  1049. for name, args, kwargs, expected in test_urls:
  1050. with self.subTest(name=name, args=args, kwargs=kwargs):
  1051. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  1052. def test_nested_namespace_pattern(self):
  1053. """Namespaces can be nested."""
  1054. test_urls = [
  1055. (
  1056. "inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view",
  1057. [],
  1058. {},
  1059. "/ns-included1/ns-included4/ns-included1/test3/inner/",
  1060. ),
  1061. (
  1062. "inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view",
  1063. [37, 42],
  1064. {},
  1065. "/ns-included1/ns-included4/ns-included1/test3/inner/37/42/",
  1066. ),
  1067. (
  1068. "inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view",
  1069. [],
  1070. {"arg1": 42, "arg2": 37},
  1071. "/ns-included1/ns-included4/ns-included1/test3/inner/42/37/",
  1072. ),
  1073. (
  1074. "inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-special-view",
  1075. [],
  1076. {},
  1077. "/ns-included1/ns-included4/ns-included1/test3/inner/+%5C$*/",
  1078. ),
  1079. ]
  1080. for name, args, kwargs, expected in test_urls:
  1081. with self.subTest(name=name, args=args, kwargs=kwargs):
  1082. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  1083. def test_app_lookup_object(self):
  1084. """A default application namespace can be used for lookup."""
  1085. test_urls = [
  1086. ("testapp:urlobject-view", [], {}, "/default/inner/"),
  1087. ("testapp:urlobject-view", [37, 42], {}, "/default/inner/37/42/"),
  1088. (
  1089. "testapp:urlobject-view",
  1090. [],
  1091. {"arg1": 42, "arg2": 37},
  1092. "/default/inner/42/37/",
  1093. ),
  1094. ("testapp:urlobject-special-view", [], {}, "/default/inner/+%5C$*/"),
  1095. ]
  1096. for name, args, kwargs, expected in test_urls:
  1097. with self.subTest(name=name, args=args, kwargs=kwargs):
  1098. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  1099. def test_app_lookup_object_with_default(self):
  1100. """A default application namespace is sensitive to the current app."""
  1101. test_urls = [
  1102. ("testapp:urlobject-view", [], {}, "test-ns3", "/default/inner/"),
  1103. (
  1104. "testapp:urlobject-view",
  1105. [37, 42],
  1106. {},
  1107. "test-ns3",
  1108. "/default/inner/37/42/",
  1109. ),
  1110. (
  1111. "testapp:urlobject-view",
  1112. [],
  1113. {"arg1": 42, "arg2": 37},
  1114. "test-ns3",
  1115. "/default/inner/42/37/",
  1116. ),
  1117. (
  1118. "testapp:urlobject-special-view",
  1119. [],
  1120. {},
  1121. "test-ns3",
  1122. "/default/inner/+%5C$*/",
  1123. ),
  1124. ]
  1125. for name, args, kwargs, current_app, expected in test_urls:
  1126. with self.subTest(
  1127. name=name, args=args, kwargs=kwargs, current_app=current_app
  1128. ):
  1129. self.assertEqual(
  1130. reverse(name, args=args, kwargs=kwargs, current_app=current_app),
  1131. expected,
  1132. )
  1133. def test_app_lookup_object_without_default(self):
  1134. """
  1135. An application namespace without a default is sensitive to the current
  1136. app.
  1137. """
  1138. test_urls = [
  1139. ("nodefault:urlobject-view", [], {}, None, "/other2/inner/"),
  1140. ("nodefault:urlobject-view", [37, 42], {}, None, "/other2/inner/37/42/"),
  1141. (
  1142. "nodefault:urlobject-view",
  1143. [],
  1144. {"arg1": 42, "arg2": 37},
  1145. None,
  1146. "/other2/inner/42/37/",
  1147. ),
  1148. ("nodefault:urlobject-special-view", [], {}, None, "/other2/inner/+%5C$*/"),
  1149. ("nodefault:urlobject-view", [], {}, "other-ns1", "/other1/inner/"),
  1150. (
  1151. "nodefault:urlobject-view",
  1152. [37, 42],
  1153. {},
  1154. "other-ns1",
  1155. "/other1/inner/37/42/",
  1156. ),
  1157. (
  1158. "nodefault:urlobject-view",
  1159. [],
  1160. {"arg1": 42, "arg2": 37},
  1161. "other-ns1",
  1162. "/other1/inner/42/37/",
  1163. ),
  1164. (
  1165. "nodefault:urlobject-special-view",
  1166. [],
  1167. {},
  1168. "other-ns1",
  1169. "/other1/inner/+%5C$*/",
  1170. ),
  1171. ]
  1172. for name, args, kwargs, current_app, expected in test_urls:
  1173. with self.subTest(
  1174. name=name, args=args, kwargs=kwargs, current_app=current_app
  1175. ):
  1176. self.assertEqual(
  1177. reverse(name, args=args, kwargs=kwargs, current_app=current_app),
  1178. expected,
  1179. )
  1180. def test_special_chars_namespace(self):
  1181. test_urls = [
  1182. (
  1183. "special:included_namespace_urls:inc-normal-view",
  1184. [],
  1185. {},
  1186. "/+%5C$*/included/normal/",
  1187. ),
  1188. (
  1189. "special:included_namespace_urls:inc-normal-view",
  1190. [37, 42],
  1191. {},
  1192. "/+%5C$*/included/normal/37/42/",
  1193. ),
  1194. (
  1195. "special:included_namespace_urls:inc-normal-view",
  1196. [],
  1197. {"arg1": 42, "arg2": 37},
  1198. "/+%5C$*/included/normal/42/37/",
  1199. ),
  1200. (
  1201. "special:included_namespace_urls:inc-special-view",
  1202. [],
  1203. {},
  1204. "/+%5C$*/included/+%5C$*/",
  1205. ),
  1206. ]
  1207. for name, args, kwargs, expected in test_urls:
  1208. with self.subTest(name=name, args=args, kwargs=kwargs):
  1209. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  1210. def test_namespaces_with_variables(self):
  1211. """Namespace prefixes can capture variables."""
  1212. test_urls = [
  1213. ("inc-ns5:inner-nothing", [], {"outer": "70"}, "/inc70/"),
  1214. (
  1215. "inc-ns5:inner-extra",
  1216. [],
  1217. {"extra": "foobar", "outer": "78"},
  1218. "/inc78/extra/foobar/",
  1219. ),
  1220. ("inc-ns5:inner-nothing", ["70"], {}, "/inc70/"),
  1221. ("inc-ns5:inner-extra", ["78", "foobar"], {}, "/inc78/extra/foobar/"),
  1222. ]
  1223. for name, args, kwargs, expected in test_urls:
  1224. with self.subTest(name=name, args=args, kwargs=kwargs):
  1225. self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
  1226. def test_nested_app_lookup(self):
  1227. """
  1228. A nested current_app should be split in individual namespaces (#24904).
  1229. """
  1230. test_urls = [
  1231. (
  1232. "inc-ns1:testapp:urlobject-view",
  1233. [],
  1234. {},
  1235. None,
  1236. "/ns-included1/test4/inner/",
  1237. ),
  1238. (
  1239. "inc-ns1:testapp:urlobject-view",
  1240. [37, 42],
  1241. {},
  1242. None,
  1243. "/ns-included1/test4/inner/37/42/",
  1244. ),
  1245. (
  1246. "inc-ns1:testapp:urlobject-view",
  1247. [],
  1248. {"arg1": 42, "arg2": 37},
  1249. None,
  1250. "/ns-included1/test4/inner/42/37/",
  1251. ),
  1252. (
  1253. "inc-ns1:testapp:urlobject-special-view",
  1254. [],
  1255. {},
  1256. None,
  1257. "/ns-included1/test4/inner/+%5C$*/",
  1258. ),
  1259. (
  1260. "inc-ns1:testapp:urlobject-view",
  1261. [],
  1262. {},
  1263. "inc-ns1:test-ns3",
  1264. "/ns-included1/test3/inner/",
  1265. ),
  1266. (
  1267. "inc-ns1:testapp:urlobject-view",
  1268. [37, 42],
  1269. {},
  1270. "inc-ns1:test-ns3",
  1271. "/ns-included1/test3/inner/37/42/",
  1272. ),
  1273. (
  1274. "inc-ns1:testapp:urlobject-view",
  1275. [],
  1276. {"arg1": 42, "arg2": 37},
  1277. "inc-ns1:test-ns3",
  1278. "/ns-included1/test3/inner/42/37/",
  1279. ),
  1280. (
  1281. "inc-ns1:testapp:urlobject-special-view",
  1282. [],
  1283. {},
  1284. "inc-ns1:test-ns3",
  1285. "/ns-included1/test3/inner/+%5C$*/",
  1286. ),
  1287. ]
  1288. for name, args, kwargs, current_app, expected in test_urls:
  1289. with self.subTest(
  1290. name=name, args=args, kwargs=kwargs, current_app=current_app
  1291. ):
  1292. self.assertEqual(
  1293. reverse(name, args=args, kwargs=kwargs, current_app=current_app),
  1294. expected,
  1295. )
  1296. def test_current_app_no_partial_match(self):
  1297. """current_app shouldn't be used unless it matches the whole path."""
  1298. test_urls = [
  1299. (
  1300. "inc-ns1:testapp:urlobject-view",
  1301. [],
  1302. {},
  1303. "nonexistent:test-ns3",
  1304. "/ns-included1/test4/inner/",
  1305. ),
  1306. (
  1307. "inc-ns1:testapp:urlobject-view",
  1308. [37, 42],
  1309. {},
  1310. "nonexistent:test-ns3",
  1311. "/ns-included1/test4/inner/37/42/",
  1312. ),
  1313. (
  1314. "inc-ns1:testapp:urlobject-view",
  1315. [],
  1316. {"arg1": 42, "arg2": 37},
  1317. "nonexistent:test-ns3",
  1318. "/ns-included1/test4/inner/42/37/",
  1319. ),
  1320. (
  1321. "inc-ns1:testapp:urlobject-special-view",
  1322. [],
  1323. {},
  1324. "nonexistent:test-ns3",
  1325. "/ns-included1/test4/inner/+%5C$*/",
  1326. ),
  1327. ]
  1328. for name, args, kwargs, current_app, expected in test_urls:
  1329. with self.subTest(
  1330. name=name, args=args, kwargs=kwargs, current_app=current_app
  1331. ):
  1332. self.assertEqual(
  1333. reverse(name, args=args, kwargs=kwargs, current_app=current_app),
  1334. expected,
  1335. )
  1336. @override_settings(ROOT_URLCONF=urlconf_outer.__name__)
  1337. class RequestURLconfTests(SimpleTestCase):
  1338. def test_urlconf(self):
  1339. response = self.client.get("/test/me/")
  1340. self.assertEqual(response.status_code, 200)
  1341. self.assertEqual(
  1342. response.content, b"outer:/test/me/,inner:/inner_urlconf/second_test/"
  1343. )
  1344. response = self.client.get("/inner_urlconf/second_test/")
  1345. self.assertEqual(response.status_code, 200)
  1346. response = self.client.get("/second_test/")
  1347. self.assertEqual(response.status_code, 404)
  1348. @override_settings(
  1349. MIDDLEWARE=[
  1350. "%s.ChangeURLconfMiddleware" % middleware.__name__,
  1351. ]
  1352. )
  1353. def test_urlconf_overridden(self):
  1354. response = self.client.get("/test/me/")
  1355. self.assertEqual(response.status_code, 404)
  1356. response = self.client.get("/inner_urlconf/second_test/")
  1357. self.assertEqual(response.status_code, 404)
  1358. response = self.client.get("/second_test/")
  1359. self.assertEqual(response.status_code, 200)
  1360. self.assertEqual(response.content, b"outer:,inner:/second_test/")
  1361. @override_settings(
  1362. MIDDLEWARE=[
  1363. "%s.NullChangeURLconfMiddleware" % middleware.__name__,
  1364. ]
  1365. )
  1366. def test_urlconf_overridden_with_null(self):
  1367. """
  1368. Overriding request.urlconf with None will fall back to the default
  1369. URLconf.
  1370. """
  1371. response = self.client.get("/test/me/")
  1372. self.assertEqual(response.status_code, 200)
  1373. self.assertEqual(
  1374. response.content, b"outer:/test/me/,inner:/inner_urlconf/second_test/"
  1375. )
  1376. response = self.client.get("/inner_urlconf/second_test/")
  1377. self.assertEqual(response.status_code, 200)
  1378. response = self.client.get("/second_test/")
  1379. self.assertEqual(response.status_code, 404)
  1380. @override_settings(
  1381. MIDDLEWARE=[
  1382. "%s.ChangeURLconfMiddleware" % middleware.__name__,
  1383. "%s.ReverseInnerInResponseMiddleware" % middleware.__name__,
  1384. ]
  1385. )
  1386. def test_reverse_inner_in_response_middleware(self):
  1387. """
  1388. Test reversing an URL from the *overridden* URLconf from inside
  1389. a response middleware.
  1390. """
  1391. response = self.client.get("/second_test/")
  1392. self.assertEqual(response.status_code, 200)
  1393. self.assertEqual(response.content, b"/second_test/")
  1394. @override_settings(
  1395. MIDDLEWARE=[
  1396. "%s.ChangeURLconfMiddleware" % middleware.__name__,
  1397. "%s.ReverseOuterInResponseMiddleware" % middleware.__name__,
  1398. ]
  1399. )
  1400. def test_reverse_outer_in_response_middleware(self):
  1401. """
  1402. Test reversing an URL from the *default* URLconf from inside
  1403. a response middleware.
  1404. """
  1405. msg = (
  1406. "Reverse for 'outer' not found. 'outer' is not a valid view "
  1407. "function or pattern name."
  1408. )
  1409. with self.assertRaisesMessage(NoReverseMatch, msg):
  1410. self.client.get("/second_test/")
  1411. @override_settings(
  1412. MIDDLEWARE=[
  1413. "%s.ChangeURLconfMiddleware" % middleware.__name__,
  1414. "%s.ReverseInnerInStreaming" % middleware.__name__,
  1415. ]
  1416. )
  1417. def test_reverse_inner_in_streaming(self):
  1418. """
  1419. Test reversing an URL from the *overridden* URLconf from inside
  1420. a streaming response.
  1421. """
  1422. response = self.client.get("/second_test/")
  1423. self.assertEqual(response.status_code, 200)
  1424. self.assertEqual(b"".join(response), b"/second_test/")
  1425. @override_settings(
  1426. MIDDLEWARE=[
  1427. "%s.ChangeURLconfMiddleware" % middleware.__name__,
  1428. "%s.ReverseOuterInStreaming" % middleware.__name__,
  1429. ]
  1430. )
  1431. def test_reverse_outer_in_streaming(self):
  1432. """
  1433. Test reversing an URL from the *default* URLconf from inside
  1434. a streaming response.
  1435. """
  1436. message = "Reverse for 'outer' not found."
  1437. with self.assertRaisesMessage(NoReverseMatch, message):
  1438. self.client.get("/second_test/")
  1439. b"".join(self.client.get("/second_test/"))
  1440. def test_urlconf_is_reset_after_request(self):
  1441. """The URLconf is reset after each request."""
  1442. self.assertIsNone(get_urlconf())
  1443. with override_settings(
  1444. MIDDLEWARE=["%s.ChangeURLconfMiddleware" % middleware.__name__]
  1445. ):
  1446. self.client.get(reverse("inner"))
  1447. self.assertIsNone(get_urlconf())
  1448. class ErrorHandlerResolutionTests(SimpleTestCase):
  1449. """Tests for handler400, handler403, handler404 and handler500"""
  1450. def setUp(self):
  1451. urlconf = "urlpatterns_reverse.urls_error_handlers"
  1452. urlconf_callables = "urlpatterns_reverse.urls_error_handlers_callables"
  1453. self.resolver = URLResolver(RegexPattern(r"^$"), urlconf)
  1454. self.callable_resolver = URLResolver(RegexPattern(r"^$"), urlconf_callables)
  1455. def test_named_handlers(self):
  1456. for code in [400, 403, 404, 500]:
  1457. with self.subTest(code=code):
  1458. self.assertEqual(self.resolver.resolve_error_handler(code), empty_view)
  1459. def test_callable_handlers(self):
  1460. for code in [400, 403, 404, 500]:
  1461. with self.subTest(code=code):
  1462. self.assertEqual(
  1463. self.callable_resolver.resolve_error_handler(code), empty_view
  1464. )
  1465. @override_settings(ROOT_URLCONF="urlpatterns_reverse.urls_without_handlers")
  1466. class DefaultErrorHandlerTests(SimpleTestCase):
  1467. def test_default_handler(self):
  1468. "If the urls.py doesn't specify handlers, the defaults are used"
  1469. response = self.client.get("/test/")
  1470. self.assertEqual(response.status_code, 404)
  1471. msg = "I don't think I'm getting good value for this view"
  1472. with self.assertRaisesMessage(ValueError, msg):
  1473. self.client.get("/bad_view/")
  1474. @override_settings(ROOT_URLCONF=None)
  1475. class NoRootUrlConfTests(SimpleTestCase):
  1476. """Tests for handler404 and handler500 if ROOT_URLCONF is None"""
  1477. def test_no_handler_exception(self):
  1478. msg = (
  1479. "The included URLconf 'None' does not appear to have any patterns "
  1480. "in it. If you see the 'urlpatterns' variable with valid patterns "
  1481. "in the file then the issue is probably caused by a circular "
  1482. "import."
  1483. )
  1484. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  1485. self.client.get("/test/me/")
  1486. @override_settings(ROOT_URLCONF="urlpatterns_reverse.namespace_urls")
  1487. class ResolverMatchTests(SimpleTestCase):
  1488. def test_urlpattern_resolve(self):
  1489. for (
  1490. path_,
  1491. url_name,
  1492. app_name,
  1493. namespace,
  1494. view_name,
  1495. func,
  1496. args,
  1497. kwargs,
  1498. ) in resolve_test_data:
  1499. with self.subTest(path=path_):
  1500. # Legacy support for extracting "function, args, kwargs".
  1501. match_func, match_args, match_kwargs = resolve(path_)
  1502. self.assertEqual(match_func, func)
  1503. self.assertEqual(match_args, args)
  1504. self.assertEqual(match_kwargs, kwargs)
  1505. # ResolverMatch capabilities.
  1506. match = resolve(path_)
  1507. self.assertEqual(match.__class__, ResolverMatch)
  1508. self.assertEqual(match.url_name, url_name)
  1509. self.assertEqual(match.app_name, app_name)
  1510. self.assertEqual(match.namespace, namespace)
  1511. self.assertEqual(match.view_name, view_name)
  1512. self.assertEqual(match.func, func)
  1513. self.assertEqual(match.args, args)
  1514. self.assertEqual(match.kwargs, kwargs)
  1515. # and for legacy purposes:
  1516. self.assertEqual(match[0], func)
  1517. self.assertEqual(match[1], args)
  1518. self.assertEqual(match[2], kwargs)
  1519. def test_resolver_match_on_request(self):
  1520. response = self.client.get("/resolver_match/")
  1521. resolver_match = response.resolver_match
  1522. self.assertEqual(resolver_match.url_name, "test-resolver-match")
  1523. def test_resolver_match_on_request_before_resolution(self):
  1524. request = HttpRequest()
  1525. self.assertIsNone(request.resolver_match)
  1526. def test_repr(self):
  1527. self.assertEqual(
  1528. repr(resolve("/no_kwargs/42/37/")),
  1529. "ResolverMatch(func=urlpatterns_reverse.views.empty_view, "
  1530. "args=('42', '37'), kwargs={}, url_name='no-kwargs', app_names=[], "
  1531. "namespaces=[], route='^no_kwargs/([0-9]+)/([0-9]+)/$')",
  1532. )
  1533. def test_repr_extra_kwargs(self):
  1534. self.assertEqual(
  1535. repr(resolve("/mixed_args/1986/11/")),
  1536. "ResolverMatch(func=urlpatterns_reverse.views.empty_view, args=(), "
  1537. "kwargs={'arg2': '11', 'extra': True}, url_name='mixed-args', "
  1538. "app_names=[], namespaces=[], "
  1539. "route='^mixed_args/([0-9]+)/(?P<arg2>[0-9]+)/$', "
  1540. "captured_kwargs={'arg2': '11'}, extra_kwargs={'extra': True})",
  1541. )
  1542. @override_settings(ROOT_URLCONF="urlpatterns_reverse.reverse_lazy_urls")
  1543. def test_classbased_repr(self):
  1544. self.assertEqual(
  1545. repr(resolve("/redirect/")),
  1546. "ResolverMatch(func=urlpatterns_reverse.views.LazyRedirectView, "
  1547. "args=(), kwargs={}, url_name=None, app_names=[], "
  1548. "namespaces=[], route='redirect/')",
  1549. )
  1550. @override_settings(ROOT_URLCONF="urlpatterns_reverse.urls")
  1551. def test_repr_functools_partial(self):
  1552. tests = [
  1553. ("partial", "template.html"),
  1554. ("partial_nested", "nested_partial.html"),
  1555. ("partial_wrapped", "template.html"),
  1556. ]
  1557. for name, template_name in tests:
  1558. with self.subTest(name=name):
  1559. func = (
  1560. f"functools.partial({views.empty_view!r}, "
  1561. f"template_name='{template_name}')"
  1562. )
  1563. self.assertEqual(
  1564. repr(resolve(f"/{name}/")),
  1565. f"ResolverMatch(func={func}, args=(), kwargs={{}}, "
  1566. f"url_name='{name}', app_names=[], namespaces=[], "
  1567. f"route='{name}/')",
  1568. )
  1569. @override_settings(ROOT_URLCONF="urlpatterns.path_urls")
  1570. def test_pickling(self):
  1571. msg = "Cannot pickle ResolverMatch."
  1572. with self.assertRaisesMessage(pickle.PicklingError, msg):
  1573. pickle.dumps(resolve("/users/"))
  1574. @override_settings(ROOT_URLCONF="urlpatterns_reverse.erroneous_urls")
  1575. class ErroneousViewTests(SimpleTestCase):
  1576. def test_noncallable_view(self):
  1577. # View is not a callable (explicit import; arbitrary Python object)
  1578. with self.assertRaisesMessage(TypeError, "view must be a callable"):
  1579. path("uncallable-object/", views.uncallable)
  1580. def test_invalid_regex(self):
  1581. # Regex contains an error (refs #6170)
  1582. msg = '(regex_error/$" is not a valid regular expression'
  1583. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  1584. reverse(views.empty_view)
  1585. class ViewLoadingTests(SimpleTestCase):
  1586. def test_view_loading(self):
  1587. self.assertEqual(
  1588. get_callable("urlpatterns_reverse.views.empty_view"), empty_view
  1589. )
  1590. self.assertEqual(get_callable(empty_view), empty_view)
  1591. def test_view_does_not_exist(self):
  1592. msg = "View does not exist in module urlpatterns_reverse.views."
  1593. with self.assertRaisesMessage(ViewDoesNotExist, msg):
  1594. get_callable("urlpatterns_reverse.views.i_should_not_exist")
  1595. def test_attributeerror_not_hidden(self):
  1596. msg = "I am here to confuse django.urls.get_callable"
  1597. with self.assertRaisesMessage(AttributeError, msg):
  1598. get_callable("urlpatterns_reverse.views_broken.i_am_broken")
  1599. def test_non_string_value(self):
  1600. msg = "'1' is not a callable or a dot-notation path"
  1601. with self.assertRaisesMessage(ViewDoesNotExist, msg):
  1602. get_callable(1)
  1603. def test_string_without_dot(self):
  1604. msg = "Could not import 'test'. The path must be fully qualified."
  1605. with self.assertRaisesMessage(ImportError, msg):
  1606. get_callable("test")
  1607. def test_module_does_not_exist(self):
  1608. with self.assertRaisesMessage(ImportError, "No module named 'foo'"):
  1609. get_callable("foo.bar")
  1610. def test_parent_module_does_not_exist(self):
  1611. msg = "Parent module urlpatterns_reverse.foo does not exist."
  1612. with self.assertRaisesMessage(ViewDoesNotExist, msg):
  1613. get_callable("urlpatterns_reverse.foo.bar")
  1614. def test_not_callable(self):
  1615. msg = (
  1616. "Could not import 'urlpatterns_reverse.tests.resolve_test_data'. "
  1617. "View is not callable."
  1618. )
  1619. with self.assertRaisesMessage(ViewDoesNotExist, msg):
  1620. get_callable("urlpatterns_reverse.tests.resolve_test_data")
  1621. class IncludeTests(SimpleTestCase):
  1622. url_patterns = [
  1623. path("inner/", views.empty_view, name="urlobject-view"),
  1624. re_path(
  1625. r"^inner/(?P<arg1>[0-9]+)/(?P<arg2>[0-9]+)/$",
  1626. views.empty_view,
  1627. name="urlobject-view",
  1628. ),
  1629. re_path(r"^inner/\+\\\$\*/$", views.empty_view, name="urlobject-special-view"),
  1630. ]
  1631. app_urls = URLObject("inc-app")
  1632. def test_include_urls(self):
  1633. self.assertEqual(include(self.url_patterns), (self.url_patterns, None, None))
  1634. def test_include_namespace(self):
  1635. msg = (
  1636. "Specifying a namespace in include() without providing an "
  1637. "app_name is not supported."
  1638. )
  1639. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  1640. include(self.url_patterns, "namespace")
  1641. def test_include_4_tuple(self):
  1642. msg = "Passing a 4-tuple to include() is not supported."
  1643. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  1644. include((self.url_patterns, "app_name", "namespace", "blah"))
  1645. def test_include_3_tuple(self):
  1646. msg = "Passing a 3-tuple to include() is not supported."
  1647. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  1648. include((self.url_patterns, "app_name", "namespace"))
  1649. def test_include_3_tuple_namespace(self):
  1650. msg = (
  1651. "Cannot override the namespace for a dynamic module that provides a "
  1652. "namespace."
  1653. )
  1654. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  1655. include((self.url_patterns, "app_name", "namespace"), "namespace")
  1656. def test_include_2_tuple(self):
  1657. self.assertEqual(
  1658. include((self.url_patterns, "app_name")),
  1659. (self.url_patterns, "app_name", "app_name"),
  1660. )
  1661. def test_include_2_tuple_namespace(self):
  1662. self.assertEqual(
  1663. include((self.url_patterns, "app_name"), namespace="namespace"),
  1664. (self.url_patterns, "app_name", "namespace"),
  1665. )
  1666. def test_include_app_name(self):
  1667. self.assertEqual(include(self.app_urls), (self.app_urls, "inc-app", "inc-app"))
  1668. def test_include_app_name_namespace(self):
  1669. self.assertEqual(
  1670. include(self.app_urls, "namespace"), (self.app_urls, "inc-app", "namespace")
  1671. )
  1672. @override_settings(ROOT_URLCONF="urlpatterns_reverse.urls")
  1673. class LookaheadTests(SimpleTestCase):
  1674. def test_valid_resolve(self):
  1675. test_urls = [
  1676. "/lookahead-/a-city/",
  1677. "/lookbehind-/a-city/",
  1678. "/lookahead+/a-city/",
  1679. "/lookbehind+/a-city/",
  1680. ]
  1681. for test_url in test_urls:
  1682. with self.subTest(url=test_url):
  1683. self.assertEqual(resolve(test_url).kwargs, {"city": "a-city"})
  1684. def test_invalid_resolve(self):
  1685. test_urls = [
  1686. "/lookahead-/not-a-city/",
  1687. "/lookbehind-/not-a-city/",
  1688. "/lookahead+/other-city/",
  1689. "/lookbehind+/other-city/",
  1690. ]
  1691. for test_url in test_urls:
  1692. with self.subTest(url=test_url):
  1693. with self.assertRaises(Resolver404):
  1694. resolve(test_url)
  1695. def test_valid_reverse(self):
  1696. test_urls = [
  1697. ("lookahead-positive", {"city": "a-city"}, "/lookahead+/a-city/"),
  1698. ("lookahead-negative", {"city": "a-city"}, "/lookahead-/a-city/"),
  1699. ("lookbehind-positive", {"city": "a-city"}, "/lookbehind+/a-city/"),
  1700. ("lookbehind-negative", {"city": "a-city"}, "/lookbehind-/a-city/"),
  1701. ]
  1702. for name, kwargs, expected in test_urls:
  1703. with self.subTest(name=name, kwargs=kwargs):
  1704. self.assertEqual(reverse(name, kwargs=kwargs), expected)
  1705. def test_invalid_reverse(self):
  1706. test_urls = [
  1707. ("lookahead-positive", {"city": "other-city"}),
  1708. ("lookahead-negative", {"city": "not-a-city"}),
  1709. ("lookbehind-positive", {"city": "other-city"}),
  1710. ("lookbehind-negative", {"city": "not-a-city"}),
  1711. ]
  1712. for name, kwargs in test_urls:
  1713. with self.subTest(name=name, kwargs=kwargs):
  1714. with self.assertRaises(NoReverseMatch):
  1715. reverse(name, kwargs=kwargs)
  1716. @override_settings(ROOT_URLCONF="urlpatterns_reverse.urls")
  1717. class ReverseResolvedTests(SimpleTestCase):
  1718. def test_rereverse(self):
  1719. match = resolve("/resolved/12/")
  1720. self.assertEqual(
  1721. reverse(match.url_name, args=match.args, kwargs=match.kwargs),
  1722. "/resolved/12/",
  1723. )
  1724. match = resolve("/resolved-overridden/12/url/")
  1725. self.assertEqual(
  1726. reverse(match.url_name, args=match.args, kwargs=match.captured_kwargs),
  1727. "/resolved-overridden/12/url/",
  1728. )