views.py 56.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
from synnefo.lib.ordereddict import OrderedDict
44

45
46
from django_tables2 import RequestConfig

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

75
76
import astakos.im.messages as astakos_messages

77
from astakos.im.activation_backends import get_backend, SimpleBackend
78
from astakos.im import tables
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
79
from astakos.im.models import (
80
    AstakosUser, ApprovalTerms,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
81
82
    EmailChange, RESOURCE_SEPARATOR,
    AstakosUserAuthProvider, PendingThirdPartyUser,
83
    PendingMembershipError,
84
85
86
    ProjectApplication, ProjectMembership, Project)
from astakos.im.util import (
    get_context, prepare_response, get_query, restrict_next)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
87
from astakos.im.forms import (
88
    LoginForm, InvitationForm,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
89
90
    FeedbackForm, SignApprovalTermsForm,
    EmailChangeForm,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
91
    ProjectApplicationForm, ProjectSortForm,
92
93
    AddProjectMembersForm, ProjectSearchForm,
    ProjectMembersSortForm)
94
from astakos.im.forms import ExtendedProfileForm as ProfileForm
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
95
96
97
98
from astakos.im.functions import (
    send_feedback, SendMailError,
    logout as auth_logout,
    activate as activate_func,
99
    invite,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
100
    send_activation as send_activation_func,
101
    SendNotificationError,
102
    reached_pending_application_limit,
103
    accept_membership, reject_membership, remove_membership, cancel_membership,
Giorgos Korfiatis's avatar
Giorgos Korfiatis committed
104
    leave_project, join_project, enroll_member, can_join_request, can_leave_request,
105
106
107
    get_related_project_id, get_by_chain_or_404,
    approve_application, deny_application,
    cancel_application, dismiss_application)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
108
109
110
from astakos.im.settings import (
    COOKIE_DOMAIN, LOGOUT_NEXT,
    LOGGING_LEVEL, PAGINATE_BY,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
111
    RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL,
112
    ACTIVATION_REDIRECT_URL,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
113
    MODERATION_ENABLED)
114
from astakos.im.api import get_services_dict
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
115
from astakos.im import settings as astakos_settings
116
from astakos.im.api.callpoint import AstakosCallpoint
Olga Brani's avatar
Olga Brani committed
117
from astakos.im import auth_providers
118
from astakos.im.project_xctx import project_transaction_context
119
from astakos.im.retry_xctx import RetryException
Olga Brani's avatar
Olga Brani committed
120

121
122
logger = logging.getLogger(__name__)

123
callpoint = AstakosCallpoint()
Olga Brani's avatar
Olga Brani committed
124

125
def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
126
127
128
129
130
    """
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
    keyword argument and returns an ``django.http.HttpResponse`` with the
    specified ``status``.
    """
131
    if tab is None:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
132
        tab = template.partition('_')[0].partition('.html')[0]
133
    kwargs.setdefault('tab', tab)
134
    html = template_loader.render_to_string(
135
        template, kwargs, context_instance=context_instance)
136
137
    response = HttpResponse(html, status=status)
    return response
138

Olga Brani's avatar
Olga Brani committed
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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:
154
                        #TODO: add session message
155
                        return HttpResponseRedirect(reverse('login'))
Olga Brani's avatar
Olga Brani committed
156
157
158
159
            return func(request, *args)
        return wrapper
    return decorator

160
161
162

def requires_anonymous(func):
    """
163
    Decorator checkes whether the request.user is not Anonymous and in that case
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
164
    redirects to `logout`.
165
166
167
168
169
    """
    @wraps(func)
    def wrapper(request, *args):
        if not request.user.is_anonymous():
            next = urlencode({'next': request.build_absolute_uri()})
170
171
            logout_uri = reverse(logout) + '?' + next
            return HttpResponseRedirect(logout_uri)
172
173
174
        return func(request, *args)
    return wrapper

175

176
177
def signed_terms_required(func):
    """
178
    Decorator checks whether the request.user is Anonymous and in that case
179
180
181
182
    redirects to `logout`.
    """
    @wraps(func)
    def wrapper(request, *args, **kwargs):
183
        if request.user.is_authenticated() and not request.user.signed_terms:
184
            params = urlencode({'next': request.build_absolute_uri(),
185
                                'show_form': ''})
186
187
188
189
190
            terms_uri = reverse('latest_terms') + '?' + params
            return HttpResponseRedirect(terms_uri)
        return func(request, *args, **kwargs)
    return wrapper

