debug.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. import functools
  2. import inspect
  3. import itertools
  4. import re
  5. import sys
  6. import types
  7. import warnings
  8. from pathlib import Path
  9. from django.conf import settings
  10. from django.http import Http404, HttpResponse, HttpResponseNotFound
  11. from django.template import Context, Engine, TemplateDoesNotExist
  12. from django.template.defaultfilters import pprint
  13. from django.urls import resolve
  14. from django.utils import timezone
  15. from django.utils.datastructures import MultiValueDict
  16. from django.utils.encoding import force_str
  17. from django.utils.module_loading import import_string
  18. from django.utils.regex_helper import _lazy_re_compile
  19. from django.utils.version import PY311, get_docs_version
  20. from django.views.decorators.debug import coroutine_functions_to_sensitive_variables
  21. # Minimal Django templates engine to render the error templates
  22. # regardless of the project's TEMPLATES setting. Templates are
  23. # read directly from the filesystem so that the error handler
  24. # works even if the template loader is broken.
  25. DEBUG_ENGINE = Engine(
  26. debug=True,
  27. libraries={"i18n": "django.templatetags.i18n"},
  28. )
  29. def builtin_template_path(name):
  30. """
  31. Return a path to a builtin template.
  32. Avoid calling this function at the module level or in a class-definition
  33. because __file__ may not exist, e.g. in frozen environments.
  34. """
  35. return Path(__file__).parent / "templates" / name
  36. class ExceptionCycleWarning(UserWarning):
  37. pass
  38. class CallableSettingWrapper:
  39. """
  40. Object to wrap callable appearing in settings.
  41. * Not to call in the debug page (#21345).
  42. * Not to break the debug page if the callable forbidding to set attributes
  43. (#23070).
  44. """
  45. def __init__(self, callable_setting):
  46. self._wrapped = callable_setting
  47. def __repr__(self):
  48. return repr(self._wrapped)
  49. def technical_500_response(request, exc_type, exc_value, tb, status_code=500):
  50. """
  51. Create a technical server error response. The last three arguments are
  52. the values returned from sys.exc_info() and friends.
  53. """
  54. reporter = get_exception_reporter_class(request)(request, exc_type, exc_value, tb)
  55. if request.accepts("text/html"):
  56. html = reporter.get_traceback_html()
  57. return HttpResponse(html, status=status_code)
  58. else:
  59. text = reporter.get_traceback_text()
  60. return HttpResponse(
  61. text, status=status_code, content_type="text/plain; charset=utf-8"
  62. )
  63. @functools.lru_cache
  64. def get_default_exception_reporter_filter():
  65. # Instantiate the default filter for the first time and cache it.
  66. return import_string(settings.DEFAULT_EXCEPTION_REPORTER_FILTER)()
  67. def get_exception_reporter_filter(request):
  68. default_filter = get_default_exception_reporter_filter()
  69. return getattr(request, "exception_reporter_filter", default_filter)
  70. def get_exception_reporter_class(request):
  71. default_exception_reporter_class = import_string(
  72. settings.DEFAULT_EXCEPTION_REPORTER
  73. )
  74. return getattr(
  75. request, "exception_reporter_class", default_exception_reporter_class
  76. )
  77. def get_caller(request):
  78. resolver_match = request.resolver_match
  79. if resolver_match is None:
  80. try:
  81. resolver_match = resolve(request.path)
  82. except Http404:
  83. pass
  84. return "" if resolver_match is None else resolver_match._func_path
  85. class SafeExceptionReporterFilter:
  86. """
  87. Use annotations made by the sensitive_post_parameters and
  88. sensitive_variables decorators to filter out sensitive information.
  89. """
  90. cleansed_substitute = "********************"
  91. hidden_settings = _lazy_re_compile(
  92. "API|TOKEN|KEY|SECRET|PASS|SIGNATURE|HTTP_COOKIE", flags=re.I
  93. )
  94. def cleanse_setting(self, key, value):
  95. """
  96. Cleanse an individual setting key/value of sensitive content. If the
  97. value is a dictionary, recursively cleanse the keys in that dictionary.
  98. """
  99. if key == settings.SESSION_COOKIE_NAME:
  100. is_sensitive = True
  101. else:
  102. try:
  103. is_sensitive = self.hidden_settings.search(key)
  104. except TypeError:
  105. is_sensitive = False
  106. if is_sensitive:
  107. cleansed = self.cleansed_substitute
  108. elif isinstance(value, dict):
  109. cleansed = {k: self.cleanse_setting(k, v) for k, v in value.items()}
  110. elif isinstance(value, list):
  111. cleansed = [self.cleanse_setting("", v) for v in value]
  112. elif isinstance(value, tuple):
  113. cleansed = tuple([self.cleanse_setting("", v) for v in value])
  114. else:
  115. cleansed = value
  116. if callable(cleansed):
  117. cleansed = CallableSettingWrapper(cleansed)
  118. return cleansed
  119. def get_safe_settings(self):
  120. """
  121. Return a dictionary of the settings module with values of sensitive
  122. settings replaced with stars (*********).
  123. """
  124. settings_dict = {}
  125. for k in dir(settings):
  126. if k.isupper():
  127. settings_dict[k] = self.cleanse_setting(k, getattr(settings, k))
  128. return settings_dict
  129. def get_safe_request_meta(self, request):
  130. """
  131. Return a dictionary of request.META with sensitive values redacted.
  132. """
  133. if not hasattr(request, "META"):
  134. return {}
  135. return {k: self.cleanse_setting(k, v) for k, v in request.META.items()}
  136. def get_safe_cookies(self, request):
  137. """
  138. Return a dictionary of request.COOKIES with sensitive values redacted.
  139. """
  140. if not hasattr(request, "COOKIES"):
  141. return {}
  142. return {k: self.cleanse_setting(k, v) for k, v in request.COOKIES.items()}
  143. def is_active(self, request):
  144. """
  145. This filter is to add safety in production environments (i.e. DEBUG
  146. is False). If DEBUG is True then your site is not safe anyway.
  147. This hook is provided as a convenience to easily activate or
  148. deactivate the filter on a per request basis.
  149. """
  150. return settings.DEBUG is False
  151. def get_cleansed_multivaluedict(self, request, multivaluedict):
  152. """
  153. Replace the keys in a MultiValueDict marked as sensitive with stars.
  154. This mitigates leaking sensitive POST parameters if something like
  155. request.POST['nonexistent_key'] throws an exception (#21098).
  156. """
  157. sensitive_post_parameters = getattr(request, "sensitive_post_parameters", [])
  158. if self.is_active(request) and sensitive_post_parameters:
  159. multivaluedict = multivaluedict.copy()
  160. for param in sensitive_post_parameters:
  161. if param in multivaluedict:
  162. multivaluedict[param] = self.cleansed_substitute
  163. return multivaluedict
  164. def get_post_parameters(self, request):
  165. """
  166. Replace the values of POST parameters marked as sensitive with
  167. stars (*********).
  168. """
  169. if request is None:
  170. return {}
  171. else:
  172. sensitive_post_parameters = getattr(
  173. request, "sensitive_post_parameters", []
  174. )
  175. if self.is_active(request) and sensitive_post_parameters:
  176. cleansed = request.POST.copy()
  177. if sensitive_post_parameters == "__ALL__":
  178. # Cleanse all parameters.
  179. for k in cleansed:
  180. cleansed[k] = self.cleansed_substitute
  181. return cleansed
  182. else:
  183. # Cleanse only the specified parameters.
  184. for param in sensitive_post_parameters:
  185. if param in cleansed:
  186. cleansed[param] = self.cleansed_substitute
  187. return cleansed
  188. else:
  189. return request.POST
  190. def cleanse_special_types(self, request, value):
  191. try:
  192. # If value is lazy or a complex object of another kind, this check
  193. # might raise an exception. isinstance checks that lazy
  194. # MultiValueDicts will have a return value.
  195. is_multivalue_dict = isinstance(value, MultiValueDict)
  196. except Exception as e:
  197. return "{!r} while evaluating {!r}".format(e, value)
  198. if is_multivalue_dict:
  199. # Cleanse MultiValueDicts (request.POST is the one we usually care about)
  200. value = self.get_cleansed_multivaluedict(request, value)
  201. return value
  202. def get_traceback_frame_variables(self, request, tb_frame):
  203. """
  204. Replace the values of variables marked as sensitive with
  205. stars (*********).
  206. """
  207. sensitive_variables = None
  208. # Coroutines don't have a proper `f_back` so they need to be inspected
  209. # separately. Handle this by stashing the registered sensitive
  210. # variables in a global dict indexed by `hash(file_path:line_number)`.
  211. if (
  212. tb_frame.f_code.co_flags & inspect.CO_COROUTINE != 0
  213. and tb_frame.f_code.co_name != "sensitive_variables_wrapper"
  214. ):
  215. key = hash(
  216. f"{tb_frame.f_code.co_filename}:{tb_frame.f_code.co_firstlineno}"
  217. )
  218. sensitive_variables = coroutine_functions_to_sensitive_variables.get(
  219. key, None
  220. )
  221. if sensitive_variables is None:
  222. # Loop through the frame's callers to see if the
  223. # sensitive_variables decorator was used.
  224. current_frame = tb_frame
  225. while current_frame is not None:
  226. if (
  227. current_frame.f_code.co_name == "sensitive_variables_wrapper"
  228. and "sensitive_variables_wrapper" in current_frame.f_locals
  229. ):
  230. # The sensitive_variables decorator was used, so take note
  231. # of the sensitive variables' names.
  232. wrapper = current_frame.f_locals["sensitive_variables_wrapper"]
  233. sensitive_variables = getattr(wrapper, "sensitive_variables", None)
  234. break
  235. current_frame = current_frame.f_back
  236. cleansed = {}
  237. if self.is_active(request) and sensitive_variables:
  238. if sensitive_variables == "__ALL__":
  239. # Cleanse all variables
  240. for name in tb_frame.f_locals:
  241. cleansed[name] = self.cleansed_substitute
  242. else:
  243. # Cleanse specified variables
  244. for name, value in tb_frame.f_locals.items():
  245. if name in sensitive_variables:
  246. value = self.cleansed_substitute
  247. else:
  248. value = self.cleanse_special_types(request, value)
  249. cleansed[name] = value
  250. else:
  251. # Potentially cleanse the request and any MultiValueDicts if they
  252. # are one of the frame variables.
  253. for name, value in tb_frame.f_locals.items():
  254. cleansed[name] = self.cleanse_special_types(request, value)
  255. if (
  256. tb_frame.f_code.co_name == "sensitive_variables_wrapper"
  257. and "sensitive_variables_wrapper" in tb_frame.f_locals
  258. ):
  259. # For good measure, obfuscate the decorated function's arguments in
  260. # the sensitive_variables decorator's frame, in case the variables
  261. # associated with those arguments were meant to be obfuscated from
  262. # the decorated function's frame.
  263. cleansed["func_args"] = self.cleansed_substitute
  264. cleansed["func_kwargs"] = self.cleansed_substitute
  265. return cleansed.items()
  266. class ExceptionReporter:
  267. """Organize and coordinate reporting on exceptions."""
  268. @property
  269. def html_template_path(self):
  270. return builtin_template_path("technical_500.html")
  271. @property
  272. def text_template_path(self):
  273. return builtin_template_path("technical_500.txt")
  274. def __init__(self, request, exc_type, exc_value, tb, is_email=False):
  275. self.request = request
  276. self.filter = get_exception_reporter_filter(self.request)
  277. self.exc_type = exc_type
  278. self.exc_value = exc_value
  279. self.tb = tb
  280. self.is_email = is_email
  281. self.template_info = getattr(self.exc_value, "template_debug", None)
  282. self.template_does_not_exist = False
  283. self.postmortem = None
  284. def _get_raw_insecure_uri(self):
  285. """
  286. Return an absolute URI from variables available in this request. Skip
  287. allowed hosts protection, so may return insecure URI.
  288. """
  289. return "{scheme}://{host}{path}".format(
  290. scheme=self.request.scheme,
  291. host=self.request._get_raw_host(),
  292. path=self.request.get_full_path(),
  293. )
  294. def get_traceback_data(self):
  295. """Return a dictionary containing traceback information."""
  296. if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist):
  297. self.template_does_not_exist = True
  298. self.postmortem = self.exc_value.chain or [self.exc_value]
  299. frames = self.get_traceback_frames()
  300. for i, frame in enumerate(frames):
  301. if "vars" in frame:
  302. frame_vars = []
  303. for k, v in frame["vars"]:
  304. v = pprint(v)
  305. # Trim large blobs of data
  306. if len(v) > 4096:
  307. v = "%s… <trimmed %d bytes string>" % (v[0:4096], len(v))
  308. frame_vars.append((k, v))
  309. frame["vars"] = frame_vars
  310. frames[i] = frame
  311. unicode_hint = ""
  312. if self.exc_type and issubclass(self.exc_type, UnicodeError):
  313. start = getattr(self.exc_value, "start", None)
  314. end = getattr(self.exc_value, "end", None)
  315. if start is not None and end is not None:
  316. unicode_str = self.exc_value.args[1]
  317. unicode_hint = force_str(
  318. unicode_str[max(start - 5, 0) : min(end + 5, len(unicode_str))],
  319. "ascii",
  320. errors="replace",
  321. )
  322. from django import get_version
  323. if self.request is None:
  324. user_str = None
  325. else:
  326. try:
  327. user_str = str(self.request.user)
  328. except Exception:
  329. # request.user may raise OperationalError if the database is
  330. # unavailable, for example.
  331. user_str = "[unable to retrieve the current user]"
  332. c = {
  333. "is_email": self.is_email,
  334. "unicode_hint": unicode_hint,
  335. "frames": frames,
  336. "request": self.request,
  337. "request_meta": self.filter.get_safe_request_meta(self.request),
  338. "request_COOKIES_items": self.filter.get_safe_cookies(self.request).items(),
  339. "user_str": user_str,
  340. "filtered_POST_items": list(
  341. self.filter.get_post_parameters(self.request).items()
  342. ),
  343. "settings": self.filter.get_safe_settings(),
  344. "sys_executable": sys.executable,
  345. "sys_version_info": "%d.%d.%d" % sys.version_info[0:3],
  346. "server_time": timezone.now(),
  347. "django_version_info": get_version(),
  348. "sys_path": sys.path,
  349. "template_info": self.template_info,
  350. "template_does_not_exist": self.template_does_not_exist,
  351. "postmortem": self.postmortem,
  352. }
  353. if self.request is not None:
  354. c["request_GET_items"] = self.request.GET.items()
  355. c["request_FILES_items"] = self.request.FILES.items()
  356. c["request_insecure_uri"] = self._get_raw_insecure_uri()
  357. c["raising_view_name"] = get_caller(self.request)
  358. # Check whether exception info is available
  359. if self.exc_type:
  360. c["exception_type"] = self.exc_type.__name__
  361. if self.exc_value:
  362. c["exception_value"] = str(self.exc_value)
  363. if exc_notes := getattr(self.exc_value, "__notes__", None):
  364. c["exception_notes"] = "\n" + "\n".join(exc_notes)
  365. if frames:
  366. c["lastframe"] = frames[-1]
  367. return c
  368. def get_traceback_html(self):
  369. """Return HTML version of debug 500 HTTP error page."""
  370. with self.html_template_path.open(encoding="utf-8") as fh:
  371. t = DEBUG_ENGINE.from_string(fh.read())
  372. c = Context(self.get_traceback_data(), use_l10n=False)
  373. return t.render(c)
  374. def get_traceback_text(self):
  375. """Return plain text version of debug 500 HTTP error page."""
  376. with self.text_template_path.open(encoding="utf-8") as fh:
  377. t = DEBUG_ENGINE.from_string(fh.read())
  378. c = Context(self.get_traceback_data(), autoescape=False, use_l10n=False)
  379. return t.render(c)
  380. def _get_source(self, filename, loader, module_name):
  381. source = None
  382. if hasattr(loader, "get_source"):
  383. try:
  384. source = loader.get_source(module_name)
  385. except ImportError:
  386. pass
  387. if source is not None:
  388. source = source.splitlines()
  389. if source is None:
  390. try:
  391. with open(filename, "rb") as fp:
  392. source = fp.read().splitlines()
  393. except OSError:
  394. pass
  395. return source
  396. def _get_lines_from_file(
  397. self, filename, lineno, context_lines, loader=None, module_name=None
  398. ):
  399. """
  400. Return context_lines before and after lineno from file.
  401. Return (pre_context_lineno, pre_context, context_line, post_context).
  402. """
  403. source = self._get_source(filename, loader, module_name)
  404. if source is None:
  405. return None, [], None, []
  406. # If we just read the source from a file, or if the loader did not
  407. # apply tokenize.detect_encoding to decode the source into a
  408. # string, then we should do that ourselves.
  409. if isinstance(source[0], bytes):
  410. encoding = "ascii"
  411. for line in source[:2]:
  412. # File coding may be specified. Match pattern from PEP-263
  413. # (https://www.python.org/dev/peps/pep-0263/)
  414. match = re.search(rb"coding[:=]\s*([-\w.]+)", line)
  415. if match:
  416. encoding = match[1].decode("ascii")
  417. break
  418. source = [str(sline, encoding, "replace") for sline in source]
  419. lower_bound = max(0, lineno - context_lines)
  420. upper_bound = lineno + context_lines
  421. try:
  422. pre_context = source[lower_bound:lineno]
  423. context_line = source[lineno]
  424. post_context = source[lineno + 1 : upper_bound]
  425. except IndexError:
  426. return None, [], None, []
  427. return lower_bound, pre_context, context_line, post_context
  428. def _get_explicit_or_implicit_cause(self, exc_value):
  429. explicit = getattr(exc_value, "__cause__", None)
  430. suppress_context = getattr(exc_value, "__suppress_context__", None)
  431. implicit = getattr(exc_value, "__context__", None)
  432. return explicit or (None if suppress_context else implicit)
  433. def get_traceback_frames(self):
  434. # Get the exception and all its causes
  435. exceptions = []
  436. exc_value = self.exc_value
  437. while exc_value:
  438. exceptions.append(exc_value)
  439. exc_value = self._get_explicit_or_implicit_cause(exc_value)
  440. if exc_value in exceptions:
  441. warnings.warn(
  442. "Cycle in the exception chain detected: exception '%s' "
  443. "encountered again." % exc_value,
  444. ExceptionCycleWarning,
  445. )
  446. # Avoid infinite loop if there's a cyclic reference (#29393).
  447. break
  448. frames = []
  449. # No exceptions were supplied to ExceptionReporter
  450. if not exceptions:
  451. return frames
  452. # In case there's just one exception, take the traceback from self.tb
  453. exc_value = exceptions.pop()
  454. tb = self.tb if not exceptions else exc_value.__traceback__
  455. while True:
  456. frames.extend(self.get_exception_traceback_frames(exc_value, tb))
  457. try:
  458. exc_value = exceptions.pop()
  459. except IndexError:
  460. break
  461. tb = exc_value.__traceback__
  462. return frames
  463. def get_exception_traceback_frames(self, exc_value, tb):
  464. exc_cause = self._get_explicit_or_implicit_cause(exc_value)
  465. exc_cause_explicit = getattr(exc_value, "__cause__", True)
  466. if tb is None:
  467. yield {
  468. "exc_cause": exc_cause,
  469. "exc_cause_explicit": exc_cause_explicit,
  470. "tb": None,
  471. "type": "user",
  472. }
  473. while tb is not None:
  474. # Support for __traceback_hide__ which is used by a few libraries
  475. # to hide internal frames.
  476. if tb.tb_frame.f_locals.get("__traceback_hide__"):
  477. tb = tb.tb_next
  478. continue
  479. filename = tb.tb_frame.f_code.co_filename
  480. function = tb.tb_frame.f_code.co_name
  481. lineno = tb.tb_lineno - 1
  482. loader = tb.tb_frame.f_globals.get("__loader__")
  483. module_name = tb.tb_frame.f_globals.get("__name__") or ""
  484. (
  485. pre_context_lineno,
  486. pre_context,
  487. context_line,
  488. post_context,
  489. ) = self._get_lines_from_file(
  490. filename,
  491. lineno,
  492. 7,
  493. loader,
  494. module_name,
  495. )
  496. if pre_context_lineno is None:
  497. pre_context_lineno = lineno
  498. pre_context = []
  499. context_line = "<source code not available>"
  500. post_context = []
  501. colno = tb_area_colno = ""
  502. if PY311:
  503. _, _, start_column, end_column = next(
  504. itertools.islice(
  505. tb.tb_frame.f_code.co_positions(), tb.tb_lasti // 2, None
  506. )
  507. )
  508. if start_column and end_column:
  509. underline = "^" * (end_column - start_column)
  510. spaces = " " * (start_column + len(str(lineno + 1)) + 2)
  511. colno = f"\n{spaces}{underline}"
  512. tb_area_spaces = " " * (
  513. 4
  514. + start_column
  515. - (len(context_line) - len(context_line.lstrip()))
  516. )
  517. tb_area_colno = f"\n{tb_area_spaces}{underline}"
  518. yield {
  519. "exc_cause": exc_cause,
  520. "exc_cause_explicit": exc_cause_explicit,
  521. "tb": tb,
  522. "type": "django" if module_name.startswith("django.") else "user",
  523. "filename": filename,
  524. "function": function,
  525. "lineno": lineno + 1,
  526. "vars": self.filter.get_traceback_frame_variables(
  527. self.request, tb.tb_frame
  528. ),
  529. "id": id(tb),
  530. "pre_context": pre_context,
  531. "context_line": context_line,
  532. "post_context": post_context,
  533. "pre_context_lineno": pre_context_lineno + 1,
  534. "colno": colno,
  535. "tb_area_colno": tb_area_colno,
  536. }
  537. tb = tb.tb_next
  538. def technical_404_response(request, exception):
  539. """Create a technical 404 error response. `exception` is the Http404."""
  540. try:
  541. error_url = exception.args[0]["path"]
  542. except (IndexError, TypeError, KeyError):
  543. error_url = request.path_info[1:] # Trim leading slash
  544. try:
  545. tried = exception.args[0]["tried"]
  546. except (IndexError, TypeError, KeyError):
  547. resolved = True
  548. tried = request.resolver_match.tried if request.resolver_match else None
  549. else:
  550. resolved = False
  551. if not tried or ( # empty URLconf
  552. request.path == "/"
  553. and len(tried) == 1
  554. and len(tried[0]) == 1 # default URLconf
  555. and getattr(tried[0][0], "app_name", "")
  556. == getattr(tried[0][0], "namespace", "")
  557. == "admin"
  558. ):
  559. return default_urlconf(request)
  560. urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
  561. if isinstance(urlconf, types.ModuleType):
  562. urlconf = urlconf.__name__
  563. with builtin_template_path("technical_404.html").open(encoding="utf-8") as fh:
  564. t = DEBUG_ENGINE.from_string(fh.read())
  565. reporter_filter = get_default_exception_reporter_filter()
  566. c = Context(
  567. {
  568. "urlconf": urlconf,
  569. "root_urlconf": settings.ROOT_URLCONF,
  570. "request_path": error_url,
  571. "urlpatterns": tried,
  572. "resolved": resolved,
  573. "reason": str(exception),
  574. "request": request,
  575. "settings": reporter_filter.get_safe_settings(),
  576. "raising_view_name": get_caller(request),
  577. }
  578. )
  579. return HttpResponseNotFound(t.render(c))
  580. def default_urlconf(request):
  581. """Create an empty URLconf 404 error response."""
  582. with builtin_template_path("default_urlconf.html").open(encoding="utf-8") as fh:
  583. t = DEBUG_ENGINE.from_string(fh.read())
  584. c = Context(
  585. {
  586. "version": get_docs_version(),
  587. }
  588. )
  589. return HttpResponse(t.render(c))