Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / forms.py @ 357987bc

History | View | Annotate | Download (30.2 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

    
35
from django import forms
36
from django.utils.translation import ugettext as _
37
from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
38
                                       PasswordResetForm, PasswordChangeForm,
39
                                       SetPasswordForm)
40
from django.core.mail import send_mail
41
from django.contrib.auth.tokens import default_token_generator
42
from django.template import Context, loader
43
from django.utils.http import int_to_base36
44
from django.core.urlresolvers import reverse
45
from django.utils.safestring import mark_safe
46
from django.utils.encoding import smart_str
47
from django.forms.extras.widgets import SelectDateWidget
48
from django.conf import settings
49

    
50
from astakos.im.models import (AstakosUser, EmailChange, AstakosGroup,
51
                               Invitation, Membership, GroupKind, Resource,
52
                               get_latest_terms, RESOURCE_SEPARATOR)
53
from astakos.im.settings import (INVITATIONS_PER_LEVEL, BASEURL, SITENAME,
54
                                 RECAPTCHA_PRIVATE_KEY, RECAPTCHA_ENABLED,
55
                                 DEFAULT_CONTACT_EMAIL, LOGGING_LEVEL,
56
                                 PASSWORD_RESET_EMAIL_SUBJECT,
57
                                 NEWPASSWD_INVALIDATE_TOKEN)
58
from astakos.im.widgets import DummyWidget, RecaptchaWidget
59
from astakos.im.functions import send_change_email
60

    
61
from astakos.im.util import reserved_email, get_query
62

    
63
import astakos.im.messages as astakos_messages
64

    
65
import logging
66
import hashlib
67
import recaptcha.client.captcha as captcha
68
from random import random
69

    
70
logger = logging.getLogger(__name__)
71

    
72

    
73
class LocalUserCreationForm(UserCreationForm):
74
    """
75
    Extends the built in UserCreationForm in several ways:
76

77
    * Adds email, first_name, last_name, recaptcha_challenge_field, recaptcha_response_field field.
78
    * The username field isn't visible and it is assigned a generated id.
79
    * User created is not active.
80
    """
81
    recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
82
    recaptcha_response_field = forms.CharField(
83
        widget=RecaptchaWidget, label='')
84

    
85
    class Meta:
86
        model = AstakosUser
87
        fields = ("email", "first_name", "last_name",
88
                  "has_signed_terms", "has_signed_terms")
89

    
90
    def __init__(self, *args, **kwargs):
91
        """
92
        Changes the order of fields, and removes the username field.
93
        """
94
        request = kwargs.get('request', None)
95
        if request:
96
            kwargs.pop('request')
97
            self.ip = request.META.get('REMOTE_ADDR',
98
                                       request.META.get('HTTP_X_REAL_IP', None))
99

    
100
        super(LocalUserCreationForm, self).__init__(*args, **kwargs)
101
        self.fields.keyOrder = ['email', 'first_name', 'last_name',
102
                                'password1', 'password2']
103

    
104
        if RECAPTCHA_ENABLED:
105
            self.fields.keyOrder.extend(['recaptcha_challenge_field',
106
                                         'recaptcha_response_field', ])
107
        if get_latest_terms():
108
            self.fields.keyOrder.append('has_signed_terms')
109

    
110
        if 'has_signed_terms' in self.fields:
111
            # Overriding field label since we need to apply a link
112
            # to the terms within the label
113
            terms_link_html = '<a href="%s" target="_blank">%s</a>' \
114
                % (reverse('latest_terms'), _("the terms"))
115
            self.fields['has_signed_terms'].label = \
116
                mark_safe("I agree with %s" % terms_link_html)
117

    
118
    def clean_email(self):
119
        email = self.cleaned_data['email']
120
        if not email:
121
            raise forms.ValidationError(_(astakos_messages.REQUIRED_FIELD))
122
        if reserved_email(email):
