# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
-import json
import logging
import socket
-import csv
-import sys
-from datetime import datetime
-from functools import wraps
-from math import ceil
-from random import randint
from smtplib import SMTPException
-from hashlib import new as newhasher
from urllib import quote
+from functools import wraps
-from django.conf import settings
from django.core.mail import send_mail
-from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
+from django.http import HttpResponse
from django.shortcuts import redirect
from django.template.loader import render_to_string
-from django.shortcuts import render_to_response
-from django.utils.http import urlencode
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
-from django.contrib.auth.models import AnonymousUser
from django.contrib.auth.decorators import login_required
-from django.contrib.sites.models import Site
from django.contrib import messages
from django.db import transaction
-from django.contrib.auth.forms import UserCreationForm
+from django.contrib.auth import logout as auth_logout
+from django.utils.http import urlencode
+from django.http import HttpResponseRedirect
-#from astakos.im.openid_store import PithosOpenIDStore
from astakos.im.models import AstakosUser, Invitation
-from astakos.im.util import isoformat, get_or_create_user, get_context
from astakos.im.backends import get_backend
-from astakos.im.forms import ProfileForm, FeedbackForm, LoginForm
+from astakos.im.util import get_context, prepare_response, set_cookie
+from astakos.im.forms import *
+from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, BASEURL
+from astakos.im.functions import invite as invite_func
-def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
+logger = logging.getLogger(__name__)
+
+def render_response(template, tab=None, status=200, reset_cookie=False, context_instance=None, **kwargs):
"""
Calls ``django.template.loader.render_to_string`` with an additional ``tab``
keyword argument and returns an ``django.http.HttpResponse`` with the
specified ``status``.
"""
if tab is None:
- tab = template.partition('_')[0]
+ tab = template.partition('_')[0].partition('.html')[0]
kwargs.setdefault('tab', tab)
html = render_to_string(template, kwargs, context_instance=context_instance)
- return HttpResponse(html, status=status)
+ response = HttpResponse(html, status=status)
+ if reset_cookie:
+ set_cookie(response, context_instance['request'].user)
+ return response
+
-def index(request, template_name='index.html', extra_context={}):
+def requires_anonymous(func):
+ """
+ Decorator checkes whether the request.user is Anonymous and in that case
+ redirects to `logout`.
"""
- Renders the index (login) page
+ @wraps(func)
+ def wrapper(request, *args):
+ if not request.user.is_anonymous():
+ next = urlencode({'next': request.build_absolute_uri()})
+ login_uri = reverse(logout) + '?' + next
+ return HttpResponseRedirect(login_uri)
+ return func(request, *args)
+ return wrapper
+
+def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context={}):
+ """
+ If there is logged on user renders the profile page otherwise renders login page.
**Arguments**
- ``template_name``
- A custom template to use. This is optional; if not specified,
- this will default to ``index.html``.
+ ``login_template_name``
+ A custom login template to use. This is optional; if not specified,
+ this will default to ``im/login.html``.
+
+ ``profile_template_name``
+ A custom profile template to use. This is optional; if not specified,
+ this will default to ``im/profile.html``.
``extra_context``
An dictionary of variables to add to the template context.
**Template:**
- index.html or ``template_name`` keyword argument.
+ im/profile.html or im/login.html or ``template_name`` keyword argument.
"""
+ template_name = login_template_name
+ formclass = 'LoginForm'
+ kwargs = {}
+ if request.user.is_authenticated():
+ template_name = profile_template_name
+ formclass = 'ProfileForm'
+ kwargs.update({'instance':request.user})
return render_response(template_name,
- form = LoginForm(),
+ form = globals()[formclass](**kwargs),
context_instance = get_context(request, extra_context))
-def _generate_invitation_code():
- while True:
- code = randint(1, 2L**63 - 1)
- try:
- Invitation.objects.get(code=code)
- # An invitation with this code already exists, try again
- except Invitation.DoesNotExist:
- return code
-
-def _send_invitation(request, baseurl, inv):
- subject = _('Invitation to Astakos')
- site = Site.objects.get_current()
- url = settings.SIGNUP_TARGET % (baseurl, inv.code, quote(site.domain))
- message = render_to_string('invitation.txt', {
- 'invitation': inv,
- 'url': url,
- 'baseurl': baseurl,
- 'service': site.name,
- 'support': settings.DEFAULT_CONTACT_EMAIL})
- sender = settings.DEFAULT_FROM_EMAIL
- send_mail(subject, message, sender, [inv.username])
- logging.info('Sent invitation %s', inv)
-
@login_required
@transaction.commit_manually
-def invite(request, template_name='invitations.html', extra_context={}):
+def invite(request, template_name='im/invitations.html', extra_context={}):
"""
Allows a user to invite somebody else.
``template_name``
A custom template to use. This is optional; if not specified,
- this will default to ``invitations.html``.
+ this will default to ``im/invitations.html``.
``extra_context``
An dictionary of variables to add to the template context.
**Template:**
- invitations.html or ``template_name`` keyword argument.
+ im/invitations.html or ``template_name`` keyword argument.
**Settings:**
The view expectes the following settings are defined:
* LOGIN_URL: login uri
- * SIGNUP_TARGET: Where users should signup with their invitation code
- * DEFAULT_CONTACT_EMAIL: service support email
- * DEFAULT_FROM_EMAIL: from email
+ * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
+ * ASTAKOS_DEFAULT_FROM_EMAIL: from email
"""
status = None
message = None
realname = request.POST.get('realname')
if inviter.invitations > 0:
- code = _generate_invitation_code()
- invitation, created = Invitation.objects.get_or_create(
- inviter=inviter,
- username=username,
- defaults={'code': code, 'realname': realname})
-
try:
- baseurl = request.build_absolute_uri('/').rstrip('/')
- _send_invitation(request, baseurl, invitation)
- if created:
- inviter.invitations = max(0, inviter.invitations - 1)
- inviter.save()
+ invite_func(inviter, username, realname)
status = messages.SUCCESS
message = _('Invitation sent to %s' % username)
transaction.commit()
message = _('No invitations left')
messages.add_message(request, status, message)
- if request.GET.get('format') == 'json':
- sent = [{'email': inv.username,
- 'realname': inv.realname,
- 'is_accepted': inv.is_accepted}
- for inv in inviter.invitations_sent.all()]
- rep = {'invitations': inviter.invitations, 'sent': sent}
- return HttpResponse(json.dumps(rep))
-
- kwargs = {'user': inviter}
+ sent = [{'email': inv.username,
+ 'realname': inv.realname,
+ 'is_consumed': inv.is_consumed}
+ for inv in inviter.invitations_sent.all()]
+ kwargs = {'inviter': inviter,
+ 'sent':sent}
context = get_context(request, extra_context, **kwargs)
return render_response(template_name,
context_instance = context)
@login_required
-def edit_profile(request, template_name='profile.html', extra_context={}):
+def edit_profile(request, template_name='im/profile.html', extra_context={}):
"""
Allows a user to edit his/her profile.
In case of GET request renders a form for displaying the user information.
- In case of POST updates the user informantion.
+ In case of POST updates the user informantion and redirects to ``next``
+ url parameter if exists.
- If the user isn't logged in, redirects to settings.LOGIN_URL.
+ If the user isn't logged in, redirects to settings.LOGIN_URL.
**Arguments**
``template_name``
A custom template to use. This is optional; if not specified,
- this will default to ``profile.html``.
+ this will default to ``im/profile.html``.
``extra_context``
An dictionary of variables to add to the template context.
**Template:**
- profile.html or ``template_name`` keyword argument.
+ im/profile.html or ``template_name`` keyword argument.
+
+ **Settings:**
+
+ The view expectes the following settings are defined:
+
+ * LOGIN_URL: login uri
"""
- try:
- user = AstakosUser.objects.get(username=request.user)
- form = ProfileForm(instance=user)
- except AstakosUser.DoesNotExist:
- token = request.GET.get('auth', None)
- user = AstakosUser.objects.get(auth_token=token)
+ form = ProfileForm(instance=request.user)
+ extra_context['next'] = request.GET.get('next')
+ reset_cookie = False
if request.method == 'POST':
- form = ProfileForm(request.POST, instance=user)
+ form = ProfileForm(request.POST, instance=request.user)
if form.is_valid():
try:
- form.save()
+ prev_token = request.user.auth_token
+ user = form.save()
+ reset_cookie = user.auth_token != prev_token
+ form = ProfileForm(instance=user)
+ next = request.POST.get('next')
+ if next:
+ return redirect(next)
msg = _('Profile has been updated successfully')
messages.add_message(request, messages.SUCCESS, msg)
except ValueError, ve:
messages.add_message(request, messages.ERROR, ve)
return render_response(template_name,
+ reset_cookie = reset_cookie,
form = form,
context_instance = get_context(request,
- extra_context,
- user=user))
+ extra_context))
-@transaction.commit_manually
-def signup(request, template_name='signup.html', extra_context={}, backend=None):
+@requires_anonymous
+def signup(request, on_failure='im/signup.html', on_success='im/signup_complete.html', extra_context={}, backend=None):
"""
Allows a user to create a local account.
The user activation will be delegated to the backend specified by the ``backend`` keyword argument
if present, otherwise to the ``astakos.im.backends.InvitationBackend``
- if settings.INVITATIONS_ENABLED is True or ``astakos.im.backends.SimpleBackend`` if not
+ if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.backends.SimpleBackend`` if not
(see backends);
Upon successful user creation if ``next`` url parameter is present the user is redirected there
On unsuccessful creation, renders the same page with an error message.
- The view uses commit_manually decorator in order to ensure the user will be created
- only if the procedure has been completed successfully.
-
**Arguments**
- ``template_name``
- A custom template to use. This is optional; if not specified,
- this will default to ``signup.html``.
+ ``on_failure``
+ A custom template to render in case of failure. This is optional;
+ if not specified, this will default to ``im/signup.html``.
+
+
+ ``on_success``
+ A custom template to render in case of success. This is optional;
+ if not specified, this will default to ``im/signup_complete.html``.
``extra_context``
An dictionary of variables to add to the template context.
**Template:**
- signup.html or ``template_name`` keyword argument.
+ im/signup.html or ``on_failure`` keyword argument.
+ im/signup_complete.html or ``on_success`` keyword argument.
"""
- if not backend:
- backend = get_backend(request)
try:
- form = backend.get_signup_form()
+ if not backend:
+ backend = get_backend(request)
+ for provider in IM_MODULES:
+ extra_context['%s_form' % provider] = backend.get_signup_form(provider)
if request.method == 'POST':
+ provider = request.POST.get('provider')
+ next = request.POST.get('next', '')
+ form = extra_context['%s_form' % provider]
if form.is_valid():
- status, message = backend.signup(form)
- # rollback in case of error
- if status == messages.ERROR:
- transaction.rollback()
+ if provider != 'local':
+ url = reverse('astakos.im.target.%s.login' % provider)
+ url = '%s?email=%s&next=%s' % (url, form.data['email'], next)
+ if backend.invitation:
+ url = '%s&code=%s' % (url, backend.invitation.code)
+ return redirect(url)
else:
- transaction.commit()
- next = request.POST.get('next')
- if next:
- return redirect(next)
- messages.add_message(request, status, message)
- except Invitation.DoesNotExist, e:
+ status, message, user = backend.signup(form)
+ if user and user.is_active:
+ return prepare_response(request, user, next=next)
+ messages.add_message(request, status, message)
+ return render_response(on_success,
+ context_instance=get_context(request, extra_context))
+ except (Invitation.DoesNotExist, ValueError), e:
messages.add_message(request, messages.ERROR, e)
- return render_response(template_name,
- form = form if 'form' in locals() else UserCreationForm(),
+ for provider in IM_MODULES:
+ main = provider.capitalize() if provider == 'local' else 'ThirdParty'
+ formclass = '%sUserCreationForm' % main
+ extra_context['%s_form' % provider] = globals()[formclass]()
+ return render_response(on_failure,
context_instance=get_context(request, extra_context))
@login_required
-def send_feedback(request, template_name='feedback.html', email_template_name='feedback_mail.txt', extra_context={}):
+def send_feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context={}):
"""
Allows a user to send feedback.
In case of GET request renders a form for providing the feedback information.
In case of POST sends an email to support team.
- If the user isn't logged in, redirects to settings.LOGIN_URL.
+ If the user isn't logged in, redirects to settings.LOGIN_URL.
**Arguments**
``template_name``
A custom template to use. This is optional; if not specified,
- this will default to ``feedback.html``.
+ this will default to ``im/feedback.html``.
``extra_context``
An dictionary of variables to add to the template context.
**Template:**
- signup.html or ``template_name`` keyword argument.
+ im/signup.html or ``template_name`` keyword argument.
**Settings:**
- * FEEDBACK_CONTACT_EMAIL: List of feedback recipients
+ * LOGIN_URL: login uri
+ * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
"""
if request.method == 'GET':
form = FeedbackForm()
form = FeedbackForm(request.POST)
if form.is_valid():
- subject = _("Feedback from Okeanos")
+ subject = _("Feedback from %s" % SITENAME)
from_email = request.user.email
- recipient_list = [settings.FEEDBACK_CONTACT_EMAIL]
+ recipient_list = [DEFAULT_CONTACT_EMAIL]
content = render_to_string(email_template_name, {
- 'message': form.cleaned_data('feedback_msg'),
- 'data': form.cleaned_data('feedback_data'),
+ 'message': form.cleaned_data['feedback_msg'],
+ 'data': form.cleaned_data['feedback_data'],
'request': request})
- send_mail(subject, content, from_email, recipient_list)
-
- resp = json.dumps({'status': 'send'})
- return HttpResponse(resp)
+ try:
+ send_mail(subject, content, from_email, recipient_list)
+ message = _('Feedback successfully sent')
+ status = messages.SUCCESS
+ except (SMTPException, socket.error) as e:
+ status = messages.ERROR
+ message = getattr(e, 'strerror', '')
+ messages.add_message(request, status, message)
return render_response(template_name,
form = form,
context_instance = get_context(request, extra_context))
+
+def logout(request, template='registration/logged_out.html', extra_context={}):
+ """
+ Wraps `django.contrib.auth.logout` and delete the cookie.
+ """
+ auth_logout(request)
+ response = HttpResponse()
+ response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
+ next = request.GET.get('next')
+ if next:
+ response['Location'] = next
+ response.status_code = 302
+ return response
+ context = get_context(request, extra_context)
+ response.write(render_to_string(template, context_instance=context))
+ return response
+
+def activate(request):
+ """
+ Activates the user identified by the ``auth`` request parameter
+ """
+ token = request.GET.get('auth')
+ next = request.GET.get('next')
+ try:
+ user = AstakosUser.objects.get(auth_token=token)
+ except AstakosUser.DoesNotExist:
+ return HttpResponseBadRequest('No such user')
+
+ user.is_active = True
+ user.save()
+ return prepare_response(request, user, next, renew=True)