views.py 40.7 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
36

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
37
from urllib import quote
38
from functools import wraps
39
from datetime import datetime, timedelta
40
from collections import defaultdict
41

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

62
63
from astakos.im.models import (
    AstakosUser, ApprovalTerms, AstakosGroup, Resource,
64
    EmailChange, GroupKind, Membership)
65
from astakos.im.activation_backends import get_backend, SimpleBackend
66
from astakos.im.util import get_context, prepare_response, set_cookie, get_query
67
68
69
70
from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
                              FeedbackForm, SignApprovalTermsForm,
                              ExtendedPasswordChangeForm, EmailChangeForm,
                              AstakosGroupCreationForm, AstakosGroupSearchForm,
71
                              AstakosGroupUpdateForm, AddGroupMembersForm,
72
                              AstakosGroupSortForm, MembersSortForm)
73
from astakos.im.functions import (send_feedback, SendMailError,
74
75
76
77
78
                                  invite as invite_func, logout as auth_logout,
                                  activate as activate_func,
                                  switch_account_to_shibboleth,
                                  send_admin_notification,
                                  SendNotificationError)
79
80
from astakos.im.settings import (COOKIE_NAME, COOKIE_DOMAIN, SITENAME,
                                 LOGOUT_NEXT, LOGGING_LEVEL, PAGINATE_BY)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
81
from astakos.im.tasks import request_billing
82

83
84
logger = logging.getLogger(__name__)

85

86
87
88
DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
                                     'https://', '')"""

89
90
def render_response(template, tab=None, status=200, reset_cookie=False,
                    context_instance=None, **kwargs):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
91
92
93
94
95
    """
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
    keyword argument and returns an ``django.http.HttpResponse`` with the
    specified ``status``.
    """
96
    if tab is None:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
97
        tab = template.partition('_')[0].partition('.html')[0]
98
    kwargs.setdefault('tab', tab)
99
    html = template_loader.render_to_string(
100
        template, kwargs, context_instance=context_instance)
101
102
103
104
    response = HttpResponse(html, status=status)
    if reset_cookie:
        set_cookie(response, context_instance['request'].user)
    return response
105

106
107
108

def requires_anonymous(func):
    """
109
    Decorator checkes whether the request.user is not Anonymous and in that case
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
110
    redirects to `logout`.
111
112
113
114
115
    """
    @wraps(func)
    def wrapper(request, *args):
        if not request.user.is_anonymous():
            next = urlencode({'next': request.build_absolute_uri()})
116
117
            logout_uri = reverse(logout) + '?' + next
            return HttpResponseRedirect(logout_uri)
118
119
120
        return func(request, *args)
    return wrapper

121

122
123
124
125
126
127
128
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):
129
        if request.user.is_authenticated() and not request.user.signed_terms:
130
            params = urlencode({'next': request.build_absolute_uri(),
131
                                'show_form': ''})
132
133
134
135
136
            terms_uri = reverse('latest_terms') + '?' + params
            return HttpResponseRedirect(terms_uri)
        return func(request, *args, **kwargs)
    return wrapper

137

138
@signed_terms_required
139
def index(request, login_template_name='im/login.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
140
    """
141
    If there is logged on user renders the profile page otherwise renders login page.
142

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
143
    **Arguments**
144

145
146
    ``login_template_name``
        A custom login template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
147
        this will default to ``im/login.html``.
148

149
150
    ``profile_template_name``
        A custom profile template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
151
        this will default to ``im/profile.html``.
152

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
153
154
    ``extra_context``
        An dictionary of variables to add to the template context.
155

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
156
    **Template:**
157

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
160
    """
161
162
    template_name = login_template_name
    if request.user.is_authenticated():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
163
        return HttpResponseRedirect(reverse('edit_profile'))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
164
    return render_response(template_name,
165
166
167
                           login_form=LoginForm(request=request),
                           context_instance=get_context(request, extra_context))

168

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
169
@login_required
170
@signed_terms_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
171
@transaction.commit_manually
172
def invite(request, template_name='im/invitations.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
173
174
    """
    Allows a user to invite somebody else.
175

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
176
177
178
    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.
179

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
180
181
    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.
182

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
185
    **Arguments**
186

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

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

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
198
    **Settings:**
199

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
200
    The view expectes the following settings are defined:
