Coverage for app/venv/lib/python3.14/site-packages/weblate/trans/management/commands/__init__.py: 0%
106 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
5"""Helper classes for management commands."""
7from __future__ import annotations
9from typing import TYPE_CHECKING
11from django.core.management.base import CommandError
12from django.db import transaction
14from weblate.lang.models import Language
15from weblate.trans.models import Component, Translation, Unit
16from weblate.utils.management.base import BaseCommand
18if TYPE_CHECKING:
19 from django.core.management.base import CommandParser
22class WeblateComponentCommand(BaseCommand):
23 """Command which accepts project/component/--all params to process."""
25 needs_repo = False
27 def add_arguments(self, parser: CommandParser) -> None:
28 parser.add_argument(
29 "--all",
30 action="store_true",
31 dest="all",
32 default=False,
33 help="process all components",
34 )
35 parser.add_argument(
36 "--file-format",
37 help="process all components using given file format",
38 )
39 parser.add_argument(
40 "component",
41 nargs="*",
42 help="Slug <project/component> of component to process",
43 )
45 def get_units(self, **options):
46 """Return list of units matching parameters."""
47 if options["all"]:
48 return Unit.objects.all()
49 return Unit.objects.filter(
50 translation__component__in=self.get_components(**options)
51 )
53 def iterate_units(self, **options):
54 """Memory effective iteration over units."""
55 units = self.get_units(**options).order_by("pk")
56 count = units.count()
57 if not count:
58 return
60 current = 0
61 last = units.order_by("-pk")[0].pk
62 done = 0
63 step = 1000
65 # Iterate over chunks
66 while current < last:
67 self.stdout.write(f"Processing {done * 100.0 / count:.1f}%")
68 with transaction.atomic():
69 step_units = units.filter(pk__gt=current)[:step].prefetch()
70 for unit in step_units:
71 current = unit.pk
72 done += 1
73 yield unit
74 self.stdout.write("Operation completed")
76 def get_translations(self, **options):
77 """Return list of translations matching parameters."""
78 return Translation.objects.prefetch().filter(
79 component__in=self.get_components(**options)
80 )
82 def get_components(self, **options):
83 """Return list of components matching parameters."""
84 if self.needs_repo:
85 base = Component.objects.exclude(repo__startswith="weblate:/")
86 else:
87 base = Component.objects.all()
88 if options["all"]:
89 # all components
90 result = base
91 elif options["file_format"]:
92 # all components
93 result = base.filter(file_format=options["file_format"])
94 elif options["component"]:
95 # start with none and add found
96 result = Component.objects.none()
98 # process arguments
99 for arg in options["component"]:
100 if "/" in arg:
101 # filter by component
102 found = base.filter_by_path(arg)
103 else:
104 # filter by project
105 found = base.filter(project__slug=arg)
107 # warn on no match
108 if not found.exists():
109 self.stderr.write(f"{arg!r} did not match any components")
110 msg = "Nothing to process!"
111 raise CommandError(msg)
113 # merge results
114 result |= found
115 else:
116 # no arguments to filter projects
117 self.stderr.write("Missing component selection!")
118 self.stderr.write(" * Use --all to select all components")
119 self.stderr.write(" * Use --file-format to filter based on the file format")
120 self.stderr.write(" * Specify at least one <project/component> argument")
121 msg = "Nothing to process!"
122 raise CommandError(msg)
124 return result
126 def handle(self, *args, **options) -> None:
127 raise NotImplementedError
130class WeblateLangCommand(WeblateComponentCommand):
131 """
132 Command accepting additional language parameter.
134 It can filter list of languages to process.
135 """
137 def add_arguments(self, parser: CommandParser) -> None:
138 super().add_arguments(parser)
139 parser.add_argument(
140 "--lang",
141 action="store",
142 dest="lang",
143 default=None,
144 help="Limit only to given languages (comma separated list)",
145 )
147 def get_units(self, **options):
148 """Return list of units matching parameters."""
149 units = super().get_units(**options)
151 if options["lang"] is not None:
152 units = units.filter(translation__language__code=options["lang"])
154 return units
156 def get_translations(self, **options):
157 """Return list of translations matching parameters."""
158 result = super().get_translations(**options)
160 if options["lang"] is not None:
161 langs = options["lang"].split(",")
162 result = result.filter(language_code__in=langs)
164 return result
166 def handle(self, *args, **options) -> None:
167 raise NotImplementedError
170class WeblateTranslationCommand(BaseCommand):
171 """Command with target of one translation."""
173 def add_arguments(self, parser: CommandParser) -> None:
174 parser.add_argument("project", help="Slug of project")
175 parser.add_argument("component", help="Slug of component")
176 parser.add_argument("language", help="Slug of language")
178 def get_translation(self, **options):
179 """Get translation object."""
180 try:
181 component = Component.objects.get(
182 project__slug=options["project"], slug=options["component"]
183 )
184 except Component.DoesNotExist as error:
185 msg = "No matching translation component found!"
186 raise CommandError(msg) from error
187 try:
188 return Translation.objects.get(
189 component=component, language__code=options["language"]
190 )
191 except Translation.DoesNotExist as error:
192 if options.get("add"):
193 language = Language.objects.fuzzy_get(options["language"])
194 if component.add_new_language(language, None):
195 return Translation.objects.get(
196 component=component, language=language
197 )
198 msg = "No matching translation project found!"
199 raise CommandError(msg) from error
201 def handle(self, *args, **options) -> None:
202 raise NotImplementedError