Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views / im.py @ 8998f09a

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

    
34
import logging
35
import inflect
36

    
37
engine = inflect.engine()
38

    
39
from urllib import quote
40

    
41
from django.shortcuts import get_object_or_404
42
from django.contrib import messages
43
from django.contrib.auth.decorators import login_required
44
from django.core.urlresolvers import reverse
45
from django.db import transaction
46
from django.http import HttpResponse, HttpResponseRedirect, Http404
47
from django.shortcuts import redirect
48
from django.utils.translation import ugettext as _
49
from django.core.exceptions import PermissionDenied
50
from django.views.decorators.http import require_http_methods
51
from django.utils import simplejson as json
52

    
53
import astakos.im.messages as astakos_messages
54

    
55
from astakos.im import activation_backends
56
from astakos.im.models import AstakosUser, ApprovalTerms, EmailChange, \
57
    AstakosUserAuthProvider, PendingThirdPartyUser, Service
58
from astakos.im.util import get_context, prepare_response, get_query, \
59
    restrict_next
60
from astakos.im.forms import LoginForm, InvitationForm, FeedbackForm, \
61
    SignApprovalTermsForm, EmailChangeForm
62
from astakos.im.forms import ExtendedProfileForm as ProfileForm
63
from astakos.im.functions import send_feedback, logout as auth_logout, \
64
    invite as invite_func
65
from astakos.im import settings
66
from astakos.im import presentation
67
from astakos.im import auth_providers as auth
68
from astakos.im import quotas
69
from astakos.im.views.util import render_response, _resources_catalog
70
from astakos.im.views.decorators import cookie_fix, signed_terms_required,\
71
    required_auth_methods_assigned, valid_astakos_user_required
72

    
73
logger = logging.getLogger(__name__)
74

    
75

    
76
@require_http_methods(["GET", "POST"])
77
@cookie_fix
78
@signed_terms_required
79
def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
80
    """
81
    If there is logged on user renders the profile page otherwise renders login page.
82

83
    **Arguments**
84

85
    ``login_template_name``
86
        A custom login template to use. This is optional; if not specified,
87
        this will default to ``im/login.html``.
88

89
    ``profile_template_name``
90
        A custom profile template to use. This is optional; if not specified,
91
        this will default to ``im/profile.html``.
92

93
    ``extra_context``
94
        An dictionary of variables to add to the template context.
95

96
    **Template:**
97

98
    im/profile.html or im/login.html or ``template_name`` keyword argument.
99

100
    """
101
    extra_context = extra_context or {}
102
    template_name = login_template_name
103
    if request.user.is_authenticated():
104
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
105

    
106
    third_party_token = request.GET.get('key', False)
107
    if third_party_token:
108
        messages.info(request, astakos_messages.AUTH_PROVIDER_LOGIN_TO_ADD)
109

    
110
    return render_response(
111
        template_name,
112
        login_form = LoginForm(request=request),
113
        context_instance = get_context(request, extra_context)
114
    )
115

    
116

    
117
@require_http_methods(["POST"])
118
@cookie_fix
119
@valid_astakos_user_required
120
def update_token(request):
121
    """
122
    Update api token view.
123
    """
124
    user = request.user
125
    user.renew_token()
126
    user.save()
127
    messages.success(request, astakos_messages.TOKEN_UPDATED)
128
    return HttpResponseRedirect(reverse('edit_profile'))
129

    
130

    
131
@require_http_methods(["GET", "POST"])
132
@cookie_fix
133
@valid_astakos_user_required
134
@transaction.commit_manually
135
def invite(request, template_name='im/invitations.html', extra_context=None):
136
    """
137
    Allows a user to invite somebody else.
138

139
    In case of GET request renders a form for providing the invitee information.
140
    In case of POST checks whether the user has not run out of invitations and then
141
    sends an invitation email to singup to the service.
142

143
    The view uses commit_manually decorator in order to ensure the number of the
144
    user invitations is going to be updated only if the email has been successfully sent.
145

146
    If the user isn't logged in, redirects to settings.LOGIN_URL.
147

148
    **Arguments**
149

150
    ``template_name``
151
        A custom template to use. This is optional; if not specified,
152
        this will default to ``im/invitations.html``.
153

154
    ``extra_context``
155
        An dictionary of variables to add to the template context.
156

157
    **Template:**
158

159
    im/invitations.html or ``template_name`` keyword argument.
160

161
    **Settings:**
162

163
    The view expectes the following settings are defined:
164

165
    * LOGIN_URL: login uri
166
    """
