views.py 60.6 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 _
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
56
from django.views.generic.create_update import (
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
57
    create_object, update_object, delete_object, get_model_and_form_class
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
58
)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
59
from django.views.generic.list_detail import object_list, object_detail
60
from django.core.xheaders import populate_xheaders
Olga Brani's avatar
Olga Brani committed
61
from django.core.exceptions import ValidationError, PermissionDenied
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 django.db.models import Q
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
65
from django.core.exceptions import PermissionDenied
66

67
from astakos.im.activation_backends import get_backend, SimpleBackend
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
68
69
70
71
from astakos.im.models import (
    AstakosUser, ApprovalTerms, AstakosGroup,
    EmailChange, GroupKind, Membership,
    RESOURCE_SEPARATOR, AstakosUserAuthProvider,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
72
    ProjectApplication, ProjectMembership, Project
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
73
)
74
from astakos.im.util import get_context, prepare_response, get_query, restrict_next
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
75
76
77
78
79
80
81
82
83
from astakos.im.forms import (
    LoginForm, InvitationForm, ProfileForm,
    FeedbackForm, SignApprovalTermsForm,
    EmailChangeForm,
    AstakosGroupCreationForm, AstakosGroupSearchForm,
    AstakosGroupUpdateForm, AddGroupMembersForm,
    MembersSortForm, AstakosGroupSortForm,
    TimelineForm, PickResourceForm,
    AstakosGroupCreationSummaryForm,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
84
85
    ProjectApplicationForm, ProjectSortForm,
    AddProjectMembersForm, ProjectGroupSearchForm
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
86
)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
87
88
89
90
91
92
93
from astakos.im.functions import (
    send_feedback, SendMailError,
    logout as auth_logout,
    activate as activate_func,
    send_activation as send_activation_func,
    send_group_creation_notification,
    SendNotificationError)
Olga Brani's avatar
Fixes    
Olga Brani committed
94
from astakos.im.endpoints.qh import timeline_charge
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
95
96
97
98
99
from astakos.im.settings import (
    COOKIE_DOMAIN, LOGOUT_NEXT,
    LOGGING_LEVEL, PAGINATE_BY,
    RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL
)
root's avatar
root committed
100
#from astakos.im.tasks import request_billing
101
from astakos.im.api.callpoint import AstakosCallpoint
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
102
from astakos.im.notifications import NotificationError
103

Olga Brani's avatar
Olga Brani committed
104
import astakos.im.messages as astakos_messages
Olga Brani's avatar
Olga Brani committed
105
106
from astakos.im import settings
from astakos.im import auth_providers
Olga Brani's avatar
Olga Brani committed
107

108
109
logger = logging.getLogger(__name__)

110
callpoint = AstakosCallpoint()
Olga Brani's avatar
Olga Brani committed
111

112
def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
113
114
115
116
117
    """
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
    keyword argument and returns an ``django.http.HttpResponse`` with the
    specified ``status``.
    """
118
    if tab is None:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
119
        tab = template.partition('_')[0].partition('.html')[0]
120
    kwargs.setdefault('tab', tab)
121
    html = template_loader.render_to_string(
122
        template, kwargs, context_instance=context_instance)
123
124
    response = HttpResponse(html, status=status)
    return response
125

Olga Brani's avatar
Olga Brani committed
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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

146
147
148

def requires_anonymous(func):
    """
149
    Decorator checkes whether the request.user is not Anonymous and in that case
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
150
    redirects to `logout`.
151
152
153
154
155
    """
    @wraps(func)
    def wrapper(request, *args):
        if not request.user.is_anonymous():
            next = urlencode({'next': request.build_absolute_uri()})
156
157
            logout_uri = reverse(logout) + '?' + next
            return HttpResponseRedirect(logout_uri)
158
159
160
        return func(request, *args)
    return wrapper

161

162
163
164
165
166
167
168
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):
169
        if request.user.is_authenticated() and not request.user.signed_terms:
170
            params = urlencode({'next': request.build_absolute_uri(),
171
                                'show_form': ''})
172
173
174
175
176
            terms_uri = reverse('latest_terms') + '?' + params
            return HttpResponseRedirect(terms_uri)
        return func(request, *args, **kwargs)
    return wrapper

177

