gremmie@1: """forms for the accounts application""" gremmie@1: bgneal@74: import logging 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: 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@782: SPAM_CHOICES = [(1, 'cat'), (2, '328'), (4, 'green'), (8, 'ocean')] bgneal@782: question4 = forms.ChoiceField(label="Pick the number", choices=SPAM_CHOICES) bgneal@782: question5 = forms.IntegerField(label="What number did you pick, above?", bgneal@782: widget=forms.TextInput(attrs={'class': 'text'})) bgneal@782: question6 = forms.ChoiceField(label="Pick the color", choices=SPAM_CHOICES) bgneal@782: bgneal@782: USER_QUALITIES = [ bgneal@782: (1, "I am the lowest form of life, a spammer"), bgneal@782: (2, "I'm a fan of surf music or I want to learn more"), bgneal@782: (3, "Someone is paying me to post links to this site"), bgneal@782: (4, "I am not going to spam this site"), bgneal@782: (5, "I understand my account may be removed if I post spam"), bgneal@782: (6, "I am going to spam the crap out of this site"), bgneal@782: (7, "Surf music is one of my interests, not spam"), bgneal@782: ] bgneal@782: question7 = forms.MultipleChoiceField( bgneal@782: label="Check all that apply", bgneal@782: choices=USER_QUALITIES, bgneal@782: required=False, bgneal@782: widget=forms.CheckboxSelectMultiple) bgneal@782: gremmie@1: 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@782: def clean_question4(self): bgneal@782: answer = self.cleaned_data.get('question4') bgneal@782: if answer != u'2': bgneal@782: self._validation_error('Please pick the number', answer) bgneal@782: return answer bgneal@782: bgneal@782: def clean_question5(self): bgneal@782: answer = self.cleaned_data.get('question5') bgneal@782: if answer != 328: bgneal@782: self._validation_error('Please enter the correct number', answer) bgneal@782: return answer bgneal@782: bgneal@782: def clean_question6(self): bgneal@782: answer = self.cleaned_data.get('question6') bgneal@782: if answer != u'4': bgneal@782: self._validation_error('Please pick the color', answer) bgneal@782: return answer bgneal@782: bgneal@782: def clean_question7(self): bgneal@782: answer = self.cleaned_data.get('question7') bgneal@782: answer.sort() bgneal@782: if answer != [u'2', u'4', u'5', u'7']: bgneal@794: self._validation_error("Please double-check your checkboxes", answer) bgneal@782: return answer bgneal@782: bgneal@74: def save(self): bgneal@74: pending_user = PendingUser.objects.create_pending_user(self.cleaned_data['username'], bgneal@74: self.cleaned_data['email'], bgneal@74: self.cleaned_data['password1']) gremmie@1: bgneal@74: # Send the confirmation email gremmie@1: bgneal@74: site = Site.objects.get_current() bgneal@74: admin_email = settings.ADMINS[0][1] gremmie@1: bgneal@316: activation_link = 'http://%s%s' % (site.domain, reverse('accounts.views.register_confirm', bgneal@74: kwargs = {'username' : pending_user.username, 'key' : pending_user.key})) gremmie@1: bgneal@74: msg = render_to_string('accounts/registration_email.txt', bgneal@74: { bgneal@74: 'site_name' : site.name, bgneal@74: 'site_domain' : site.domain, bgneal@74: 'user_email' : pending_user.email, bgneal@74: 'activation_link' : activation_link, bgneal@74: 'username' : pending_user.username, bgneal@74: 'admin_email' : admin_email, bgneal@74: }) gremmie@1: bgneal@74: subject = 'Registration Confirmation for ' + site.name bgneal@659: send_mail(subject, msg, admin_email, [self.cleaned_data['email']], bgneal@659: defer=False) bgneal@690: logger.info('Accounts/registration conf. email sent to %s for user %s; IP = %s', bgneal@316: self.cleaned_data['email'], pending_user.username, self.ip) gremmie@1: bgneal@74: return pending_user 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@659: admin_email = settings.ADMINS[0][1] 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@659: send_mail(subject, msg, admin_email, [email], defer=False) bgneal@659: bgneal@690: logger.info('Forgotten username email sent to {} <{}>'.format( bgneal@659: user.username, email))