views.py 47.5 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
logger = logging.getLogger(__name__)

95

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

99
callpoint = AstakosCallpoint()
Olga Brani's avatar
Olga Brani committed
100

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

118
119
120

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

133

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

149

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
156
    **Arguments**
157

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

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
169
    **Template:**
170

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

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

181

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

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

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
199
    **Arguments**
200

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
208
    **Template:**
209

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
212
    **Settings:**
213

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
214
    The view expectes the following settings are defined:
215

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

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

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

260

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

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
274
    **Arguments**
275

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
283
    **Template:**
284

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
287
    **Settings:**
288

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
289
    The view expectes the following settings are defined:
290

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

322

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

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

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

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

340
    On unsuccessful creation, renders ``template_name`` with an error message.
341

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
342
    **Arguments**
343

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

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
355
    **Template:**
356

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

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

409

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

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
422
    **Arguments**
423

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
431
    **Template:**
432

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
435
    **Settings:**
436

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

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

461

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

490

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

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

509
    if user.is_active:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
510
        message = _('Account already active.')
511
        messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
512
        return index(request)
513

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

564

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

580
    if not term:
Olga Brani's avatar
Olga Brani committed
581
        messages.error(request, 'There are no approval terms.')
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
582
        return HttpResponseRedirect(reverse('index'))
583
584
    f = open(term.location, 'r')
    terms = f.read()
585

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

607

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

615

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

643
644
    if not request.user.is_authenticated():
        path = quote(request.get_full_path())
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
645
        url = request.build_absolute_uri(reverse('index'))
646
647
648
649
650
651
652
        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
653
            messages.error(request, msg)
654
655
656
            transaction.rollback()
        except IntegrityError, e:
            msg = _('There is already a pending change email request.')
657
            messages.error(request, msg)
658
659
660
        else:
            msg = _('Change email request has been registered succefully.\
                    You are going to receive a verification email in the new address.')
661
            messages.success(request, msg)
662
663
            transaction.commit()
    return render_response(form_template_name,
664
665
666
667
                           form=form,
                           context_instance=get_context(request,
                                                        extra_context))

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
668

Olga Brani's avatar
Olga Brani committed
669
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
697
698
699
700
701
702
703
704
705
706
707

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'
        },
        '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'
        }
    }

708
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
709
@signed_terms_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
710
@login_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
711
def group_add(request, kind_name='default'):
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
    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
734
    try:
735
        kind = GroupKind.objects.get(name=kind_name)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
736
737
    except:
        return HttpResponseBadRequest(_('No such group kind'))
738
    
Olga Brani's avatar
Olga Brani committed
739
    
740
741
742

    post_save_redirect = '/im/group/%(id)s/'
    context_processors = None
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
743
744
745
746
    model, form_class = get_model_and_form_class(
        model=None,
        form_class=AstakosGroupCreationForm
    )
747
    
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
748
    if request.method == 'POST':
749
        form = form_class(request.POST, request.FILES)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
750
        if form.is_valid():
751
752
753
            return render_response(
                template='im/astakosgroup_form_summary.html',
                context_instance=get_context(request),
754
755
                form = AstakosGroupCreationSummaryForm(form.cleaned_data),
                policies = form.policies(),
Olga Brani's avatar
Olga Brani committed
756
757
                resource_catalog=resource_catalog,
                resource_presentation=resource_presentation
758
            )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
759
760
761
    else:
        now = datetime.now()
        data = {
762
            'kind': kind
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
763
        }
764
        form = form_class(data)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
765
766

    # Create the template, context, response
767
    template_name = "%s/%s_form_demo.html" % (
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
768
769
770
771
772
        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
773
774
        'form': form,
        'kind': kind,
775
776
        'resource_catalog':resource_catalog,
        'resource_presentation':resource_presentation,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
777
778
    }, context_processors)
    return HttpResponse(t.render(c))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
779

780

781
782
783
@require_http_methods(["POST"])
@signed_terms_required
@login_required
784
def group_add_complete(request):
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
    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]
            msg = _("The %(verbose_name)s was created successfully.") %\
                {"verbose_name": model._meta.verbose_name}
            messages.success(request, msg, fail_silently=True)
            
            # send notification
            try:
                send_group_creation_notification(
                    template_name='im/group_creation_notification.txt',
                    dictionary={
                        'group': new_object,
                        'owner': request.user,
                        'policies': d.get('policies', [])
                    }
                )
            except SendNotificationError, e:
                messages.error(request, e, fail_silently=True)
            post_save_redirect = '/im/group/%(id)s/'
            return HttpResponseRedirect(post_save_redirect % new_object)
        else:
            msg = _("The %(verbose_name)s creation failed: %(reason)s.") %\
                {"verbose_name": model._meta.verbose_name,
                 "reason":result.reason}
            messages.error(request, msg, fail_silently=True)
816
    return render_response(
817
818
819
        template='im/astakosgroup_form_summary.html',
        context_instance=get_context(request),
        form=form)
820
821
822


@require_http_methods(["GET"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
823
824
@signed_terms_required
@login_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
825
def group_list(request):
826
827
828
829
830
831
832
833
834
835
    none = request.user.astakos_groups.none()
    q = AstakosGroup.objects.raw("""
        SELECT auth_group.id,
        %s AS groupname,
        im_groupkind.name AS kindname,
        im_astakosgroup.*,
        owner.email AS groupowner,
        (SELECT COUNT(*) FROM im_membership
            WHERE group_id = im_astakosgroup.group_ptr_id
            AND date_joined IS NOT NULL) AS approved_members_num,
836
837
838
839
        (SELECT CASE WHEN(
                    SELECT date_joined FROM im_membership
                    WHERE group_id = im_astakosgroup.group_ptr_id
                    AND person_id = %s) IS NULL
840
                    THEN 0 ELSE 1 END) AS membership_status
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
        FROM im_astakosgroup
        INNER JOIN im_membership ON (
            im_astakosgroup.group_ptr_id = im_membership.group_id)
        INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
        INNER JOIN im_groupki