views.py 45.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

44
from django.shortcuts import get_object_or_404
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
45
from django.contrib import messages
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
46
47
48
49
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
50
51
52
53
from django.http import (
    HttpResponse, HttpResponseBadRequest,
    HttpResponseForbidden, HttpResponseRedirect,
    HttpResponseBadRequest, Http404)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
54
from django.shortcuts import redirect
55
from django.template import RequestContext, loader as template_loader
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
56
57
from django.utils.http import urlencode
from django.utils.translation import ugettext as _
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
58
from django.views.generic.create_update import (
59
    apply_extra_context, lookup_object, delete_object, get_model_and_form_class)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
60
from django.views.generic.list_detail import object_list, object_detail
61
from django.core.xheaders import populate_xheaders
Olga Brani's avatar
Olga Brani committed
62
from django.core.exceptions import ValidationError, PermissionDenied
63
from django.template.loader import render_to_string
Olga Brani's avatar
Olga Brani committed
64
from django.views.decorators.http import require_http_methods
65
from django.db.models import Q
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
66
from django.core.exceptions import PermissionDenied
67

68
69
import astakos.im.messages as astakos_messages

70
from astakos.im.activation_backends import get_backend, SimpleBackend
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
71
from astakos.im.models import (
72
    AstakosUser, ApprovalTerms,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
73
74
    EmailChange, RESOURCE_SEPARATOR,
    AstakosUserAuthProvider, PendingThirdPartyUser,
75
76
77
    ProjectApplication, ProjectMembership, Project)
from astakos.im.util import (
    get_context, prepare_response, get_query, restrict_next)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
78
79
80
81
from astakos.im.forms import (
    LoginForm, InvitationForm, ProfileForm,
    FeedbackForm, SignApprovalTermsForm,
    EmailChangeForm,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
82
    ProjectApplicationForm, ProjectSortForm,
83
84
    AddProjectMembersForm, ProjectSearchForm,
    ProjectMembersSortForm)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
85
86
87
88
from astakos.im.functions import (
    send_feedback, SendMailError,
    logout as auth_logout,
    activate as activate_func,
89
    invite,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
90
    send_activation as send_activation_func,
91
92
    SendNotificationError,
    accept_membership, reject_membership, remove_membership,
93
    leave_project, join_project, enroll_member)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
94
95
96
from astakos.im.settings import (
    COOKIE_DOMAIN, LOGOUT_NEXT,
    LOGGING_LEVEL, PAGINATE_BY,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
97
98
    RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL,
    MODERATION_ENABLED)
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
99
from astakos.im import settings as astakos_settings
100
from astakos.im.api.callpoint import AstakosCallpoint
Olga Brani's avatar
Olga Brani committed
101
from astakos.im import auth_providers
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
102
from astakos.im.templatetags.filters import ResourcePresentation
Olga Brani's avatar
Olga Brani committed
103

104
105
logger = logging.getLogger(__name__)

106
callpoint = AstakosCallpoint()
Olga Brani's avatar
Olga Brani committed
107

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

Olga Brani's avatar
Olga Brani committed
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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:
137
                        #TODO: add session message
138
                        return HttpResponseRedirect(reverse('login'))
Olga Brani's avatar
Olga Brani committed
139
140
141
142
            return func(request, *args)
        return wrapper
    return decorator

143
144
145

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

158

159
160
def signed_terms_required(func):
    """
161
    Decorator checks whether the request.user is Anonymous and in that case
162
163
164
165
    redirects to `logout`.
    """
    @wraps(func)
    def wrapper(request, *args, **kwargs):
166
        if request.user.is_authenticated() and not request.user.signed_terms:
167
            params = urlencode({'next': request.build_absolute_uri(),
168
                                'show_form': ''})
169
170
171
172
173
            terms_uri = reverse('latest_terms') + '?' + params
            return HttpResponseRedirect(terms_uri)
        return func(request, *args, **kwargs)
    return wrapper

174

175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
def required_auth_methods_assigned(only_warn=False):
    """
    Decorator that checks whether the request.user has all required auth providers
    assigned.
    """
    required_providers = auth_providers.REQUIRED_PROVIDERS.keys()

    def decorator(func):
        if not required_providers:
            return func

        @wraps(func)
        def wrapper(request, *args, **kwargs):
            if request.user.is_authenticated():
                for required in required_providers:
                    if not request.user.has_auth_provider(required):
                        provider = auth_providers.get_provider(required)
                        if only_warn:
                            messages.error(request,
                                           _(astakos_messages.AUTH_PROVIDER_REQUIRED  % {
                                               'provider': provider.get_title_display}))
                        else:
                            return HttpResponseRedirect(reverse('edit_profile'))
            return func(request, *args, **kwargs)
        return wrapper
    return decorator


