annotate accounts/forms.py @ 791:0ca691cccf8d

Utilize select_related() for user & user profiles. This commit also removes the caching of the avatar URL in the avatar template tag. This is because we are now using select_related, so we already have the profile & avatar when we get to the tag. Thus we don't need to waste time querying the cache. Removed an apparently unused member map template as well.
author Brian Neal <bgneal@gmail.com>
date Fri, 23 May 2014 21:52:41 -0500
parents 9133b4626a4b
children f416f5db3d91
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@782 175 self._validation_error("Sorry, try again", 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))