Coverage for app/venv/lib/python3.14/site-packages/weblate/addons/views.py: 18%

149 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 

4 

5from __future__ import annotations 

6 

7from typing import TYPE_CHECKING 

8 

9from django.core.exceptions import PermissionDenied 

10from django.shortcuts import redirect 

11from django.urls import reverse 

12from django.utils.translation import gettext 

13from django.views.generic import DetailView, ListView, UpdateView 

14 

15from weblate.addons.models import ADDONS, Addon 

16from weblate.trans.models import Change, Component, Project 

17from weblate.utils import messages 

18from weblate.utils.views import PathViewMixin, get_paginator 

19 

20if TYPE_CHECKING: 20 ↛ 21line 20 didn't jump to line 21 because the condition on line 20 was never true

21 from weblate.auth.models import AuthenticatedHttpRequest 

22 

23 

24class AddonList(PathViewMixin, ListView): 

25 paginate_by = None 

26 model = Addon 

27 supported_path_types = (None, Component, Project) 

28 path_object: Component | Project | None 

29 request: AuthenticatedHttpRequest 

30 

31 def get_queryset(self): 

32 if isinstance(self.path_object, Component): 

33 if not self.request.user.has_perm("component.edit", self.path_object): 

34 msg = "Can not edit component" 

35 raise PermissionDenied(msg) 

36 self.kwargs["component_obj"] = self.path_object 

37 return Addon.objects.filter_component(self.path_object) 

38 if isinstance(self.path_object, Project): 

39 if not self.request.user.has_perm("project.edit", self.path_object): 

40 msg = "Can not edit project" 

41 raise PermissionDenied(msg) 

42 self.kwargs["project_obj"] = self.path_object 

43 return Addon.objects.filter_project(self.path_object) 

44 

45 if not self.request.user.has_perm("management.addons"): 

46 msg = "Can not manage add-ons" 

47 raise PermissionDenied(msg) 

48 return Addon.objects.filter_sitewide() 

49 

50 def get_success_url(self): 

51 if self.path_object is None: 

52 return reverse("manage-addons") 

53 return reverse("addons", kwargs={"path": self.path_object.get_url_path()}) 

54 

55 def redirect_list(self, message=None): 

56 if message: 

57 messages.error(self.request, message) 

58 return redirect(self.get_success_url()) 

59 

60 def get_context_data(self, **kwargs): 

61 result = super().get_context_data(**kwargs) 

62 target = self.path_object 

63 result["object"] = target 

64 result["change_actions"] = Change.ACTIONS_ADDON 

65 if target is None: 

66 result["last_changes"] = Change.objects.filter( 

67 action__in=Change.ACTIONS_ADDON 

68 ).order()[:10] 

69 elif isinstance(target, Component): 

70 result["last_changes"] = target.change_set.filter( 

71 action__in=Change.ACTIONS_ADDON 

72 ).order()[:10] 

73 else: 

74 result["last_changes"] = target.change_set.filter( 

75 action__in=Change.ACTIONS_ADDON, component=None 

76 ).order()[:10] 

77 installed = {x.addon_name for x in result["object_list"]} 

78 

79 if isinstance(target, Component): 

80 result["available"] = sorted( 

81 ( 

82 x(Addon()) 

83 for x in ADDONS.values() 

84 if x.can_install(target, self.request.user) 

85 and (x.multiple or x.name not in installed) 

86 ), 

87 key=lambda x: x.name, 

88 ) 

89 result["scope"] = "component" 

90 result["project_addons"] = Addon.objects.filter_project( 

91 target.project 

92 ).count() 

93 else: 

94 # This covers both project-wide and site-wide 

95 result["available"] = sorted( 

96 ( 

97 x(Addon()) 

98 for x in ADDONS.values() 

99 if (x.multiple or x.name not in installed) 

100 ), 

101 key=lambda x: x.name, 

102 ) 

103 result["scope"] = "sitewide" if target is None else "project" 

104 

105 if target is not None: 

106 result["sitewide_addons"] = Addon.objects.filter_sitewide().count() 

107 

108 return result 

109 

110 def post(self, request: AuthenticatedHttpRequest, **kwargs): 

111 obj = self.path_object 

112 obj_component, obj_project = None, None 

113 

114 if isinstance(obj, Component): 

115 obj_component = obj 

116 elif isinstance(obj, Project): 

117 obj_project = obj 

118 

119 name = request.POST.get("name") 

