views.py 48.4 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, timedelta
43
from collections import defaultdict
44

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

Olga Brani's avatar
Olga Brani committed
66
67
68
69
70
from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
                               Resource, EmailChange, GroupKind, Membership,
                               AstakosGroupQuota, RESOURCE_SEPARATOR)
from django.views.decorators.http import require_http_methods

71
from astakos.im.activation_backends import get_backend, SimpleBackend
72
from astakos.im.util import get_context, prepare_response, set_cookie, get_query
73
74
75
76
from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
                              FeedbackForm, SignApprovalTermsForm,
                              ExtendedPasswordChangeForm, EmailChangeForm,
                              AstakosGroupCreationForm, AstakosGroupSearchForm,
77
                              AstakosGroupUpdateForm, AddGroupMembersForm,
78
                              AstakosGroupSortForm, MembersSortForm,
79
80
                              TimelineForm, PickResourceForm,
                              AstakosGroupCreationSummaryForm)
81
from astakos.im.functions import (send_feedback, SendMailError,
Olga Brani's avatar
Olga Brani committed
82
                                  logout as auth_logout,
83
84
                                  activate as activate_func,
                                  switch_account_to_shibboleth,
Olga Brani's avatar
Olga Brani committed
85
                                  send_group_creation_notification,
86
                                  SendNotificationError)
87
from astakos.im.endpoints.quotaholder import timeline_charge
Olga Brani's avatar
Olga Brani committed
88
89
from astakos.im.settings import (COOKIE_NAME, COOKIE_DOMAIN, LOGOUT_NEXT,
                                 LOGGING_LEVEL, PAGINATE_BY)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
90
from astakos.im.tasks import request_billing
91
from astakos.im.api.callpoint import AstakosCallpoint
92

93
94
import astakos.im.messages as astakos_messages

95
96
logger = logging.getLogger(__name__)

97

98
99
100
DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
                                     'https://', '')"""

101
callpoint = AstakosCallpoint()
Olga Brani's avatar
Olga Brani committed
102

103
104
def render_response(template, tab=None, status=200, reset_cookie=False,
                    context_instance=None, **kwargs):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
105
106
107
108
109
    """
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
    keyword argument and returns an ``django.http.HttpResponse`` with the
    specified ``status``.
    """
110
    if tab is None:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
111
        tab = template.partition('_')[0].partition('.html')[0]
112
    kwargs.setdefault('tab', tab)
113
    html = template_loader.render_to_string(
114
        template, kwargs, context_instance=context_instance)
115
116
117
118
    response = HttpResponse(html, status=status)
    if reset_cookie:
        set_cookie(response, context_instance['request'].user)
    return response
119

120
121
122

def requires_anonymous(func):
    """
123
    Decorator checkes whether the request.user is not Anonymous and in that case
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
124
    redirects to `logout`.
125
126
127
128
129
    """
    @wraps(func)
    def wrapper(request, *args):
        if not request.user.is_anonymous():
            next = urlencode({'next': request.build_absolute_uri()})
130
131
            logout_uri = reverse(logout) + '?' + next
            return HttpResponseRedirect(logout_uri)
132
133
134
        return func(request, *args)
    return wrapper

135

136
137
138
139
140
141
142
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):
143
        if request.user.is_authenticated() and not request.user.signed_terms:
144
            params = urlencode({'next': request.build_absolute_uri(),
145
                                'show_form': ''})
146
147
148
149
150
            terms_uri = reverse('latest_terms') + '?' + params
            return HttpResponseRedirect(terms_uri)
        return func(request, *args, **kwargs)
    return wrapper

151

Olga Brani's avatar
Olga Brani committed
152
@require_http_methods(["GET", "POST"])
153
@signed_terms_required
154
def index(request, login_template_name='im/login.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
155
    """
156
    If there is logged on user renders the profile page otherwise renders login page.
157

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
158
    **Arguments**
159

160
161
    ``login_template_name``
        A custom login template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
162
        this will default to ``im/login.html``.
163

164
165
    ``profile_template_name``
        A custom profile template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
166
        this will default to ``im/profile.html``.
167

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
168
169
    ``extra_context``
        An dictionary of variables to add to the template context.
170

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
171
    **Template:**
172

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
175
    """
176
177
    template_name = login_template_name
    if request.user.is_authenticated():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
178
        return HttpResponseRedirect(reverse('edit_profile'))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
179
    return render_response(template_name,
180
181
182
                           login_form=LoginForm(request=request),
                           context_instance=get_context(request, extra_context))

183

Olga Brani's avatar
Olga Brani committed
184
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
185
@login_required
186
@signed_terms_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
187
@transaction.commit_manually
188
def invite(request, template_name='im/invitations.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
189
190
    """
    Allows a user to invite somebody else.
