Hide keyboard shortcuts

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 

2 

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 

15 

16UserModel = get_user_model() 

17UserImpersonationModel = get_user_impersonation_model() 

18 

19# Exports 

20 

21__all__ = ( 

22 "ImpersonationHelp", 

23 "ImpersonationViewSet", 

24 "ListUsersForImpersonation", 

25 "SearchUsersForImpersonation", 

26 "StartImpersonationRedirect", 

27 "StopImpersonationRedirect", 

28) 

29 

30# Constants 

31 

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") 

36 

37# Views 

38 

39 

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" 

45 

46 def get_breadcrumbs(self): 

47 crumbs = super().get_breadcrumbs() 

48 

49 crumbs.add(_("User Impersonation"), "") 

50 

51 return crumbs 

52 

53 def get_context_data(self, **kwargs): 

54 context = super().get_context_data(**kwargs) 

55 

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 

61 

62 return context 

63 

64 

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" 

70 

71 def get_breadcrumbs(self): 

72 crumbs = super().get_breadcrumbs() 

73 

74 crumbs.add(_("User Impersonation"), reverse("impersonation_help")) 

75 crumbs.add(_("Users"), reverse("list_users_for_impersonation")) 

76 

77 return crumbs 

78 

79 def get_context_data(self, **kwargs): 

80 context = super().get_context_data(**kwargs) 

81 

82 # context['form'] = UserImpersonationSearchForm() 

83 context['next_url'] = self.request.GET.get(REDIRECT_FIELD_NAME, None) 

84 

85 users = UserModel.objects.filter(is_superuser=False).order_by("email") 

86 

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) 

97 

98 context['users'] = objects 

99 

100 return context 

101 

102 

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" 

109 

110 def get_breadcrumbs(self): 

111 crumbs = super().get_breadcrumbs() 

112 

113 crumbs.add(_("User Impersonation"), "/accounts/impersonation/help/") 

114 crumbs.add(_("Users"), reverse("list_users_for_impersonation")) 

115 crumbs.add(_("Search"), "") 

116 

117 return crumbs 

118 

119 def get_context_data(self, **kwargs): 

120 context = super().get_context_data(**kwargs) 

121 

122 context['users'] = None 

123 

124 term = self.request.GET.get("term", None) 

125 context['term'] = term 

126 

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 ) 

134 

135 context['users'] = users 

136 

137 return context 

138 

139 def form_valid(self, form): 

140 # noinspection PyAttributeOutsideInit 

141 self.term = form.cleaned_data['term'] 

142 

143 return super().form_valid(form) 

144 

145 def get_success_url(self): 

146 return reverse("search_users_for_impersonation") + "?term=%s" % self.term 

147 

148 

149class StartImpersonationRedirect(AccessMixin, RedirectView): 

150 login_required = True 

151 pattern_name = "start_impersonation_redirect" 

152 pattern_value = 'impersonation/start/%s/' % PATTERN_PK 

153 

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']) 

157 

158 # Make sure the current user is allowed to impersonate another user. 

159 if user_is_allowed_to_impersonate(self.request.user): 

160 

161 # Generate and save a session key. 

162 session_key = generate_session_key() 

163 self.request.session[IMPERSONATION_SESSION_KEY] = session_key 

164 

165 # Note who is being impersonated. See middleware. 

166 self.request.session[IMPERSONATION_FOR_ID] = user.pk 

167 

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 

174 

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 # ) 

182 

183 # Optional logging to the database. 

184 if UserImpersonationModel is not None: 

185 UserImpersonationModel.start(self.request.user, user, session_key) 

186 

187 # Make sure the session updates immediately. 

188 self.request.session.modified = True 

189 

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 

194 

195 return next_url 

196 

197 

198class StopImpersonationRedirect(AccessMixin, RedirectView): 

199 login_required = True 

200 pattern_name = "stop_impersonation_redirect" 

201 pattern_value = 'impersonation/stop/' 

202 

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") 

208 

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 

224 

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 

231 

232 # Redirect. 

233 return redirect_url 

234 

235 

236# BaseView Sets 

237 

238 

239class ImpersonationViewSet(ViewSet): 

240 

241 views = [ 

242 ImpersonationHelp, 

243 ListUsersForImpersonation, 

244 SearchUsersForImpersonation, 

245 StartImpersonationRedirect, 

246 StopImpersonationRedirect, 

247 ]