Coverage for app/venv/lib/python3.14/site-packages/weblate/checks/views.py: 15%
149 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
6from typing import TYPE_CHECKING
8from django.db.models import Count, Q
9from django.http import Http404
10from django.shortcuts import get_object_or_404, redirect
11from django.urls import reverse
12from django.utils.translation import gettext
13from django.views.generic import ListView
15from weblate.auth.models import AuthenticatedHttpRequest
16from weblate.checks.models import CHECKS, Check
17from weblate.lang.models import Language
18from weblate.trans.models import Component, Project, Translation, Unit
19from weblate.utils.random import get_random_identifier
20from weblate.utils.state import STATE_TRANSLATED
21from weblate.utils.stats import ProjectLanguage
22from weblate.utils.views import PathViewMixin
24if TYPE_CHECKING: 24 ↛ 25line 24 didn't jump to line 25 because the condition on line 24 was never true
25 from weblate.auth.models import AuthenticatedHttpRequest
28class CheckWrapper:
29 check_obj: Check | None
31 def __init__(
32 self,
33 name: str,
34 check_count: int,
35 dismissed_check_count: int,
36 active_check_count: int,
37 translated_check_count: int,
38 path_object,
39 ) -> None:
40 self.pk = get_random_identifier()
41 self.name = name
42 try:
43 self.check_obj = CHECKS[name]
44 except KeyError:
45 self.check_obj = None
46 self.check_count = check_count
47 self.dismissed_check_count = dismissed_check_count
48 self.active_check_count = active_check_count
49 self.translated_check_count = translated_check_count
50 self.row_title = self.check_obj.name if self.check_obj else self.name
51 kwargs = {"name": self.name}
52 if path_object is not None:
53 kwargs["path"] = path_object.get_url_path()
54 self.row_url = reverse("checks", kwargs=kwargs)
57class CheckList(PathViewMixin, ListView):
58 supported_path_types = (
59 None,
60 Project,
61 Component,
62 Translation,
63 Language,
64 ProjectLanguage,
65 )
66 template_name = "check_list.html"
67 request: AuthenticatedHttpRequest
69 def annotate(self, queryset, prefix: str):
70 id_field = f"{prefix}unit__check"
71 return queryset.annotate(
72 check_count=Count(id_field),
73 dismissed_check_count=Count(
74 id_field, filter=Q(**{f"{prefix}unit__check__dismissed": True})
75 ),
76 active_check_count=Count(
77 id_field, filter=Q(**{f"{prefix}unit__check__dismissed": False})
78 ),
79 translated_check_count=Count(
80 id_field,
81 filter=Q(
82 **{
83 f"{prefix}unit__check__dismissed": False,
84 f"{prefix}unit__state__gte": STATE_TRANSLATED,
85 },
86 ),
87 ),
88 )
90 def postprocess_queryset(self, result):
91 # Annotate with progress indicator and attributes
92 max_value = max((item.check_count for item in result), default=0)
93 for item in result:
94 item.check_progress = item.check_count * 100 // max_value
95 if not hasattr(item, "check_obj"):
96 item.check_obj = self.check_obj
97 if not hasattr(item, "row_title"):
98 if isinstance(item, Translation):
99 if isinstance(self.path_object, ProjectLanguage):
100 item.row_title = item.component.name
101 else:
102 item.row_title = item.language.name
103 elif isinstance(item, ProjectLanguage):
104 item.row_title = item.project.name
105 else:
106 item.row_title = item.name
107 if not hasattr(item, "row_url"):
108 item.row_url = reverse(
109 "checks",
110 kwargs={
111 "name": self.check_obj.check_id if self.check_obj else "-",
112 "path": item.get_url_path(),
113 },
114 )
115 return result
117 def get_queryset(self):
118 if self.check_obj is None:
119 if self.path_object is None:
120 all_checks = Check.objects.filter_access(self.request.user)
121 elif isinstance(self.path_object, Project):
122 all_checks = Check.objects.filter(
123 unit__translation__component__project=self.path_object
124 )
125 elif isinstance(self.path_object, Component):
126 all_checks = Check.objects.filter(
127 unit__translation__component=self.path_object
128 )
129 elif isinstance(self.path_object, Translation):
130 all_checks = Check.objects.filter(unit__translation=self.path_object)
131 elif isinstance(self.path_object, Language):
132 all_checks = Check.objects.filter(
133 unit__translation__language=self.path_object
134 )
135 elif isinstance(self.path_object, ProjectLanguage):
136 all_checks = Check.objects.filter(
137 unit__translation__language=self.path_object.language,
138 unit__translation__component__project=self.path_object.project,
139 )
140 else:
141 msg = f"Unsupported {self.path_object}"
142 raise TypeError(msg)
143 result = [
144 CheckWrapper(**item, path_object=self.path_object)
145 for item in all_checks.values("name").annotate(
146 check_count=Count("id"),
147 dismissed_check_count=Count("id", filter=Q(dismissed=True)),
148 active_check_count=Count("id", filter=Q(dismissed=False)),
149 translated_check_count=Count(
150 "id",
151 filter=Q(dismissed=False, unit__state__gte=STATE_TRANSLATED),
152 ),
153 )
154 ]
155 elif self.path_object is None:
156 result = self.annotate(
157 self.request.user.allowed_projects.filter(
158 component__translation__unit__check__name=self.check_obj.check_id
159 ),
160 "component__translation__",
161 ).order()
162 elif isinstance(self.path_object, Project):
163 result = self.annotate(
164 Component.objects.filter_access(self.request.user)
165 .filter(
166 translation__unit__check__name=self.check_obj.check_id,
167 project=self.path_object,
168 )
169 .prefetch_related("project"),
170 "translation__",
171 ).order()
172 elif isinstance(self.path_object, Component):
173 result = self.annotate(
174 Translation.objects.filter(
175 component=self.path_object,
176 unit__check__name=self.check_obj.check_id,
177 )
178 .select_related("language")
179 .prefetch_related("component__project"),
180 "",
181 ).order_by("language__code")
182 elif isinstance(self.path_object, Language):
183 result = [
184 ProjectLanguage(project=obj, language=self.path_object)
185 for obj in self.annotate(
186 self.request.user.allowed_projects.filter(
187 component__translation__language=self.path_object,
188 component__translation__unit__check__name=self.check_obj.check_id,
189 ),
190 "component__translation__",
191 ).order()
192 ]
193 # Mirror counts
194 for item in result:
195 item.check_count = item.project.check_count
196 item.dismissed_check_count = item.project.dismissed_check_count
197 item.active_check_count = item.project.active_check_count
198 item.translated_check_count = item.project.translated_check_count
199 elif isinstance(self.path_object, ProjectLanguage):
200 result = self.annotate(
201 Translation.objects.filter(
202 component__project=self.path_object.project,
203 language=self.path_object.language,
204 unit__check__name=self.check_obj.check_id,
205 )
206 .select_related("language")
207 .prefetch_related("component__project"),
208 "",
209 ).order_by("language__code")
210 else:
211 # Translation should never reach this, it is handled in get()
212 msg = f"Unsupported {self.path_object}"
213 raise TypeError(msg)
215 return self.postprocess_queryset(result)
217 def get_context_data(self, **kwargs):
218 context = super().get_context_data(**kwargs)
219 context["check"] = self.check_obj
220 context["path_object"] = self.path_object
221 context["bootstrap_5"] = True
222 if self.check_obj is None:
223 if self.path_object is None:
224 context["title"] = gettext("Failing checks")
225 else:
226 context["title"] = f"{gettext('Failing checks')} / {self.path_object}"
227 elif self.path_object is None:
228 context["title"] = self.check_obj.name
229 else:
230 context["title"] = f"{self.check_obj.name} / {self.path_object}"
231 if self.check_obj is None:
232 context["column_title"] = gettext("Check")
233 elif self.path_object is None:
234 context["column_title"] = gettext("Project")
235 elif isinstance(self.path_object, Project):
236 context["column_title"] = gettext("Component")
237 elif isinstance(self.path_object, Component):
238 context["column_title"] = gettext("Translation")
239 context["translate_links"] = True
240 elif isinstance(self.path_object, Language):
241 context["column_title"] = gettext("Project")
242 elif isinstance(self.path_object, ProjectLanguage):
243 context["column_title"] = gettext("Component")
244 context["translate_links"] = True
245 else:
246 msg = f"Type not supported: {self.path_object}"
247 raise TypeError(msg)
248 return context
250 def setup(self, request: AuthenticatedHttpRequest, **kwargs) -> None:
251 super().setup(request, **kwargs)
252 self.check_obj = None
253 name = kwargs.get("name")
254 if name and name != "-":
255 try:
256 self.check_obj = CHECKS[name]
257 except KeyError as error:
258 msg = "No check matches the given query."
259 raise Http404(msg) from error
261 def get(self, request: AuthenticatedHttpRequest, *args, **kwargs):
262 if isinstance(self.path_object, Translation) and self.check_obj:
263 return redirect(
264 f"{self.path_object.get_translate_url()}?q={self.check_obj.url_id} OR dismissed_{self.check_obj.url_id}"
265 )
266 return super().get(request, *args, **kwargs)
269def render_check(request: AuthenticatedHttpRequest, unit_id, check_id):
270 """Render endpoint for checks."""
271 try:
272 obj = Check.objects.get(unit_id=unit_id, name=check_id)
273 except Check.DoesNotExist:
274 unit = get_object_or_404(Unit, pk=int(unit_id))
275 obj = Check(unit=unit, dismissed=False, name=check_id)
276 request.user.check_access_component(obj.unit.translation.component)
278 if obj.check_obj is None:
279 msg = "No check object found."
280 raise Http404(msg)
282 return obj.check_obj.render(request, obj.unit)