191

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
192
193
194
    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.
195

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
196
197
    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.
198

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
201
    **Arguments**
202

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
203
204
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
205
        this will default to ``im/invitations.html``.
206

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
207
208
    ``extra_context``
        An dictionary of variables to add to the template context.
209

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
210
    **Template:**
211

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
214
    **Settings:**
215

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
216
    The view expectes the following settings are defined:
217

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
218
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
219
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
220
    """
221
222
    status = None
    message = None
223
    form = InvitationForm()
224

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
225
    inviter = request.user
226
    if request.method == 'POST':
227
        form = InvitationForm(request.POST)
228
        if inviter.invitations > 0:
229
230
            if form.is_valid():
                try:
Olga Brani's avatar
Olga Brani committed
231
232
233
                    email = form.cleaned_data.get('username')
                    realname = form.cleaned_data.get('realname')
                    inviter.invite(email, realname)
234
                    message = _(astakos_messages.INVITATION_SENT) % locals()
235
                    messages.success(request, message)
236
237
                except SendMailError, e:
                    message = e.message
238
                    messages.error(request, message)
239
                    transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
240
                except BaseException, e:
241
                    message = _(astakos_messages.GENERIC_ERROR)
242
                    messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
243
244
245
246
                    logger.exception(e)
                    transaction.rollback()
                else:
                    transaction.commit()
247
        else:
248
            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
249
            messages.error(request, message)
250

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
251
    sent = [{'email': inv.username,
252
253
             'realname': inv.realname,
             'is_consumed': inv.is_consumed}
254
            for inv in request.user.invitations_sent.all()]
255
    kwargs = {'inviter': inviter,
256
              'sent': sent}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
257
258
    context = get_context(request, extra_context, **kwargs)
    return render_response(template_name,
259
260
261
                           invitation_form=form,
                           context_instance=context)

262

Olga Brani's avatar
Olga Brani committed
263
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
264
@login_required
265
@signed_terms_required
266
def edit_profile(request, template_name='im/profile.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
267
268
    """
    Allows a user to edit his/her profile.
269

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
276
    **Arguments**
277

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
278
279
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
280
        this will default to ``im/profile.html``.
281

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
282
283
    ``extra_context``
        An dictionary of variables to add to the template context.
284

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
285
    **Template:**
286

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
289
    **Settings:**
290

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
291
    The view expectes the following settings are defined:
292

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
293
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
294
    """
295
    extra_context = extra_context or {}
296
297
    form = ProfileForm(instance=request.user)
    extra_context['next'] = request.GET.get('next')
298
    reset_cookie = False
299
    if request.method == 'POST':
300
        form = ProfileForm(request.POST, instance=request.user)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
301
        if form.is_valid():
302
            try:
303
304
305
306
                prev_token = request.user.auth_token
                user = form.save()
                reset_cookie = user.auth_token != prev_token
                form = ProfileForm(instance=user)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
307
308
309
                next = request.POST.get('next')
                if next:
                    return redirect(next)
310
                msg = _(astakos_messages.PROFILE_UPDATED)
311
                messages.success(request, msg)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
312
            except ValueError, ve:
313
                messages.success(request, ve)
314
    elif request.method == "GET":
315
316
317
        if not request.user.is_verified:
            request.user.is_verified = True
            request.user.save()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
318
    return render_response(template_name,
319
320
321
322
323
                           reset_cookie=reset_cookie,
                           profile_form=form,
                           context_instance=get_context(request,
                                                        extra_context))

324

325
@transaction.commit_manually
Olga Brani's avatar
Olga Brani committed
326
@require_http_methods(["GET", "POST"])
327
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
328
329
    """
    Allows a user to create a local account.
330

331
    In case of GET request renders a form for entering the user information.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
332
    In case of POST handles the signup.
333

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
334
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
335
336
337
    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);
338

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

342
    On unsuccessful creation, renders ``template_name`` with an error message.
343

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
344
    **Arguments**
345

346
347
    ``template_name``
        A custom template to render. This is optional;
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
348
        if not specified, this will default to ``im/signup.html``.
349

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
354
355
    ``extra_context``
        An dictionary of variables to add to the template context.
356

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
357
    **Template:**
358

359
    im/signup.html or ``template_name`` keyword argument.
360
    im/signup_complete.html or ``on_success`` keyword argument.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
