PageRenderTime 25ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/apps/users/forms.py

https://github.com/tgavankar/kitsune
Python | 336 lines | 303 code | 21 blank | 12 comment | 5 complexity | 39744f63bd42b35b7415339f45fb8939 MD5 | raw file
  1. import re
  2. from django import forms
  3. from django.conf import settings
  4. from django.contrib.auth import authenticate, forms as auth_forms
  5. from django.contrib.auth.models import User
  6. from django.contrib.sites.models import get_current_site
  7. from django.core.mail import send_mail
  8. from django.template import Context, loader
  9. from tower import ugettext as _, ugettext_lazy as _lazy
  10. from sumo.urlresolvers import reverse
  11. from sumo.widgets import ImageWidget
  12. from upload.forms import clean_image_extension
  13. from upload.utils import check_file_size, FileTooLargeError
  14. from users.models import Profile
  15. from users.passwords import password_allowed, username_allowed
  16. from users.widgets import FacebookURLWidget, TwitterURLWidget
  17. USERNAME_INVALID = _lazy(u'Username may contain only letters, '
  18. 'numbers and ./+/-/_ characters.')
  19. USERNAME_REQUIRED = _lazy(u'Username is required.')
  20. USERNAME_SHORT = _lazy(u'Username is too short (%(show_value)s characters). '
  21. 'It must be at least %(limit_value)s characters.')
  22. USERNAME_LONG = _lazy(u'Username is too long (%(show_value)s characters). '
  23. 'It must be %(limit_value)s characters or less.')
  24. EMAIL_REQUIRED = _lazy(u'Email address is required.')
  25. EMAIL_SHORT = _lazy(u'Email address is too short (%(show_value)s characters). '
  26. 'It must be at least %(limit_value)s characters.')
  27. EMAIL_LONG = _lazy(u'Email address is too long (%(show_value)s characters). '
  28. 'It must be %(limit_value)s characters or less.')
  29. PASSWD_REQUIRED = _lazy(u'Password is required.')
  30. PASSWD2_REQUIRED = _lazy(u'Please enter your password twice.')
  31. PASSWD_MIN_LENGTH = 8
  32. PASSWD_MIN_LENGTH_MSG = _lazy('Password must be 8 or more characters.')
  33. # Enforces at least one digit and at least one alpha character.
  34. password_re = re.compile(r'(?=.*\d)(?=.*[a-zA-Z])')
  35. class SettingsForm(forms.Form):
  36. forums_watch_new_thread = forms.BooleanField(
  37. required=False, initial=True,
  38. label=_lazy(u'Watch forum threads I start'))
  39. forums_watch_after_reply = forms.BooleanField(
  40. required=False, initial=True,
  41. label=_lazy(u'Watch forum threads I comment in'))
  42. kbforums_watch_new_thread = forms.BooleanField(
  43. required=False, initial=True,
  44. label=_lazy(u'Watch KB discussion threads I start'))
  45. kbforums_watch_after_reply = forms.BooleanField(
  46. required=False, initial=True,
  47. label=_lazy(u'Watch KB discussion threads I comment in'))
  48. questions_watch_after_reply = forms.BooleanField(
  49. required=False, initial=True,
  50. label=_lazy(u'Watch Question threads I comment in'))
  51. email_private_messages = forms.BooleanField(
  52. required=False, initial=True,
  53. label=_lazy(u'Send emails for private messages'))
  54. def save_for_user(self, user):
  55. for field in self.fields.keys():
  56. value = str(self.cleaned_data[field])
  57. setting = user.settings.filter(name=field)
  58. update_count = setting.update(value=value)
  59. if update_count == 0:
  60. # This user didn't have this setting so create it.
  61. user.settings.create(name=field, value=value)
  62. class RegisterForm(forms.ModelForm):
  63. """A user registration form that requires unique email addresses.
  64. The default Django user creation form does not require an email address,
  65. let alone that it be unique. This form does, and sets a minimum length
  66. for usernames.
  67. """
  68. username = forms.RegexField(
  69. label=_lazy(u'Username:'), max_length=30, min_length=4,
  70. regex=r'^[\w.+-]+$',
  71. help_text=_lazy(u'Required. 30 characters or fewer. Letters, digits '
  72. 'and ./+/-/_ only.'),
  73. error_messages={'invalid': USERNAME_INVALID,
  74. 'required': USERNAME_REQUIRED,
  75. 'min_length': USERNAME_SHORT,
  76. 'max_length': USERNAME_LONG})
  77. email = forms.EmailField(
  78. label=_lazy(u'Email address:'),
  79. error_messages={'required': EMAIL_REQUIRED,
  80. 'min_length': EMAIL_SHORT,
  81. 'max_length': EMAIL_LONG})
  82. password = forms.CharField(
  83. label=_lazy(u'Password:'),
  84. min_length=PASSWD_MIN_LENGTH,
  85. widget=forms.PasswordInput(render_value=False),
  86. error_messages={'required': PASSWD_REQUIRED,
  87. 'min_length': PASSWD_MIN_LENGTH_MSG})
  88. password2 = forms.CharField(
  89. label=_lazy(u'Repeat password:'),
  90. widget=forms.PasswordInput(render_value=False),
  91. error_messages={'required': PASSWD2_REQUIRED},
  92. help_text=_lazy(u'Enter the same password as '
  93. 'above, for verification.'))
  94. class Meta(object):
  95. model = User
  96. fields = ('username', 'password', 'password2', 'email')
  97. def clean(self):
  98. super(RegisterForm, self).clean()
  99. username = self.cleaned_data.get('username')
  100. password = self.cleaned_data.get('password')
  101. password2 = self.cleaned_data.get('password2')
  102. if not password == password2:
  103. raise forms.ValidationError(_('Passwords must match.'))
  104. _check_password(password)
  105. _check_username(username)
  106. return self.cleaned_data
  107. def clean_email(self):
  108. email = self.cleaned_data['email']
  109. if User.objects.filter(email=email).exists():
  110. raise forms.ValidationError(_('A user with that email address '
  111. 'already exists.'))
  112. return email
  113. def __init__(self, request=None, *args, **kwargs):
  114. super(RegisterForm, self).__init__(request, auto_id='id_for_%s',
  115. *args, **kwargs)
  116. class AuthenticationForm(auth_forms.AuthenticationForm):
  117. """Overrides the default django form.
  118. * Doesn't prefill password on validation error.
  119. * Allows logging in inactive users (initialize with `only_active=False`).
  120. """
  121. password = forms.CharField(label=_lazy(u"Password"),
  122. widget=forms.PasswordInput(render_value=False))
  123. def __init__(self, request=None, only_active=True, *args, **kwargs):
  124. self.only_active = only_active
  125. super(AuthenticationForm, self).__init__(request, *args, **kwargs)
  126. def clean(self):
  127. username = self.cleaned_data.get('username')
  128. password = self.cleaned_data.get('password')
  129. if username and password:
  130. self.user_cache = authenticate(username=username,
  131. password=password)
  132. if self.user_cache is None:
  133. raise forms.ValidationError(
  134. _('Please enter a correct username and password. Note '
  135. 'that both fields are case-sensitive.'))
  136. elif self.only_active and not self.user_cache.is_active:
  137. raise forms.ValidationError(_('This account is inactive.'))
  138. if self.request:
  139. if not self.request.session.test_cookie_worked():
  140. raise forms.ValidationError(
  141. _("Your Web browser doesn't appear to have cookies "
  142. "enabled. Cookies are required for logging in."))
  143. return self.cleaned_data
  144. class ProfileForm(forms.ModelForm):
  145. """The form for editing the user's profile."""
  146. class Meta(object):
  147. model = Profile
  148. fields = ('name', 'public_email', 'bio', 'website', 'twitter',
  149. 'facebook', 'irc_handle', 'timezone', 'country', 'city',
  150. 'locale')
  151. widgets = {
  152. 'twitter': TwitterURLWidget,
  153. 'facebook': FacebookURLWidget,
  154. }
  155. def clean_twitter(self):
  156. twitter = self.cleaned_data['twitter']
  157. if twitter and not re.match(TwitterURLWidget.pattern, twitter):
  158. raise forms.ValidationError(_(u'Please enter a twitter.com URL.'))
  159. return twitter
  160. def clean_facebook(self):
  161. facebook = self.cleaned_data['facebook']
  162. if facebook and not re.match(FacebookURLWidget.pattern, facebook):
  163. raise forms.ValidationError(_(u'Please enter a facebook.com URL.'))
  164. return facebook
  165. class AvatarForm(forms.ModelForm):
  166. """The form for editing the user's avatar."""
  167. avatar = forms.ImageField(required=True, widget=ImageWidget)
  168. def __init__(self, *args, **kwargs):
  169. super(AvatarForm, self).__init__(*args, **kwargs)
  170. self.fields['avatar'].help_text = (
  171. u'Your avatar will be resized to {size}x{size}'.format(
  172. size=settings.AVATAR_SIZE))
  173. class Meta(object):
  174. model = Profile
  175. fields = ('avatar',)
  176. def clean_avatar(self):
  177. if not ('avatar' in self.cleaned_data and self.cleaned_data['avatar']):
  178. return self.cleaned_data['avatar']
  179. try:
  180. check_file_size(self.cleaned_data['avatar'],
  181. settings.MAX_AVATAR_FILE_SIZE)
  182. except FileTooLargeError as e:
  183. raise forms.ValidationError(e.args[0])
  184. clean_image_extension(self.cleaned_data.get('avatar'))
  185. return self.cleaned_data['avatar']
  186. class EmailConfirmationForm(forms.Form):
  187. """A simple form that requires an email address."""
  188. email = forms.EmailField(label=_lazy(u'Email address:'))
  189. class EmailChangeForm(forms.Form):
  190. """A simple form that requires an email address and validates that it is
  191. not the current user's email."""
  192. email = forms.EmailField(label=_lazy(u'Email address:'))
  193. def __init__(self, user, *args, **kwargs):
  194. super(EmailChangeForm, self).__init__(*args, **kwargs)
  195. self.user = user
  196. def clean_email(self):
  197. email = self.cleaned_data['email']
  198. if self.user.email == email:
  199. raise forms.ValidationError(_('This is your current email.'))
  200. if User.objects.filter(email=email).exists():
  201. raise forms.ValidationError(_('A user with that email address '
  202. 'already exists.'))
  203. return self.cleaned_data['email']
  204. class SetPasswordForm(auth_forms.SetPasswordForm):
  205. new_password1 = forms.CharField(
  206. label=_lazy(u'New password:'),
  207. min_length=PASSWD_MIN_LENGTH,
  208. widget=forms.PasswordInput(render_value=False),
  209. error_messages={'required': PASSWD_REQUIRED,
  210. 'min_length': PASSWD_MIN_LENGTH_MSG})
  211. def clean(self):
  212. super(SetPasswordForm, self).clean()
  213. _check_password(self.cleaned_data.get('new_password1'))
  214. return self.cleaned_data
  215. class PasswordChangeForm(auth_forms.PasswordChangeForm):
  216. new_password1 = forms.CharField(
  217. label=_lazy(u'New password:'),
  218. min_length=PASSWD_MIN_LENGTH,
  219. widget=forms.PasswordInput(render_value=False),
  220. error_messages={'required': PASSWD_REQUIRED,
  221. 'min_length': PASSWD_MIN_LENGTH_MSG})
  222. def clean(self):
  223. super(PasswordChangeForm, self).clean()
  224. _check_password(self.cleaned_data.get('new_password1'))
  225. return self.cleaned_data
  226. class ForgotUsernameForm(forms.Form):
  227. """A simple form to retrieve username.
  228. Requires an email address."""
  229. email = forms.EmailField(label=_lazy(u'Email address:'))
  230. def clean_email(self):
  231. """
  232. Validates that an active user exists with the given e-mail address.
  233. """
  234. email = self.cleaned_data["email"]
  235. try:
  236. self.user = User.objects.get(email__iexact=email, is_active=True)
  237. except User.DoesNotExist:
  238. raise forms.ValidationError(
  239. _(u"That e-mail address doesn't have an associated user"
  240. " account. Are you sure you've registered?"))
  241. return email
  242. def save(self, email_template='users/email/forgot_username.ltxt',
  243. use_https=False, request=None):
  244. """Sends email with username."""
  245. user = self.user
  246. current_site = get_current_site(request)
  247. site_name = current_site.name
  248. domain = current_site.domain
  249. t = loader.get_template(email_template)
  250. c = {
  251. 'email': user.email,
  252. 'domain': domain,
  253. 'login_url': reverse('users.login'),
  254. 'site_name': site_name,
  255. 'username': user.username,
  256. 'protocol': use_https and 'https' or 'http'}
  257. send_mail(
  258. _("Your username on %s") % site_name,
  259. t.render(Context(c)), settings.TIDINGS_FROM_ADDRESS, [user.email])
  260. def _check_password(password):
  261. if password: # Oddly, empty password validation happens after this.
  262. if not password_allowed(password):
  263. msg = _('The password entered is known to be commonly used and '
  264. 'is not allowed.')
  265. raise forms.ValidationError(msg)
  266. if not password_re.search(password):
  267. msg = _('At least one number and one English letter are required '
  268. 'in the password.')
  269. raise forms.ValidationError(msg)
  270. def _check_username(username):
  271. if not username_allowed(username):
  272. msg = _('The user name you entered is inappropriate. Please pick '
  273. 'another and consider that our helpers are other Firefox '
  274. 'users just like you.')
  275. raise forms.ValidationError(msg)