Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Imports
3from django.contrib.auth import get_user_model
4from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
5from django.db.models import Q
6from django.shortcuts import get_object_or_404
7from django.urls import reverse
8from django.utils.translation import ugettext_lazy as _
9from superdjango.patterns import PATTERN_PK
10from superdjango.conf import settings
11from superdjango.views import AccessMixin, BreadcrumbsMixin, FormView, RedirectView, TemplateView, ViewSet
12from .constants import IMPERSONATION_FOR_ID, IMPERSONATION_PREVIOUS_URL, IMPERSONATION_SESSION_KEY
13from .forms import UserImpersonationSearchForm
14from .utils import generate_session_key, get_user_impersonation_model, user_is_allowed_to_impersonate
16UserModel = get_user_model()
17UserImpersonationModel = get_user_impersonation_model()
19# Exports
21__all__ = (
22 "ImpersonationHelp",
23 "ImpersonationViewSet",
24 "ListUsersForImpersonation",
25 "SearchUsersForImpersonation",
26 "StartImpersonationRedirect",
27 "StopImpersonationRedirect",
28)
30# Constants
32EMAIL_FIELD = getattr(UserModel, "EMAIL_FIELD", "email")
33LOGIN_REDIRECT_URL = settings.LOGIN_REDIRECT_URL
34REDIRECT_FIELD_NAME = getattr(settings, "REDIRECT_FIELD_NAME", "next")
35USERNAME_FIELD = getattr(UserModel, "USERNAME_FIELD", "username")
37# Views
40class ImpersonationHelp(BreadcrumbsMixin, TemplateView):
41 login_required = True
42 pattern_name = "impersonation_help"
43 pattern_value = "impersonation/help/"
44 template_name = "accounts/impersonation_help.html"
46 def get_breadcrumbs(self):
47 crumbs = super().get_breadcrumbs()
49 crumbs.add(_("User Impersonation"), "")
51 return crumbs
53 def get_context_data(self, **kwargs):
54 context = super().get_context_data(**kwargs)
56 # noinspection PyPep8Naming
57 if UserImpersonationModel is not None:
58 context['impersonation_history'] = UserImpersonationModel.objects.filter(impersonation_by=self.request.user)
59 else:
60 context['impersonation_history'] = None
62 return context
65class ListUsersForImpersonation(BreadcrumbsMixin, TemplateView):
66 login_required = True
67 pattern_name = "list_users_for_impersonation"
68 pattern_value = 'impersonation/'
69 template_name = "accounts/list_users_for_impersonation.html"
71 def get_breadcrumbs(self):
72 crumbs = super().get_breadcrumbs()
74 crumbs.add(_("User Impersonation"), reverse("impersonation_help"))
75 crumbs.add(_("Users"), reverse("list_users_for_impersonation"))
77 return crumbs
79 def get_context_data(self, **kwargs):
80 context = super().get_context_data(**kwargs)
82 # context['form'] = UserImpersonationSearchForm()
83 context['next_url'] = self.request.GET.get(REDIRECT_FIELD_NAME, None)
85 users = UserModel.objects.filter(is_superuser=False).order_by("email")
87 # Pagination
88 page = self.request.GET.get('page')
89 entries_per_page = 25
90 paginator = Paginator(users, entries_per_page)
91 try:
92 objects = paginator.page(page)
93 except PageNotAnInteger:
94 objects = paginator.page(1)
95 except EmptyPage:
96 objects = paginator.page(paginator.num_pages)
98 context['users'] = objects
100 return context
103class SearchUsersForImpersonation(BreadcrumbsMixin, FormView):
104 form_class = UserImpersonationSearchForm
105 login_required = True
106 pattern_name = "search_users_for_impersonation"
107 pattern_value = "impersonation/search/"
108 template_name = "accounts/search_users_for_impersonation.html"
110 def get_breadcrumbs(self):
111 crumbs = super().get_breadcrumbs()
113 crumbs.add(_("User Impersonation"), "/accounts/impersonation/help/")
114 crumbs.add(_("Users"), reverse("list_users_for_impersonation"))
115 crumbs.add(_("Search"), "")
117 return crumbs
119 def get_context_data(self, **kwargs):
120 context = super().get_context_data(**kwargs)
122 context['users'] = None
124 term = self.request.GET.get("term", None)
125 context['term'] = term
127 if term is not None:
128 users = UserModel.objects.filter(is_superuser=False).filter(
129 Q(first_name__iexact=term) |
130 Q(last_name__iexact=term) |
131 Q(**{USERNAME_FIELD: term}) |
132 Q(**{EMAIL_FIELD: term})
133 )
135 context['users'] = users
137 return context
139 def form_valid(self, form):
140 # noinspection PyAttributeOutsideInit
141 self.term = form.cleaned_data['term']
143 return super().form_valid(form)
145 def get_success_url(self):
146 return reverse("search_users_for_impersonation") + "?term=%s" % self.term
149class StartImpersonationRedirect(AccessMixin, RedirectView):
150 login_required = True
151 pattern_name = "start_impersonation_redirect"
152 pattern_value = 'impersonation/start/%s/' % PATTERN_PK
154 def get_redirect_url(self, *args, **kwargs):
155 # Get the user to be impersonated or die trying.
156 user = get_object_or_404(UserModel, pk=self.kwargs['pk'])
158 # Make sure the current user is allowed to impersonate another user.
159 if user_is_allowed_to_impersonate(self.request.user):
161 # Generate and save a session key.
162 session_key = generate_session_key()
163 self.request.session[IMPERSONATION_SESSION_KEY] = session_key
165 # Note who is being impersonated. See middleware.
166 self.request.session[IMPERSONATION_FOR_ID] = user.pk
168 # Get the previous path so we can redirect there when impersonation ends.
169 previous_url = self.request.META.get('HTTP_REFERER')
170 if previous_url:
171 self.request.session[IMPERSONATION_PREVIOUS_URL] = self.request.build_absolute_uri(previous_url)
172 else:
173 self.request.session[IMPERSONATION_PREVIOUS_URL] = LOGIN_REDIRECT_URL
175 # TODO: Send a signal to indicate an impersonation session has started?
176 # impersonation_session_started.send(
177 # sender=None,
178 # impersonation_by=self.request.user,
179 # impersonation_for=user,
180 # request=self.request
181 # )
183 # Optional logging to the database.
184 if UserImpersonationModel is not None:
185 UserImpersonationModel.start(self.request.user, user, session_key)
187 # Make sure the session updates immediately.
188 self.request.session.modified = True
190 # Redirect.
191 next_url = self.request.GET.get(REDIRECT_FIELD_NAME, None)
192 if next_url is None:
193 next_url = LOGIN_REDIRECT_URL
195 return next_url
198class StopImpersonationRedirect(AccessMixin, RedirectView):
199 login_required = True
200 pattern_name = "stop_impersonation_redirect"
201 pattern_value = 'impersonation/stop/'
203 def get_redirect_url(self, *args, **kwargs):
204 # There's nothing to do if impersonation is inactive.
205 if IMPERSONATION_SESSION_KEY not in self.request.session:
206 self.messages.warning(_("You don't appear to be impersonating another user."))
207 return reverse("impersonation_help")
209 # Update the log entry.
210 if UserImpersonationModel is not None:
211 try:
212 criteria = {
213 'impersonation_by': self.request.user,
214 'impersonation_for_id': self.request.session[IMPERSONATION_FOR_ID],
215 'session_key': self.request.session[IMPERSONATION_SESSION_KEY],
216 'stop_dt__isnull': True,
217 }
218 record = UserImpersonationModel.objects.get(**criteria)
219 record.stop()
220 except UserImpersonationModel.DoesNotExist:
221 pass
222 except UserImpersonationModel.MultipleObjectsReturned:
223 pass
225 # Get the redirect URL and then obliterate session data.
226 redirect_url = self.request.session[IMPERSONATION_PREVIOUS_URL]
227 del self.request.session[IMPERSONATION_FOR_ID]
228 del self.request.session[IMPERSONATION_PREVIOUS_URL]
229 del self.request.session[IMPERSONATION_SESSION_KEY]
230 self.request.session.modified = True
232 # Redirect.
233 return redirect_url
236# BaseView Sets
239class ImpersonationViewSet(ViewSet):
241 views = [
242 ImpersonationHelp,
243 ListUsersForImpersonation,
244 SearchUsersForImpersonation,
245 StartImpersonationRedirect,
246 StopImpersonationRedirect,
247 ]