361
    """
362
    if request.user.is_authenticated():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
363
        return HttpResponseRedirect(reverse('edit_profile'))
364

365
    provider = get_query(request).get('provider', 'local')
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
366
    try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
367
368
        if not backend:
            backend = get_backend(request)
369
        form = backend.get_signup_form(provider)
370
    except Exception, e:
371
        form = SimpleBackend(request).get_signup_form(provider)
372
        messages.error(request, e)
373
374
    if request.method == 'POST':
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
375
            user = form.save(commit=False)
376
377
378
            try:
                result = backend.handle_activation(user)
                status = messages.SUCCESS
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
379
380
                message = result.message
                user.save()
381
382
383
384
                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)
385
386
                        msg = 'Additional email: %s saved for user %s.' % (
                            additional_email, user.email)
387
                        logger.log(LOGGING_LEVEL, msg)
388
389
                if user and user.is_active:
                    next = request.POST.get('next', '')
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
390
                    response = prepare_response(request, user, next=next)
391
                    transaction.commit()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
392
                    return response
393
                messages.add_message(request, status, message)
394
                transaction.commit()
395
396
                return render_response(on_success,
                                       context_instance=get_context(request, extra_context))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
397
398
            except SendMailError, e:
                message = e.message
399
                messages.error(request, message)
400
                transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
401
            except BaseException, e:
402
                message = _(astakos_messages.GENERIC_ERROR)
403
                messages.error(request, message)
404
                logger.exception(e)
405
                transaction.rollback()
406
    return render_response(template_name,
407
408
                           signup_form=form,
                           provider=provider,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
409
                           context_instance=get_context(request, extra_context))
410

411

Olga Brani's avatar
Olga Brani committed
412
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
413
@login_required
414
@signed_terms_required
415
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
416
417
    """
    Allows a user to send feedback.
418

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
424
    **Arguments**
425

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
426
427
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
428
        this will default to ``im/feedback.html``.
429

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
430
431
    ``extra_context``
        An dictionary of variables to add to the template context.
432

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
433
    **Template:**
434

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
437
    **Settings:**
438

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
439
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
440
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
441
    """
442
    if request.method == 'GET':
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
443
444
445
446
        form = FeedbackForm()
    if request.method == 'POST':
        if not request.user:
            return HttpResponse('Unauthorized', status=401)
447

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
448
449
        form = FeedbackForm(request.POST)
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
450
            msg = form.cleaned_data['feedback_msg']
451
            data = form.cleaned_data['feedback_data']
452
            try:
453
454
                send_feedback(msg, data, request.user, email_template_name)
            except SendMailError, e:
455
                messages.error(request, message)
456
            else:
457
                message = _(astakos_messages.FEEDBACK_SENT)
458
                messages.success(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
459
    return render_response(template_name,
460
461
462
                           feedback_form=form,
                           context_instance=get_context(request, extra_context))

463

Olga Brani's avatar
Olga Brani committed
464
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
465
@signed_terms_required
466
def logout(request, template='registration/logged_out.html', extra_context=None):
467
    """
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
468
    Wraps `django.contrib.auth.logout` and delete the cookie.
469
    """
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
470
    response = HttpResponse()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
471
472
473
474
475
    if request.user.is_authenticated():
        email = request.user.email
        auth_logout(request)
        response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
        msg = 'Cookie deleted for %s' % email
476
        logger.log(LOGGING_LEVEL, msg)
477
478
479
480
481
    next = request.GET.get('next')
    if next:
        response['Location'] = next
        response.status_code = 302
        return response
482
483
484
485
    elif LOGOUT_NEXT:
        response['Location'] = LOGOUT_NEXT
        response.status_code = 301
        return response
486
    messages.success(request, _(astakos_messages.LOGOUT_SUCCESS))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
487
    context = get_context(request, extra_context)
Olga Brani's avatar
Olga Brani committed
488
489
    response.write(
        template_loader.render_to_string(template, context_instance=context))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
490
    return response
491

492

Olga Brani's avatar
Olga Brani committed
493
@require_http_methods(["GET", "POST"])
494
@transaction.commit_manually
495
496
def activate(request, greeting_email_template_name='im/welcome_email.txt',
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
497
    """
498
499
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
    and renews the user token.
500

501
502
    The view uses commit_manually decorator in order to ensure the user state will be updated
    only if the email will be send successfully.
