views.py 46.4 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
45
from django_tables2 import RequestConfig

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

71
72
import astakos.im.messages as astakos_messages

73
from astakos.im.activation_backends import get_backend, SimpleBackend
74
from astakos.im import tables
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
75
from astakos.im.models import (
76
    AstakosUser, ApprovalTerms,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
77
78
    EmailChange, RESOURCE_SEPARATOR,
    AstakosUserAuthProvider, PendingThirdPartyUser,
79
80
81
    ProjectApplication, ProjectMembership, Project)
from astakos.im.util import (
    get_context, prepare_response, get_query, restrict_next)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
82
83
84
85
from astakos.im.forms import (
    LoginForm, InvitationForm, ProfileForm,
    FeedbackForm, SignApprovalTermsForm,
    EmailChangeForm,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
86
    ProjectApplicationForm, ProjectSortForm,
87
88
    AddProjectMembersForm, ProjectSearchForm,
    ProjectMembersSortForm)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
89
90
91
92
from astakos.im.functions import (
    send_feedback, SendMailError,
    logout as auth_logout,
    activate as activate_func,
93
    invite,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
94
    send_activation as send_activation_func,
95
96
    SendNotificationError,
    accept_membership, reject_membership, remove_membership,
97
    leave_project, join_project, enroll_member)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
98
99
100
from astakos.im.settings import (
    COOKIE_DOMAIN, LOGOUT_NEXT,
    LOGGING_LEVEL, PAGINATE_BY,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
101
102
    RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL,
    MODERATION_ENABLED)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
103
from astakos.im.api import get_services
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
104
from astakos.im import settings as astakos_settings
105
from astakos.im.api.callpoint import AstakosCallpoint
Olga Brani's avatar
Olga Brani committed
106
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
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:
141
                        #TODO: add session message
142
                        return HttpResponseRedirect(reverse('login'))
Olga Brani's avatar
Olga Brani committed
143
144
145
146
            return func(request, *args)
        return wrapper
    return decorator

147
148
149

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

162

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

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
207
208
209
210
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
211
@require_http_methods(["GET", "POST"])
212
@signed_terms_required
213
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
214
    """
215
    If there is logged on user renders the profile page otherwise renders login page.
216

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
217
    **Arguments**
218

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

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

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

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

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

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

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
240
241
242
243
    third_party_token = request.GET.get('key', False)
    if third_party_token:
        messages.info(request, astakos_messages.AUTH_PROVIDER_LOGIN_TO_ADD)

244
245
246
247
248
    return render_response(
        template_name,
        login_form = LoginForm(request=request),
        context_instance = get_context(request, extra_context)
    )
249

250

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
262
263
    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.
264

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
267
    **Arguments**
268

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
269
270
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
271
        this will default to ``im/invitations.html``.
272

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
273
274
    ``extra_context``
        An dictionary of variables to add to the template context.
275

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
280
    **Settings:**
281

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
282
    The view expectes the following settings are defined:
283

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
284
285
    * LOGIN_URL: login uri
    """
286
    extra_context = extra_context or {}
287
288
    status = None
    message = None
289
    form = InvitationForm()
290

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

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

328

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

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

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

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
349
350
    ``extra_context``
        An dictionary of variables to add to the template context.
351

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
356
    **Settings:**
357

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
358
    The view expectes the following settings are defined:
359

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

416

417
@transaction.commit_manually
Olga Brani's avatar
Olga Brani committed
418
@require_http_methods(["GET", "POST"])
419
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
420
421
    """
    Allows a user to create a local account.
422

423
    In case of GET request renders a form for entering the user information.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
424
    In case of POST handles the signup.
425

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
426
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
427
428
429
    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);
430

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

434
    On unsuccessful creation, renders ``template_name`` with an error message.
435

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
436
    **Arguments**
437

438
439
    ``template_name``
        A custom template to render. This is optional;
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
440
        if not specified, this will default to ``im/signup.html``.
441

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
446
447
    ``extra_context``
        An dictionary of variables to add to the template context.
448

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
449
    **Template:**
450

451
    im/signup.html or ``template_name`` keyword argument.
452
    im/signup_complete.html or ``on_success`` keyword argument.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
453
    """
454
    extra_context = extra_context or {}
455
    if request.user.is_authenticated():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
456
        return HttpResponseRedirect(reverse('edit_profile'))
457

458
    provider = get_query(request).get('provider', 'local')
459
460
461
    if not auth_providers.get_provider(provider).is_available_for_create():
        raise PermissionDenied

