|
23 | 23 | from werkzeug.routing import RequestRedirect |
24 | 24 | from werkzeug.routing import RoutingException |
25 | 25 | from werkzeug.routing import Rule |
| 26 | +from werkzeug.urls import url_quote |
26 | 27 | from werkzeug.utils import redirect as _wz_redirect |
27 | 28 | from werkzeug.wrappers import Response as BaseResponse |
28 | 29 |
|
|
33 | 34 | from .ctx import _AppCtxGlobals |
34 | 35 | from .ctx import AppContext |
35 | 36 | from .ctx import RequestContext |
| 37 | +from .globals import _app_ctx_stack |
36 | 38 | from .globals import _request_ctx_stack |
37 | 39 | from .globals import g |
38 | 40 | from .globals import request |
@@ -439,15 +441,16 @@ def __init__( |
439 | 441 | #: .. versionadded:: 2.2 |
440 | 442 | self.aborter = self.make_aborter() |
441 | 443 |
|
442 | | - #: A list of functions that are called when :meth:`url_for` raises a |
443 | | - #: :exc:`~werkzeug.routing.BuildError`. Each function registered here |
444 | | - #: is called with `error`, `endpoint` and `values`. If a function |
445 | | - #: returns ``None`` or raises a :exc:`BuildError` the next function is |
446 | | - #: tried. |
| 444 | + #: A list of functions that are called by |
| 445 | + #: :meth:`handle_url_build_error` when :meth:`.url_for` raises a |
| 446 | + #: :exc:`~werkzeug.routing.BuildError`. Each function is called |
| 447 | + #: with ``error``, ``endpoint`` and ``values``. If a function |
| 448 | + #: returns ``None`` or raises a ``BuildError``, it is skipped. |
| 449 | + #: Otherwise, its return value is returned by ``url_for``. |
447 | 450 | #: |
448 | 451 | #: .. versionadded:: 0.9 |
449 | 452 | self.url_build_error_handlers: t.List[ |
450 | | - t.Callable[[Exception, str, dict], str] |
| 453 | + t.Callable[[Exception, str, t.Dict[str, t.Any]], str] |
451 | 454 | ] = [] |
452 | 455 |
|
453 | 456 | #: A list of functions that will be called at the beginning of the |
@@ -1661,6 +1664,130 @@ def async_to_sync( |
1661 | 1664 |
|
1662 | 1665 | return asgiref_async_to_sync(func) |
1663 | 1666 |
|
| 1667 | + def url_for( |
| 1668 | + self, |
| 1669 | + endpoint: str, |
| 1670 | + *, |
| 1671 | + _anchor: t.Optional[str] = None, |
| 1672 | + _method: t.Optional[str] = None, |
| 1673 | + _scheme: t.Optional[str] = None, |
| 1674 | + _external: t.Optional[bool] = None, |
| 1675 | + **values: t.Any, |
| 1676 | + ) -> str: |
| 1677 | + """Generate a URL to the given endpoint with the given values. |
| 1678 | +
|
| 1679 | + This is called by :func:`flask.url_for`, and can be called |
| 1680 | + directly as well. |
| 1681 | +
|
| 1682 | + An *endpoint* is the name of a URL rule, usually added with |
| 1683 | + :meth:`@app.route() <route>`, and usually the same name as the |
| 1684 | + view function. A route defined in a :class:`~flask.Blueprint` |
| 1685 | + will prepend the blueprint's name separated by a ``.`` to the |
| 1686 | + endpoint. |
| 1687 | +
|
| 1688 | + In some cases, such as email messages, you want URLs to include |
| 1689 | + the scheme and domain, like ``https://example.com/hello``. When |
| 1690 | + not in an active request, URLs will be external by default, but |
| 1691 | + this requires setting :data:`SERVER_NAME` so Flask knows what |
| 1692 | + domain to use. :data:`APPLICATION_ROOT` and |
| 1693 | + :data:`PREFERRED_URL_SCHEME` should also be configured as |
| 1694 | + needed. This config is only used when not in an active request. |
| 1695 | +
|
| 1696 | + Functions can be decorated with :meth:`url_defaults` to modify |
| 1697 | + keyword arguments before the URL is built. |
| 1698 | +
|
| 1699 | + If building fails for some reason, such as an unknown endpoint |
| 1700 | + or incorrect values, the app's :meth:`handle_url_build_error` |
| 1701 | + method is called. If that returns a string, that is returned, |
| 1702 | + otherwise a :exc:`~werkzeug.routing.BuildError` is raised. |
| 1703 | +
|
| 1704 | + :param endpoint: The endpoint name associated with the URL to |
| 1705 | + generate. If this starts with a ``.``, the current blueprint |
| 1706 | + name (if any) will be used. |
| 1707 | + :param _anchor: If given, append this as ``#anchor`` to the URL. |
| 1708 | + :param _method: If given, generate the URL associated with this |
| 1709 | + method for the endpoint. |
| 1710 | + :param _scheme: If given, the URL will have this scheme if it |
| 1711 | + is external. |
| 1712 | + :param _external: If given, prefer the URL to be internal |
| 1713 | + (False) or require it to be external (True). External URLs |
| 1714 | + include the scheme and domain. When not in an active |
| 1715 | + request, URLs are external by default. |
| 1716 | + :param values: Values to use for the variable parts of the URL |
| 1717 | + rule. Unknown keys are appended as query string arguments, |
| 1718 | + like ``?a=b&c=d``. |
| 1719 | +
|
| 1720 | + .. versionadded:: 2.2 |
| 1721 | + Moved from ``flask.url_for``, which calls this method. |
| 1722 | + """ |
| 1723 | + req_ctx = _request_ctx_stack.top |
| 1724 | + |
| 1725 | + if req_ctx is not None: |
| 1726 | + url_adapter = req_ctx.url_adapter |
| 1727 | + blueprint_name = req_ctx.request.blueprint |
| 1728 | + |
| 1729 | + # If the endpoint starts with "." and the request matches a |
| 1730 | + # blueprint, the endpoint is relative to the blueprint. |
| 1731 | + if endpoint[:1] == ".": |
| 1732 | + if blueprint_name is not None: |
| 1733 | + endpoint = f"{blueprint_name}{endpoint}" |
| 1734 | + else: |
| 1735 | + endpoint = endpoint[1:] |
| 1736 | + |
| 1737 | + # When in a request, generate a URL without scheme and |
| 1738 | + # domain by default, unless a scheme is given. |
| 1739 | + if _external is None: |
| 1740 | + _external = _scheme is not None |
| 1741 | + else: |
| 1742 | + app_ctx = _app_ctx_stack.top |
| 1743 | + |
| 1744 | + # If called by helpers.url_for, an app context is active, |
| 1745 | + # use its url_adapter. Otherwise, app.url_for was called |
| 1746 | + # directly, build an adapter. |
| 1747 | + if app_ctx is not None: |
| 1748 | + url_adapter = app_ctx.url_adapter |
| 1749 | + else: |
| 1750 | + url_adapter = self.create_url_adapter(None) |
| 1751 | + |
| 1752 | + if url_adapter is None: |
| 1753 | + raise RuntimeError( |
| 1754 | + "Unable to build URLs outside an active request" |
| 1755 | + " without 'SERVER_NAME' configured. Also configure" |
| 1756 | + " 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as" |
| 1757 | + " needed." |
| 1758 | + ) |
| 1759 | + |
| 1760 | + # When outside a request, generate a URL with scheme and |
| 1761 | + # domain by default. |
| 1762 | + if _external is None: |
| 1763 | + _external = True |
| 1764 | + |
| 1765 | + # It is an error to set _scheme when _external=False, in order |
| 1766 | + # to avoid accidental insecure URLs. |
| 1767 | + if _scheme is not None and not _external: |
| 1768 | + raise ValueError("When specifying '_scheme', '_external' must be True.") |
| 1769 | + |
| 1770 | + self.inject_url_defaults(endpoint, values) |
| 1771 | + |
| 1772 | + try: |
| 1773 | + rv = url_adapter.build( |
| 1774 | + endpoint, |
| 1775 | + values, |
| 1776 | + method=_method, |
| 1777 | + url_scheme=_scheme, |
| 1778 | + force_external=_external, |
| 1779 | + ) |
| 1780 | + except BuildError as error: |
| 1781 | + values.update( |
| 1782 | + _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external |
| 1783 | + ) |
| 1784 | + return self.handle_url_build_error(error, endpoint, values) |
| 1785 | + |
| 1786 | + if _anchor is not None: |
| 1787 | + rv = f"{rv}#{url_quote(_anchor)}" |
| 1788 | + |
| 1789 | + return rv |
| 1790 | + |
1664 | 1791 | def redirect(self, location: str, code: int = 302) -> BaseResponse: |
1665 | 1792 | """Create a redirect response object. |
1666 | 1793 |
|
@@ -1860,10 +1987,21 @@ def inject_url_defaults(self, endpoint: str, values: dict) -> None: |
1860 | 1987 | func(endpoint, values) |
1861 | 1988 |
|
1862 | 1989 | def handle_url_build_error( |
1863 | | - self, error: Exception, endpoint: str, values: dict |
| 1990 | + self, error: BuildError, endpoint: str, values: t.Dict[str, t.Any] |
1864 | 1991 | ) -> str: |
1865 | | - """Handle :class:`~werkzeug.routing.BuildError` on |
1866 | | - :meth:`url_for`. |
| 1992 | + """Called by :meth:`.url_for` if a |
| 1993 | + :exc:`~werkzeug.routing.BuildError` was raised. If this returns |
| 1994 | + a value, it will be returned by ``url_for``, otherwise the error |
| 1995 | + will be re-raised. |
| 1996 | +
|
| 1997 | + Each function in :attr:`url_build_error_handlers` is called with |
| 1998 | + ``error``, ``endpoint`` and ``values``. If a function returns |
| 1999 | + ``None`` or raises a ``BuildError``, it is skipped. Otherwise, |
| 2000 | + its return value is returned by ``url_for``. |
| 2001 | +
|
| 2002 | + :param error: The active ``BuildError`` being handled. |
| 2003 | + :param endpoint: The endpoint being built. |
| 2004 | + :param values: The keyword arguments passed to ``url_for``. |
1867 | 2005 | """ |
1868 | 2006 | for handler in self.url_build_error_handlers: |
1869 | 2007 | try: |
|
0 commit comments