167
    extra_context = extra_context or {}
168
    status = None
169
    message = None
170
    form = InvitationForm()
171

    
172
    inviter = request.user
173
    if request.method == 'POST':
174
        form = InvitationForm(request.POST)
175
        if inviter.invitations > 0:
176
            if form.is_valid():
177
                try:
178
                    email = form.cleaned_data.get('username')
179
                    realname = form.cleaned_data.get('realname')
180
                    invite_func(inviter, email, realname)
181
                    message = _(astakos_messages.INVITATION_SENT) % locals()
182
                    messages.success(request, message)
183
                except Exception, e:
184
                    transaction.rollback()
185
                    raise
186
                else:
187
                    transaction.commit()
188
        else:
189
            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
190
            messages.error(request, message)
191

    
192
    sent = [{'email': inv.username,
193
             'realname': inv.realname,
194
             'is_consumed': inv.is_consumed}
195
            for inv in request.user.invitations_sent.all()]
196
    kwargs = {'inviter': inviter,
197
              'sent': sent}
198
    context = get_context(request, extra_context, **kwargs)
199
    return render_response(template_name,
200
                           invitation_form=form,
201
                           context_instance=context)
202

    
203

    
204
@require_http_methods(["GET", "POST"])
205
@required_auth_methods_assigned(allow_access=True)
206
@login_required
207
@cookie_fix
208
@signed_terms_required
209
def edit_profile(request, template_name='im/profile.html', extra_context=None):
210
    """
211
    Allows a user to edit his/her profile.
212

213
    In case of GET request renders a form for displaying the user information.
214
    In case of POST updates the user informantion and redirects to ``next``
215
    url parameter if exists.
216

217
    If the user isn't logged in, redirects to settings.LOGIN_URL.
218

219
    **Arguments**
220

221
    ``template_name``
222
        A custom template to use. This is optional; if not specified,
223
        this will default to ``im/profile.html``.
224

225
    ``extra_context``
226
        An dictionary of variables to add to the template context.
227

228
    **Template:**
229

230
    im/profile.html or ``template_name`` keyword argument.
231

232
    **Settings:**
233

234
    The view expectes the following settings are defined:
235

236
    * LOGIN_URL: login uri
237
    """
238
    extra_context = extra_context or {}
239
    form = ProfileForm(
240
        instance=request.user,
241
        session_key=request.session.session_key
242
    )
243
    extra_context['next'] = request.GET.get('next')
244
    if request.method == 'POST':
245
        form = ProfileForm(
246
            request.POST,
247
            instance=request.user,
248
            session_key=request.session.session_key
249
        )
250
        if form.is_valid():
251
            try:
252
                prev_token = request.user.auth_token
253
                user = form.save(request=request)
254
                next = restrict_next(
255
                    request.POST.get('next'),
256
                    domain=settings.COOKIE_DOMAIN
257
                )
258
                msg = _(astakos_messages.PROFILE_UPDATED)
259
                messages.success(request, msg)
260

    
261
                if form.email_changed:
262
                    msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
263
                    messages.success(request, msg)
264
                if form.password_changed:
265
                    msg = _(astakos_messages.PASSWORD_CHANGED)
266
                    messages.success(request, msg)
267

    
268
                if next:
269
                    return redirect(next)
270
                else:
271
                    return redirect(reverse('edit_profile'))
272
            except ValueError, ve:
273
                messages.success(request, ve)
274
    elif request.method == "GET":
275
        request.user.is_verified = True
276
        request.user.save()
277

    
278
    # existing providers
279
    user_providers = request.user.get_enabled_auth_providers()
280
    user_disabled_providers = request.user.get_disabled_auth_providers()
281

    
282
    # providers that user can add
283
    user_available_providers = request.user.get_available_auth_providers()
284

    
285
    extra_context['services'] = Service.catalog().values()
286
    return render_response(template_name,
287
                           profile_form=form,
288
                           user_providers=user_providers,
289
                           user_disabled_providers=user_disabled_providers,
290
                           user_available_providers=user_available_providers,
291
                           context_instance=get_context(request,
292
                                                          extra_context))
293

    
294

    
295
@transaction.commit_manually
296
@require_http_methods(["GET", "POST"])
297
@cookie_fix
298
def signup(request, template_name='im/signup.html', on_success='index',
299
           extra_context=None, activation_backend=None):
