1 # Copyright 2011-2012 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
11 # 2. Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials
14 # provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
33 from urlparse import urljoin
34 from datetime import datetime
36 from django import forms
37 from django.utils.translation import ugettext as _
38 from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordResetForm
39 from django.core.mail import send_mail
40 from django.contrib.auth.tokens import default_token_generator
41 from django.template import Context, loader
42 from django.utils.http import int_to_base36
43 from django.core.urlresolvers import reverse
44 from django.utils.functional import lazy
45 from django.utils.safestring import mark_safe
47 from astakos.im.models import AstakosUser, Invitation
48 from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL, RECAPTCHA_ENABLED
49 from astakos.im.widgets import DummyWidget, RecaptchaWidget, ApprovalTermsWidget
51 # since Django 1.4 use django.core.urlresolvers.reverse_lazy instead
52 from astakos.im.util import reverse_lazy, get_latest_terms
55 import recaptcha.client.captcha as captcha
57 logger = logging.getLogger(__name__)
59 class LocalUserCreationForm(UserCreationForm):
61 Extends the built in UserCreationForm in several ways:
63 * Adds email, first_name, last_name, recaptcha_challenge_field, recaptcha_response_field field.
64 * The username field isn't visible and it is assigned a generated id.
65 * User created is not active.
67 recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
68 recaptcha_response_field = forms.CharField(widget=RecaptchaWidget, label='')
72 fields = ("email", "first_name", "last_name", "has_signed_terms")
73 widgets = {"has_signed_terms":ApprovalTermsWidget(terms_uri=reverse_lazy('latest_terms'))}
75 def __init__(self, *args, **kwargs):
77 Changes the order of fields, and removes the username field.
80 self.ip = kwargs['ip']
82 super(LocalUserCreationForm, self).__init__(*args, **kwargs)
83 self.fields.keyOrder = ['email', 'first_name', 'last_name',
84 'password1', 'password2']
85 if get_latest_terms():
86 self.fields.keyOrder.append('has_signed_terms')
88 self.fields.keyOrder.extend(['recaptcha_challenge_field',
89 'recaptcha_response_field',])
91 if 'has_signed_terms' in self.fields:
92 # Overriding field label since we need to apply a link
93 # to the terms within the label
94 terms_link_html = '<a href="%s" target="_blank">%s</a>' \
95 % (reverse('latest_terms'), _("the terms"))
96 self.fields['has_signed_terms'].label = \
97 mark_safe("I agree with %s" % terms_link_html)
99 def clean_email(self):
100 email = self.cleaned_data['email']
102 raise forms.ValidationError(_("This field is required"))
104 AstakosUser.objects.get(email = email)
105 raise forms.ValidationError(_("This email is already used"))
106 except AstakosUser.DoesNotExist:
109 def clean_has_signed_terms(self):
110 has_signed_terms = self.cleaned_data['has_signed_terms']
111 if not has_signed_terms:
112 raise forms.ValidationError(_('You have to agree with the terms'))
113 return has_signed_terms
115 def clean_recaptcha_response_field(self):
116 if 'recaptcha_challenge_field' in self.cleaned_data:
117 self.validate_captcha()
118 return self.cleaned_data['recaptcha_response_field']
120 def clean_recaptcha_challenge_field(self):
121 if 'recaptcha_response_field' in self.cleaned_data:
122 self.validate_captcha()
123 return self.cleaned_data['recaptcha_challenge_field']
125 def validate_captcha(self):
126 rcf = self.cleaned_data['recaptcha_challenge_field']
127 rrf = self.cleaned_data['recaptcha_response_field']
128 check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
129 if not check.is_valid:
130 raise forms.ValidationError(_('You have not entered the correct words'))
132 def save(self, commit=True):
134 Saves the email, first_name and last_name properties, after the normal
135 save behavior is complete.
137 user = super(LocalUserCreationForm, self).save(commit=False)
141 logger.info('Created user %s', user)
144 class InvitedLocalUserCreationForm(LocalUserCreationForm):
146 Extends the LocalUserCreationForm: adds an inviter readonly field.
149 inviter = forms.CharField(widget=forms.TextInput(), label=_('Inviter Real Name'))
153 fields = ("email", "first_name", "last_name", "has_signed_terms")
154 widgets = {"has_signed_terms":ApprovalTermsWidget(terms_uri=reverse_lazy('latest_terms'))}
156 def __init__(self, *args, **kwargs):
158 Changes the order of fields, and removes the username field.
160 super(InvitedLocalUserCreationForm, self).__init__(*args, **kwargs)
162 #set readonly form fields
163 self.fields['inviter'].widget.attrs['readonly'] = True
164 self.fields['email'].widget.attrs['readonly'] = True
165 self.fields['username'].widget.attrs['readonly'] = True
167 def save(self, commit=True):
168 user = super(InvitedLocalUserCreationForm, self).save(commit=False)
169 level = user.invitation.inviter.level + 1
171 user.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
172 user.email_verified = True
177 class ThirdPartyUserCreationForm(forms.ModelForm):
180 fields = ("email", "first_name", "last_name", "third_party_identifier",
181 "has_signed_terms", "provider")
182 widgets = {"has_signed_terms":ApprovalTermsWidget(terms_uri=reverse_lazy('latest_terms'))}
184 def __init__(self, *args, **kwargs):
186 Changes the order of fields, and removes the username field.
190 super(ThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
191 self.fields.keyOrder = ['email', 'first_name', 'last_name',
192 'provider', 'third_party_identifier']
193 if get_latest_terms():
194 self.fields.keyOrder.append('has_signed_terms')
195 #set readonly form fields
196 ro = ["provider", "third_party_identifier", "first_name", "last_name"]
198 self.fields[f].widget.attrs['readonly'] = True
200 if 'has_signed_terms' in self.fields:
201 # Overriding field label since we need to apply a link
202 # to the terms within the label
203 terms_link_html = '<a href="%s" target="_blank">%s</a>' \
204 % (reverse('latest_terms'), _("the terms"))
205 self.fields['has_signed_terms'].label = \
206 mark_safe("I agree with %s" % terms_link_html)
208 def clean_email(self):
209 email = self.cleaned_data['email']
211 raise forms.ValidationError(_("This field is required"))
213 AstakosUser.objects.get(email = email)
214 raise forms.ValidationError(_("This email is already used"))
215 except AstakosUser.DoesNotExist:
218 def clean_has_signed_terms(self):
219 has_signed_terms = self.cleaned_data['has_signed_terms']
220 if not has_signed_terms:
221 raise forms.ValidationError(_('You have to agree with the terms'))
222 return has_signed_terms
224 def save(self, commit=True):
225 user = super(ThirdPartyUserCreationForm, self).save(commit=False)
226 user.set_unusable_password()
230 logger.info('Created user %s', user)
233 class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm):
234 def __init__(self, *args, **kwargs):
235 super(InvitedThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
236 #set readonly form fields
237 self.fields['email'].widget.attrs['readonly'] = True
239 class ShibbolethUserCreationForm(ThirdPartyUserCreationForm):
240 def clean_email(self):
241 email = self.cleaned_data['email']
243 raise forms.ValidationError(_("This field is required"))
245 user = AstakosUser.objects.get(email = email)
246 if user.provider == 'local':
250 raise forms.ValidationError(_("This email is already associated with another shibboleth account."))
251 except AstakosUser.DoesNotExist:
254 class LoginForm(AuthenticationForm):
255 username = forms.EmailField(label=_("Email"))
257 class ProfileForm(forms.ModelForm):
259 Subclass of ``ModelForm`` for permiting user to edit his/her profile.
260 Most of the fields are readonly since the user is not allowed to change them.
262 The class defines a save method which sets ``is_verified`` to True so as the user
263 during the next login will not to be redirected to profile page.
265 renew = forms.BooleanField(label='Renew token', required=False)
269 fields = ('email', 'first_name', 'last_name', 'auth_token', 'auth_token_expires', 'groups')
271 def __init__(self, *args, **kwargs):
272 super(ProfileForm, self).__init__(*args, **kwargs)
273 instance = getattr(self, 'instance', None)
274 ro_fields = ('auth_token', 'auth_token_expires', 'groups')
275 if instance and instance.id:
276 for field in ro_fields:
277 self.fields[field].widget.attrs['readonly'] = True
279 def save(self, commit=True):
280 user = super(ProfileForm, self).save(commit=False)
281 user.is_verified = True
282 if self.cleaned_data.get('renew'):
288 class FeedbackForm(forms.Form):
290 Form for writing feedback.
292 feedback_msg = forms.CharField(widget=forms.TextInput(), label=u'Message')
293 feedback_data = forms.CharField(widget=forms.HiddenInput(), label='',
296 class SendInvitationForm(forms.Form):
298 Form for sending an invitations
301 email = forms.EmailField(required = True, label = 'Email address')
302 first_name = forms.EmailField(label = 'First name')
303 last_name = forms.EmailField(label = 'Last name')
305 class ExtendedPasswordResetForm(PasswordResetForm):
307 Extends PasswordResetForm by overriding save method:
308 passes a custom from_email in send_mail.
310 Since Django 1.3 this is useless since ``django.contrib.auth.views.reset_password``
311 accepts a from_email argument.
313 def save(self, domain_override=None, email_template_name='registration/password_reset_email.html',
314 use_https=False, token_generator=default_token_generator, request=None):
316 Generates a one-use only link for resetting password and sends to the user.
318 for user in self.users_cache:
319 url = reverse('django.contrib.auth.views.password_reset_confirm',
320 kwargs={'uidb36':int_to_base36(user.id),
321 'token':token_generator.make_token(user)})
322 url = request.build_absolute_uri(url)
323 t = loader.get_template(email_template_name)
327 'site_name': SITENAME,
330 'support': DEFAULT_CONTACT_EMAIL
332 from_email = DEFAULT_FROM_EMAIL
333 send_mail(_("Password reset on %s alpha2 testing") % SITENAME,
334 t.render(Context(c)), from_email, [user.email])
336 class SignApprovalTermsForm(forms.ModelForm):
339 fields = ("has_signed_terms",)
341 def __init__(self, *args, **kwargs):
342 super(SignApprovalTermsForm, self).__init__(*args, **kwargs)
344 def clean_has_signed_terms(self):
345 has_signed_terms = self.cleaned_data['has_signed_terms']
346 if not has_signed_terms:
347 raise forms.ValidationError(_('You have to agree with the terms'))
348 return has_signed_terms
350 class InvitationForm(forms.ModelForm):
351 username = forms.EmailField(label=_("Email"))
353 def __init__(self, *args, **kwargs):
354 super(InvitationForm, self).__init__(*args, **kwargs)
358 fields = ('username', 'realname')
360 def clean_username(self):
361 username = self.cleaned_data['username']
363 Invitation.objects.get(username = username)
364 raise forms.ValidationError(_('There is already invitation for this email.'))
365 except Invitation.DoesNotExist: