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
« 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
7from typing import TYPE_CHECKING
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
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
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
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
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)
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()
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()})
55 def redirect_list(self, message=None):
56 if message:
57 messages.error(self.request, message)
58 return redirect(self.get_success_url())
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"]}
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"
105 if target is not None:
106 result["sitewide_addons"] = Addon.objects.filter_sitewide().count()
108 return result
110 def post(self, request: AuthenticatedHttpRequest, **kwargs):
111 obj = self.path_object
112 obj_component, obj_project = None, None
114 if isinstance(obj, Component):
115 obj_component = obj
116 elif isinstance(obj, Project):
117 obj_project = obj
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 )
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()
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 )
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 )
175class BaseAddonView(DetailView):
176 model = Addon
177 request: AuthenticatedHttpRequest
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
199class AddonDetail(BaseAddonView, UpdateView):
200 template_name_suffix = "_detail"
201 object: Addon
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 )
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
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()})
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)
236class AddonLogs(BaseAddonView):
237 template_name_suffix = "_logs"
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