Revision 890b0eaf astakos/im/views.py

b/astakos/im/views.py
54 54
from django.utils.http import urlencode
55 55
from django.utils.translation import ugettext as _
56 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
57 64

  
58 65
#from astakos.im.openid_store import PithosOpenIDStore
59 66
from astakos.im.models import AstakosUser, Invitation
60 67
from astakos.im.util import isoformat, get_or_create_user, get_context
61
from astakos.im.forms import *
62 68
from astakos.im.backends import get_backend
69
from astakos.im.forms import ProfileForm, FeedbackForm
63 70

  
64 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
    """
65 77
    if tab is None:
66 78
        tab = template.partition('_')[0]
67 79
    kwargs.setdefault('tab', tab)
68 80
    html = render_to_string(template, kwargs, context_instance=context_instance)
69 81
    return HttpResponse(html, status=status)
70 82

  
71
def requires_login(func):
72
    @wraps(func)
73
    def wrapper(request, *args):
74
        if not settings.BYPASS_ADMIN_AUTH:
75
            if not request.user:
76
                next = urlencode({'next': request.build_absolute_uri()})
77
                login_uri = reverse(index) + '?' + next
78
                return HttpResponseRedirect(login_uri)
79
        return func(request, *args)
80
    return wrapper
81

  
82 83
def index(request, template_name='index.html', extra_context={}):
83
    print '#', get_context(request, 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
    """
84 101
    return render_response(template_name,
85
                           form = LoginForm(),
102
                           form = AuthenticationForm(),
86 103
                           context_instance = get_context(request, extra_context))
87 104

  
88 105
def _generate_invitation_code():
......
97 114
def _send_invitation(baseurl, inv):
98 115
    url = settings.SIGNUP_TARGET % (baseurl, inv.code, quote(baseurl))
99 116
    subject = _('Invitation to Pithos')
117
    site = get_current_site(request)
100 118
    message = render_to_string('invitation.txt', {
101 119
                'invitation': inv,
102 120
                'url': url,
103 121
                'baseurl': baseurl,
104
                'service': settings.SERVICE_NAME,
122
                'service': site_name,
105 123
                'support': settings.DEFAULT_CONTACT_EMAIL})
106 124
    sender = settings.DEFAULT_FROM_EMAIL
107 125
    send_mail(subject, message, sender, [inv.username])
108 126
    logging.info('Sent invitation %s', inv)
109 127

  
110
@requires_login
128
@login_required
129
@transaction.commit_manually
111 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
    """
112 165
    status = None
113 166
    message = None
114 167
    inviter = request.user
......
129 182
                if created:
130 183
                    inviter.invitations = max(0, inviter.invitations - 1)
131 184
                    inviter.save()
132
                status = 'success'
185
                status = messages.SUCCESS
133 186
                message = _('Invitation sent to %s' % username)
187
                transaction.commit()
134 188
            except (SMTPException, socket.error) as e:
135
                status = 'error'
189
                status = messages.ERROR
136 190
                message = getattr(e, 'strerror', '')
191
                transaction.rollback()
137 192
        else:
138
            status = 'error'
193
            status = messages.ERROR
139 194
            message = _('No invitations left')
140

  
195
    messages.add_message(request, status, message)
196
    
141 197
    if request.GET.get('format') == 'json':
142 198
        sent = [{'email': inv.username,
143 199
                 'realname': inv.realname,
......
146 202
        rep = {'invitations': inviter.invitations, 'sent': sent}
147 203
        return HttpResponse(json.dumps(rep))
148 204
    
149
    kwargs = {'user': inviter, 'status': status, 'message': message}
205
    kwargs = {'user': inviter}
150 206
    context = get_context(request, extra_context, **kwargs)
151 207
    return render_response(template_name,
152 208
                           context_instance = context)
153 209

  
154
def _send_password(baseurl, user):
155
    url = settings.PASSWORD_RESET_TARGET % (baseurl,
156
                                            quote(user.username),
157
                                            quote(baseurl))
158
    message = render_to_string('password.txt', {
159
            'user': user,
160
            'url': url,
161
            'baseurl': baseurl,
162
            'service': settings.SERVICE_NAME,
163
            'support': settings.DEFAULT_CONTACT_EMAIL})
164
    sender = settings.DEFAULT_FROM_EMAIL
165
    send_mail('Pithos password recovering', message, sender, [user.email])
166
    logging.info('Sent password %s', user)
167

  
168
def reclaim_password(request, template_name='reclaim.html', extra_context={}):
169
    if request.method == 'GET':
170
        return render_response(template_name,
171
                               context_instance = get_context(request, extra_context))
172
    elif request.method == 'POST':
173
        username = request.POST.get('username')
174
        try:
175
            user = AstakosUser.objects.get(username=username)
176
            try:
177
                _send_password(request.build_absolute_uri('/').rstrip('/'), user)
178
                status = 'success'
179
                message = _('Password reset sent to %s' % user.email)
180
                user.is_active = False
181
                user.save()
182
            except (SMTPException, socket.error) as e:
183
                status = 'error'
184
                name = 'strerror'
185
                message = getattr(e, name) if hasattr(e, name) else e
186
        except AstakosUser.DoesNotExist:
187
            status = 'error'
188
            message = 'Username does not exist'
189
        
190
        kwargs = {'status': status, 'message': message}
191
        return render_response(template_name,
192
                                context_instance = get_context(request, extra_context, **kwargs))
193

  
194
@requires_login
195
def users_profile(request, template_name='users_profile.html', extra_context={}):
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
    """
