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