Statistics
| Branch: | Tag: | Revision:

root / astakos / im / views.py @ 5ed6816e

History | View | Annotate | Download (13.4 kB)

1
# Copyright 2011-2012 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.models import AnonymousUser
58
from django.contrib.auth.decorators import login_required
59
from django.contrib.sites.models import Site
60
from django.contrib import messages
61
from django.db import transaction
62
from django.contrib.auth.forms import UserCreationForm
63

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

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

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

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

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

    
127
@login_required
128
@transaction.commit_manually
129
def invite(request, template_name='invitations.html', extra_context={}):
130
    """
131
    Allows a user to invite somebody else.
132
    
133
    In case of GET request renders a form for providing the invitee information.
134
    In case of POST checks whether the user has not run out of invitations and then
135
    sends an invitation email to singup to the service.
136
    
137
    The view uses commit_manually decorator in order to ensure the number of the
138
    user invitations is going to be updated only if the email has been successfully sent.
139
    
140
    If the user isn't logged in, redirects to settings.LOGIN_URL.
141
    
142
    **Arguments**
143
    
144
    ``template_name``
145
        A custom template to use. This is optional; if not specified,
146
        this will default to ``invitations.html``.
147
    
148
    ``extra_context``
149
        An dictionary of variables to add to the template context.
150
    
151
    **Template:**
152
    
153
    invitations.html or ``template_name`` keyword argument.
154
    
155
    **Settings:**
156
    
157
    The view expectes the following settings are defined:
158
    
159
    * LOGIN_URL: login uri
160
    * SIGNUP_TARGET: Where users should signup with their invitation code
161
    * DEFAULT_CONTACT_EMAIL: service support email
162
    * DEFAULT_FROM_EMAIL: from email
163
    """
164
    status = None
165
    message = None
166
    inviter = AstakosUser.objects.get(username = request.user.username)
167
    
168
    if request.method == 'POST':
169
        username = request.POST.get('uniq')
170
        realname = request.POST.get('realname')
171
        
172
        if inviter.invitations > 0:
173
            code = _generate_invitation_code()
174
            invitation, created = Invitation.objects.get_or_create(
175
                inviter=inviter,
176
                username=username,
177
                defaults={'code': code, 'realname': realname})
178
            
179
            try:
180
                baseurl = request.build_absolute_uri('/').rstrip('/')
181
                _send_invitation(request, baseurl, 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(request)
290
    try:
291
        form = backend.get_signup_form()
292
        if request.method == 'POST':
293
            if form.is_valid():
294
                status, message = backend.signup(form)
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))