Coverage for app/venv/lib/python3.14/site-packages/weblate/trans/views/widgets.py: 24%

103 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 

4from __future__ import annotations 

5 

6import json 

7from typing import TYPE_CHECKING 

8 

9from django.http import Http404, HttpResponse 

10from django.shortcuts import redirect 

11from django.urls import reverse 

12from django.utils.html import format_html 

13from django.utils.translation import gettext 

14from django.views.decorators.cache import cache_control 

15from django.views.decorators.vary import vary_on_cookie 

16from django.views.generic import RedirectView 

17 

18from weblate.lang.models import Language 

19from weblate.trans.forms import EngageForm 

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

21from weblate.trans.util import render 

22from weblate.trans.widgets import WIDGETS, OpenGraphWidget 

23from weblate.utils.site import get_site_url 

24from weblate.utils.stats import ProjectLanguage 

25from weblate.utils.views import parse_path, show_form_errors, try_set_language 

26 

27if TYPE_CHECKING: 27 ↛ 28line 27 didn't jump to line 28 because the condition on line 27 was never true

28 from weblate.auth.models import AuthenticatedHttpRequest 

29 

30 

31def widgets_sorter(widget): 

32 """Provide better ordering of widgets.""" 

33 return WIDGETS[widget].order 

34 

35 

36def widgets(request: AuthenticatedHttpRequest, path: list[str]): 

37 engage_obj = project = parse_path(request, path, (Project,)) 

38 

39 # Parse possible language selection 

40 form = EngageForm(request.user, project, request.GET) 

41 lang = None 

42 component = None 

43 if form.is_valid(): 

44 lang = form.cleaned_data["lang"] 

45 component = form.cleaned_data["component"] 

46 else: 

47 show_form_errors(request, form) 

48 

49 if component: 

50 if lang: 

51 try: 

52 obj = component.translation_set.get(language=lang) 

53 except Translation.DoesNotExist: 

54 obj = component 

55 engage_obj = ProjectLanguage(project=component.project, language=lang) 

56 else: 

57 obj = component 

58 elif lang: 

59 engage_obj = obj = ProjectLanguage(project=project, language=lang) 

60 else: 

61 obj = project 

62 

63 engage_url = get_site_url( 

64 reverse("engage", kwargs={"path": engage_obj.get_url_path()}) 

65 ) 

66 engage_link = format_html('<a href="{0}" id="engage-link">{0}</a>', engage_url) 

67 

68 engage_base_url = get_site_url( 

69 reverse("engage", kwargs={"path": project.get_url_path()}) 

70 ) 

71 widget_base_url = f"{get_site_url()}/widget/{'/'.join(project.get_url_path())}" 

72 

73 widget_list = [] 

74 widgets_json = {} 

75 for widget_name in sorted(WIDGETS, key=widgets_sorter): 

76 widget_class = WIDGETS[widget_name] 

77 widgets_json[widget_name] = { 

78 "name": widget_name, 

79 "extension": widget_class.extension, 

80 "colors": widget_class.colors, 

81 "extra_parameters": widget_class.extra_parameters, 

82 } 

83 widget_list.append({"name": widget_name, "verbose": widget_class.verbose}) 

84 

85 return render( 

86 request, 

87 "widgets.html", 

88 { 

89 "engage_link": engage_link, 

90 "widget_list": widget_list, 

91 "object": obj, 

92 "project": project, 

93 "form": form, 

94 "widgets_json": json.dumps( 

95 { 

96 "widget_base_url": widget_base_url, 

97 "engage_base_url": engage_base_url, 

98 "language": lang.code if lang else None, 

99 "component": component.id if component else None, 

100 "components": [ 

101 {"id": c.id, "slug": c.slug} 

102 for c in form.fields["component"].queryset 

103 ], 

104 "translation_status": gettext("Translation status"), 

105 "widgets": widgets_json, 

106 } 

107 ), 

108 }, 

109 ) 

110 

111 

112class WidgetRedirectView(RedirectView): 

113 permanent = True 

114 query_string = True 

115 

116 def get_redirect_url( 

117 self, 

118 project: str, 

119 widget: str, 

120 color: str, 

121 extension: str, 

122 lang: str | None = None, 

123 component: str | None = None, 

124 ): 

125 path = [project] 

126 if component: 

127 path.append(component) 

128 if lang: 

129 if not component: 

130 path.append("-") 

131 path.append(lang) 

132 # Redirect no longer supported badge styles to svg 

133 if widget == "shields": 

134 widget = "svg" 

135 return reverse( 

136 "widget-image", 

137 kwargs={ 

138 "color": color, 

139 "extension": extension, 

140 "widget": widget, 

141 "path": path, 

142 }, 

143 ) 

144 

145 

146@vary_on_cookie 

147@cache_control(max_age=3600) 

148def render_widget( 

149 request: AuthenticatedHttpRequest, 

150 path: list[str], 

151 widget: str, 

152 color: str, 

153 extension: str, 

154): 

155 # We intentionally skip ACL here to allow widget sharing 

156 obj = parse_path( 

157 None, 

158 path, 

159 (Component, ProjectLanguage, Project, Translation, Language, None), 

160 ) 

161 lang = set_lang = None 

162 if isinstance(obj, Language): 

163 set_lang = obj 

164 elif hasattr(obj, "language"): 

165 set_lang = lang = obj.language 

166 if isinstance(obj, Translation): 

167 obj = obj.component 

168 if isinstance(obj, ProjectLanguage): 

169 obj = obj.project 

170 

171 if set_lang: 

172 if "native" not in request.GET: 

173 try_set_language(set_lang.code) 

174 else: 

175 try_set_language("en") 

176 

177 # Get widget class 

178 try: 

179 widget_class = WIDGETS[widget] 

180 except KeyError as error: 

181 raise Http404 from error 

182 

183 # Construct object 

184 widget_obj = widget_class(obj, color, lang) 

185 

186 # Invalid extension 

187 if extension != widget_obj.extension or color != widget_obj.color: 

188 kwargs = { 

189 "path": path, 

190 "widget": widget, 

191 "color": widget_obj.color, 

192 "extension": widget_obj.extension, 

193 } 

194 return redirect("widget-image", permanent=True, **kwargs) 

195 

196 # Render widget 

197 response = HttpResponse(content_type=widget_obj.content_type) 

198 widget_obj.render(request, response) 

199 return response 

200 

201 

202@vary_on_cookie 

203@cache_control(max_age=3600) 

204def render_og(request: AuthenticatedHttpRequest): 

205 # Construct object 

206 widget_obj = OpenGraphWidget(None) 

207 

208 # Render widget 

209 response = HttpResponse(content_type=widget_obj.content_type) 

210 widget_obj.render(request, response) 

211 return response