gremmie@1
|
1 """forms for the accounts application"""
|
gremmie@1
|
2
|
bgneal@74
|
3 import logging
|
bgneal@74
|
4
|
gremmie@1
|
5 from django import forms
|
gremmie@1
|
6 from django.contrib.auth.models import User
|
gremmie@1
|
7 from django.core.urlresolvers import reverse
|
gremmie@1
|
8 from django.template.loader import render_to_string
|
gremmie@1
|
9 from django.contrib.sites.models import Site
|
bgneal@6
|
10 from django.conf import settings
|
gremmie@1
|
11
|
gremmie@1
|
12 from core.functions import send_mail
|
gremmie@1
|
13 from accounts.models import PendingUser
|
gremmie@1
|
14 from accounts.models import IllegalUsername
|
gremmie@1
|
15 from accounts.models import IllegalEmail
|
bgneal@690
|
16
|
bgneal@690
|
17
|
bgneal@690
|
18 logger = logging.getLogger('auth')
|
gremmie@1
|
19
|
gremmie@1
|
20
|
gremmie@1
|
21 class RegisterForm(forms.Form):
|
bgneal@74
|
22 """Form used to register with the website"""
|
bgneal@498
|
23 username = forms.RegexField(
|
bgneal@498
|
24 max_length=30,
|
bgneal@498
|
25 regex=r'^\w+$',
|
bgneal@498
|
26 error_messages={'invalid': ('Your username must be 30 characters or'
|
bgneal@498
|
27 ' less and contain only letters, numbers and underscores.')},
|
bgneal@498
|
28 widget=forms.TextInput(attrs={'class': 'text'}),
|
bgneal@498
|
29 )
|
bgneal@498
|
30 email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'text'}))
|
bgneal@498
|
31 password1 = forms.CharField(label="Password",
|
bgneal@498
|
32 widget=forms.PasswordInput(attrs={'class': 'text'}))
|
bgneal@498
|
33 password2 = forms.CharField(label="Password confirmation",
|
bgneal@498
|
34 widget=forms.PasswordInput(attrs={'class': 'text'}))
|
bgneal@316
|
35 agree_age = forms.BooleanField(required=True,
|
bgneal@155
|
36 label='I certify that I am over the age of 13',
|
bgneal@155
|
37 error_messages={
|
bgneal@347
|
38 'required': 'Sorry, but you must be over the age of 13 to '
|
bgneal@155
|
39 'register at our site.',
|
bgneal@155
|
40 })
|
bgneal@316
|
41 agree_tos = forms.BooleanField(required=True,
|
bgneal@155
|
42 label='I agree to the Terms of Service',
|
bgneal@155
|
43 error_messages={
|
bgneal@155
|
44 'required': 'You have not agreed to our Terms of Service.',
|
bgneal@155
|
45 })
|
bgneal@155
|
46 agree_privacy = forms.BooleanField(required=True,
|
bgneal@155
|
47 label='I agree to the Privacy Policy',
|
bgneal@155
|
48 error_messages={
|
bgneal@155
|
49 'required': 'You have not agreed to our Privacy Policy.',
|
bgneal@155
|
50 })
|
bgneal@498
|
51 question1 = forms.CharField(label="What number appears in the site name?",
|
bgneal@498
|
52 widget=forms.TextInput(attrs={'class': 'text'}))
|
bgneal@347
|
53 question2 = forms.CharField(label='', required=False,
|
bgneal@347
|
54 widget=forms.TextInput(attrs={'style': 'display: none;'}))
|
bgneal@782
|
55 question3 = forms.CharField(label="Don't put anything in this box",
|
bgneal@782
|
56 required=False,
|
bgneal@782
|
57 widget=forms.TextInput(attrs={'class': 'text'}))
|
bgneal@782
|
58
|
bgneal@782
|
59 SPAM_CHOICES = [(1, 'cat'), (2, '328'), (4, 'green'), (8, 'ocean')]
|
bgneal@782
|
60 question4 = forms.ChoiceField(label="Pick the number", choices=SPAM_CHOICES)
|
bgneal@782
|
61 question5 = forms.IntegerField(label="What number did you pick, above?",
|
bgneal@782
|
62 widget=forms.TextInput(attrs={'class': 'text'}))
|
bgneal@782
|
63 question6 = forms.ChoiceField(label="Pick the color", choices=SPAM_CHOICES)
|
bgneal@782
|
64
|
bgneal@782
|
65 USER_QUALITIES = [
|
bgneal@782
|
66 (1, "I am the lowest form of life, a spammer"),
|
bgneal@782
|
67 (2, "I'm a fan of surf music or I want to learn more"),
|
bgneal@782
|
68 (3, "Someone is paying me to post links to this site"),
|
bgneal@782
|
69 (4, "I am not going to spam this site"),
|
bgneal@782
|
70 (5, "I understand my account may be removed if I post spam"),
|
bgneal@782
|
71 (6, "I am going to spam the crap out of this site"),
|
bgneal@782
|
72 (7, "Surf music is one of my interests, not spam"),
|
bgneal@782
|
73 ]
|
bgneal@782
|
74 question7 = forms.MultipleChoiceField(
|
bgneal@782
|
75 label="Check all that apply",
|
bgneal@782
|
76 choices=USER_QUALITIES,
|
bgneal@782
|
77 required=False,
|
bgneal@782
|
78 widget=forms.CheckboxSelectMultiple)
|
bgneal@782
|
79
|
gremmie@1
|
80
|
bgneal@74
|
81 def __init__(self, *args, **kwargs):
|
bgneal@74
|
82 self.ip = kwargs.pop('ip', '?')
|
bgneal@74
|
83 super(RegisterForm, self).__init__(*args, **kwargs)
|
bgneal@74
|
84
|
bgneal@74
|
85 def clean_username(self):
|
bgneal@74
|
86 username = self.cleaned_data['username']
|
bgneal@74
|
87 try:
|
bgneal@565
|
88 User.objects.get(username=username)
|
bgneal@74
|
89 except User.DoesNotExist:
|
gremmie@1
|
90 try:
|
bgneal@565
|
91 PendingUser.objects.get(username=username)
|
bgneal@74
|
92 except PendingUser.DoesNotExist:
|
bgneal@74
|
93 try:
|
bgneal@565
|
94 IllegalUsername.objects.get(username=username)
|
bgneal@74
|
95 except IllegalUsername.DoesNotExist:
|
bgneal@74
|
96 return username
|
bgneal@74
|
97 self._validation_error("That username is not allowed.", username)
|
bgneal@74
|
98 self._validation_error("A pending user with that username already exists.", username)
|
bgneal@74
|
99 self._validation_error("A user with that username already exists.", username)
|
gremmie@1
|
100
|
bgneal@74
|
101 def clean_email(self):
|
bgneal@74
|
102 email = self.cleaned_data['email']
|
bgneal@565
|
103
|
bgneal@565
|
104 if User.objects.filter(email=email).count():
|
bgneal@565
|
105 self._validation_error("A user with that email address already exists.", email)
|
bgneal@565
|
106 elif PendingUser.objects.filter(email=email).count():
|
bgneal@74
|
107 self._validation_error("A pending user with that email address already exists.", email)
|
bgneal@565
|
108 elif IllegalEmail.objects.filter(email=email).count():
|
bgneal@565
|
109 self._validation_error("That email address is not allowed.", email)
|
bgneal@659
|
110
|
bgneal@565
|
111 # email is ok
|
bgneal@565
|
112 return email
|
gremmie@1
|
113
|
bgneal@74
|
114 def clean_password2(self):
|
bgneal@74
|
115 password1 = self.cleaned_data.get("password1", "")
|
bgneal@74
|
116 password2 = self.cleaned_data["password2"]
|
bgneal@74
|
117 if password1 != password2:
|
bgneal@74
|
118 self._validation_error("The two password fields didn't match.")
|
bgneal@155
|
119 if len(password1) < 6:
|
bgneal@155
|
120 self._validation_error("Please choose a password of 6 characters or more.")
|
bgneal@74
|
121 return password2
|
gremmie@1
|
122
|
bgneal@346
|
123 def clean_question1(self):
|
bgneal@346
|
124 answer = self.cleaned_data.get('question1')
|
bgneal@346
|
125 success = False
|
bgneal@346
|
126 if answer:
|
bgneal@346
|
127 try:
|
bgneal@346
|
128 val = int(answer)
|
bgneal@346
|
129 except ValueError:
|
bgneal@346
|
130 pass
|
bgneal@346
|
131 else:
|
bgneal@346
|
132 success = val == 101
|
bgneal@346
|
133 if not success:
|
bgneal@346
|
134 self._validation_error("Incorrect answer to our anti-spam question.", answer)
|
bgneal@346
|
135 return answer
|
bgneal@346
|
136
|
bgneal@347
|
137 def clean_question2(self):
|
bgneal@347
|
138 """
|
bgneal@347
|
139 Honeypot field should be empty.
|
bgneal@347
|
140 """
|
bgneal@347
|
141 answer = self.cleaned_data.get('question2')
|
bgneal@347
|
142 if answer:
|
bgneal@690
|
143 logger.critical('Accounts/registration: Honeypot filled [%s]', self.ip)
|
bgneal@690
|
144 self._validation_error('Wrong answer #2', answer)
|
bgneal@347
|
145 return answer
|
bgneal@347
|
146
|
bgneal@782
|
147 def clean_question3(self):
|
bgneal@782
|
148 answer = self.cleaned_data.get('question3')
|
bgneal@782
|
149 if answer:
|
bgneal@782
|
150 self._validation_error('Oops, that should be blank', answer)
|
bgneal@782
|
151 return answer
|
bgneal@782
|
152
|
bgneal@782
|
153 def clean_question4(self):
|
bgneal@782
|
154 answer = self.cleaned_data.get('question4')
|
bgneal@782
|
155 if answer != u'2':
|
bgneal@782
|
156 self._validation_error('Please pick the number', answer)
|
bgneal@782
|
157 return answer
|
bgneal@782
|
158
|
bgneal@782
|
159 def clean_question5(self):
|
bgneal@782
|
160 answer = self.cleaned_data.get('question5')
|
bgneal@782
|
161 if answer != 328:
|
bgneal@782
|
162 self._validation_error('Please enter the correct number', answer)
|
bgneal@782
|
163 return answer
|
bgneal@782
|
164
|
bgneal@782
|
165 def clean_question6(self):
|
bgneal@782
|
166 answer = self.cleaned_data.get('question6')
|
bgneal@782
|
167 if answer != u'4':
|
bgneal@782
|
168 self._validation_error('Please pick the color', answer)
|
bgneal@782
|
169 return answer
|
bgneal@782
|
170
|
bgneal@782
|
171 def clean_question7(self):
|
bgneal@782
|
172 answer = self.cleaned_data.get('question7')
|
bgneal@782
|
173 answer.sort()
|
bgneal@782
|
174 if answer != [u'2', u'4', u'5', u'7']:
|
bgneal@794
|
175 self._validation_error("Please double-check your checkboxes", answer)
|
bgneal@782
|
176 return answer
|
bgneal@782
|
177
|
bgneal@74
|
178 def save(self):
|
bgneal@74
|
179 pending_user = PendingUser.objects.create_pending_user(self.cleaned_data['username'],
|
bgneal@74
|
180 self.cleaned_data['email'],
|
bgneal@74
|
181 self.cleaned_data['password1'])
|
gremmie@1
|
182
|
bgneal@74
|
183 # Send the confirmation email
|
gremmie@1
|
184
|
bgneal@74
|
185 site = Site.objects.get_current()
|
bgneal@892
|
186 admin_email = settings.DEFAULT_FROM_EMAIL
|
gremmie@1
|
187
|
bgneal@316
|
188 activation_link = 'http://%s%s' % (site.domain, reverse('accounts.views.register_confirm',
|
bgneal@74
|
189 kwargs = {'username' : pending_user.username, 'key' : pending_user.key}))
|
gremmie@1
|
190
|
bgneal@74
|
191 msg = render_to_string('accounts/registration_email.txt',
|
bgneal@74
|
192 {
|
bgneal@74
|
193 'site_name' : site.name,
|
bgneal@74
|
194 'site_domain' : site.domain,
|
bgneal@74
|
195 'user_email' : pending_user.email,
|
bgneal@74
|
196 'activation_link' : activation_link,
|
bgneal@74
|
197 'username' : pending_user.username,
|
bgneal@74
|
198 'admin_email' : admin_email,
|
bgneal@74
|
199 })
|
gremmie@1
|
200
|
bgneal@74
|
201 subject = 'Registration Confirmation for ' + site.name
|
bgneal@892
|
202 send_mail(subject, msg, admin_email, [self.cleaned_data['email']])
|
bgneal@690
|
203 logger.info('Accounts/registration conf. email sent to %s for user %s; IP = %s',
|
bgneal@316
|
204 self.cleaned_data['email'], pending_user.username, self.ip)
|
gremmie@1
|
205
|
bgneal@74
|
206 return pending_user
|
gremmie@1
|
207
|
bgneal@74
|
208 def _validation_error(self, msg, param=None):
|
bgneal@690
|
209 logger.error('Accounts/registration [%s]: %s (%s)', self.ip, msg, param)
|
bgneal@74
|
210 raise forms.ValidationError(msg)
|
bgneal@659
|
211
|
bgneal@659
|
212
|
bgneal@659
|
213 class ForgotUsernameForm(forms.Form):
|
bgneal@659
|
214 """Form used to recover lost username"""
|
bgneal@659
|
215 email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'text'}))
|
bgneal@659
|
216
|
bgneal@659
|
217 def save(self):
|
bgneal@659
|
218 """Email the username associated with the email address to the user."""
|
bgneal@659
|
219 email = self.cleaned_data['email']
|
bgneal@659
|
220 try:
|
bgneal@659
|
221 user = User.objects.get(email=email)
|
bgneal@659
|
222 except User.DoesNotExist:
|
bgneal@659
|
223 # nothing to do; we don't tell the user as this gives away info
|
bgneal@659
|
224 # about our database
|
bgneal@659
|
225 return
|
bgneal@659
|
226
|
bgneal@659
|
227 site = Site.objects.get_current()
|
bgneal@892
|
228 admin_email = settings.DEFAULT_FROM_EMAIL
|
bgneal@659
|
229
|
bgneal@659
|
230 subject = 'Forgotten username for %s' % site.name
|
bgneal@659
|
231 msg = render_to_string('accounts/forgot_user_email.txt', {
|
bgneal@659
|
232 'user': user,
|
bgneal@659
|
233 'site': site,
|
bgneal@659
|
234 'admin_email': admin_email,
|
bgneal@659
|
235 })
|
bgneal@892
|
236 send_mail(subject, msg, admin_email, [email])
|
bgneal@659
|
237
|
bgneal@690
|
238 logger.info('Forgotten username email sent to {} <{}>'.format(
|
bgneal@659
|
239 user.username, email))
|