300
    """
301
    Allows a user to create a local account.
302

303
    In case of GET request renders a form for entering the user information.
304
    In case of POST handles the signup.
305

306
    The user activation will be delegated to the backend specified by the
307
    ``activation_backend`` keyword argument if present, otherwise to the
308
    ``astakos.im.activation_backends.InvitationBackend`` if
309
    settings.ASTAKOS_INVITATIONS_ENABLED is True or
310
    ``astakos.im.activation_backends.SimpleBackend`` if not (see
311
    activation_backends);
312

313
    Upon successful user creation, if ``next`` url parameter is present the
314
    user is redirected there otherwise renders the same page with a success
315
    message.
316

317
    On unsuccessful creation, renders ``template_name`` with an error message.
318

319
    **Arguments**
320

321
    ``template_name``
322
        A custom template to render. This is optional;
323
        if not specified, this will default to ``im/signup.html``.
324

325
    ``extra_context``
326
        An dictionary of variables to add to the template context.
327

328
    ``on_success``
329
        Resolvable view name to redirect on registration success.
330

331
    **Template:**
332

333
    im/signup.html or ``template_name`` keyword argument.
334
    """
335
    extra_context = extra_context or {}
336
    if request.user.is_authenticated():
337
        logger.info("%s already signed in, redirect to index",
338
                    request.user.log_display)
339
        return HttpResponseRedirect(reverse('index'))
340

    
341
    provider = get_query(request).get('provider', 'local')
342
    if not auth.get_provider(provider).get_create_policy:
343
        logger.error("%s provider not available for signup", provider)
344
        raise PermissionDenied
345

    
346
    instance = None
347

    
348
    # user registered using third party provider
349
    third_party_token = request.REQUEST.get('third_party_token', None)
350
    unverified = None
351
    if third_party_token:
352
        # retreive third party entry. This was created right after the initial
353
        # third party provider handshake.
354
        pending = get_object_or_404(PendingThirdPartyUser,
355
                                    token=third_party_token)
356

    
357
        provider = pending.provider
358

    
359
        # clone third party instance into the corresponding AstakosUser
360
        instance = pending.get_user_instance()
361
        get_unverified = AstakosUserAuthProvider.objects.unverified
362

    
363
        # check existing unverified entries
364
        unverified = get_unverified(pending.provider,
365
                                    identifier=pending.third_party_identifier)
366

    
367
        if unverified and request.method == 'GET':
368
            messages.warning(request, unverified.get_pending_registration_msg)
369
            if unverified.user.moderated:
370
                messages.warning(request,
371
                                 unverified.get_pending_resend_activation_msg)
372
            else:
373
                messages.warning(request,
374
                                 unverified.get_pending_moderation_msg)
375

    
376
    # prepare activation backend based on current request
377
    if not activation_backend:
378
        activation_backend = activation_backends.get_backend()
379

    
380
    form_kwargs = {'instance': instance}
381
    if third_party_token:
382
        form_kwargs['third_party_token'] = third_party_token
383

    
384
    form = activation_backend.get_signup_form(
385
        provider, None, **form_kwargs)
386

    
387
    if request.method == 'POST':
388
        form = activation_backend.get_signup_form(
389
            provider,
390
            request.POST,
391
            **form_kwargs)
392

    
393
        if form.is_valid():
394
            commited = False
395
            try:
396
                user = form.save(commit=False)
397

    
398
                # delete previously unverified accounts
399
                if AstakosUser.objects.user_exists(user.email):
400
                    AstakosUser.objects.get_by_identifier(user.email).delete()
401

    
402
                # store_user so that user auth providers get initialized
403
                form.store_user(user, request)
404
                result = activation_backend.handle_registration(user)
405
                if result.status == \
406
                        activation_backend.Result.PENDING_MODERATION:
407
                    # user should be warned that his account is not active yet
408
                    status = messages.WARNING
409
                else:
410
                    status = messages.SUCCESS
411
                message = result.message
412
                activation_backend.send_result_notifications(result, user)
413

    
414
                # commit user entry
415
                transaction.commit()
416
                # commited flag
417
                # in case an exception get raised from this point
418
                commited = True
419

    
420
                if user and user.is_active:
421
                    # activation backend directly activated the user
422
                    # log him in
423
                    next = request.POST.get('next', '')
424
                    response = prepare_response(request, user, next=next)
425
                    return response
426

    
427
                messages.add_message(request, status, message)
