-
Notifications
You must be signed in to change notification settings - Fork 81
/
Copy pathforms.py
363 lines (289 loc) · 12.4 KB
/
forms.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
from django import forms
from django.conf import settings
from django.utils.translation import ugettext as _
from django.contrib.auth.password_validation import validate_password
from allauth.account.utils import send_email_confirmation
from allauth.account import forms as allauth_forms
from allauth.account.models import EmailAddress
from core.form_mixins import SanitizeFieldsForm
from . import utils
from .models import User, VerificationDevice
from .validators import check_username_case_insensitive, phone_validator
from . import messages
from parsley.decorators import parsleyfy
from phonenumbers import parse as parse_phone
from phonenumbers import NumberParseException
@parsleyfy
class RegisterForm(SanitizeFieldsForm, forms.ModelForm):
email = forms.EmailField(required=False)
phone = forms.RegexField(regex=r'^\+[0-9]{5,14}$',
error_messages={'invalid': messages.phone_format},
required=False)
password = forms.CharField(widget=forms.PasswordInput())
MIN_LENGTH = 10
class Meta:
model = User
fields = ['username', 'email', 'phone', 'password',
'full_name', 'language']
class Media:
js = ('js/sanitize.js', )
def clean(self):
super(RegisterForm, self).clean()
email = self.data.get('email')
phone = self.data.get('phone')
if (not phone) and (not email):
raise forms.ValidationError(
_("You cannot leave both phone and email empty."
" Signup with either phone or email or both."))
def clean_username(self):
username = self.data.get('username')
check_username_case_insensitive(username)
if username.lower() in settings.CADASTA_INVALID_ENTITY_NAMES:
raise forms.ValidationError(
_("Username cannot be “add” or “new”."))
return username
def clean_password(self):
password = self.data.get('password')
validate_password(password)
errors = []
email = self.data.get('email')
if email:
email = email.split('@')
if email[0].casefold() in password.casefold():
errors.append(_("Passwords cannot contain your email."))
username = self.data.get('username')
if len(username) and username.casefold() in password.casefold():
errors.append(
_("The password is too similar to the username."))
phone = self.data.get('phone')
if phone and phone_validator(phone):
try:
phone = str(parse_phone(phone).national_number)
if phone in password:
errors.append(_("Passwords cannot contain your phone."))
except NumberParseException:
pass
if errors:
raise forms.ValidationError(errors)
return password
def clean_email(self):
email = self.data.get('email')
if email:
email = email.casefold()
if EmailAddress.objects.filter(email=email).exists():
raise forms.ValidationError(
_("User with this Email address already exists."))
else:
email = None
return email
def clean_phone(self):
phone = self.data.get('phone')
if phone:
if VerificationDevice.objects.filter(
unverified_phone=phone).exists():
raise forms.ValidationError(
_("User with this Phone number already exists."))
try:
parse_phone(phone)
except NumberParseException:
raise forms.ValidationError(
_("Please enter a valid country code."))
else:
phone = None
return phone
def save(self, *args, **kwargs):
user = super().save(*args, **kwargs)
user.set_password(self.cleaned_data['password'])
user.save()
return user
class ProfileForm(SanitizeFieldsForm, forms.ModelForm):
email = forms.EmailField(required=False)
phone = forms.RegexField(regex=r'^\+[0-9]{5,14}$',
error_messages={'invalid': messages.phone_format},
required=False)
password = forms.CharField(widget=forms.PasswordInput())
class Meta:
model = User
fields = ['username', 'email', 'phone', 'full_name', 'language',
'measurement', 'avatar']
class Media:
js = ('js/image-upload.js', 'js/sanitize.js', )
def __init__(self, *args, **kwargs):
self._send_confirmation = False
self.request = kwargs.pop('request', None)
super().__init__(*args, **kwargs)
self.current_email = self.instance.email
if self.current_email:
self.fields['email'].required = True
self.current_phone = self.instance.phone
if self.current_phone:
self.fields['phone'].required = True
def clean(self):
if not self.instance.update_profile:
raise forms.ValidationError(
_("The profile for this user can not be updated."))
return super().clean()
def clean_username(self):
username = self.data.get('username')
if self.instance.username.casefold() != username.casefold():
check_username_case_insensitive(username)
if username.lower() in settings.CADASTA_INVALID_ENTITY_NAMES:
raise forms.ValidationError(
_("Username cannot be “add” or “new”."))
return username
def clean_password(self):
if (self.fields['password'].required and
not self.instance.check_password(self.data.get('password'))):
raise forms.ValidationError(
_("Please provide the correct password for your account."))
def clean_phone(self):
phone = self.data.get('phone')
if phone:
if (phone != self.current_phone and
VerificationDevice.objects.filter(unverified_phone=phone
).exists()):
raise forms.ValidationError(
_("User with this Phone number already exists."))
try:
parse_phone(phone)
except NumberParseException:
raise forms.ValidationError(
_("Please enter a valid country code."))
else:
phone = None
return phone
def clean_email(self):
email = self.data.get('email')
if email:
email = email.casefold()
if (email != self.current_email and
EmailAddress.objects.filter(email=email).exists()):
raise forms.ValidationError(
_("User with this Email address already exists."))
else:
email = None
return email
def save(self, *args, **kwargs):
user = super().save(commit=False, *args, **kwargs)
# email_update_message = None
if self.current_email != user.email:
self.instance.emailaddress_set.all().delete()
send_email_confirmation(self.request, user)
utils.send_email_update_notification(self.current_email)
user.email = self.current_email
if self.current_phone != user.phone:
self.instance.verificationdevice_set.all().delete()
device = VerificationDevice.objects.create(
user=self.instance, unverified_phone=user.phone)
device.generate_challenge()
utils.send_sms(self.current_phone, messages.phone_change)
user.phone = self.current_phone
user.save()
return user
class ChangePasswordMixin:
def clean_password(self):
if not self.user or not self.user.update_profile:
raise forms.ValidationError(_("The password for this user can not "
"be changed."))
password = self.cleaned_data['password']
validate_password(password, user=self.user)
username = self.user.username
if len(username) and username.casefold() in password.casefold():
raise forms.ValidationError(
_("The password is too similar to the username."))
phone = self.user.phone
if phone and phone_validator(phone):
phone = str(parse_phone(phone).national_number)
if phone in password:
raise forms.ValidationError(
_("Passwords cannot contain your phone."))
return password
def save(self):
allauth_forms.get_adapter().set_password(
self.user, self.cleaned_data['password'])
class ChangePasswordForm(ChangePasswordMixin,
allauth_forms.UserForm):
oldpassword = allauth_forms.PasswordField(label=_("Current Password"))
password = allauth_forms.SetPasswordField(label=_("New Password"))
def clean_oldpassword(self):
if not self.user.check_password(self.cleaned_data.get('oldpassword')):
raise forms.ValidationError(_("Please type your current"
" password."))
return self.cleaned_data['oldpassword']
class ResetPasswordKeyForm(ChangePasswordMixin,
forms.Form):
password = allauth_forms.SetPasswordField(label=_("New Password"))
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
self.temp_key = kwargs.pop('temp_key', None)
super().__init__(*args, **kwargs)
class ResetPasswordForm(allauth_forms.ResetPasswordForm):
email = forms.EmailField(required=False)
phone = forms.RegexField(regex=r'^\+[0-9]{5,14}$',
error_messages={'invalid': messages.phone_format},
required=False)
def clean(self):
if not self.data.get('email') and not self.data.get('phone'):
raise forms.ValidationError(_(
"You cannot leave both phone and email empty."))
def clean_email(self):
email = self.cleaned_data.get('email')
if email:
email = email.casefold()
self.users = User.objects.filter(email=email)
else:
email = None
return email
def clean_phone(self):
phone = self.data.get('phone')
if not phone:
phone = None
return phone
def save(self, request, **kwargs):
phone = self.data.get('phone')
if phone:
request.session['phone'] = phone
try:
user = User.objects.get(phone=phone)
device = user.verificationdevice_set.get_or_create(
unverified_phone=phone, label='password_reset')
device[0].generate_challenge()
except User.DoesNotExist:
pass
return phone
else:
super().save(request, **kwargs)
class TokenVerificationForm(forms.Form):
token = forms.CharField(label=_("Token"), max_length=settings.TOTP_DIGITS)
def __init__(self, *args, **kwargs):
self.device = kwargs.pop('device', None)
super().__init__(*args, **kwargs)
def clean_token(self):
token = self.data.get('token')
device = self.device
if not device:
raise forms.ValidationError(
_("The token could not be verified."
" Please click on 'here' to try again."))
try:
token = int(token)
except ValueError:
raise forms.ValidationError(_("Token must be a number."))
if device.verify_token(token):
return token
elif device.verify_token(token=token, tolerance=5):
raise forms.ValidationError(
_("The token has expired."
" Please click on 'here' to receive the new token."))
else:
raise forms.ValidationError(
"Invalid Token. Enter a valid token.")
class ResendTokenForm(forms.Form):
email = forms.EmailField(required=False)
phone = forms.RegexField(regex=r'^\+[0-9]{5,14}$',
error_messages={'invalid': messages.phone_format},
required=False)
def clean(self):
if not self.data.get('email') and not self.data.get('phone'):
raise forms.ValidationError(
_("You cannot leave both phone and email empty."))