annotate accounts/forms.py @ 989:2908859c2fe4

Smilies now use relative links. This is for upcoming switch to SSL. Currently we do not need absolute URLs for smilies. If this changes we can add it later.
author Brian Neal <bgneal@gmail.com>
date Thu, 29 Oct 2015 20:54:34 -0500
parents be233ba7ca31
children 4aadaf3bc234
rev   line source
gremmie@1 1 """forms for the accounts application"""
gremmie@1 2
bgneal@74 3 import logging
bgneal@905 4 import random
bgneal@74 5
gremmie@1 6 from django import forms
gremmie@1 7 from django.contrib.auth.models import User
gremmie@1 8 from django.core.urlresolvers import reverse
gremmie@1 9 from django.template.loader import render_to_string
gremmie@1 10 from django.contrib.sites.models import Site
bgneal@6 11 from django.conf import settings
gremmie@1 12
gremmie@1 13 from core.functions import send_mail
gremmie@1 14 from accounts.models import PendingUser
gremmie@1 15 from accounts.models import IllegalUsername
gremmie@1 16 from accounts.models import IllegalEmail
bgneal@690 17
bgneal@690 18
bgneal@690 19 logger = logging.getLogger('auth')
gremmie@1 20
gremmie@1 21
bgneal@905 22 CODE_WORD_CHOICES = [
bgneal@905 23 'reverb',
bgneal@905 24 'sg101',
bgneal@905 25 'guitar',
bgneal@905 26 'showman',
bgneal@905 27 'surf',
bgneal@905 28 'surfer',
bgneal@905 29 'tank',
bgneal@905 30 'splash',
bgneal@905 31 'gremmie',
bgneal@905 32 'hodad',
bgneal@905 33 'stomp',
bgneal@905 34 'belairs',
bgneal@905 35 'dickdale',
bgneal@905 36 'chantays',
bgneal@905 37 'astronauts',
bgneal@905 38 'fender',
bgneal@905 39 ]
bgneal@905 40
bgneal@905 41 def generate_registration_code():
bgneal@905 42 """Generates a simple, random registration code for the user to enter."""
bgneal@905 43 return '{}-{}'.format(random.choice(CODE_WORD_CHOICES), random.randint(100, 999))
bgneal@905 44
bgneal@905 45
gremmie@1 46 class RegisterForm(forms.Form):
bgneal@74 47 """Form used to register with the website"""
bgneal@498 48 username = forms.RegexField(
bgneal@498 49 max_length=30,
bgneal@498 50 regex=r'^\w+$',
bgneal@498 51 error_messages={'invalid': ('Your username must be 30 characters or'
bgneal@498 52 ' less and contain only letters, numbers and underscores.')},
bgneal@498 53 widget=forms.TextInput(attrs={'class': 'text'}),
bgneal@498 54 )
bgneal@498 55 email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@498 56 password1 = forms.CharField(label="Password",
bgneal@498 57 widget=forms.PasswordInput(attrs={'class': 'text'}))
bgneal@498 58 password2 = forms.CharField(label="Password confirmation",
bgneal@498 59 widget=forms.PasswordInput(attrs={'class': 'text'}))
bgneal@316 60 agree_age = forms.BooleanField(required=True,
bgneal@155 61 label='I certify that I am over the age of 13',
bgneal@155 62 error_messages={
bgneal@347 63 'required': 'Sorry, but you must be over the age of 13 to '
bgneal@155 64 'register at our site.',
bgneal@155 65 })
bgneal@316 66 agree_tos = forms.BooleanField(required=True,
bgneal@155 67 label='I agree to the Terms of Service',
bgneal@155 68 error_messages={
bgneal@155 69 'required': 'You have not agreed to our Terms of Service.',
bgneal@155 70 })
bgneal@155 71 agree_privacy = forms.BooleanField(required=True,
bgneal@155 72 label='I agree to the Privacy Policy',
bgneal@155 73 error_messages={
bgneal@155 74 'required': 'You have not agreed to our Privacy Policy.',
bgneal@155 75 })
bgneal@498 76 question1 = forms.CharField(label="What number appears in the site name?",
bgneal@498 77 widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@347 78 question2 = forms.CharField(label='', required=False,
bgneal@347 79 widget=forms.TextInput(attrs={'style': 'display: none;'}))
bgneal@782 80 question3 = forms.CharField(label="Don't put anything in this box",
bgneal@782 81 required=False,
bgneal@782 82 widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@782 83
bgneal@74 84 def __init__(self, *args, **kwargs):
bgneal@74 85 self.ip = kwargs.pop('ip', '?')
bgneal@74 86 super(RegisterForm, self).__init__(*args, **kwargs)
bgneal@74 87
bgneal@74 88 def clean_username(self):
bgneal@74 89 username = self.cleaned_data['username']
bgneal@74 90 try:
bgneal@565 91 User.objects.get(username=username)
bgneal@74 92 except User.DoesNotExist:
gremmie@1 93 try:
bgneal@565 94 PendingUser.objects.get(username=username)
bgneal@74 95 except PendingUser.DoesNotExist:
bgneal@74 96 try:
bgneal@565 97 IllegalUsername.objects.get(username=username)
bgneal@74 98 except IllegalUsername.DoesNotExist:
bgneal@74 99 return username
bgneal@74 100 self._validation_error("That username is not allowed.", username)
bgneal@74 101 self._validation_error("A pending user with that username already exists.", username)
bgneal@74 102 self._validation_error("A user with that username already exists.", username)
gremmie@1 103
bgneal@74 104 def clean_email(self):
bgneal@74 105 email = self.cleaned_data['email']
bgneal@565 106
bgneal@565 107 if User.objects.filter(email=email).count():
bgneal@565 108 self._validation_error("A user with that email address already exists.", email)
bgneal@565 109 elif PendingUser.objects.filter(email=email).count():
bgneal@74 110 self._validation_error("A pending user with that email address already exists.", email)
bgneal@565 111 elif IllegalEmail.objects.filter(email=email).count():
bgneal@565 112 self._validation_error("That email address is not allowed.", email)
bgneal@659 113
bgneal@565 114 # email is ok
bgneal@565 115 return email
gremmie@1 116
bgneal@74 117 def clean_password2(self):
bgneal@74 118 password1 = self.cleaned_data.get("password1", "")
bgneal@74 119 password2 = self.cleaned_data["password2"]
bgneal@74 120 if password1 != password2:
bgneal@74 121 self._validation_error("The two password fields didn't match.")
bgneal@155 122 if len(password1) < 6:
bgneal@155 123 self._validation_error("Please choose a password of 6 characters or more.")
bgneal@74 124 return password2
gremmie@1 125
bgneal@346 126 def clean_question1(self):
bgneal@346 127 answer = self.cleaned_data.get('question1')
bgneal@346 128 success = False
bgneal@346 129 if answer:
bgneal@346 130 try:
bgneal@346 131 val = int(answer)
bgneal@346 132 except ValueError:
bgneal@346 133 pass
bgneal@346 134 else:
bgneal@346 135 success = val == 101
bgneal@346 136 if not success:
bgneal@346 137 self._validation_error("Incorrect answer to our anti-spam question.", answer)
bgneal@346 138 return answer
bgneal@346 139
bgneal@347 140 def clean_question2(self):
bgneal@347 141 """
bgneal@347 142 Honeypot field should be empty.
bgneal@347 143 """
bgneal@347 144 answer = self.cleaned_data.get('question2')
bgneal@347 145 if answer:
bgneal@690 146 logger.critical('Accounts/registration: Honeypot filled [%s]', self.ip)
bgneal@690 147 self._validation_error('Wrong answer #2', answer)
bgneal@347 148 return answer
bgneal@347 149
bgneal@782 150 def clean_question3(self):
bgneal@782 151 answer = self.cleaned_data.get('question3')
bgneal@782 152 if answer:
bgneal@782 153 self._validation_error('Oops, that should be blank', answer)
bgneal@782 154 return answer
bgneal@782 155
bgneal@905 156 def save(self, request):
bgneal@905 157 request.session['reg_info'] = {
bgneal@905 158 'username': self.cleaned_data['username'],
bgneal@905 159 'email': self.cleaned_data['email'],
bgneal@905 160 'password': self.cleaned_data['password1'],
bgneal@905 161 'code': generate_registration_code(),
bgneal@905 162 }
bgneal@782 163
bgneal@905 164 def _validation_error(self, msg, param=None):
bgneal@905 165 logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param)
bgneal@905 166 raise forms.ValidationError(msg)
bgneal@782 167
bgneal@782 168
bgneal@905 169 class RegisterCodeForm(forms.Form):
bgneal@905 170 """Form for processing anti-spam code."""
bgneal@905 171 code = forms.CharField(
bgneal@905 172 label="Registration code",
bgneal@905 173 widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@905 174
bgneal@905 175 def __init__(self, *args, **kwargs):
bgneal@905 176 self.session = kwargs.pop('session', None)
bgneal@905 177 self.ip = kwargs.pop('ip', '?')
bgneal@905 178 super(RegisterCodeForm, self).__init__(*args, **kwargs)
bgneal@905 179
bgneal@905 180 def clean_code(self):
bgneal@905 181 reg_info = self.session.get('reg_info')
bgneal@905 182 saved_code = reg_info.get('code') if reg_info else None
bgneal@905 183 if not saved_code:
bgneal@905 184 self._validation_error("Registration code error")
bgneal@905 185 user_code = self.cleaned_data['code']
bgneal@905 186 if user_code:
bgneal@905 187 user_code = user_code.strip()
bgneal@905 188
bgneal@905 189 if user_code != saved_code:
bgneal@905 190 self._validation_error("The registration code does not match")
bgneal@782 191
bgneal@74 192 def save(self):
bgneal@905 193 """Process successful registration."""
bgneal@905 194 reg_info = self.session['reg_info']
bgneal@905 195 username = reg_info['username']
bgneal@905 196 email = reg_info['email']
bgneal@905 197 password = reg_info['password']
bgneal@905 198
bgneal@905 199 pending_user = PendingUser.objects.create_pending_user(
bgneal@905 200 username,
bgneal@905 201 email,
bgneal@905 202 password)
gremmie@1 203
bgneal@74 204 # Send the confirmation email
bgneal@74 205 site = Site.objects.get_current()
bgneal@892 206 admin_email = settings.DEFAULT_FROM_EMAIL
gremmie@1 207
bgneal@905 208 activation_link = 'http://%s%s' % (
bgneal@905 209 site.domain,
bgneal@905 210 reverse('accounts-register_confirm',
bgneal@905 211 kwargs={'username': pending_user.username,
bgneal@905 212 'key': pending_user.key}))
gremmie@1 213
bgneal@905 214 msg = render_to_string('accounts/registration_email.txt', {
bgneal@905 215 'site_name' : site.name,
bgneal@905 216 'site_domain' : site.domain,
bgneal@905 217 'user_email' : pending_user.email,
bgneal@905 218 'activation_link' : activation_link,
bgneal@905 219 'username' : pending_user.username,
bgneal@905 220 'admin_email' : admin_email,
bgneal@74 221 })
gremmie@1 222
bgneal@74 223 subject = 'Registration Confirmation for ' + site.name
bgneal@905 224 send_mail(subject, msg, admin_email, [email])
bgneal@690 225 logger.info('Accounts/registration conf. email sent to %s for user %s; IP = %s',
bgneal@905 226 email, pending_user.username, self.ip)
bgneal@905 227 del self.session['reg_info']
gremmie@1 228
bgneal@74 229 def _validation_error(self, msg, param=None):
bgneal@690 230 logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param)
bgneal@74 231 raise forms.ValidationError(msg)
bgneal@659 232
bgneal@659 233
bgneal@659 234 class ForgotUsernameForm(forms.Form):
bgneal@659 235 """Form used to recover lost username"""
bgneal@659 236 email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'text'}))
bgneal@659 237
bgneal@659 238 def save(self):
bgneal@659 239 """Email the username associated with the email address to the user."""
bgneal@659 240 email = self.cleaned_data['email']
bgneal@659 241 try:
bgneal@659 242 user = User.objects.get(email=email)
bgneal@659 243 except User.DoesNotExist:
bgneal@659 244 # nothing to do; we don't tell the user as this gives away info
bgneal@659 245 # about our database
bgneal@659 246 return
bgneal@659 247
bgneal@659 248 site = Site.objects.get_current()
bgneal@892 249 admin_email = settings.DEFAULT_FROM_EMAIL
bgneal@659 250
bgneal@659 251 subject = 'Forgotten username for %s' % site.name
bgneal@659 252 msg = render_to_string('accounts/forgot_user_email.txt', {
bgneal@659 253 'user': user,
bgneal@659 254 'site': site,
bgneal@659 255 'admin_email': admin_email,
bgneal@659 256 })
bgneal@892 257 send_mail(subject, msg, admin_email, [email])
bgneal@659 258
bgneal@690 259 logger.info('Forgotten username email sent to {} <{}>'.format(
bgneal@659 260 user.username, email))