123
            raise forms.ValidationError(_(astakos_messages.EMAIL_USED))
124
        return email
125

    
126
    def clean_has_signed_terms(self):
127
        has_signed_terms = self.cleaned_data['has_signed_terms']
128
        if not has_signed_terms:
129
            raise forms.ValidationError(_(astakos_messages.SIGN_TERMS))
130
        return has_signed_terms
131

    
132
    def clean_recaptcha_response_field(self):
133
        if 'recaptcha_challenge_field' in self.cleaned_data:
134
            self.validate_captcha()
135
        return self.cleaned_data['recaptcha_response_field']
136

    
137
    def clean_recaptcha_challenge_field(self):
138
        if 'recaptcha_response_field' in self.cleaned_data:
139
            self.validate_captcha()
140
        return self.cleaned_data['recaptcha_challenge_field']
141

    
142
    def validate_captcha(self):
143
        rcf = self.cleaned_data['recaptcha_challenge_field']
144
        rrf = self.cleaned_data['recaptcha_response_field']
145
        check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
146
        if not check.is_valid:
147
            raise forms.ValidationError(_(astakos_messages.CAPTCHA_VALIDATION_ERR))
148

    
149
    def save(self, commit=True):
150
        """
151
        Saves the email, first_name and last_name properties, after the normal
152
        save behavior is complete.
153
        """
154
        user = super(LocalUserCreationForm, self).save(commit=False)
155
        user.renew_token()
156
        if commit:
157
            user.save()
158
            logger.log(LOGGING_LEVEL, 'Created user %s' % user.email)
159
        return user
160

    
161

    
162
class InvitedLocalUserCreationForm(LocalUserCreationForm):
163
    """
164
    Extends the LocalUserCreationForm: email is readonly.
165
    """
166
    class Meta:
167
        model = AstakosUser
168
        fields = ("email", "first_name", "last_name", "has_signed_terms")
169

    
170
    def __init__(self, *args, **kwargs):
171
        """
172
        Changes the order of fields, and removes the username field.
173
        """
174
        super(InvitedLocalUserCreationForm, self).__init__(*args, **kwargs)
175

    
176
        #set readonly form fields
177
        ro = ('email', 'username',)
178
        for f in ro:
179
            self.fields[f].widget.attrs['readonly'] = True
180

    
181
    def save(self, commit=True):
182
        user = super(InvitedLocalUserCreationForm, self).save(commit=False)
183
        level = user.invitation.inviter.level + 1
184
        user.level = level
185
        user.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
186
        user.email_verified = True
187
        if commit:
188
            user.save()
189
        return user
190

    
191

    
192
class ThirdPartyUserCreationForm(forms.ModelForm):
193
    class Meta:
194
        model = AstakosUser
195
        fields = ("email", "first_name", "last_name",
196
                  "third_party_identifier", "has_signed_terms")
197

    
198
    def __init__(self, *args, **kwargs):
199
        """
200
        Changes the order of fields, and removes the username field.
201
        """
202
        self.request = kwargs.get('request', None)
203
        if self.request:
204
            kwargs.pop('request')