201

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
202
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
203
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
204
    """
205
206
    status = None
    message = None
207
    form = InvitationForm()
208

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
209
    inviter = request.user
210
    if request.method == 'POST':
211
        form = InvitationForm(request.POST)
212
        if inviter.invitations > 0:
213
214
215
216
217
            if form.is_valid():
                try:
                    invitation = form.save()
                    invite_func(invitation, inviter)
                    message = _('Invitation sent to %s' % invitation.username)
218
                    messages.success(request, message)
219
220
                except SendMailError, e:
                    message = e.message
221
                    messages.error(request, message)
222
                    transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
223
224
                except BaseException, e:
                    message = _('Something went wrong.')
225
                    messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
226
227
228
229
                    logger.exception(e)
                    transaction.rollback()
                else:
                    transaction.commit()
230
231
        else:
            message = _('No invitations left')
232
            messages.error(request, message)
233

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
234
    sent = [{'email': inv.username,
235
236
             'realname': inv.realname,
             'is_consumed': inv.is_consumed}
237
            for inv in request.user.invitations_sent.all()]
238
    kwargs = {'inviter': inviter,
239
              'sent': sent}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
240
241
    context = get_context(request, extra_context, **kwargs)
    return render_response(template_name,
242
243
244
                           invitation_form=form,
                           context_instance=context)

245

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
246
@login_required
247
@signed_terms_required
248
def edit_profile(request, template_name='im/profile.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
249
250
    """
    Allows a user to edit his/her profile.
251

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
258
    **Arguments**
259

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
260
261
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
262
        this will default to ``im/profile.html``.
263

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
264
265
    ``extra_context``
        An dictionary of variables to add to the template context.
266

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
267
    **Template:**
268

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
271
    **Settings:**
272

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
273
    The view expectes the following settings are defined:
274

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
275
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
276
    """
277
    extra_context = extra_context or {}
278
279
    form = ProfileForm(instance=request.user)
    extra_context['next'] = request.GET.get('next')
280
    reset_cookie = False
281
    if request.method == 'POST':
282
        form = ProfileForm(request.POST, instance=request.user)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
283
        if form.is_valid():
284
            try:
285
286
287
288
                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
289
290
291
                next = request.POST.get('next')
                if next:
                    return redirect(next)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
292
                msg = _('Profile has been updated successfully')
293
                messages.success(request, msg)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
294
            except ValueError, ve:
295
                messages.success(request, ve)
296
    elif request.method == "GET":
297
298
299
        if not request.user.is_verified:
            request.user.is_verified = True
            request.user.save()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
300
    return render_response(template_name,
301
302
303
304
305
                           reset_cookie=reset_cookie,
                           profile_form=form,
                           context_instance=get_context(request,
                                                        extra_context))

306

307
@transaction.commit_manually
308
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
309
310
    """
    Allows a user to create a local account.
311

312
    In case of GET request renders a form for entering the user information.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
313
    In case of POST handles the signup.
314

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
315
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
316
317
318
    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);
319

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

323
    On unsuccessful creation, renders ``template_name`` with an error message.
324

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
325
    **Arguments**
326

327
328
    ``template_name``
        A custom template to render. This is optional;
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
329
        if not specified, this will default to ``im/signup.html``.
330

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
335
336
    ``extra_context``
        An dictionary of variables to add to the template context.
337

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
338
    **Template:**
339

340
    im/signup.html or ``template_name`` keyword argument.
341
    im/signup_complete.html or ``on_success`` keyword argument.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
342
    """
343
    if request.user.is_authenticated():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
344
        return HttpResponseRedirect(reverse('edit_profile'))
345

346
    provider = get_query(request).get('provider', 'local')
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
347
    try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
348
349
        if not backend:
            backend = get_backend(request)
350
        form = backend.get_signup_form(provider)
351
    except Exception, e:
352
        form = SimpleBackend(request).get_signup_form(provider)
353
        messages.error(request, e)
354
355
    if request.method == 'POST':
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
356
            user = form.save(commit=False)
357
358
359
            try:
                result = backend.handle_activation(user)
                status = messages.SUCCESS
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
360
361
                message = result.message
                user.save()
362
363
364
365
                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)
366
367
                        msg = 'Additional email: %s saved for user %s.' % (
                            additional_email, user.email)
368
                        logger.log(LOGGING_LEVEL, msg)
369
370
                if user and user.is_active:
                    next = request.POST.get('next', '')
371
		    transaction.commit()
372
373
                    return prepare_response(request, user, next=next)
                messages.add_message(request, status, message)
374
		transaction.commit()
375
376
                return render_response(on_success,
                                       context_instance=get_context(request, extra_context))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
377
378
            except SendMailError, e:
                message = e.message
379
                messages.error(request, message)
380
                transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
381
382
            except BaseException, e:
                message = _('Something went wrong.')
383
                messages.error(request, message)
384
                logger.exception(e)
385
                transaction.rollback()
386
    return render_response(template_name,
387
388
                           signup_form=form,
                           provider=provider,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
389
                           context_instance=get_context(request, extra_context))
390

391

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
392
@login_required
393
@signed_terms_required
394
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
395
396
    """
    Allows a user to send feedback.
