Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / forms.py @ 270dd48d

History | View | Annotate | Download (12.3 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
# 
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
# 
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
# 
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.
15
# 
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.
28
# 
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
35

    
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

    
46
from astakos.im.models import AstakosUser
47
from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL
48
from astakos.im.widgets import DummyWidget, RecaptchaWidget, ApprovalTermsWidget
49

    
50
# since Django 1.4 use django.core.urlresolvers.reverse_lazy instead
51
from astakos.im.util import reverse_lazy
52

    
53
import logging
54
import recaptcha.client.captcha as captcha
55

    
56
logger = logging.getLogger(__name__)
57

    
58
class LocalUserCreationForm(UserCreationForm):
59
    """
60
    Extends the built in UserCreationForm in several ways:
61
    
62
    * Adds email, first_name, last_name, recaptcha_challenge_field, recaptcha_response_field field.
63
    * The username field isn't visible and it is assigned a generated id.
64
    * User created is not active. 
65
    """
66
    recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
67
    recaptcha_response_field = forms.CharField(widget=RecaptchaWidget, label='')
68
    
69
    class Meta:
70
        model = AstakosUser
71
        fields = ("email", "first_name", "last_name", "has_signed_terms")
72
        widgets = {"has_signed_terms":ApprovalTermsWidget(terms_uri=reverse_lazy('latest_terms'))}
73
    
74
    def __init__(self, *args, **kwargs):
75
        """
76
        Changes the order of fields, and removes the username field.
77
        """
78
        if 'ip' in kwargs:
79
            self.ip = kwargs['ip']
80
            kwargs.pop('ip')
81
        super(LocalUserCreationForm, self).__init__(*args, **kwargs)
82
        self.fields.keyOrder = ['email', 'first_name', 'last_name',
83
                                'password1', 'password2',
84
                                'has_signed_terms',
85
                                'recaptcha_challenge_field',
86
                                'recaptcha_response_field',]
87
    
88
    def clean_email(self):
89
        email = self.cleaned_data['email']
90
        if not email:
91
            raise forms.ValidationError(_("This field is required"))
92
        try:
93
            AstakosUser.objects.get(email = email)
94
            raise forms.ValidationError(_("This email is already used"))
95
        except AstakosUser.DoesNotExist:
96
            return email
97
    
98
    def clean_has_signed_terms(self):
99
        has_signed_terms = self.cleaned_data['has_signed_terms']
100
        if not has_signed_terms:
101
            raise forms.ValidationError(_('You have to agree with the terms'))
102
        return has_signed_terms
103
    
104
    def clean_recaptcha_response_field(self):
105
        if 'recaptcha_challenge_field' in self.cleaned_data:
106
            self.validate_captcha()
107
        return self.cleaned_data['recaptcha_response_field']
108

    
109
    def clean_recaptcha_challenge_field(self):
110
        if 'recaptcha_response_field' in self.cleaned_data:
111
            self.validate_captcha()
112
        return self.cleaned_data['recaptcha_challenge_field']
113

    
114
    def validate_captcha(self):
115
        rcf = self.cleaned_data['recaptcha_challenge_field']
116
        rrf = self.cleaned_data['recaptcha_response_field']
117
        check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
118
        if not check.is_valid:
119
            raise forms.ValidationError(_('You have not entered the correct words'))
120
    
121
    def save(self, commit=True):
122
        """
123
        Saves the email, first_name and last_name properties, after the normal
124
        save behavior is complete.
125
        """
126
        user = super(LocalUserCreationForm, self).save(commit=False)
127
        user.renew_token()
128
        user.date_signed_terms = datetime.now()
129
        if commit:
130
            user.save()
131
        logger.info('Created user %s', user)
132
        return user
133

    
134
class InvitedLocalUserCreationForm(LocalUserCreationForm):
135
    """
136
    Extends the LocalUserCreationForm: adds an inviter readonly field.
137
    """
138
    
139
    inviter = forms.CharField(widget=forms.TextInput(), label=_('Inviter Real Name'))
140
    
141
    class Meta:
142
        model = AstakosUser
143
        fields = ("email", "first_name", "last_name", "has_signed_terms")
144
        widgets = {"has_signed_terms":ApprovalTermsWidget(terms_uri=reverse_lazy('latest_terms'))}
145
    
146
    def __init__(self, *args, **kwargs):
147
        """
148
        Changes the order of fields, and removes the username field.
149
        """
150
        super(InvitedLocalUserCreationForm, self).__init__(*args, **kwargs)
151
        self.fields.keyOrder = ['email', 'inviter', 'first_name',
152
                                'last_name', 'password1', 'password2',
153
                                'has_signed_terms',
154
                                'recaptcha_challenge_field',
155
                                'recaptcha_response_field']
156
        #set readonly form fields
157
        self.fields['inviter'].widget.attrs['readonly'] = True
158
        self.fields['email'].widget.attrs['readonly'] = True
159
        self.fields['username'].widget.attrs['readonly'] = True
160
    
161
    def save(self, commit=True):
162
        user = super(InvitedLocalUserCreationForm, self).save(commit=False)
163
        level = user.invitation.inviter.level + 1
164
        user.level = level
165
        user.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
166
        user.email_verified = True
167
        if commit:
168
            user.save()
169
        return user
170

    
171
class LoginForm(AuthenticationForm):
172
    username = forms.EmailField(label=_("Email"))
173

    
174
class ProfileForm(forms.ModelForm):
175
    """
176
    Subclass of ``ModelForm`` for permiting user to edit his/her profile.
177
    Most of the fields are readonly since the user is not allowed to change them.
178
    
179
    The class defines a save method which sets ``is_verified`` to True so as the user
180
    during the next login will not to be redirected to profile page.
181
    """
182
    renew = forms.BooleanField(label='Renew token', required=False)
183
    
184
    class Meta:
185
        model = AstakosUser
186
        fields = ('email', 'first_name', 'last_name', 'auth_token', 'auth_token_expires')
187
    
188
    def __init__(self, *args, **kwargs):
189
        super(ProfileForm, self).__init__(*args, **kwargs)
190
        instance = getattr(self, 'instance', None)
191
        ro_fields = ('auth_token', 'auth_token_expires', 'email')
192
        if instance and instance.id:
193
            for field in ro_fields:
194
                self.fields[field].widget.attrs['readonly'] = True
195
    
196
    def save(self, commit=True):
197
        user = super(ProfileForm, self).save(commit=False)
198
        user.is_verified = True
199
        if self.cleaned_data.get('renew'):
200
            user.renew_token()
201
        if commit:
202
            user.save()
203
        return user
204

    
205
class ThirdPartyUserCreationForm(ProfileForm):
206
    class Meta:
207
        model = AstakosUser
208
        fields = ('email', 'last_name', 'first_name', 'affiliation', 'provider', 'third_party_identifier')
209
    
210
    def __init__(self, *args, **kwargs):
211
        if 'ip' in kwargs:
212
            self.ip = kwargs['ip']
213
            kwargs.pop('ip')
214
        super(ThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
215
        self.fields.keyOrder = ['email']
216
    
217
    def clean_email(self):
218
        email = self.cleaned_data['email']
219
        if not email:
220
            raise forms.ValidationError(_("This field is required"))
221
        try:
222
            user = AstakosUser.objects.get(email = email)
223
            raise forms.ValidationError(_("This email is already used"))
224
        except AstakosUser.DoesNotExist:
225
            return email
226
    
227
    def save(self, commit=True):
228
        user = super(ThirdPartyUserCreationForm, self).save(commit=False)
229
        user.verified = False
230
        user.renew_token()
231
        if commit:
232
            user.save()
233
        logger.info('Created user %s', user)
234
        return user
235

    
236
class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm):
237
    def __init__(self, *args, **kwargs):
238
        super(InvitedThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
239
        #set readonly form fields
240
        self.fields['email'].widget.attrs['readonly'] = True
241

    
242
class FeedbackForm(forms.Form):
243
    """
244
    Form for writing feedback.
245
    """
246
    feedback_msg = forms.CharField(widget=forms.Textarea(),
247
                                label=u'Message', required=False)
248
    feedback_data = forms.CharField(widget=forms.HiddenInput(),
249
                                label='', required=False)
250

    
251
class SendInvitationForm(forms.Form):
252
    """
253
    Form for sending an invitations
254
    """
255
    
256
    email = forms.EmailField(required = True, label = 'Email address')
257
    first_name = forms.EmailField(label = 'First name')
258
    last_name = forms.EmailField(label = 'Last name')
259

    
260
class ExtendedPasswordResetForm(PasswordResetForm):
261
    """
262
    Extends PasswordResetForm by overriding save method:
263
    passes a custom from_email in send_mail.
264
    
265
    Since Django 1.3 this is useless since ``django.contrib.auth.views.reset_password``
266
    accepts a from_email argument.
267
    """
268
    def save(self, domain_override=None, email_template_name='registration/password_reset_email.html',
269
             use_https=False, token_generator=default_token_generator, request=None):
270
        """
271
        Generates a one-use only link for resetting password and sends to the user.
272
        """
273
        for user in self.users_cache:
274
            url = urljoin(BASEURL,
275
                          '/im/local/reset/confirm/%s-%s' %(int_to_base36(user.id),
276
                                                            token_generator.make_token(user)))
277
            t = loader.get_template(email_template_name)
278
            c = {
279
                'email': user.email,
280
                'url': url,
281
                'site_name': SITENAME,
282
                'user': user,
283
                'baseurl': BASEURL,
284
                'support': DEFAULT_CONTACT_EMAIL
285
            }
286
            from_email = DEFAULT_FROM_EMAIL
287
            send_mail(_("Password reset on %s alpha2 testing") % SITENAME,
288
                t.render(Context(c)), from_email, [user.email])
289

    
290
class SignApprovalTermsForm(forms.ModelForm):
291
    class Meta:
292
        model = AstakosUser
293
        fields = ("has_signed_terms",)
294
    
295
    def __init__(self, *args, **kwargs):
296
        super(SignApprovalTermsForm, self).__init__(*args, **kwargs)
297
    
298
    def clean_has_signed_terms(self):
299
        has_signed_terms = self.cleaned_data['has_signed_terms']
300
        if not has_signed_terms:
301
            raise forms.ValidationError(_('You have to agree with the terms'))
302
        return has_signed_terms
303
    
304
    def save(self, commit=True):
305
        """
306
        Saves the , after the normal
307
        save behavior is complete.
308
        """
309
        user = super(SignApprovalTermsForm, self).save(commit=False)
310
        user.date_signed_terms = datetime.now()
311
        if commit:
312
            user.save()
313
        return user