views.py 50.9 KB
Newer Older
Antony Chazapis's avatar
Antony Chazapis committed
1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
4
5
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
6
#
7
8
9
#   1. Redistributions of source code must retain the above
#      copyright notice, this list of conditions and the following
#      disclaimer.
10
#
11
12
13
14
#   2. Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials
#      provided with the distribution.
15
#
16
17
18
19
20
21
22
23
24
25
26
27
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
30
31
32
33
34
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.

import logging
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
35
import calendar
Olga Brani's avatar
Olga Brani committed
36
37
38
import inflect

engine = inflect.engine()
39

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
40
from urllib import quote
41
from functools import wraps
42
from datetime import datetime
43

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

62
from django.template.loader import render_to_string
Olga Brani's avatar
Olga Brani committed
63
from django.views.decorators.http import require_http_methods
64
from astakos.im.activation_backends import get_backend, SimpleBackend
65
66
67

from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
                               EmailChange, GroupKind, Membership,
Olga Brani's avatar
Olga Brani committed
68
                               RESOURCE_SEPARATOR, AstakosUserAuthProvider)
69
from astakos.im.util import get_context, prepare_response, get_query, restrict_next
70
71
from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
                              FeedbackForm, SignApprovalTermsForm,
72
                              EmailChangeForm,
73
                              AstakosGroupCreationForm, AstakosGroupSearchForm,
74
                              AstakosGroupUpdateForm, AddGroupMembersForm,
75
                              MembersSortForm,
76
77
                              TimelineForm, PickResourceForm,
                              AstakosGroupCreationSummaryForm)
78
from astakos.im.functions import (send_feedback, SendMailError,
Olga Brani's avatar
Olga Brani committed
79
                                  logout as auth_logout,
80
                                  activate as activate_func,
81
                                  send_activation as send_activation_func,
Olga Brani's avatar
Olga Brani committed
82
                                  send_group_creation_notification,
83
                                  SendNotificationError)
Olga Brani's avatar
Fixes    
Olga Brani committed
84
from astakos.im.endpoints.qh import timeline_charge
85
from astakos.im.settings import (COOKIE_DOMAIN, LOGOUT_NEXT,
Olga Brani's avatar
Fixes    
Olga Brani committed
86
                                 LOGGING_LEVEL, PAGINATE_BY, RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
87
from astakos.im.tasks import request_billing
88
from astakos.im.api.callpoint import AstakosCallpoint
89

Olga Brani's avatar
Olga Brani committed
90
import astakos.im.messages as astakos_messages
Olga Brani's avatar
Olga Brani committed
91
92
from astakos.im import settings
from astakos.im import auth_providers
Olga Brani's avatar
Olga Brani committed
93

94
95
logger = logging.getLogger(__name__)

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
def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
102
103
104
105
106
    """
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
    keyword argument and returns an ``django.http.HttpResponse`` with the
    specified ``status``.
    """
107
    if tab is None:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
108
        tab = template.partition('_')[0].partition('.html')[0]
109
    kwargs.setdefault('tab', tab)
110
    html = template_loader.render_to_string(
111
        template, kwargs, context_instance=context_instance)
112
113
    response = HttpResponse(html, status=status)
    return response
114

Olga Brani's avatar
Olga Brani committed
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def requires_auth_provider(provider_id, **perms):
    """
    """
    def decorator(func, *args, **kwargs):
        @wraps(func)
        def wrapper(request, *args, **kwargs):
            provider = auth_providers.get_provider(provider_id)

            if not provider or not provider.is_active():
                raise PermissionDenied

            if provider:
                for pkey, value in perms.iteritems():
                    attr = 'is_available_for_%s' % pkey.lower()
                    if getattr(provider, attr)() != value:
                        raise PermissionDenied
            return func(request, *args)
        return wrapper
    return decorator

135
136
137

def requires_anonymous(func):
    """
138
    Decorator checkes whether the request.user is not Anonymous and in that case
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
139
    redirects to `logout`.
140
141
142
143
144
    """
    @wraps(func)
    def wrapper(request, *args):
        if not request.user.is_anonymous():
            next = urlencode({'next': request.build_absolute_uri()})
145
146
            logout_uri = reverse(logout) + '?' + next
            return HttpResponseRedirect(logout_uri)
147
148
149
        return func(request, *args)
    return wrapper

150

151
152
153
154
155
156
157
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):
158
        if request.user.is_authenticated() and not request.user.signed_terms:
