forms.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. from __future__ import unicode_literals
  2. from django import forms
  3. from django.forms.util import flatatt
  4. from django.template import loader
  5. from django.utils.datastructures import SortedDict
  6. from django.utils.html import format_html, format_html_join
  7. from django.utils.http import int_to_base36
  8. from django.utils.safestring import mark_safe
  9. from django.utils.translation import ugettext, ugettext_lazy as _
  10. from django.contrib.auth import authenticate
  11. from django.contrib.auth.models import User
  12. from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, identify_hasher
  13. from django.contrib.auth.tokens import default_token_generator
  14. from django.contrib.sites.models import get_current_site
  15. UNMASKED_DIGITS_TO_SHOW = 6
  16. mask_password = lambda p: "%s%s" % (p[:UNMASKED_DIGITS_TO_SHOW], "*" * max(len(p) - UNMASKED_DIGITS_TO_SHOW, 0))
  17. class ReadOnlyPasswordHashWidget(forms.Widget):
  18. def render(self, name, value, attrs):
  19. encoded = value
  20. if not is_password_usable(encoded):
  21. return "None"
  22. final_attrs = self.build_attrs(attrs)
  23. try:
  24. hasher = identify_hasher(encoded)
  25. except ValueError:
  26. summary = mark_safe("<strong>Invalid password format or unknown hashing algorithm.</strong>")
  27. else:
  28. summary = format_html_join('',
  29. "<strong>{0}</strong>: {1} ",
  30. ((ugettext(key), value)
  31. for key, value in hasher.safe_summary(encoded).items())
  32. )
  33. return format_html("<div{0}>{1}</div>", flatatt(final_attrs), summary)
  34. class ReadOnlyPasswordHashField(forms.Field):
  35. widget = ReadOnlyPasswordHashWidget
  36. def __init__(self, *args, **kwargs):
  37. kwargs.setdefault("required", False)
  38. super(ReadOnlyPasswordHashField, self).__init__(*args, **kwargs)
  39. class UserCreationForm(forms.ModelForm):
  40. """
  41. A form that creates a user, with no privileges, from the given username and
  42. password.
  43. """
  44. error_messages = {
  45. 'duplicate_username': _("A user with that username already exists."),
  46. 'password_mismatch': _("The two password fields didn't match."),
  47. }
  48. username = forms.RegexField(label=_("Username"), max_length=30,
  49. regex=r'^[\w.@+-]+$',
  50. help_text=_("Required. 30 characters or fewer. Letters, digits and "
  51. "@/./+/-/_ only."),
  52. error_messages={
  53. 'invalid': _("This value may contain only letters, numbers and "
  54. "@/./+/-/_ characters.")})
  55. password1 = forms.CharField(label=_("Password"),
  56. widget=forms.PasswordInput)
  57. password2 = forms.CharField(label=_("Password confirmation"),
  58. widget=forms.PasswordInput,
  59. help_text=_("Enter the same password as above, for verification."))
  60. class Meta:
  61. model = User
  62. fields = ("username",)
  63. def clean_username(self):
  64. # Since User.username is unique, this check is redundant,
  65. # but it sets a nicer error message than the ORM. See #13147.
  66. username = self.cleaned_data["username"]
  67. try:
  68. User.objects.get(username=username)
  69. except User.DoesNotExist:
  70. return username
  71. raise forms.ValidationError(self.error_messages['duplicate_username'])
  72. def clean_password2(self):
  73. password1 = self.cleaned_data.get("password1")
  74. password2 = self.cleaned_data.get("password2")
  75. if password1 and password2 and password1 != password2:
  76. raise forms.ValidationError(
  77. self.error_messages['password_mismatch'])
  78. return password2
  79. def save(self, commit=True):
  80. user = super(UserCreationForm, self).save(commit=False)
  81. user.set_password(self.cleaned_data["password1"])
  82. if commit:
  83. user.save()
  84. return user
  85. class UserChangeForm(forms.ModelForm):
  86. username = forms.RegexField(
  87. label=_("Username"), max_length=30, regex=r"^[\w.@+-]+$",
  88. help_text=_("Required. 30 characters or fewer. Letters, digits and "
  89. "@/./+/-/_ only."),
  90. error_messages={
  91. 'invalid': _("This value may contain only letters, numbers and "
  92. "@/./+/-/_ characters.")})
  93. password = ReadOnlyPasswordHashField(label=_("Password"),
  94. help_text=_("Raw passwords are not stored, so there is no way to see "
  95. "this user's password, but you can change the password "
  96. "using <a href=\"password/\">this form</a>."))
  97. def clean_password(self):
  98. return self.initial["password"]
  99. class Meta:
  100. model = User
  101. def __init__(self, *args, **kwargs):
  102. super(UserChangeForm, self).__init__(*args, **kwargs)
  103. f = self.fields.get('user_permissions', None)
  104. if f is not None:
  105. f.queryset = f.queryset.select_related('content_type')
  106. class AuthenticationForm(forms.Form):
  107. """
  108. Base class for authenticating users. Extend this to get a form that accepts
  109. username/password logins.
  110. """
  111. username = forms.CharField(label=_("Username"), max_length=30)
  112. password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
  113. error_messages = {
  114. 'invalid_login': _("Please enter a correct username and password. "
  115. "Note that both fields are case-sensitive."),
  116. 'no_cookies': _("Your Web browser doesn't appear to have cookies "
  117. "enabled. Cookies are required for logging in."),
  118. 'inactive': _("This account is inactive."),
  119. }
  120. def __init__(self, request=None, *args, **kwargs):
  121. """
  122. If request is passed in, the form will validate that cookies are
  123. enabled. Note that the request (a HttpRequest object) must have set a
  124. cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before
  125. running this validation.
  126. """
  127. self.request = request
  128. self.user_cache = None
  129. super(AuthenticationForm, self).__init__(*args, **kwargs)
  130. def clean(self):
  131. username = self.cleaned_data.get('username')
  132. password = self.cleaned_data.get('password')
  133. if username and password:
  134. self.user_cache = authenticate(username=username,
  135. password=password)
  136. if self.user_cache is None:
  137. raise forms.ValidationError(
  138. self.error_messages['invalid_login'])
  139. elif not self.user_cache.is_active:
  140. raise forms.ValidationError(self.error_messages['inactive'])
  141. self.check_for_test_cookie()
  142. return self.cleaned_data
  143. def check_for_test_cookie(self):
  144. if self.request and not self.request.session.test_cookie_worked():
  145. raise forms.ValidationError(self.error_messages['no_cookies'])
  146. def get_user_id(self):
  147. if self.user_cache:
  148. return self.user_cache.id
  149. return None
  150. def get_user(self):
  151. return self.user_cache
  152. class PasswordResetForm(forms.Form):
  153. error_messages = {
  154. 'unknown': _("That e-mail address doesn't have an associated "
  155. "user account. Are you sure you've registered?"),
  156. 'unusable': _("The user account associated with this e-mail "
  157. "address cannot reset the password."),
  158. }
  159. email = forms.EmailField(label=_("E-mail"), max_length=75)
  160. def clean_email(self):
  161. """
  162. Validates that an active user exists with the given email address.
  163. """
  164. email = self.cleaned_data["email"]
  165. self.users_cache = User.objects.filter(email__iexact=email,
  166. is_active=True)
  167. if not len(self.users_cache):
  168. raise forms.ValidationError(self.error_messages['unknown'])
  169. if any((user.password == UNUSABLE_PASSWORD)
  170. for user in self.users_cache):
  171. raise forms.ValidationError(self.error_messages['unusable'])
  172. return email
  173. def save(self, domain_override=None,
  174. subject_template_name='registration/password_reset_subject.txt',
  175. email_template_name='registration/password_reset_email.html',
  176. use_https=False, token_generator=default_token_generator,
  177. from_email=None, request=None):
  178. """
  179. Generates a one-use only link for resetting password and sends to the
  180. user.
  181. """
  182. from django.core.mail import send_mail
  183. for user in self.users_cache:
  184. if not domain_override:
  185. current_site = get_current_site(request)
  186. site_name = current_site.name
  187. domain = current_site.domain
  188. else:
  189. site_name = domain = domain_override
  190. c = {
  191. 'email': user.email,
  192. 'domain': domain,
  193. 'site_name': site_name,
  194. 'uid': int_to_base36(user.id),
  195. 'user': user,
  196. 'token': token_generator.make_token(user),
  197. 'protocol': use_https and 'https' or 'http',
  198. }
  199. subject = loader.render_to_string(subject_template_name, c)
  200. # Email subject *must not* contain newlines
  201. subject = ''.join(subject.splitlines())
  202. email = loader.render_to_string(email_template_name, c)
  203. send_mail(subject, email, from_email, [user.email])
  204. class SetPasswordForm(forms.Form):
  205. """
  206. A form that lets a user change set his/her password without entering the
  207. old password
  208. """
  209. error_messages = {
  210. 'password_mismatch': _("The two password fields didn't match."),
  211. }
  212. new_password1 = forms.CharField(label=_("New password"),
  213. widget=forms.PasswordInput)
  214. new_password2 = forms.CharField(label=_("New password confirmation"),
  215. widget=forms.PasswordInput)
  216. def __init__(self, user, *args, **kwargs):
  217. self.user = user
  218. super(SetPasswordForm, self).__init__(*args, **kwargs)
  219. def clean_new_password2(self):
  220. password1 = self.cleaned_data.get('new_password1')
  221. password2 = self.cleaned_data.get('new_password2')
  222. if password1 and password2:
  223. if password1 != password2:
  224. raise forms.ValidationError(
  225. self.error_messages['password_mismatch'])
  226. return password2
  227. def save(self, commit=True):
  228. self.user.set_password(self.cleaned_data['new_password1'])
  229. if commit:
  230. self.user.save()
  231. return self.user
  232. class PasswordChangeForm(SetPasswordForm):
  233. """
  234. A form that lets a user change his/her password by entering
  235. their old password.
  236. """
  237. error_messages = dict(SetPasswordForm.error_messages, **{
  238. 'password_incorrect': _("Your old password was entered incorrectly. "
  239. "Please enter it again."),
  240. })
  241. old_password = forms.CharField(label=_("Old password"),
  242. widget=forms.PasswordInput)
  243. def clean_old_password(self):
  244. """
  245. Validates that the old_password field is correct.
  246. """
  247. old_password = self.cleaned_data["old_password"]
  248. if not self.user.check_password(old_password):
  249. raise forms.ValidationError(
  250. self.error_messages['password_incorrect'])
  251. return old_password
  252. PasswordChangeForm.base_fields = SortedDict([
  253. (k, PasswordChangeForm.base_fields[k])
  254. for k in ['old_password', 'new_password1', 'new_password2']
  255. ])
  256. class AdminPasswordChangeForm(forms.Form):
  257. """
  258. A form used to change the password of a user in the admin interface.
  259. """
  260. error_messages = {
  261. 'password_mismatch': _("The two password fields didn't match."),
  262. }
  263. password1 = forms.CharField(label=_("Password"),
  264. widget=forms.PasswordInput)
  265. password2 = forms.CharField(label=_("Password (again)"),
  266. widget=forms.PasswordInput)
  267. def __init__(self, user, *args, **kwargs):
  268. self.user = user
  269. super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
  270. def clean_password2(self):
  271. password1 = self.cleaned_data.get('password1')
  272. password2 = self.cleaned_data.get('password2')
  273. if password1 and password2:
  274. if password1 != password2:
  275. raise forms.ValidationError(
  276. self.error_messages['password_mismatch'])
  277. return password2
  278. def save(self, commit=True):
  279. """
  280. Saves the new password.
  281. """
  282. self.user.set_password(self.cleaned_data["password1"])
  283. if commit:
  284. self.user.save()
  285. return self.user