120 if not name: 

121 return self.redirect_list(gettext("Invalid add-on name: ”%s”") % name) 

122 addon = ADDONS.get(name) 

123 if addon is None: 

124 return self.redirect_list(gettext("Invalid add-on name: ”%s”") % name) 

125 installed = {x.addon.name for x in self.get_queryset()} 

126 if (obj_component and not addon.can_install(obj_component, request.user)) or ( 

127 name in installed and not addon.multiple 

128 ): 

129 return self.redirect_list( 

130 gettext("Add-on cannot be installed: ”%s”") % name 

131 ) 

132 

133 form = None 

134 if addon.settings_form is None: 

135 addon.create( 

136 component=obj_component, project=obj_project, acting_user=request.user 

137 ) 

138 return self.redirect_list() 

139 

140 if "form" in request.POST: 

141 form = addon.get_add_form( 

142 request.user, 

143 component=obj_component, 

144 project=obj_project, 

145 data=request.POST, 

146 ) 

147 if form.is_valid(): 

148 instance = form.save() 

149 if addon.stay_on_create: 

150 messages.info( 

151 self.request, 

152 gettext( 

153 "Add-on installed, please review integration instructions." 

154 ), 

155 ) 

156 return redirect(instance) 

157 return self.redirect_list() 

158 else: 

159 form = addon.get_add_form( 

160 request.user, component=obj_component, project=obj_project 

161 ) 

162 

163 addon.pre_install(obj, request) 

164 return self.response_class( 

165 request=self.request, 

166 template=["addons/addon_detail.html"], 

167 context={ 

168 "addon": addon(Addon()), 

169 "form": form, 

170 "object": self.path_object, 

171 }, 

172 ) 

173 

174 

175class BaseAddonView(DetailView): 

176 model = Addon 

177 request: AuthenticatedHttpRequest 

178 

179 def get_object(self): # type: ignore[override] 

180 obj = super().get_object() 

181 if obj.component and not self.request.user.has_perm( 

182 "component.edit", obj.component 

183 ): 

184 msg = "Can not edit component" 

185 raise PermissionDenied(msg) 

186 if obj.project and not self.request.user.has_perm("project.edit", obj.project): 

187 msg = "Can not edit project" 

188 raise PermissionDenied(msg) 

189 if ( 

190 obj.project is None 

191 and obj.component is None 

192 and not self.request.user.has_perm("management.addons") 

193 ): 

194 msg = "Can not manage add-ons" 

195 raise PermissionDenied(msg) 

196 return obj 

197 

198 

199class AddonDetail(BaseAddonView, UpdateView): 

200 template_name_suffix = "_detail" 

201 object: Addon 

202 

203 def get_form(self, form_class=None): 

204 return self.object.addon.get_settings_form( 

205 self.request.user, **self.get_form_kwargs() 

206 ) 

207 

208 def get_context_data(self, **kwargs): 

209 result = super().get_context_data(**kwargs) 

210 if self.object.project: 

211 result["object"] = self.object.project 

212 else: 

213 result["object"] = self.object.component 

214 result["instance"] = self.object 

215 result["addon"] = self.object.addon 

216 return result 

217 

218 def get_success_url(self): 

219 target = self.object.component or self.object.project 

220 if target is None: 

221 return reverse("manage-addons") 

222 return reverse("addons", kwargs={"path": target.get_url_path()}) 

223 

224 def post(self, request: AuthenticatedHttpRequest, *args, **kwargs): # type: ignore[override] 

225 obj = self.get_object() 

226 obj.acting_user = request.user 

227 if "delete" in request.POST: 

228 target = obj.component or obj.project 

229 obj.delete() 

230 if target is None: 

231 return redirect(reverse("manage-addons")) 

232 return redirect(reverse("addons", kwargs={"path": target.get_url_path()})) 

233 return super().post(request, *args, **kwargs) 

234 

235 

236class AddonLogs(BaseAddonView): 

237 template_name_suffix = "_logs" 

238 

239 def get_context_data(self, **kwargs): 

240 result = super().get_context_data(**kwargs) 

241 if self.object.project: 

242 result["object"] = self.object.project 

243 else: 

244 result["object"] = self.object.component 

245 result["instance"] = self.object 

246 result["addon"] = self.object.addon 

247 result["addon_activity_log"] = get_paginator( 

248 self.request, 

249 self.object.get_addon_activity_logs(), 

250 ) 

251 return result