Coverage for app/venv/lib/python3.14/site-packages/weblate/utils/render.py: 83%
96 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
1# Copyright © Michal Čihař <michal@weblate.org>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
5from __future__ import annotations
7from collections import UserString
8from typing import TYPE_CHECKING
10from django.conf import settings
11from django.core.exceptions import ValidationError
12from django.template import Context, Engine, Template, TemplateSyntaxError
13from django.urls import reverse
14from django.utils.functional import SimpleLazyObject
15from django.utils.translation import gettext, override
17from weblate.utils.site import get_site_url
18from weblate.utils.validators import WeblateEditorURLValidator, WeblateURLValidator
20if TYPE_CHECKING: 20 ↛ 21line 20 didn't jump to line 21 because the condition on line 20 was never true
21 from django.core.validators import URLValidator
24class InvalidString(UserString):
25 def __mod__(self, other: str):
26 raise TemplateSyntaxError(gettext('Undefined variable: "%s"') % other)
29class RestrictedEngine(Engine):
30 default_builtins: list[str] = [ # noqa: RUF012
31 "django.template.defaultfilters",
32 "weblate.utils.templatetags.safe_render",
33 ]
35 def __init__(self, *args, **kwargs) -> None:
36 kwargs["autoescape"] = False
37 kwargs["string_if_invalid"] = InvalidString("%s")
38 super().__init__(*args, **kwargs)
41def render_template(template: str, **kwargs):
42 """Render string template with Weblate context."""
43 from weblate.trans.models import Component, Project, Translation
45 translation = kwargs.get("translation")
46 component = kwargs.get("component")
47 project = kwargs.get("project")
49 # Comppatibility with older templates
50 if "addon_name" in kwargs:
51 kwargs["hook_name"] = kwargs["addon_name"]
53 if isinstance(translation, Translation):
54 kwargs["language_code"] = translation.language_code
55 kwargs["language_name"] = translation.language.get_name()
56 kwargs["stats"] = translation.stats.get_data()
57 kwargs["url"] = get_site_url(translation.get_absolute_url())
58 kwargs["filename"] = translation.filename
59 component = translation.component
60 kwargs.pop("translation", None)
62 if isinstance(component, Component): 62 ↛ 88line 62 didn't jump to line 88 because the condition on line 62 was always true
63 kwargs["component_name"] = component.name
64 kwargs["component_slug"] = component.slug
65 kwargs["component_remote_branch"] = (
66 component.repository.get_remote_branch_name()
67 )
68 if "url" not in kwargs:
69 kwargs["url"] = get_site_url(component.get_absolute_url())
70 kwargs["widget_url"] = get_site_url(
71 reverse(
72 "widget-image",
73 kwargs={
74 "path": component.get_url_path(),
75 "widget": "horizontal",
76 "color": "auto",
77 "extension": "svg",
78 },
79 )
80 )
81 if component.pk: 81 ↛ 85line 81 didn't jump to line 85 because the condition on line 81 was always true
82 kwargs["component_linked_children"] = SimpleLazyObject(
83 component.get_linked_children_for_template
84 )
85 project = component.project
86 kwargs.pop("component", None)
88 if isinstance(project, Project): 88 ↛ 95line 88 didn't jump to line 95 because the condition on line 88 was always true
89 kwargs["project_name"] = project.name
90 kwargs["project_slug"] = project.slug
91 if "url" not in kwargs: 91 ↛ 92line 91 didn't jump to line 92 because the condition on line 91 was never true
92 kwargs["url"] = get_site_url(project.get_absolute_url())
93 kwargs.pop("project", None)
95 kwargs["site_title"] = settings.SITE_TITLE
96 kwargs["site_url"] = get_site_url()
98 with override("en"):
99 return Template(template, engine=RestrictedEngine()).render(
100 Context(kwargs, autoescape=False)
101 )
104def validate_render(value: str, **kwargs) -> str:
105 """Validate rendered template."""
106 try:
107 return render_template(value, **kwargs)
108 except Exception as err:
109 raise ValidationError(
110 gettext("Could not render template: {}").format(err)
111 ) from err
114def validate_render_mock(value: str, *, translation: bool = False, **kwargs) -> str:
115 from weblate.lang.models import Language
116 from weblate.trans.models import Component, Project, Translation
117 from weblate.utils.stats import DummyTranslationStats
119 project = Project(name="project", slug="project", id=-1)
120 project.stats = DummyTranslationStats(project) # type: ignore[assignment]
121 component = Component(
122 project=project,
123 name="component",
124 slug="component",
125 branch="main",
126 source_language=Language(name="aa", code="x-aa"),
127 vcs="git",
128 id=-1,
129 )
130 component.stats = DummyTranslationStats(component) # type: ignore[assignment]
131 if translation:
132 kwargs["translation"] = Translation(
133 id=-1,
134 component=component,
135 language_code="xx",
136 language=Language(name="xxx", code="xx"),
137 )
138 kwargs["translation"].stats = DummyTranslationStats(translation)
139 else:
140 kwargs["component"] = component
141 return validate_render(value, **kwargs)
144def validate_render_translation(value: str) -> None:
145 validate_render_mock(value, translation=True)
148def validate_render_component(value: str) -> None:
149 validate_render_mock(value)
152def validate_render_addon(value: str) -> None:
153 validate_render_mock(value, hook_name="addon", addon_name="addon")
156def validate_render_commit(value: str) -> None:
157 validate_render_mock(value, translation=True, author="author")
160def validate_repoweb(val: str, allow_editor: bool = False) -> None:
161 """
162 Validate whether URL for repository browser is valid.
164 It checks whether it can be filled in using format string.
165 """
166 if "%(file)s" in val or "%(line)s" in val: 166 ↛ 167line 166 didn't jump to line 167 because the condition on line 166 was never true
167 raise ValidationError(
168 gettext(
169 "The format strings are no longer supported, "
170 "please use the template language instead."
171 )
172 )
173 url = validate_render_mock(val, filename="file.po", line=9, branch="main")
175 validator: URLValidator
176 if ( 176 ↛ 180line 176 didn't jump to line 180 because the condition on line 176 was never true
177 allow_editor
178 and val.split("://", 1)[0].lower() in WeblateEditorURLValidator.schemes
179 ):
180 validator = WeblateEditorURLValidator()
181 else:
182 validator = WeblateURLValidator()
183 validator(url)
186def validate_editor(val: str) -> None:
187 """
188 Validate URL for custom editor link.
190 - Check whether it correctly uses format strings.
191 - Check whether scheme is sane.
192 """
193 if not val:
194 return
195 validate_repoweb(val, allow_editor=True)