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
« 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
6import json
7from typing import TYPE_CHECKING
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
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
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
31def widgets_sorter(widget):
32 """Provide better ordering of widgets."""
33 return WIDGETS[widget].order
36def widgets(request: AuthenticatedHttpRequest, path: list[str]):
37 engage_obj = project = parse_path(request, path, (Project,))
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)
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
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)
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())}"
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})
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 )
112class WidgetRedirectView(RedirectView):
113 permanent = True
114 query_string = True
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 )
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
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")
177 # Get widget class
178 try:
179 widget_class = WIDGETS[widget]
180 except KeyError as error:
181 raise Http404 from error
183 # Construct object
184 widget_obj = widget_class(obj, color, lang)
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)
196 # Render widget
197 response = HttpResponse(content_type=widget_obj.content_type)
198 widget_obj.render(request, response)
199 return response
202@vary_on_cookie
203@cache_control(max_age=3600)
204def render_og(request: AuthenticatedHttpRequest):
205 # Construct object
206 widget_obj = OpenGraphWidget(None)
208 # Render widget
209 response = HttpResponse(content_type=widget_obj.content_type)
210 widget_obj.render(request, response)
211 return response