428
                return HttpResponseRedirect(reverse(on_success))
429
            except Exception, e:
430
                if not commited:
431
                    transaction.rollback()
432
                raise
433

    
434
    return render_response(template_name,
435
                           signup_form=form,
436
                           third_party_token=third_party_token,
437
                           provider=provider,
438
                           context_instance=get_context(request, extra_context))
439

    
440

    
441
@require_http_methods(["GET", "POST"])
442
@required_auth_methods_assigned(allow_access=True)
443
@login_required
444
@cookie_fix
445
@signed_terms_required
446
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
447
    """
448
    Allows a user to send feedback.
449

450
    In case of GET request renders a form for providing the feedback information.
451
    In case of POST sends an email to support team.
452

453
    If the user isn't logged in, redirects to settings.LOGIN_URL.
454

455
    **Arguments**
456

457
    ``template_name``
458
        A custom template to use. This is optional; if not specified,
459
        this will default to ``im/feedback.html``.
460

461
    ``extra_context``
462
        An dictionary of variables to add to the template context.
463

464
    **Template:**
465

466
    im/signup.html or ``template_name`` keyword argument.
467

468
    **Settings:**
469

470
    * LOGIN_URL: login uri
471
    """
472
    extra_context = extra_context or {}
473
    if request.method == 'GET':
474
        form = FeedbackForm()
475
    if request.method == 'POST':
476
        if not request.user:
477
            return HttpResponse('Unauthorized', status=401)
478

    
479
        form = FeedbackForm(request.POST)
480
        if form.is_valid():
481
            msg = form.cleaned_data['feedback_msg']
482
            data = form.cleaned_data['feedback_data']
483
            send_feedback(msg, data, request.user, email_template_name)
484
            message = _(astakos_messages.FEEDBACK_SENT)
485
            messages.success(request, message)
486
            return HttpResponseRedirect(reverse('feedback'))
487

    
488
    return render_response(template_name,
489
                           feedback_form=form,
490
                           context_instance=get_context(request,
491
                                                        extra_context))
492

    
493

    
494
@require_http_methods(["GET"])
495
@cookie_fix
496
def logout(request, template='registration/logged_out.html',
497
           extra_context=None):
498
    """
499
    Wraps `django.contrib.auth.logout`.
500
    """
501
    extra_context = extra_context or {}
502
    response = HttpResponse()
503
    if request.user.is_authenticated():
504
        email = request.user.email
505
        auth_logout(request)
506
    else:
507
        response['Location'] = reverse('index')
508
        response.status_code = 301
509
        return response
510

    
511
    next = restrict_next(
512
        request.GET.get('next'),
513
        domain=settings.COOKIE_DOMAIN
514
    )
515

    
516
    if next:
517
        response['Location'] = next
518
        response.status_code = 302
519
    elif settings.LOGOUT_NEXT:
520
        response['Location'] = settings.LOGOUT_NEXT
521
        response.status_code = 301
522
    else:
523
        last_provider = request.COOKIES.get('astakos_last_login_method', 'local')
524
        provider = auth.get_provider(last_provider)
525
        message = provider.get_logout_success_msg
526
        extra = provider.get_logout_success_extra_msg
527
        if extra:
528
            message += "<br />"  + extra
529
        messages.success(request, message)
530
        response['Location'] = reverse('index')
531
        response.status_code = 301
532
    return response
