gremmie@1: """forms for the accounts application"""
gremmie@1: 
bgneal@74: import logging
bgneal@905: import random
bgneal@74: 
gremmie@1: from django import forms
gremmie@1: from django.contrib.auth.models import User
gremmie@1: from django.core.urlresolvers import reverse
gremmie@1: from django.template.loader import render_to_string
gremmie@1: from django.contrib.sites.models import Site
bgneal@6: from django.conf import settings
gremmie@1: 
gremmie@1: from core.functions import send_mail
gremmie@1: from accounts.models import PendingUser
gremmie@1: from accounts.models import IllegalUsername
gremmie@1: from accounts.models import IllegalEmail
bgneal@690: 
bgneal@690: 
bgneal@690: logger = logging.getLogger('auth')
gremmie@1: 
gremmie@1: 
bgneal@905: CODE_WORD_CHOICES = [
bgneal@905:     'reverb',
bgneal@905:     'sg101',
bgneal@905:     'guitar',
bgneal@905:     'showman',
bgneal@905:     'surf',
bgneal@905:     'surfer',
bgneal@905:     'tank',
bgneal@905:     'splash',
bgneal@905:     'gremmie',
bgneal@905:     'hodad',
bgneal@905:     'stomp',
bgneal@905:     'belairs',
bgneal@905:     'dickdale',
bgneal@905:     'chantays',
bgneal@905:     'astronauts',
bgneal@905:     'fender',
bgneal@905: ]
bgneal@905: 
bgneal@905: def generate_registration_code():
bgneal@905:     """Generates a simple, random registration code for the user to enter."""
bgneal@905:     return '{}-{}'.format(random.choice(CODE_WORD_CHOICES), random.randint(100, 999))
bgneal@905: 
bgneal@905: 
gremmie@1: class RegisterForm(forms.Form):
bgneal@74:     """Form used to register with the website"""
bgneal@498:     username = forms.RegexField(
bgneal@498:             max_length=30,
bgneal@498:             regex=r'^\w+$',
bgneal@498:             error_messages={'invalid': ('Your username must be 30 characters or'
bgneal@498:                 ' less and contain only letters, numbers and underscores.')},
bgneal@498:             widget=forms.TextInput(attrs={'class': 'text'}),
bgneal@498:             )
bgneal@498:     email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@498:     password1 = forms.CharField(label="Password",
bgneal@498:             widget=forms.PasswordInput(attrs={'class': 'text'}))
bgneal@498:     password2 = forms.CharField(label="Password confirmation",
bgneal@498:             widget=forms.PasswordInput(attrs={'class': 'text'}))
bgneal@316:     agree_age = forms.BooleanField(required=True,
bgneal@155:         label='I certify that I am over the age of 13',
bgneal@155:         error_messages={
bgneal@347:             'required': 'Sorry, but you must be over the age of 13 to '
bgneal@155:                     'register at our site.',
bgneal@155:             })
bgneal@316:     agree_tos = forms.BooleanField(required=True,
bgneal@155:        label='I agree to the Terms of Service',
bgneal@155:         error_messages={
bgneal@155:             'required': 'You have not agreed to our Terms of Service.',
bgneal@155:             })
bgneal@155:     agree_privacy = forms.BooleanField(required=True,
bgneal@155:         label='I agree to the Privacy Policy',
bgneal@155:         error_messages={
bgneal@155:             'required': 'You have not agreed to our Privacy Policy.',
bgneal@155:             })
bgneal@498:     question1 = forms.CharField(label="What number appears in the site name?",
bgneal@498:         widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@347:     question2 = forms.CharField(label='', required=False,
bgneal@347:         widget=forms.TextInput(attrs={'style': 'display: none;'}))
bgneal@782:     question3 = forms.CharField(label="Don't put anything in this box",
bgneal@782:         required=False,
bgneal@782:         widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@782: 
bgneal@74:     def __init__(self, *args, **kwargs):
bgneal@74:         self.ip = kwargs.pop('ip', '?')
bgneal@74:         super(RegisterForm, self).__init__(*args, **kwargs)
bgneal@74: 
bgneal@74:     def clean_username(self):
bgneal@74:         username = self.cleaned_data['username']
bgneal@74:         try:
bgneal@565:             User.objects.get(username=username)
bgneal@74:         except User.DoesNotExist:
gremmie@1:             try:
bgneal@565:                 PendingUser.objects.get(username=username)
bgneal@74:             except PendingUser.DoesNotExist:
bgneal@74:                 try:
bgneal@565:                     IllegalUsername.objects.get(username=username)
bgneal@74:                 except IllegalUsername.DoesNotExist:
bgneal@74:                     return username
bgneal@74:                 self._validation_error("That username is not allowed.", username)
bgneal@74:             self._validation_error("A pending user with that username already exists.", username)
bgneal@74:         self._validation_error("A user with that username already exists.", username)
gremmie@1: 
bgneal@74:     def clean_email(self):
bgneal@74:         email = self.cleaned_data['email']
bgneal@565: 
bgneal@565:         if User.objects.filter(email=email).count():
bgneal@565:             self._validation_error("A user with that email address already exists.", email)
bgneal@565:         elif PendingUser.objects.filter(email=email).count():
bgneal@74:             self._validation_error("A pending user with that email address already exists.", email)
bgneal@565:         elif IllegalEmail.objects.filter(email=email).count():
bgneal@565:             self._validation_error("That email address is not allowed.", email)
bgneal@659: 
bgneal@565:         # email is ok
bgneal@565:         return email
gremmie@1: 
bgneal@74:     def clean_password2(self):
bgneal@74:         password1 = self.cleaned_data.get("password1", "")
bgneal@74:         password2 = self.cleaned_data["password2"]
bgneal@74:         if password1 != password2:
bgneal@74:             self._validation_error("The two password fields didn't match.")
bgneal@155:         if len(password1) < 6:
bgneal@155:             self._validation_error("Please choose a password of 6 characters or more.")
bgneal@74:         return password2
gremmie@1: 
bgneal@346:     def clean_question1(self):
bgneal@346:         answer = self.cleaned_data.get('question1')
bgneal@346:         success = False
bgneal@346:         if answer:
bgneal@346:             try:
bgneal@346:                 val = int(answer)
bgneal@346:             except ValueError:
bgneal@346:                 pass
bgneal@346:             else:
bgneal@346:                 success = val == 101
bgneal@346:         if not success:
bgneal@346:             self._validation_error("Incorrect answer to our anti-spam question.", answer)
bgneal@346:         return answer
bgneal@346: 
bgneal@347:     def clean_question2(self):
bgneal@347:         """
bgneal@347:         Honeypot field should be empty.
bgneal@347:         """
bgneal@347:         answer = self.cleaned_data.get('question2')
bgneal@347:         if answer:
bgneal@690:             logger.critical('Accounts/registration: Honeypot filled [%s]', self.ip)
bgneal@690:             self._validation_error('Wrong answer #2', answer)
bgneal@347:         return answer
bgneal@347: 
bgneal@782:     def clean_question3(self):
bgneal@782:         answer = self.cleaned_data.get('question3')
bgneal@782:         if answer:
bgneal@782:             self._validation_error('Oops, that should be blank', answer)
bgneal@782:         return answer
bgneal@782: 
bgneal@905:     def save(self, request):
bgneal@905:         request.session['reg_info'] = {
bgneal@905:             'username': self.cleaned_data['username'],
bgneal@905:             'email': self.cleaned_data['email'],
bgneal@905:             'password': self.cleaned_data['password1'],
bgneal@905:             'code': generate_registration_code(),
bgneal@905:         }
bgneal@782: 
bgneal@905:     def _validation_error(self, msg, param=None):
bgneal@905:         logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param)
bgneal@905:         raise forms.ValidationError(msg)
bgneal@782: 
bgneal@782: 
bgneal@905: class RegisterCodeForm(forms.Form):
bgneal@905:     """Form for processing anti-spam code."""
bgneal@905:     code = forms.CharField(
bgneal@905:             label="Registration code",
bgneal@905:             widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@905: 
bgneal@905:     def __init__(self, *args, **kwargs):
bgneal@905:         self.session = kwargs.pop('session', None)
bgneal@905:         self.ip = kwargs.pop('ip', '?')
bgneal@905:         super(RegisterCodeForm, self).__init__(*args, **kwargs)
bgneal@905: 
bgneal@905:     def clean_code(self):
bgneal@905:         reg_info = self.session.get('reg_info')
bgneal@905:         saved_code = reg_info.get('code') if reg_info else None
bgneal@905:         if not saved_code:
bgneal@905:             self._validation_error("Registration code error")
bgneal@905:         user_code = self.cleaned_data['code']
bgneal@905:         if user_code:
bgneal@905:             user_code = user_code.strip()
bgneal@905: 
bgneal@905:         if user_code != saved_code:
bgneal@905:             self._validation_error("The registration code does not match")
bgneal@782: 
bgneal@74:     def save(self):
bgneal@905:         """Process successful registration."""
bgneal@905:         reg_info = self.session['reg_info']
bgneal@905:         username = reg_info['username']
bgneal@905:         email = reg_info['email']
bgneal@905:         password = reg_info['password']
bgneal@905: 
bgneal@905:         pending_user = PendingUser.objects.create_pending_user(
bgneal@905:                 username,
bgneal@905:                 email,
bgneal@905:                 password)
gremmie@1: 
bgneal@74:         # Send the confirmation email
bgneal@74:         site = Site.objects.get_current()
bgneal@892:         admin_email = settings.DEFAULT_FROM_EMAIL
gremmie@1: 
bgneal@991:         activation_link = '%s://%s%s' % (
bgneal@991:                 settings.SITE_SCHEME,
bgneal@905:                 site.domain,
bgneal@905:                 reverse('accounts-register_confirm',
bgneal@905:                         kwargs={'username': pending_user.username,
bgneal@905:                                 'key': pending_user.key}))
gremmie@1: 
bgneal@905:         msg = render_to_string('accounts/registration_email.txt', {
bgneal@905:                 'site_name' : site.name,
bgneal@905:                 'site_domain' : site.domain,
bgneal@905:                 'user_email' : pending_user.email,
bgneal@905:                 'activation_link' : activation_link,
bgneal@905:                 'username' : pending_user.username,
bgneal@905:                 'admin_email' : admin_email,
bgneal@74:                 })
gremmie@1: 
bgneal@74:         subject = 'Registration Confirmation for ' + site.name
bgneal@905:         send_mail(subject, msg, admin_email, [email])
bgneal@690:         logger.info('Accounts/registration conf. email sent to %s for user %s; IP = %s',
bgneal@905:                     email, pending_user.username, self.ip)
bgneal@905:         del self.session['reg_info']
gremmie@1: 
bgneal@74:     def _validation_error(self, msg, param=None):
bgneal@690:         logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param)
bgneal@74:         raise forms.ValidationError(msg)
bgneal@659: 
bgneal@659: 
bgneal@659: class ForgotUsernameForm(forms.Form):
bgneal@659:     """Form used to recover lost username"""
bgneal@659:     email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@659: 
bgneal@659:     def save(self):
bgneal@659:         """Email the username associated with the email address to the user."""
bgneal@659:         email = self.cleaned_data['email']
bgneal@659:         try:
bgneal@659:             user = User.objects.get(email=email)
bgneal@659:         except User.DoesNotExist:
bgneal@659:             # nothing to do; we don't tell the user as this gives away info
bgneal@659:             # about our database
bgneal@659:             return
bgneal@659: 
bgneal@659:         site = Site.objects.get_current()
bgneal@892:         admin_email = settings.DEFAULT_FROM_EMAIL
bgneal@659: 
bgneal@659:         subject = 'Forgotten username for %s' % site.name
bgneal@659:         msg = render_to_string('accounts/forgot_user_email.txt', {
bgneal@659:             'user': user,
bgneal@659:             'site': site,
bgneal@659:             'admin_email': admin_email,
bgneal@659:             })
bgneal@892:         send_mail(subject, msg, admin_email, [email])
bgneal@659: 
bgneal@690:         logger.info('Forgotten username email sent to {} <{}>'.format(
bgneal@659:             user.username, email))