123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785 |
- """
- Unit tests for reverse URL lookups.
- """
- import pickle
- import sys
- import threading
- from admin_scripts.tests import AdminScriptTestCase
- from django.conf import settings
- from django.contrib.auth.models import User
- from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
- from django.http import HttpRequest, HttpResponsePermanentRedirect, HttpResponseRedirect
- from django.shortcuts import redirect
- from django.test import RequestFactory, SimpleTestCase, TestCase, override_settings
- from django.test.utils import override_script_prefix
- from django.urls import (
- NoReverseMatch,
- Resolver404,
- ResolverMatch,
- URLPattern,
- URLResolver,
- get_callable,
- get_resolver,
- get_urlconf,
- include,
- path,
- re_path,
- resolve,
- reverse,
- reverse_lazy,
- )
- from django.urls.resolvers import RegexPattern
- from . import middleware, urlconf_outer, views
- from .utils import URLObject
- from .views import empty_view
- resolve_test_data = (
- # These entries are in the format:
- # (path, url_name, app_name, namespace, view_name, func, args, kwargs)
- # Simple case
- (
- "/normal/42/37/",
- "normal-view",
- "",
- "",
- "normal-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/view_class/42/37/",
- "view-class",
- "",
- "",
- "view-class",
- views.view_class_instance,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/included/normal/42/37/",
- "inc-normal-view",
- "included_namespace_urls",
- "included_namespace_urls",
- "included_namespace_urls:inc-normal-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/included/view_class/42/37/",
- "inc-view-class",
- "included_namespace_urls",
- "included_namespace_urls",
- "included_namespace_urls:inc-view-class",
- views.view_class_instance,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- # Unnamed args are dropped if you have *any* kwargs in a pattern
- (
- "/mixed_args/42/37/",
- "mixed-args",
- "",
- "",
- "mixed-args",
- views.empty_view,
- (),
- {"extra": True, "arg2": "37"},
- ),
- (
- "/included/mixed_args/42/37/",
- "inc-mixed-args",
- "included_namespace_urls",
- "included_namespace_urls",
- "included_namespace_urls:inc-mixed-args",
- views.empty_view,
- (),
- {"arg2": "37"},
- ),
- (
- "/included/12/mixed_args/42/37/",
- "inc-mixed-args",
- "included_namespace_urls",
- "included_namespace_urls",
- "included_namespace_urls:inc-mixed-args",
- views.empty_view,
- (),
- {"arg2": "37"},
- ),
- # Unnamed views should have None as the url_name. Regression data for #21157.
- (
- "/unnamed/normal/42/37/",
- None,
- "",
- "",
- "urlpatterns_reverse.views.empty_view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/unnamed/view_class/42/37/",
- None,
- "",
- "",
- "urlpatterns_reverse.views.ViewClass",
- views.view_class_instance,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- # If you have no kwargs, you get an args list.
- (
- "/no_kwargs/42/37/",
- "no-kwargs",
- "",
- "",
- "no-kwargs",
- views.empty_view,
- ("42", "37"),
- {},
- ),
- (
- "/included/no_kwargs/42/37/",
- "inc-no-kwargs",
- "included_namespace_urls",
- "included_namespace_urls",
- "included_namespace_urls:inc-no-kwargs",
- views.empty_view,
- ("42", "37"),
- {},
- ),
- (
- "/included/12/no_kwargs/42/37/",
- "inc-no-kwargs",
- "included_namespace_urls",
- "included_namespace_urls",
- "included_namespace_urls:inc-no-kwargs",
- views.empty_view,
- ("12", "42", "37"),
- {},
- ),
- # Namespaces
- (
- "/test1/inner/42/37/",
- "urlobject-view",
- "testapp",
- "test-ns1",
- "test-ns1:urlobject-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/included/test3/inner/42/37/",
- "urlobject-view",
- "included_namespace_urls:testapp",
- "included_namespace_urls:test-ns3",
- "included_namespace_urls:test-ns3:urlobject-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/ns-included1/normal/42/37/",
- "inc-normal-view",
- "included_namespace_urls",
- "inc-ns1",
- "inc-ns1:inc-normal-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/included/test3/inner/42/37/",
- "urlobject-view",
- "included_namespace_urls:testapp",
- "included_namespace_urls:test-ns3",
- "included_namespace_urls:test-ns3:urlobject-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/default/inner/42/37/",
- "urlobject-view",
- "testapp",
- "testapp",
- "testapp:urlobject-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/other2/inner/42/37/",
- "urlobject-view",
- "nodefault",
- "other-ns2",
- "other-ns2:urlobject-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/other1/inner/42/37/",
- "urlobject-view",
- "nodefault",
- "other-ns1",
- "other-ns1:urlobject-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- # Nested namespaces
- (
- "/ns-included1/test3/inner/42/37/",
- "urlobject-view",
- "included_namespace_urls:testapp",
- "inc-ns1:test-ns3",
- "inc-ns1:test-ns3:urlobject-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/ns-included1/ns-included4/ns-included2/test3/inner/42/37/",
- "urlobject-view",
- "included_namespace_urls:namespace_urls:included_namespace_urls:testapp",
- "inc-ns1:inc-ns4:inc-ns2:test-ns3",
- "inc-ns1:inc-ns4:inc-ns2:test-ns3:urlobject-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/app-included/test3/inner/42/37/",
- "urlobject-view",
- "included_namespace_urls:testapp",
- "inc-app:test-ns3",
- "inc-app:test-ns3:urlobject-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- (
- "/app-included/ns-included4/ns-included2/test3/inner/42/37/",
- "urlobject-view",
- "included_namespace_urls:namespace_urls:included_namespace_urls:testapp",
- "inc-app:inc-ns4:inc-ns2:test-ns3",
- "inc-app:inc-ns4:inc-ns2:test-ns3:urlobject-view",
- views.empty_view,
- (),
- {"arg1": "42", "arg2": "37"},
- ),
- # Namespaces capturing variables
- (
- "/inc70/",
- "inner-nothing",
- "included_urls",
- "inc-ns5",
- "inc-ns5:inner-nothing",
- views.empty_view,
- (),
- {"outer": "70"},
- ),
- (
- "/inc78/extra/foobar/",
- "inner-extra",
- "included_urls",
- "inc-ns5",
- "inc-ns5:inner-extra",
- views.empty_view,
- (),
- {"outer": "78", "extra": "foobar"},
- ),
- )
- test_data = (
- ("places", "/places/3/", [3], {}),
- ("places", "/places/3/", ["3"], {}),
- ("places", NoReverseMatch, ["a"], {}),
- ("places", NoReverseMatch, [], {}),
- ("places?", "/place/", [], {}),
- ("places+", "/places/", [], {}),
- ("places*", "/place/", [], {}),
- ("places2?", "/", [], {}),
- ("places2+", "/places/", [], {}),
- ("places2*", "/", [], {}),
- ("places3", "/places/4/", [4], {}),
- ("places3", "/places/harlem/", ["harlem"], {}),
- ("places3", NoReverseMatch, ["harlem64"], {}),
- ("places4", "/places/3/", [], {"id": 3}),
- ("people", NoReverseMatch, [], {}),
- ("people", "/people/adrian/", ["adrian"], {}),
- ("people", "/people/adrian/", [], {"name": "adrian"}),
- ("people", NoReverseMatch, ["name with spaces"], {}),
- ("people", NoReverseMatch, [], {"name": "name with spaces"}),
- ("people2", "/people/name/", [], {}),
- ("people2a", "/people/name/fred/", ["fred"], {}),
- ("people_backref", "/people/nate-nate/", ["nate"], {}),
- ("people_backref", "/people/nate-nate/", [], {"name": "nate"}),
- ("optional", "/optional/fred/", [], {"name": "fred"}),
- ("optional", "/optional/fred/", ["fred"], {}),
- ("named_optional", "/optional/1/", [1], {}),
- ("named_optional", "/optional/1/", [], {"arg1": 1}),
- ("named_optional", "/optional/1/2/", [1, 2], {}),
- ("named_optional", "/optional/1/2/", [], {"arg1": 1, "arg2": 2}),
- ("named_optional_terminated", "/optional/1/", [1], {}),
- ("named_optional_terminated", "/optional/1/", [], {"arg1": 1}),
- ("named_optional_terminated", "/optional/1/2/", [1, 2], {}),
- ("named_optional_terminated", "/optional/1/2/", [], {"arg1": 1, "arg2": 2}),
- ("hardcoded", "/hardcoded/", [], {}),
- ("hardcoded2", "/hardcoded/doc.pdf", [], {}),
- ("people3", "/people/il/adrian/", [], {"state": "il", "name": "adrian"}),
- ("people3", NoReverseMatch, [], {"state": "il"}),
- ("people3", NoReverseMatch, [], {"name": "adrian"}),
- ("people4", NoReverseMatch, [], {"state": "il", "name": "adrian"}),
- ("people6", "/people/il/test/adrian/", ["il/test", "adrian"], {}),
- ("people6", "/people//adrian/", ["adrian"], {}),
- ("range", "/character_set/a/", [], {}),
- ("range2", "/character_set/x/", [], {}),
- ("price", "/price/$10/", ["10"], {}),
- ("price2", "/price/$10/", ["10"], {}),
- ("price3", "/price/$10/", ["10"], {}),
- (
- "product",
- "/product/chocolate+($2.00)/",
- [],
- {"price": "2.00", "product": "chocolate"},
- ),
- ("headlines", "/headlines/2007.5.21/", [], {"year": 2007, "month": 5, "day": 21}),
- (
- "windows",
- r"/windows_path/C:%5CDocuments%20and%20Settings%5Cspam/",
- [],
- {"drive_name": "C", "path": r"Documents and Settings\spam"},
- ),
- ("special", r"/special_chars/~@+%5C$*%7C/", [r"~@+\$*|"], {}),
- ("special", r"/special_chars/some%20resource/", [r"some resource"], {}),
- ("special", r"/special_chars/10%25%20complete/", [r"10% complete"], {}),
- ("special", r"/special_chars/some%20resource/", [], {"chars": r"some resource"}),
- ("special", r"/special_chars/10%25%20complete/", [], {"chars": r"10% complete"}),
- ("special", NoReverseMatch, [""], {}),
- ("mixed", "/john/0/", [], {"name": "john"}),
- ("repeats", "/repeats/a/", [], {}),
- ("repeats2", "/repeats/aa/", [], {}),
- ("repeats3", "/repeats/aa/", [], {}),
- ("test", "/test/1", [], {}),
- ("inner-nothing", "/outer/42/", [], {"outer": "42"}),
- ("inner-nothing", "/outer/42/", ["42"], {}),
- ("inner-nothing", NoReverseMatch, ["foo"], {}),
- ("inner-extra", "/outer/42/extra/inner/", [], {"extra": "inner", "outer": "42"}),
- ("inner-extra", "/outer/42/extra/inner/", ["42", "inner"], {}),
- ("inner-extra", NoReverseMatch, ["fred", "inner"], {}),
- ("inner-no-kwargs", "/outer-no-kwargs/42/inner-no-kwargs/1/", ["42", "1"], {}),
- ("disjunction", NoReverseMatch, ["foo"], {}),
- ("inner-disjunction", NoReverseMatch, ["10", "11"], {}),
- ("extra-places", "/e-places/10/", ["10"], {}),
- ("extra-people", "/e-people/fred/", ["fred"], {}),
- ("extra-people", "/e-people/fred/", [], {"name": "fred"}),
- ("part", "/part/one/", [], {"value": "one"}),
- ("part", "/prefix/xx/part/one/", [], {"value": "one", "prefix": "xx"}),
- ("part2", "/part2/one/", [], {"value": "one"}),
- ("part2", "/part2/", [], {}),
- ("part2", "/prefix/xx/part2/one/", [], {"value": "one", "prefix": "xx"}),
- ("part2", "/prefix/xx/part2/", [], {"prefix": "xx"}),
- # Tests for nested groups. Nested capturing groups will only work if you
- # *only* supply the correct outer group.
- ("nested-noncapture", "/nested/noncapture/opt", [], {"p": "opt"}),
- ("nested-capture", "/nested/capture/opt/", ["opt/"], {}),
- ("nested-capture", NoReverseMatch, [], {"p": "opt"}),
- ("nested-mixedcapture", "/nested/capture/mixed/opt", ["opt"], {}),
- ("nested-mixedcapture", NoReverseMatch, [], {"p": "opt"}),
- ("nested-namedcapture", "/nested/capture/named/opt/", [], {"outer": "opt/"}),
- ("nested-namedcapture", NoReverseMatch, [], {"outer": "opt/", "inner": "opt"}),
- ("nested-namedcapture", NoReverseMatch, [], {"inner": "opt"}),
- ("non_path_include", "/includes/non_path_include/", [], {}),
- # Tests for #13154
- ("defaults", "/defaults_view1/3/", [], {"arg1": 3, "arg2": 1}),
- ("defaults", "/defaults_view2/3/", [], {"arg1": 3, "arg2": 2}),
- ("defaults", NoReverseMatch, [], {"arg1": 3, "arg2": 3}),
- ("defaults", NoReverseMatch, [], {"arg2": 1}),
- # Security tests
- ("security", "/%2Fexample.com/security/", ["/example.com"], {}),
- )
- @override_settings(ROOT_URLCONF="urlpatterns_reverse.no_urls")
- class NoURLPatternsTests(SimpleTestCase):
- def test_no_urls_exception(self):
- """
- URLResolver should raise an exception when no urlpatterns exist.
- """
- resolver = URLResolver(RegexPattern(r"^$"), settings.ROOT_URLCONF)
- with self.assertRaisesMessage(
- ImproperlyConfigured,
- "The included URLconf 'urlpatterns_reverse.no_urls' does not "
- "appear to have any patterns in it. If you see the 'urlpatterns' "
- "variable with valid patterns in the file then the issue is "
- "probably caused by a circular import.",
- ):
- getattr(resolver, "url_patterns")
- @override_settings(ROOT_URLCONF="urlpatterns_reverse.urls")
- class URLPatternReverse(SimpleTestCase):
- def test_urlpattern_reverse(self):
- for name, expected, args, kwargs in test_data:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- try:
- got = reverse(name, args=args, kwargs=kwargs)
- except NoReverseMatch:
- self.assertEqual(NoReverseMatch, expected)
- else:
- self.assertEqual(got, expected)
- def test_reverse_none(self):
- # Reversing None should raise an error, not return the last un-named view.
- with self.assertRaises(NoReverseMatch):
- reverse(None)
- def test_mixing_args_and_kwargs(self):
- msg = "Don't mix *args and **kwargs in call to reverse()!"
- with self.assertRaisesMessage(ValueError, msg):
- reverse("name", args=["a"], kwargs={"b": "c"})
- @override_script_prefix("/{{invalid}}/")
- def test_prefix_braces(self):
- self.assertEqual(
- "/%7B%7Binvalid%7D%7D/includes/non_path_include/",
- reverse("non_path_include"),
- )
- def test_prefix_parenthesis(self):
- # Parentheses are allowed and should not cause errors or be escaped
- with override_script_prefix("/bogus)/"):
- self.assertEqual(
- "/bogus)/includes/non_path_include/", reverse("non_path_include")
- )
- with override_script_prefix("/(bogus)/"):
- self.assertEqual(
- "/(bogus)/includes/non_path_include/", reverse("non_path_include")
- )
- @override_script_prefix("/bump%20map/")
- def test_prefix_format_char(self):
- self.assertEqual(
- "/bump%2520map/includes/non_path_include/", reverse("non_path_include")
- )
- @override_script_prefix("/%7Eme/")
- def test_non_urlsafe_prefix_with_args(self):
- # Regression for #20022, adjusted for #24013 because ~ is an unreserved
- # character. Tests whether % is escaped.
- self.assertEqual("/%257Eme/places/1/", reverse("places", args=[1]))
- def test_patterns_reported(self):
- # Regression for #17076
- with self.assertRaisesMessage(
- NoReverseMatch, r"1 pattern(s) tried: ['people/(?P<name>\\w+)/$']"
- ):
- # this url exists, but requires an argument
- reverse("people", args=[])
- @override_script_prefix("/script:name/")
- def test_script_name_escaping(self):
- self.assertEqual(
- reverse("optional", args=["foo:bar"]), "/script:name/optional/foo:bar/"
- )
- def test_view_not_found_message(self):
- msg = (
- "Reverse for 'nonexistent-view' not found. 'nonexistent-view' "
- "is not a valid view function or pattern name."
- )
- with self.assertRaisesMessage(NoReverseMatch, msg):
- reverse("nonexistent-view")
- def test_no_args_message(self):
- msg = "Reverse for 'places' with no arguments not found. 1 pattern(s) tried:"
- with self.assertRaisesMessage(NoReverseMatch, msg):
- reverse("places")
- def test_illegal_args_message(self):
- msg = (
- "Reverse for 'places' with arguments '(1, 2)' not found. 1 pattern(s) "
- "tried:"
- )
- with self.assertRaisesMessage(NoReverseMatch, msg):
- reverse("places", args=(1, 2))
- def test_illegal_kwargs_message(self):
- msg = (
- "Reverse for 'places' with keyword arguments '{'arg1': 2}' not found. 1 "
- "pattern(s) tried:"
- )
- with self.assertRaisesMessage(NoReverseMatch, msg):
- reverse("places", kwargs={"arg1": 2})
- class ResolverTests(SimpleTestCase):
- def test_resolver_repr(self):
- """
- Test repr of URLResolver, especially when urlconf_name is a list
- (#17892).
- """
- # Pick a resolver from a namespaced URLconf
- resolver = get_resolver("urlpatterns_reverse.namespace_urls")
- sub_resolver = resolver.namespace_dict["test-ns1"][1]
- self.assertIn("<URLPattern list>", repr(sub_resolver))
- def test_reverse_lazy_object_coercion_by_resolve(self):
- """
- Verifies lazy object returned by reverse_lazy is coerced to
- text by resolve(). Previous to #21043, this would raise a TypeError.
- """
- urls = "urlpatterns_reverse.named_urls"
- proxy_url = reverse_lazy("named-url1", urlconf=urls)
- resolver = get_resolver(urls)
- resolver.resolve(proxy_url)
- def test_resolver_reverse(self):
- resolver = get_resolver("urlpatterns_reverse.named_urls")
- test_urls = [
- # (name, args, kwargs, expected)
- ("named-url1", (), {}, ""),
- ("named-url2", ("arg",), {}, "extra/arg/"),
- ("named-url2", (), {"extra": "arg"}, "extra/arg/"),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(resolver.reverse(name, *args, **kwargs), expected)
- def test_resolver_reverse_conflict(self):
- """
- URL pattern name arguments don't need to be unique. The last registered
- pattern takes precedence for conflicting names.
- """
- resolver = get_resolver("urlpatterns_reverse.named_urls_conflict")
- test_urls = [
- # (name, args, kwargs, expected)
- # Without arguments, the last URL in urlpatterns has precedence.
- ("name-conflict", (), {}, "conflict/"),
- # With an arg, the last URL in urlpatterns has precedence.
- ("name-conflict", ("arg",), {}, "conflict-last/arg/"),
- # With a kwarg, other URL patterns can be reversed.
- ("name-conflict", (), {"first": "arg"}, "conflict-first/arg/"),
- ("name-conflict", (), {"middle": "arg"}, "conflict-middle/arg/"),
- ("name-conflict", (), {"last": "arg"}, "conflict-last/arg/"),
- # The number and order of the arguments don't interfere with reversing.
- ("name-conflict", ("arg", "arg"), {}, "conflict/arg/arg/"),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(resolver.reverse(name, *args, **kwargs), expected)
- def test_non_regex(self):
- """
- A Resolver404 is raised if resolving doesn't meet the basic
- requirements of a path to match - i.e., at the very least, it matches
- the root pattern '^/'. Never return None from resolve() to prevent a
- TypeError from occurring later (#10834).
- """
- test_urls = ["", "a", "\\", "."]
- for path_ in test_urls:
- with self.subTest(path=path_):
- with self.assertRaises(Resolver404):
- resolve(path_)
- def test_404_tried_urls_have_names(self):
- """
- The list of URLs that come back from a Resolver404 exception contains
- a list in the right format for printing out in the DEBUG 404 page with
- both the patterns and URL names, if available.
- """
- urls = "urlpatterns_reverse.named_urls"
- # this list matches the expected URL types and names returned when
- # you try to resolve a nonexistent URL in the first level of included
- # URLs in named_urls.py (e.g., '/included/nonexistent-url')
- url_types_names = [
- [{"type": URLPattern, "name": "named-url1"}],
- [{"type": URLPattern, "name": "named-url2"}],
- [{"type": URLPattern, "name": None}],
- [{"type": URLResolver}, {"type": URLPattern, "name": "named-url3"}],
- [{"type": URLResolver}, {"type": URLPattern, "name": "named-url4"}],
- [{"type": URLResolver}, {"type": URLPattern, "name": None}],
- [{"type": URLResolver}, {"type": URLResolver}],
- ]
- with self.assertRaisesMessage(Resolver404, "tried") as cm:
- resolve("/included/nonexistent-url", urlconf=urls)
- e = cm.exception
- # make sure we at least matched the root ('/') url resolver:
- self.assertIn("tried", e.args[0])
- 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"])),
- )
- for tried, expected in zip(e.args[0]["tried"], url_types_names):
- for t, e in zip(tried, expected):
- with self.subTest(t):
- self.assertIsInstance(
- t, e["type"]
- ), "%s is not an instance of %s" % (t, e["type"])
- if "name" in e:
- if not e["name"]:
- self.assertIsNone(
- t.name, "Expected no URL name but found %s." % t.name
- )
- else:
- self.assertEqual(
- t.name,
- e["name"],
- 'Wrong URL name. Expected "%s", got "%s".'
- % (e["name"], t.name),
- )
- def test_namespaced_view_detail(self):
- resolver = get_resolver("urlpatterns_reverse.nested_urls")
- self.assertTrue(resolver._is_callback("urlpatterns_reverse.nested_urls.view1"))
- self.assertTrue(resolver._is_callback("urlpatterns_reverse.nested_urls.view2"))
- self.assertTrue(resolver._is_callback("urlpatterns_reverse.nested_urls.View3"))
- self.assertFalse(resolver._is_callback("urlpatterns_reverse.nested_urls.blub"))
- def test_view_detail_as_method(self):
- # Views which have a class name as part of their path.
- resolver = get_resolver("urlpatterns_reverse.method_view_urls")
- self.assertTrue(
- resolver._is_callback(
- "urlpatterns_reverse.method_view_urls.ViewContainer.method_view"
- )
- )
- self.assertTrue(
- resolver._is_callback(
- "urlpatterns_reverse.method_view_urls.ViewContainer.classmethod_view"
- )
- )
- def test_populate_concurrency(self):
- """
- URLResolver._populate() can be called concurrently, but not more
- than once per thread (#26888).
- """
- resolver = URLResolver(RegexPattern(r"^/"), "urlpatterns_reverse.urls")
- resolver._local.populating = True
- thread = threading.Thread(target=resolver._populate)
- thread.start()
- thread.join()
- self.assertNotEqual(resolver._reverse_dict, {})
- @override_settings(ROOT_URLCONF="urlpatterns_reverse.reverse_lazy_urls")
- class ReverseLazyTest(TestCase):
- def test_redirect_with_lazy_reverse(self):
- response = self.client.get("/redirect/")
- self.assertRedirects(response, "/redirected_to/", status_code=302)
- def test_user_permission_with_lazy_reverse(self):
- alfred = User.objects.create_user(
- "alfred", "alfred@example.com", password="testpw"
- )
- response = self.client.get("/login_required_view/")
- self.assertRedirects(
- response, "/login/?next=/login_required_view/", status_code=302
- )
- self.client.force_login(alfred)
- response = self.client.get("/login_required_view/")
- self.assertEqual(response.status_code, 200)
- def test_inserting_reverse_lazy_into_string(self):
- self.assertEqual(
- "Some URL: %s" % reverse_lazy("some-login-page"), "Some URL: /login/"
- )
- def test_build_absolute_uri(self):
- factory = RequestFactory()
- request = factory.get("/")
- self.assertEqual(
- request.build_absolute_uri(reverse_lazy("some-login-page")),
- "http://testserver/login/",
- )
- class ReverseLazySettingsTest(AdminScriptTestCase):
- """
- reverse_lazy can be used in settings without causing a circular
- import error.
- """
- def setUp(self):
- super().setUp()
- self.write_settings(
- "settings.py",
- extra=(
- "from django.urls import reverse_lazy\n"
- "LOGIN_URL = reverse_lazy('login')"
- ),
- )
- def test_lazy_in_settings(self):
- out, err = self.run_manage(["check"])
- self.assertNoOutput(err)
- @override_settings(ROOT_URLCONF="urlpatterns_reverse.urls")
- class ReverseShortcutTests(SimpleTestCase):
- def test_redirect_to_object(self):
- # We don't really need a model; just something with a get_absolute_url
- class FakeObj:
- def get_absolute_url(self):
- return "/hi-there/"
- res = redirect(FakeObj())
- self.assertIsInstance(res, HttpResponseRedirect)
- self.assertEqual(res.url, "/hi-there/")
- res = redirect(FakeObj(), permanent=True)
- self.assertIsInstance(res, HttpResponsePermanentRedirect)
- self.assertEqual(res.url, "/hi-there/")
- def test_redirect_to_view_name(self):
- res = redirect("hardcoded2")
- self.assertEqual(res.url, "/hardcoded/doc.pdf")
- res = redirect("places", 1)
- self.assertEqual(res.url, "/places/1/")
- res = redirect("headlines", year="2008", month="02", day="17")
- self.assertEqual(res.url, "/headlines/2008.02.17/")
- with self.assertRaises(NoReverseMatch):
- redirect("not-a-view")
- def test_redirect_to_url(self):
- res = redirect("/foo/")
- self.assertEqual(res.url, "/foo/")
- res = redirect("http://example.com/")
- self.assertEqual(res.url, "http://example.com/")
- # Assert that we can redirect using UTF-8 strings
- res = redirect("/æøå/abc/")
- self.assertEqual(res.url, "/%C3%A6%C3%B8%C3%A5/abc/")
- # Assert that no imports are attempted when dealing with a relative path
- # (previously, the below would resolve in a UnicodeEncodeError from __import__ )
- res = redirect("/æøå.abc/")
- self.assertEqual(res.url, "/%C3%A6%C3%B8%C3%A5.abc/")
- res = redirect("os.path")
- self.assertEqual(res.url, "os.path")
- def test_no_illegal_imports(self):
- # modules that are not listed in urlpatterns should not be importable
- redirect("urlpatterns_reverse.nonimported_module.view")
- self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules)
- def test_reverse_by_path_nested(self):
- # Views added to urlpatterns using include() should be reversible.
- from .views import nested_view
- self.assertEqual(reverse(nested_view), "/includes/nested_path/")
- def test_redirect_view_object(self):
- from .views import absolute_kwargs_view
- res = redirect(absolute_kwargs_view)
- self.assertEqual(res.url, "/absolute_arg_view/")
- with self.assertRaises(NoReverseMatch):
- redirect(absolute_kwargs_view, wrong_argument=None)
- @override_settings(ROOT_URLCONF="urlpatterns_reverse.namespace_urls")
- class NamespaceTests(SimpleTestCase):
- def test_ambiguous_object(self):
- """
- Names deployed via dynamic URL objects that require namespaces can't
- be resolved.
- """
- test_urls = [
- ("urlobject-view", [], {}),
- ("urlobject-view", [37, 42], {}),
- ("urlobject-view", [], {"arg1": 42, "arg2": 37}),
- ]
- for name, args, kwargs in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- with self.assertRaises(NoReverseMatch):
- reverse(name, args=args, kwargs=kwargs)
- def test_ambiguous_urlpattern(self):
- """
- Names deployed via dynamic URL objects that require namespaces can't
- be resolved.
- """
- test_urls = [
- ("inner-nothing", [], {}),
- ("inner-nothing", [37, 42], {}),
- ("inner-nothing", [], {"arg1": 42, "arg2": 37}),
- ]
- for name, args, kwargs in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- with self.assertRaises(NoReverseMatch):
- reverse(name, args=args, kwargs=kwargs)
- def test_non_existent_namespace(self):
- """Nonexistent namespaces raise errors."""
- test_urls = [
- "blahblah:urlobject-view",
- "test-ns1:blahblah:urlobject-view",
- ]
- for name in test_urls:
- with self.subTest(name=name):
- with self.assertRaises(NoReverseMatch):
- reverse(name)
- def test_normal_name(self):
- """Normal lookups work as expected."""
- test_urls = [
- ("normal-view", [], {}, "/normal/"),
- ("normal-view", [37, 42], {}, "/normal/37/42/"),
- ("normal-view", [], {"arg1": 42, "arg2": 37}, "/normal/42/37/"),
- ("special-view", [], {}, "/+%5C$*/"),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_simple_included_name(self):
- """Normal lookups work on names included from other patterns."""
- test_urls = [
- ("included_namespace_urls:inc-normal-view", [], {}, "/included/normal/"),
- (
- "included_namespace_urls:inc-normal-view",
- [37, 42],
- {},
- "/included/normal/37/42/",
- ),
- (
- "included_namespace_urls:inc-normal-view",
- [],
- {"arg1": 42, "arg2": 37},
- "/included/normal/42/37/",
- ),
- ("included_namespace_urls:inc-special-view", [], {}, "/included/+%5C$*/"),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_namespace_object(self):
- """Dynamic URL objects can be found using a namespace."""
- test_urls = [
- ("test-ns1:urlobject-view", [], {}, "/test1/inner/"),
- ("test-ns1:urlobject-view", [37, 42], {}, "/test1/inner/37/42/"),
- (
- "test-ns1:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- "/test1/inner/42/37/",
- ),
- ("test-ns1:urlobject-special-view", [], {}, "/test1/inner/+%5C$*/"),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_app_object(self):
- """
- Dynamic URL objects can return a (pattern, app_name) 2-tuple, and
- include() can set the namespace.
- """
- test_urls = [
- ("new-ns1:urlobject-view", [], {}, "/newapp1/inner/"),
- ("new-ns1:urlobject-view", [37, 42], {}, "/newapp1/inner/37/42/"),
- (
- "new-ns1:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- "/newapp1/inner/42/37/",
- ),
- ("new-ns1:urlobject-special-view", [], {}, "/newapp1/inner/+%5C$*/"),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_app_object_default_namespace(self):
- """
- Namespace defaults to app_name when including a (pattern, app_name)
- 2-tuple.
- """
- test_urls = [
- ("newapp:urlobject-view", [], {}, "/new-default/inner/"),
- ("newapp:urlobject-view", [37, 42], {}, "/new-default/inner/37/42/"),
- (
- "newapp:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- "/new-default/inner/42/37/",
- ),
- ("newapp:urlobject-special-view", [], {}, "/new-default/inner/+%5C$*/"),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_embedded_namespace_object(self):
- """Namespaces can be installed anywhere in the URL pattern tree."""
- test_urls = [
- (
- "included_namespace_urls:test-ns3:urlobject-view",
- [],
- {},
- "/included/test3/inner/",
- ),
- (
- "included_namespace_urls:test-ns3:urlobject-view",
- [37, 42],
- {},
- "/included/test3/inner/37/42/",
- ),
- (
- "included_namespace_urls:test-ns3:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- "/included/test3/inner/42/37/",
- ),
- (
- "included_namespace_urls:test-ns3:urlobject-special-view",
- [],
- {},
- "/included/test3/inner/+%5C$*/",
- ),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_namespace_pattern(self):
- """Namespaces can be applied to include()'d urlpatterns."""
- test_urls = [
- ("inc-ns1:inc-normal-view", [], {}, "/ns-included1/normal/"),
- ("inc-ns1:inc-normal-view", [37, 42], {}, "/ns-included1/normal/37/42/"),
- (
- "inc-ns1:inc-normal-view",
- [],
- {"arg1": 42, "arg2": 37},
- "/ns-included1/normal/42/37/",
- ),
- ("inc-ns1:inc-special-view", [], {}, "/ns-included1/+%5C$*/"),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_app_name_pattern(self):
- """
- Namespaces can be applied to include()'d urlpatterns that set an
- app_name attribute.
- """
- test_urls = [
- ("app-ns1:inc-normal-view", [], {}, "/app-included1/normal/"),
- ("app-ns1:inc-normal-view", [37, 42], {}, "/app-included1/normal/37/42/"),
- (
- "app-ns1:inc-normal-view",
- [],
- {"arg1": 42, "arg2": 37},
- "/app-included1/normal/42/37/",
- ),
- ("app-ns1:inc-special-view", [], {}, "/app-included1/+%5C$*/"),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_namespace_pattern_with_variable_prefix(self):
- """
- Using include() with namespaces when there is a regex variable in front
- of it.
- """
- test_urls = [
- ("inc-outer:inc-normal-view", [], {"outer": 42}, "/ns-outer/42/normal/"),
- ("inc-outer:inc-normal-view", [42], {}, "/ns-outer/42/normal/"),
- (
- "inc-outer:inc-normal-view",
- [],
- {"arg1": 37, "arg2": 4, "outer": 42},
- "/ns-outer/42/normal/37/4/",
- ),
- ("inc-outer:inc-normal-view", [42, 37, 4], {}, "/ns-outer/42/normal/37/4/"),
- ("inc-outer:inc-special-view", [], {"outer": 42}, "/ns-outer/42/+%5C$*/"),
- ("inc-outer:inc-special-view", [42], {}, "/ns-outer/42/+%5C$*/"),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_multiple_namespace_pattern(self):
- """Namespaces can be embedded."""
- test_urls = [
- ("inc-ns1:test-ns3:urlobject-view", [], {}, "/ns-included1/test3/inner/"),
- (
- "inc-ns1:test-ns3:urlobject-view",
- [37, 42],
- {},
- "/ns-included1/test3/inner/37/42/",
- ),
- (
- "inc-ns1:test-ns3:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- "/ns-included1/test3/inner/42/37/",
- ),
- (
- "inc-ns1:test-ns3:urlobject-special-view",
- [],
- {},
- "/ns-included1/test3/inner/+%5C$*/",
- ),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_nested_namespace_pattern(self):
- """Namespaces can be nested."""
- test_urls = [
- (
- "inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view",
- [],
- {},
- "/ns-included1/ns-included4/ns-included1/test3/inner/",
- ),
- (
- "inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view",
- [37, 42],
- {},
- "/ns-included1/ns-included4/ns-included1/test3/inner/37/42/",
- ),
- (
- "inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- "/ns-included1/ns-included4/ns-included1/test3/inner/42/37/",
- ),
- (
- "inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-special-view",
- [],
- {},
- "/ns-included1/ns-included4/ns-included1/test3/inner/+%5C$*/",
- ),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_app_lookup_object(self):
- """A default application namespace can be used for lookup."""
- test_urls = [
- ("testapp:urlobject-view", [], {}, "/default/inner/"),
- ("testapp:urlobject-view", [37, 42], {}, "/default/inner/37/42/"),
- (
- "testapp:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- "/default/inner/42/37/",
- ),
- ("testapp:urlobject-special-view", [], {}, "/default/inner/+%5C$*/"),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_app_lookup_object_with_default(self):
- """A default application namespace is sensitive to the current app."""
- test_urls = [
- ("testapp:urlobject-view", [], {}, "test-ns3", "/default/inner/"),
- (
- "testapp:urlobject-view",
- [37, 42],
- {},
- "test-ns3",
- "/default/inner/37/42/",
- ),
- (
- "testapp:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- "test-ns3",
- "/default/inner/42/37/",
- ),
- (
- "testapp:urlobject-special-view",
- [],
- {},
- "test-ns3",
- "/default/inner/+%5C$*/",
- ),
- ]
- for name, args, kwargs, current_app, expected in test_urls:
- with self.subTest(
- name=name, args=args, kwargs=kwargs, current_app=current_app
- ):
- self.assertEqual(
- reverse(name, args=args, kwargs=kwargs, current_app=current_app),
- expected,
- )
- def test_app_lookup_object_without_default(self):
- """
- An application namespace without a default is sensitive to the current
- app.
- """
- test_urls = [
- ("nodefault:urlobject-view", [], {}, None, "/other2/inner/"),
- ("nodefault:urlobject-view", [37, 42], {}, None, "/other2/inner/37/42/"),
- (
- "nodefault:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- None,
- "/other2/inner/42/37/",
- ),
- ("nodefault:urlobject-special-view", [], {}, None, "/other2/inner/+%5C$*/"),
- ("nodefault:urlobject-view", [], {}, "other-ns1", "/other1/inner/"),
- (
- "nodefault:urlobject-view",
- [37, 42],
- {},
- "other-ns1",
- "/other1/inner/37/42/",
- ),
- (
- "nodefault:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- "other-ns1",
- "/other1/inner/42/37/",
- ),
- (
- "nodefault:urlobject-special-view",
- [],
- {},
- "other-ns1",
- "/other1/inner/+%5C$*/",
- ),
- ]
- for name, args, kwargs, current_app, expected in test_urls:
- with self.subTest(
- name=name, args=args, kwargs=kwargs, current_app=current_app
- ):
- self.assertEqual(
- reverse(name, args=args, kwargs=kwargs, current_app=current_app),
- expected,
- )
- def test_special_chars_namespace(self):
- test_urls = [
- (
- "special:included_namespace_urls:inc-normal-view",
- [],
- {},
- "/+%5C$*/included/normal/",
- ),
- (
- "special:included_namespace_urls:inc-normal-view",
- [37, 42],
- {},
- "/+%5C$*/included/normal/37/42/",
- ),
- (
- "special:included_namespace_urls:inc-normal-view",
- [],
- {"arg1": 42, "arg2": 37},
- "/+%5C$*/included/normal/42/37/",
- ),
- (
- "special:included_namespace_urls:inc-special-view",
- [],
- {},
- "/+%5C$*/included/+%5C$*/",
- ),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_namespaces_with_variables(self):
- """Namespace prefixes can capture variables."""
- test_urls = [
- ("inc-ns5:inner-nothing", [], {"outer": "70"}, "/inc70/"),
- (
- "inc-ns5:inner-extra",
- [],
- {"extra": "foobar", "outer": "78"},
- "/inc78/extra/foobar/",
- ),
- ("inc-ns5:inner-nothing", ["70"], {}, "/inc70/"),
- ("inc-ns5:inner-extra", ["78", "foobar"], {}, "/inc78/extra/foobar/"),
- ]
- for name, args, kwargs, expected in test_urls:
- with self.subTest(name=name, args=args, kwargs=kwargs):
- self.assertEqual(reverse(name, args=args, kwargs=kwargs), expected)
- def test_nested_app_lookup(self):
- """
- A nested current_app should be split in individual namespaces (#24904).
- """
- test_urls = [
- (
- "inc-ns1:testapp:urlobject-view",
- [],
- {},
- None,
- "/ns-included1/test4/inner/",
- ),
- (
- "inc-ns1:testapp:urlobject-view",
- [37, 42],
- {},
- None,
- "/ns-included1/test4/inner/37/42/",
- ),
- (
- "inc-ns1:testapp:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- None,
- "/ns-included1/test4/inner/42/37/",
- ),
- (
- "inc-ns1:testapp:urlobject-special-view",
- [],
- {},
- None,
- "/ns-included1/test4/inner/+%5C$*/",
- ),
- (
- "inc-ns1:testapp:urlobject-view",
- [],
- {},
- "inc-ns1:test-ns3",
- "/ns-included1/test3/inner/",
- ),
- (
- "inc-ns1:testapp:urlobject-view",
- [37, 42],
- {},
- "inc-ns1:test-ns3",
- "/ns-included1/test3/inner/37/42/",
- ),
- (
- "inc-ns1:testapp:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- "inc-ns1:test-ns3",
- "/ns-included1/test3/inner/42/37/",
- ),
- (
- "inc-ns1:testapp:urlobject-special-view",
- [],
- {},
- "inc-ns1:test-ns3",
- "/ns-included1/test3/inner/+%5C$*/",
- ),
- ]
- for name, args, kwargs, current_app, expected in test_urls:
- with self.subTest(
- name=name, args=args, kwargs=kwargs, current_app=current_app
- ):
- self.assertEqual(
- reverse(name, args=args, kwargs=kwargs, current_app=current_app),
- expected,
- )
- def test_current_app_no_partial_match(self):
- """current_app shouldn't be used unless it matches the whole path."""
- test_urls = [
- (
- "inc-ns1:testapp:urlobject-view",
- [],
- {},
- "nonexistent:test-ns3",
- "/ns-included1/test4/inner/",
- ),
- (
- "inc-ns1:testapp:urlobject-view",
- [37, 42],
- {},
- "nonexistent:test-ns3",
- "/ns-included1/test4/inner/37/42/",
- ),
- (
- "inc-ns1:testapp:urlobject-view",
- [],
- {"arg1": 42, "arg2": 37},
- "nonexistent:test-ns3",
- "/ns-included1/test4/inner/42/37/",
- ),
- (
- "inc-ns1:testapp:urlobject-special-view",
- [],
- {},
- "nonexistent:test-ns3",
- "/ns-included1/test4/inner/+%5C$*/",
- ),
- ]
- for name, args, kwargs, current_app, expected in test_urls:
- with self.subTest(
- name=name, args=args, kwargs=kwargs, current_app=current_app
- ):
- self.assertEqual(
- reverse(name, args=args, kwargs=kwargs, current_app=current_app),
- expected,
- )
- @override_settings(ROOT_URLCONF=urlconf_outer.__name__)
- class RequestURLconfTests(SimpleTestCase):
- def test_urlconf(self):
- response = self.client.get("/test/me/")
- self.assertEqual(response.status_code, 200)
- self.assertEqual(
- response.content, b"outer:/test/me/,inner:/inner_urlconf/second_test/"
- )
- response = self.client.get("/inner_urlconf/second_test/")
- self.assertEqual(response.status_code, 200)
- response = self.client.get("/second_test/")
- self.assertEqual(response.status_code, 404)
- @override_settings(
- MIDDLEWARE=[
- "%s.ChangeURLconfMiddleware" % middleware.__name__,
- ]
- )
- def test_urlconf_overridden(self):
- response = self.client.get("/test/me/")
- self.assertEqual(response.status_code, 404)
- response = self.client.get("/inner_urlconf/second_test/")
- self.assertEqual(response.status_code, 404)
- response = self.client.get("/second_test/")
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, b"outer:,inner:/second_test/")
- @override_settings(
- MIDDLEWARE=[
- "%s.NullChangeURLconfMiddleware" % middleware.__name__,
- ]
- )
- def test_urlconf_overridden_with_null(self):
- """
- Overriding request.urlconf with None will fall back to the default
- URLconf.
- """
- response = self.client.get("/test/me/")
- self.assertEqual(response.status_code, 200)
- self.assertEqual(
- response.content, b"outer:/test/me/,inner:/inner_urlconf/second_test/"
- )
- response = self.client.get("/inner_urlconf/second_test/")
- self.assertEqual(response.status_code, 200)
- response = self.client.get("/second_test/")
- self.assertEqual(response.status_code, 404)
- @override_settings(
- MIDDLEWARE=[
- "%s.ChangeURLconfMiddleware" % middleware.__name__,
- "%s.ReverseInnerInResponseMiddleware" % middleware.__name__,
- ]
- )
- def test_reverse_inner_in_response_middleware(self):
- """
- Test reversing an URL from the *overridden* URLconf from inside
- a response middleware.
- """
- response = self.client.get("/second_test/")
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, b"/second_test/")
- @override_settings(
- MIDDLEWARE=[
- "%s.ChangeURLconfMiddleware" % middleware.__name__,
- "%s.ReverseOuterInResponseMiddleware" % middleware.__name__,
- ]
- )
- def test_reverse_outer_in_response_middleware(self):
- """
- Test reversing an URL from the *default* URLconf from inside
- a response middleware.
- """
- msg = (
- "Reverse for 'outer' not found. 'outer' is not a valid view "
- "function or pattern name."
- )
- with self.assertRaisesMessage(NoReverseMatch, msg):
- self.client.get("/second_test/")
- @override_settings(
- MIDDLEWARE=[
- "%s.ChangeURLconfMiddleware" % middleware.__name__,
- "%s.ReverseInnerInStreaming" % middleware.__name__,
- ]
- )
- def test_reverse_inner_in_streaming(self):
- """
- Test reversing an URL from the *overridden* URLconf from inside
- a streaming response.
- """
- response = self.client.get("/second_test/")
- self.assertEqual(response.status_code, 200)
- self.assertEqual(b"".join(response), b"/second_test/")
- @override_settings(
- MIDDLEWARE=[
- "%s.ChangeURLconfMiddleware" % middleware.__name__,
- "%s.ReverseOuterInStreaming" % middleware.__name__,
- ]
- )
- def test_reverse_outer_in_streaming(self):
- """
- Test reversing an URL from the *default* URLconf from inside
- a streaming response.
- """
- message = "Reverse for 'outer' not found."
- with self.assertRaisesMessage(NoReverseMatch, message):
- self.client.get("/second_test/")
- b"".join(self.client.get("/second_test/"))
- def test_urlconf_is_reset_after_request(self):
- """The URLconf is reset after each request."""
- self.assertIsNone(get_urlconf())
- with override_settings(
- MIDDLEWARE=["%s.ChangeURLconfMiddleware" % middleware.__name__]
- ):
- self.client.get(reverse("inner"))
- self.assertIsNone(get_urlconf())
- class ErrorHandlerResolutionTests(SimpleTestCase):
- """Tests for handler400, handler404 and handler500"""
- def setUp(self):
- urlconf = "urlpatterns_reverse.urls_error_handlers"
- urlconf_callables = "urlpatterns_reverse.urls_error_handlers_callables"
- self.resolver = URLResolver(RegexPattern(r"^$"), urlconf)
- self.callable_resolver = URLResolver(RegexPattern(r"^$"), urlconf_callables)
- def test_named_handlers(self):
- for code in [400, 404, 500]:
- with self.subTest(code=code):
- self.assertEqual(self.resolver.resolve_error_handler(code), empty_view)
- def test_callable_handlers(self):
- for code in [400, 404, 500]:
- with self.subTest(code=code):
- self.assertEqual(
- self.callable_resolver.resolve_error_handler(code), empty_view
- )
- @override_settings(ROOT_URLCONF="urlpatterns_reverse.urls_without_handlers")
- class DefaultErrorHandlerTests(SimpleTestCase):
- def test_default_handler(self):
- "If the urls.py doesn't specify handlers, the defaults are used"
- response = self.client.get("/test/")
- self.assertEqual(response.status_code, 404)
- msg = "I don't think I'm getting good value for this view"
- with self.assertRaisesMessage(ValueError, msg):
- self.client.get("/bad_view/")
- @override_settings(ROOT_URLCONF=None)
- class NoRootUrlConfTests(SimpleTestCase):
- """Tests for handler404 and handler500 if ROOT_URLCONF is None"""
- def test_no_handler_exception(self):
- msg = (
- "The included URLconf 'None' does not appear to have any patterns "
- "in it. If you see the 'urlpatterns' variable with valid patterns "
- "in the file then the issue is probably caused by a circular "
- "import."
- )
- with self.assertRaisesMessage(ImproperlyConfigured, msg):
- self.client.get("/test/me/")
- @override_settings(ROOT_URLCONF="urlpatterns_reverse.namespace_urls")
- class ResolverMatchTests(SimpleTestCase):
- def test_urlpattern_resolve(self):
- for (
- path_,
- url_name,
- app_name,
- namespace,
- view_name,
- func,
- args,
- kwargs,
- ) in resolve_test_data:
- with self.subTest(path=path_):
- # Legacy support for extracting "function, args, kwargs".
- match_func, match_args, match_kwargs = resolve(path_)
- self.assertEqual(match_func, func)
- self.assertEqual(match_args, args)
- self.assertEqual(match_kwargs, kwargs)
- # ResolverMatch capabilities.
- match = resolve(path_)
- self.assertEqual(match.__class__, ResolverMatch)
- self.assertEqual(match.url_name, url_name)
- self.assertEqual(match.app_name, app_name)
- self.assertEqual(match.namespace, namespace)
- self.assertEqual(match.view_name, view_name)
- self.assertEqual(match.func, func)
- self.assertEqual(match.args, args)
- self.assertEqual(match.kwargs, kwargs)
- # and for legacy purposes:
- self.assertEqual(match[0], func)
- self.assertEqual(match[1], args)
- self.assertEqual(match[2], kwargs)
- def test_resolver_match_on_request(self):
- response = self.client.get("/resolver_match/")
- resolver_match = response.resolver_match
- self.assertEqual(resolver_match.url_name, "test-resolver-match")
- def test_resolver_match_on_request_before_resolution(self):
- request = HttpRequest()
- self.assertIsNone(request.resolver_match)
- def test_repr(self):
- self.assertEqual(
- repr(resolve("/no_kwargs/42/37/")),
- "ResolverMatch(func=urlpatterns_reverse.views.empty_view, "
- "args=('42', '37'), kwargs={}, url_name='no-kwargs', app_names=[], "
- "namespaces=[], route='^no_kwargs/([0-9]+)/([0-9]+)/$')",
- )
- def test_repr_extra_kwargs(self):
- self.assertEqual(
- repr(resolve("/mixed_args/1986/11/")),
- "ResolverMatch(func=urlpatterns_reverse.views.empty_view, args=(), "
- "kwargs={'arg2': '11', 'extra': True}, url_name='mixed-args', "
- "app_names=[], namespaces=[], "
- "route='^mixed_args/([0-9]+)/(?P<arg2>[0-9]+)/$', "
- "captured_kwargs={'arg2': '11'}, extra_kwargs={'extra': True})",
- )
- @override_settings(ROOT_URLCONF="urlpatterns_reverse.reverse_lazy_urls")
- def test_classbased_repr(self):
- self.assertEqual(
- repr(resolve("/redirect/")),
- "ResolverMatch(func=urlpatterns_reverse.views.LazyRedirectView, "
- "args=(), kwargs={}, url_name=None, app_names=[], "
- "namespaces=[], route='redirect/')",
- )
- @override_settings(ROOT_URLCONF="urlpatterns_reverse.urls")
- def test_repr_functools_partial(self):
- tests = [
- ("partial", "template.html"),
- ("partial_nested", "nested_partial.html"),
- ("partial_wrapped", "template.html"),
- ]
- for name, template_name in tests:
- with self.subTest(name=name):
- func = (
- f"functools.partial({views.empty_view!r}, "
- f"template_name='{template_name}')"
- )
- self.assertEqual(
- repr(resolve(f"/{name}/")),
- f"ResolverMatch(func={func}, args=(), kwargs={{}}, "
- f"url_name='{name}', app_names=[], namespaces=[], "
- f"route='{name}/')",
- )
- @override_settings(ROOT_URLCONF="urlpatterns.path_urls")
- def test_pickling(self):
- msg = "Cannot pickle ResolverMatch."
- with self.assertRaisesMessage(pickle.PicklingError, msg):
- pickle.dumps(resolve("/users/"))
- @override_settings(ROOT_URLCONF="urlpatterns_reverse.erroneous_urls")
- class ErroneousViewTests(SimpleTestCase):
- def test_noncallable_view(self):
- # View is not a callable (explicit import; arbitrary Python object)
- with self.assertRaisesMessage(TypeError, "view must be a callable"):
- path("uncallable-object/", views.uncallable)
- def test_invalid_regex(self):
- # Regex contains an error (refs #6170)
- msg = '(regex_error/$" is not a valid regular expression'
- with self.assertRaisesMessage(ImproperlyConfigured, msg):
- reverse(views.empty_view)
- class ViewLoadingTests(SimpleTestCase):
- def test_view_loading(self):
- self.assertEqual(
- get_callable("urlpatterns_reverse.views.empty_view"), empty_view
- )
- self.assertEqual(get_callable(empty_view), empty_view)
- def test_view_does_not_exist(self):
- msg = "View does not exist in module urlpatterns_reverse.views."
- with self.assertRaisesMessage(ViewDoesNotExist, msg):
- get_callable("urlpatterns_reverse.views.i_should_not_exist")
- def test_attributeerror_not_hidden(self):
- msg = "I am here to confuse django.urls.get_callable"
- with self.assertRaisesMessage(AttributeError, msg):
- get_callable("urlpatterns_reverse.views_broken.i_am_broken")
- def test_non_string_value(self):
- msg = "'1' is not a callable or a dot-notation path"
- with self.assertRaisesMessage(ViewDoesNotExist, msg):
- get_callable(1)
- def test_string_without_dot(self):
- msg = "Could not import 'test'. The path must be fully qualified."
- with self.assertRaisesMessage(ImportError, msg):
- get_callable("test")
- def test_module_does_not_exist(self):
- with self.assertRaisesMessage(ImportError, "No module named 'foo'"):
- get_callable("foo.bar")
- def test_parent_module_does_not_exist(self):
- msg = "Parent module urlpatterns_reverse.foo does not exist."
- with self.assertRaisesMessage(ViewDoesNotExist, msg):
- get_callable("urlpatterns_reverse.foo.bar")
- def test_not_callable(self):
- msg = (
- "Could not import 'urlpatterns_reverse.tests.resolve_test_data'. "
- "View is not callable."
- )
- with self.assertRaisesMessage(ViewDoesNotExist, msg):
- get_callable("urlpatterns_reverse.tests.resolve_test_data")
- class IncludeTests(SimpleTestCase):
- url_patterns = [
- path("inner/", views.empty_view, name="urlobject-view"),
- re_path(
- r"^inner/(?P<arg1>[0-9]+)/(?P<arg2>[0-9]+)/$",
- views.empty_view,
- name="urlobject-view",
- ),
- re_path(r"^inner/\+\\\$\*/$", views.empty_view, name="urlobject-special-view"),
- ]
- app_urls = URLObject("inc-app")
- def test_include_urls(self):
- self.assertEqual(include(self.url_patterns), (self.url_patterns, None, None))
- def test_include_namespace(self):
- msg = (
- "Specifying a namespace in include() without providing an "
- "app_name is not supported."
- )
- with self.assertRaisesMessage(ImproperlyConfigured, msg):
- include(self.url_patterns, "namespace")
- def test_include_4_tuple(self):
- msg = "Passing a 4-tuple to include() is not supported."
- with self.assertRaisesMessage(ImproperlyConfigured, msg):
- include((self.url_patterns, "app_name", "namespace", "blah"))
- def test_include_3_tuple(self):
- msg = "Passing a 3-tuple to include() is not supported."
- with self.assertRaisesMessage(ImproperlyConfigured, msg):
- include((self.url_patterns, "app_name", "namespace"))
- def test_include_3_tuple_namespace(self):
- msg = (
- "Cannot override the namespace for a dynamic module that provides a "
- "namespace."
- )
- with self.assertRaisesMessage(ImproperlyConfigured, msg):
- include((self.url_patterns, "app_name", "namespace"), "namespace")
- def test_include_2_tuple(self):
- self.assertEqual(
- include((self.url_patterns, "app_name")),
- (self.url_patterns, "app_name", "app_name"),
- )
- def test_include_2_tuple_namespace(self):
- self.assertEqual(
- include((self.url_patterns, "app_name"), namespace="namespace"),
- (self.url_patterns, "app_name", "namespace"),
- )
- def test_include_app_name(self):
- self.assertEqual(include(self.app_urls), (self.app_urls, "inc-app", "inc-app"))
- def test_include_app_name_namespace(self):
- self.assertEqual(
- include(self.app_urls, "namespace"), (self.app_urls, "inc-app", "namespace")
- )
- @override_settings(ROOT_URLCONF="urlpatterns_reverse.urls")
- class LookaheadTests(SimpleTestCase):
- def test_valid_resolve(self):
- test_urls = [
- "/lookahead-/a-city/",
- "/lookbehind-/a-city/",
- "/lookahead+/a-city/",
- "/lookbehind+/a-city/",
- ]
- for test_url in test_urls:
- with self.subTest(url=test_url):
- self.assertEqual(resolve(test_url).kwargs, {"city": "a-city"})
- def test_invalid_resolve(self):
- test_urls = [
- "/lookahead-/not-a-city/",
- "/lookbehind-/not-a-city/",
- "/lookahead+/other-city/",
- "/lookbehind+/other-city/",
- ]
- for test_url in test_urls:
- with self.subTest(url=test_url):
- with self.assertRaises(Resolver404):
- resolve(test_url)
- def test_valid_reverse(self):
- test_urls = [
- ("lookahead-positive", {"city": "a-city"}, "/lookahead+/a-city/"),
- ("lookahead-negative", {"city": "a-city"}, "/lookahead-/a-city/"),
- ("lookbehind-positive", {"city": "a-city"}, "/lookbehind+/a-city/"),
- ("lookbehind-negative", {"city": "a-city"}, "/lookbehind-/a-city/"),
- ]
- for name, kwargs, expected in test_urls:
- with self.subTest(name=name, kwargs=kwargs):
- self.assertEqual(reverse(name, kwargs=kwargs), expected)
- def test_invalid_reverse(self):
- test_urls = [
- ("lookahead-positive", {"city": "other-city"}),
- ("lookahead-negative", {"city": "not-a-city"}),
- ("lookbehind-positive", {"city": "other-city"}),
- ("lookbehind-negative", {"city": "not-a-city"}),
- ]
- for name, kwargs in test_urls:
- with self.subTest(name=name, kwargs=kwargs):
- with self.assertRaises(NoReverseMatch):
- reverse(name, kwargs=kwargs)
- @override_settings(ROOT_URLCONF="urlpatterns_reverse.urls")
- class ReverseResolvedTests(SimpleTestCase):
- def test_rereverse(self):
- match = resolve("/resolved/12/")
- self.assertEqual(
- reverse(match.url_name, args=match.args, kwargs=match.kwargs),
- "/resolved/12/",
- )
- match = resolve("/resolved-overridden/12/url/")
- self.assertEqual(
- reverse(match.url_name, args=match.args, kwargs=match.captured_kwargs),
- "/resolved-overridden/12/url/",
- )
|