Statistics
| Branch: | Tag: | Revision:

root / astakos / im / views.py @ 890b0eaf

History | View | Annotate | Download (13.3 kB)

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

    
34
import json
35
import logging
36
import socket
37
import csv
38
import sys
39

    
40
from datetime import datetime
41
from functools import wraps
42
from math import ceil
43
from random import randint
44
from smtplib import SMTPException
45
from hashlib import new as newhasher
46
from urllib import quote
47

    
48
from django.conf import settings
49
from django.core.mail import send_mail
50
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
51
from django.shortcuts import redirect
52
from django.template.loader import render_to_string
53
from django.shortcuts import render_to_response
54
from django.utils.http import urlencode
55
from django.utils.translation import ugettext as _
56
from django.core.urlresolvers import reverse
57
from django.contrib.auth.forms import AuthenticationForm
58
from django.contrib.auth.models import AnonymousUser
59
from django.contrib.auth.decorators import login_required
60
from django.contrib.sites.models import get_current_site
61
from django.contrib import messages
62
from django.db import transaction
63
from django.contrib.auth.forms import UserCreationForm
64

    
65
#from astakos.im.openid_store import PithosOpenIDStore
66
from astakos.im.models import AstakosUser, Invitation
67
from astakos.im.util import isoformat, get_or_create_user, get_context
68
from astakos.im.backends import get_backend
69
from astakos.im.forms import ProfileForm, FeedbackForm
70

    
71
def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
72
    """
73
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
74
    keyword argument and returns an ``django.http.HttpResponse`` with the
75
    specified ``status``.
76
    """
77
    if tab is None:
78
        tab = template.partition('_')[0]
79
    kwargs.setdefault('tab', tab)
80
    html = render_to_string(template, kwargs, context_instance=context_instance)
81
    return HttpResponse(html, status=status)
82

    
83
def index(request, template_name='index.html', extra_context={}):
84
    """
85
    Renders the index (login) page
86
    
87
    **Arguments**
88
    
89
    ``template_name``
90
        A custom template to use. This is optional; if not specified,
91
        this will default to ``index.html``.
92
    
93
    ``extra_context``
94
        An dictionary of variables to add to the template context.
95
    
96
    **Template:**
97
    
98
    index.html or ``template_name`` keyword argument.
99
    
100
    """
101
    return render_response(template_name,
102
                           form = AuthenticationForm(),
103
                           context_instance = get_context(request, extra_context))
104

    
105
def _generate_invitation_code():
106
    while True:
107
        code = randint(1, 2L**63 - 1)
108
        try:
109
            Invitation.objects.get(code=code)
110
            # An invitation with this code already exists, try again
111
        except Invitation.DoesNotExist:
112
            return code
113

    
114
def _send_invitation(baseurl, inv):
115
    url = settings.SIGNUP_TARGET % (baseurl, inv.code, quote(baseurl))
116
    subject = _('Invitation to Pithos')
117
    site = get_current_site(request)
118
    message = render_to_string('invitation.txt', {
119
                'invitation': inv,
120
                'url': url,
121
                'baseurl': baseurl,
122
                'service': site_name,
123
                'support': settings.DEFAULT_CONTACT_EMAIL})
124
    sender = settings.DEFAULT_FROM_EMAIL
125
    send_mail(subject, message, sender, [inv.username])
126
    logging.info('Sent invitation %s', inv)
127

    
128
@login_required
129
@transaction.commit_manually
130
def invite(request, template_name='invitations.html', extra_context={}):
131
    """
132
    Allows a user to invite somebody else.
133
    
134
    In case of GET request renders a form for providing the invitee information.
135
    In case of POST checks whether the user has not run out of invitations and then
136
    sends an invitation email to singup to the service.
137
    
138
    The view uses commit_manually decorator in order to ensure the number of the
139
    user invitations is going to be updated only if the email has been successfully sent.
140
    
141
    If the user isn't logged in, redirects to settings.LOGIN_URL.
142
    
143
    **Arguments**
144
    
145
    ``template_name``
146
        A custom template to use. This is optional; if not specified,
147
        this will default to ``invitations.html``.
148
    
149
    ``extra_context``
150
        An dictionary of variables to add to the template context.
151
    
152
    **Template:**
153
    
154
    invitations.html or ``template_name`` keyword argument.
155
    
156
    **Settings:**
157
    
158
    The view expectes the following settings are defined:
159
    
160
    * LOGIN_URL: login uri
161
    * SIGNUP_TARGET: Where users should signup with their invitation code
162
    * DEFAULT_CONTACT_EMAIL: service support email
163
    * DEFAULT_FROM_EMAIL: from email
164
    """
165
    status = None
166
    message = None
167
    inviter = request.user
168

    
169
    if request.method == 'POST':
170
        username = request.POST.get('uniq')
171
        realname = request.POST.get('realname')
172
        
173
        if inviter.invitations > 0:
174
            code = _generate_invitation_code()
175
            invitation, created = Invitation.objects.get_or_create(
176
                inviter=inviter,
177
                username=username,
178
                defaults={'code': code, 'realname': realname})
179
            
180
            try:
181
                _send_invitation(request.build_absolute_uri('/').rstrip('/'), invitation)
182
                if created:
183
                    inviter.invitations = max(0, inviter.invitations - 1)
184
                    inviter.save()
185
                status = messages.SUCCESS
186
                message = _('Invitation sent to %s' % username)
187
                transaction.commit()
188
            except (SMTPException, socket.error) as e:
189
                status = messages.ERROR
190
                message = getattr(e, 'strerror', '')
191
                transaction.rollback()
192
        else:
193
            status = messages.ERROR
194
            message = _('No invitations left')
195
    messages.add_message(request, status, message)
196
    