205
        super(ThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
206
        self.fields.keyOrder = ['email', 'first_name', 'last_name',
207
                                'third_party_identifier']
208
        if get_latest_terms():
209
            self.fields.keyOrder.append('has_signed_terms')
210
        #set readonly form fields
211
        ro = ["third_party_identifier"]
212
        for f in ro:
213
            self.fields[f].widget.attrs['readonly'] = True
214

    
215
        if 'has_signed_terms' in self.fields:
216
            # Overriding field label since we need to apply a link
217
            # to the terms within the label
218
            terms_link_html = '<a href="%s" target="_blank">%s</a>' \
219
                % (reverse('latest_terms'), _("the terms"))
220
            self.fields['has_signed_terms'].label = \
221
                mark_safe("I agree with %s" % terms_link_html)
222

    
223
    def clean_email(self):
224
        email = self.cleaned_data['email']
225
        if not email:
226
            raise forms.ValidationError(_(astakos_messages.REQUIRED_FIELD))
227
        return email
228

    
229
    def clean_has_signed_terms(self):
230
        has_signed_terms = self.cleaned_data['has_signed_terms']
231
        if not has_signed_terms:
232
            raise forms.ValidationError(_(astakos_messages.SIGN_TERMS))
233
        return has_signed_terms
234

    
235
    def save(self, commit=True):
236
        user = super(ThirdPartyUserCreationForm, self).save(commit=False)
237
        user.set_unusable_password()
238
        user.renew_token()
239
        user.provider = get_query(self.request).get('provider')
240
        if commit:
241
            user.save()
242
            logger.log(LOGGING_LEVEL, 'Created user %s' % user.email)
243
        return user
244

    
245

    
246
class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm):
247
    """
248
    Extends the ThirdPartyUserCreationForm: email is readonly.
249
    """
250
    def __init__(self, *args, **kwargs):
251
        """
252
        Changes the order of fields, and removes the username field.
253
        """