397

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
403
    **Arguments**
404

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
405
406
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
407
        this will default to ``im/feedback.html``.
408

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
409
410
    ``extra_context``
        An dictionary of variables to add to the template context.
411

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
412
    **Template:**
413

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
416
    **Settings:**
417

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
418
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
419
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
420
    """
421
    if request.method == 'GET':
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
422
423
424
425
        form = FeedbackForm()
    if request.method == 'POST':
        if not request.user:
            return HttpResponse('Unauthorized', status=401)
426

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
427
428
        form = FeedbackForm(request.POST)
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
429
            msg = form.cleaned_data['feedback_msg']
430
            data = form.cleaned_data['feedback_data']
431
            try:
432
433
                send_feedback(msg, data, request.user, email_template_name)
            except SendMailError, e:
434
                messages.error(request, message)
435
            else:
436
                message = _('Feedback successfully sent')
437
                messages.success(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
438
    return render_response(template_name,
439
440
441
                           feedback_form=form,
                           context_instance=get_context(request, extra_context))

442

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
443
@signed_terms_required
444
def logout(request, template='registration/logged_out.html', extra_context=None):
445
    """
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
446
    Wraps `django.contrib.auth.logout` and delete the cookie.
447
    """
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
448
    response = HttpResponse()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
449
450
451
452
453
    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
454
        logger.log(LOGGING_LEVEL, msg)
455
456
457
458
459
    next = request.GET.get('next')
    if next:
        response['Location'] = next
        response.status_code = 302
        return response
460
461
462
463
    elif LOGOUT_NEXT:
        response['Location'] = LOGOUT_NEXT
        response.status_code = 301
        return response
464
    messages.success(request, _('You have successfully logged out.'))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
465
    context = get_context(request, extra_context)
466
    response.write(template_loader.render_to_string(template, context_instance=context))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
467
    return response
468

469

470
@transaction.commit_manually
471
def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
472
    """
473
474
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
    and renews the user token.
475

476
477
    The view uses commit_manually decorator in order to ensure the user state will be updated
    only if the email will be send successfully.
