comparison accounts/forms.py @ 905:be233ba7ca31

Reworked registration process. Previous one proved too challenging for some humans. Hopefully made it simpler but still unusual to confuse bots. Increased test coverage also.
author Brian Neal <bgneal@gmail.com>
date Sun, 08 Mar 2015 11:06:07 -0500
parents 79a71b9d0a2a
children 4aadaf3bc234
comparison
equal deleted inserted replaced
904:d4479ebbd118 905:be233ba7ca31
1 """forms for the accounts application""" 1 """forms for the accounts application"""
2 2
3 import logging 3 import logging
4 import random
4 5
5 from django import forms 6 from django import forms
6 from django.contrib.auth.models import User 7 from django.contrib.auth.models import User
7 from django.core.urlresolvers import reverse 8 from django.core.urlresolvers import reverse
8 from django.template.loader import render_to_string 9 from django.template.loader import render_to_string
14 from accounts.models import IllegalUsername 15 from accounts.models import IllegalUsername
15 from accounts.models import IllegalEmail 16 from accounts.models import IllegalEmail
16 17
17 18
18 logger = logging.getLogger('auth') 19 logger = logging.getLogger('auth')
20
21
22 CODE_WORD_CHOICES = [
23 'reverb',
24 'sg101',
25 'guitar',
26 'showman',
27 'surf',
28 'surfer',
29 'tank',
30 'splash',
31 'gremmie',
32 'hodad',
33 'stomp',
34 'belairs',
35 'dickdale',
36 'chantays',
37 'astronauts',
38 'fender',
39 ]
40
41 def generate_registration_code():
42 """Generates a simple, random registration code for the user to enter."""
43 return '{}-{}'.format(random.choice(CODE_WORD_CHOICES), random.randint(100, 999))
19 44
20 45
21 class RegisterForm(forms.Form): 46 class RegisterForm(forms.Form):
22 """Form used to register with the website""" 47 """Form used to register with the website"""
23 username = forms.RegexField( 48 username = forms.RegexField(
54 widget=forms.TextInput(attrs={'style': 'display: none;'})) 79 widget=forms.TextInput(attrs={'style': 'display: none;'}))
55 question3 = forms.CharField(label="Don't put anything in this box", 80 question3 = forms.CharField(label="Don't put anything in this box",
56 required=False, 81 required=False,
57 widget=forms.TextInput(attrs={'class': 'text'})) 82 widget=forms.TextInput(attrs={'class': 'text'}))
58 83
59 SPAM_CHOICES = [(1, 'cat'), (2, '328'), (4, 'green'), (8, 'ocean')]
60 question4 = forms.ChoiceField(label="Pick the number", choices=SPAM_CHOICES)
61 question5 = forms.IntegerField(label="What number did you pick, above?",
62 widget=forms.TextInput(attrs={'class': 'text'}))
63 question6 = forms.ChoiceField(label="Pick the color", choices=SPAM_CHOICES)
64
65 USER_QUALITIES = [
66 (1, "I am the lowest form of life, a spammer"),
67 (2, "I'm a fan of surf music or I want to learn more"),
68 (3, "Someone is paying me to post links to this site"),
69 (4, "I am not going to spam this site"),
70 (5, "I understand my account may be removed if I post spam"),
71 (6, "I am going to spam the crap out of this site"),
72 (7, "Surf music is one of my interests, not spam"),
73 ]
74 question7 = forms.MultipleChoiceField(
75 label="Check all that apply",
76 choices=USER_QUALITIES,
77 required=False,
78 widget=forms.CheckboxSelectMultiple)
79
80
81 def __init__(self, *args, **kwargs): 84 def __init__(self, *args, **kwargs):
82 self.ip = kwargs.pop('ip', '?') 85 self.ip = kwargs.pop('ip', '?')
83 super(RegisterForm, self).__init__(*args, **kwargs) 86 super(RegisterForm, self).__init__(*args, **kwargs)
84 87
85 def clean_username(self): 88 def clean_username(self):
148 answer = self.cleaned_data.get('question3') 151 answer = self.cleaned_data.get('question3')
149 if answer: 152 if answer:
150 self._validation_error('Oops, that should be blank', answer) 153 self._validation_error('Oops, that should be blank', answer)
151 return answer 154 return answer
152 155
153 def clean_question4(self): 156 def save(self, request):
154 answer = self.cleaned_data.get('question4') 157 request.session['reg_info'] = {
155 if answer != u'2': 158 'username': self.cleaned_data['username'],
156 self._validation_error('Please pick the number', answer) 159 'email': self.cleaned_data['email'],
157 return answer 160 'password': self.cleaned_data['password1'],
158 161 'code': generate_registration_code(),
159 def clean_question5(self): 162 }
160 answer = self.cleaned_data.get('question5') 163
161 if answer != 328: 164 def _validation_error(self, msg, param=None):
162 self._validation_error('Please enter the correct number', answer) 165 logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param)
163 return answer 166 raise forms.ValidationError(msg)
164 167
165 def clean_question6(self): 168
166 answer = self.cleaned_data.get('question6') 169 class RegisterCodeForm(forms.Form):
167 if answer != u'4': 170 """Form for processing anti-spam code."""
168 self._validation_error('Please pick the color', answer) 171 code = forms.CharField(
169 return answer 172 label="Registration code",
170 173 widget=forms.TextInput(attrs={'class': 'text'}))
171 def clean_question7(self): 174
172 answer = self.cleaned_data.get('question7') 175 def __init__(self, *args, **kwargs):
173 answer.sort() 176 self.session = kwargs.pop('session', None)
174 if answer != [u'2', u'4', u'5', u'7']: 177 self.ip = kwargs.pop('ip', '?')
175 self._validation_error("Please double-check your checkboxes", answer) 178 super(RegisterCodeForm, self).__init__(*args, **kwargs)
176 return answer 179
180 def clean_code(self):
181 reg_info = self.session.get('reg_info')
182 saved_code = reg_info.get('code') if reg_info else None
183 if not saved_code:
184 self._validation_error("Registration code error")
185 user_code = self.cleaned_data['code']
186 if user_code:
187 user_code = user_code.strip()
188
189 if user_code != saved_code:
190 self._validation_error("The registration code does not match")
177 191
178 def save(self): 192 def save(self):
179 pending_user = PendingUser.objects.create_pending_user(self.cleaned_data['username'], 193 """Process successful registration."""
180 self.cleaned_data['email'], 194 reg_info = self.session['reg_info']
181 self.cleaned_data['password1']) 195 username = reg_info['username']
196 email = reg_info['email']
197 password = reg_info['password']
198
199 pending_user = PendingUser.objects.create_pending_user(
200 username,
201 email,
202 password)
182 203
183 # Send the confirmation email 204 # Send the confirmation email
184
185 site = Site.objects.get_current() 205 site = Site.objects.get_current()
186 admin_email = settings.DEFAULT_FROM_EMAIL 206 admin_email = settings.DEFAULT_FROM_EMAIL
187 207
188 activation_link = 'http://%s%s' % (site.domain, reverse('accounts.views.register_confirm', 208 activation_link = 'http://%s%s' % (
189 kwargs = {'username' : pending_user.username, 'key' : pending_user.key})) 209 site.domain,
190 210 reverse('accounts-register_confirm',
191 msg = render_to_string('accounts/registration_email.txt', 211 kwargs={'username': pending_user.username,
192 { 212 'key': pending_user.key}))
193 'site_name' : site.name, 213
194 'site_domain' : site.domain, 214 msg = render_to_string('accounts/registration_email.txt', {
195 'user_email' : pending_user.email, 215 'site_name' : site.name,
196 'activation_link' : activation_link, 216 'site_domain' : site.domain,
197 'username' : pending_user.username, 217 'user_email' : pending_user.email,
198 'admin_email' : admin_email, 218 'activation_link' : activation_link,
219 'username' : pending_user.username,
220 'admin_email' : admin_email,
199 }) 221 })
200 222
201 subject = 'Registration Confirmation for ' + site.name 223 subject = 'Registration Confirmation for ' + site.name
202 send_mail(subject, msg, admin_email, [self.cleaned_data['email']]) 224 send_mail(subject, msg, admin_email, [email])
203 logger.info('Accounts/registration conf. email sent to %s for user %s; IP = %s', 225 logger.info('Accounts/registration conf. email sent to %s for user %s; IP = %s',
204 self.cleaned_data['email'], pending_user.username, self.ip) 226 email, pending_user.username, self.ip)
205 227 del self.session['reg_info']
206 return pending_user
207 228
208 def _validation_error(self, msg, param=None): 229 def _validation_error(self, msg, param=None):
209 logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param) 230 logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param)
210 raise forms.ValidationError(msg) 231 raise forms.ValidationError(msg)
211 232