462
463
464
465
466
467
    id = get_query(request).get('id')
    try:
        instance = AstakosUser.objects.get(id=id) if id else None
    except AstakosUser.DoesNotExist:
        instance = None

468
    third_party_token = request.REQUEST.get('third_party_token', None)
469
470
471
472
473
    if third_party_token:
        pending = get_object_or_404(PendingThirdPartyUser,
                                    token=third_party_token)
        provider = pending.provider
        instance = pending.get_user_instance()
474

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
475
    try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
476
477
        if not backend:
            backend = get_backend(request)
478
        form = backend.get_signup_form(provider, instance)
479
    except Exception, e:
480
        form = SimpleBackend(request).get_signup_form(provider)
481
        messages.error(request, e)
482
483
    if request.method == 'POST':
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
484
            user = form.save(commit=False)
485
486
487
            try:
                result = backend.handle_activation(user)
                status = messages.SUCCESS
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
488
                message = result.message
Olga Brani's avatar
Olga Brani committed
489
490
491

                form.store_user(user, request)

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

533

Olga Brani's avatar
Olga Brani committed
534
@require_http_methods(["GET", "POST"])
535
@required_auth_methods_assigned(only_warn=True)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
536
@login_required
537
@signed_terms_required
538
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
539
540
    """
    Allows a user to send feedback.
541

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
547
    **Arguments**
548

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
549
550
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
551
        this will default to ``im/feedback.html``.
552

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
553
554
    ``extra_context``
        An dictionary of variables to add to the template context.
555

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
556
    **Template:**
557

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
560
    **Settings:**
561

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
562
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
563
    """
564
    extra_context = extra_context or {}
565
    if request.method == 'GET':
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
566
567
568
569
        form = FeedbackForm()
    if request.method == 'POST':
        if not request.user:
            return HttpResponse('Unauthorized', status=401)
570

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
571
572
        form = FeedbackForm(request.POST)
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
573
            msg = form.cleaned_data['feedback_msg']
574
            data = form.cleaned_data['feedback_data']
575
            try:
576
577
                send_feedback(msg, data, request.user, email_template_name)
            except SendMailError, e:
578
                messages.error(request, message)
579
            else:
Olga Brani's avatar
Olga Brani committed
580
                message = _(astakos_messages.FEEDBACK_SENT)
