Commit 067961d3 authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

Merge branch 'master' into aquarium

Conflicts:
	snf-astakos-app/Changelog
	snf-astakos-app/README
	snf-astakos-app/astakos/im/api.py
	snf-astakos-app/astakos/im/backends.py
	snf-astakos-app/astakos/im/context_processors.py
	snf-astakos-app/astakos/im/functions.py
	snf-astakos-app/astakos/im/management/commands/sendactivation.py
	snf-astakos-app/astakos/im/models.py
	snf-astakos-app/astakos/im/settings.py
	snf-astakos-app/astakos/im/static/im/cloudbar/cloudbar.css
	snf-astakos-app/astakos/im/static/im/cloudbar/cloudbar.less
	snf-astakos-app/astakos/im/static/im/css/styles.css
	snf-astakos-app/astakos/im/static/im/css/styles.less
	snf-astakos-app/astakos/im/templates/im/login.html
	snf-astakos-app/astakos/im/views.py
parents 809b6d11 7c706f5f
v0.3.5
======
- New grnet styles
- New setting for recaptcha to use SSL
- New menu texts
- Improvements in get_menu view
v0.3.4
======
- Updated mail contents
- Cloudbar improvements
v0.3.3
======
......@@ -15,6 +31,7 @@ v0.3.3
- Fixed circular redirects when visiting login page from the logout one
- Removed im.context_processors.cloudbar (now using snf-common processor)
v0.3.2
======
......
......@@ -117,7 +117,7 @@ def get_services(request):
return HttpResponse(content=data, mimetype=mimetype)
def get_menu(request):
def get_menu(request, with_extra_links=False, with_signout=True):
location = request.GET.get('location', '')
exclude = []
index_url = reverse('index')
......@@ -137,18 +137,20 @@ def get_menu(request):
l.append({ 'url': absolute(reverse('astakos.im.views.index')),
'name': request.user.email})
l.append({ 'url': absolute(reverse('astakos.im.views.edit_profile')),
'name': "View your profile" })
if request.user.password:
l.append({ 'url': absolute(reverse('password_change')),
'name': "Change your password" })
if INVITATIONS_ENABLED:
l.append({ 'url': absolute(reverse('astakos.im.views.invite')),
'name': "Invite some friends" })
l.append({ 'url': absolute(reverse('astakos.im.views.send_feedback')),
'name': "Send feedback" })
l.append({ 'url': absolute(reverse('astakos.im.views.logout')),
'name': "Sign out"})
'name': "My account" })
if with_extra_links:
if request.user.password:
l.append({ 'url': absolute(reverse('password_change')),
'name': "Change password" })
if INVITATIONS_ENABLED:
l.append({ 'url': absolute(reverse('astakos.im.views.invite')),
'name': "Invitations" })
l.append({ 'url': absolute(reverse('astakos.im.views.send_feedback')),
'name': "Feedback" })
if with_signout:
l.append({ 'url': absolute(reverse('astakos.im.views.logout')),
'name': "Sign out"})
callback = request.GET.get('callback', None)
data = json.dumps(tuple(l))
mimetype = 'application/json'
......
......@@ -42,12 +42,12 @@ from django.db import transaction
from django.core.urlresolvers import reverse
from smtplib import SMTPException
from urllib import quote
from urlparse import urljoin
from astakos.im.models import AstakosUser, Invitation
from astakos.im.forms import *
from astakos.im.util import get_invitation
from astakos.im.functions import send_verification, send_notification, activate
from astakos.im.settings import INVITATIONS_ENABLED, DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, MODERATION_ENABLED, SITENAME, BASEURL, DEFAULT_ADMIN_EMAIL, RE_USER_EMAIL_PATTERNS
import socket
......@@ -157,7 +157,7 @@ class InvitationsBackend(SignupBackend):
return False
@transaction.commit_manually
def signup(self, form, email_template_name='im/activation_email.txt', admin_email_template_name='im/admin_notification.txt'):
def signup(self, form, verification_template_name='im/activation_email.txt', greeting_template_name='im/welcome_email.txt', admin_email_template_name='im/admin_notification.txt'):
"""
Initially creates an inactive user account. If the user is preaccepted
(has a valid invitation code) the user is activated and if the request
......@@ -172,23 +172,26 @@ class InvitationsBackend(SignupBackend):
user = form.save()
if self._is_preaccepted(user):
if user.email_verified:
user.is_active = True
user.save()
message = _('Registration completed. You can now login.')
try:
activate(user, greeting_template_name)
message = _('Registration completed. You can now login.')
except (SMTPException, socket.error) as e:
status = messages.ERROR
name = 'strerror'
message = getattr(e, 'name') if hasattr(e, 'name') else e
else:
try:
_send_verification(self.request, user, email_template_name)
send_verification(user, verification_template_name)
message = _('Verification sent to %s' % user.email)
except (SMTPException, socket.error) as e:
status = messages.ERROR
name = 'strerror'
message = getattr(e, name) if hasattr(e, name) else e
message = getattr(e, 'name') if hasattr(e, 'name') else e
else:
_send_notification(user, admin_email_template_name)
message = _('Your request for an account was successfully sent \
and pending approval from our administrators. You \
will be notified by email the next days. \
Thanks for being patient, the GRNET team')
send_notification(user, admin_email_template_name)
message = _('Your request for an account was successfully received and is now pending \
approval. You will be notified by email in the next few days. Thanks for \
your interest in ~okeanos! The GRNET team.')
status = messages.SUCCESS
except Invitation.DoesNotExist, e:
status = messages.ERROR
......@@ -264,23 +267,22 @@ class SimpleBackend(SignupBackend):
status = messages.SUCCESS
if not self._is_preaccepted(user):
try:
_send_notification(user, admin_email_template_name)
message = _('Your request for an account was successfully sent \
and pending approval from our administrators. You \
will be notified by email the next days. \
Thanks for being patient, the GRNET team')
send_notification(user, admin_email_template_name)
message = _('Your request for an account was successfully received and is now pending \
approval. You will be notified by email in the next few days. Thanks for \
your interest in ~okeanos! The GRNET team.')
except (SMTPException, socket.error) as e:
status = messages.ERROR
name = 'strerror'
message = getattr(e, name) if hasattr(e, name) else e
message = getattr(e, 'name') if hasattr(e, 'name') else e
else:
try:
_send_verification(self.request, user, email_template_name)
send_verification(user, email_template_name)
message = _('Verification sent to %s' % user.email)
except (SMTPException, socket.error) as e:
status = messages.ERROR
name = 'strerror'
message = getattr(e, name) if hasattr(e, name) else e
message = getattr(e, 'name') if hasattr(e, 'name') else e
# rollback in case of error
if status == messages.ERROR:
......@@ -288,29 +290,3 @@ class SimpleBackend(SignupBackend):
else:
transaction.commit()
return status, message, user
def _send_verification(request, user, template_name):
url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('astakos.im.views.activate')),
quote(user.auth_token),
quote(BASEURL))
message = render_to_string(template_name, {
'user': user,
'url': url,
'baseurl': BASEURL,
'site_name': SITENAME,
'support': DEFAULT_CONTACT_EMAIL})
sender = DEFAULT_FROM_EMAIL
send_mail('%s account activation' % SITENAME, message, sender, [user.email])
logger.info('Sent activation %s', user)
def _send_notification(user, template_name):
if not DEFAULT_ADMIN_EMAIL:
return
message = render_to_string(template_name, {
'user': user,
'baseurl': BASEURL,
'site_name': SITENAME,
'support': DEFAULT_CONTACT_EMAIL})
sender = DEFAULT_FROM_EMAIL
send_mail('%s account notification' % SITENAME, message, sender, [DEFAULT_ADMIN_EMAIL])
logger.info('Sent admin notification for user %s', user)
......@@ -56,7 +56,7 @@ def media(request):
def menu(request):
absolute = lambda (url): request.build_absolute_uri(url)
resp = get_menu(request)
resp = get_menu(request, True, False)
menu_items = json.loads(resp.content)[1:]
for item in menu_items:
item['is_active'] = absolute(request.path) == item['url']
......
......@@ -42,7 +42,7 @@ from django.utils.http import int_to_base36
from django.core.urlresolvers import reverse
from astakos.im.models import AstakosUser
from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY
from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL
from astakos.im.widgets import DummyWidget, RecaptchaWidget
import logging
......@@ -84,7 +84,7 @@ class LocalUserCreationForm(UserCreationForm):
raise forms.ValidationError(_("This field is required"))
try:
AstakosUser.objects.get(email = email)
raise forms.ValidationError(_("Email is reserved"))
raise forms.ValidationError(_("This email is already used"))
except AstakosUser.DoesNotExist:
return email
......@@ -204,7 +204,7 @@ class ThirdPartyUserCreationForm(ProfileForm):
raise forms.ValidationError(_("This field is required"))
try:
user = AstakosUser.objects.get(email = email)
raise forms.ValidationError(_("Email is reserved"))
raise forms.ValidationError(_("This email is already used"))
except AstakosUser.DoesNotExist:
return email
......@@ -264,8 +264,9 @@ class ExtendedPasswordResetForm(PasswordResetForm):
'url': url,
'site_name': SITENAME,
'user': user,
'baseurl': BASEURL
'baseurl': BASEURL,
'support': DEFAULT_CONTACT_EMAIL
}
from_email = DEFAULT_FROM_EMAIL
send_mail(_("Password reset on %s") % SITENAME,
send_mail(_("Password reset on %s alpha2 testing") % SITENAME,
t.render(Context(c)), from_email, [user.email])
......@@ -37,23 +37,76 @@ from django.utils.translation import ugettext as _
from django.template.loader import render_to_string
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from urllib import quote
from urlparse import urljoin
from random import randint
from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, SITENAME, BASEURL
from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, SITENAME, BASEURL, DEFAULT_ADMIN_EMAIL
from astakos.im.models import Invitation, AstakosUser
logger = logging.getLogger(__name__)
def activate(user, email_template_name='im/welcome_email.txt'):
def send_verification(user, template_name='im/activation_email.txt'):
"""
Activates the specific user and sends email.
Send email to user to verify his/her email and activate his/her account.
Raises SMTPException, socket.error
"""
user.is_active = True
user.save()
subject = _('Welcome to %s' % SITENAME)
url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('astakos.im.views.activate')),
quote(user.auth_token),
quote(BASEURL))
message = render_to_string(template_name, {
'user': user,
'url': url,
'baseurl': BASEURL,
'site_name': SITENAME,
'support': DEFAULT_CONTACT_EMAIL})
sender = DEFAULT_FROM_EMAIL
send_mail('%s alpha2 testing account activation is needed' % SITENAME, message, sender, [user.email])
logger.info('Sent activation %s', user)
def send_notification(user, template_name='im/admin_notification.txt'):
"""
Send email to DEFAULT_ADMIN_EMAIL to notify for a new user registration.
Raises SMTPException, socket.error
"""
if not DEFAULT_ADMIN_EMAIL:
return
message = render_to_string(template_name, {
'user': user,
'baseurl': BASEURL,
'site_name': SITENAME,
'support': DEFAULT_CONTACT_EMAIL})
sender = DEFAULT_FROM_EMAIL
send_mail('%s alpha2 testing account notification' % SITENAME, message, sender, [DEFAULT_ADMIN_EMAIL])
logger.info('Sent admin notification for user %s', user)
def send_invitation(invitation, template_name='im/invitation.txt'):
"""
Send invitation email.
Raises SMTPException, socket.error
"""
subject = _('Invitation to %s alpha2 testing' % SITENAME)
url = '%s?code=%d' % (urljoin(BASEURL, reverse('astakos.im.views.signup')), invitation.code)
message = render_to_string('im/invitation.txt', {
'invitation': invitation,
'url': url,
'baseurl': BASEURL,
'site_name': SITENAME,
'support': DEFAULT_CONTACT_EMAIL})
sender = DEFAULT_FROM_EMAIL
send_mail(subject, message, sender, [invitation.username])
logger.info('Sent invitation %s', invitation)
def send_greeting(user, email_template_name='im/welcome_email.txt'):
"""
Send welcome email.
Raises SMTPException, socket.error
"""
subject = _('Welcome to %s alpha2 testing' % SITENAME)
message = render_to_string(email_template_name, {
'user': user,
'url': urljoin(BASEURL, reverse('astakos.im.views.index')),
......@@ -64,6 +117,16 @@ def activate(user, email_template_name='im/welcome_email.txt'):
send_mail(subject, message, sender, [user.email])
logger.info('Sent greeting %s', user)
def activate(user, email_template_name='im/welcome_email.txt'):
"""
Activates the specific user and sends email.
Raises SMTPException, socket.error
"""
user.is_active = True
user.save()
send_greeting(user, email_template_name)
def _generate_invitation_code():
while True:
code = randint(1, 2L**63 - 1)
......@@ -73,7 +136,7 @@ def _generate_invitation_code():
except Invitation.DoesNotExist:
return code
def invite(inviter, username, realname):
def invite(inviter, username, realname, email_template_name='im/welcome_email.txt'):
"""
Send an invitation email and upon success reduces inviter's invitation by one.
......@@ -85,17 +148,7 @@ def invite(inviter, username, realname):
code=code,
realname=realname)
invitation.save()
subject = _('Invitation to %s' % SITENAME)
url = '%s?code=%d' % (urljoin(BASEURL, reverse('astakos.im.views.signup')), code)
message = render_to_string('im/invitation.txt', {
'invitation': invitation,
'url': url,
'baseurl': BASEURL,
'service': SITENAME,
'support': DEFAULT_CONTACT_EMAIL})
sender = DEFAULT_FROM_EMAIL
send_mail(subject, message, sender, [invitation.username])
logger.info('Sent invitation %s', invitation)
send_invitation(invitation, email_template_name)
inviter.invitations = max(0, inviter.invitations - 1)
inviter.save()
......@@ -105,4 +158,4 @@ def set_user_credibility(email, has_credits):
user.has_credits = has_credits
user.save()
except AstakosUser.DoesNotExist, e:
logger.exception(e)
\ No newline at end of file
logger.exception(e)
......@@ -34,16 +34,14 @@
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from astakos.im.functions import activate
from astakos.im.functions import send_verification
from ._common import get_user
class Command(BaseCommand):
args = "<user ID or email> [user ID or email] ..."
help = "Activates one or more users"
help = "Sends an activation email to one or more users"
@transaction.commit_manually
def handle(self, *args, **options):
if not args:
raise CommandError("No user was given")
......@@ -59,11 +57,6 @@ class Command(BaseCommand):
self.stderr.write(msg)
continue
try:
activate(user)
transaction.commit()
except Exception, e:
transaction.rollback()
raise e
send_verification(user)
self.stdout.write("Activated '%s'\n" % (user.email,))
\ No newline at end of file
......@@ -57,6 +57,7 @@ CLOUD_SERVICES = getattr(settings, 'ASTAKOS_CLOUD_SERVICES', (
RECAPTCHA_PUBLIC_KEY = getattr(settings, 'ASTAKOS_RECAPTCHA_PUBLIC_KEY', '')
RECAPTCHA_PRIVATE_KEY = getattr(settings, 'ASTAKOS_RECAPTCHA_PRIVATE_KEY', '')
RECAPTCHA_OPTIONS = getattr(settings, 'ASTAKOS_RECAPTCHA_OPTIONS', {'theme': 'white'})
RECAPTCHA_USE_SSL = getattr(settings, 'ASTAKOS_RECAPTCHA_USE_SSL', True)
# set AstakosUser fields to propagate in the billing system
BILLING_FIELDS = getattr(settings, 'ASTAKOS_BILLING_FIELDS', ['is_active'])
......@@ -68,4 +69,4 @@ QUEUE_CONNECTION = getattr(settings, 'ASTAKOS_QUEUE_CONNECTION', None) # Example
LOGOUT_NEXT = getattr(settings, 'ASTAKOS_LOGOUT_NEXT', '')
# Set user email patterns that are automatically activated
RE_USER_EMAIL_PATTERNS = getattr(settings, 'ASTAKOS_RE_USER_EMAIL_PATTERNS', [])
\ No newline at end of file
RE_USER_EMAIL_PATTERNS = getattr(settings, 'ASTAKOS_RE_USER_EMAIL_PATTERNS', [])
......@@ -73,6 +73,12 @@
font-weight: bold;
color: #ccc;
}
.servicesbar .services a.with-icon {
margin: 0 !important;
padding: 0 !important;
width: 35px;
height: 35px;
}
.servicesbar .services a.active {
font-size: 13px !important;
color: #ffffff !important;
......
......@@ -44,6 +44,7 @@ $(document).ready(function(){
var slink = $("<a>");
if (el.icon) {
slink.append($('<img src="'+cssloc+el.icon+'"/>'));
slink.addClass("with-icon");
} else {
slink.text(el.name);
}
......
......@@ -51,6 +51,14 @@
.clearfix();
a {
&.with-icon {
margin: 0 !important;
padding: 0 !important;
width: 35px;
height: 35px;
}
font-size: 13px !important;
font-weight: bold;
color: #ccc;
......
......@@ -466,6 +466,17 @@ a.button:hover {
box-sizing: border-box;
*behavior: url(boxsizing.htc);
}
.clearme {
zoom: 1;
}
.clearme:before, .clearme:after {
display: table;
content: "";
zoom: 1;
}
.clearme:after {
clear: both;
}
body {
font-family: 'Antic', sans-serif;
font-size: 14px;
......@@ -477,13 +488,14 @@ body {
font-weight: normal;
line-height: 22px;
letter-spacing: 1px;
font-size: 12px;
}
.topbar {
background-color: #cfcdc7;
}
.topbar .head {
float: left;
padding: 6px;
padding: 2.333333333333333px;
}
.topbar .links {
zoom: 1;
......@@ -503,7 +515,7 @@ body {
display: block;
float: left;
margin-left: 10px;
padding: 12px;
padding: 8.333333333333332px;
}
.topbar .links a:hover {
background-color: #1a1a1a;
......@@ -513,7 +525,8 @@ section a,
p a,
form a,
.section a,
.styledlinks a {
.styledlinks a,
a.styled {
color: #000000;
text-decoration: none;
border-bottom: 1px solid #f89a1c;
......@@ -522,21 +535,24 @@ section a:hover,
p a:hover,
form a:hover,
.section a:hover,
.styledlinks a:hover {
.styledlinks a:hover,
a.styled:hover {
color: #f89a1c;
}
section a.noborder,
p a.noborder,
form a.noborder,
.section a.noborder,
.styledlinks a.noborder {
.styledlinks a.noborder,
a.styled.noborder {
border: none;
}
section a em,
p a em,
form a em,
.section a em,
.styledlinks a em {
.styledlinks a em,
a.styled em {
color: #3582ac;
}
a.simple {
......@@ -584,8 +600,8 @@ a img {
}
div.header {
position: relative;
margin-top: 88px;
margin-bottom: 22px;
margin-top: 36.666666666666664px;
margin-bottom: 14.666666666666666px;
}
div.header h1 {
color: #cfcdc7;
......@@ -606,8 +622,8 @@ div.header h1 {
.footer {
border-bottom: 1px solid #808080;
border-top: 1px solid #a6a6a6;
padding-top: 22px;
padding-bottom: 22px;
padding-top: 14.666666666666666px;
padding-bottom: 14.666666666666666px;
}
ul.inline {
zoom: 1;
......@@ -628,7 +644,7 @@ ul.inline li {
.mainnav.quicknav {
position: absolute;
right: 0;
top: -57.2px;
top: -38.13333333333333px;
margin: 0;
}
.mainnav.quicknav li {
......@@ -637,18 +653,22 @@ ul.inline li {
}
.navigation {
height: 83px;
position: absolute;
right: -15px;
top: -23px;
}
.mainnav {
font-size: 1.2em;
font-size: 1.3em;
}
.mainnav.subnav {
margin-bottom: -22px;
margin-bottom: -14.666666666666666px;
}
.mainnav.subnav li {
margin-top: 1.2em;
margin-top: 5.866666666666667px;
float: right;
}
.mainnav li {
margin-top: 66px;
margin-top: 29.333333333333332px;
}
.mainnav li.active a {
/*border-bottom: 1px solid @linkColor;*/
......@@ -697,7 +717,7 @@ div.page {
margin-left: -22px;
zoom: 1;
margin-left: -22px;
margin-top: 132px;
margin-top: 73.33333333333333px;