159
            params = urlencode({'next': request.build_absolute_uri(),
160
                                'show_form': ''})
161
162
163
164
165
            terms_uri = reverse('latest_terms') + '?' + params
            return HttpResponseRedirect(terms_uri)
        return func(request, *args, **kwargs)
    return wrapper

166

Olga Brani's avatar
Olga Brani committed
167
@require_http_methods(["GET", "POST"])
168
@signed_terms_required
169
def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
170
    """
171
    If there is logged on user renders the profile page otherwise renders login page.
172

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
173
    **Arguments**
174

175
176
    ``login_template_name``
        A custom login template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
177
        this will default to ``im/login.html``.
178

179
180
    ``profile_template_name``
        A custom profile template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
181
        this will default to ``im/profile.html``.
182

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
183
184
    ``extra_context``
        An dictionary of variables to add to the template context.
185

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
186
    **Template:**
187

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
190
    """
191
    extra_context = extra_context or {}
192
193
    template_name = login_template_name
    if request.user.is_authenticated():
194
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
Olga Brani's avatar
Olga Brani committed
195

196
197
198
199
200
    return render_response(
        template_name,
        login_form = LoginForm(request=request),
        context_instance = get_context(request, extra_context)
    )
201

202

Olga Brani's avatar
Olga Brani committed
203
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
204
@login_required
205
@signed_terms_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
206
@transaction.commit_manually
207
def invite(request, template_name='im/invitations.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
208
209
    """
    Allows a user to invite somebody else.
210

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
211
212
213
    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.
214

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
215
216
    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.
217

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
220
    **Arguments**
221

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
222
223
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
224
        this will default to ``im/invitations.html``.
225

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
226
227
    ``extra_context``
        An dictionary of variables to add to the template context.
228

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
229
    **Template:**
230

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
233
    **Settings:**
234

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
235
    The view expectes the following settings are defined:
236

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
237
238
    * LOGIN_URL: login uri
    """
239
    extra_context = extra_context or {}
240
241
    status = None
    message = None
242
    form = InvitationForm()
243

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
244
    inviter = request.user
245
    if request.method == 'POST':
246
        form = InvitationForm(request.POST)
247
        if inviter.invitations > 0:
248
249
            if form.is_valid():
                try:
Olga Brani's avatar
Olga Brani committed
250
251
252
                    email = form.cleaned_data.get('username')
                    realname = form.cleaned_data.get('realname')
                    inviter.invite(email, realname)
Olga Brani's avatar
Olga Brani committed
253
                    message = _(astakos_messages.INVITATION_SENT) % locals()
254
                    messages.success(request, message)
255
256
                except SendMailError, e:
                    message = e.message
257
                    messages.error(request, message)
258
                    transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
259
                except BaseException, e:
Olga Brani's avatar
Olga Brani committed
260
                    message = _(astakos_messages.GENERIC_ERROR)
261
                    messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
262
263
264
265
                    logger.exception(e)
                    transaction.rollback()
                else:
                    transaction.commit()
266
        else:
Olga Brani's avatar
Olga Brani committed
267
            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
268
            messages.error(request, message)
269

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
270
    sent = [{'email': inv.username,
271
272
             'realname': inv.realname,
             'is_consumed': inv.is_consumed}
273
            for inv in request.user.invitations_sent.all()]
274
    kwargs = {'inviter': inviter,
275
              'sent': sent}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
276
277
    context = get_context(request, extra_context, **kwargs)
    return render_response(template_name,
278
279
280
                           invitation_form=form,
                           context_instance=context)

281

Olga Brani's avatar
Olga Brani committed
282
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
283
@login_required
284
@signed_terms_required
285
def edit_profile(request, template_name='im/profile.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
286
287
    """
    Allows a user to edit his/her profile.
288

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
295
    **Arguments**
296

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
297
298
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
299
        this will default to ``im/profile.html``.
300

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
301
302
    ``extra_context``
        An dictionary of variables to add to the template context.
303

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
304
    **Template:**
305

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
308
    **Settings:**
309

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
310
    The view expectes the following settings are defined:
311

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

    # existing providers
    user_providers = request.user.get_active_auth_providers()

    # providers that user can add
    user_available_providers = request.user.get_available_auth_providers()

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
354
    return render_response(template_name,
355
                           profile_form = form,
Olga Brani's avatar
Olga Brani committed
356
357
                           user_providers = user_providers,
                           user_available_providers = user_available_providers,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
358
                           context_instance = get_context(request,
359
                                                          extra_context))
360

361

362
@transaction.commit_manually
Olga Brani's avatar
Olga Brani committed
363
@require_http_methods(["GET", "POST"])
364
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
365
366
    """
    Allows a user to create a local account.
367

368
    In case of GET request renders a form for entering the user information.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
369
    In case of POST handles the signup.
370

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
371
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
372
373
374
    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);
375

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

379
    On unsuccessful creation, renders ``template_name`` with an error message.
380

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
381
    **Arguments**
382

383
384
    ``template_name``
        A custom template to render. This is optional;
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
385
        if not specified, this will default to ``im/signup.html``.
386

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
391
392
    ``extra_context``
        An dictionary of variables to add to the template context.
393

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
394
    **Template:**
395

396
    im/signup.html or ``template_name`` keyword argument.
397
    im/signup_complete.html or ``on_success`` keyword argument.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
398
    """
399
    extra_context = extra_context or {}
400
    if request.user.is_authenticated():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
401
        return HttpResponseRedirect(reverse('edit_profile'))
402

403
    provider = get_query(request).get('provider', 'local')
Olga Brani's avatar
Olga Brani committed
404
405
406
    if not auth_providers.get_provider(provider).is_available_for_create():
        raise PermissionDenied

407
408
409
410
411
412
    id = get_query(request).get('id')
    try:
        instance = AstakosUser.objects.get(id=id) if id else None
    except AstakosUser.DoesNotExist:
        instance = None

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
413
    try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
414
415
        if not backend:
            backend = get_backend(request)
416
        form = backend.get_signup_form(provider, instance)
417
    except Exception, e:
418
        form = SimpleBackend(request).get_signup_form(provider)
419
        messages.error(request, e)
420
421
    if request.method == 'POST':
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
422
            user = form.save(commit=False)
423
424
425
            try:
                result = backend.handle_activation(user)
                status = messages.SUCCESS
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
426
                message = result.message
Olga Brani's avatar
Olga Brani committed
427
428
429

                form.store_user(user, request)

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

470

Olga Brani's avatar
Olga Brani committed
471
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
472
@login_required
473
@signed_terms_required
474
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
475
476
    """
    Allows a user to send feedback.
477

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
483
    **Arguments**
484

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
485
486
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
487
        this will default to ``im/feedback.html``.
488

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
489
490
    ``extra_context``
        An dictionary of variables to add to the template context.
491

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
492
    **Template:**
493

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
496
    **Settings:**
497

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
498
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
499
    """
500
    extra_context = extra_context or {}
501
    if request.method == 'GET':
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
502
503
504
505
        form = FeedbackForm()
    if request.method == 'POST':
        if not request.user:
            return HttpResponse('Unauthorized', status=401)
506

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
507
508
        form = FeedbackForm(request.POST)
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
509
            msg = form.cleaned_data['feedback_msg']
510
            data = form.cleaned_data['feedback_data']
511
            try:
512
513
                send_feedback(msg, data, request.user, email_template_name)
            except SendMailError, e:
514
                messages.error(request, message)
515
            else:
Olga Brani's avatar
Olga Brani committed
516
                message = _(astakos_messages.FEEDBACK_SENT)
517
                messages.success(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
518
    return render_response(template_name,
519
520
521
                           feedback_form=form,
                           context_instance=get_context(request, extra_context))

522

523
@require_http_methods(["GET"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
524
@signed_terms_required
525
def logout(request, template='registration/logged_out.html', extra_context=None):
526
    """
527
    Wraps `django.contrib.auth.logout`.
528
    """
529
    extra_context = extra_context or {}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
530
    response = HttpResponse()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
531
532
533
    if request.user.is_authenticated():
        email = request.user.email
        auth_logout(request)
534
535
536
537
    next = restrict_next(
        request.GET.get('next'),
        domain=COOKIE_DOMAIN
    )
538
539
540
    if next:
        response['Location'] = next
        response.status_code = 302
541
542
543
    elif LOGOUT_NEXT:
        response['Location'] = LOGOUT_NEXT
        response.status_code = 301
544
    else:
545
        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
546
547
        context = get_context(request, extra_context)
        response.write(render_to_string(template, context_instance=context))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
548
    return response
549

550

Olga Brani's avatar
Olga Brani committed
551
@require_http_methods(["GET", "POST"])
552
@transaction.commit_manually
553
554
def activate(request, greeting_email_template_name='im/welcome_email.txt',
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
555
    """
556
557
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
    and renews the user token.
558

559
560
    The view uses commit_manually decorator in order to ensure the user state will be updated
    only if the email will be send successfully.
561
562
563
564
565
566
    """
    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
567
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
568

569
    if user.is_active:
Olga Brani's avatar
Olga Brani committed
570
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
571
        messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
572
        return index(request)
573

574
    try:
575
576
577
578
579
580
581
582
583
584
585
        activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
        response = prepare_response(request, user, next, renew=True)
        transaction.commit()
        return response
    except SendMailError, e:
        message = e.message
        messages.add_message(request, messages.ERROR, message)
        transaction.rollback()
        return index(request)
    except BaseException, e:
        status = messages.ERROR
586
        message = _(astakos_messages.GENERIC_ERROR)
587
588
589
590
        messages.add_message(request, messages.ERROR, message)
        logger.exception(e)
        transaction.rollback()
        return index(request)
591

592

Olga Brani's avatar
Olga Brani committed
593
@require_http_methods(["GET", "POST"])
594
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
595
    extra_context = extra_context or {}
596
597
598
599
600
601
602
603
604
    term = None
    terms = None
    if not term_id:
        try:
            term = ApprovalTerms.objects.order_by('-id')[0]
        except IndexError:
            pass
    else:
        try:
605
606
            term = ApprovalTerms.objects.get(id=term_id)
        except ApprovalTerms.DoesNotExist, e:
607
            pass
608

609
    if not term:
Olga Brani's avatar
Olga Brani committed
610
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
611
        return HttpResponseRedirect(reverse('index'))
612
613
    f = open(term.location, 'r')
    terms = f.read()
614

615
    if request.method == 'POST':
616
617
618
619
        next = restrict_next(
            request.POST.get('next'),
            domain=COOKIE_DOMAIN
        )
620
        if not next:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
621
            next = reverse('index')
622
623
624
        form = SignApprovalTermsForm(request.POST, instance=request.user)
        if not form.is_valid():
            return render_response(template_name,
625
626
627
                                   terms=terms,
                                   approval_terms_form=form,
                                   context_instance=get_context(request, extra_context))
628
629
630
        user = form.save()
        return HttpResponseRedirect(next)
    else:
631
        form = None
632
        if request.user.is_authenticated() and not request.user.signed_terms:
633
            form = SignApprovalTermsForm(instance=request.user)
634
        return render_response(template_name,
635
636
637
638
                               terms=terms,
                               approval_terms_form=form,
                               context_instance=get_context(request, extra_context))

639

Olga Brani's avatar
Olga Brani committed
640
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
641
@login_required
642
@signed_terms_required
643
644
645
646
647
@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',
648
                 extra_context=None):
649
    extra_context = extra_context or {}
650
651
652
653
    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
654
                msg = _(astakos_messages.EMAIL_CHANGED)
655
                messages.success(request, msg)
656
657
658
659
660
                auth_logout(request)
                response = prepare_response(request, user)
                transaction.commit()
                return response
        except ValueError, e:
661
            messages.error(request, e)
662
        return render_response(confirm_template_name,
663
664
665
666
667
                               modified_user=user if 'user' in locals(
                               ) else None,
                               context_instance=get_context(request,
                                                            extra_context))

668
669
    if not request.user.is_authenticated():
        path = quote(request.get_full_path())
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
670
        url = request.build_absolute_uri(reverse('index'))
671
672
673
674
675
676
677
        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
678
            messages.error(request, msg)
679
680
            transaction.rollback()
        except IntegrityError, e:
Olga Brani's avatar
Olga Brani committed
681
            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
682
            messages.error(request, msg)
683
        else:
Olga Brani's avatar
Olga Brani committed
684
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
685
            messages.success(request, msg)
686
            transaction.commit()
687
688
689
690
691
    return render_response(
        form_template_name,
        form=form,
        context_instance=get_context(request, extra_context)
    )
692
693
694


def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
Olga Brani's avatar
Olga Brani committed
695
696
697
698

    if settings.MODERATION_ENABLED:
        raise PermissionDenied

699
700
701
702
    extra_context = extra_context or {}
    try:
        u = AstakosUser.objects.get(id=user_id)
    except AstakosUser.DoesNotExist:
703
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
704
705
706
    else:
        try:
            send_activation_func(u)
707
            msg = _(astakos_messages.ACTIVATION_SENT)
708
709
710
711
712
            messages.success(request, msg)
        except SendMailError, e:
            messages.error(request, e)
    return render_response(
        template_name,
713
        login_form = LoginForm(request=request),
714
715
716
717
718
        context_instance = get_context(
            request,
            extra_context
        )
    )
719

Olga Brani's avatar
Olga Brani committed
720
class ResourcePresentation():
Olga Brani's avatar
Olga Brani committed
721

Olga Brani's avatar
Olga Brani committed
722
723
    def __init__(self, data):
        self.data = data
Olga Brani's avatar
Olga Brani committed
724

Olga Brani's avatar
Olga Brani committed
725
726
727
728
729
730
    def update_from_result(self, result):
        if result.is_success:
            for r in result.data:
                rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
                if not rname in self.data['resources']:
                    self.data['resources'][rname] = {}
Olga Brani's avatar
Olga Brani committed
731

Olga Brani's avatar
Olga Brani committed
732
733
734
735
736
                self.data['resources'][rname].update(r)
                self.data['resources'][rname]['id'] = rname
                group = r.get('group')
                if not group in self.data['groups']:
                    self.data['groups'][group] = {}
Olga Brani's avatar
Olga Brani committed
737

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

Olga Brani's avatar
Olga Brani committed
740
741
742
743
744
    def test(self, quota_dict):
        for k, v in quota_dict.iteritems():
            rname = k
            value = v
            if not rname in self.data['resources']:
745
                self.data['resources'][rname] = {}
Olga Brani's avatar
Olga Brani committed
746
747


Olga Brani's avatar
Olga Brani committed
748
            self.data['resources'][rname]['value'] = value
Olga Brani's avatar
Olga Brani committed
749
750


Olga Brani's avatar
Olga Brani committed
751
752
753
754
755
756
    def update_from_result_report(self, result):
        if result.is_success:
            for r in result.data:
                rname = r.get('name')
                if not rname in self.data['resources']:
                    self.data['resources'][rname] = {}
Olga Brani's avatar
Olga Brani committed
757

Olga Brani's avatar
Olga Brani committed
758
759
760
761
762
                self.data['resources'][rname].update(r)
                self.data['resources'][rname]['id'] = rname
                group = r.get('group')
                if not group in self.data['groups']:
                    self.data['groups'][group] = {}
Olga Brani's avatar
Olga Brani committed
763

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

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

Olga Brani's avatar
Olga Brani committed
769
770
771
    def get_groups_resources(self):
        for g in self.data['groups']:
            yield g, self.get_group_resources(g)
Olga Brani's avatar
Olga Brani committed
772

Olga Brani's avatar
Olga Brani committed
773
774
775
776
777
778
    def get_quota(self, group_quotas):
        for r, v in group_quotas.iteritems():
            rname = str(r)
            quota = self.data['resources'].get(rname)
            quota['value'] = v
            yield quota
Olga Brani's avatar
Olga Brani committed
779
780


Olga Brani's avatar
Olga Brani committed
781
782
783
784
785
    def get_policies(self, policies_data):
        for policy in policies_data:
            rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
            policy.update(self.data['resources'].get(rname))
            yield policy
Olga Brani's avatar
Olga Brani committed
786

Olga Brani's avatar
Olga Brani committed
787
788
    def __repr__(self):
        return self.data.__repr__()
Olga Brani's avatar
Olga Brani committed
789

Olga Brani's avatar
Olga Brani committed
790
791
    def __iter__(self, *args, **kwargs):
        return self.data.__iter__(*args, **kwargs)
Olga Brani's avatar
Olga Brani committed
792

Olga Brani's avatar
Olga Brani committed
793
794
    def __getitem__(self, *args, **kwargs):
        return self.data.__getitem__(*args, **kwargs)
Olga Brani's avatar
Olga Brani committed
795