Coverage for app/venv/lib/python3.14/site-packages/weblate/auth/views.py: 18%
143 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
6from typing import TYPE_CHECKING
8from django.core.exceptions import PermissionDenied
9from django.forms import inlineformset_factory
10from django.http import Http404, HttpResponseRedirect
11from django.shortcuts import redirect
12from django.urls import reverse
13from django.utils.translation import gettext
14from django.views.generic import DetailView, UpdateView
16from weblate.auth.forms import ProjectTeamForm, SitewideTeamForm
17from weblate.auth.models import (
18 AutoGroup,
19 Group,
20 Invitation,
21)
22from weblate.trans.forms import UserAddTeamForm, UserManageForm
23from weblate.trans.util import redirect_next, redirect_param
24from weblate.utils import messages
25from weblate.utils.views import get_paginator, show_form_errors
26from weblate.wladmin.forms import ChangedCharField
28if TYPE_CHECKING: 28 ↛ 29line 28 didn't jump to line 29 because the condition on line 28 was never true
29 from weblate.auth.models import (
30 AuthenticatedHttpRequest,
31 User,
32 )
35class TeamUpdateView(UpdateView):
36 model = Group
37 template_name = "auth/team.html"
38 request: AuthenticatedHttpRequest
40 auto_formset = inlineformset_factory(
41 Group,
42 AutoGroup,
43 fields=("match",),
44 extra=0,
45 field_classes={"match": ChangedCharField},
46 )
48 def get_form_class(self):
49 if self.object.defining_project:
50 return ProjectTeamForm
51 return SitewideTeamForm
53 def get_form(self, form_class=None):
54 if not self.request.user.has_perm("meta:team.edit", self.object):
55 return None
56 return super().get_form(form_class)
58 def get_form_kwargs(self):
59 kwargs = super().get_form_kwargs()
60 if self.object.defining_project:
61 kwargs["project"] = self.object.defining_project
62 return kwargs
64 def get_object(self, queryset=None):
65 result = super().get_object(queryset=queryset)
66 user = self.request.user
68 if (
69 not user.has_perm("meta:team.edit", result)
70 and not user.has_perm("meta:team.users", result)
71 and not user.groups.filter(pk=result.pk).exists()
72 ):
73 raise PermissionDenied
75 return result
77 def get_context_data(self, **kwargs):
78 result = super().get_context_data(**kwargs)
80 if "auto_formset" not in result:
81 result["auto_formset"] = self.auto_formset(instance=self.object)
83 if self.request.user.has_perm("meta:team.users", self.object):
84 result["users"] = get_paginator(
85 self.request,
86 self.object.user_set.filter(is_active=True, is_bot=False).order(),
87 )
88 result["add_user_form"] = UserAddTeamForm()
89 result["admins"] = self.object.admins.all()
91 return result
93 def handle_add_user(self, request: AuthenticatedHttpRequest):
94 form = UserAddTeamForm(request.POST)
95 if form.is_valid():
96 if form.cleaned_data["make_admin"]:
97 self.object.admins.add(form.cleaned_data["user"])
98 else:
99 self.object.admins.remove(form.cleaned_data["user"])
100 form.cleaned_data["user"].add_team(request, self.object)
101 else:
102 show_form_errors(request, form)
103 return HttpResponseRedirect(self.get_success_url())
105 def handle_remove_user(self, request: AuthenticatedHttpRequest):
106 form = UserManageForm(request.POST)
107 if form.is_valid():
108 form.cleaned_data["user"].remove_team(request, self.object)
109 else:
110 show_form_errors(request, form)
111 return HttpResponseRedirect(self.get_success_url())
113 def handle_delete(self, request: AuthenticatedHttpRequest):
114 if self.object.defining_project:
115 fallback = (
116 reverse(
117 "manage-access",
118 kwargs={"project": self.object.defining_project.slug},
119 )
120 + "#teams"
121 )
122 elif request.user.is_superuser:
123 fallback = reverse("manage-teams")
124 else:
125 fallback = reverse("manage_access") + "#teams"
126 if self.object.internal and not self.object.defining_project:
127 messages.error(request, gettext("Cannot remove built-in team!"))
128 else:
129 self.object.delete()
130 return redirect_next(request.POST.get("next"), fallback)
132 def post(self, request: AuthenticatedHttpRequest, **kwargs):
133 self.object = self.get_object()
134 if self.request.user.has_perm("meta:team.users", self.object):
135 if "add_user" in request.POST:
136 return self.handle_add_user(request)
137 if "remove_user" in request.POST:
138 return self.handle_remove_user(request)
140 form = self.get_form()
141 if form is None:
142 raise PermissionDenied
144 if "delete" in request.POST:
145 return self.handle_delete(request)
147 formset = self.auto_formset(instance=self.object, data=request.POST)
148 if form.is_valid() and formset.is_valid():
149 formset.save()
150 return self.form_valid(form)
151 return self.form_invalid(form, formset)
153 def form_invalid(self, form, formset):
154 """If the form is invalid, render the invalid form."""
155 return self.render_to_response(
156 self.get_context_data(form=form, auto_formset=formset)
157 )
160class InvitationView(DetailView):
161 model = Invitation
163 def get(self, request: AuthenticatedHttpRequest, *args, **kwargs):
164 self.object = self.get_object()
165 if request.user.is_authenticated and self.object.user != request.user:
166 # Invitation not for this user (either is for email and user is None or different user)
167 messages.error(
168 request,
169 gettext(
170 "This invitation can be accepted only by the e-mail address chosen by the inviter; it can't be used by your account."
171 ),
172 )
173 return redirect_param("profile", "#account")
174 if not self.object.user:
175 # When inviting new user go through registration
176 request.session["invitation_link"] = str(self.object.pk)
177 return redirect("register")
178 context = self.get_context_data(object=self.object)
179 return self.render_to_response(context)
181 def post(self, request: AuthenticatedHttpRequest, **kwargs):
182 self.object = invitation = self.get_object()
183 user = request.user
185 # Handle admin actions first
186 action = request.POST.get("action", "")
187 if action in {"resend", "remove"}:
188 project = invitation.group.defining_project
189 # Permission check
190 if not user.has_perm(
191 "project.permissions" if project else "user.edit", project
192 ):
193 raise PermissionDenied
195 # Perform admin action
196 if action == "resend":
197 invitation.send_email()
198 messages.success(request, gettext("User invitation e-mail was sent."))
199 else:
200 invitation.delete()
201 messages.success(request, gettext("User invitation was removed."))
203 # Redirect
204 if project:
205 return redirect("manage-access", project=project.slug)
206 return redirect("manage-users")
208 # Check if invitation can be accepted
209 if not invitation.user:
210 # This should go via registration path
211 raise Http404
212 if not user.is_authenticated:
213 raise PermissionDenied
214 if invitation.user != user:
215 raise Http404
217 # Accept invitation
218 invitation.accept(request, user)
220 if invitation.group.defining_project:
221 return redirect(invitation.group.defining_project)
222 return redirect("home")
225def accept_invitation(
226 request: AuthenticatedHttpRequest, invitation: Invitation, user: User | None
227) -> None:
228 if user is None:
229 user = invitation.user
230 if user is None:
231 raise Http404
233 # Add user to invited group
234 user.add_team(request, invitation.group)
235 # Let him watch the project
236 if invitation.group.defining_project:
237 user.profile.watched.add(invitation.group.defining_project)
239 messages.success(
240 request, gettext("Accepted invitation to the %s team.") % invitation.group
241 )
242 invitation.delete()