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

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4from __future__ import annotations 

5 

6import os 

7from typing import TYPE_CHECKING 

8 

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 

14 

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) 

36 

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 

39 

40 

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] = {} 

51 

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) 

60 

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 

67 

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) 

97 

98 return zip_download(data_dir("vcs"), sorted(filenames), name, extra=extra) 

99 

100 

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 ) 

113 

114 

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 

124 

125 if isinstance(obj, Translation): 

126 kwargs = {} 

127 

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) 

133 

134 kwargs["query_string"] = form.cleaned_data.get("q", "") 

135 kwargs["fmt"] = form.cleaned_data["format"] 

136 

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) 

192 

193 

194@require_POST 

195def upload(request: AuthenticatedHttpRequest, path): 

196 """Handle translation upload.""" 

197 obj = parse_path(request, path, (Translation,)) 

198 

199 if not request.user.has_perm("upload.perform", obj): 

200 raise PermissionDenied 

201 

202 # Check method and lock 

203 if obj.component.locked: 

204 messages.error(request, gettext("Access denied.")) 

205 return redirect(obj) 

206 

207 # Get correct form handler based on permissions 

208 form = get_upload_form(request.user, obj, request.POST, request.FILES) 

209 

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) 

215 

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"] 

222 

223 # Check for overwriting 

224 conflicts = "" 

225 if request.user.has_perm("upload.overwrite", obj): 

226 conflicts = form.cleaned_data["conflicts"] 

227 

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) 

261 

262 return redirect(obj)