581
                messages.success(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
582
    return render_response(template_name,
583
584
585
                           feedback_form=form,
                           context_instance=get_context(request, extra_context))

586

587
@require_http_methods(["GET"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
588
@signed_terms_required
589
def logout(request, template='registration/logged_out.html', extra_context=None):
590
    """
591
    Wraps `django.contrib.auth.logout`.
592
    """
593
    extra_context = extra_context or {}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
594
    response = HttpResponse()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
595
596
597
    if request.user.is_authenticated():
        email = request.user.email
        auth_logout(request)
598
599
600
601
602
    else:
        response['Location'] = reverse('index')
        response.status_code = 301
        return response

603
604
605
606
    next = restrict_next(
        request.GET.get('next'),
        domain=COOKIE_DOMAIN
    )
607

608
609
610
    if next:
        response['Location'] = next
        response.status_code = 302
611
612
613
    elif LOGOUT_NEXT:
        response['Location'] = LOGOUT_NEXT
        response.status_code = 301
614
    else:
615
        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
616
617
        response['Location'] = reverse('index')
        response.status_code = 301
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
618
    return response
619

620

Olga Brani's avatar
Olga Brani committed
621
@require_http_methods(["GET", "POST"])
622
@transaction.commit_manually
623
624
def activate(request, greeting_email_template_name='im/welcome_email.txt',
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
625
    """
626
627
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
    and renews the user token.
628

629
630
    The view uses commit_manually decorator in order to ensure the user state will be updated
    only if the email will be send successfully.
631
632
633
634
635
636
    """
    token = request.GET.get('auth')
    next = request.GET.get('next')
    try:
        user = AstakosUser.objects.get(auth_token=token)
    except AstakosUser.DoesNotExist:
637
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
638

639
    if user.is_active:
Olga Brani's avatar
Olga Brani committed
640
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
641
        messages.error(request, message)
642
        return index(request)
643

644
    try:
645
        activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
646
647
        response = prepare_response(request, user, next, renew=True)
        transaction.commit()
648
        return response
649
650
651
652
    except SendMailError, e:
        message = e.message
        messages.add_message(request, messages.ERROR, message)
        transaction.rollback()
653
        return index(request)
654
655
    except BaseException, e:
        status = messages.ERROR
656
        message = _(astakos_messages.GENERIC_ERROR)
657
658
659
        messages.add_message(request, messages.ERROR, message)
        logger.exception(e)
        transaction.rollback()
660
        return index(request)
661

662

Olga Brani's avatar
Olga Brani committed
663
@require_http_methods(["GET", "POST"])
664
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
665
    extra_context = extra_context or {}
666
667
668
669
670
671
672
673
674
    term = None
    terms = None
    if not term_id:
        try:
            term = ApprovalTerms.objects.order_by('-id')[0]
        except IndexError:
            pass
    else:
        try:
675
676
            term = ApprovalTerms.objects.get(id=term_id)
        except ApprovalTerms.DoesNotExist, e:
677
            pass
678

679
    if not term:
Olga Brani's avatar
Olga Brani committed
680
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
681
        return HttpResponseRedirect(reverse('index'))
682
683
    f = open(term.location, 'r')
    terms = f.read()
684

685
    if request.method == 'POST':
686
687
688
689
        next = restrict_next(
            request.POST.get('next'),
            domain=COOKIE_DOMAIN
        )
690
        if not next:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
691
            next = reverse('index')
692
693
694
        form = SignApprovalTermsForm(request.POST, instance=request.user)
        if not form.is_valid():
            return render_response(template_name,
695
696
697
                                   terms=terms,
                                   approval_terms_form=form,
                                   context_instance=get_context(request, extra_context))
698
699
700
        user = form.save()
        return HttpResponseRedirect(next)
    else:
701
        form = None
702
        if request.user.is_authenticated() and not request.user.signed_terms:
703
            form = SignApprovalTermsForm(instance=request.user)
704
        return render_response(template_name,
705
706
707
708
                               terms=terms,
                               approval_terms_form=form,
                               context_instance=get_context(request, extra_context))

709

Olga Brani's avatar
Olga Brani committed
710
@require_http_methods(["GET", "POST"])
711
@valid_astakos_user_required
712
713
714
715
716
@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',
717
                 extra_context=None):
718
    extra_context = extra_context or {}
719
720


721
722
723
724
    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
725
                msg = _(astakos_messages.EMAIL_CHANGED)
726
                messages.success(request, msg)
727
728
729
                auth_logout(request)
                response = prepare_response(request, user)
                transaction.commit()
730
                return HttpResponseRedirect(reverse('edit_profile'))
731
        except ValueError, e:
732
            messages.error(request, e)
733
734
735
            transaction.rollback()
            return HttpResponseRedirect(reverse('index'))

736
        return render_response(confirm_template_name,
737
738
                               modified_user=user if 'user' in locals() \
                               else None, context_instance=get_context(request,
739
740
                                                            extra_context))

741
742
    if not request.user.is_authenticated():
        path = quote(request.get_full_path())
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
743
        url = request.build_absolute_uri(reverse('index'))
744
        return HttpResponseRedirect(url + '?next=' + path)
745
746
747
748
749
750
751
752
753

    # 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'))

754
755
756
    form = EmailChangeForm(request.POST or None)
    if request.method == 'POST' and form.is_valid():
        try:
757
758
            # delete pending email changes
            request.user.emailchanges.all().delete()
759
760
761
            ec = form.save(email_template_name, request)
        except SendMailError, e:
            msg = e
762
            messages.error(request, msg)
763
            transaction.rollback()
764
            return HttpResponseRedirect(reverse('edit_profile'))
765
        else:
Olga Brani's avatar
Olga Brani committed
766
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
767
            messages.success(request, msg)
768
            transaction.commit()
769
770
771
772
773
            return HttpResponseRedirect(reverse('edit_profile'))

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

774
775
776
777
778
    return render_response(
        form_template_name,
        form=form,
        context_instance=get_context(request, extra_context)
    )
779
780
781


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

783
784
785
786
    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
787
    if astakos_settings.MODERATION_ENABLED:
Olga Brani's avatar
Olga Brani committed
788
789
        raise PermissionDenied

790
791
792
793
    extra_context = extra_context or {}
    try:
        u = AstakosUser.objects.get(id=user_id)
    except AstakosUser.DoesNotExist:
794
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
795
796
797
    else:
        try:
            send_activation_func(u)
798
            msg = _(astakos_messages.ACTIVATION_SENT)
799
800
801
802
803
            messages.success(request, msg)
        except SendMailError, e:
            messages.error(request, e)
    return render_response(
        template_name,
804
        login_form = LoginForm(request=request),
805
806
807
808