views.py 54.3 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
47
48
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.db import transaction
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 _
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
56
57
58
from django.views.generic.create_update import (
    create_object, delete_object, get_model_and_form_class
)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
59
from django.views.generic.list_detail import object_list, object_detail
60
from django.core.xheaders import populate_xheaders
Olga Brani's avatar
Olga Brani committed
61
from django.core.exceptions import ValidationError, PermissionDenied
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 django.db.models import Q
65

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

Olga Brani's avatar
Olga Brani committed
102
import astakos.im.messages as astakos_messages
Olga Brani's avatar
Olga Brani committed
103
104
from astakos.im import settings
from astakos.im import auth_providers
Olga Brani's avatar
Olga Brani committed
105

106
107
logger = logging.getLogger(__name__)

108
callpoint = AstakosCallpoint()
Olga Brani's avatar
Olga Brani committed
109

110
def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
111
112
113
114
115
    """
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
    keyword argument and returns an ``django.http.HttpResponse`` with the
    specified ``status``.
    """
116
    if tab is None:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
117
        tab = template.partition('_')[0].partition('.html')[0]
118
    kwargs.setdefault('tab', tab)
119
    html = template_loader.render_to_string(
120
        template, kwargs, context_instance=context_instance)
121
122
    response = HttpResponse(html, status=status)
    return response
123

Olga Brani's avatar
Olga Brani committed
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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

144
145
146

def requires_anonymous(func):
    """
147
    Decorator checkes whether the request.user is not Anonymous and in that case
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
148
    redirects to `logout`.
149
150
151
152
153
    """
    @wraps(func)
    def wrapper(request, *args):
        if not request.user.is_anonymous():
            next = urlencode({'next': request.build_absolute_uri()})
154
155
            logout_uri = reverse(logout) + '?' + next
            return HttpResponseRedirect(logout_uri)
156
157
158
        return func(request, *args)
    return wrapper

159

160
161
162
163
164
165
166
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):
167
        if request.user.is_authenticated() and not request.user.signed_terms:
168
            params = urlencode({'next': request.build_absolute_uri(),
169
                                'show_form': ''})
170
171
172
173
174
            terms_uri = reverse('latest_terms') + '?' + params
            return HttpResponseRedirect(terms_uri)
        return func(request, *args, **kwargs)
    return wrapper

175

Olga Brani's avatar
Olga Brani committed
176
@require_http_methods(["GET", "POST"])
177
@signed_terms_required
178
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
179
    """
180
    If there is logged on user renders the profile page otherwise renders login page.
181

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
182
    **Arguments**
183

184
185
    ``login_template_name``
        A custom login template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
186
        this will default to ``im/login.html``.
187

188
189
    ``profile_template_name``
        A custom profile template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
190
        this will default to ``im/profile.html``.
191

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
192
193
    ``extra_context``
        An dictionary of variables to add to the template context.
194

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
195
    **Template:**
196

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
199
    """
200
    extra_context = extra_context or {}
201
202
    template_name = login_template_name
    if request.user.is_authenticated():
203
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
Olga Brani's avatar
Olga Brani committed
204

205
206
207
208
209
    return render_response(
        template_name,
        login_form = LoginForm(request=request),
        context_instance = get_context(request, extra_context)
    )
210

211