478
479
480
481
482
483
    """
    token = request.GET.get('auth')
    next = request.GET.get('next')
    try:
        user = AstakosUser.objects.get(auth_token=token)
    except AstakosUser.DoesNotExist:
484
        return HttpResponseBadRequest(_('No such user'))
485

486
    if user.is_active:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
487
        message = _('Account already active.')
488
        messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
489
        return index(request)
490

491
    try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
492
        local_user = AstakosUser.objects.get(
493
            ~Q(id=user.id),
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
494
495
496
            email=user.email,
            is_active=True
        )
497
    except AstakosUser.DoesNotExist:
498
        try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
499
500
501
502
503
504
            activate_func(
                user,
                greeting_email_template_name,
                helpdesk_email_template_name,
                verify_email=True
            )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
505
506
507
508
509
            response = prepare_response(request, user, next, renew=True)
            transaction.commit()
            return response
        except SendMailError, e:
            message = e.message
510
            messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
511
512
513
514
            transaction.rollback()
            return index(request)
        except BaseException, e:
            message = _('Something went wrong.')
515
            messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
516
517
518
            logger.exception(e)
            transaction.rollback()
            return index(request)
519
    else:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
520
        try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
521
522
523
524
525
            user = switch_account_to_shibboleth(
                user,
                local_user,
                greeting_email_template_name
            )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
526
527
528
529
530
            response = prepare_response(request, user, next, renew=True)
            transaction.commit()
            return response
        except SendMailError, e:
            message = e.message
531
            messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
532
533
534
535
            transaction.rollback()
            return index(request)
        except BaseException, e:
            message = _('Something went wrong.')
536
            messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
537
538
539
            logger.exception(e)
            transaction.rollback()
            return index(request)
540

541

542
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
543
544
545
546
547
548
549
550
551
    term = None
    terms = None
    if not term_id:
        try:
            term = ApprovalTerms.objects.order_by('-id')[0]
        except IndexError:
            pass
    else:
        try:
552
553
            term = ApprovalTerms.objects.get(id=term_id)
        except ApprovalTerms.DoesNotExist, e:
554
            pass
555

556
    if not term:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
557
        return HttpResponseRedirect(reverse('index'))
558
559
    f = open(term.location, 'r')
    terms = f.read()
560

561
562
563
    if request.method == 'POST':
        next = request.POST.get('next')
        if not next:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
564
            next = reverse('index')
565
566
567
        form = SignApprovalTermsForm(request.POST, instance=request.user)
        if not form.is_valid():
            return render_response(template_name,
568
569
570
                                   terms=terms,
                                   approval_terms_form=form,
                                   context_instance=get_context(request, extra_context))
571
572
573
        user = form.save()
        return HttpResponseRedirect(next)
    else:
574
        form = None
575
        if request.user.is_authenticated() and not request.user.signed_terms:
576
            form = SignApprovalTermsForm(instance=request.user)
577
        return render_response(template_name,
578
579
580
581
                               terms=terms,
                               approval_terms_form=form,
                               context_instance=get_context(request, extra_context))

582
583
584

@signed_terms_required
def change_password(request):
585
    return password_change(request,
586
587
588
                           post_change_redirect=reverse('edit_profile'),
                           password_change_form=ExtendedPasswordChangeForm)

589

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
590
591
@signed_terms_required
@login_required
592
593
594
595
596
@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',
597
                 extra_context=None):
598
599
600
601
602
    if activation_key:
        try:
            user = EmailChange.objects.change_email(activation_key)
            if request.user.is_authenticated() and request.user == user:
                msg = _('Email changed successfully.')
603
                messages.success(request, msg)
604
605
606
607
608
                auth_logout(request)
                response = prepare_response(request, user)
                transaction.commit()
                return response
        except ValueError, e:
609
            messages.error(request, e)
610
        return render_response(confirm_template_name,
611
612
613
614
615
                               modified_user=user if 'user' in locals(
                               ) else None,
                               context_instance=get_context(request,
                                                            extra_context))

616
617
    if not request.user.is_authenticated():
        path = quote(request.get_full_path())
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
618
        url = request.build_absolute_uri(reverse('index'))
619
620
621
622
623
624
625
        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
626
            messages.error(request, msg)
627
628
629
            transaction.rollback()
        except IntegrityError, e:
            msg = _('There is already a pending change email request.')
630
            messages.error(request, msg)
631
632
633
        else:
            msg = _('Change email request has been registered succefully.\
                    You are going to receive a verification email in the new address.')
634
            messages.success(request, msg)
635
636
            transaction.commit()
    return render_response(form_template_name,
637
638
639
640
                           form=form,
                           context_instance=get_context(request,
                                                        extra_context))

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
641
642

@signed_terms_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
643
@login_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
644
645
def group_add(request, kind_name='default'):
    try:
646
        kind = GroupKind.objects.get(name=kind_name)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
647
648
    except:
        return HttpResponseBadRequest(_('No such group kind'))
649
650
651

    post_save_redirect = '/im/group/%(id)s/'
    context_processors = None
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
652
653
654
655
    model, form_class = get_model_and_form_class(
        model=None,
        form_class=AstakosGroupCreationForm
    )
656
657
    resources = dict(
        (str(r.id), r) for r in Resource.objects.select_related().all())
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
658
    policies = []
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
659
660
661
662
    if request.method == 'POST':
        form = form_class(request.POST, request.FILES, resources=resources)
        if form.is_valid():
            new_object = form.save()
663

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
664
            # save owner
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
665
            new_object.owners = [request.user]
666

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
667
            # save quota policies
668
            for (rid, uplimit) in form.resources():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
669
670
671
672
                try:
                    r = resources[rid]
                except KeyError, e:
                    logger.exception(e)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
673
                    # TODO Should I stay or should I go???
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
674
675
676
                    continue
                else:
                    new_object.astakosgroupquota_set.create(
677
                        resource=r,
678
                        uplimit=uplimit
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
679
                    )
680
                policies.append('%s %d' % (r, uplimit))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
681
            msg = _("The %(verbose_name)s was created successfully.") %\
682
                {"verbose_name": model._meta.verbose_name}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
683
            messages.success(request, msg, fail_silently=True)
684

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
685
686
687
688
689
            # send notification
            try:
                send_admin_notification(
                    template_name='im/group_creation_notification.txt',
                    dictionary={
690
691
692
                        'group': new_object,
                        'owner': request.user,
                        'policies': policies,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
693
694
695
696
697
                    },
                    subject='%s alpha2 testing group creation notification' % SITENAME
                )
            except SendNotificationError, e:
                messages.error(request, e, fail_silently=True)
698
            return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
699
700
701
    else:
        now = datetime.now()
        data = {
702
            'kind': kind
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
703
704
705
706
707
708
709
710
711
712
        }
        form = form_class(data, resources=resources)

    # Create the template, context, response
    template_name = "%s/%s_form.html" % (
        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
713
714
        'form': form,
        'kind': kind,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
715
716
    }, context_processors)
    return HttpResponse(t.render(c))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
717

718

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
719
720
@signed_terms_required
@login_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
721
def group_list(request):
722
723
724
725
726
727
728
729
730
731
    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,
732
733
734
735
        (SELECT CASE WHEN(
                    SELECT date_joined FROM im_membership
                    WHERE group_id = im_astakosgroup.group_ptr_id
                    AND person_id = %s) IS NULL
736
                    THEN 0 ELSE 1 END) AS membership_status
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
        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_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
        LEFT JOIN im_astakosuser_owner ON (
            im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
        LEFT JOIN auth_user as owner ON (
            im_astakosuser_owner.astakosuser_id = owner.id)
        WHERE im_membership.person_id = %s
        """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id))
    d = defaultdict(list)
    for g in q:
        if request.user.email == g.groupowner:
            d['own'].append(g)
        else:
            d['other'].append(g)