197
    if request.GET.get('format') == 'json':
198
        sent = [{'email': inv.username,
199
                 'realname': inv.realname,
200
                 'is_accepted': inv.is_accepted}
201
                    for inv in inviter.invitations_sent.all()]
202
        rep = {'invitations': inviter.invitations, 'sent': sent}
203
        return HttpResponse(json.dumps(rep))
204
    
205
    kwargs = {'user': inviter}
206
    context = get_context(request, extra_context, **kwargs)
207
    return render_response(template_name,
208
                           context_instance = context)
209

    
210
@login_required
211
def edit_profile(request, template_name='profile.html', extra_context={}):
212
    """
213
    Allows a user to edit his/her profile.
214
    
215
    In case of GET request renders a form for displaying the user information.
216
    In case of POST updates the user informantion.
217
    
218
    If the user isn't logged in, redirects to settings.LOGIN_URL.  
219
    
220
    **Arguments**
221
    
222
    ``template_name``
223
        A custom template to use. This is optional; if not specified,
224
        this will default to ``profile.html``.
225
    
226
    ``extra_context``
227
        An dictionary of variables to add to the template context.
228
    
229
    **Template:**
230
    
231
    profile.html or ``template_name`` keyword argument.
232
    """
233
    try:
234
        user = AstakosUser.objects.get(username=request.user)
235
        form = ProfileForm(instance=user)
236
    except AstakosUser.DoesNotExist:
237
        token = request.GET.get('auth', None)
238
        user = AstakosUser.objects.get(auth_token=token)
239
    if request.method == 'POST':
240
        form = ProfileForm(request.POST, instance=user)
241
        if form.is_valid():
242
            try:
243
                form.save()
244
                msg = _('Profile has been updated successfully')
245
                messages.add_message(request, messages.SUCCESS, msg)
246
            except ValueError, ve:
247
                messages.add_message(request, messages.ERROR, ve)
248
    return render_response(template_name,
249
                           form = form,
250
                           context_instance = get_context(request,
251
                                                          extra_context,
252
                                                          user=user))
253

    
254
@transaction.commit_manually
255
def signup(request, template_name='signup.html', extra_context={}, backend=None):
256
    """
257
    Allows a user to create a local account.
258
    
259
    In case of GET request renders a form for providing the user information.
260
    In case of POST handles the signup.
261
    
262
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
263
    if present, otherwise to the ``astakos.im.backends.InvitationBackend``
264
    if settings.INVITATIONS_ENABLED is True or ``astakos.im.backends.SimpleBackend`` if not
265
    (see backends);
266
    
267
    Upon successful user creation if ``next`` url parameter is present the user is redirected there
268
    otherwise renders the same page with a success message.
269
    
270
    On unsuccessful creation, renders the same page with an error message.
271
    
272
    The view uses commit_manually decorator in order to ensure the user will be created
273
    only if the procedure has been completed successfully.
274
    
275
    **Arguments**
276
    
277
    ``template_name``
278
        A custom template to use. This is optional; if not specified,
279
        this will default to ``signup.html``.
280
    
281
    ``extra_context``
282
        An dictionary of variables to add to the template context.
283
    
284
    **Template:**
285
    
286
    signup.html or ``template_name`` keyword argument.
287
    """
288
    if not backend:
289
            backend = get_backend()
290
    try:
291
        form = backend.get_signup_form(request)
292
        if request.method == 'POST':
293
            if form.is_valid():
294
                status, message = backend.signup(request)
295
                # rollback incase of error
296
                if status == messages.ERROR:
297
                    transaction.rollback()
298
                else:
299
                    transaction.commit()
300
                next = request.POST.get('next')
301
                if next:
302
                    return redirect(next)
303
                messages.add_message(request, status, message)
304
    except (Invitation.DoesNotExist), e:
305
        messages.add_message(request, messages.ERROR, e)
306
    return render_response(template_name,
307
                           form = form if 'form' in locals() else UserCreationForm(),
308
                           context_instance=get_context(request, extra_context))
309

    
310
@login_required
311
def send_feedback(request, template_name='feedback.html', email_template_name='feedback_mail.txt', extra_context={}):
312
    """
313
    Allows a user to send feedback.
314
    
315
    In case of GET request renders a form for providing the feedback information.
316
    In case of POST sends an email to support team.
317
    
318
    If the user isn't logged in, redirects to settings.LOGIN_URL.  
319
    
320
    **Arguments**
321
    
322
    ``template_name``
323
        A custom template to use. This is optional; if not specified,
324
        this will default to ``feedback.html``.
325
    
326
    ``extra_context``
327
        An dictionary of variables to add to the template context.
328
    
329
    **Template:**
330
    
331
    signup.html or ``template_name`` keyword argument.
332
    
333
    **Settings:**
334
    
335
    * FEEDBACK_CONTACT_EMAIL: List of feedback recipients
336
    """
337
    if request.method == 'GET':
338
        form = FeedbackForm()
339
    if request.method == 'POST':
340
        if not request.user:
341
            return HttpResponse('Unauthorized', status=401)
342
        
343
        form = FeedbackForm(request.POST)
344
        if form.is_valid():
345
            subject = _("Feedback from Okeanos")
346
            from_email = request.user.email
347
            recipient_list = [settings.FEEDBACK_CONTACT_EMAIL]
348
            content = render_to_string(email_template_name, {
349
                        'message': form.cleaned_data('feedback_msg'),
350
                        'data': form.cleaned_data('feedback_data'),
351
                        'request': request})
352
            
353
            send_mail(subject, content, from_email, recipient_list)
354
            
355
            resp = json.dumps({'status': 'send'})
356
            return HttpResponse(resp)
357
    return render_response(template_name,
358
                           form = form,
359
                           context_instance = get_context(request, extra_context))