views.py 50.9 KB
Newer Older
Antony Chazapis's avatar
Antony Chazapis committed
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
4
5
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
6
#
7
8
9
#   1. Redistributions of source code must retain the above
#      copyright notice, this list of conditions and the following
#      disclaimer.
10
#
11
12
13
14
#   2. Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials
#      provided with the distribution.
15
#
16
17
18
19
20
21
22
23
24
25
26
27
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
30
31
32
33
34
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.

import logging
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
35
import calendar
Olga Brani's avatar
Olga Brani committed
36
37
38
import inflect

engine = inflect.engine()
39

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
40
from urllib import quote
41
from functools import wraps
42
from datetime import datetime
43

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
44
from django.contrib import messages
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
45
46
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
47
from django.db import transaction
48
from django.db.utils import IntegrityError
Olga Brani's avatar
Olga Brani committed
49
50
51
from django.http import (HttpResponse, HttpResponseBadRequest,
                         HttpResponseForbidden, HttpResponseRedirect,
                         HttpResponseBadRequest, Http404)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
52
from django.shortcuts import redirect
53
from django.template import RequestContext, loader as template_loader
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
54
55
from django.utils.http import urlencode
from django.utils.translation import ugettext as _
56
from django.views.generic.create_update import (delete_object,
57
                                                get_model_and_form_class)
58
from django.views.generic.list_detail import object_list
59
from django.core.xheaders import populate_xheaders
60
from django.core.exceptions import ValidationError, PermissionDenied
61

62
from django.template.loader import render_to_string
Olga Brani's avatar
Olga Brani committed
63
from django.views.decorators.http import require_http_methods
64
from astakos.im.activation_backends import get_backend, SimpleBackend
65
66
67

from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
                               EmailChange, GroupKind, Membership,
68
                               RESOURCE_SEPARATOR, AstakosUserAuthProvider)
69
from astakos.im.util import get_context, prepare_response, get_query, restrict_next
70
71
from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
                              FeedbackForm, SignApprovalTermsForm,
72
                              EmailChangeForm,
73
                              AstakosGroupCreationForm, AstakosGroupSearchForm,
74
                              AstakosGroupUpdateForm, AddGroupMembersForm,
75
                              MembersSortForm, AstakosGroupSortForm,
76
77
                              TimelineForm, PickResourceForm,
                              AstakosGroupCreationSummaryForm)
78
from astakos.im.functions import (send_feedback, SendMailError,
Olga Brani's avatar
Olga Brani committed
79
                                  logout as auth_logout,
80
                                  activate as activate_func,
81
                                  send_activation as send_activation_func,
Olga Brani's avatar
Olga Brani committed
82
                                  send_group_creation_notification,
83
                                  SendNotificationError)