Olga Brani's avatar
Olga Brani committed
212
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
213
@login_required
214
@signed_terms_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
215
@transaction.commit_manually
216
def invite(request, template_name='im/invitations.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
217
218
    """
    Allows a user to invite somebody else.
219

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
220
221
222
    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.
223

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
224
225
    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.
226

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
229
    **Arguments**
230

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
231
232
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
233
        this will default to ``im/invitations.html``.
234

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
235
236
    ``extra_context``
        An dictionary of variables to add to the template context.
237

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
238
    **Template:**
239

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
242
    **Settings:**
243

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
244
    The view expectes the following settings are defined:
245

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
246
247
    * LOGIN_URL: login uri
    """
248
    extra_context = extra_context or {}
249
250
    status = None
    message = None
251
    form = InvitationForm()
252

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
253
    inviter = request.user
254
    if request.method == 'POST':
255
        form = InvitationForm(request.POST)
256
        if inviter.invitations > 0:
257
258
            if form.is_valid():
                try:
Olga Brani's avatar
Olga Brani committed
259
260
261
                    email = form.cleaned_data.get('username')
                    realname = form.cleaned_data.get('realname')
                    inviter.invite(email, realname)
Olga Brani's avatar
Olga Brani committed
262
                    message = _(astakos_messages.INVITATION_SENT) % locals()
263
                    messages.success(request, message)
264
265
                except SendMailError, e:
                    message = e.message
266
                    messages.error(request, message)
267
                    transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
268
                except BaseException, e:
Olga Brani's avatar
Olga Brani committed
269
                    message = _(astakos_messages.GENERIC_ERROR)
270
                    messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
271
272
273
274
                    logger.exception(e)
                    transaction.rollback()
                else:
                    transaction.commit()
275
        else:
Olga Brani's avatar
Olga Brani committed
276
            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
277
            messages.error(request, message)
278

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
279
    sent = [{'email': inv.username,
280
281
             'realname': inv.realname,
             'is_consumed': inv.is_consumed}
282
            for inv in request.user.invitations_sent.all()]
283
    kwargs = {'inviter': inviter,
284
              'sent': sent}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
285
286
    context = get_context(request, extra_context, **kwargs)
    return render_response(template_name,
287
288
289
                           invitation_form=form,
                           context_instance=context)

290

Olga Brani's avatar
Olga Brani committed
291
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
292
@login_required
293
@signed_terms_required
294
def edit_profile(request, template_name='im/profile.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
295
296
    """
    Allows a user to edit his/her profile.
297

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
304
    **Arguments**
305

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
306
307
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
308
        this will default to ``im/profile.html``.
309

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
310
311
    ``extra_context``
        An dictionary of variables to add to the template context.
312

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
313
    **Template:**
314

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
317
    **Settings:**
318

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
319
    The view expectes the following settings are defined:
320

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
321
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
322
    """
323
    extra_context = extra_context or {}
324
325
326
327
    form = ProfileForm(
        instance=request.user,
        session_key=request.session.session_key
    )
328
    extra_context['next'] = request.GET.get('next')
329
    if request.method == 'POST':
330
331
332
333
334
        form = ProfileForm(
            request.POST,
            instance=request.user,
            session_key=request.session.session_key
        )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
335
        if form.is_valid():
336
            try:
337
338
                prev_token = request.user.auth_token
                user = form.save()
339
340
341
342
                form = ProfileForm(
                    instance=user,
                    session_key=request.session.session_key
                )
343
344
345
346
                next = restrict_next(
                    request.POST.get('next'),
                    domain=COOKIE_DOMAIN
                )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
347
348
                if next:
                    return redirect(next)
Olga Brani's avatar
Olga Brani committed
349
                msg = _(astakos_messages.PROFILE_UPDATED)
350
                messages.success(request, msg)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
351
            except ValueError, ve:
352
                messages.success(request, ve)
353
    elif request.method == "GET":
Olga Brani's avatar
Olga Brani committed
354
355
356
357
358
359
360
361
362
        request.user.is_verified = True
        request.user.save()

    # 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
363
    return render_response(template_name,
364
                           profile_form = form,
Olga Brani's avatar
Olga Brani committed
365
366
                           user_providers = user_providers,
                           user_available_providers = user_available_providers,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
367
                           context_instance = get_context(request,
368
                                                          extra_context))
369

370

371
@transaction.commit_manually
Olga Brani's avatar
Olga Brani committed
372
@require_http_methods(["GET", "POST"])
373
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
374
375
    """
    Allows a user to create a local account.
376

377
    In case of GET request renders a form for entering the user information.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
378
    In case of POST handles the signup.
379

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
380
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
381
382
383
    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);
384

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

388
    On unsuccessful creation, renders ``template_name`` with an error message.
389

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
390
    **Arguments**
391

392
393
    ``template_name``
        A custom template to render. This is optional;
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
394
        if not specified, this will default to ``im/signup.html``.
395

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
400
401
    ``extra_context``
        An dictionary of variables to add to the template context.
402

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
403
    **Template:**
404

405
    im/signup.html or ``template_name`` keyword argument.
406
    im/signup_complete.html or ``on_success`` keyword argument.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
407
    """
408
    extra_context = extra_context or {}
409
    if request.user.is_authenticated():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
410
        return HttpResponseRedirect(reverse('edit_profile'))
411

412
    provider = get_query(request).get('provider', 'local')
Olga Brani's avatar
Olga Brani committed
413
414
415
    if not auth_providers.get_provider(provider).is_available_for_create():
        raise PermissionDenied

416
417
418
419
420
421
    id = get_query(request).get('id')
    try:
        instance = AstakosUser.objects.get(id=id) if id else None
    except AstakosUser.DoesNotExist:
        instance = None

422
423
    third_party_token = request.REQUEST.get('third_party_token', None)

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
424
    try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
425
426
        if not backend:
            backend = get_backend(request)
427
        form = backend.get_signup_form(provider, instance)
428
    except Exception, e:
429
        form = SimpleBackend(request).get_signup_form(provider)
430
        messages.error(request, e)
431
432
    if request.method == 'POST':
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
433
            user = form.save(commit=False)
434
435
436
            try:
                result = backend.handle_activation(user)
                status = messages.SUCCESS
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
437
                message = result.message
Olga Brani's avatar
Olga Brani committed
438
439
440

                form.store_user(user, request)

441
442
443
444
                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)
445
                        msg = 'Additional email: %s saved for user %s.' % (
446
447
448
                            additional_email,
                            user.email
                        )
449
                        logger._log(LOGGING_LEVEL, msg, [])
450
451
                if user and user.is_active:
                    next = request.POST.get('next', '')
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
452
                    response = prepare_response(request, user, next=next)
453
                    transaction.commit()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
454
                    return response
455
                messages.add_message(request, status, message)
Olga Brani's avatar
Olga Brani committed
456
                transaction.commit()
457
458
459
460
461
462
463
                return render_response(
                    on_success,
                    context_instance=get_context(
                        request,
                        extra_context
                    )
                )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
464
            except SendMailError, e:
465
                logger.exception(e)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
466
467
                status = messages.ERROR
                message = e.message
468
                messages.error(request, message)
469
                transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
470
            except BaseException, e:
471
                logger.exception(e)
Olga Brani's avatar
Olga Brani committed
472
                message = _(astakos_messages.GENERIC_ERROR)
473
                messages.error(request, message)
474
                logger.exception(e)
475
                transaction.rollback()
476
    return render_response(template_name,
477
                           signup_form=form,
478
                           third_party_token=third_party_token,
479
                           provider=provider,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
480
                           context_instance=get_context(request, extra_context))
481

482

Olga Brani's avatar
Olga Brani committed
483
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
484
@login_required
485
@signed_terms_required
486
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
487
488
    """
    Allows a user to send feedback.
489

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
495
    **Arguments**
496

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
497
498
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
499
        this will default to ``im/feedback.html``.
500

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
501
502
    ``extra_context``
        An dictionary of variables to add to the template context.
503

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
504
    **Template:**
505

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
508
    **Settings:**
509

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
510
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
511
    """
512
    extra_context = extra_context or {}
513
    if request.method == 'GET':
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
514
515
516
517
        form = FeedbackForm()
    if request.method == 'POST':
        if not request.user:
            return HttpResponse('Unauthorized', status=401)
518

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
519
520
        form = FeedbackForm(request.POST)
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
521
            msg = form.cleaned_data['feedback_msg']
522
            data = form.cleaned_data['feedback_data']
523
            try:
524
525
                send_feedback(msg, data, request.user, email_template_name)
            except SendMailError, e:
526
                messages.error(request, message)
527
            else:
Olga Brani's avatar
Olga Brani committed
528
                message = _(astakos_messages.FEEDBACK_SENT)
529
                messages.success(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
530
    return render_response(template_name,
531
532
533
                           feedback_form=form,
                           context_instance=get_context(request, extra_context))

534

535
@require_http_methods(["GET"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
536
@signed_terms_required
537
def logout(request, template='registration/logged_out.html', extra_context=None):
538
    """
539
    Wraps `django.contrib.auth.logout`.
540
    """
541
    extra_context = extra_context or {}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
542
    response = HttpResponse()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
543
544
545
    if request.user.is_authenticated():
        email = request.user.email
        auth_logout(request)
546
547
548
549
    next = restrict_next(
        request.GET.get('next'),
        domain=COOKIE_DOMAIN
    )
550
551
552
    if next:
        response['Location'] = next
        response.status_code = 302
553
554
555
    elif LOGOUT_NEXT:
        response['Location'] = LOGOUT_NEXT
        response.status_code = 301
556
    else:
557
        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
558
559
        context = get_context(request, extra_context)
        response.write(render_to_string(template, context_instance=context))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
560
    return response
561

562

Olga Brani's avatar
Olga Brani committed
563
@require_http_methods(["GET", "POST"])
564
@transaction.commit_manually
565
566
def activate(request, greeting_email_template_name='im/welcome_email.txt',
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
567
    """
568
569
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
    and renews the user token.
570

571
572
    The view uses commit_manually decorator in order to ensure the user state will be updated
    only if the email will be send successfully.
573
574
575
576
577
578
    """
    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
579
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
580

581
    if user.is_active:
Olga Brani's avatar
Olga Brani committed
582
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
583
        messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
584
        return index(request)
585

586
    try:
587
588
589
590
591
592
593
594
595
596
597
        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
598
        message = _(astakos_messages.GENERIC_ERROR)
599
600
601
602
        messages.add_message(request, messages.ERROR, message)
        logger.exception(e)
        transaction.rollback()
        return index(request)
603

604

Olga Brani's avatar
Olga Brani committed
605
@require_http_methods(["GET", "POST"])
606
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
607
    extra_context = extra_context or {}
608
609
610
611
612
613
614
615
616
    term = None
    terms = None
    if not term_id:
        try:
            term = ApprovalTerms.objects.order_by('-id')[0]
        except IndexError:
            pass
    else:
        try:
617
618
            term = ApprovalTerms.objects.get(id=term_id)
        except ApprovalTerms.DoesNotExist, e:
619
            pass
620

621
    if not term:
Olga Brani's avatar
Olga Brani committed
622
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
623
        return HttpResponseRedirect(reverse('index'))
624
625
    f = open(term.location, 'r')
    terms = f.read()
626

627
    if request.method == 'POST':
628
629
630
631
        next = restrict_next(
            request.POST.get('next'),
            domain=COOKIE_DOMAIN
        )
632
        if not next:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
633
            next = reverse('index')
634
635
636
        form = SignApprovalTermsForm(request.POST, instance=request.user)
        if not form.is_valid():
            return render_response(template_name,
637
638
639
                                   terms=terms,
                                   approval_terms_form=form,
                                   context_instance=get_context(request, extra_context))
640
641
642
        user = form.save()
        return HttpResponseRedirect(next)
    else:
643
        form = None
644
        if request.user.is_authenticated() and not request.user.signed_terms:
645
            form = SignApprovalTermsForm(instance=request.user)
646
        return render_response(template_name,
647
648
649
650
                               terms=terms,
                               approval_terms_form=form,
                               context_instance=get_context(request, extra_context))

651

Olga Brani's avatar
Olga Brani committed
652
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
653
@login_required
654
@signed_terms_required
655
656
657
658
659
@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',
660
                 extra_context=None):
661
    extra_context = extra_context or {}
662
663
664
665
    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
666
                msg = _(astakos_messages.EMAIL_CHANGED)
667
                messages.success(request, msg)
668
669
670
671
672
                auth_logout(request)
                response = prepare_response(request, user)
                transaction.commit()
                return response
        except ValueError, e:
673
            messages.error(request, e)
674
        return render_response(confirm_template_name,
675
676
677
678
679
                               modified_user=user if 'user' in locals(
                               ) else None,
                               context_instance=get_context(request,
                                                            extra_context))

680
681
    if not request.user.is_authenticated():
        path = quote(request.get_full_path())
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
682
        url = request.build_absolute_uri(reverse('index'))
683
684
685
686
687
688
689
        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
690
            messages.error(request, msg)
691
692
            transaction.rollback()
        except IntegrityError, e:
Olga Brani's avatar
Olga Brani committed
693
            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
694
            messages.error(request, msg)
695
        else:
Olga Brani's avatar
Olga Brani committed
696
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
697
            messages.success(request, msg)
698
            transaction.commit()
699
700
701
702
703
    return render_response(
        form_template_name,
        form=form,
        context_instance=get_context(request, extra_context)
    )
704
705
706


def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
Olga Brani's avatar
Olga Brani committed
707
708
709
710

    if settings.MODERATION_ENABLED:
        raise PermissionDenied

711
712
713
714
    extra_context = extra_context or {}
    try:
        u = AstakosUser.objects.get(id=user_id)
    except AstakosUser.DoesNotExist:
715
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
716
717
718
    else:
        try:
            send_activation_func(u)
719
            msg = _(astakos_messages.ACTIVATION_SENT)
720
721
722
723
724
            messages.success(request, msg)
        except SendMailError, e:
            messages.error(request, e)
    return render_response(
        template_name,
725
        login_form = LoginForm(request=request),
726
727
728
729
730
        context_instance = get_context(
            request,
            extra_context
        )
    )
731

Olga Brani's avatar
Olga Brani committed
732
class ResourcePresentation():
Olga Brani's avatar
Olga Brani committed
733

Olga Brani's avatar
Olga Brani committed
734
735
    def __init__(self, data):
        self.data = data
Olga Brani's avatar
Olga Brani committed
736

Olga Brani's avatar
Olga Brani committed
737
738
739
740
741
742
    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] = {}
Olga Brani's avatar
Olga Brani committed
743

Olga Brani's avatar
Olga Brani committed
744
745
746
747
748
                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] = {}
Olga Brani's avatar
Olga Brani committed
749

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

Olga Brani's avatar
Olga Brani committed
752
753
754
755
756
    def test(self, quota_dict):
        for k, v in quota_dict.iteritems():
            rname = k
            value = v
            if not rname in self.data['resources']:
757
                self.data['resources'][rname] = {}
Olga Brani's avatar
Olga Brani committed
758
759


Olga Brani's avatar
Olga Brani committed
760
            self.data['resources'][rname]['value'] = value
Olga Brani's avatar
Olga Brani committed
761
762


Olga Brani's avatar
Olga Brani committed
763
764
765
766
767
768
    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] = {}
Olga Brani's avatar
Olga Brani committed
769

Olga Brani's avatar
Olga Brani committed
770
771
772
773
774
                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] = {}
