annotate accounts/forms.py @ 943:cf9918328c64

Haystack tweaks for Django 1.7.7. I had to upgrade to Haystack 2.3.1 to get it to work with Django 1.7.7. I also had to update the Xapian backend. But I ran into problems. On my laptop anyway (Ubuntu 14.0.4), xapian gets mad when search terms are greater than 245 chars (or something) when indexing. So I created a custom field that would simply omit terms greater than 64 chars and used this field everywhere I previously used a CharField. Secondly, the custom search form was broken now. Something changed in the Xapian backend and exact searches stopped working. Fortunately the auto_query (which I was using originally and broke during an upgrade) started working again. So I cut the search form back over to doing an auto_query. I kept the form the same (3 fields) because I didn't want to change the form and I think it's better that way.
author Brian Neal <bgneal@gmail.com>
date Wed, 13 May 2015 20:25:07 -0500
parents be233ba7ca31
children 4aadaf3bc234
rev   line source
gremmie@1 1 """forms for the accounts application"""
gremmie@1 2
bgneal@74 3 import logging
bgneal@905 4 import random
bgneal@74 5
gremmie@1 6 from django import forms
gremmie@1 7 from django.contrib.auth.models import User
gremmie@1 8 from django.core.urlresolvers import reverse
gremmie@1 9 from django.template.loader import render_to_string
gremmie@1 10 from django.contrib.sites.models import Site
bgneal@6 11 from django.conf import settings
gremmie@1 12
gremmie@1 13 from core.functions import send_mail
gremmie@1 14 from accounts.models import PendingUser
gremmie@1 15 from accounts.models import IllegalUsername
gremmie@1 16 from accounts.models import IllegalEmail
bgneal@690 17
bgneal@690 18
bgneal@690 19 logger = logging.getLogger('auth')
gremmie@1 20
gremmie@1 21
bgneal@905 22 CODE_WORD_CHOICES = [
bgneal@905 23 'reverb',
bgneal@905 24 'sg101',
bgneal@905 25 'guitar',
bgneal@905 26 'showman',
bgneal@905 27 'surf',
bgneal@905 28 'surfer',
bgneal@905 29 'tank',
bgneal@905 30 'splash',
bgneal@905 31 'gremmie',
bgneal@905 32 'hodad',
bgneal@905 33 'stomp',
bgneal@905 34 'belairs',
bgneal@905 35 'dickdale',
bgneal@905 36 'chantays',
bgneal@905 37 'astronauts',
bgneal@905 38 'fender',
bgneal@905 39 ]
bgneal@905 40
bgneal@905 41 def generate_registration_code():
bgneal@905 42 """Generates a simple, random registration code for the user to enter."""
bgneal@905 43 return '{}-{}'.format(random.choice(CODE_WORD_CHOICES), random.randint(100, 999))
bgneal@905 44
bgneal@905 45
gremmie@1 46 class RegisterForm(forms.Form):
bgneal@74 47 """Form used to register with the website"""
bgneal@498 48 username = forms.RegexField(
bgneal@498 49 max_length=30,
bgneal@498 50 regex=r'^\w+$',
bgneal@498 51 error_messages={'invalid': ('Your username must be 30 characters or'
bgneal@498 52 ' less and contain only letters, numbers and underscores.')},
bgneal@498 53 widget=forms.TextInput(attrs={'class': 'text'}),
bgneal@498 54 )
bgneal@498 55 email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@498 56 password1 = forms.CharField(label="Password",
bgneal@498 57 widget=forms.PasswordInput(attrs={'class': 'text'}))
bgneal@498 58 password2 = forms.CharField(label="Password confirmation",
bgneal@498 59 widget=forms.PasswordInput(attrs={'class': 'text'}))
bgneal@316 60 agree_age = forms.BooleanField(required=True,
bgneal@155 61 label='I certify that I am over the age of 13',
bgneal@155 62 error_messages={
bgneal@347 63 'required': 'Sorry, but you must be over the age of 13 to '
bgneal@155 64 'register at our site.',
bgneal@155 65 })
bgneal@316 66 agree_tos = forms.BooleanField(required=True,
bgneal@155 67 label='I agree to the Terms of Service',
bgneal@155 68 error_messages={
bgneal@155 69 'required': 'You have not agreed to our Terms of Service.',
bgneal@155 70 })
bgneal@155 71 agree_privacy = forms.BooleanField(required=True,
bgneal@155 72 label='I agree to the Privacy Policy',
bgneal@155 73 error_messages={
bgneal@155 74 'required': 'You have not agreed to our Privacy Policy.',
bgneal@155 75 })
bgneal@498 76 question1 = forms.CharField(label="What number appears in the site name?",
bgneal@498 77 widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@347 78 question2 = forms.CharField(label='', required=False,
bgneal@347 79 widget=forms.TextInput(attrs={'style': 'display: none;'}))
bgneal@782 80 question3 = forms.CharField(label="Don't put anything in this box",
bgneal@782 81 required=False,
bgneal@782 82 widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@782 83
bgneal@74 84 def __init__(self, *args, **kwargs):
bgneal@74 85 self.ip = kwargs.pop('ip', '?')
bgneal@74 86 super(RegisterForm, self).__init__(*args, **kwargs)
bgneal@74 87
bgneal@74 88 def clean_username(self):
bgneal@74 89 username = self.cleaned_data['username']
bgneal@74 90 try:
bgneal@565 91 User.objects.get(username=username)
bgneal@74 92 except User.DoesNotExist:
gremmie@1 93 try:
bgneal@565 94 PendingUser.objects.get(username=username)
bgneal@74 95 except PendingUser.DoesNotExist:
bgneal@74 96 try:
bgneal@565 97 IllegalUsername.objects.get(username=username)
bgneal@74 98 except IllegalUsername.DoesNotExist:
bgneal@74 99 return username
bgneal@74 100 self._validation_error("That username is not allowed.", username)
bgneal@74 101 self._validation_error("A pending user with that username already exists.", username)
bgneal@74 102 self._validation_error("A user with that username already exists.", username)
gremmie@1 103
bgneal@74 104 def clean_email(self):
bgneal@74 105 email = self.cleaned_data['email']
bgneal@565 106
bgneal@565 107 if User.objects.filter(email=email).count():
bgneal@565 108 self._validation_error("A user with that email address already exists.", email)
bgneal@565 109 elif PendingUser.objects.filter(email=email).count():
bgneal@74 110 self._validation_error("A pending user with that email address already exists.", email)
bgneal@565 111 elif IllegalEmail.objects.filter(email=email).count():
bgneal@565 112 self._validation_error("That email address is not allowed.", email)
bgneal@659 113
bgneal@565 114 # email is ok
bgneal@565 115 return email
gremmie@1 116
bgneal@74 117 def clean_password2(self):
bgneal@74 118 password1 = self.cleaned_data.get("password1", "")
bgneal@74 119 password2 = self.cleaned_data["password2"]
bgneal@74 120 if password1 != password2:
bgneal@74 121 self._validation_error("The two password fields didn't match.")
bgneal@155 122 if len(password1) < 6:
bgneal@155 123 self._validation_error("Please choose a password of 6 characters or more.")
bgneal@74 124 return password2
gremmie@1 125
bgneal@346 126 def clean_question1(self):
bgneal@346 127 answer = self.cleaned_data.get('question1')
bgneal@346 128 success = False
bgneal@346 129 if answer:
bgneal@346 130 try:
bgneal@346 131 val = int(answer)
bgneal@346 132 except ValueError:
bgneal@346 133 pass
bgneal@346 134 else:
bgneal@346 135 success = val == 101
bgneal@346 136 if not success:
bgneal@346 137 self._validation_error("Incorrect answer to our anti-spam question.", answer)
bgneal@346 138 return answer
bgneal@346 139
bgneal@347 140 def clean_question2(self):
bgneal@347 141 """
bgneal@347 142 Honeypot field should be empty.
bgneal@347 143 """
bgneal@347 144 answer = self.cleaned_data.get('question2')
bgneal@347 145 if answer:
bgneal@690 146 logger.critical('Accounts/registration: Honeypot filled [%s]', self.ip)
bgneal@690 147 self._validation_error('Wrong answer #2', answer)
bgneal@347 148 return answer
bgneal@347 149
bgneal@782 150 def clean_question3(self):
bgneal@782 151 answer = self.cleaned_data.get('question3')
bgneal@782 152 if answer:
bgneal@782 153 self._validation_error('Oops, that should be blank', answer)
bgneal@782 154 return answer
bgneal@782 155
bgneal@905 156 def save(self, request):
bgneal@905 157 request.session['reg_info'] = {
bgneal@905 158 'username': self.cleaned_data['username'],
bgneal@905 159 'email': self.cleaned_data['email'],
bgneal@905 160 'password': self.cleaned_data['password1'],
bgneal@905 161 'code': generate_registration_code(),
bgneal@905 162 }
bgneal@782 163
bgneal@905 164 def _validation_error(self, msg, param=None):
bgneal@905 165 logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param)
bgneal@905 166 raise forms.ValidationError(msg)
bgneal@782 167
bgneal@782 168
bgneal@905 169 class RegisterCodeForm(forms.Form):
bgneal@905 170 """Form for processing anti-spam code."""
bgneal@905 171 code = forms.CharField(
bgneal@905 172 label="Registration code",
bgneal@905 173 widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@905 174
bgneal@905 175 def __init__(self, *args, **kwargs):
bgneal@905 176 self.session = kwargs.pop('session', None)
bgneal@905 177 self.ip = kwargs.pop('ip', '?')
bgneal@905 178 super(RegisterCodeForm, self).__init__(*args, **kwargs)
bgneal@905 179
bgneal@905 180 def clean_code(self):
bgneal@905 181 reg_info = self.session.get('reg_info')
bgneal@905 182 saved_code = reg_info.get('code') if reg_info else None
bgneal@905 183 if not saved_code:
bgneal@905 184 self._validation_error("Registration code error")
bgneal@905 185 user_code = self.cleaned_data['code']
bgneal@905 186 if user_code:
bgneal@905 187 user_code = user_code.strip()
bgneal@905 188
bgneal@905 189 if user_code != saved_code:
bgneal@905 190 self._validation_error("The registration code does not match")
bgneal@782 191
bgneal@74 192 def save(self):
bgneal@905 193 """Process successful registration."""
bgneal@905 194 reg_info = self.session['reg_info']
bgneal@905 195 username = reg_info['username']
bgneal@905 196 email = reg_info['email']
bgneal@905 197 password = reg_info['password']
bgneal@905 198
bgneal@905 199 pending_user = PendingUser.objects.create_pending_user(
bgneal@905 200 username,
bgneal@905 201 email,
bgneal@905 202 password)
gremmie@1 203
bgneal@74 204 # Send the confirmation email
bgneal@74 205 site = Site.objects.get_current()
bgneal@892 206 admin_email = settings.DEFAULT_FROM_EMAIL
gremmie@1 207
bgneal@905 208 activation_link = 'http://%s%s' % (
bgneal@905 209 site.domain,
bgneal@905 210 reverse('accounts-register_confirm',
bgneal@905 211 kwargs={'username': pending_user.username,
bgneal@905 212 'key': pending_user.key}))
gremmie@1 213
bgneal@905 214 msg = render_to_string('accounts/registration_email.txt', {
bgneal@905 215 'site_name' : site.name,
bgneal@905 216 'site_domain' : site.domain,
bgneal@905 217 'user_email' : pending_user.email,
bgneal@905 218 'activation_link' : activation_link,
bgneal@905 219 'username' : pending_user.username,
bgneal@905 220 'admin_email' : admin_email,
bgneal@74 221 })
gremmie@1 222
bgneal@74 223 subject = 'Registration Confirmation for ' + site.name
bgneal@905 224 send_mail(subject, msg, admin_email, [email])
bgneal@690 225 logger.info('Accounts/registration conf. email sent to %s for user %s; IP = %s',
bgneal@905 226 email, pending_user.username, self.ip)
bgneal@905 227 del self.session['reg_info']
gremmie@1 228
bgneal@74 229 def _validation_error(self, msg, param=None):
bgneal@690 230 logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param)
bgneal@74 231 raise forms.ValidationError(msg)
bgneal@659 232
bgneal@659 233
bgneal@659 234 class ForgotUsernameForm(forms.Form):
bgneal@659 235 """Form used to recover lost username"""
bgneal@659 236 email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@659 237
bgneal@659 238 def save(self):
bgneal@659 239 """Email the username associated with the email address to the user."""
bgneal@659 240 email = self.cleaned_data['email']
bgneal@659 241 try:
bgneal@659 242 user = User.objects.get(email=email)
bgneal@659 243 except User.DoesNotExist:
bgneal@659 244 # nothing to do; we don't tell the user as this gives away info
bgneal@659 245 # about our database
bgneal@659 246 return
bgneal@659 247
bgneal@659 248 site = Site.objects.get_current()
bgneal@892 249 admin_email = settings.DEFAULT_FROM_EMAIL
bgneal@659 250
bgneal@659 251 subject = 'Forgotten username for %s' % site.name
bgneal@659 252 msg = render_to_string('accounts/forgot_user_email.txt', {
bgneal@659 253 'user': user,
bgneal@659 254 'site': site,
bgneal@659 255 'admin_email': admin_email,
bgneal@659 256 })
bgneal@892 257 send_mail(subject, msg, admin_email, [email])
bgneal@659 258
bgneal@690 259 logger.info('Forgotten username email sent to {} <{}>'.format(
bgneal@659 260 user.username, email))