views.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. import functools
  2. import warnings
  3. from django.conf import settings
  4. # Avoid shadowing the login() and logout() views below.
  5. from django.contrib.auth import (
  6. REDIRECT_FIELD_NAME, get_user_model, login as auth_login,
  7. logout as auth_logout, update_session_auth_hash,
  8. )
  9. from django.contrib.auth.decorators import login_required
  10. from django.contrib.auth.forms import (
  11. AuthenticationForm, PasswordChangeForm, PasswordResetForm, SetPasswordForm,
  12. )
  13. from django.contrib.auth.tokens import default_token_generator
  14. from django.contrib.sites.shortcuts import get_current_site
  15. from django.core.urlresolvers import reverse
  16. from django.http import HttpResponseRedirect, QueryDict
  17. from django.shortcuts import resolve_url
  18. from django.template.response import TemplateResponse
  19. from django.utils.deprecation import RemovedInDjango20Warning
  20. from django.utils.encoding import force_text
  21. from django.utils.http import is_safe_url, urlsafe_base64_decode
  22. from django.utils.six.moves.urllib.parse import urlparse, urlunparse
  23. from django.utils.translation import ugettext as _
  24. from django.views.decorators.cache import never_cache
  25. from django.views.decorators.csrf import csrf_protect
  26. from django.views.decorators.debug import sensitive_post_parameters
  27. def deprecate_current_app(func):
  28. """
  29. Handle deprecation of the current_app parameter of the views.
  30. """
  31. @functools.wraps(func)
  32. def inner(*args, **kwargs):
  33. if 'current_app' in kwargs:
  34. warnings.warn(
  35. "Passing `current_app` as a keyword argument is deprecated. "
  36. "Instead the caller of `{0}` should set "
  37. "`request.current_app`.".format(func.__name__),
  38. RemovedInDjango20Warning
  39. )
  40. current_app = kwargs.pop('current_app')
  41. request = kwargs.get('request', None)
  42. if request and current_app is not None:
  43. request.current_app = current_app
  44. return func(*args, **kwargs)
  45. return inner
  46. @deprecate_current_app
  47. @sensitive_post_parameters()
  48. @csrf_protect
  49. @never_cache
  50. def login(request, template_name='registration/login.html',
  51. redirect_field_name=REDIRECT_FIELD_NAME,
  52. authentication_form=AuthenticationForm,
  53. extra_context=None):
  54. """
  55. Displays the login form and handles the login action.
  56. """
  57. redirect_to = request.POST.get(redirect_field_name,
  58. request.GET.get(redirect_field_name, ''))
  59. if request.method == "POST":
  60. form = authentication_form(request, data=request.POST)
  61. if form.is_valid():
  62. # Ensure the user-originating redirection url is safe.
  63. if not is_safe_url(url=redirect_to, host=request.get_host()):
  64. redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
  65. # Okay, security check complete. Log the user in.
  66. auth_login(request, form.get_user())
  67. return HttpResponseRedirect(redirect_to)
  68. else:
  69. form = authentication_form(request)
  70. current_site = get_current_site(request)
  71. context = {
  72. 'form': form,
  73. redirect_field_name: redirect_to,
  74. 'site': current_site,
  75. 'site_name': current_site.name,
  76. }
  77. if extra_context is not None:
  78. context.update(extra_context)
  79. return TemplateResponse(request, template_name, context)
  80. @deprecate_current_app
  81. def logout(request, next_page=None,
  82. template_name='registration/logged_out.html',
  83. redirect_field_name=REDIRECT_FIELD_NAME,
  84. extra_context=None):
  85. """
  86. Logs out the user and displays 'You are logged out' message.
  87. """
  88. auth_logout(request)
  89. if next_page is not None:
  90. next_page = resolve_url(next_page)
  91. if (redirect_field_name in request.POST or
  92. redirect_field_name in request.GET):
  93. next_page = request.POST.get(redirect_field_name,
  94. request.GET.get(redirect_field_name))
  95. # Security check -- don't allow redirection to a different host.
  96. if not is_safe_url(url=next_page, host=request.get_host()):
  97. next_page = request.path
  98. if next_page:
  99. # Redirect to this page until the session has been cleared.
  100. return HttpResponseRedirect(next_page)
  101. current_site = get_current_site(request)
  102. context = {
  103. 'site': current_site,
  104. 'site_name': current_site.name,
  105. 'title': _('Logged out')
  106. }
  107. if extra_context is not None:
  108. context.update(extra_context)
  109. return TemplateResponse(request, template_name, context)
  110. @deprecate_current_app
  111. def logout_then_login(request, login_url=None, extra_context=None):
  112. """
  113. Logs out the user if they are logged in. Then redirects to the log-in page.
  114. """
  115. if not login_url:
  116. login_url = settings.LOGIN_URL
  117. login_url = resolve_url(login_url)
  118. return logout(request, login_url, extra_context=extra_context)
  119. def redirect_to_login(next, login_url=None,
  120. redirect_field_name=REDIRECT_FIELD_NAME):
  121. """
  122. Redirects the user to the login page, passing the given 'next' page
  123. """
  124. resolved_url = resolve_url(login_url or settings.LOGIN_URL)
  125. login_url_parts = list(urlparse(resolved_url))
  126. if redirect_field_name:
  127. querystring = QueryDict(login_url_parts[4], mutable=True)
  128. querystring[redirect_field_name] = next
  129. login_url_parts[4] = querystring.urlencode(safe='/')
  130. return HttpResponseRedirect(urlunparse(login_url_parts))
  131. # 4 views for password reset:
  132. # - password_reset sends the mail
  133. # - password_reset_done shows a success message for the above
  134. # - password_reset_confirm checks the link the user clicked and
  135. # prompts for a new password
  136. # - password_reset_complete shows a success message for the above
  137. @deprecate_current_app
  138. @csrf_protect
  139. def password_reset(request,
  140. template_name='registration/password_reset_form.html',
  141. email_template_name='registration/password_reset_email.html',
  142. subject_template_name='registration/password_reset_subject.txt',
  143. password_reset_form=PasswordResetForm,
  144. token_generator=default_token_generator,
  145. post_reset_redirect=None,
  146. from_email=None,
  147. extra_context=None,
  148. html_email_template_name=None,
  149. extra_email_context=None):
  150. if post_reset_redirect is None:
  151. post_reset_redirect = reverse('password_reset_done')
  152. else:
  153. post_reset_redirect = resolve_url(post_reset_redirect)
  154. if request.method == "POST":
  155. form = password_reset_form(request.POST)
  156. if form.is_valid():
  157. opts = {
  158. 'use_https': request.is_secure(),
  159. 'token_generator': token_generator,
  160. 'from_email': from_email,
  161. 'email_template_name': email_template_name,
  162. 'subject_template_name': subject_template_name,
  163. 'request': request,
  164. 'html_email_template_name': html_email_template_name,
  165. 'extra_email_context': extra_email_context,
  166. }
  167. form.save(**opts)
  168. return HttpResponseRedirect(post_reset_redirect)
  169. else:
  170. form = password_reset_form()
  171. context = {
  172. 'form': form,
  173. 'title': _('Password reset'),
  174. }
  175. if extra_context is not None:
  176. context.update(extra_context)
  177. return TemplateResponse(request, template_name, context)
  178. @deprecate_current_app
  179. def password_reset_done(request,
  180. template_name='registration/password_reset_done.html',
  181. extra_context=None):
  182. context = {
  183. 'title': _('Password reset sent'),
  184. }
  185. if extra_context is not None:
  186. context.update(extra_context)
  187. return TemplateResponse(request, template_name, context)
  188. # Doesn't need csrf_protect since no-one can guess the URL
  189. @sensitive_post_parameters()
  190. @never_cache
  191. @deprecate_current_app
  192. def password_reset_confirm(request, uidb64=None, token=None,
  193. template_name='registration/password_reset_confirm.html',
  194. token_generator=default_token_generator,
  195. set_password_form=SetPasswordForm,
  196. post_reset_redirect=None,
  197. extra_context=None):
  198. """
  199. View that checks the hash in a password reset link and presents a
  200. form for entering a new password.
  201. """
  202. UserModel = get_user_model()
  203. assert uidb64 is not None and token is not None # checked by URLconf
  204. if post_reset_redirect is None:
  205. post_reset_redirect = reverse('password_reset_complete')
  206. else:
  207. post_reset_redirect = resolve_url(post_reset_redirect)
  208. try:
  209. # urlsafe_base64_decode() decodes to bytestring on Python 3
  210. uid = force_text(urlsafe_base64_decode(uidb64))
  211. user = UserModel._default_manager.get(pk=uid)
  212. except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
  213. user = None
  214. if user is not None and token_generator.check_token(user, token):
  215. validlink = True
  216. title = _('Enter new password')
  217. if request.method == 'POST':
  218. form = set_password_form(user, request.POST)
  219. if form.is_valid():
  220. form.save()
  221. return HttpResponseRedirect(post_reset_redirect)
  222. else:
  223. form = set_password_form(user)
  224. else:
  225. validlink = False
  226. form = None
  227. title = _('Password reset unsuccessful')
  228. context = {
  229. 'form': form,
  230. 'title': title,
  231. 'validlink': validlink,
  232. }
  233. if extra_context is not None:
  234. context.update(extra_context)
  235. return TemplateResponse(request, template_name, context)
  236. @deprecate_current_app
  237. def password_reset_complete(request,
  238. template_name='registration/password_reset_complete.html',
  239. extra_context=None):
  240. context = {
  241. 'login_url': resolve_url(settings.LOGIN_URL),
  242. 'title': _('Password reset complete'),
  243. }
  244. if extra_context is not None:
  245. context.update(extra_context)
  246. return TemplateResponse(request, template_name, context)
  247. @sensitive_post_parameters()
  248. @csrf_protect
  249. @login_required
  250. @deprecate_current_app
  251. def password_change(request,
  252. template_name='registration/password_change_form.html',
  253. post_change_redirect=None,
  254. password_change_form=PasswordChangeForm,
  255. extra_context=None):
  256. if post_change_redirect is None:
  257. post_change_redirect = reverse('password_change_done')
  258. else:
  259. post_change_redirect = resolve_url(post_change_redirect)
  260. if request.method == "POST":
  261. form = password_change_form(user=request.user, data=request.POST)
  262. if form.is_valid():
  263. form.save()
  264. # Updating the password logs out all other sessions for the user
  265. # except the current one.
  266. update_session_auth_hash(request, form.user)
  267. return HttpResponseRedirect(post_change_redirect)
  268. else:
  269. form = password_change_form(user=request.user)
  270. context = {
  271. 'form': form,
  272. 'title': _('Password change'),
  273. }
  274. if extra_context is not None:
  275. context.update(extra_context)
  276. return TemplateResponse(request, template_name, context)
  277. @login_required
  278. @deprecate_current_app
  279. def password_change_done(request,
  280. template_name='registration/password_change_done.html',
  281. extra_context=None):
  282. context = {
  283. 'title': _('Password change successful'),
  284. }
  285. if extra_context is not None:
  286. context.update(extra_context)
  287. return TemplateResponse(request, template_name, context)