191

192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
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
224
@require_http_methods(["GET", "POST"])
225
@signed_terms_required
226
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
227
    """
228
    If there is logged on user renders the profile page otherwise renders login page.
229

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

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

236
237
    ``profile_template_name``
        A custom profile template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
238
        this will default to ``im/profile.html``.
239

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
240
241
    ``extra_context``
        An dictionary of variables to add to the template context.
242

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
243
    **Template:**
244

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
247
    """
248
    extra_context = extra_context or {}
249
250
    template_name = login_template_name
    if request.user.is_authenticated():
251
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
Olga Brani's avatar
Olga Brani committed
252

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
253
254
255
256
    third_party_token = request.GET.get('key', False)
    if third_party_token:
        messages.info(request, astakos_messages.AUTH_PROVIDER_LOGIN_TO_ADD)

257
258
259
260
261
    return render_response(
        template_name,
        login_form = LoginForm(request=request),
        context_instance = get_context(request, extra_context)
    )
262

263

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
264
265
266
267
268
269
270
271
272
273
274
275
276
@require_http_methods(["POST"])
@valid_astakos_user_required
def update_token(request):
    """
    Update api token view.
    """
    user = request.user
    user.renew_token()
    user.save()
    messages.success(request, astakos_messages.TOKEN_UPDATED)
    return HttpResponseRedirect(reverse('edit_profile'))