def valid_astakos_user_required(func):
    return signed_terms_required(required_auth_methods_assigned()(login_required(func)))


Olga Brani's avatar
Olga Brani committed
207
@require_http_methods(["GET", "POST"])
208
@signed_terms_required
209
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
210
    """
211
    If there is logged on user renders the profile page otherwise renders login page.
212

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
213
    **Arguments**
214

215
216
    ``login_template_name``
        A custom login template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
217
        this will default to ``im/login.html``.
218

219
220
    ``profile_template_name``
        A custom profile template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
221
        this will default to ``im/profile.html``.
222

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
223
224
    ``extra_context``
        An dictionary of variables to add to the template context.
225

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
226
    **Template:**
227

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
230
    """
231
    extra_context = extra_context or {}
232
233
    template_name = login_template_name
    if request.user.is_authenticated():
234
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
Olga Brani's avatar
Olga Brani committed
235

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
236
237
238
239
    third_party_token = request.GET.get('key', False)
    if third_party_token:
        messages.info(request, astakos_messages.AUTH_PROVIDER_LOGIN_TO_ADD)

240
241
242
243
244
    return render_response(
        template_name,
        login_form = LoginForm(request=request),
        context_instance = get_context(request, extra_context)
    )
245

246

Olga Brani's avatar
Olga Brani committed
247
@require_http_methods(["GET", "POST"])
248
@valid_astakos_user_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
249
@transaction.commit_manually
250
def invite(request, template_name='im/invitations.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
251
252
    """
    Allows a user to invite somebody else.
253

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
254
255
256
    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.
257

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
258
259
    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.
260

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
263
    **Arguments**
264

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
265
266
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
267
        this will default to ``im/invitations.html``.
268

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
269
270
    ``extra_context``
        An dictionary of variables to add to the template context.
271

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
272
    **Template:**
273

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
276
    **Settings:**
277

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
278
    The view expectes the following settings are defined:
279

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
280
281
    * LOGIN_URL: login uri
    """
282
    extra_context = extra_context or {}
283
284
    status = None
    message = None
285
    form = InvitationForm()
286

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
287
    inviter = request.user
288
    if request.method == 'POST':
289
        form = InvitationForm(request.POST)
290
        if inviter.invitations > 0:
291
292
            if form.is_valid():
                try:
Olga Brani's avatar
Olga Brani committed
293
294
                    email = form.cleaned_data.get('username')
                    realname = form.cleaned_data.get('realname')
295
                    invite(inviter, email, realname)
Olga Brani's avatar
Olga Brani committed
296
                    message = _(astakos_messages.INVITATION_SENT) % locals()
297
                    messages.success(request, message)
298
299
                except SendMailError, e:
                    message = e.message
300
                    messages.error(request, message)
301
                    transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
302
                except BaseException, e:
Olga Brani's avatar
Olga Brani committed
303
                    message = _(astakos_messages.GENERIC_ERROR)
304
                    messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
305
306
307
308
                    logger.exception(e)
                    transaction.rollback()
                else:
                    transaction.commit()
309
        else:
Olga Brani's avatar
Olga Brani committed
310
            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
311
            messages.error(request, message)
312

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
313
    sent = [{'email': inv.username,
314
315
             'realname': inv.realname,
             'is_consumed': inv.is_consumed}
316
            for inv in request.user.invitations_sent.all()]
317
    kwargs = {'inviter': inviter,
318
              'sent': sent}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
319
320
    context = get_context(request, extra_context, **kwargs)
    return render_response(template_name,
321
322
323
                           invitation_form=form,
                           context_instance=context)

324

Olga Brani's avatar
Olga Brani committed
325
@require_http_methods(["GET", "POST"])
326
@required_auth_methods_assigned(only_warn=True)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
327
@login_required
328
@signed_terms_required
329
def edit_profile(request, template_name='im/profile.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
330
331
    """
    Allows a user to edit his/her profile.
332

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
339
    **Arguments**
340

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
341
342
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
343
        this will default to ``im/profile.html``.
344

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
345
346
    ``extra_context``
        An dictionary of variables to add to the template context.
347

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
348
    **Template:**
349

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
352
    **Settings:**
353

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
354
    The view expectes the following settings are defined:
355

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
356
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
357
    """
358
    extra_context = extra_context or {}
359
360
361
362
    form = ProfileForm(
        instance=request.user,
        session_key=request.session.session_key
    )
363
    extra_context['next'] = request.GET.get('next')
364
    if request.method == 'POST':
365
366
367
368
369
        form = ProfileForm(
            request.POST,
            instance=request.user,
            session_key=request.session.session_key
        )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
370
        if form.is_valid():
371
            try:
372
373
                prev_token = request.user.auth_token
                user = form.save()
374
375
376
377
                form = ProfileForm(
                    instance=user,
                    session_key=request.session.session_key
                )
378
379
380
381
                next = restrict_next(
                    request.POST.get('next'),
                    domain=COOKIE_DOMAIN
                )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
382
383
                if next:
                    return redirect(next)
Olga Brani's avatar
Olga Brani committed
384
                msg = _(astakos_messages.PROFILE_UPDATED)
385
                messages.success(request, msg)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
386
            except ValueError, ve:
387
                messages.success(request, ve)
388
    elif request.method == "GET":
Olga Brani's avatar
Olga Brani committed
389
390
391
392
393
394
395
396
397
        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
398
    return render_response(template_name,
399
                           profile_form = form,
Olga Brani's avatar
Olga Brani committed
400
401
                           user_providers = user_providers,
                           user_available_providers = user_available_providers,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
402
                           context_instance = get_context(request,
403
                                                          extra_context))
404

405

406
@transaction.commit_manually
Olga Brani's avatar
Olga Brani committed
407
@require_http_methods(["GET", "POST"])
408
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
409
410
    """
    Allows a user to create a local account.
411

412
    In case of GET request renders a form for entering the user information.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
413
    In case of POST handles the signup.
414

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
415
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
416
417
418
    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);
419

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

423
    On unsuccessful creation, renders ``template_name`` with an error message.
424

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

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
435
436
    ``extra_context``
        An dictionary of variables to add to the template context.
437

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

440
    im/signup.html or ``template_name`` keyword argument.
441
    im/signup_complete.html or ``on_success`` keyword argument.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
442
    """
443
    extra_context = extra_context or {}
444
    if request.user.is_authenticated():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
445
        return HttpResponseRedirect(reverse('edit_profile'))
446

447
    provider = get_query(request).get('provider', 'local')
448
449
450
    if not auth_providers.get_provider(provider).is_available_for_create():
        raise PermissionDenied

451
452
453
454
455
456
    id = get_query(request).get('id')
    try:
        instance = AstakosUser.objects.get(id=id) if id else None
    except AstakosUser.DoesNotExist:
        instance = None

457
    third_party_token = request.REQUEST.get('third_party_token', None)
458
459
460
461
462
    if third_party_token:
        pending = get_object_or_404(PendingThirdPartyUser,
                                    token=third_party_token)
        provider = pending.provider
        instance = pending.get_user_instance()
463

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
464
    try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
465
466
        if not backend:
            backend = get_backend(request)
467
        form = backend.get_signup_form(provider, instance)
468
    except Exception, e:
469
        form = SimpleBackend(request).get_signup_form(provider)
470
        messages.error(request, e)
471
472
    if request.method == 'POST':
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
473
            user = form.save(commit=False)
474
475
476
            try:
                result = backend.handle_activation(user)
                status = messages.SUCCESS
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
477
                message = result.message
Olga Brani's avatar
Olga Brani committed
478
479
480

                form.store_user(user, request)

481
482
483
484
                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)
485
                        msg = 'Additional email: %s saved for user %s.' % (
486
487
488
                            additional_email,
                            user.email
                        )
489
                        logger._log(LOGGING_LEVEL, msg, [])
490
491
                if user and user.is_active:
                    next = request.POST.get('next', '')
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
492
                    response = prepare_response(request, user, next=next)
493
                    transaction.commit()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
494
                    return response
495
                messages.add_message(request, status, message)
Olga Brani's avatar
Olga Brani committed
496
                transaction.commit()
497
498
499
500
501
502
503
                return render_response(
                    on_success,
                    context_instance=get_context(
                        request,
                        extra_context
                    )
                )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
504
            except SendMailError, e:
505
                logger.exception(e)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
506
507
                status = messages.ERROR
                message = e.message
508
                messages.error(request, message)
509
                transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
510
            except BaseException, e:
511
                logger.exception(e)
Olga Brani's avatar
Olga Brani committed
512
                message = _(astakos_messages.GENERIC_ERROR)
513
                messages.error(request, message)
514
                logger.exception(e)
515
                transaction.rollback()
516
    return render_response(template_name,
517
                           signup_form=form,
518
                           third_party_token=third_party_token,
519
                           provider=provider,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
520
                           context_instance=get_context(request, extra_context))
521

522

Olga Brani's avatar
Olga Brani committed
523
@require_http_methods(["GET", "POST"])
524
@required_auth_methods_assigned(only_warn=True)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
525
@login_required
526
@signed_terms_required
527
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
528
529
    """
    Allows a user to send feedback.
530

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
536
    **Arguments**
537

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
538
539
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
540
        this will default to ``im/feedback.html``.
541

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
542
543
    ``extra_context``
        An dictionary of variables to add to the template context.
544

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
545
    **Template:**
546

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
549
    **Settings:**
550

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
551
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
552
    """
553
    extra_context = extra_context or {}
554
    if request.method == 'GET':
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
555
556
557
558
        form = FeedbackForm()
    if request.method == 'POST':
        if not request.user:
            return HttpResponse('Unauthorized', status=401)
559

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
560
561
        form = FeedbackForm(request.POST)
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
562
            msg = form.cleaned_data['feedback_msg']
563
            data = form.cleaned_data['feedback_data']
564
            try:
565
566
                send_feedback(msg, data, request.user, email_template_name)
            except SendMailError, e:
567
                messages.error(request, message)
568
            else:
Olga Brani's avatar
Olga Brani committed
569
                message = _(astakos_messages.FEEDBACK_SENT)
570
                messages.success(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
571
    return render_response(template_name,
572
573
574
                           feedback_form=form,
                           context_instance=get_context(request, extra_context))

575

576
@require_http_methods(["GET"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
577
@signed_terms_required
578
def logout(request, template='registration/logged_out.html', extra_context=None):
579
    """
580
    Wraps `django.contrib.auth.logout`.
581
    """
582
    extra_context = extra_context or {}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
583
    response = HttpResponse()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
584
585
586
    if request.user.is_authenticated():
        email = request.user.email
        auth_logout(request)
587
588
589
590
591
    else:
        response['Location'] = reverse('index')
        response.status_code = 301
        return response

592
593
594
595
    next = restrict_next(
        request.GET.get('next'),
        domain=COOKIE_DOMAIN
    )
596

597
598
599
    if next:
        response['Location'] = next
        response.status_code = 302
600
601
602
    elif LOGOUT_NEXT:
        response['Location'] = LOGOUT_NEXT
        response.status_code = 301
603
    else:
604
        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
605
606
        response['Location'] = reverse('index')
        response.status_code = 301
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
607
    return response
608

609

Olga Brani's avatar
Olga Brani committed
610
@require_http_methods(["GET", "POST"])
611
@transaction.commit_manually
612
613
def activate(request, greeting_email_template_name='im/welcome_email.txt',
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
614
    """
615
616
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
    and renews the user token.
617

618
619
    The view uses commit_manually decorator in order to ensure the user state will be updated
    only if the email will be send successfully.
620
621
622
623
624
625
    """
    token = request.GET.get('auth')
    next = request.GET.get('next')
    try:
        user = AstakosUser.objects.get(auth_token=token)
    except AstakosUser.DoesNotExist:
626
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
627

628
    if user.is_active:
Olga Brani's avatar
Olga Brani committed
629
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
630
        messages.error(request, message)
631
        return index(request)
632

633
    try:
634
        activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
635
636
        response = prepare_response(request, user, next, renew=True)
        transaction.commit()
637
        return response
638
639
640
641
    except SendMailError, e:
        message = e.message
        messages.add_message(request, messages.ERROR, message)
        transaction.rollback()
642
        return index(request)
643
644
    except BaseException, e:
        status = messages.ERROR
645
        message = _(astakos_messages.GENERIC_ERROR)
646
647
648
        messages.add_message(request, messages.ERROR, message)
        logger.exception(e)
        transaction.rollback()
649
        return index(request)
650

651

Olga Brani's avatar
Olga Brani committed
652
@require_http_methods(["GET", "POST"])
653
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
654
    extra_context = extra_context or {}
655
656
657
658
659
660
661
662
663
    term = None
    terms = None
    if not term_id:
        try:
            term = ApprovalTerms.objects.order_by('-id')[0]
        except IndexError:
            pass
    else:
        try:
664
665
            term = ApprovalTerms.objects.get(id=term_id)
        except ApprovalTerms.DoesNotExist, e:
666
            pass
667

668
    if not term:
Olga Brani's avatar
Olga Brani committed
669
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
670
        return HttpResponseRedirect(reverse('index'))
671
672
    f = open(term.location, 'r')
    terms = f.read()
673

674
    if request.method == 'POST':
675
676
677
678
        next = restrict_next(
            request.POST.get('next'),
            domain=COOKIE_DOMAIN
        )
679
        if not next:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
680
            next = reverse('index')
681
682
683
        form = SignApprovalTermsForm(request.POST, instance=request.user)
        if not form.is_valid():
            return render_response(template_name,
684
685
686
                                   terms=terms,
                                   approval_terms_form=form,
                                   context_instance=get_context(request, extra_context))
687
688
689
        user = form.save()
        return HttpResponseRedirect(next)
    else:
690
        form = None
691
        if request.user.is_authenticated() and not request.user.signed_terms:
692
            form = SignApprovalTermsForm(instance=request.user)
693
        return render_response(template_name,
694
695
696
697
                               terms=terms,
                               approval_terms_form=form,
                               context_instance=get_context(request, extra_context))

698

Olga Brani's avatar
Olga Brani committed
699
@require_http_methods(["GET", "POST"])
700
@valid_astakos_user_required
701
702
703
704
705
@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',
706
                 extra_context=None):
707
    extra_context = extra_context or {}
708
709


710
711
712
713
    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
714
                msg = _(astakos_messages.EMAIL_CHANGED)
715
                messages.success(request, msg)
716
717
718
                auth_logout(request)
                response = prepare_response(request, user)
                transaction.commit()
719
                return HttpResponseRedirect(reverse('edit_profile'))
720
        except ValueError, e:
721
            messages.error(request, e)
722
723
724
            transaction.rollback()
            return HttpResponseRedirect(reverse('index'))

725
        return render_response(confirm_template_name,
726
727
                               modified_user=user if 'user' in locals() \
                               else None, context_instance=get_context(request,
728
729
                                                            extra_context))

730
731
    if not request.user.is_authenticated():
        path = quote(request.get_full_path())
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
732
        url = request.build_absolute_uri(reverse('index'))
733
        return HttpResponseRedirect(url + '?next=' + path)
734
735
736
737
738
739
740
741
742

    # clean up expired email changes
    if request.user.email_change_is_pending():
        change = request.user.emailchanges.get()
        if change.activation_key_expired():
            change.delete()
            transaction.commit()
            return HttpResponseRedirect(reverse('email_change'))

743
744
745
    form = EmailChangeForm(request.POST or None)
    if request.method == 'POST' and form.is_valid():
        try:
746
747
            # delete pending email changes
            request.user.emailchanges.all().delete()
748
749
750
            ec = form.save(email_template_name, request)
        except SendMailError, e:
            msg = e
751
            messages.error(request, msg)
752
            transaction.rollback()
753
            return HttpResponseRedirect(reverse('edit_profile'))
754
        else:
Olga Brani's avatar
Olga Brani committed
755
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
756
            messages.success(request, msg)
757
            transaction.commit()
758
759
760
761
762
            return HttpResponseRedirect(reverse('edit_profile'))

    if request.user.email_change_is_pending():
        messages.warning(request, astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)

763
764
765
766
767
    return render_response(
        form_template_name,
        form=form,
        context_instance=get_context(request, extra_context)
    )
768
769
770


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

772
773
774
775
    if request.user.is_authenticated():
        messages.error(request, _(astakos_messages.ALREADY_LOGGED_IN))
        return HttpResponseRedirect(reverse('edit_profile'))

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
776
    if astakos_settings.MODERATION_ENABLED:
Olga Brani's avatar
Olga Brani committed
777
778
        raise PermissionDenied

779
780
781
782
    extra_context = extra_context or {}
    try:
        u = AstakosUser.objects.get(id=user_id)
    except AstakosUser.DoesNotExist:
783
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
784
785
786
    else:
        try:
            send_activation_func(u)
787
            msg = _(astakos_messages.ACTIVATION_SENT)
788
789
790
791
792
            messages.success(request, msg)
        except SendMailError, e:
            messages.error(request, e)
    return render_response(
        template_name,
793
        login_form = LoginForm(request=request),
794
795
796
797
798
        context_instance = get_context(
            request,
            extra_context
        )
    )
799

800
801

@require_http_methods(["GET"])
802
@valid_astakos_user_required
803
def resource_usage(request):
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed