1 # Copyright 2011 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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.
40 from datetime import datetime
41 from functools import wraps
43 from random import randint
44 from smtplib import SMTPException
45 from hashlib import new as newhasher
46 from urllib import quote
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
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
71 def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
73 Calls ``django.template.loader.render_to_string`` with an additional ``tab``
74 keyword argument and returns an ``django.http.HttpResponse`` with the
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)
83 def index(request, template_name='index.html', extra_context={}):
85 Renders the index (login) page
90 A custom template to use. This is optional; if not specified,
91 this will default to ``index.html``.
94 An dictionary of variables to add to the template context.
98 index.html or ``template_name`` keyword argument.
101 return render_response(template_name,
102 form = AuthenticationForm(),
103 context_instance = get_context(request, extra_context))
105 def _generate_invitation_code():
107 code = randint(1, 2L**63 - 1)
109 Invitation.objects.get(code=code)
110 # An invitation with this code already exists, try again
111 except Invitation.DoesNotExist:
114 def _send_invitation(request, baseurl, inv):
115 subject = _('Invitation to Astakos')
116 site = get_current_site(request)
117 url = settings.SIGNUP_TARGET % (baseurl, inv.code, quote(site.domain))
118 message = render_to_string('invitation.txt', {
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)
129 @transaction.commit_manually
130 def invite(request, template_name='invitations.html', extra_context={}):
132 Allows a user to invite somebody else.
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.
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.
141 If the user isn't logged in, redirects to settings.LOGIN_URL.
146 A custom template to use. This is optional; if not specified,
147 this will default to ``invitations.html``.
150 An dictionary of variables to add to the template context.
154 invitations.html or ``template_name`` keyword argument.
158 The view expectes the following settings are defined:
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
167 inviter = request.user
169 if request.method == 'POST':
170 username = request.POST.get('uniq')
171 realname = request.POST.get('realname')
173 if inviter.invitations > 0:
174 code = _generate_invitation_code()
175 invitation, created = Invitation.objects.get_or_create(
178 defaults={'code': code, 'realname': realname})
181 baseurl = request.build_absolute_uri('/').rstrip('/')
182 _send_invitation(request, baseurl, invitation)
184 inviter.invitations = max(0, inviter.invitations - 1)
186 status = messages.SUCCESS
187 message = _('Invitation sent to %s' % username)
189 except (SMTPException, socket.error) as e:
190 status = messages.ERROR
191 message = getattr(e, 'strerror', '')
192 transaction.rollback()
194 status = messages.ERROR
195 message = _('No invitations left')
196 messages.add_message(request, status, message)
198 if request.GET.get('format') == 'json':
199 sent = [{'email': inv.username,
200 'realname': inv.realname,
201 'is_accepted': inv.is_accepted}
202 for inv in inviter.invitations_sent.all()]
203 rep = {'invitations': inviter.invitations, 'sent': sent}
204 return HttpResponse(json.dumps(rep))
206 kwargs = {'user': inviter}
207 context = get_context(request, extra_context, **kwargs)
208 return render_response(template_name,
209 context_instance = context)
212 def edit_profile(request, template_name='profile.html', extra_context={}):
214 Allows a user to edit his/her profile.
216 In case of GET request renders a form for displaying the user information.
217 In case of POST updates the user informantion.
219 If the user isn't logged in, redirects to settings.LOGIN_URL.
224 A custom template to use. This is optional; if not specified,
225 this will default to ``profile.html``.
228 An dictionary of variables to add to the template context.
232 profile.html or ``template_name`` keyword argument.
235 user = AstakosUser.objects.get(username=request.user)
236 form = ProfileForm(instance=user)
237 except AstakosUser.DoesNotExist:
238 token = request.GET.get('auth', None)
239 user = AstakosUser.objects.get(auth_token=token)
240 if request.method == 'POST':
241 form = ProfileForm(request.POST, instance=user)
245 msg = _('Profile has been updated successfully')
246 messages.add_message(request, messages.SUCCESS, msg)
247 except ValueError, ve:
248 messages.add_message(request, messages.ERROR, ve)
249 return render_response(template_name,
251 context_instance = get_context(request,
255 @transaction.commit_manually
256 def signup(request, template_name='signup.html', extra_context={}, backend=None):
258 Allows a user to create a local account.
260 In case of GET request renders a form for providing the user information.
261 In case of POST handles the signup.
263 The user activation will be delegated to the backend specified by the ``backend`` keyword argument
264 if present, otherwise to the ``astakos.im.backends.InvitationBackend``
265 if settings.INVITATIONS_ENABLED is True or ``astakos.im.backends.SimpleBackend`` if not
268 Upon successful user creation if ``next`` url parameter is present the user is redirected there
269 otherwise renders the same page with a success message.
271 On unsuccessful creation, renders the same page with an error message.
273 The view uses commit_manually decorator in order to ensure the user will be created
274 only if the procedure has been completed successfully.
279 A custom template to use. This is optional; if not specified,
280 this will default to ``signup.html``.
283 An dictionary of variables to add to the template context.
287 signup.html or ``template_name`` keyword argument.
290 backend = get_backend()
292 form = backend.get_signup_form(request)
293 if request.method == 'POST':
295 status, message = backend.signup(request)
296 # rollback incase of error
297 if status == messages.ERROR:
298 transaction.rollback()
301 next = request.POST.get('next')
303 return redirect(next)
304 messages.add_message(request, status, message)
305 except (Invitation.DoesNotExist, Exception), e:
306 messages.add_message(request, messages.ERROR, e)
307 return render_response(template_name,
308 form = form if 'form' in locals() else UserCreationForm(),
309 context_instance=get_context(request, extra_context))
312 def send_feedback(request, template_name='feedback.html', email_template_name='feedback_mail.txt', extra_context={}):
314 Allows a user to send feedback.
316 In case of GET request renders a form for providing the feedback information.
317 In case of POST sends an email to support team.
319 If the user isn't logged in, redirects to settings.LOGIN_URL.
324 A custom template to use. This is optional; if not specified,
325 this will default to ``feedback.html``.
328 An dictionary of variables to add to the template context.
332 signup.html or ``template_name`` keyword argument.
336 * FEEDBACK_CONTACT_EMAIL: List of feedback recipients
338 if request.method == 'GET':
339 form = FeedbackForm()
340 if request.method == 'POST':
342 return HttpResponse('Unauthorized', status=401)
344 form = FeedbackForm(request.POST)
346 subject = _("Feedback from Okeanos")
347 from_email = request.user.email
348 recipient_list = [settings.FEEDBACK_CONTACT_EMAIL]
349 content = render_to_string(email_template_name, {
350 'message': form.cleaned_data('feedback_msg'),
351 'data': form.cleaned_data('feedback_data'),
354 send_mail(subject, content, from_email, recipient_list)
356 resp = json.dumps({'status': 'send'})
357 return HttpResponse(resp)
358 return render_response(template_name,
360 context_instance = get_context(request, extra_context))