Olga Brani's avatar
Olga Brani committed
277
@require_http_methods(["GET", "POST"])
278
@valid_astakos_user_required
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
279
@transaction.commit_manually
280
def invite(request, template_name='im/invitations.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
281
282
    """
    Allows a user to invite somebody else.
283

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
284
285
286
    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.
287

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
288
289
    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.
290

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
293
    **Arguments**
294

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
302
    **Template:**
303

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
306
    **Settings:**
307

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
308
    The view expectes the following settings are defined:
309

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
310
311
    * LOGIN_URL: login uri
    """
312
    extra_context = extra_context or {}
313
314
    status = None
    message = None
315
    form = InvitationForm()
316

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
317
    inviter = request.user
318
    if request.method == 'POST':
319
        form = InvitationForm(request.POST)
320
        if inviter.invitations > 0:
321
322
            if form.is_valid():
                try:
Olga Brani's avatar
Olga Brani committed
323
324
                    email = form.cleaned_data.get('username')
                    realname = form.cleaned_data.get('realname')
325
                    invite(inviter, email, realname)
Olga Brani's avatar
Olga Brani committed
326
                    message = _(astakos_messages.INVITATION_SENT) % locals()
327
                    messages.success(request, message)
328
329
                except SendMailError, e:
                    message = e.message
330
                    messages.error(request, message)
331
                    transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
332
                except BaseException, e:
Olga Brani's avatar
Olga Brani committed
333
                    message = _(astakos_messages.GENERIC_ERROR)
334
                    messages.error(request, message)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
335
336
337
338
                    logger.exception(e)
                    transaction.rollback()
                else:
                    transaction.commit()
339
        else:
Olga Brani's avatar
Olga Brani committed
340
            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
341
            messages.error(request, message)
342

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
343
    sent = [{'email': inv.username,
344
345
             'realname': inv.realname,
             'is_consumed': inv.is_consumed}
346
            for inv in request.user.invitations_sent.all()]
347
    kwargs = {'inviter': inviter,
348
              'sent': sent}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
349
350
    context = get_context(request, extra_context, **kwargs)
    return render_response(template_name,
351
352
353
                           invitation_form=form,
                           context_instance=context)

354

Olga Brani's avatar
Olga Brani committed
355
@require_http_methods(["GET", "POST"])
356
@required_auth_methods_assigned(only_warn=True)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
357
@login_required
358
@signed_terms_required
359
def edit_profile(request, template_name='im/profile.html', extra_context=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
360
361
    """
    Allows a user to edit his/her profile.
362

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
369
    **Arguments**
370

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
371
372
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
373
        this will default to ``im/profile.html``.
374

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
375
376
    ``extra_context``
        An dictionary of variables to add to the template context.
377

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
378
    **Template:**
379

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
382
    **Settings:**
383

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
384
    The view expectes the following settings are defined:
385

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
386
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
387
    """
388
    extra_context = extra_context or {}
389
390
391
392
    form = ProfileForm(
        instance=request.user,
        session_key=request.session.session_key
    )
393
    extra_context['next'] = request.GET.get('next')
394
    if request.method == 'POST':
395
396
397
398
399
        form = ProfileForm(
            request.POST,
            instance=request.user,
            session_key=request.session.session_key
        )
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
400
        if form.is_valid():
401
            try:
402
                prev_token = request.user.auth_token
403
                user = form.save(request=request)
404
405
406
407
                next = restrict_next(
                    request.POST.get('next'),
                    domain=COOKIE_DOMAIN
                )
Olga Brani's avatar
Olga Brani committed
408
                msg = _(astakos_messages.PROFILE_UPDATED)
409
                messages.success(request, msg)
410
411
412
413
414
415
416
417

                if form.email_changed:
                    msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
                    messages.success(request, msg)
                if form.password_changed:
                    msg = _(astakos_messages.PASSWORD_CHANGED)
                    messages.success(request, msg)

418
419
420
421
                if next:
                    return redirect(next)
                else:
                    return redirect(reverse('edit_profile'))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
422
            except ValueError, ve:
423
                messages.success(request, ve)
424
    elif request.method == "GET":
Olga Brani's avatar
Olga Brani committed
425
426
427
428
429
430
431
432
433
        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()

434
    extra_context['services'] = get_services_dict()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
435
    return render_response(template_name,
436
                           profile_form = form,
Olga Brani's avatar
Olga Brani committed
437
438
                           user_providers = user_providers,
                           user_available_providers = user_available_providers,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
439
                           context_instance = get_context(request,
440
                                                          extra_context))
441

442

443
@transaction.commit_manually
Olga Brani's avatar
Olga Brani committed
444
@require_http_methods(["GET", "POST"])
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
445
def signup(request, template_name='im/signup.html', on_success='index', extra_context=None, backend=None):
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
446
447
    """
    Allows a user to create a local account.
448

449
    In case of GET request renders a form for entering the user information.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
450
    In case of POST handles the signup.
451

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
452
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
453
454
455
    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);
456

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

460
    On unsuccessful creation, renders ``template_name`` with an error message.
461

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
462
    **Arguments**
463

464
465
    ``template_name``
        A custom template to render. This is optional;
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
466
        if not specified, this will default to ``im/signup.html``.
467

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
468
469
    ``extra_context``
        An dictionary of variables to add to the template context.
470

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
471
472
473
    ``on_success``
        Resolvable view name to redirect on registration success.

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
474
    **Template:**
475

476
    im/signup.html or ``template_name`` keyword argument.
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
477
    """
478
    extra_context = extra_context or {}
479
    if request.user.is_authenticated():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
480
        return HttpResponseRedirect(reverse('edit_profile'))
481

482
    provider = get_query(request).get('provider', 'local')
483
484
485
    if not auth_providers.get_provider(provider).is_available_for_create():
        raise PermissionDenied

486
487
488
489
490
491
    id = get_query(request).get('id')
    try:
        instance = AstakosUser.objects.get(id=id) if id else None
    except AstakosUser.DoesNotExist:
        instance = None

492
    pending_user = None
493
    third_party_token = request.REQUEST.get('third_party_token', None)
494
495
496
497
498
    if third_party_token:
        pending = get_object_or_404(PendingThirdPartyUser,
                                    token=third_party_token)
        provider = pending.provider
        instance = pending.get_user_instance()
499
500
501
502
503
504
505
        if pending.existing_user().count() > 0:
            pending_user = pending.existing_user().get()
            if request.method == "GET":
                messages.warning(request, pending_user.get_inactive_message())


    extra_context['pending_user_exists'] = pending_user
506

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
507
    try:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
508
509
        if not backend:
            backend = get_backend(request)
510
        form = backend.get_signup_form(provider, instance)
511
    except Exception, e:
512
        form = SimpleBackend(request).get_signup_form(provider)
513
        messages.error(request, e)
514
515
    if request.method == 'POST':
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
516
            user = form.save(commit=False)
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
517
518
519
520
521

            # delete previously unverified accounts
            if AstakosUser.objects.user_exists(user.email):
                AstakosUser.objects.get_by_identifier(user.email).delete()

522
523
524
            try:
                result = backend.handle_activation(user)
                status = messages.SUCCESS
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
525
                message = result.message
Olga Brani's avatar
Olga Brani committed
526
527
528

                form.store_user(user, request)

529
530
531
532
                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)
533
                        msg = 'Additional email: %s saved for user %s.' % (
534
535
536
                            additional_email,
                            user.email
                        )
537
                        logger._log(LOGGING_LEVEL, msg, [])
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
538

539
540
                if user and user.is_active:
                    next = request.POST.get('next', '')
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
541
                    response = prepare_response(request, user, next=next)
542
                    transaction.commit()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
543
                    return response
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
544

Olga Brani's avatar
Olga Brani committed
545
                transaction.commit()
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
546
547
548
                messages.add_message(request, status, message)
                return HttpResponseRedirect(reverse(on_success))

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
549
            except SendMailError, e:
550
                logger.exception(e)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
551
552
                status = messages.ERROR
                message = e.message
553
                messages.error(request, message)
554
                transaction.rollback()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
555
            except BaseException, e:
556
                logger.exception(e)
Olga Brani's avatar
Olga Brani committed
557
                message = _(astakos_messages.GENERIC_ERROR)
558
                messages.error(request, message)
559
                logger.exception(e)
560
                transaction.rollback()
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
561

562
    return render_response(template_name,
563
                           signup_form=form,
564
                           third_party_token=third_party_token,
565
                           provider=provider,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
566
                           context_instance=get_context(request, extra_context))
567

568

Olga Brani's avatar
Olga Brani committed
569
@require_http_methods(["GET", "POST"])
570
@required_auth_methods_assigned(only_warn=True)
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
571
@login_required
572
@signed_terms_required
573
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
574
575
    """
    Allows a user to send feedback.
576

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

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
582
    **Arguments**
583

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
584
585
    ``template_name``
        A custom template to use. This is optional; if not specified,
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
586
        this will default to ``im/feedback.html``.
587

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
588
589
    ``extra_context``
        An dictionary of variables to add to the template context.
590

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
591
    **Template:**
592

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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
595
    **Settings:**
596

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
597
    * LOGIN_URL: login uri
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
598
    """
599
    extra_context = extra_context or {}
600
    if request.method == 'GET':
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
601
602
603
604
        form = FeedbackForm()
    if request.method == 'POST':
        if not request.user:
            return HttpResponse('Unauthorized', status=401)
605

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
606
607
        form = FeedbackForm(request.POST)
        if form.is_valid():
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
608
            msg = form.cleaned_data['feedback_msg']
609
            data = form.cleaned_data['feedback_data']
610
            try:
611
612
                send_feedback(msg, data, request.user, email_template_name)
            except SendMailError, e:
613
                messages.error(request, message)
614
            else:
Olga Brani's avatar
Olga Brani committed
615
                message = _(astakos_messages.FEEDBACK_SENT)
616
                messages.success(request, message)
617
            return HttpResponseRedirect(reverse('feedback'))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
618
    return render_response(template_name,
619
620
621
                           feedback_form=form,
                           context_instance=get_context(request, extra_context))

622

623
@require_http_methods(["GET"])
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
624
@signed_terms_required
625
def logout(request, template='registration/logged_out.html', extra_context=None):
626
    """
627
    Wraps `django.contrib.auth.logout`.
628
    """
629
    extra_context = extra_context or {}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
630
    response = HttpResponse()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
631
632
633
    if request.user.is_authenticated():
        email = request.user.email
        auth_logout(request)
634
635
636
637
638
    else:
        response['Location'] = reverse('index')
        response.status_code = 301
        return response

639
640
641
642
    next = restrict_next(
        request.GET.get('next'),
        domain=COOKIE_DOMAIN
    )
643

644
645
646
    if next:
        response['Location'] = next
        response.status_code = 302
647
648
649
    elif LOGOUT_NEXT:
        response['Location'] = LOGOUT_NEXT
        response.status_code = 301
650
    else:
651
652
653
654
655
656
657
        message = _(astakos_messages.LOGOUT_SUCCESS)
        last_provider = request.COOKIES.get('astakos_last_login_method', None)
        if last_provider:
            provider = auth_providers.get_provider(last_provider)
            extra_message = provider.get_logout_message_display
            if extra_message:
                message += '<br />' + extra_message
658
        messages.success(request, message)
659
660
        response['Location'] = reverse('index')
        response.status_code = 301
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
661
    return response
662

663

Olga Brani's avatar
Olga Brani committed
664
@require_http_methods(["GET", "POST"])
665
@transaction.commit_manually
666
667
def activate(request, greeting_email_template_name='im/welcome_email.txt',
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
668
    """
669
670
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
    and renews the user token.
671

672
673
    The view uses commit_manually decorator in order to ensure the user state will be updated
    only if the email will be send successfully.
674
675
676
677
678
679
    """
    token = request.GET.get('auth')
    next = request.GET.get('next')
    try:
        user = AstakosUser.objects.get(auth_token=token)
    except AstakosUser.DoesNotExist:
680
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
681

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
682
    if user.is_active or user.email_verified:
Olga Brani's avatar
Olga Brani committed
683
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
684
        messages.error(request, message)
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
685
        return HttpResponseRedirect(reverse('index'))
686

687
    try:
688
689
        activate_func(user, greeting_email_template_name,
                      helpdesk_email_template_name, verify_email=True)
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
690
        messages.success(request, _(astakos_messages.ACCOUNT_ACTIVATED))
691
        next = ACTIVATION_REDIRECT_URL or next
692
693
        response = prepare_response(request, user, next, renew=True)
        transaction.commit()
694
        return response
695
696
697
698
    except SendMailError, e:
        message = e.message
        messages.add_message(request, messages.ERROR, message)
        transaction.rollback()
699
        return index(request)
700
701
    except BaseException, e:
        status = messages.ERROR
702
        message = _(astakos_messages.GENERIC_ERROR)
703
704
705
        messages.add_message(request, messages.ERROR, message)
        logger.exception(e)
        transaction.rollback()
706
        return index(request)
707

708

Olga Brani's avatar
Olga Brani committed
709
@require_http_methods(["GET", "POST"])
710
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
711
    extra_context = extra_context or {}
712
713
714
715
716
717
718
719
720
    term = None
    terms = None
    if not term_id:
        try:
            term = ApprovalTerms.objects.order_by('-id')[0]
        except IndexError:
            pass
    else:
        try:
721
722
            term = ApprovalTerms.objects.get(id=term_id)
        except ApprovalTerms.DoesNotExist, e:
723
            pass
724

725
    if not term:
Olga Brani's avatar
Olga Brani committed
726
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
727
        return HttpResponseRedirect(reverse('index'))
728
729
730
731
732
733
734
    try:
        f = open(term.location, 'r')
    except IOError:
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
        return render_response(
            template_name, context_instance=get_context(request, extra_context))

735
    terms = f.read()
736

737
    if request.method == 'POST':
738
739
740
741
        next = restrict_next(
            request.POST.get('next'),
            domain=COOKIE_DOMAIN
        )
742
        if not next:
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
743
            next = reverse('index')
744
745
746
        form = SignApprovalTermsForm(request.POST, instance=request.user)
        if not form.is_valid():
            return render_response(template_name,
747
748
749
                                   terms=terms,
                                   approval_terms_form=form,
                                   context_instance=get_context(request, extra_context))
750
751
752
        user = form.save()
        return HttpResponseRedirect(next)
    else:
753
        form = None
754
        if request.user.is_authenticated() and not request.user.signed_terms:
755
            form = SignApprovalTermsForm(instance=request.user)
756
        return render_response(template_name,
757
758
759
760
                               terms=terms,
                               approval_terms_form=form,
                               context_instance=get_context(request, extra_context))

761

Olga Brani's avatar
Olga Brani committed
762
@require_http_methods(["GET", "POST"])
763
764
765
766
767
@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',
768
                 extra_context=None):
769
    extra_context = extra_context or {}
770
771


772
773
774
    if not astakos_settings.EMAILCHANGE_ENABLED:
        raise PermissionDenied

775
776
777
    if activation_key:
        try:
            user = EmailChange.objects.change_email(activation_key)
778
779
            if request.user.is_authenticated() and request.user == user or not \
                    request.user.is_authenticated():
Olga Brani's avatar
Olga Brani committed
780
                msg = _(astakos_messages.EMAIL_CHANGED)
781
                messages.success(request, msg)
782
                transaction.commit()
783
                return HttpResponseRedirect(reverse('edit_profile'))
784
        except ValueError, e:
785
            messages.error(request, e)
786
787
788
            transaction.rollback()
            return HttpResponseRedirect(reverse('index'))

789
        return render_response(confirm_template_name,
790
791
                               modified_user=user if 'user' in locals() \
                               else None, context_instance=get_context(request,
792
793
                                                            extra_context))

794
795
    if not request.user.is_authenticated():
        path = quote(request.get_full_path())
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
796
        url = request.build_absolute_uri(reverse('index'))
797
        return HttpResponseRedirect(url + '?next=' + path)
798
799
800
801