diff 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
line wrap: on
line diff
--- a/accounts/forms.py	Sun Mar 08 11:01:00 2015 -0500
+++ b/accounts/forms.py	Sun Mar 08 11:06:07 2015 -0500
@@ -1,6 +1,7 @@
 """forms for the accounts application"""
 
 import logging
+import random
 
 from django import forms
 from django.contrib.auth.models import User
@@ -18,6 +19,30 @@
 logger = logging.getLogger('auth')
 
 
+CODE_WORD_CHOICES = [
+    'reverb',
+    'sg101',
+    'guitar',
+    'showman',
+    'surf',
+    'surfer',
+    'tank',
+    'splash',
+    'gremmie',
+    'hodad',
+    'stomp',
+    'belairs',
+    'dickdale',
+    'chantays',
+    'astronauts',
+    'fender',
+]
+
+def generate_registration_code():
+    """Generates a simple, random registration code for the user to enter."""
+    return '{}-{}'.format(random.choice(CODE_WORD_CHOICES), random.randint(100, 999))
+
+
 class RegisterForm(forms.Form):
     """Form used to register with the website"""
     username = forms.RegexField(
@@ -56,28 +81,6 @@
         required=False,
         widget=forms.TextInput(attrs={'class': 'text'}))
 
-    SPAM_CHOICES = [(1, 'cat'), (2, '328'), (4, 'green'), (8, 'ocean')]
-    question4 = forms.ChoiceField(label="Pick the number", choices=SPAM_CHOICES)
-    question5 = forms.IntegerField(label="What number did you pick, above?",
-        widget=forms.TextInput(attrs={'class': 'text'}))
-    question6 = forms.ChoiceField(label="Pick the color", choices=SPAM_CHOICES)
-
-    USER_QUALITIES = [
-        (1, "I am the lowest form of life, a spammer"),
-        (2, "I'm a fan of surf music or I want to learn more"),
-        (3, "Someone is paying me to post links to this site"),
-        (4, "I am not going to spam this site"),
-        (5, "I understand my account may be removed if I post spam"),
-        (6, "I am going to spam the crap out of this site"),
-        (7, "Surf music is one of my interests, not spam"),
-    ]
-    question7 = forms.MultipleChoiceField(
-        label="Check all that apply",
-        choices=USER_QUALITIES,
-        required=False,
-        widget=forms.CheckboxSelectMultiple)
-
-
     def __init__(self, *args, **kwargs):
         self.ip = kwargs.pop('ip', '?')
         super(RegisterForm, self).__init__(*args, **kwargs)
@@ -150,60 +153,78 @@
             self._validation_error('Oops, that should be blank', answer)
         return answer
 
-    def clean_question4(self):
-        answer = self.cleaned_data.get('question4')
-        if answer != u'2':
-            self._validation_error('Please pick the number', answer)
-        return answer
+    def save(self, request):
+        request.session['reg_info'] = {
+            'username': self.cleaned_data['username'],
+            'email': self.cleaned_data['email'],
+            'password': self.cleaned_data['password1'],
+            'code': generate_registration_code(),
+        }
 
-    def clean_question5(self):
-        answer = self.cleaned_data.get('question5')
-        if answer != 328:
-            self._validation_error('Please enter the correct number', answer)
-        return answer
+    def _validation_error(self, msg, param=None):
+        logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param)
+        raise forms.ValidationError(msg)
 
-    def clean_question6(self):
-        answer = self.cleaned_data.get('question6')
-        if answer != u'4':
-            self._validation_error('Please pick the color', answer)
-        return answer
 
-    def clean_question7(self):
-        answer = self.cleaned_data.get('question7')
-        answer.sort()
-        if answer != [u'2', u'4', u'5', u'7']:
-            self._validation_error("Please double-check your checkboxes", answer)
-        return answer
+class RegisterCodeForm(forms.Form):
+    """Form for processing anti-spam code."""
+    code = forms.CharField(
+            label="Registration code",
+            widget=forms.TextInput(attrs={'class': 'text'}))
+
+    def __init__(self, *args, **kwargs):
+        self.session = kwargs.pop('session', None)
+        self.ip = kwargs.pop('ip', '?')
+        super(RegisterCodeForm, self).__init__(*args, **kwargs)
+
+    def clean_code(self):
+        reg_info = self.session.get('reg_info')
+        saved_code = reg_info.get('code') if reg_info else None
+        if not saved_code:
+            self._validation_error("Registration code error")
+        user_code = self.cleaned_data['code']
+        if user_code:
+            user_code = user_code.strip()
+
+        if user_code != saved_code:
+            self._validation_error("The registration code does not match")
 
     def save(self):
-        pending_user = PendingUser.objects.create_pending_user(self.cleaned_data['username'],
-                self.cleaned_data['email'],
-                self.cleaned_data['password1'])
+        """Process successful registration."""
+        reg_info = self.session['reg_info']
+        username = reg_info['username']
+        email = reg_info['email']
+        password = reg_info['password']
+
+        pending_user = PendingUser.objects.create_pending_user(
+                username,
+                email,
+                password)
 
         # Send the confirmation email
-
         site = Site.objects.get_current()
         admin_email = settings.DEFAULT_FROM_EMAIL
 
-        activation_link = 'http://%s%s' % (site.domain, reverse('accounts.views.register_confirm',
-                kwargs = {'username' : pending_user.username, 'key' : pending_user.key}))
+        activation_link = 'http://%s%s' % (
+                site.domain,
+                reverse('accounts-register_confirm',
+                        kwargs={'username': pending_user.username,
+                                'key': pending_user.key}))
 
-        msg = render_to_string('accounts/registration_email.txt',
-                {
-                    'site_name' : site.name,
-                    'site_domain' : site.domain,
-                    'user_email' : pending_user.email,
-                    'activation_link' : activation_link,
-                    'username' : pending_user.username,
-                    'admin_email' : admin_email,
+        msg = render_to_string('accounts/registration_email.txt', {
+                'site_name' : site.name,
+                'site_domain' : site.domain,
+                'user_email' : pending_user.email,
+                'activation_link' : activation_link,
+                'username' : pending_user.username,
+                'admin_email' : admin_email,
                 })
 
         subject = 'Registration Confirmation for ' + site.name
-        send_mail(subject, msg, admin_email, [self.cleaned_data['email']])
+        send_mail(subject, msg, admin_email, [email])
         logger.info('Accounts/registration conf. email sent to %s for user %s; IP = %s',
-                self.cleaned_data['email'], pending_user.username, self.ip)
-
-        return pending_user
+                    email, pending_user.username, self.ip)
+        del self.session['reg_info']
 
     def _validation_error(self, msg, param=None):
         logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param)