Coverage for app/venv/lib/python3.14/site-packages/weblate/trans/views/files.py: 31%
129 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 os
7from typing import TYPE_CHECKING
9from django.core.exceptions import PermissionDenied
10from django.http import Http404
11from django.shortcuts import get_object_or_404, redirect
12from django.utils.translation import gettext
13from django.views.decorators.http import require_POST
15from weblate.formats.models import EXPORTERS
16from weblate.trans.exceptions import FailedCommitError, PluralFormsMismatchError
17from weblate.trans.forms import DownloadForm, get_upload_form
18from weblate.trans.models import (
19 Category,
20 Component,
21 ComponentList,
22 Project,
23 Translation,
24)
25from weblate.utils import messages
26from weblate.utils.data import data_dir
27from weblate.utils.errors import report_error
28from weblate.utils.files import get_upload_message
29from weblate.utils.stats import CategoryLanguage, ProjectLanguage
30from weblate.utils.views import (
31 download_translation_file,
32 parse_path,
33 show_form_errors,
34 zip_download,
35)
37if TYPE_CHECKING: 37 ↛ 38line 37 didn't jump to line 38 because the condition on line 37 was never true
38 from weblate.auth.models import AuthenticatedHttpRequest
41def download_multi(
42 request: AuthenticatedHttpRequest,
43 translations,
44 commit_objs,
45 fmt=None,
46 name="translations",
47):
48 filenames = set()
49 components = set()
50 extra: dict[str, str | bytes] = {}
52 for obj in commit_objs:
53 try:
54 obj.commit_pending("download", None)
55 except Exception:
56 if isinstance(obj, Project):
57 report_error("Download commit", project=obj)
58 else:
59 report_error("Download commit", project=obj.project)
61 if fmt and fmt.startswith("zip:"): 61 ↛ 62line 61 didn't jump to line 62 because the condition on line 61 was never true
62 try:
63 exporter_cls = EXPORTERS[fmt[4:]]
64 except KeyError as exc:
65 msg = f"Conversion to {fmt} is not supported"
66 raise Http404(msg) from exc
68 for translation in translations:
69 exporter = exporter_cls(translation=translation)
70 filename = exporter.get_filename()
71 if not exporter_cls.supports(translation):
72 extra[f"{filename}.skipped"] = (
73 "File format is not compatible with this translation"
74 )
75 else:
76 units = translation.unit_set.prefetch_full().order_by("position")
77 exporter.add_units(units)
78 extra[filename] = exporter.serialize()
79 else:
80 for translation in translations:
81 # Add translation files
82 if translation.filename:
83 filenames.add(translation.get_filename())
84 # Add templates for all components
85 if translation.component_id in components:
86 continue
87 components.add(translation.component_id)
88 for filename in (
89 translation.component.template,
90 translation.component.new_base,
91 translation.component.intermediate,
92 ):
93 if filename:
94 fullname = os.path.join(translation.component.full_path, filename)
95 if os.path.exists(fullname): 95 ↛ 88line 95 didn't jump to line 88 because the condition on line 95 was always true
96 filenames.add(fullname)
98 return zip_download(data_dir("vcs"), sorted(filenames), name, extra=extra)
101def download_component_list(request: AuthenticatedHttpRequest, name):
102 obj = get_object_or_404(ComponentList, slug__iexact=name)
103 if not request.user.has_perm("translation.download", obj):
104 raise PermissionDenied
105 components = obj.components.filter_access(request.user)
106 return download_multi(
107 request,
108 Translation.objects.filter(component__in=components),
109 components,
110 request.GET.get("format"),
111 name=obj.slug,
112 )
115def download(request: AuthenticatedHttpRequest, path):
116 """Download translation."""
117 obj = parse_path(
118 request,
119 path,
120 (Translation, Component, Project, ProjectLanguage, Category, CategoryLanguage),
121 )
122 if not request.user.has_perm("translation.download", obj):
123 raise PermissionDenied
125 if isinstance(obj, Translation):
126 kwargs = {}
128 if "format" in request.GET or "q" in request.GET:
129 form = DownloadForm(obj, request.GET)
130 if not form.is_valid():
131 show_form_errors(request, form)
132 return redirect(obj)
134 kwargs["query_string"] = form.cleaned_data.get("q", "")
135 kwargs["fmt"] = form.cleaned_data["format"]
137 return download_translation_file(request, obj, **kwargs)
138 if isinstance(obj, ProjectLanguage):
139 components = obj.project.component_set.filter_access(request.user)
140 return download_multi(
141 request,
142 Translation.objects.filter(
143 component__in=components, language=obj.language
144 ).prefetch(),
145 [obj.project],
146 request.GET.get("format"),
147 name=f"{obj.project.slug}-{obj.language.code}",
148 )
149 if isinstance(obj, Project):
150 components = obj.component_set.filter_access(request.user)
151 return download_multi(
152 request,
153 Translation.objects.filter(component__in=components).prefetch(),
154 [obj],
155 request.GET.get("format"),
156 name=obj.slug,
157 )
158 if isinstance(obj, CategoryLanguage):
159 components = obj.category.project.component_set.filter_access(
160 request.user
161 ).filter(pk__in=obj.category.all_component_ids)
162 return download_multi(
163 request,
164 Translation.objects.filter(
165 component__in=components, language=obj.language
166 ).prefetch(),
167 [obj.category.project],
168 request.GET.get("format"),
169 name=f"{obj.category.slug}-{obj.language.code}",
170 )
171 if isinstance(obj, Category):
172 components = obj.project.component_set.filter_access(request.user).filter(
173 pk__in=obj.all_component_ids
174 )
175 return download_multi(
176 request,
177 Translation.objects.filter(component__in=components).prefetch(),
178 [obj.project],
179 request.GET.get("format"),
180 name=obj.slug,
181 )
182 if isinstance(obj, Component):
183 return download_multi(
184 request,
185 obj.translation_set.prefetch_meta(),
186 [obj],
187 request.GET.get("format"),
188 name=obj.full_slug.replace("/", "-"),
189 )
190 msg = f"Unsupported download: {obj}"
191 raise TypeError(msg)
194@require_POST
195def upload(request: AuthenticatedHttpRequest, path):
196 """Handle translation upload."""
197 obj = parse_path(request, path, (Translation,))
199 if not request.user.has_perm("upload.perform", obj):
200 raise PermissionDenied
202 # Check method and lock
203 if obj.component.locked:
204 messages.error(request, gettext("Access denied."))
205 return redirect(obj)
207 # Get correct form handler based on permissions
208 form = get_upload_form(request.user, obj, request.POST, request.FILES)
210 # Check form validity
211 if not form.is_valid():
212 messages.error(request, gettext("Please fix errors in the form."))
213 show_form_errors(request, form)
214 return redirect(obj)
216 # Create author name
217 author_name = None
218 author_email = None
219 if request.user.has_perm("upload.authorship", obj):
220 author_name = form.cleaned_data["author_name"]
221 author_email = form.cleaned_data["author_email"]
223 # Check for overwriting
224 conflicts = ""
225 if request.user.has_perm("upload.overwrite", obj):
226 conflicts = form.cleaned_data["conflicts"]
228 # Do actual import
229 try:
230 not_found, skipped, accepted, total = obj.handle_upload(
231 request,
232 request.FILES["file"],
233 conflicts,
234 author_name,
235 author_email,
236 method=form.cleaned_data["method"],
237 fuzzy=form.cleaned_data["fuzzy"],
238 )
239 message = get_upload_message(not_found, skipped, accepted, total)
240 if accepted == 0:
241 messages.warning(request, message)
242 else:
243 messages.success(request, message)
244 except PluralFormsMismatchError:
245 messages.error(
246 request,
247 gettext(
248 "Plural forms in the uploaded file do not match current translation."
249 ),
250 )
251 except FailedCommitError as error:
252 messages.error(request, str(error))
253 report_error("Upload error", project=obj.component.project)
254 except Exception as error:
255 messages.error(
256 request,
257 gettext("File upload has failed: %s")
258 % str(error).replace(obj.component.full_path, ""),
259 )
260 report_error("Upload error", project=obj.component.project)
262 return redirect(obj)