Coverage for app/venv/lib/python3.14/site-packages/weblate/checks/models.py: 41%
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
7import json
8from typing import TYPE_CHECKING
10from appconf import AppConf
11from django.db import models
12from django.db.models import Q
13from django.utils.functional import cached_property
15from weblate.utils.classloader import ClassLoader
17from .base import BaseCheck
19if TYPE_CHECKING: 19 ↛ 20line 19 didn't jump to line 20 because the condition on line 19 was never true
20 from collections.abc import Generator, Iterable
22 from django_stubs_ext import StrOrPromise
24 from weblate.auth.models import User
25 from weblate.trans.models import Unit
27 from .base import FixupType
30class ChecksLoader(ClassLoader[BaseCheck]):
31 def __init__(self) -> None:
32 super().__init__("CHECK_LIST", base_class=BaseCheck)
34 @cached_property
35 def source(self):
36 return {k: v for k, v in self.items() if v.source}
38 @cached_property
39 def target(self):
40 return {k: v for k, v in self.items() if v.target}
42 @cached_property
43 def glossary(self):
44 return {k: v for k, v in self.items() if v.glossary}
47# Initialize checks list
48CHECKS = ChecksLoader()
51class WeblateChecksConf(AppConf):
52 # List of quality checks
53 CHECK_LIST = (
54 "weblate.checks.same.SameCheck",
55 "weblate.checks.chars.BeginNewlineCheck",
56 "weblate.checks.chars.EndNewlineCheck",
57 "weblate.checks.chars.BeginSpaceCheck",
58 "weblate.checks.chars.EndSpaceCheck",
59 "weblate.checks.chars.DoubleSpaceCheck",
60 "weblate.checks.chars.EndStopCheck",
61 "weblate.checks.chars.EndColonCheck",
62 "weblate.checks.chars.EndQuestionCheck",
63 "weblate.checks.chars.EndExclamationCheck",
64 "weblate.checks.chars.EndInterrobangCheck",
65 "weblate.checks.chars.EndEllipsisCheck",
66 "weblate.checks.chars.EndSemicolonCheck",
67 "weblate.checks.chars.MaxLengthCheck",
68 "weblate.checks.chars.KashidaCheck",
69 "weblate.checks.chars.PunctuationSpacingCheck",
70 "weblate.checks.chars.KabyleCharactersCheck",
71 "weblate.checks.format.PythonFormatCheck",
72 "weblate.checks.format.PythonBraceFormatCheck",
73 "weblate.checks.format.PHPFormatCheck",
74 "weblate.checks.format.CFormatCheck",
75 "weblate.checks.format.PerlFormatCheck",
76 "weblate.checks.format.PerlBraceFormatCheck",
77 "weblate.checks.format.JavaScriptFormatCheck",
78 "weblate.checks.format.LuaFormatCheck",
79 "weblate.checks.format.ObjectPascalFormatCheck",
80 "weblate.checks.format.SchemeFormatCheck",
81 "weblate.checks.format.CSharpFormatCheck",
82 "weblate.checks.format.JavaFormatCheck",
83 "weblate.checks.format.JavaMessageFormatCheck",
84 "weblate.checks.format.PercentPlaceholdersCheck",
85 "weblate.checks.format.VueFormattingCheck",
86 "weblate.checks.format.I18NextInterpolationCheck",
87 "weblate.checks.format.ESTemplateLiteralsCheck",
88 "weblate.checks.format.AutomatticComponentsCheck",
89 "weblate.checks.angularjs.AngularJSInterpolationCheck",
90 "weblate.checks.icu.ICUMessageFormatCheck",
91 "weblate.checks.icu.ICUSourceCheck",
92 "weblate.checks.qt.QtFormatCheck",
93 "weblate.checks.qt.QtPluralCheck",
94 "weblate.checks.ruby.RubyFormatCheck",
95 "weblate.checks.consistency.PluralsCheck",
96 "weblate.checks.consistency.SamePluralsCheck",
97 "weblate.checks.consistency.ConsistencyCheck",
98 "weblate.checks.consistency.ReusedCheck",
99 "weblate.checks.consistency.TranslatedCheck",
100 "weblate.checks.chars.EscapedNewlineCountingCheck",
101 "weblate.checks.chars.NewLineCountCheck",
102 "weblate.checks.markup.BBCodeCheck",
103 "weblate.checks.chars.ZeroWidthSpaceCheck",
104 "weblate.checks.render.MaxSizeCheck",
105 "weblate.checks.markup.XMLValidityCheck",
106 "weblate.checks.markup.XMLTagsCheck",
107 "weblate.checks.markup.MarkdownRefLinkCheck",
108 "weblate.checks.markup.MarkdownLinkCheck",
109 "weblate.checks.markup.MarkdownSyntaxCheck",
110 "weblate.checks.markup.URLCheck",
111 "weblate.checks.markup.SafeHTMLCheck",
112 "weblate.checks.markup.RSTReferencesCheck",
113 "weblate.checks.markup.RSTSyntaxCheck",
114 "weblate.checks.placeholders.PlaceholderCheck",
115 "weblate.checks.placeholders.RegexCheck",
116 "weblate.checks.duplicate.DuplicateCheck",
117 "weblate.checks.source.OptionalPluralCheck",
118 "weblate.checks.source.EllipsisCheck",
119 "weblate.checks.source.MultipleFailingCheck",
120 "weblate.checks.source.LongUntranslatedCheck",
121 "weblate.checks.format.MultipleUnnamedFormatsCheck",
122 "weblate.checks.glossary.GlossaryCheck",
123 "weblate.checks.glossary.ProhibitedInitialCharacterCheck",
124 "weblate.checks.fluent.syntax.FluentSourceSyntaxCheck",
125 "weblate.checks.fluent.syntax.FluentTargetSyntaxCheck",
126 "weblate.checks.fluent.parts.FluentPartsCheck",
127 "weblate.checks.fluent.references.FluentReferencesCheck",
128 "weblate.checks.fluent.inner_html.FluentSourceInnerHTMLCheck",
129 "weblate.checks.fluent.inner_html.FluentTargetInnerHTMLCheck",
130 )
132 class Meta:
133 prefix = ""
136class CheckQuerySet(models.QuerySet):
137 def filter_access(self, user: User):
138 result = self
139 if user.needs_project_filter:
140 result = result.filter(
141 unit__translation__component__project__in=user.allowed_projects
142 )
143 if user.needs_component_restrictions_filter:
144 result = result.filter(
145 Q(unit__translation__component__restricted=False)
146 | Q(unit__translation__component_id__in=user.component_permissions)
147 )
148 return result
151class Check(models.Model):
152 unit = models.ForeignKey(
153 "trans.Unit", on_delete=models.deletion.CASCADE, db_index=False
154 )
155 name = models.CharField(max_length=50, choices=CHECKS.get_choices())
156 dismissed = models.BooleanField(db_index=True, default=False)
158 objects = CheckQuerySet.as_manager()
160 class Meta:
161 unique_together = [ # noqa: RUF012
162 ("unit", "name"),
163 ]
164 verbose_name = "Quality check"
165 verbose_name_plural = "Quality checks"
167 def __str__(self) -> str:
168 return str(self.get_name())
170 @cached_property
171 def check_obj(self) -> BaseCheck | None:
172 try:
173 return CHECKS[self.name]
174 except KeyError:
175 return None
177 def is_enforced(self) -> bool:
178 return self.name in self.unit.translation.component.enforced_checks
180 def get_description(self) -> StrOrPromise:
181 if self.check_obj:
182 return self.check_obj.get_description(self)
183 return self.name
185 def get_fixup(self) -> Iterable[FixupType] | None:
186 if self.check_obj:
187 return self.check_obj.get_fixup(self.unit)
188 return None
190 def get_fixup_json(self) -> str | None:
191 fixup = self.get_fixup()
192 if not fixup:
193 return None
194 return json.dumps(fixup)
196 def get_name(self) -> StrOrPromise:
197 if self.check_obj:
198 return self.check_obj.name
199 return self.name
201 def get_doc_url(self, user: User | None = None) -> str:
202 if self.check_obj:
203 return self.check_obj.get_doc_url(user=user)
204 return ""
206 def set_dismiss(self, *, state: bool = True, recurse: bool = True) -> None:
207 """Set ignore flag."""
208 if self.dismissed != state:
209 self.dismissed = state
210 self.save(update_fields=["dismissed"])
211 self.unit.translation.invalidate_cache()
212 if recurse:
213 for child in Check.objects.filter(
214 name=self.name,
215 unit__in=self.unit.propagated_units,
216 dismissed=not state,
217 ).select_for_update():
218 child.set_dismiss(state=state, recurse=False)
221def get_display_checks(unit: Unit) -> Generator[Check]:
222 check_objects = {check.name: check for check in unit.all_checks}
223 for check, check_obj in CHECKS.target.items():
224 if check_obj.should_display(unit):
225 try:
226 yield check_objects[check]
227 except KeyError:
228 yield Check(unit=unit, dismissed=False, name=check)