Olga Brani's avatar
Olga Brani committed
178
@require_http_methods(["GET", "POST"])
179
@signed_terms_required
180
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
181
    """
182
    If there is logged on user renders the profile page otherwise renders login page.
183

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

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

190
191
    ``profile_template_name``
        A custom profile template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
192
        this will default to ``im/profile.html``.
193

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
194
195
    ``extra_context``
        An dictionary of variables to add to the template context.
196

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
197
    **Template:**
198

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
201
    """
202
    extra_context = extra_context or {}
203
204
    template_name = login_template_name
    if request.user.is_authenticated():
205
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
Olga Brani's avatar
Olga Brani committed
206

207
208
209
210
211
    return render_response(
        template_name,
        login_form = LoginForm(request=request),
        context_instance = get_context(request, extra_context)
    )
212

213

Olga Brani's avatar
Olga Brani committed
214
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
215
@login_required
216
@signed_terms_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
217
@transaction.commit_manually
218
def invite(request, template_name='im/invitations.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
219
220
    """
    Allows a user to invite somebody else.
221

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
222
223
224
    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.
225

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
226
227
    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.
228

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
231
    **Arguments**
232

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
233
234
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
235
        this will default to ``im/invitations.html``.
236

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
237
238
    ``extra_context``
        An dictionary of variables to add to the template context.
239

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
240
    **Template:**
241

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
244
    **Settings:**
245

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
246
    The view expectes the following settings are defined:
247

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
248
249
    * LOGIN_URL: login uri
    """
250
    extra_context = extra_context or {}
251
252
    status = None
    message = None
253
    form = InvitationForm()
254

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
255
    inviter = request.user
256
    if request.method == 'POST':
257
        form = InvitationForm(request.POST)
258
        if inviter.invitations > 0:
259
260
            if form.is_valid():
                try:
Olga Brani's avatar
Olga Brani committed
261
262
263
                    email = form.cleaned_data.get('username')
                    realname = form.cleaned_data.get('realname')
                    inviter.invite(email, realname)
Olga Brani's avatar
Olga Brani committed
264
                    message = _(astakos_messages.INVITATION_SENT) % locals()
265
                    messages.success(request, message)
266
267
                except SendMailError, e:
                    message = e.message
268
                    messages.error(request, message)
269
                    transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
270
                except BaseException, e:
Olga Brani's avatar
Olga Brani committed
271
                    message = _(astakos_messages.GENERIC_ERROR)
272
                    messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
273
274
275
276
                    logger.exception(e)
                    transaction.rollback()
                else:
                    transaction.commit()
277
        else:
Olga Brani's avatar
Olga Brani committed
278
            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
279
            messages.error(request, message)
280

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
281
    sent = [{'email': inv.username,
282
283
             'realname': inv.realname,
             'is_consumed': inv.is_consumed}
284
            for inv in request.user.invitations_sent.all()]
285
    kwargs = {'inviter': inviter,
286
              'sent': sent}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
287
288
    context = get_context(request, extra_context, **kwargs)
    return render_response(template_name,
289
290
291
                           invitation_form=form,
                           context_instance=context)

292

Olga Brani's avatar
Olga Brani committed
293
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
294
@login_required
295
@signed_terms_required
296
def edit_profile(request, template_name='im/profile.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
297
298
    """
    Allows a user to edit his/her profile.
299

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
306
    **Arguments**
307

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
308
309
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
310
        this will default to ``im/profile.html``.
311

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
312
313
    ``extra_context``
        An dictionary of variables to add to the template context.
314

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
315
    **Template:**
316

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
319
    **Settings:**
320

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
321
    The view expectes the following settings are defined:
322

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
323
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
324
    """
325
    extra_context = extra_context or {}
326
327
328
329
    form = ProfileForm(
        instance=request.user,
        session_key=request.session.session_key
    )
330
    extra_context['next'] = request.GET.get('next')
331
    if request.method == 'POST':
332
333
334
335
336
        form = ProfileForm(
            request.POST,
            instance=request.user,
            session_key=request.session.session_key
        )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
337
        if form.is_valid():
338
            try:
339
340
                prev_token = request.user.auth_token
                user = form.save()
341
342
343
344
                form = ProfileForm(
                    instance=user,
                    session_key=request.session.session_key
                )
345
346
347
348
                next = restrict_next(
                    request.POST.get('next'),
                    domain=COOKIE_DOMAIN
                )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
349
350
                if next:
                    return redirect(next)
Olga Brani's avatar
Olga Brani committed
351
                msg = _(astakos_messages.PROFILE_UPDATED)
352
                messages.success(request, msg)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
353
            except ValueError, ve:
354
                messages.success(request, ve)
355
    elif request.method == "GET":
Olga Brani's avatar
Olga Brani committed
356
357
358
359
360
361
362
363
364
        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
365
    return render_response(template_name,
366
                           profile_form = form,
Olga Brani's avatar
Olga Brani committed
367
368
                           user_providers = user_providers,
                           user_available_providers = user_available_providers,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
369
                           context_instance = get_context(request,
370
                                                          extra_context))
371

372

373
@transaction.commit_manually
Olga Brani's avatar
Olga Brani committed
374
@require_http_methods(["GET", "POST"])
375
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
376
377
    """
    Allows a user to create a local account.
378

379
    In case of GET request renders a form for entering the user information.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
380
    In case of POST handles the signup.
381

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
382
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
383
384
385
    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);
386

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

390
    On unsuccessful creation, renders ``template_name`` with an error message.
391

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
392
    **Arguments**
393

394
395
    ``template_name``
        A custom template to render. This is optional;
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
396
        if not specified, this will default to ``im/signup.html``.
397

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
402
403
    ``extra_context``
        An dictionary of variables to add to the template context.
404

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
405
    **Template:**
406

407
    im/signup.html or ``template_name`` keyword argument.
408
    im/signup_complete.html or ``on_success`` keyword argument.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
409
    """
410
    extra_context = extra_context or {}
411
    if request.user.is_authenticated():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
412
        return HttpResponseRedirect(reverse('edit_profile'))
413

414
    provider = get_query(request).get('provider', 'local')
Olga Brani's avatar
Olga Brani committed
415
416
417
    if not auth_providers.get_provider(provider).is_available_for_create():
        raise PermissionDenied

418
419
420
421
422
423
    id = get_query(request).get('id')
    try:
        instance = AstakosUser.objects.get(id=id) if id else None
    except AstakosUser.DoesNotExist:
        instance = None

424
425
    third_party_token = request.REQUEST.get('third_party_token', None)

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
426
    try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
427
428
        if not backend:
            backend = get_backend(request)
429
        form = backend.get_signup_form(provider, instance)
430
    except Exception, e:
431
        form = SimpleBackend(request).get_signup_form(provider)
432
        messages.error(request, e)
433
434
    if request.method == 'POST':
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
435
            user = form.save(commit=False)
436
437
438
            try:
                result = backend.handle_activation(user)
                status = messages.SUCCESS
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
439
                message = result.message
Olga Brani's avatar
Olga Brani committed
440
441
442

                form.store_user(user, request)

443
444
445
446
                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)
447
                        msg = 'Additional email: %s saved for user %s.' % (
448
449
450
                            additional_email,
                            user.email
                        )
451
                        logger._log(LOGGING_LEVEL, msg, [])
452
453
                if user and user.is_active:
                    next = request.POST.get('next', '')
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
454
                    response = prepare_response(request, user, next=next)
455
                    transaction.commit()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
456
                    return response
457
                messages.add_message(request, status, message)
Olga Brani's avatar
Olga Brani committed
458
                transaction.commit()
459
460
461
462
463
464
465
                return render_response(
                    on_success,
                    context_instance=get_context(
                        request,
                        extra_context
                    )
                )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
466
            except SendMailError, e:
467
                logger.exception(e)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
468
469
                status = messages.ERROR
                message = e.message
470
                messages.error(request, message)
471
                transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
472
            except BaseException, e:
473
                logger.exception(e)
Olga Brani's avatar
Olga Brani committed
474
                message = _(astakos_messages.GENERIC_ERROR)
475
                messages.error(request, message)
476
                logger.exception(e)
477
                transaction.rollback()
478
    return render_response(template_name,
479
                           signup_form=form,
480
                           third_party_token=third_party_token,
481
                           provider=provider,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
482
                           context_instance=get_context(request, extra_context))
483

484

Olga Brani's avatar
Olga Brani committed
485
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
486
@login_required
487
@signed_terms_required
488
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
489
490
    """
    Allows a user to send feedback.
491

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
497
    **Arguments**
498

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
499
500
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
501
        this will default to ``im/feedback.html``.
502

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
503
504
    ``extra_context``
        An dictionary of variables to add to the template context.
505

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
506
    **Template:**
507

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
510
    **Settings:**
511

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
512
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
513
    """
514
    extra_context = extra_context or {}
515
    if request.method == 'GET':
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
516
517
518
519
        form = FeedbackForm()
    if request.method == 'POST':
        if not request.user:
            return HttpResponse('Unauthorized', status=401)
520

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
521
522
        form = FeedbackForm(request.POST)
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
523
            msg = form.cleaned_data['feedback_msg']
524
            data = form.cleaned_data['feedback_data']
525
            try:
526
527
                send_feedback(msg, data, request.user, email_template_name)
            except SendMailError, e:
528
                messages.error(request, message)
529
            else:
Olga Brani's avatar
Olga Brani committed
530
                message = _(astakos_messages.FEEDBACK_SENT)
531
                messages.success(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
532
    return render_response(template_name,
533
534
535
                           feedback_form=form,
                           context_instance=get_context(request, extra_context))

536

537
@require_http_methods(["GET"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
538
@signed_terms_required
539
def logout(request, template='registration/logged_out.html', extra_context=None):
540
    """
541
    Wraps `django.contrib.auth.logout`.
542
    """
543
    extra_context = extra_context or {}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
544
    response = HttpResponse()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
545
546
547
    if request.user.is_authenticated():
        email = request.user.email
        auth_logout(request)
548
549
550
551
    next = restrict_next(
        request.GET.get('next'),
        domain=COOKIE_DOMAIN
    )
552
553
554
    if next:
        response['Location'] = next
        response.status_code = 302
555
556
557
    elif LOGOUT_NEXT:
        response['Location'] = LOGOUT_NEXT
        response.status_code = 301
558
    else:
559
        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
560
561
        context = get_context(request, extra_context)
        response.write(render_to_string(template, context_instance=context))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
562
    return response
563

564

Olga Brani's avatar
Olga Brani committed
565
@require_http_methods(["GET", "POST"])
566
@transaction.commit_manually
567
568
def activate(request, greeting_email_template_name='im/welcome_email.txt',
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
569
    """
570
571
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
    and renews the user token.
572

573
574
    The view uses commit_manually decorator in order to ensure the user state will be updated
    only if the email will be send successfully.
575
576
577
578
579
580
    """
    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
581
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
582

583
    if user.is_active:
Olga Brani's avatar
Olga Brani committed
584
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
585
        messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
586
        return index(request)
587

588
    try:
589
590
591
592
593
594
595
596
597
598
599
        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
600
        message = _(astakos_messages.GENERIC_ERROR)
601
602
603
604
        messages.add_message(request, messages.ERROR, message)
        logger.exception(e)
        transaction.rollback()
        return index(request)
605

606

Olga Brani's avatar
Olga Brani committed
607
@require_http_methods(["GET", "POST"])
608
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
609
    extra_context = extra_context or {}
610
611
612
613
614
615
616
617
618
    term = None
    terms = None
    if not term_id:
        try:
            term = ApprovalTerms.objects.order_by('-id')[0]
        except IndexError:
            pass
    else:
        try:
619
620
            term = ApprovalTerms.objects.get(id=term_id)
        except ApprovalTerms.DoesNotExist, e:
621
            pass
622

623
    if not term:
Olga Brani's avatar
Olga Brani committed
624
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
625
        return HttpResponseRedirect(reverse('index'))
626
627
    f = open(term.location, 'r')
    terms = f.read()
628

629
    if request.method == 'POST':
630
631
632
633
        next = restrict_next(
            request.POST.get('next'),
            domain=COOKIE_DOMAIN
        )
634
        if not next:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
635
            next = reverse('index')
636
637
638
        form = SignApprovalTermsForm(request.POST, instance=request.user)
        if not form.is_valid():
            return render_response(template_name,
639
640
641
                                   terms=terms,
                                   approval_terms_form=form,
                                   context_instance=get_context(request, extra_context))
642
643
644
        user = form.save()
        return HttpResponseRedirect(next)
    else:
645
        form = None
646
        if request.user.is_authenticated() and not request.user.signed_terms:
647
            form = SignApprovalTermsForm(instance=request.user)
648
        return render_response(template_name,
649
650
651
652
                               terms=terms,
                               approval_terms_form=form,
                               context_instance=get_context(request, extra_context))

653

Olga Brani's avatar
Olga Brani committed
654
@require_http_methods(["GET", "POST"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
655
@login_required
656
@signed_terms_required
657
658
659
660
661
@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',
662
                 extra_context=None):
663
    extra_context = extra_context or {}
664
665
666
667
    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
668
                msg = _(astakos_messages.EMAIL_CHANGED)
669
                messages.success(request, msg)
670
671
672
673
674
                auth_logout(request)
                response = prepare_response(request, user)
                transaction.commit()
                return response
        except ValueError, e:
675
            messages.error(request, e)
676
        return render_response(confirm_template_name,
677
678
679
680
681
                               modified_user=user if 'user' in locals(
                               ) else None,
                               context_instance=get_context(request,
                                                            extra_context))

682
683
    if not request.user.is_authenticated():
        path = quote(request.get_full_path())
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
684
        url = request.build_absolute_uri(reverse('index'))
685
686
687
688
689
690
691
        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
692
            messages.error(request, msg)
693
694
            transaction.rollback()
        except IntegrityError, e:
Olga Brani's avatar
Olga Brani committed
695
            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
696
            messages.error(request, msg)
697
        else:
Olga Brani's avatar
Olga Brani committed
698
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
699
            messages.success(request, msg)
700
            transaction.commit()
701
702
703
704
705
    return render_response(
        form_template_name,
        form=form,
        context_instance=get_context(request, extra_context)
    )
706
707
708


def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
Olga Brani's avatar
Olga Brani committed
709
710
711
712

    if settings.MODERATION_ENABLED:
        raise PermissionDenied

713
714
715
716
    extra_context = extra_context or {}
    try:
        u = AstakosUser.objects.get(id=user_id)
    except AstakosUser.DoesNotExist:
717
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
718
719
720
    else:
        try:
            send_activation_func(u)
721
            msg = _(astakos_messages.ACTIVATION_SENT)
722
723
724
725
726
            messages.success(request, msg)
        except SendMailError, e:
            messages.error(request, e)
    return render_response(
        template_name,
727
        login_form = LoginForm(request=request),
728
729
730
731
732
        context_instance = get_context(
            request,
            extra_context
        )
    )
733

Olga Brani's avatar
Olga Brani committed
734
class ResourcePresentation():
Olga Brani's avatar
Olga Brani committed
735

Olga Brani's avatar
Olga Brani committed
736
737
    def __init__(self, data):
        self.data = data
Olga Brani's avatar
Olga Brani committed
738

Olga Brani's avatar
Olga Brani committed
739
740
741
742
743
744
    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
745

Olga Brani's avatar
Olga Brani committed
746
747
748
749
750
                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
751

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

Olga Brani's avatar
Olga Brani committed
754
755
756
757
758
    def test(self, quota_dict):
        for k, v in quota_dict.iteritems():
            rname = k
            value = v
            if not rname in self.data['resources']:
759
                self.data['resources'][rname] = {}
Olga Brani's avatar
Olga Brani committed
760
761


Olga Brani's avatar
Olga Brani committed
762
            self.data['resources'][rname]['value'] = value
Olga Brani's avatar
Olga Brani committed
763
764


Olga Brani's avatar
Olga Brani committed
765
766
767
768
769
770
    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
771

Olga Brani's avatar
Olga Brani committed
772
773
774
775
776
                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
777

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

Olga Brani's avatar
Olga Brani committed
780
781
    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
782

Olga Brani's avatar
Olga Brani committed
783
784
785
    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
786

Olga Brani's avatar
Olga Brani committed
787
    def get_quota(self, group_quotas):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
788
        for r, v in group_quotas:
Olga Brani's avatar
Olga Brani committed
789
790
791
792
            rname = str(r)
            quota = self.data['resources'].get(rname)
            quota['value'] = v
            yield quota
Olga Brani's avatar
Olga Brani committed
793
794


Olga Brani's avatar
Olga Brani committed
795
796
797
798
799
    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
800

Olga Brani's avatar
Olga Brani committed
801
802
    def __repr__(self):
        return self.data.__repr__()
Olga Brani's avatar
Olga Brani committed
803

Olga Brani's avatar
Olga Brani committed
804
805
    def __iter__(self, *args, **kwargs):
        return self.data.__iter__(*args, **kwargs)
Olga Brani's avatar
Olga Brani committed
806

Olga Brani's avatar
Olga Brani committed