annotate accounts/forms.py @ 887:9a15f7c27526

Actually save model object upon change. This commit was tested on the comments model. Additional logging added. Added check for Markdown image references. Added TODOs after observing behavior on comments.
author Brian Neal <bgneal@gmail.com>
date Tue, 03 Feb 2015 21:09:44 -0600
parents f416f5db3d91
children 79a71b9d0a2a
rev   line source
gremmie@1 1 """forms for the accounts application"""
gremmie@1 2
bgneal@74 3 import logging
bgneal@74 4
gremmie@1 5 from django import forms
gremmie@1 6 from django.contrib.auth.models import User
gremmie@1 7 from django.core.urlresolvers import reverse
gremmie@1 8 from django.template.loader import render_to_string
gremmie@1 9 from django.contrib.sites.models import Site
bgneal@6 10 from django.conf import settings
gremmie@1 11
gremmie@1 12 from core.functions import send_mail
gremmie@1 13 from accounts.models import PendingUser
gremmie@1 14 from accounts.models import IllegalUsername
gremmie@1 15 from accounts.models import IllegalEmail
bgneal@690 16
bgneal@690 17
bgneal@690 18 logger = logging.getLogger('auth')
gremmie@1 19
gremmie@1 20
gremmie@1 21 class RegisterForm(forms.Form):
bgneal@74 22 """Form used to register with the website"""
bgneal@498 23 username = forms.RegexField(
bgneal@498 24 max_length=30,
bgneal@498 25 regex=r'^\w+$',
bgneal@498 26 error_messages={'invalid': ('Your username must be 30 characters or'
bgneal@498 27 ' less and contain only letters, numbers and underscores.')},
bgneal@498 28 widget=forms.TextInput(attrs={'class': 'text'}),
bgneal@498 29 )
bgneal@498 30 email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@498 31 password1 = forms.CharField(label="Password",
bgneal@498 32 widget=forms.PasswordInput(attrs={'class': 'text'}))
bgneal@498 33 password2 = forms.CharField(label="Password confirmation",
bgneal@498 34 widget=forms.PasswordInput(attrs={'class': 'text'}))
bgneal@316 35 agree_age = forms.BooleanField(required=True,
bgneal@155 36 label='I certify that I am over the age of 13',
bgneal@155 37 error_messages={
bgneal@347 38 'required': 'Sorry, but you must be over the age of 13 to '
bgneal@155 39 'register at our site.',
bgneal@155 40 })
bgneal@316 41 agree_tos = forms.BooleanField(required=True,
bgneal@155 42 label='I agree to the Terms of Service',
bgneal@155 43 error_messages={
bgneal@155 44 'required': 'You have not agreed to our Terms of Service.',
bgneal@155 45 })
bgneal@155 46 agree_privacy = forms.BooleanField(required=True,
bgneal@155 47 label='I agree to the Privacy Policy',
bgneal@155 48 error_messages={
bgneal@155 49 'required': 'You have not agreed to our Privacy Policy.',
bgneal@155 50 })
bgneal@498 51 question1 = forms.CharField(label="What number appears in the site name?",
bgneal@498 52 widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@347 53 question2 = forms.CharField(label='', required=False,
bgneal@347 54 widget=forms.TextInput(attrs={'style': 'display: none;'}))
bgneal@782 55 question3 = forms.CharField(label="Don't put anything in this box",
bgneal@782 56 required=False,
bgneal@782 57 widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@782 58
bgneal@782 59 SPAM_CHOICES = [(1, 'cat'), (2, '328'), (4, 'green'), (8, 'ocean')]
bgneal@782 60 question4 = forms.ChoiceField(label="Pick the number", choices=SPAM_CHOICES)
bgneal@782 61 question5 = forms.IntegerField(label="What number did you pick, above?",
bgneal@782 62 widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@782 63 question6 = forms.ChoiceField(label="Pick the color", choices=SPAM_CHOICES)
bgneal@782 64
bgneal@782 65 USER_QUALITIES = [
bgneal@782 66 (1, "I am the lowest form of life, a spammer"),
bgneal@782 67 (2, "I'm a fan of surf music or I want to learn more"),
bgneal@782 68 (3, "Someone is paying me to post links to this site"),
bgneal@782 69 (4, "I am not going to spam this site"),
bgneal@782 70 (5, "I understand my account may be removed if I post spam"),
bgneal@782 71 (6, "I am going to spam the crap out of this site"),
bgneal@782 72 (7, "Surf music is one of my interests, not spam"),
bgneal@782 73 ]
bgneal@782 74 question7 = forms.MultipleChoiceField(
bgneal@782 75 label="Check all that apply",
bgneal@782 76 choices=USER_QUALITIES,
bgneal@782 77 required=False,
bgneal@782 78 widget=forms.CheckboxSelectMultiple)
bgneal@782 79
gremmie@1 80
bgneal@74 81 def __init__(self, *args, **kwargs):
bgneal@74 82 self.ip = kwargs.pop('ip', '?')
bgneal@74 83 super(RegisterForm, self).__init__(*args, **kwargs)
bgneal@74 84
bgneal@74 85 def clean_username(self):
bgneal@74 86 username = self.cleaned_data['username']
bgneal@74 87 try:
bgneal@565 88 User.objects.get(username=username)
bgneal@74 89 except User.DoesNotExist:
gremmie@1 90 try:
bgneal@565 91 PendingUser.objects.get(username=username)
bgneal@74 92 except PendingUser.DoesNotExist:
bgneal@74 93 try:
bgneal@565 94 IllegalUsername.objects.get(username=username)
bgneal@74 95 except IllegalUsername.DoesNotExist:
bgneal@74 96 return username
bgneal@74 97 self._validation_error("That username is not allowed.", username)
bgneal@74 98 self._validation_error("A pending user with that username already exists.", username)
bgneal@74 99 self._validation_error("A user with that username already exists.", username)
gremmie@1 100
bgneal@74 101 def clean_email(self):
bgneal@74 102 email = self.cleaned_data['email']
bgneal@565 103
bgneal@565 104 if User.objects.filter(email=email).count():
bgneal@565 105 self._validation_error("A user with that email address already exists.", email)
bgneal@565 106 elif PendingUser.objects.filter(email=email).count():
bgneal@74 107 self._validation_error("A pending user with that email address already exists.", email)
bgneal@565 108 elif IllegalEmail.objects.filter(email=email).count():
bgneal@565 109 self._validation_error("That email address is not allowed.", email)
bgneal@659 110
bgneal@565 111 # email is ok
bgneal@565 112 return email
gremmie@1 113
bgneal@74 114 def clean_password2(self):
bgneal@74 115 password1 = self.cleaned_data.get("password1", "")
bgneal@74 116 password2 = self.cleaned_data["password2"]
bgneal@74 117 if password1 != password2:
bgneal@74 118 self._validation_error("The two password fields didn't match.")
bgneal@155 119 if len(password1) < 6:
bgneal@155 120 self._validation_error("Please choose a password of 6 characters or more.")
bgneal@74 121 return password2
gremmie@1 122
bgneal@346 123 def clean_question1(self):
bgneal@346 124 answer = self.cleaned_data.get('question1')
bgneal@346 125 success = False
bgneal@346 126 if answer:
bgneal@346 127 try:
bgneal@346 128 val = int(answer)
bgneal@346 129 except ValueError:
bgneal@346 130 pass
bgneal@346 131 else:
bgneal@346 132 success = val == 101
bgneal@346 133 if not success:
bgneal@346 134 self._validation_error("Incorrect answer to our anti-spam question.", answer)
bgneal@346 135 return answer
bgneal@346 136
bgneal@347 137 def clean_question2(self):
bgneal@347 138 """
bgneal@347 139 Honeypot field should be empty.
bgneal@347 140 """
bgneal@347 141 answer = self.cleaned_data.get('question2')
bgneal@347 142 if answer:
bgneal@690 143 logger.critical('Accounts/registration: Honeypot filled [%s]', self.ip)
bgneal@690 144 self._validation_error('Wrong answer #2', answer)
bgneal@347 145 return answer
bgneal@347 146
bgneal@782 147 def clean_question3(self):
bgneal@782 148 answer = self.cleaned_data.get('question3')
bgneal@782 149 if answer:
bgneal@782 150 self._validation_error('Oops, that should be blank', answer)
bgneal@782 151 return answer
bgneal@782 152
bgneal@782 153 def clean_question4(self):
bgneal@782 154 answer = self.cleaned_data.get('question4')
bgneal@782 155 if answer != u'2':
bgneal@782 156 self._validation_error('Please pick the number', answer)
bgneal@782 157 return answer
bgneal@782 158
bgneal@782 159 def clean_question5(self):
bgneal@782 160 answer = self.cleaned_data.get('question5')
bgneal@782 161 if answer != 328:
bgneal@782 162 self._validation_error('Please enter the correct number', answer)
bgneal@782 163 return answer
bgneal@782 164
bgneal@782 165 def clean_question6(self):
bgneal@782 166 answer = self.cleaned_data.get('question6')
bgneal@782 167 if answer != u'4':
bgneal@782 168 self._validation_error('Please pick the color', answer)
bgneal@782 169 return answer
bgneal@782 170
bgneal@782 171 def clean_question7(self):
bgneal@782 172 answer = self.cleaned_data.get('question7')
bgneal@782 173 answer.sort()
bgneal@782 174 if answer != [u'2', u'4', u'5', u'7']:
bgneal@794 175 self._validation_error("Please double-check your checkboxes", answer)
bgneal@782 176 return answer
bgneal@782 177
bgneal@74 178 def save(self):
bgneal@74 179 pending_user = PendingUser.objects.create_pending_user(self.cleaned_data['username'],
bgneal@74 180 self.cleaned_data['email'],
bgneal@74 181 self.cleaned_data['password1'])
gremmie@1 182
bgneal@74 183 # Send the confirmation email
gremmie@1 184
bgneal@74 185 site = Site.objects.get_current()
bgneal@74 186 admin_email = settings.ADMINS[0][1]
gremmie@1 187
bgneal@316 188 activation_link = 'http://%s%s' % (site.domain, reverse('accounts.views.register_confirm',
bgneal@74 189 kwargs = {'username' : pending_user.username, 'key' : pending_user.key}))
gremmie@1 190
bgneal@74 191 msg = render_to_string('accounts/registration_email.txt',
bgneal@74 192 {
bgneal@74 193 'site_name' : site.name,
bgneal@74 194 'site_domain' : site.domain,
bgneal@74 195 'user_email' : pending_user.email,
bgneal@74 196 'activation_link' : activation_link,
bgneal@74 197 'username' : pending_user.username,
bgneal@74 198 'admin_email' : admin_email,
bgneal@74 199 })
gremmie@1 200
bgneal@74 201 subject = 'Registration Confirmation for ' + site.name
bgneal@659 202 send_mail(subject, msg, admin_email, [self.cleaned_data['email']],
bgneal@659 203 defer=False)
bgneal@690 204 logger.info('Accounts/registration conf. email sent to %s for user %s; IP = %s',
bgneal@316 205 self.cleaned_data['email'], pending_user.username, self.ip)
gremmie@1 206
bgneal@74 207 return pending_user
gremmie@1 208
bgneal@74 209 def _validation_error(self, msg, param=None):
bgneal@690 210 logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param)
bgneal@74 211 raise forms.ValidationError(msg)
bgneal@659 212
bgneal@659 213
bgneal@659 214 class ForgotUsernameForm(forms.Form):
bgneal@659 215 """Form used to recover lost username"""
bgneal@659 216 email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@659 217
bgneal@659 218 def save(self):
bgneal@659 219 """Email the username associated with the email address to the user."""
bgneal@659 220 email = self.cleaned_data['email']
bgneal@659 221 try:
bgneal@659 222 user = User.objects.get(email=email)
bgneal@659 223 except User.DoesNotExist:
bgneal@659 224 # nothing to do; we don't tell the user as this gives away info
bgneal@659 225 # about our database
bgneal@659 226 return
bgneal@659 227
bgneal@659 228 site = Site.objects.get_current()
bgneal@659 229 admin_email = settings.ADMINS[0][1]
bgneal@659 230
bgneal@659 231 subject = 'Forgotten username for %s' % site.name
bgneal@659 232 msg = render_to_string('accounts/forgot_user_email.txt', {
bgneal@659 233 'user': user,
bgneal@659 234 'site': site,
bgneal@659 235 'admin_email': admin_email,
bgneal@659 236 })
bgneal@659 237 send_mail(subject, msg, admin_email, [email], defer=False)
bgneal@659 238
bgneal@690 239 logger.info('Forgotten username email sent to {} <{}>'.format(
bgneal@659 240 user.username, email))