254
        super(
255
            InvitedThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
256

    
257
        #set readonly form fields
258
        ro = ('email',)
259
        for f in ro:
260
            self.fields[f].widget.attrs['readonly'] = True
261

    
262
    def save(self, commit=True):
263
        user = super(
264
            InvitedThirdPartyUserCreationForm, self).save(commit=False)
265
        level = user.invitation.inviter.level + 1
266
        user.level = level
267
        user.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
268
        user.email_verified = True
269
        if commit:
270
            user.save()
271
        return user
272

    
273

    
274
class ShibbolethUserCreationForm(ThirdPartyUserCreationForm):
275
    additional_email = forms.CharField(
276
        widget=forms.HiddenInput(), label='', required=False)
277

    
278
    def __init__(self, *args, **kwargs):
279
        super(ShibbolethUserCreationForm, self).__init__(*args, **kwargs)
280
        self.fields.keyOrder.append('additional_email')
281
        # copy email value to additional_mail in case user will change it
282
        name = 'email'
283
        field = self.fields[name]
284
        self.initial['additional_email'] = self.initial.get(name,
285
                                                            field.initial)
286

    
287
    def clean_email(self):
288
        email = self.cleaned_data['email']
289
        for user in AstakosUser.objects.filter(email=email):
290
            if user.provider == 'shibboleth':
291
                raise forms.ValidationError(_(astakos_messages.SHIBBOLETH_EMAIL_USED))
292
            elif not user.is_active:
293
                raise forms.ValidationError(_(astakos_messages.SHIBBOLETH_INACTIVE_ACC))
294
        super(ShibbolethUserCreationForm, self).clean_email()
295
        return email
296

    
297

    
298
class InvitedShibbolethUserCreationForm(ShibbolethUserCreationForm,
299
                                        InvitedThirdPartyUserCreationForm):
300
    pass
301

    
302

    
303
class LoginForm(AuthenticationForm):
304
    username = forms.EmailField(label=_("Email"))
305
    recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
306
    recaptcha_response_field = forms.CharField(
307
        widget=RecaptchaWidget, label='')
308

    
309
    def __init__(self, *args, **kwargs):
310
        was_limited = kwargs.get('was_limited', False)
311
        request = kwargs.get('request', None)
312
        if request:
313
            self.ip = request.META.get('REMOTE_ADDR',
314
                                       request.META.get('HTTP_X_REAL_IP', None))
315

    
316
        t = ('request', 'was_limited')
317
        for elem in t:
318
            if elem in kwargs.keys():
319
                kwargs.pop(elem)
320
        super(LoginForm, self).__init__(*args, **kwargs)
321

    
322
        self.fields.keyOrder = ['username', 'password']
323
        if was_limited and RECAPTCHA_ENABLED:
324
            self.fields.keyOrder.extend(['recaptcha_challenge_field',
325
                                         'recaptcha_response_field', ])
326

    
327
    def clean_username(self):
328
        if 'username' in self.cleaned_data:
329
            return self.cleaned_data['username'].lower()
330

    
331
    def clean_recaptcha_response_field(self):
332
        if 'recaptcha_challenge_field' in self.cleaned_data:
333
            self.validate_captcha()
334
        return self.cleaned_data['recaptcha_response_field']
335

    
336
    def clean_recaptcha_challenge_field(self):
337
        if 'recaptcha_response_field' in self.cleaned_data:
338
            self.validate_captcha()
339
        return self.cleaned_data['recaptcha_challenge_field']
340

    
341
    def validate_captcha(self):
342
        rcf = self.cleaned_data['recaptcha_challenge_field']
343
        rrf = self.cleaned_data['recaptcha_response_field']
344
        check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
345
        if not check.is_valid:
346
            raise forms.ValidationError(_(astakos_messages.CAPTCHA_VALIDATION_ERR))
347

    
348
    def clean(self):
349
        super(LoginForm, self).clean()
350
        if self.user_cache and self.user_cache.provider not in ('local', ''):
351
            raise forms.ValidationError(_(astakos_messages.SUSPENDED_LOCAL_ACC))
352
        return self.cleaned_data
353

    
354

    
355
class ProfileForm(forms.ModelForm):
356
    """
357
    Subclass of ``ModelForm`` for permiting user to edit his/her profile.
358
    Most of the fields are readonly since the user is not allowed to change
359
    them.
360

361
    The class defines a save method which sets ``is_verified`` to True so as the
362
    user during the next login will not to be redirected to profile page.
363
    """
364
    renew = forms.BooleanField(label='Renew token', required=False)
365

    
366
    class Meta:
367
        model = AstakosUser
368
        fields = ('email', 'first_name', 'last_name', 'auth_token',
369
                  'auth_token_expires')
370

    
371
    def __init__(self, *args, **kwargs):
372
        super(ProfileForm, self).__init__(*args, **kwargs)
373
        instance = getattr(self, 'instance', None)
374
        ro_fields = ('email', 'auth_token', 'auth_token_expires')
375
        if instance and instance.id:
376
            for field in ro_fields:
377
                self.fields[field].widget.attrs['readonly'] = True
378

    
379
    def save(self, commit=True):
380
        user = super(ProfileForm, self).save(commit=False)
381
        user.is_verified = True
382
        if self.cleaned_data.get('renew'):
383
            user.renew_token()
384
        if commit:
385
            user.save()
386
        return user
387

    
388

    
389
class FeedbackForm(forms.Form):
390
    """
391
    Form for writing feedback.
392
    """
393
    feedback_msg = forms.CharField(widget=forms.Textarea, label=u'Message')
394
    feedback_data = forms.CharField(widget=forms.HiddenInput(), label='',
395
                                    required=False)
396

    
397

    
398
class SendInvitationForm(forms.Form):
399
    """
400
    Form for sending an invitations
401
    """
402

    
403
    email = forms.EmailField(required=True, label='Email address')
404
    first_name = forms.EmailField(label='First name')
405
    last_name = forms.EmailField(label='Last name')
406

    
407

    
408
class ExtendedPasswordResetForm(PasswordResetForm):
409
    """
410
    Extends PasswordResetForm by overriding save method:
411
    passes a custom from_email in send_mail.
412

413
    Since Django 1.3 this is useless since ``django.contrib.auth.views.reset_password``
414
    accepts a from_email argument.
415
    """
416
    def clean_email(self):
417
        email = super(ExtendedPasswordResetForm, self).clean_email()
418
        try:
419
            user = AstakosUser.objects.get(email=email, is_active=True)
420
            if not user.has_usable_password():
421
                raise forms.ValidationError(_(astakos_messages.UNUSABLE_PASSWORD))
422
        except AstakosUser.DoesNotExist:
423
            raise forms.ValidationError(_(astakos_messages.EMAIL_UNKNOWN))
424
        return email
425

    
426
    def save(
427
        self, domain_override=None, email_template_name='registration/password_reset_email.html',
428
            use_https=False, token_generator=default_token_generator, request=None):
429
        """
430
        Generates a one-use only link for resetting password and sends to the user.
431
        """
432
        for user in self.users_cache:
433
            url = reverse('django.contrib.auth.views.password_reset_confirm',
434
                          kwargs={'uidb36': int_to_base36(user.id),
435
                                  'token': token_generator.make_token(user)
436
                                  }
437
                          )
438
            url = urljoin(BASEURL, url)
439
            t = loader.get_template(email_template_name)
440
            c = {
441
                'email': user.email,
442
                'url': url,
443
                'site_name': SITENAME,
444
                'user': user,
445
                'baseurl': BASEURL,
446
                'support': DEFAULT_CONTACT_EMAIL
447
            }
448
            from_email = settings.SERVER_EMAIL
449
            send_mail(_(PASSWORD_RESET_EMAIL_SUBJECT),
450
                      t.render(Context(c)), from_email, [user.email])
451

    
452

    
453
class EmailChangeForm(forms.ModelForm):
454
    class Meta:
455
        model = EmailChange
456
        fields = ('new_email_address',)
457

    
458
    def clean_new_email_address(self):
459
        addr = self.cleaned_data['new_email_address']
460
        if AstakosUser.objects.filter(email__iexact=addr):
461
            raise forms.ValidationError(_(astakos_messages.EMAIL_USED))
462
        return addr
463

    
464
    def save(self, email_template_name, request, commit=True):
465
        ec = super(EmailChangeForm, self).save(commit=False)
466
        ec.user = request.user
467
        activation_key = hashlib.sha1(
468
            str(random()) + smart_str(ec.new_email_address))
469
        ec.activation_key = activation_key.hexdigest()
470
        if commit:
471
            ec.save()
472
        send_change_email(ec, request, email_template_name=email_template_name)
473

    
474

    
475
class SignApprovalTermsForm(forms.ModelForm):
476
    class Meta:
477
        model = AstakosUser
478
        fields = ("has_signed_terms",)
479

    
480
    def __init__(self, *args, **kwargs):
481
        super(SignApprovalTermsForm, self).__init__(*args, **kwargs)
482

    
483
    def clean_has_signed_terms(self):
484
        has_signed_terms = self.cleaned_data['has_signed_terms']
485
        if not has_signed_terms:
486
            raise forms.ValidationError(_(astakos_messages.SIGN_TERMS))
487
        return has_signed_terms
488

    
489

    
490
class InvitationForm(forms.ModelForm):
491
    username = forms.EmailField(label=_("Email"))
492

    
493
    def __init__(self, *args, **kwargs):
494
        super(InvitationForm, self).__init__(*args, **kwargs)
495

    
496
    class Meta:
497
        model = Invitation
498
        fields = ('username', 'realname')
499

    
500
    def clean_username(self):
501
        username = self.cleaned_data['username']
502
        try:
503
            Invitation.objects.get(username=username)
504
            raise forms.ValidationError(_(astakos_messages.INVITATION_EMAIL_EXISTS))
505
        except Invitation.DoesNotExist:
506
            pass
507
        return username
508

    
509

    
510
class ExtendedPasswordChangeForm(PasswordChangeForm):
511
    """
512
    Extends PasswordChangeForm by enabling user
513
    to optionally renew also the token.
514
    """
515
    if not NEWPASSWD_INVALIDATE_TOKEN:
516
        renew = forms.BooleanField(label='Renew token', required=False,
517
                                   initial=True,
518
                                   help_text='Unsetting this may result in security risk.')
519

    
520
    def __init__(self, user, *args, **kwargs):
521
        super(ExtendedPasswordChangeForm, self).__init__(user, *args, **kwargs)
522

    
523
    def save(self, commit=True):
524
        if NEWPASSWD_INVALIDATE_TOKEN or self.cleaned_data.get('renew'):
525
            self.user.renew_token()
526
        return super(ExtendedPasswordChangeForm, self).save(commit=commit)
527

    
528

    
529
class AstakosGroupCreationForm(forms.ModelForm):
530
    kind = forms.ModelChoiceField(
531
        queryset=GroupKind.objects.all(),
532
        label="",
533
        widget=forms.HiddenInput()
534
    )
535
    name = forms.URLField(widget=forms.TextInput(attrs={'placeholder': 'eg. foo.ece.ntua.gr'}), help_text="Name should be in the form of dns",)
536
    moderation_enabled = forms.BooleanField(
537
        help_text="Check if you want to approve members participation manually",
538
        required=False,
539
        initial=True
540
    )
541
    max_participants = forms.IntegerField(
542
        required=True, min_value=1
543
    )
544

    
545
    class Meta:
546
        model = AstakosGroup
547

    
548
    def __init__(self, *args, **kwargs):
549
        #update QueryDict
550
        args = list(args)
551
        qd = args.pop(0).copy()
552
        members_unlimited = qd.pop('members_unlimited', False)
553
        members_uplimit = qd.pop('members_uplimit', None)
554
#         max_participants = None if members_unlimited else members_uplimit
555
#         qd['max_participants']= max_participants.pop(0) if max_participants else None
556
        
557
        #substitue QueryDict
558
        args.insert(0, qd)
559
        
560
        super(AstakosGroupCreationForm, self).__init__(*args, **kwargs)
561
        self.fields.keyOrder = ['kind', 'name', 'homepage', 'desc',
562
                                'issue_date', 'expiration_date',
563
                                'moderation_enabled', 'max_participants']
564
        def add_fields((k, v)):
565
            k = k.partition('_proxy')[0]
566
            self.fields[k] = forms.IntegerField(
567
                required=False,
568
                widget=forms.HiddenInput(),
569
                min_value=1
570
            )
571
        map(add_fields,
572
            ((k, v) for k,v in qd.iteritems() if k.endswith('_uplimit'))
573
        )
574
        
575
        def add_fields((k, v)):
576
            self.fields[k] = forms.BooleanField(
577
                required=False,
578
                #widget=forms.HiddenInput()
579
            )
580
        map(add_fields,
581
            ((k, v) for k,v in qd.iteritems() if k.startswith('is_selected_'))
582
        )
583
    
584
    def policies(self):
585
        self.clean()
586
        policies = []
587
        append = policies.append
588
        for name, uplimit in self.cleaned_data.iteritems():
589
            
590
            subs = name.split('_uplimit')
591
            if len(subs) == 2:
592
                prefix, suffix = subs
593
                s, sep, r = prefix.partition(RESOURCE_SEPARATOR)
594
                resource = Resource.objects.get(service__name=s, name=r)
595
 
596
                # keep only resource limits for selected resource groups
597
                print '###', resource.group, s, r, uplimit, self.cleaned_data
598
                if self.cleaned_data.get(
599
                    'is_selected_%s' % resource.group, False
600
                ):
601
                    append(dict(service=s, resource=r, uplimit=uplimit))
602
        return policies
603

    
604
class AstakosGroupCreationSummaryForm(forms.ModelForm):
605
    kind = forms.ModelChoiceField(
606
        queryset=GroupKind.objects.all(),
607
        label="",
608
        widget=forms.HiddenInput()
609
    )
610
    name = forms.URLField()
611
    moderation_enabled = forms.BooleanField(
612
        help_text="Check if you want to approve members participation manually",
613
        required=False,
614
        initial=True
615
    )
616
    max_participants = forms.IntegerField(
617
        required=False, min_value=1
618
    )
619

    
620
    class Meta:
621
        model = AstakosGroup
622

    
623
    def __init__(self, *args, **kwargs):
624
        #update QueryDict
625
        args = list(args)
626
        qd = args.pop(0).copy()
627
        members_unlimited = qd.pop('members_unlimited', False)
628
        members_uplimit = qd.pop('members_uplimit', None)
629
#         max_participants = None if members_unlimited else members_uplimit
630
#         qd['max_participants']= max_participants.pop(0) if max_participants else None
631
        
632
        #substitue QueryDict
633
        args.insert(0, qd)
634
        
635
        super(AstakosGroupCreationSummaryForm, self).__init__(*args, **kwargs)
636
        self.fields.keyOrder = ['kind', 'name', 'homepage', 'desc',
637
                                'issue_date', 'expiration_date',
638
                                'moderation_enabled', 'max_participants']
639
        def add_fields((k, v)):
640
            self.fields[k] = forms.IntegerField(
641
                required=False,
642
                widget=forms.TextInput(),
643
                min_value=1
644
            )
645
        map(add_fields,
646
            ((k, v) for k,v in qd.iteritems() if k.endswith('_uplimit'))
647
        )
648
        
649
        def add_fields((k, v)):
650
            self.fields[k] = forms.BooleanField(
651
                required=False,
652
                widget=forms.HiddenInput()
653
            )
654
        map(add_fields,
655
            ((k, v) for k,v in qd.iteritems() if k.startswith('is_selected_'))
656
        )
657
        for f in self.fields.values():
658
            f.widget = forms.HiddenInput()
659

    
660
    def clean(self):
661
        super(AstakosGroupCreationSummaryForm, self).clean()
662
        self.cleaned_data['policies'] = []
663
        append = self.cleaned_data['policies'].append
664
        print '#', self.cleaned_data
665
        #tbd = [f for f in self.fields if (f.startswith('is_selected_') and (not f.endswith('_proxy')))]
666
        tbd = [f for f in self.fields if f.startswith('is_selected_')]
667
        for name, uplimit in self.cleaned_data.iteritems():
668
            print '####', name, uplimit
669
            subs = name.split('_uplimit')
670
            if len(subs) == 2:
671
                tbd.append(name)
672
                prefix, suffix = subs
673
                s, sep, r = prefix.partition(RESOURCE_SEPARATOR)
674
                resource = Resource.objects.get(service__name=s, name=r)
675
                
676
                print '#### ####', resource
677
                # keep only resource limits for selected resource groups
678
                if self.cleaned_data.get(
679
                    'is_selected_%s' % resource.group, False
680
                ):
681
                    append(dict(service=s, resource=r, uplimit=uplimit))
682
        for name in tbd:
683
            self.cleaned_data.pop(name, None)
684
        return self.cleaned_data
685

    
686
class AstakosGroupUpdateForm(forms.ModelForm):
687
    class Meta:
688
        model = AstakosGroup
689
        fields = ('homepage', 'desc')
690

    
691

    
692
class AddGroupMembersForm(forms.Form):
693
    q = forms.CharField(
694
        max_length=800, widget=forms.Textarea, label=_('Add members'),
695
        help_text=_(astakos_messages.ADD_GROUP_MEMBERS_Q_HELP),
696
        required=True)
697

    
698
    def clean(self):
699
        q = self.cleaned_data.get('q') or ''
700
        users = q.split(',')
701
        users = list(u.strip() for u in users if u)
702
        db_entries = AstakosUser.objects.filter(email__in=users)
703
        unknown = list(set(users) - set(u.email for u in db_entries))
704
        if unknown:
705
            raise forms.ValidationError(_(astakos_messages.UNKNOWN_USERS) % ','.join(unknown))
706
        self.valid_users = db_entries
707
        return self.cleaned_data
708

    
709
    def get_valid_users(self):
710
        """Should be called after form cleaning"""
711
        try:
712
            return self.valid_users
713
        except:
714
            return ()
715

    
716

    
717
class AstakosGroupSearchForm(forms.Form):
718
    q = forms.CharField(max_length=200, label='Search group')
719

    
720

    
721
class TimelineForm(forms.Form):
722
#    entity = forms.CharField(
723
#        widget=forms.HiddenInput(), label='')
724
    entity = forms.ModelChoiceField(
725
        queryset=AstakosUser.objects.filter(is_active=True)
726
    )
727
    resource = forms.ModelChoiceField(
728
        queryset=Resource.objects.all()
729
    )
730
    start_date = forms.DateTimeField()
731
    end_date = forms.DateTimeField()
732
    details = forms.BooleanField(required=False, label="Detailed Listing")
733
    operation = forms.ChoiceField(
734
        label='Charge Method',
735
        choices=(('', '-------------'),
736
                 ('charge_usage', 'Charge Usage'),
737
                 ('charge_traffic', 'Charge Traffic'), )
738
    )
739

    
740
    def clean(self):
741
        super(TimelineForm, self).clean()
742
        d = self.cleaned_data
743
        if 'resource' in d:
744
            d['resource'] = str(d['resource'])
745
        if 'start_date' in d:
746
            d['start_date'] = d['start_date'].strftime(
747
                "%Y-%m-%dT%H:%M:%S.%f")[:24]
748
        if 'end_date' in d:
749
            d['end_date'] = d['end_date'].strftime("%Y-%m-%dT%H:%M:%S.%f")[:24]
750
        if 'entity' in d:
751
            d['entity'] = d['entity'].email
752
        return d
753

    
754

    
755
class AstakosGroupSortForm(forms.Form):
756
    sort_by = forms.ChoiceField(label='Sort by',
757
                                choices=(('groupname', 'Name'),
758
                                         ('kindname', 'Type'),
759
                                         ('issue_date', 'Issue Date'),
760
                                         ('expiration_date',
761
                                          'Expiration Date'),
762
                                         ('approved_members_num',
763
                                          'Participants'),
764
                                         ('is_enabled', 'Status'),
765
                                         ('moderation_enabled', 'Moderation'),
766
                                         ('membership_status',
767
                                          'Enrollment Status')
768
                                         ),
769
                                required=False)
770

    
771

    
772
class MembersSortForm(forms.Form):
773
    sort_by = forms.ChoiceField(label='Sort by',
774
                                choices=(('person__email', 'User Id'),
775
                                         ('person__first_name', 'Name'),
776
                                         ('date_joined', 'Status')
777
                                         ),
778
                                required=False)
779

    
780

    
781
class PickResourceForm(forms.Form):
782
    resource = forms.ModelChoiceField(
783
        queryset=Resource.objects.select_related().all()
784
    )
785
    resource.widget.attrs["onchange"] = "this.form.submit()"
786

    
787

    
788
class ExtendedSetPasswordForm(SetPasswordForm):
789
    """
790
    Extends SetPasswordForm by enabling user
791
    to optionally renew also the token.
792
    """
793
    if not NEWPASSWD_INVALIDATE_TOKEN:
794
        renew = forms.BooleanField(label='Renew token', required=False,
795
                                   initial=True,
796
                                   help_text='Unsetting this may result in security risk.')
797

    
798
    def __init__(self, user, *args, **kwargs):
799
        super(ExtendedSetPasswordForm, self).__init__(user, *args, **kwargs)
800

    
801
    def save(self, commit=True):
802
        if NEWPASSWD_INVALIDATE_TOKEN or self.cleaned_data.get('renew'):
803
            if isinstance(self.user, AstakosUser):
804
                self.user.renew_token()
805
        return super(ExtendedSetPasswordForm, self).save(commit=commit)