196 233
    try:
197 234
        user = AstakosUser.objects.get(username=request.user)
235
        form = ProfileForm(instance=user)
198 236
    except AstakosUser.DoesNotExist:
199 237
        token = request.GET.get('auth', None)
200 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)
201 248
    return render_response(template_name,
249
                           form = form,
202 250
                           context_instance = get_context(request,
203 251
                                                          extra_context,
204 252
                                                          user=user))
205 253

  
206
@requires_login
207
def users_edit(request, template_name='users_profile.html', extra_context={}):
208
    try:
209
        user = AstakosUser.objects.get(username=request.user)
210
    except AstakosUser.DoesNotExist:
211
        token = request.POST.get('auth', None)
212
        #users = AstakosUser.objects.all()
213
        user = AstakosUser.objects.get(auth_token=token)
214
    user.first_name = request.POST.get('first_name')
215
    user.last_name = request.POST.get('last_name')
216
    user.affiliation = request.POST.get('affiliation')
217
    user.is_verified = True
218
    user.save()
219
    next = request.POST.get('next')
220
    if next:
221
        return redirect(next)
222
    
223
    status = 'success'
224
    message = _('Profile has been updated')
225
    return render_response(template_name,
226
                           context_instance = get_context(request, extra_context, **kwargs))
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.
227 269
    
228
def signup(request, template_name='signup.html', extra_context={}, backend=None, success_url = None):
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
    """
229 288
    if not backend:
230
        backend = get_backend()
231
    if request.method == 'GET':
232
        try:
233
            form = backend.get_signup_form(request)
234
            return render_response(template_name,
235
                               form=form,
236
                               context_instance = get_context(request, extra_context))
237
        except Exception, e:
238
            return _on_failure(e, template_name=template_name)
239
    elif request.method == 'POST':
240
        try:
241
            form = backend.get_signup_form(request)
242
            if not form.is_valid():
243
                return render_response(template_name,
244
                                       form = form,
245
                                       context_instance = get_context(request, extra_context))
246
            status, message = backend.signup(request, form, success_url)
247
            next = request.POST.get('next')
248
            if next:
249
                return redirect(next)
250
            return _info(status, message)
251
        except Exception, e:
252
            return _on_failure(e, template_name=template_name)
253

  
254
def _info(status, message, template_name='base.html'):
255
    html = render_to_string(template_name, {
256
            'status': status,
257
            'message': message})
258
    response = HttpResponse(html)
259
    return response
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))
260 309

  
261
def _on_success(message, template_name='base.html'):
262
    return _info('success', message, template_name)
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:**
263 330
    
264
def _on_failure(message, template_name='base.html'):
265
    return _info('error', message, template_name)
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))

Also available in: Unified diff