Olga Brani's avatar
Olga Brani committed
775

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

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

Olga Brani's avatar
Olga Brani committed
781
782
783
    def get_groups_resources(self):
        for g in self.data['groups']:
            yield g, self.get_group_resources(g)
Olga Brani's avatar
Olga Brani committed
784

Olga Brani's avatar
Olga Brani committed
785
786
787
788
789
790
    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
Olga Brani's avatar
Olga Brani committed
791
792


Olga Brani's avatar
Olga Brani committed
793
794
795
796
797
    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
Olga Brani's avatar
Olga Brani committed
798

Olga Brani's avatar
Olga Brani committed
799
800
    def __repr__(self):
        return self.data.__repr__()
Olga Brani's avatar
Olga Brani committed
801

Olga Brani's avatar
Olga Brani committed
802
803
    def __iter__(self, *args, **kwargs):
        return self.data.__iter__(*args, **kwargs)
Olga Brani's avatar
Olga Brani committed
804

Olga Brani's avatar
Olga Brani committed
805
806
    def __getitem__(self, *args, **kwargs):
        return self.data.__getitem__(*args, **kwargs)
Olga Brani's avatar
Olga Brani committed
807

Olga Brani's avatar
Olga Brani committed
808
809
    def get(self, *args, **kwargs):
        return self.data.get(*args, **kwargs)
Olga Brani's avatar
Olga Brani committed
810
811


Olga Brani's avatar
Olga Brani committed
812

813
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
814
@signed_terms_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
815