views.py 50 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
from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
                               Resource, EmailChange, GroupKind, Membership,
                               AstakosGroupQuota, RESOURCE_SEPARATOR)
from django.views.decorators.http import require_http_methods
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
70
from django.db.models.query import QuerySet
Olga Brani's avatar
Olga Brani committed
71

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

Olga Brani's avatar
Olga Brani committed
94
95
import astakos.im.messages as astakos_messages

96
97
logger = logging.getLogger(__name__)

98

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

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

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

121
122
123

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

136

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

152

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

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

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

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

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

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

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

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

184

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

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

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

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

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

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

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

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

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

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

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

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

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

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

263

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

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

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

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

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

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

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

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

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

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

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

325

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

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

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

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

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

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

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

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

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

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

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

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

412

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

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

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

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

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

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

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

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

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

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

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

464

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

493

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

502
503
    The view uses commit_manually decorator in order to ensure the user state will be updated
    only if the email will be send successfully.
504
505
506
507
508
509
    """
    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
510
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
511

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

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

567

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

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

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

610

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

618

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
670

Olga Brani's avatar
Olga Brani committed
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685

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,
Olga Brani's avatar
Olga Brani committed
686
687
            'report_desc':'Pithos+ Diskspace',
            'placeholder':'eg. 10GB'
Olga Brani's avatar
Olga Brani committed
688
689
690
691
        },
        '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,
Olga Brani's avatar
Olga Brani committed
692
693
            'report_desc':'Virtual Machines',
            'placeholder':'eg. 2'
Olga Brani's avatar
Olga Brani committed
694
695
696
697
        },
        'cyclades.disksize': {
            'help_text':'resource cyclades.disksize help text',
            'is_abbreviation':False,
Olga Brani's avatar
Olga Brani committed
698
            'report_desc':'Disksize',
Olga Brani's avatar
Olga Brani committed
699
            'placeholder':'eg. 5GB, 2GB etc'
Olga Brani's avatar
Olga Brani committed
700
        },
Olga Brani's avatar
Olga Brani committed
701
702
703
        'cyclades.disk': {
            'help_text':'resource cyclades.disk help text',
            'is_abbreviation':False,
Olga Brani's avatar
Olga Brani committed
704
            'report_desc':'Disk',
Olga Brani's avatar
Olga Brani committed
705
            'placeholder':'eg. 5GB, 2GB etc'
Olga Brani's avatar
Olga Brani committed
706
        },
Olga Brani's avatar
Olga Brani committed
707
708
709
        'cyclades.ram': {
            'help_text':'resource cyclades.ram help text',
            'is_abbreviation':True,
Olga Brani's avatar
Olga Brani committed
710
711
            'report_desc':'RAM',
            'placeholder':'eg. 4GB'
Olga Brani's avatar
Olga Brani committed
712
713
714
715
        },
        'cyclades.cpu': {
            'help_text':'resource cyclades.cpu help text',
            'is_abbreviation':True,
Olga Brani's avatar
Olga Brani committed
716
717
            'report_desc':'CPUs',
            'placeholder':'eg. 1'
Olga Brani's avatar
Olga Brani committed
718
719
720
721
        },
        'cyclades.network.private': {
            'help_text':'resource cyclades.network.private help text',
            'is_abbreviation':False,
Olga Brani's avatar
Olga Brani committed
722
723
            'report_desc':'Network',
            'placeholder':'eg. 1'
Olga Brani's avatar
Olga Brani committed
724
725
726
        }
    }

727
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
728
@signed_terms_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
729
@login_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
730
def group_add(request, kind_name='default'):
731
    result = callpoint.list_resources()
Olga Brani's avatar
Olga Brani committed
732
    print '###', result
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
    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
754
    try:
755
        kind = GroupKind.objects.get(name=kind_name)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
756
    except:
Olga Brani's avatar
Olga Brani committed
757
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
758
    
Olga Brani's avatar
Olga Brani committed
759
    
760
761
762

    post_save_redirect = '/im/group/%(id)s/'
    context_processors = None
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
763
764
765
766
    model, form_class = get_model_and_form_class(
        model=None,
        form_class=AstakosGroupCreationForm
    )
767
    
Olga Brani's avatar
Olga Brani committed
768
769
770
    resources = resource_catalog['resources']
    
 
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
771
    if request.method == 'POST':
772
        form = form_class(request.POST, request.FILES)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
773
        if form.is_valid():
774
775
776
            return render_response(
                template='im/astakosgroup_form_summary.html',
                context_instance=get_context(request),
777
778
                form = AstakosGroupCreationSummaryForm(form.cleaned_data),
                policies = form.policies(),
Olga Brani's avatar
Olga Brani committed
779
780
781
782
                resource_presentation=resource_presentation,
                resource_catalog= resource_catalog,
                resources = resources,
            
783
            )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
784
785
786
    else:
        now = datetime.now()
        data = {
787
            'kind': kind,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
788
        }
789
790
791
792
793
        for group, resources in resource_catalog['groups'].iteritems():
            data['is_selected_%s' % group] = False
            for resource in resources:
                data['%s_uplimit' % resource] = ''
        
Sofia Papagiannaki's avatar