# Imports
from django.contrib.auth import get_user_model
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from superdjango.patterns import PATTERN_PK
from superdjango.conf import settings
from superdjango.views import AccessMixin, BreadcrumbsMixin, FormView, RedirectView, TemplateView, ViewSet
from .constants import IMPERSONATION_FOR_ID, IMPERSONATION_PREVIOUS_URL, IMPERSONATION_SESSION_KEY
from .forms import UserImpersonationSearchForm
from .utils import generate_session_key, get_user_impersonation_model, user_is_allowed_to_impersonate
UserModel = get_user_model()
UserImpersonationModel = get_user_impersonation_model()
# Exports
__all__ = (
"ImpersonationHelp",
"ImpersonationViewSet",
"ListUsersForImpersonation",
"SearchUsersForImpersonation",
"StartImpersonationRedirect",
"StopImpersonationRedirect",
)
# Constants
EMAIL_FIELD = getattr(UserModel, "EMAIL_FIELD", "email")
LOGIN_REDIRECT_URL = settings.LOGIN_REDIRECT_URL
REDIRECT_FIELD_NAME = getattr(settings, "REDIRECT_FIELD_NAME", "next")
USERNAME_FIELD = getattr(UserModel, "USERNAME_FIELD", "username")
# Views
[docs]class ImpersonationHelp(BreadcrumbsMixin, TemplateView):
login_required = True
pattern_name = "impersonation_help"
pattern_value = "impersonation/help/"
template_name = "accounts/impersonation_help.html"
[docs] def get_breadcrumbs(self):
crumbs = super().get_breadcrumbs()
crumbs.add(_("User Impersonation"), "")
return crumbs
[docs] def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# noinspection PyPep8Naming
if UserImpersonationModel is not None:
context['impersonation_history'] = UserImpersonationModel.objects.filter(impersonation_by=self.request.user)
else:
context['impersonation_history'] = None
return context
[docs]class ListUsersForImpersonation(BreadcrumbsMixin, TemplateView):
login_required = True
pattern_name = "list_users_for_impersonation"
pattern_value = 'impersonation/'
template_name = "accounts/list_users_for_impersonation.html"
[docs] def get_breadcrumbs(self):
crumbs = super().get_breadcrumbs()
crumbs.add(_("User Impersonation"), reverse("impersonation_help"))
crumbs.add(_("Users"), reverse("list_users_for_impersonation"))
return crumbs
[docs] def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# context['form'] = UserImpersonationSearchForm()
context['next_url'] = self.request.GET.get(REDIRECT_FIELD_NAME, None)
users = UserModel.objects.filter(is_superuser=False).order_by("email")
# Pagination
page = self.request.GET.get('page')
entries_per_page = 25
paginator = Paginator(users, entries_per_page)
try:
objects = paginator.page(page)
except PageNotAnInteger:
objects = paginator.page(1)
except EmptyPage:
objects = paginator.page(paginator.num_pages)
context['users'] = objects
return context
[docs]class SearchUsersForImpersonation(BreadcrumbsMixin, FormView):
form_class = UserImpersonationSearchForm
login_required = True
pattern_name = "search_users_for_impersonation"
pattern_value = "impersonation/search/"
template_name = "accounts/search_users_for_impersonation.html"
[docs] def get_breadcrumbs(self):
crumbs = super().get_breadcrumbs()
crumbs.add(_("User Impersonation"), "/accounts/impersonation/help/")
crumbs.add(_("Users"), reverse("list_users_for_impersonation"))
crumbs.add(_("Search"), "")
return crumbs
[docs] def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['users'] = None
term = self.request.GET.get("term", None)
context['term'] = term
if term is not None:
users = UserModel.objects.filter(is_superuser=False).filter(
Q(first_name__iexact=term) |
Q(last_name__iexact=term) |
Q(**{USERNAME_FIELD: term}) |
Q(**{EMAIL_FIELD: term})
)
context['users'] = users
return context
[docs] def get_success_url(self):
return reverse("search_users_for_impersonation") + "?term=%s" % self.term
[docs]class StartImpersonationRedirect(AccessMixin, RedirectView):
login_required = True
pattern_name = "start_impersonation_redirect"
pattern_value = 'impersonation/start/%s/' % PATTERN_PK
[docs] def get_redirect_url(self, *args, **kwargs):
# Get the user to be impersonated or die trying.
user = get_object_or_404(UserModel, pk=self.kwargs['pk'])
# Make sure the current user is allowed to impersonate another user.
if user_is_allowed_to_impersonate(self.request.user):
# Generate and save a session key.
session_key = generate_session_key()
self.request.session[IMPERSONATION_SESSION_KEY] = session_key
# Note who is being impersonated. See middleware.
self.request.session[IMPERSONATION_FOR_ID] = user.pk
# Get the previous path so we can redirect there when impersonation ends.
previous_url = self.request.META.get('HTTP_REFERER')
if previous_url:
self.request.session[IMPERSONATION_PREVIOUS_URL] = self.request.build_absolute_uri(previous_url)
else:
self.request.session[IMPERSONATION_PREVIOUS_URL] = LOGIN_REDIRECT_URL
# TODO: Send a signal to indicate an impersonation session has started?
# impersonation_session_started.send(
# sender=None,
# impersonation_by=self.request.user,
# impersonation_for=user,
# request=self.request
# )
# Optional logging to the database.
if UserImpersonationModel is not None:
UserImpersonationModel.start(self.request.user, user, session_key)
# Make sure the session updates immediately.
self.request.session.modified = True
# Redirect.
next_url = self.request.GET.get(REDIRECT_FIELD_NAME, None)
if next_url is None:
next_url = LOGIN_REDIRECT_URL
return next_url
[docs]class StopImpersonationRedirect(AccessMixin, RedirectView):
login_required = True
pattern_name = "stop_impersonation_redirect"
pattern_value = 'impersonation/stop/'
[docs] def get_redirect_url(self, *args, **kwargs):
# There's nothing to do if impersonation is inactive.
if IMPERSONATION_SESSION_KEY not in self.request.session:
self.messages.warning(_("You don't appear to be impersonating another user."))
return reverse("impersonation_help")
# Update the log entry.
if UserImpersonationModel is not None:
try:
criteria = {
'impersonation_by': self.request.user,
'impersonation_for_id': self.request.session[IMPERSONATION_FOR_ID],
'session_key': self.request.session[IMPERSONATION_SESSION_KEY],
'stop_dt__isnull': True,
}
record = UserImpersonationModel.objects.get(**criteria)
record.stop()
except UserImpersonationModel.DoesNotExist:
pass
except UserImpersonationModel.MultipleObjectsReturned:
pass
# Get the redirect URL and then obliterate session data.
redirect_url = self.request.session[IMPERSONATION_PREVIOUS_URL]
del self.request.session[IMPERSONATION_FOR_ID]
del self.request.session[IMPERSONATION_PREVIOUS_URL]
del self.request.session[IMPERSONATION_SESSION_KEY]
self.request.session.modified = True
# Redirect.
return redirect_url
# BaseView Sets
[docs]class ImpersonationViewSet(ViewSet):
views = [
ImpersonationHelp,
ListUsersForImpersonation,
SearchUsersForImpersonation,
StartImpersonationRedirect,
StopImpersonationRedirect,
]