Olga Brani's avatar
Fixes    
Olga Brani committed
84
from astakos.im.endpoints.qh import timeline_charge
85
from astakos.im.settings import (COOKIE_DOMAIN, LOGOUT_NEXT,
Olga Brani's avatar
Fixes    
Olga Brani committed
86
                                 LOGGING_LEVEL, PAGINATE_BY, RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
87
from astakos.im.tasks import request_billing
88
from astakos.im.api.callpoint import AstakosCallpoint
89

Olga Brani's avatar
Olga Brani committed
90
import astakos.im.messages as astakos_messages
91
92
from astakos.im import settings
from astakos.im import auth_providers
93

94
95
logger = logging.getLogger(__name__)

96
callpoint = AstakosCallpoint()
Olga Brani's avatar
Olga Brani committed
97

98
def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
99
100
101
102
103
    """
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
    keyword argument and returns an ``django.http.HttpResponse`` with the
    specified ``status``.
    """
104
    if tab is None:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
105
        tab = template.partition('_')[0].partition('.html')[0]
106
    kwargs.setdefault('tab', tab)
107
    html = template_loader.render_to_string(
108
        template, kwargs, context_instance=context_instance)
109
110
    response = HttpResponse(html, status=status)
    return response
111

112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
def requires_auth_provider(provider_id, **perms):
    """
    """
    def decorator(func, *args, **kwargs):
        @wraps(func)
        def wrapper(request, *args, **kwargs):
            provider = auth_providers.get_provider(provider_id)

            if not provider or not provider.is_active():
                raise PermissionDenied

            if provider:
                for pkey, value in perms.iteritems():
                    attr = 'is_available_for_%s' % pkey.lower()
                    if getattr(provider, attr)() != value:
                        raise PermissionDenied
            return func(request, *args)
        return wrapper
    return decorator

132
133
134

def requires_anonymous(func):
    """
135
    Decorator checkes whether the request.user is not Anonymous and in that case
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
136
    redirects to `logout`.
137
138
139
140
141
    """
    @wraps(func)
    def wrapper(request, *args):
        if not request.user.is_anonymous():
            next = urlencode({'next': request.build_absolute_uri()})
142
143
            logout_uri = reverse(logout) + '?' + next
            return HttpResponseRedirect(logout_uri)
144
145
146
        return func(request, *args)
    return wrapper

147

148
149
150
151
152
153
154
def signed_terms_required(func):
    """
    Decorator checkes whether the request.user is Anonymous and in that case
    redirects to `logout`.
    """
    @wraps(func)
    def wrapper(request, *args, **kwargs):
155
        if request.user.is_authenticated() and not request.user.signed_terms:
156
            params = urlencode({'next': request.build_absolute_uri(),
157
                                'show_form': ''})
158
159
160
161
162
            terms_uri = reverse('latest_terms') + '?' + params
            return HttpResponseRedirect(terms_uri)
        return func(request, *args, **kwargs)
    return wrapper

163

164
@require_http_methods(["GET", "POST"])
165
@signed_terms_required
166
def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
167
    """
168
    If there is logged on user renders the profile page otherwise renders login page.
169

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
170
    **Arguments**
171

172
173
    ``login_template_name``
        A custom login template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
174
        this will default to ``im/login.html``.
175

176
177
    ``profile_template_name``
        A custom profile template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
178
        this will default to ``im/profile.html``.
179

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
180
181
    ``extra_context``
        An dictionary of variables to add to the template context.
182

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
183
    **Template:**
184

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
185
    im/profile.html or im/login.html or ``template_name`` keyword argument.
186

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
187
    """
188
    extra_context = extra_context or {}
189
190
    template_name = login_template_name
    if request.user.is_authenticated():
191
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
192

193
194
195
196
197
    return render_response(
        template_name,
        login_form = LoginForm(request=request),
        context_instance = get_context(request, extra_context)
    )
198
199


200
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
201
@login_required
202
@signed_terms_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
203
@transaction.commit_manually
204
def invite(request, template_name='im/invitations.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
205
206
    """
    Allows a user to invite somebody else.
207

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
208
209
210
    In case of GET request renders a form for providing the invitee information.
    In case of POST checks whether the user has not run out of invitations and then
    sends an invitation email to singup to the service.
211

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
212
213
    The view uses commit_manually decorator in order to ensure the number of the
    user invitations is going to be updated only if the email has been successfully sent.
214

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
215
    If the user isn't logged in, redirects to settings.LOGIN_URL.
216

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
217
    **Arguments**
218

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
219
220
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
221
        this will default to ``im/invitations.html``.
222

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
223
224
    ``extra_context``
        An dictionary of variables to add to the template context.
225

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
226
    **Template:**
227

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
228
    im/invitations.html or ``template_name`` keyword argument.
229

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
230
    **Settings:**
231

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
232
    The view expectes the following settings are defined:
233

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
234
235
    * LOGIN_URL: login uri
    """
236
    extra_context = extra_context or {}
237
238
    status = None
    message = None
239
    form = InvitationForm()
240

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
241
    inviter = request.user
242
    if request.method == 'POST':
243
        form = InvitationForm(request.POST)
244
        if inviter.invitations > 0:
245
246
            if form.is_valid():
                try:
Olga Brani's avatar
Olga Brani committed
247
248
249
                    email = form.cleaned_data.get('username')
                    realname = form.cleaned_data.get('realname')
                    inviter.invite(email, realname)
Olga Brani's avatar
Olga Brani committed
250
                    message = _(astakos_messages.INVITATION_SENT) % locals()
251
                    messages.success(request, message)
252
253
                except SendMailError, e:
                    message = e.message
254
                    messages.error(request, message)
255
                    transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
256
                except BaseException, e:
Olga Brani's avatar
Olga Brani committed
257
                    message = _(astakos_messages.GENERIC_ERROR)
258
                    messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
259
260
261
262
                    logger.exception(e)
                    transaction.rollback()
                else:
                    transaction.commit()
263
        else:
Olga Brani's avatar
Olga Brani committed
264
            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
265
            messages.error(request, message)
266

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
267
    sent = [{'email': inv.username,
268
269
             'realname': inv.realname,
             'is_consumed': inv.is_consumed}
270
            for inv in request.user.invitations_sent.all()]
271
    kwargs = {'inviter': inviter,
272
              'sent': sent}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
273
274
    context = get_context(request, extra_context, **kwargs)
    return render_response(template_name,
275
276
277
                           invitation_form=form,
                           context_instance=context)

278

279
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
280
@login_required
281
@signed_terms_required
282
def edit_profile(request, template_name='im/profile.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
283
284
    """
    Allows a user to edit his/her profile.
285

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
286
    In case of GET request renders a form for displaying the user information.
287
288
    In case of POST updates the user informantion and redirects to ``next``
    url parameter if exists.
289

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
290
    If the user isn't logged in, redirects to settings.LOGIN_URL.
291

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
292
    **Arguments**
293

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
294
295
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
296
        this will default to ``im/profile.html``.
297

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
298
299
    ``extra_context``
        An dictionary of variables to add to the template context.
300

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
301
    **Template:**
302

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
303
    im/profile.html or ``template_name`` keyword argument.
304

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
305
    **Settings:**
306

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
307
    The view expectes the following settings are defined:
308

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
309
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
310
    """
311
    extra_context = extra_context or {}
312
313
314
315
    form = ProfileForm(
        instance=request.user,
        session_key=request.session.session_key
    )
316
    extra_context['next'] = request.GET.get('next')
317
    if request.method == 'POST':
318
319
320
321
322
        form = ProfileForm(
            request.POST,
            instance=request.user,
            session_key=request.session.session_key
        )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
323
        if form.is_valid():
324
            try:
325
326
                prev_token = request.user.auth_token
                user = form.save()
327
328
329
330
                form = ProfileForm(
                    instance=user,
                    session_key=request.session.session_key
                )
331
332
333
334
                next = restrict_next(
                    request.POST.get('next'),
                    domain=COOKIE_DOMAIN
                )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
335
336
                if next:
                    return redirect(next)
Olga Brani's avatar
Olga Brani committed
337
                msg = _(astakos_messages.PROFILE_UPDATED)
338
                messages.success(request, msg)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
339
            except ValueError, ve:
340
                messages.success(request, ve)
341
342
343
    elif request.method == "GET":
        request.user.is_verified = True
        request.user.save()
344
345
346
347
348
349
350

    # existing providers
    user_providers = request.user.get_active_auth_providers()

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
351
    return render_response(template_name,
352
                           profile_form = form,
353
354
                           user_providers = user_providers,
                           user_available_providers = user_available_providers,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
355
                           context_instance = get_context(request,
356
                                                          extra_context))
357
358


359
@transaction.commit_manually
360
@require_http_methods(["GET", "POST"])
361
def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
362
363
    """
    Allows a user to create a local account.
364

365
    In case of GET request renders a form for entering the user information.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
366
    In case of POST handles the signup.
367

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
368
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
369
370
371
    if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
    if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
    (see activation_backends);
372

373
    Upon successful user creation, if ``next`` url parameter is present the user is redirected there
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
374
    otherwise renders the same page with a success message.
375

376
    On unsuccessful creation, renders ``template_name`` with an error message.
377

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
378
    **Arguments**
379

380
381
    ``template_name``
        A custom template to render. This is optional;
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
382
        if not specified, this will default to ``im/signup.html``.
383

384
385
    ``on_success``
        A custom template to render in case of success. This is optional;
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
386
        if not specified, this will default to ``im/signup_complete.html``.
387

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
388
389
    ``extra_context``
        An dictionary of variables to add to the template context.
390

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
391
    **Template:**
392

393
    im/signup.html or ``template_name`` keyword argument.
394
    im/signup_complete.html or ``on_success`` keyword argument.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
395
    """
396
    extra_context = extra_context or {}
397
    if request.user.is_authenticated():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
398
        return HttpResponseRedirect(reverse('edit_profile'))
399

400
    provider = get_query(request).get('provider', 'local')
401
402
403
    if not auth_providers.get_provider(provider).is_available_for_create():
        raise PermissionDenied

404
405
406
407
408
409
    id = get_query(request).get('id')
    try:
        instance = AstakosUser.objects.get(id=id) if id else None
    except AstakosUser.DoesNotExist:
        instance = None

410
411
    third_party_token = request.REQUEST.get('third_party_token', None)

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
412
    try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
413
414
        if not backend:
            backend = get_backend(request)
415
        form = backend.get_signup_form(provider, instance)
416
    except Exception, e:
417
        form = SimpleBackend(request).get_signup_form(provider)
418
        messages.error(request, e)
419
420
    if request.method == 'POST':
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
421
            user = form.save(commit=False)
422
423
424
            try:
                result = backend.handle_activation(user)
                status = messages.SUCCESS
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
425
                message = result.message
426
427
428

                form.store_user(user, request)

429
430
431
432
                if 'additional_email' in form.cleaned_data:
                    additional_email = form.cleaned_data['additional_email']
                    if additional_email != user.email:
                        user.additionalmail_set.create(email=additional_email)
433
434
435
436
                        msg = 'Additional email: %s saved for user %s.' % (
                            additional_email,
                            user.email
                        )
437
                        logger._log(LOGGING_LEVEL, msg, [])
438
439
                if user and user.is_active:
                    next = request.POST.get('next', '')
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
440
                    response = prepare_response(request, user, next=next)
441
                    transaction.commit()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
442
                    return response
443
                messages.add_message(request, status, message)
444
                transaction.commit()
445
446
447
448
449
450
451
                return render_response(
                    on_success,
                    context_instance=get_context(
                        request,
                        extra_context
                    )
                )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
452
            except SendMailError, e:
453
                logger.exception(e)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
454
455
                status = messages.ERROR
                message = e.message
456
                messages.error(request, message)
457
                transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
458
            except BaseException, e:
459
                logger.exception(e)
Olga Brani's avatar
Olga Brani committed
460
                message = _(astakos_messages.GENERIC_ERROR)
461
                messages.error(request, message)
462
                logger.exception(e)
463
                transaction.rollback()
464
    return render_response(template_name,
465
                           signup_form=form,
466
                           third_party_token=third_party_token,
467
                           provider=provider,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
468
                           context_instance=get_context(request, extra_context))
469

470

471
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
472
@login_required
473
@signed_terms_required
474
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
475
476
    """
    Allows a user to send feedback.
477

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
478
479
    In case of GET request renders a form for providing the feedback information.
    In case of POST sends an email to support team.
480

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
481
    If the user isn't logged in, redirects to settings.LOGIN_URL.
482

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
483
    **Arguments**
484

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
485
486
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
487
        this will default to ``im/feedback.html``.
488

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
489
490
    ``extra_context``
        An dictionary of variables to add to the template context.
491

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
492
    **Template:**
493

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
494
    im/signup.html or ``template_name`` keyword argument.
495

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
496
    **Settings:**
497

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
498
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
499
    """
500
    extra_context = extra_context or {}
501
    if request.method == 'GET':
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
502
503
504
505
        form = FeedbackForm()
    if request.method == 'POST':
        if not request.user:
            return HttpResponse('Unauthorized', status=401)
506

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
507
508
        form = FeedbackForm(request.POST)
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
509
            msg = form.cleaned_data['feedback_msg']
510
            data = form.cleaned_data['feedback_data']
511
            try:
512
513
                send_feedback(msg, data, request.user, email_template_name)
            except SendMailError, e:
514
                messages.error(request, message)
515
            else:
Olga Brani's avatar
Olga Brani committed
516
                message = _(astakos_messages.FEEDBACK_SENT)
517
                messages.success(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
518
    return render_response(template_name,
519
520
521
                           feedback_form=form,
                           context_instance=get_context(request, extra_context))

522

523
@require_http_methods(["GET"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
524
@signed_terms_required
525
def logout(request, template='registration/logged_out.html', extra_context=None):
526
    """
527
    Wraps `django.contrib.auth.logout`.
528
    """
529
    extra_context = extra_context or {}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
530
    response = HttpResponse()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
531
532
533
    if request.user.is_authenticated():
        email = request.user.email
        auth_logout(request)
534
535
536
537
    next = restrict_next(
        request.GET.get('next'),
        domain=COOKIE_DOMAIN
    )
538
539
540
    if next:
        response['Location'] = next
        response.status_code = 302
541
542
543
    elif LOGOUT_NEXT:
        response['Location'] = LOGOUT_NEXT
        response.status_code = 301
544
    else:
545
        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
546
547
        context = get_context(request, extra_context)
        response.write(render_to_string(template, context_instance=context))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
548
    return response
549

550

551
@require_http_methods(["GET", "POST"])
552
@transaction.commit_manually
553
554
def activate(request, greeting_email_template_name='im/welcome_email.txt',
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
555
    """
556
557
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
    and renews the user token.
558

559
560
    The view uses commit_manually decorator in order to ensure the user state will be updated
    only if the email will be send successfully.
561
562
563
564
565
566
    """
    token = request.GET.get('auth')
    next = request.GET.get('next')
    try:
        user = AstakosUser.objects.get(auth_token=token)
    except AstakosUser.DoesNotExist:
Olga Brani's avatar
Olga Brani committed
567
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
568

569
    if user.is_active:
Olga Brani's avatar
Olga Brani committed
570
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
571
        messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
572
        return index(request)
573

574
    try:
575
576
577
578
579
580
581
582
583
584
585
        activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
        response = prepare_response(request, user, next, renew=True)
        transaction.commit()
        return response
    except SendMailError, e:
        message = e.message
        messages.add_message(request, messages.ERROR, message)
        transaction.rollback()
        return index(request)
    except BaseException, e:
        status = messages.ERROR
586
        message = _(astakos_messages.GENERIC_ERROR)
587
588
589
590
        messages.add_message(request, messages.ERROR, message)
        logger.exception(e)
        transaction.rollback()
        return index(request)
591

592

593
@require_http_methods(["GET", "POST"])
594
595
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
    extra_context = extra_context or {}
596
597
598
599
600
601
602
603
604
    term = None
    terms = None
    if not term_id:
        try:
            term = ApprovalTerms.objects.order_by('-id')[0]
        except IndexError:
            pass
    else:
        try:
605
606
            term = ApprovalTerms.objects.get(id=term_id)
        except ApprovalTerms.DoesNotExist, e:
607
            pass
608

609
    if not term:
Olga Brani's avatar
Olga Brani committed
610
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
611
        return HttpResponseRedirect(reverse('index'))
612
613
    f = open(term.location, 'r')
    terms = f.read()
614

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

639

640
641
642
@require_http_methods(["GET", "POST"])
@login_required
@signed_terms_required
643
644
645
646
647
@transaction.commit_manually
def change_email(request, activation_key=None,
                 email_template_name='registration/email_change_email.txt',
                 form_template_name='registration/email_change_form.html',
                 confirm_template_name='registration/email_change_done.html',
648
649
                 extra_context=None):
    extra_context = extra_context or {}
650
651
652
653
    if activation_key:
        try:
            user = EmailChange.objects.change_email(activation_key)
            if request.user.is_authenticated() and request.user == user:
Olga Brani's avatar
Olga Brani committed
654
                msg = _(astakos_messages.EMAIL_CHANGED)
655
                messages.success(request, msg)
656
657
658
659
660
                auth_logout(request)
                response = prepare_response(request, user)
                transaction.commit()
                return response
        except ValueError, e:
661
            messages.error(request, e)
662
        return render_response(confirm_template_name,
663
664
665
666
667
                               modified_user=user if 'user' in locals(
                               ) else None,
                               context_instance=get_context(request,
                                                            extra_context))

668
669
    if not request.user.is_authenticated():
        path = quote(request.get_full_path())
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
670
        url = request.build_absolute_uri(reverse('index'))
671
672
673
674
675
676
677
        return HttpResponseRedirect(url + '?next=' + path)
    form = EmailChangeForm(request.POST or None)
    if request.method == 'POST' and form.is_valid():
        try:
            ec = form.save(email_template_name, request)
        except SendMailError, e:
            msg = e
678
            messages.error(request, msg)
679
680
            transaction.rollback()
        except IntegrityError, e:
Olga Brani's avatar
Olga Brani committed
681
            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
682
            messages.error(request, msg)
683
        else:
Olga Brani's avatar
Olga Brani committed
684
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
685
            messages.success(request, msg)
686
            transaction.commit()
687
688
689
690
691
    return render_response(
        form_template_name,
        form=form,
        context_instance=get_context(request, extra_context)
    )
692
693
694


def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
695
696
697
698

    if settings.MODERATION_ENABLED:
        raise PermissionDenied

699
700
701
702
    extra_context = extra_context or {}
    try:
        u = AstakosUser.objects.get(id=user_id)
    except AstakosUser.DoesNotExist:
703
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
704
705
706
    else:
        try:
            send_activation_func(u)
707
            msg = _(astakos_messages.ACTIVATION_SENT)
708
709
710
711
712
            messages.success(request, msg)
        except SendMailError, e:
            messages.error(request, e)
    return render_response(
        template_name,
713
        login_form = LoginForm(request=request),
714
715
716
717
718
        context_instance = get_context(
            request,
            extra_context
        )
    )
719

Olga Brani's avatar
Olga Brani committed
720
class ResourcePresentation():
721

Olga Brani's avatar
Olga Brani committed
722
723
    def __init__(self, data):
        self.data = data
724

Olga Brani's avatar
Olga Brani committed
725
726
727
728
729
730
    def update_from_result(self, result):
        if result.is_success:
            for r in result.data:
                rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
                if not rname in self.data['resources']:
                    self.data['resources'][rname] = {}
731

Olga Brani's avatar
Olga Brani committed
732
733
734
735
736
                self.data['resources'][rname].update(r)
                self.data['resources'][rname]['id'] = rname
                group = r.get('group')
                if not group in self.data['groups']:
                    self.data['groups'][group] = {}
737

Olga Brani's avatar
Olga Brani committed
738
                self.data['groups'][r.get('group')].update({'name': r.get('group')})
739

Olga Brani's avatar
Olga Brani committed
740
741
742
743
744
    def test(self, quota_dict):
        for k, v in quota_dict.iteritems():
            rname = k
            value = v
            if not rname in self.data['resources']:
745
                self.data['resources'][rname] = {}
746
747


Olga Brani's avatar
Olga Brani committed
748
            self.data['resources'][rname]['value'] = value
749
750


Olga Brani's avatar
Olga Brani committed
751
752
753
754
755
756
    def update_from_result_report(self, result):
        if result.is_success:
            for r in result.data:
                rname = r.get('name')
                if not rname in self.data['resources']:
                    self.data['resources'][rname] = {}
757

Olga Brani's avatar
Olga Brani committed
758
759
760
761
762
                self.data['resources'][rname].update(r)
                self.data['resources'][rname]['id'] = rname
                group = r.get('group')
                if not group in self.data['groups']:
                    self.data['groups'][group] = {}
763

Olga Brani's avatar
Olga Brani committed
764
                self.data['groups'][r.get('group')].update({'name': r.get('group')})
765

Olga Brani's avatar
Olga Brani committed
766
767
    def get_group_resources(self, group):
        return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
768

Olga Brani's avatar
Olga Brani committed
769
770
771
    def get_groups_resources(self):
        for g in self.data['groups']:
            yield g, self.get_group_resources(g)
772

Olga Brani's avatar
Olga Brani committed
773
774
775
776
777
778
    def get_quota(self, group_quotas):
        for r, v in group_quotas.iteritems():
            rname = str(r)
            quota = self.data['resources'].get(rname)
            quota['value'] = v
            yield quota
779
780


Olga Brani's avatar
Olga Brani committed
781
782
783
784
785
    def get_policies(self, policies_data):
        for policy in policies_data:
            rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
            policy.update(self.data['resources'].get(rname))
            yield policy
786

Olga Brani's avatar
Olga Brani committed
787
788
    def __repr__(self):
        return self.data.__repr__()
789

Olga Brani's avatar
Olga Brani committed
790
791
    def __iter__(self, *args, **kwargs):
        return self.data.__iter__(*args, **kwargs)
792

Olga Brani's avatar
Olga Brani committed
793
794
    def __getitem__(self, *args, **kwargs):
        return self.data.__getitem__(*args, **kwargs)
795