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

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4 

5from __future__ import annotations 

6 

7from collections import UserString 

8from typing import TYPE_CHECKING 

9 

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 

16 

17from weblate.utils.site import get_site_url 

18from weblate.utils.validators import WeblateEditorURLValidator, WeblateURLValidator 

19 

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 

22 

23 

24class InvalidString(UserString): 

25 def __mod__(self, other: str): 

26 raise TemplateSyntaxError(gettext('Undefined variable: "%s"') % other) 

27 

28 

29class RestrictedEngine(Engine): 

30 default_builtins: list[str] = [ # noqa: RUF012 

31 "django.template.defaultfilters", 

32 "weblate.utils.templatetags.safe_render", 

33 ] 

34 

35 def __init__(self, *args, **kwargs) -> None: 

36 kwargs["autoescape"] = False 

37 kwargs["string_if_invalid"] = InvalidString("%s") 

38 super().__init__(*args, **kwargs) 

39 

40 

41def render_template(template: str, **kwargs): 

42 """Render string template with Weblate context.""" 

43 from weblate.trans.models import Component, Project, Translation 

44 

45 translation = kwargs.get("translation") 

46 component = kwargs.get("component") 

47 project = kwargs.get("project") 

48 

49 # Comppatibility with older templates 

50 if "addon_name" in kwargs: 

51 kwargs["hook_name"] = kwargs["addon_name"] 

52 

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) 

61 

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) 

87 

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) 

94 

95 kwargs["site_title"] = settings.SITE_TITLE 

96 kwargs["site_url"] = get_site_url() 

97 

98 with override("en"): 

99 return Template(template, engine=RestrictedEngine()).render( 

100 Context(kwargs, autoescape=False) 

101 ) 

102 

103 

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 

112 

113 

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 

118 

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) 

142 

143 

144def validate_render_translation(value: str) -> None: 

145 validate_render_mock(value, translation=True) 

146 

147 

148def validate_render_component(value: str) -> None: 

149 validate_render_mock(value) 

150 

151 

152def validate_render_addon(value: str) -> None: 

153 validate_render_mock(value, hook_name="addon", addon_name="addon") 

154 

155 

156def validate_render_commit(value: str) -> None: 

157 validate_render_mock(value, translation=True, author="author") 

158 

159 

160def validate_repoweb(val: str, allow_editor: bool = False) -> None: 

161 """ 

162 Validate whether URL for repository browser is valid. 

163 

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") 

174 

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) 

184 

185 

186def validate_editor(val: str) -> None: 

187 """ 

188 Validate URL for custom editor link. 

189 

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)