503
504
505
506
507
508
    """
    token = request.GET.get('auth')
    next = request.GET.get('next')
    try:
        user = AstakosUser.objects.get(auth_token=token)
    except AstakosUser.DoesNotExist:
509
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
510

511
    if user.is_active:
512
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
513
        messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
514
        return index(request)
515

516
    try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
517
        local_user = AstakosUser.objects.get(
518
            ~Q(id=user.id),
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
519
520
521
            email=user.email,
            is_active=True
        )
522
    except AstakosUser.DoesNotExist:
523
        try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
524
525
526
527
528
529
            activate_func(
                user,
                greeting_email_template_name,
                helpdesk_email_template_name,
                verify_email=True
            )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
530
531
532
533
534
            response = prepare_response(request, user, next, renew=True)
            transaction.commit()
            return response
        except SendMailError, e:
            message = e.message
535
            messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
536
537
538
            transaction.rollback()
            return index(request)
        except BaseException, e:
539
            message = _(astakos_messages.GENERIC_ERROR)
540
            messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
541
542
543
            logger.exception(e)
            transaction.rollback()
            return index(request)
544
    else:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
545
        try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
546
547
548
549
550
            user = switch_account_to_shibboleth(
                user,
                local_user,
                greeting_email_template_name
            )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
551
552
553
554
555
            response = prepare_response(request, user, next, renew=True)
            transaction.commit()
            return response
        except SendMailError, e:
            message = e.message
556
            messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
557
558
559
            transaction.rollback()
            return index(request)
        except BaseException, e:
560
            message = _(astakos_messages.GENERIC_ERROR)
561
            messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
562
563
564
            logger.exception(e)
            transaction.rollback()
            return index(request)
565

566

Olga Brani's avatar
Olga Brani committed
567
@require_http_methods(["GET", "POST"])
568
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
569
570
571
572
573
574
575
576
577
    term = None
    terms = None
    if not term_id:
        try:
            term = ApprovalTerms.objects.order_by('-id')[0]
        except IndexError:
            pass
    else:
        try:
578
579
            term = ApprovalTerms.objects.get(id=term_id)
        except ApprovalTerms.DoesNotExist, e:
580
            pass
581

582
    if not term:
583
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
584
        return HttpResponseRedirect(reverse('index'))
585
586
    f = open(term.location, 'r')
    terms = f.read()
587

588
589
590
    if request.method == 'POST':
        next = request.POST.get('next')
        if not next:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
591
            next = reverse('index')
592
593
594
        form = SignApprovalTermsForm(request.POST, instance=request.user)
        if not form.is_valid():
            return render_response(template_name,
595
596
597
                                   terms=terms,
                                   approval_terms_form=form,
                                   context_instance=get_context(request, extra_context))
598
599
600
        user = form.save()
        return HttpResponseRedirect(next)
    else:
601
        form = None
602
        if request.user.is_authenticated() and not request.user.signed_terms:
603
            form = SignApprovalTermsForm(instance=request.user)
604
        return render_response(template_name,
605
606
607
608
                               terms=terms,
                               approval_terms_form=form,
                               context_instance=get_context(request, extra_context))

609

Olga Brani's avatar
Olga Brani committed
610
@require_http_methods(["GET", "POST"])
611
612
@signed_terms_required
def change_password(request):
613
    return password_change(request,
614
615
616
                           post_change_redirect=reverse('edit_profile'),
                           password_change_form=ExtendedPasswordChangeForm)

617

Olga Brani's avatar
Olga Brani committed
618
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
619
620
@signed_terms_required
@login_required
621
622
623
624
625
@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',
626
                 extra_context=None):
627
628
629
630
    if activation_key:
        try:
            user = EmailChange.objects.change_email(activation_key)
            if request.user.is_authenticated() and request.user == user:
631
                msg = _(astakos_messages.EMAIL_CHANGED)
632
                messages.success(request, msg)
633
634
635
636
637
                auth_logout(request)
                response = prepare_response(request, user)
                transaction.commit()
                return response
        except ValueError, e:
638
            messages.error(request, e)
639
        return render_response(confirm_template_name,
640
641
642
643
644
                               modified_user=user if 'user' in locals(
                               ) else None,
                               context_instance=get_context(request,
                                                            extra_context))

645
646
    if not request.user.is_authenticated():
        path = quote(request.get_full_path())
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
647
        url = request.build_absolute_uri(reverse('index'))
648
649
650
651
652
653
654
        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
655
            messages.error(request, msg)
656
657
            transaction.rollback()
        except IntegrityError, e:
658
            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
659
            messages.error(request, msg)
660
        else:
661
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
662
            messages.success(request, msg)
663
664
            transaction.commit()
    return render_response(form_template_name,
665
666
667
668
                           form=form,
                           context_instance=get_context(request,
                                                        extra_context))

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
669

Olga Brani's avatar
Olga Brani committed
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696

resource_presentation = {
       'compute': {
            'help_text':'group compute help text',
            'is_abbreviation':False,
            'report_desc':''
        },
        'storage': {
            'help_text':'group storage help text',
            'is_abbreviation':False,
            'report_desc':''
        },
        'pithos+.diskspace': {
            'help_text':'resource pithos+.diskspace help text',
            'is_abbreviation':False,
            'report_desc':'Diskspace used'
        },
        'cyclades.vm': {
            'help_text':'resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text',
            'is_abbreviation':True,
            'report_desc':'Number of Virtual Machines'
        },
        'cyclades.disksize': {
            'help_text':'resource cyclades.disksize help text',
            'is_abbreviation':False,
            'report_desc':'Amount of Disksize used'
        },
Olga Brani's avatar
Olga Brani committed
697
698
699
700
701
        'cyclades.disk': {
            'help_text':'resource cyclades.disk help text',
            'is_abbreviation':False,
            'report_desc':'Amount of Disk used'    
        },
Olga Brani's avatar
Olga Brani committed
702
703
704
705
706
707
708
709
710
        'cyclades.ram': {
            'help_text':'resource cyclades.ram help text',
            'is_abbreviation':True,
            'report_desc':'RAM used'
        },
        'cyclades.cpu': {
            'help_text':'resource cyclades.cpu help text',
            'is_abbreviation':True,
            'report_desc':'CPUs used'
Olga Brani's avatar
Olga Brani committed
711
712
713
714
715
        },
        'cyclades.network.private': {
            'help_text':'resource cyclades.network.private help text',
            'is_abbreviation':False,
            'report_desc':'Network used'
Olga Brani's avatar
Olga Brani committed
716
717
718
        }
    }

719
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
720
@signed_terms_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
721
@login_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
722
def group_add(request, kind_name='default'):
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
    result = callpoint.list_resources()
    resource_catalog = {'resources':defaultdict(defaultdict),
                        'groups':defaultdict(list)}
    if result.is_success:
        for r in result.data:
            service = r.get('service', '')
            name = r.get('name', '')
            group = r.get('group', '')
            unit = r.get('unit', '')
            fullname = '%s%s%s' % (service, RESOURCE_SEPARATOR, name)
            resource_catalog['resources'][fullname] = dict(unit=unit)
            resource_catalog['groups'][group].append(fullname)
        
        resource_catalog = dict(resource_catalog)
        for k, v in resource_catalog.iteritems():
            resource_catalog[k] = dict(v)
    else:
        messages.error(
            request,
            'Unable to retrieve system resources: %s' % result.reason
    )
    
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
745
    try:
746
        kind = GroupKind.objects.get(name=kind_name)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
747
    except:
748
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
749
    
Olga Brani's avatar
Olga Brani committed
750
    
751
752
753

    post_save_redirect = '/im/group/%(id)s/'
    context_processors = None
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
754
755
756
757
    model, form_class = get_model_and_form_class(
        model=None,
        form_class=AstakosGroupCreationForm
    )
758
    
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
759
    if request.method == 'POST':
760
        form = form_class(request.POST, request.FILES)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
761
        if form.is_valid():
762
763
764
            return render_response(
                template='im/astakosgroup_form_summary.html',
                context_instance=get_context(request),
765
766
                form = AstakosGroupCreationSummaryForm(form.cleaned_data),
                policies = form.policies(),
Olga Brani's avatar
Olga Brani committed
767
768
                resource_catalog=resource_catalog,
                resource_presentation=resource_presentation
769
            )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
770
771
772
    else:
        now = datetime.now()
        data = {
773
            'kind': kind
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
774
        }
775
        form = form_class(data)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
776
777

    # Create the template, context, response
778
    template_name = "%s/%s_form.html" % (
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
779
780
781
782
783
        model._meta.app_label,
        model._meta.object_name.lower()
    )
    t = template_loader.get_template(template_name)
    c = RequestContext(request, {
Olga Brani's avatar
Olga Brani committed
784
785
        'form': form,
        'kind': kind,
786
787
        'resource_catalog':resource_catalog,
        'resource_presentation':resource_presentation,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
788
789
    }, context_processors)
    return HttpResponse(t.render(c))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
790

791

792
793
#@require_http_methods(["POST"])
@require_http_methods(["GET", "POST"])
794
795
@signed_terms_required
@login_required
796
def group_add_complete(request):
797
798
799
800
801
802
803
804
    model = AstakosGroup
    form = AstakosGroupCreationSummaryForm(request.POST)
    if form.is_valid():
        d = form.cleaned_data
        d['owners'] = [request.user]
        result = callpoint.create_groups((d,)).next()
        if result.is_success:
            new_object = result.data[0]
805
            msg = _(astakos_messages.OBJECT_CREATED) %\
806
807
808
809
810
811
812
<