tina4-python 3.13.26__tar.gz → 3.13.28__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tina4_python-3.13.26 → tina4_python-3.13.28}/PKG-INFO +1 -1
- {tina4_python-3.13.26 → tina4_python-3.13.28}/pyproject.toml +1 -1
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/__init__.py +1 -1
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/frond/engine.py +69 -11
- {tina4_python-3.13.26 → tina4_python-3.13.28}/.gitignore +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/README.md +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/env.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -851,6 +851,13 @@ def _eval_test(value_expr: str, test_name: str, args: str, context: dict, eval_f
|
|
|
851
851
|
"boolean": lambda v: isinstance(v, bool),
|
|
852
852
|
}
|
|
853
853
|
|
|
854
|
+
# Merge in custom tests registered via add_test(). They live on the Frond
|
|
855
|
+
# instance, reachable through the bound eval_fn (eval_fn.__self__._tests).
|
|
856
|
+
# Custom registrations override built-ins, matching PHP/Ruby/Node.
|
|
857
|
+
custom = getattr(getattr(eval_fn, "__self__", None), "_tests", None)
|
|
858
|
+
if custom:
|
|
859
|
+
tests = {**tests, **custom}
|
|
860
|
+
|
|
854
861
|
# 'divisible by(n)'
|
|
855
862
|
if test_name == "divisible":
|
|
856
863
|
m = _DIVISIBLE_BY_RE.match(args)
|
|
@@ -989,9 +996,26 @@ def _coerce_arg(s: str):
|
|
|
989
996
|
return s
|
|
990
997
|
|
|
991
998
|
|
|
999
|
+
class _Ref:
|
|
1000
|
+
"""A filter argument that was an UNQUOTED bareword — i.e. a variable
|
|
1001
|
+
reference (or dotted/bracket path), not a literal. Resolved against the
|
|
1002
|
+
render context at apply-time so e.g. `{{ '%.2f' | format(price) }}` binds
|
|
1003
|
+
`price` to its value. Quoted literals (`default('fb')`) are kept as plain
|
|
1004
|
+
strings and never resolved."""
|
|
1005
|
+
|
|
1006
|
+
__slots__ = ("name",)
|
|
1007
|
+
|
|
1008
|
+
def __init__(self, name: str):
|
|
1009
|
+
self.name = name
|
|
1010
|
+
|
|
1011
|
+
|
|
992
1012
|
def _parse_args(raw: str) -> list:
|
|
993
|
-
"""Parse filter arguments, respecting quoted strings, braces, and backslash escapes.
|
|
994
|
-
|
|
1013
|
+
"""Parse filter arguments, respecting quoted strings, braces, and backslash escapes.
|
|
1014
|
+
|
|
1015
|
+
Quoted args become literal strings; numbers/bools/null/JSON are coerced to
|
|
1016
|
+
their Python types; an unquoted bareword becomes a `_Ref` (a variable
|
|
1017
|
+
reference resolved against the context when the filter runs)."""
|
|
1018
|
+
tokens = []
|
|
995
1019
|
current = ""
|
|
996
1020
|
in_quote = None
|
|
997
1021
|
depth = 0
|
|
@@ -1006,15 +1030,27 @@ def _parse_args(raw: str) -> list:
|
|
|
1006
1030
|
elif ch in (")", "}", "]") and not in_quote:
|
|
1007
1031
|
depth -= 1
|
|
1008
1032
|
elif ch == "," and not in_quote and depth == 0:
|
|
1009
|
-
|
|
1033
|
+
tokens.append(current.strip())
|
|
1010
1034
|
current = ""
|
|
1011
1035
|
continue
|
|
1012
1036
|
current += ch
|
|
1013
1037
|
|
|
1014
1038
|
if current.strip():
|
|
1015
|
-
|
|
1039
|
+
tokens.append(current.strip())
|
|
1016
1040
|
|
|
1017
|
-
|
|
1041
|
+
out = []
|
|
1042
|
+
for tok in tokens:
|
|
1043
|
+
if len(tok) >= 2 and tok[0] == tok[-1] and tok[0] in ('"', "'"):
|
|
1044
|
+
out.append(_strip_outer_quotes(tok)) # quoted literal → string
|
|
1045
|
+
else:
|
|
1046
|
+
coerced = _coerce_arg(tok)
|
|
1047
|
+
# A bareword that stayed a string is a variable/expression
|
|
1048
|
+
# reference (e.g. `n`, `user.age`) — resolve it at apply-time.
|
|
1049
|
+
if isinstance(coerced, str) and tok:
|
|
1050
|
+
out.append(_Ref(tok))
|
|
1051
|
+
else:
|
|
1052
|
+
out.append(coerced)
|
|
1053
|
+
return out
|
|
1018
1054
|
|
|
1019
1055
|
|
|
1020
1056
|
def _strip_outer_quotes(s: str) -> str:
|
|
@@ -1071,16 +1107,16 @@ _BUILTIN_FILTERS = {
|
|
|
1071
1107
|
"default": lambda v, *a: v if v is not None and v != "" else (a[0] if a else ""),
|
|
1072
1108
|
"raw": lambda v, *a: v, # Mark as safe (no escaping)
|
|
1073
1109
|
"safe": lambda v, *a: v,
|
|
1074
|
-
"escape": lambda v, *a: html.escape(str(v)),
|
|
1075
|
-
"e": lambda v, *a: html.escape(str(v)),
|
|
1110
|
+
"escape": lambda v, *a: SafeString(html.escape(str(v))),
|
|
1111
|
+
"e": lambda v, *a: SafeString(html.escape(str(v))),
|
|
1076
1112
|
"striptags": lambda v, *a: _STRIPTAGS_RE.sub("", str(v)),
|
|
1077
|
-
"nl2br": lambda v, *a: str(v).replace("\n", "<br
|
|
1113
|
+
"nl2br": lambda v, *a: SafeString(html.escape(str(v)).replace("\n", "<br />\n")),
|
|
1078
1114
|
"abs": lambda v, *a: abs(v) if isinstance(v, (int, float)) else v,
|
|
1079
|
-
"round": lambda v, *a: round(float(v), int(a[0]) if a else
|
|
1115
|
+
"round": lambda v, *a: round(float(v), int(a[0])) if a else int(round(float(v))),
|
|
1080
1116
|
"int": lambda v, *a: int(v) if v else 0,
|
|
1081
1117
|
"float": lambda v, *a: float(v) if v else 0.0,
|
|
1082
1118
|
"string": lambda v, *a: str(v),
|
|
1083
|
-
"json_encode": lambda v, *a: json.dumps(v),
|
|
1119
|
+
"json_encode": lambda v, *a: json.dumps(v, separators=(",", ":")),
|
|
1084
1120
|
"to_json": lambda v, *a: SafeString(json.dumps(v, default=str, separators=(",", ":")).replace("<", "\\u003c").replace(">", "\\u003e").replace("&", "\\u0026")),
|
|
1085
1121
|
"tojson": lambda v, *a: SafeString(json.dumps(v, default=str, separators=(",", ":")).replace("<", "\\u003c").replace(">", "\\u003e").replace("&", "\\u0026")),
|
|
1086
1122
|
"js_escape": lambda v, *a: SafeString(str(v).replace("\\", "\\\\").replace("'", "\\'").replace('"', '\\"').replace("\n", "\\n").replace("\r", "\\r")),
|
|
@@ -1633,6 +1669,21 @@ class Frond:
|
|
|
1633
1669
|
|
|
1634
1670
|
def _render_tokens(self, tokens: list, context: dict) -> str:
|
|
1635
1671
|
"""Render a list of tokens to string."""
|
|
1672
|
+
# Whitespace control pre-pass: every {{- / -}} and {%- / -%} marker
|
|
1673
|
+
# trims its neighbouring TEXT token. Doing this up-front over the whole
|
|
1674
|
+
# token list (not per-handler) means the markers on closing tags
|
|
1675
|
+
# (endif/endfor) and the body boundaries are honoured too — a block tag
|
|
1676
|
+
# consumes its own end tag during dispatch, so the inline pass below
|
|
1677
|
+
# never sees it. Idempotent, so recursion into block bodies is safe.
|
|
1678
|
+
for _idx in range(len(tokens)):
|
|
1679
|
+
_tt, _raw = tokens[_idx]
|
|
1680
|
+
if _tt in (VAR, BLOCK):
|
|
1681
|
+
_, _sb, _sa = _strip_tag(_raw)
|
|
1682
|
+
if _sb and _idx > 0 and tokens[_idx - 1][0] == TEXT:
|
|
1683
|
+
tokens[_idx - 1] = (TEXT, tokens[_idx - 1][1].rstrip())
|
|
1684
|
+
if _sa and _idx + 1 < len(tokens) and tokens[_idx + 1][0] == TEXT:
|
|
1685
|
+
tokens[_idx + 1] = (TEXT, tokens[_idx + 1][1].lstrip())
|
|
1686
|
+
|
|
1636
1687
|
output = []
|
|
1637
1688
|
i = 0
|
|
1638
1689
|
|
|
@@ -1835,6 +1886,13 @@ class Frond:
|
|
|
1835
1886
|
is_safe = True
|
|
1836
1887
|
continue
|
|
1837
1888
|
|
|
1889
|
+
# Resolve any unquoted-bareword args against the context (a quoted
|
|
1890
|
+
# literal stays a plain string). Lets `{{ '%.2f' | format(n) }}`
|
|
1891
|
+
# bind `n` to its value instead of the literal text "n".
|
|
1892
|
+
if args and any(isinstance(a, _Ref) for a in args):
|
|
1893
|
+
args = [_eval_expr(a.name, context) if isinstance(a, _Ref) else a
|
|
1894
|
+
for a in args]
|
|
1895
|
+
|
|
1838
1896
|
# Sandbox: check filter access
|
|
1839
1897
|
if self._sandbox and self._allowed_filters is not None:
|
|
1840
1898
|
if fname not in self._allowed_filters:
|
|
@@ -1885,7 +1943,7 @@ class Frond:
|
|
|
1885
1943
|
value = int(value) if value else 0
|
|
1886
1944
|
continue
|
|
1887
1945
|
if fname in ("e", "escape"):
|
|
1888
|
-
value = html.escape(str(value))
|
|
1946
|
+
value = SafeString(html.escape(str(value)))
|
|
1889
1947
|
continue
|
|
1890
1948
|
|
|
1891
1949
|
fn = self._filters.get(fname)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/images/tina4-logo-icon.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/queue_backends/rabbitmq_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/templates/docker/python/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.26 → tina4_python-3.13.28}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|