533

    
534

    
535
@require_http_methods(["GET", "POST"])
536
@cookie_fix
537
@transaction.commit_manually
538
def activate(request, greeting_email_template_name='im/welcome_email.txt',
539
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
540
    """
541
    Activates the user identified by the ``auth`` request parameter, sends a
542
    welcome email and renews the user token.
543

544
    The view uses commit_manually decorator in order to ensure the user state
545
    will be updated only if the email will be send successfully.
546
    """
547
    token = request.GET.get('auth')
548
    next = request.GET.get('next')
549

    
550
    if request.user.is_authenticated():
551
        message = _(astakos_messages.LOGGED_IN_WARNING)
552
        messages.error(request, message)
553
        return HttpResponseRedirect(reverse('index'))
554

    
555
    try:
556
        user = AstakosUser.objects.get(verification_code=token)
557
    except AstakosUser.DoesNotExist:
558
        raise Http404
559

    
560
    if user.email_verified:
561
        message = _(astakos_messages.ACCOUNT_ALREADY_VERIFIED)
562
        messages.error(request, message)
563
        return HttpResponseRedirect(reverse('index'))
564

    
565
    try:
566
        backend = activation_backends.get_backend()
567
        result = backend.handle_verification(user, token)
568
        backend.send_result_notifications(result, user)
569
        next = settings.ACTIVATION_REDIRECT_URL or next
570
        response = HttpResponseRedirect(reverse('index'))
571
        if user.is_active:
572
            response = prepare_response(request, user, next, renew=True)
573
            messages.success(request, _(result.message))
574
        else:
575
            messages.warning(request, _(result.message))
576
    except Exception:
577
        transaction.rollback()
578
        raise
579
    else:
580
        transaction.commit()
581
        return response
582

    
583

    
584
@require_http_methods(["GET", "POST"])
585
@cookie_fix
586
def approval_terms(request, term_id=None,
587
                   template_name='im/approval_terms.html', extra_context=None):
588
    extra_context = extra_context or {}
589
    term = None
590
    terms = None
591
    if not term_id:
592
        try:
593
            term = ApprovalTerms.objects.order_by('-id')[0]
594
        except IndexError:
595
            pass
596
    else:
597
        try:
598
            term = ApprovalTerms.objects.get(id=term_id)
599
        except ApprovalTerms.DoesNotExist, e:
600
            pass
601

    
602
    if not term:
603
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
604
        return HttpResponseRedirect(reverse('index'))
605
    try:
606
        f = open(term.location, 'r')
607
    except IOError:
608
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
609
        return render_response(
610
            template_name, context_instance=get_context(request,
611
                                                        extra_context))
612

    
613
    terms = f.read()
614

    
615
    if request.method == 'POST':
616
        next = restrict_next(
617
            request.POST.get('next'),
618
            domain=settings.COOKIE_DOMAIN
619
        )
620
        if not next:
621
            next = reverse('index')
622
        form = SignApprovalTermsForm(request.POST, instance=request.user)
623
        if not form.is_valid():
624
            return render_response(template_name,
625
                                   terms=terms,
626
                                   approval_terms_form=form,
627
                                   context_instance=get_context(request,
628
                                                                extra_context))
629
        user = form.save()
630
        return HttpResponseRedirect(next)
631
    else:
632
        form = None
633
        if request.user.is_authenticated() and not request.user.signed_terms:
634
            form = SignApprovalTermsForm(instance=request.user)
635
        return render_response(template_name,
636
                               terms=terms,
637
                               approval_terms_form=form,
638
                               context_instance=get_context(request,
639
                                                            extra_context))
640

    
641

    
642
@require_http_methods(["GET", "POST"])
643
@cookie_fix
644
@transaction.commit_manually
645
def change_email(request, activation_key=None,
646
                 email_template_name='registration/email_change_email.txt',
647
                 form_template_name='registration/email_change_form.html',
648
                 confirm_template_name='registration/email_change_done.html',
649
                 extra_context=None):
650
    extra_context = extra_context or {}
651

    
652
    if not settings.EMAILCHANGE_ENABLED:
653
        raise PermissionDenied
654

    
655
    if activation_key:
656
        try:
657
            try:
658
                email_change = EmailChange.objects.get(
659
                    activation_key=activation_key)
660
            except EmailChange.DoesNotExist:
661
                transaction.rollback()
662
                logger.error("[change-email] Invalid or used activation "
663
                             "code, %s", activation_key)
664
                raise Http404
665

    
666
            if (request.user.is_authenticated() and \
667
                request.user == email_change.user) or not \
668
                    request.user.is_authenticated():
669
                user = EmailChange.objects.change_email(activation_key)
670
                msg = _(astakos_messages.EMAIL_CHANGED)
671
                messages.success(request, msg)
672
                transaction.commit()
673
                return HttpResponseRedirect(reverse('edit_profile'))
674
            else:
675
                logger.error("[change-email] Access from invalid user, %s %s",
676
                             email_change.user, request.user.log_display)
677
                transaction.rollback()
678
                raise PermissionDenied
679
        except ValueError, e:
680
            messages.error(request, e)
681
            transaction.rollback()
682
            return HttpResponseRedirect(reverse('index'))
683

    
684
        return render_response(confirm_template_name,
685
                               modified_user=user if 'user' in locals()
686
                               else None, context_instance=get_context(request,
687
                               extra_context))
688

    
689
    if not request.user.is_authenticated():
690
        path = quote(request.get_full_path())
691
        url = request.build_absolute_uri(reverse('index'))
692
        return HttpResponseRedirect(url + '?next=' + path)
693

    
694
    # clean up expired email changes
695
    if request.user.email_change_is_pending():
696
        change = request.user.emailchanges.get()
697
        if change.activation_key_expired():
698
            change.delete()
699
            transaction.commit()
700
            return HttpResponseRedirect(reverse('email_change'))
701

    
702
    form = EmailChangeForm(request.POST or None)
703
    if request.method == 'POST' and form.is_valid():
704
        try:
705
            ec = form.save(request, email_template_name, request)
706
        except Exception, e:
707
            transaction.rollback()
708
            raise
709
        else:
710
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
711
            messages.success(request, msg)
712
            transaction.commit()
713
            return HttpResponseRedirect(reverse('edit_profile'))
714

    
715
    if request.user.email_change_is_pending():
716
        messages.warning(request, astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
717

    
718
    return render_response(
719
        form_template_name,
720
        form=form,
721
        context_instance=get_context(request, extra_context)
722
    )
723

    
724

    
725
@cookie_fix
726
def send_activation(request, user_id, template_name='im/login.html',
727
                    extra_context=None):
728

    
729
    if request.user.is_authenticated():
730
        return HttpResponseRedirect(reverse('index'))
731

    
732
    extra_context = extra_context or {}
733
    try:
734
        u = AstakosUser.objects.get(id=user_id)
735
    except AstakosUser.DoesNotExist:
736
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
737
    else:
738
        if u.email_verified:
739
            logger.warning("[resend activation] Account already verified: %s",
740
                           u.log_display)
741

    
742
            messages.error(request,
743
                           _(astakos_messages.ACCOUNT_ALREADY_VERIFIED))
744
        else:
745
            activation_backend = activation_backends.get_backend()
746
            activation_backend.send_user_verification_email(u)
747
            messages.success(request, astakos_messages.ACTIVATION_SENT)
748

    
749
    return HttpResponseRedirect(reverse('index'))
750

    
751

    
752
@require_http_methods(["GET"])
753
@cookie_fix
754
@valid_astakos_user_required
755
def resource_usage(request):
756

    
757
    resources_meta = presentation.RESOURCES
758

    
759
    current_usage = quotas.get_user_quotas(request.user)
760
    current_usage = json.dumps(current_usage['system'])
761
    resource_catalog, resource_groups = _resources_catalog(for_usage=True)
762
    if resource_catalog is False:
763
        # on fail resource_groups contains the result object
764
        result = resource_groups
765
        messages.error(request, 'Unable to retrieve system resources: %s' %
766
                       result.reason)
767

    
768
    resource_catalog = json.dumps(resource_catalog)
769
    resource_groups = json.dumps(resource_groups)
770
    resources_order = json.dumps(resources_meta.get('resources_order'))
771

    
772
    return render_response('im/resource_usage.html',
773
                           context_instance=get_context(request),
774
                           resource_catalog=resource_catalog,
775
                           resource_groups=resource_groups,
776
                           resources_order=resources_order,
777
                           current_usage=current_usage,
778
                           token_cookie_name=settings.COOKIE_NAME,
779
                           usage_update_interval=
780
                           settings.USAGE_UPDATE_INTERVAL)
781

    
782

    
783
# TODO: action only on POST and user should confirm the removal
784
@require_http_methods(["GET", "POST"])
785
@cookie_fix
786
@valid_astakos_user_required
787
def remove_auth_provider(request, pk):
788
    try:
789
        provider = request.user.auth_providers.get(pk=int(pk)).settings
790
    except AstakosUserAuthProvider.DoesNotExist:
791
        raise Http404
792

    
793
    if provider.get_remove_policy:
794
        messages.success(request, provider.get_removed_msg)
795
        provider.remove_from_user()
796
        return HttpResponseRedirect(reverse('edit_profile'))
797
    else:
798
        raise PermissionDenied
799

    
800

    
801
@require_http_methods(["GET"])
802
@required_auth_methods_assigned(allow_access=True)
803
@login_required
804
@cookie_fix
805
@signed_terms_required
806
def landing(request):
807
    context = {'services': Service.catalog(orderfor='dashboard')}
808
    return render_response(
809
        'im/landing.html',
810
        context_instance=get_context(request), **context)
811

    
812

    
813
def api_access(request):
814
    return render_response(
815
        'im/api_access.html',
816
        context_instance=get_context(request))