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))