754
755
756
757
758
759
760
761
762
    
    # validate sorting
    fields = ('own', 'other')
    for f in fields:
        v = globals()['%s_sorting' % f] = request.GET.get('%s_sorting' % f)
        if v:
            form = AstakosGroupSortForm({'sort_by': v})
            if not form.is_valid():
                globals()['%s_sorting' % f] = form.cleaned_data.get('sort_by')
763
    return object_list(request, queryset=none,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
764
                       extra_context={'is_search':False,
765
766
                                      'mine': d['own'],
                                      'other': d['other'],
767
768
                                      'own_sorting': own_sorting,
                                      'other_sorting': other_sorting,
769
                                      'own_page': request.GET.get('own_page', 1),
770
                                      'other_page': request.GET.get('other_page', 1)
771
                                      })
772

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
773
774
775
776

@signed_terms_required
@login_required
def group_detail(request, group_id):
777
    q = AstakosGroup.objects.select_related().filter(pk=group_id)
778
779
780
781
782
783
784
    q = q.extra(select={
        'is_member': """SELECT CASE WHEN EXISTS(
                            SELECT id FROM im_membership
                            WHERE group_id = im_astakosgroup.group_ptr_id
                            AND person_id = %s)
                        THEN 1 ELSE 0 END""" % request.user.id,
        'is_owner': """SELECT CASE WHEN EXISTS(
785
786
787
                        SELECT id FROM im_astakosuser_owner
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
                        AND astakosuser_id = %s)
788
789
790
791
792
793
794
                        THEN 1 ELSE 0 END""" % request.user.id,
        'kindname': """SELECT name FROM im_groupkind
                       WHERE id = im_astakosgroup.kind_id"""})
    
    model = q.model
    context_processors = None
    mimetype = None
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
795
    try:
796
        obj = q.get()
797
798
799
    except AstakosGroup.DoesNotExist:
        raise Http404("No %s found matching the query" % (
            model._meta.verbose_name))
800
    
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
    update_form = AstakosGroupUpdateForm(instance=obj)
    addmembers_form = AddGroupMembersForm()
    if request.method == 'POST':
        update_data = {}
        addmembers_data = {}
        for k,v in request.POST.iteritems():
            if k in update_form.fields:
                update_data[k] = v
            if k in addmembers_form.fields:
                addmembers_data[k] = v
        update_data = update_data or None
        addmembers_data = addmembers_data or None
        update_form = AstakosGroupUpdateForm(update_data, instance=obj)
        addmembers_form = AddGroupMembersForm(addmembers_data)
        if update_form.is_valid():
            update_form.save()
        if addmembers_form.is_valid():
            map(obj.approve_member, addmembers_form.valid_users)
            addmembers_form = AddGroupMembersForm()
820
    
821
822
823
824
825
    template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
    t = template_loader.get_template(template_name)
    c = RequestContext(request, {
        'object': obj,
    }, context_processors)
826
827
828
829
830
831
832
833
    
    # validate sorting
    sorting= request.GET.get('sorting')
    if sorting:
        form = MembersSortForm({'sort_by': sorting})
        if form.is_valid():
            sorting = form.cleaned_data.get('sort_by')
         
834
835
836
    extra_context = {'update_form': update_form,
                     'addmembers_form': addmembers_form,
                     'page': request.GET.get('page', 1),
837
                     'sorting': sorting}
838