anchor in URL

61 views
Skip to first unread message

Lucas

unread,
Sep 22, 2025, 7:36:20 AM9/22/25
to py4web
hello one and all,

I trying to put anchor under the URL function, but py4web is returning error 500 on that "anchor" parameter.  

I also tried embedding it under the url itself also, like URL('courses#edit'), but when clicked py4web returns error 404 and the # is returned as %23.

anchor worked great in py4web, why is it gone?  thank you in advance, Lucas

Massimo DiPierro

unread,
Sep 22, 2025, 11:06:35 AM9/22/25
to Lucas, py4web
Overlooked. I will add it back today

--
You received this message because you are subscribed to the Google Groups "py4web" group.
To unsubscribe from this group and stop receiving emails from it, send an email to py4web+un...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/py4web/0371c6e7-4875-4263-a5b4-40a1cb712419n%40googlegroups.com.
Message has been deleted

Lucas

unread,
Sep 27, 2025, 12:36:37 AM9/27/25
to py4web
oh, thank you. I didn't know how to get around that.  Lucas

Lucas

unread,
Sep 27, 2025, 7:47:53 AM9/27/25
to py4web
I noticed too that the args is also missing.  I can get around that but passing the args list is easier than using a f-string directly on the url parameter.

laundmo

unread,
Sep 30, 2025, 8:03:28 AM9/30/25
to py4web
I made my own version of URL(), with the idea to use controller function names instead of the actual url path, in case you're interested.

import os
from collections import defaultdict
from urllib.parse import urlencode, urljoin, urlparse, urlunparse

import ombott

from py4web import request


def url_of(
controller,
*path_args,
filter_by_method=None,
filter_by_appname=None,
add_query=None,
add_scheme=None,
add_anchor=None,
**path_kwargs,
):
"""
Generates a URL for the given controller.
Controllers can be passed by their function object or by name.

Accepts extra positional and keyword arguments. These are used
to fill url patterns like: <some_id:int>
The number of positional and keyword arguments summed must match the
number of patterns in the URL. Keyword arguments take precedence,
positional arguments fill whatever patterns are left. That means a if you have a
controller named foobar with path /api/object/<some_id:int>/value/<field:str>
and call `url_of('foobar', 'baz', some_id=5)` the returned URL will be:
/api/object/5/value/baz

#### Arguments:
- `filter_by_method` - only get urls which have a handler registered
capable of handling this HTTP request method
- `filter_by_appname` - get urls for a different app_name
- `add_query` - dictionary of query params to be added to url
- `add_scheme` - bool or str, bool toggles the scheme (https:// etc.) found in the
current request on or off, str overwrites the scheme.
None by default, which outputs a relative url

#### Examples:
- `url_of('index')` -> `{app_name?}/index`
- `url_of('api_object_thing', id=5)` -> `{app_name?}/api/object/5/thing`
- `url_of('index', add_scheme=True)` -> `https://{domain}/{app_name?}/index`
"""
if filter_by_appname:
app_name = filter_by_appname
else:
has_scriptname = len(request.script_name) > 1
app_name = request.fullpath.split("/")[1 + has_scriptname]

if isinstance(controller, str):
# if controller_name contains a ".", treat it as the path separator
controller_path = controller.split(".")
else:
controller_path = [*controller.__module__.split("."), controller.__name__]

condition_txt = "this name"
routes = ombott.app.routes.values()

handler_routes = defaultdict(list)
for route in routes:
# needs to be a route of this app
# route.pattern is a url like someapp/part1/part2
if not route.pattern.startswith(app_name):
condition_txt = f" app {app_name}"
continue

# the number of params needs to match
if len(route.params) != len(path_args) + len(path_kwargs):
condition_txt = f"{len(path_args) + len(path_kwargs)} path args"
continue

# all path_kwargs need to be known params of the route
if any(kwarg not in route.params for kwarg in path_kwargs.keys()):
condition_txt = f"these path kwargs {tuple(path_kwargs.keys())}"
continue

if filter_by_method:
meth = route.methods.get(filter_by_method, None)
if meth is None:
condition_txt = f"HTTP method {filter_by_method}"
continue
methods = [meth]
else:
methods = route.methods.values()

for handler in methods:
# one of the methods needs to match the controller name
# method.handler_fullname is the function name import path
# dot-separated: apps.<appname>.<controllers_file>.<controllerfuncname>
handler_path = handler.handler_fullname.split(".")

if handler_path[-len(controller_path) :] != controller_path:
condition_txt = f"controller path '{controller_path}'"
continue

handler_routes[handler.handler_fullname].append(route)

if len(handler_routes) == 0:
raise ValueError(f"No controller '{controller}' with {condition_txt} found.")

if len(handler_routes) > 1:
optstr = "\n - ".join(f"{k}: {rms[0].route.rule}" for k, rms in handler_routes.items())
raise ValueError(f"More than 1 url route found for specified controller and arguments:\n{optstr}")

route = next(iter(handler_routes.values()))[0]

pos_args = list(reversed(path_args))
params = {p: path_kwargs.get(p, None) or pos_args.pop() for p in route.params}
url = route.url(**params)

# if domain-mapped, remove prepended appname
if request.environ.get("HTTP_X_PY4WEB_APPNAME"):
# if its also prefixed, have to remove prefix before appname is removed
# since bottle routes are registered including the prefix
prefix = os.environ.get("PY4WEB_URL_PREFIX", "").strip("/")
split_url = url.split("/")
if prefix:
prefix_parts = len(prefix.split("/"))
split_url = [prefix, *split_url[prefix_parts + 1 :]]
else:
split_url = split_url[1:]
url = "/".join(split_url)

if add_query:
url = urljoin(url, "?" + urlencode(add_query))

if url[0] != "/":
url = f"/{url}"

url = urlparse(url)

# False = //example.com/etc
if add_scheme is False:
scheme = ""
# "custom" = custom://example.com/etc
elif isinstance(add_scheme, str):
scheme = add_scheme
# True (default) = http(s)://example.com/etc
else:
request_url = request.environ.get("HTTP_ORIGIN") or request.url
scheme = urlparse(request_url).scheme
if add_scheme is not None:
url = url._replace(scheme=scheme)

if add_anchor:
url = url._replace(fragment=urlencode(add_anchor))

return urlunparse(url)

Reply all
Reply to author
Forward
0 new messages