+v0.3.2
+======
+
+- improved styles of sign up button
+- do not send admin notification email if ASTAKOS_DEFAULT_ADMIN_EMAIL is not
+ set
+- updated cloudbar styles
+
+
v0.3.1
======
ASTAKOS_BASEURL \http://pithos.dev.grnet.gr Astakos baseurl
ASTAKOS_SITENAME GRNET Cloud Service name that appears in emails
ASTAKOS_CLOUD_SERVICES ({'icon': 'home-icon.png', 'id': 'cloud', 'name': 'grnet cloud', 'url': '/'}, Cloud services appear in the horizontal bar
- {'id': 'okeanos', 'name': '~okeanos', 'url': '/okeanos.html'},
+ {'id': 'okeanos', 'name': 'cyclades', 'url': '/okeanos.html'},
{'id': 'pithos', 'name': 'pithos+', 'url': '/ui/'})
ASTAKOS_RECAPTCHA_PUBLIC_KEY Recaptcha public key obtained after registration here: http://recaptcha.net
ASTAKOS_RECAPTCHA_PRIVATE_KEY Recaptcha private key obtained after registration here: http://recaptcha.net
+ASTAKOS_RECAPTCHA_OPTIONS {'theme': 'white'} Options for customizing reCAPTCHA look and feel
+ (see: http://code.google.com/intl/el-GR/apis/recaptcha/docs/customization.html)
+ASTAKOS_LOGOUT_NEXT Where the user should be redirected after logout
+ (if not set and no next parameter is defined it renders login page with message)
+ASTAKOS_BILLING_FIELDS ['id', 'is_active', 'provider', 'third_party_identifier'] AstakosUser fields to propagate in the billing system
+ASTAKOS_QUEUE_CONNECTION The queue connection ex. 'rabbitmq://guest:guest@localhost:5672/astakos.userEvent.#'
+ (if it is not set, it does not send messages)
============================== ============================================================================= ===========================================================================================
Administrator functions
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
+import logging
+
from traceback import format_exc
from time import time, mktime
from urllib import quote
from astakos.im.models import AstakosUser
from astakos.im.settings import CLOUD_SERVICES, INVITATIONS_ENABLED
+logger = logging.getLogger(__name__)
+
def render_fault(request, fault):
if isinstance(fault, InternalServerError) and settings.DEBUG:
fault.details = format_exc(fault)
response['Content-Length'] = len(response.content)
return response
except BaseException, e:
+ logger.exception(e)
fault = InternalServerError('Unexpected error')
return render_fault(request, fault)
index_url = absolute(reverse('astakos.im.views.index'))
if urlparse(location).query.rfind('next=') == -1:
index_url = '%s?next=%s' % (index_url, quote(location))
- l = [{ 'url': index_url, 'name': "login..."}]
+ l = [{ 'url': index_url, 'name': "Sign in"}]
if request.user.is_authenticated():
l = []
l.append({ 'url': absolute(reverse('astakos.im.views.edit_profile')),
'name': request.user.email})
l.append({ 'url': absolute(reverse('astakos.im.views.edit_profile')),
- 'name': "view your profile..." })
+ 'name': "View your profile" })
if request.user.password:
l.append({ 'url': absolute(reverse('password_change')),
- 'name': "change your password..." })
+ 'name': "Change your password" })
if INVITATIONS_ENABLED:
l.append({ 'url': absolute(reverse('astakos.im.views.invite')),
- 'name': "invite some friends..." })
+ 'name': "Invite some friends" })
l.append({ 'url': absolute(reverse('astakos.im.views.send_feedback')),
- 'name': "feedback..." })
+ 'name': "Send feedback" })
l.append({ 'url': absolute(reverse('astakos.im.views.logout')),
- 'name': "logout..."})
+ 'name': "Sign out"})
callback = request.GET.get('callback', None)
data = json.dumps(tuple(l))
message = _('Registration completed. You can now login.')
else:
_send_notification(user, admin_email_template_name)
- message = _('Registration completed. You will receive an email upon your account\'s activation.')
+ 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')
status = messages.SUCCESS
except Invitation.DoesNotExist, e:
status = messages.ERROR
if MODERATION_ENABLED:
try:
_send_notification(user, admin_email_template_name)
- message = _('Registration completed. You will receive an email upon your account\'s activation.')
+ 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')
except (SMTPException, socket.error) as e:
status = messages.ERROR
name = 'strerror'
from django.db import models
from django.contrib.auth.models import User, UserManager
-from astakos.im.settings import DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL, AUTH_TOKEN_DURATION, BILLING_FIELDS, QUEUE_EXCHANGE
+from astakos.im.settings import DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL, AUTH_TOKEN_DURATION, BILLING_FIELDS, QUEUE_CONNECTION
from synnefo.lib.queue import exchange_connect, exchange_send, exchange_close, Receipt
QUEUE_CLIENT_ID = 3 # Astakos.
return True
return False
- if QUEUE_EXCHANGE and should_send(user):
+ if QUEUE_CONNECTION and should_send(user):
l = [[elem, str(user.__getattribute__(elem))] for elem in BILLING_FIELDS]
details = dict(l)
details['eventType'] = 'create' if not user.id else 'modify'
body = Receipt(QUEUE_CLIENT_ID, user.email, '', 0, details).format()
- conn = exchange_connect(QUEUE_EXCHANGE)
- routing_key = QUEUE_EXCHANGE.replace('#', body['id'])
+ conn = exchange_connect(QUEUE_CONNECTION)
+ routing_key = QUEUE_CONNECTION.replace('#', body['id'])
exchange_send(conn, routing_key, body)
exchange_close(conn)
\ No newline at end of file
# Set cloud services appear in the horizontal bar
CLOUD_SERVICES = getattr(settings, 'ASTAKOS_CLOUD_SERVICES', (
{ 'url':'/', 'name':'grnet cloud', 'id':'cloud', 'icon':'home-icon.png' },
- { 'url':'/okeanos.html', 'name':'~okeanos', 'id':'okeanos' },
+ { 'url':'/okeanos.html', 'name':'cyclades', 'id':'okeanos' },
{ 'url':'/ui/', 'name':'pithos+', 'id':'pithos' }))
# Set recaptcha keys
BILLING_FIELDS = getattr(settings, 'ASTAKOS_BILLING_FIELDS', ['id', 'is_active', 'provider', 'third_party_identifier'])
# Queue for billing.
-QUEUE_EXCHANGE = getattr(settings, 'ASTAKOS_QUEUE_EXCHANGE', None) # Example: 'rabbitmq://guest:guest@localhost:5672/astakos.userEvent.#'
\ No newline at end of file
+QUEUE_CONNECTION = getattr(settings, 'ASTAKOS_QUEUE_CONNECTION', None) # Example: 'rabbitmq://guest:guest@localhost:5672/astakos.userEvent.#'
+
+# Set where the user should be redirected after logout
+LOGOUT_NEXT = getattr(settings, 'ASTAKOS_LOGOUT_NEXT', '')
\ No newline at end of file
* Snippets of reusable CSS to develop faster and keep code readable
* ----------------------------------------------------------------- */
.servicesbar {
- font-family: arial, sans-serif;
+ font-family: arial, sans-serif !important;
font-size: 13px !important;
line-height: 13px;
letter-spacing: 0px;
}
.servicesbar a {
border: none !important;
- font-size: inherit !important;
+ font-family: arial, sans-serif !important;
+ font-size: 13px !important;
color: #e6e6e6;
text-decoration: none;
display: block;
}
.servicesbar a.active {
font-weight: bold;
+ font-size: 13px !important;
background-color: #333;
}
.servicesbar a img {
.servicesbar .services:after {
clear: both;
}
+.servicesbar .services a {
+ font-size: 13px !important;
+ font-weight: bold;
+ color: #ccc;
+}
+.servicesbar .services a.active {
+ font-size: 13px !important;
+ color: #ffffff !important;
+}
+.servicesbar .services a:hover {
+ background-color: #444;
+}
.servicesbar .profile {
margin-top: -35px;
zoom: 1;
.servicesbar .profile:after {
clear: both;
}
+.servicesbar .profile .user > a {
+ font-weight: bold !important;
+ font-size: 12px !important;
+}
.servicesbar .profile a {
float: none;
}
// mini reset
ol, ul { list-style:none; margin:0; padding:0;}
li {margin:0; padding:0;}
- font-family: arial, sans-serif;
+ font-family: arial, sans-serif !important;
font-size: 13px !important;
line-height: 13px;
letter-spacing: 0px;
a {
border: none !important;
- font-size: inherit !important;
+ font-family: arial, sans-serif !important;
+ font-size: 13px !important;
color: @toolbarColor;
text-decoration: none;
display: block;
}
&.active {
font-weight: bold;
+ font-size: 13px !important;
background-color: #333;
}
.services {
.clearfix();
+
+ a {
+ font-size: 13px !important;
+ font-weight: bold;
+ color: #ccc;
+
+ &.active {
+ font-size: 13px !important;
+ color: @white !important;
+ }
+ &:hover {
+ background-color: #444;
+ }
+ }
}
position: relative;
text-align: right;
min-width: 200px;
width: 200px;
+
+ .user {
+ &> a {
+ font-weight: bold !important;
+ font-size: 12px !important;
+ }
+ }
a {
float: none;
}
font-weight: normal;
line-height: 22px;
letter-spacing: 1px;
+ font-family: 'Antic', sans-serif;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 22px;
+ letter-spacing: 1px;
background-color: #3582ac;
+ -webkit-transition: background-color 0.15s linear;
+ transition: background-color 0.15s linear;
+ -webkit-transition: background-color 0.15s linear;
+ transition: background-color 0.15s linear;
color: #ffffff;
border: none;
padding: 0.8em 22px;
.button:hover {
background-color: #f89a1c;
}
+.button a {
+ color: #ffffff !important;
+ text-decoration: none !important;
+ border: none !important;
+}
+a.button {
+ color: #ffffff !important;
+ text-decoration: none !important;
+ border: none !important;
+}
+.makeRow {
+ zoom: 1;
+ margin-left: -22px;
+}
+.makeRow:before, .makeRow:after {
+ display: table;
+ content: "";
+ zoom: 1;
+}
+.makeRow:after {
+ clear: both;
+}
+.button {
+ font-family: 'Antic', sans-serif;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 22px;
+ letter-spacing: 1px;
+ font-family: 'Antic', sans-serif;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 22px;
+ letter-spacing: 1px;
+ background-color: #3582ac;
+ -webkit-transition: background-color 0.15s linear;
+ transition: background-color 0.15s linear;
+ -webkit-transition: background-color 0.15s linear;
+ transition: background-color 0.15s linear;
+ color: #ffffff;
+ border: none;
+ padding: 0.8em 22px;
+ font-size: 1em;
+}
+.button:hover {
+ background-color: #f89a1c;
+}
+.button a {
+ color: #ffffff !important;
+ text-decoration: none !important;
+ border: none !important;
+}
+a.button {
+ color: #ffffff !important;
+ text-decoration: none !important;
+ border: none !important;
+}
/*addon to style django forms rendered with as_p filter*/
/*
* Tables.less
font-weight: normal;
line-height: 22px;
letter-spacing: 1px;
+ font-family: 'Antic', sans-serif;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 22px;
+ letter-spacing: 1px;
}
.topbar {
background-color: #cfcdc7;
.page {
zoom: 1;
margin-left: -22px;
+ zoom: 1;
+ margin-left: -22px;
margin-top: 42px;
font-size: 1.1em;
}
.page:after {
clear: both;
}
+.page:before, .page:after {
+ display: table;
+ content: "";
+ zoom: 1;
+}
+.page:after {
+ clear: both;
+}
.page .page-inner {
position: relative;
}
.maincol.full {
zoom: 1;
margin-left: -22px;
+ zoom: 1;
+ margin-left: -22px;
margin-left: 0;
display: inline;
float: left;
.maincol.full:after {
clear: both;
}
+.maincol.full:before, .maincol.full:after {
+ display: table;
+ content: "";
+ zoom: 1;
+}
+.maincol.full:after {
+ clear: both;
+}
.appbar {
height: 30px;
background-color: #3582ac;
font-weight: normal;
line-height: 22px;
letter-spacing: 1px;
+ font-family: 'Antic', sans-serif;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 22px;
+ letter-spacing: 1px;
border: 1px solid #808080;
margin-bottom: -1px;
padding: 0.8em;
font-weight: normal;
line-height: 22px;
letter-spacing: 1px;
+ font-family: 'Antic', sans-serif;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 22px;
+ letter-spacing: 1px;
border: 1px solid #808080;
margin-bottom: -1px;
padding: 0.8em;
font-weight: normal;
line-height: 22px;
letter-spacing: 1px;
+ font-family: 'Antic', sans-serif;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 22px;
+ letter-spacing: 1px;
+ background-color: #3582ac;
+ -webkit-transition: background-color 0.15s linear;
+ transition: background-color 0.15s linear;
+ -webkit-transition: background-color 0.15s linear;
+ transition: background-color 0.15s linear;
+ color: #ffffff;
+ border: none;
+ padding: 0.8em 22px;
+ font-size: 1em;
+ font-family: 'Antic', sans-serif;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 22px;
+ letter-spacing: 1px;
+ font-family: 'Antic', sans-serif;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 22px;
+ letter-spacing: 1px;
background-color: #3582ac;
+ -webkit-transition: background-color 0.15s linear;
+ transition: background-color 0.15s linear;
+ -webkit-transition: background-color 0.15s linear;
+ transition: background-color 0.15s linear;
color: #ffffff;
border: none;
padding: 0.8em 22px;
form input.submit:hover, form input[type="submit"]:hover {
background-color: #f89a1c;
}
+form input.submit a, form input[type="submit"] a {
+ color: #ffffff !important;
+ text-decoration: none !important;
+ border: none !important;
+}
+form input.submit:hover, form input[type="submit"]:hover {
+ background-color: #f89a1c;
+}
+form input.submit a, form input[type="submit"] a {
+ color: #ffffff !important;
+ text-decoration: none !important;
+ border: none !important;
+}
form .with-errors input, form .with-errors textarea, form .with-errors select {
color: #9d261d;
}
.row {
zoom: 1;
margin-left: -22px;
+ zoom: 1;
+ margin-left: -22px;
+}
+.row:before, .row:after {
+ display: table;
+ content: "";
+ zoom: 1;
+}
+.row:after {
+ clear: both;
}
.row:before, .row:after {
display: table;
color: #808080 !important;
-webkit-transition: color 0.15s linear;
transition: color 0.15s linear;
+ -webkit-transition: color 0.15s linear;
+ transition: color 0.15s linear;
}
.footer a {
color: #b3b3b3;
-webkit-transition: color 0.15s linear;
transition: color 0.15s linear;
+ -webkit-transition: color 0.15s linear;
+ transition: color 0.15s linear;
text-decoration: none;
}
.footer a:hover {
@import "../less/bootstrap.less";
+@import "../less/xtra.less";
@import "../less/django_forms.less";
@import "../less/tables.less";
.button {
#font.main();
background-color: @buttonBg;
+ .transit(background-color);
color: @buttonColor;
border: none;
padding: 0.8em @gridGutterWidth;
&:hover {
background-color: @linkColor;
}
+
+ a {
+ color: @white !important;
+ text-decoration: none !important;
+ border: none !important;
+ }
+}
+
+a.button {
+ color: @white !important;
+ text-decoration: none !important;
+ border: none !important;
}
</div>
{% endfor %}
</div>
- <div class="section">
- <a href="{% url astakos.im.views.signup %}" class="action">SIGN UP</a>
+ {% block body.signup %}
+ <div class="section signup">
+ <br /><br />
+ <h2>NEW TO OKEANOS ?</h2>
+ <a class="button" href="{% url astakos.im.views.signup %}">CREATE ACCOUNT</a>
</div>
</div>
+ {% endblock %}
{% endblock %}
from django.conf.urls.defaults import patterns, include, url
-from astakos.im.forms import ExtendedPasswordResetForm
+from astakos.im.forms import ExtendedPasswordResetForm, LoginForm
from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED
urlpatterns = patterns('astakos.im.views',
url(r'^login/?$', 'index'),
url(r'^profile/?$', 'edit_profile'),
url(r'^feedback/?$', 'send_feedback'),
- url(r'^signup/?$', 'signup'),
- url(r'^logout/?$', 'logout'),
+ url(r'^signup/?$', 'signup', {'on_success':'im/login.html', 'extra_context':{'form':LoginForm()}}),
+ url(r'^logout/?$', 'logout', {'template':'im/login.html', 'extra_context':{'form':LoginForm()}}),
url(r'^activate/?$', 'activate')
)
from astakos.im.backends import get_backend
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.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, BASEURL, LOGOUT_NEXT
from astakos.im.functions import invite as invite_func
logger = logging.getLogger(__name__)
context_instance = get_context(request,
extra_context))
-@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.
im/signup.html or ``on_failure`` keyword argument.
im/signup_complete.html or ``on_success`` keyword argument.
"""
+ if request.user.is_authenticated():
+ return HttpResponseRedirect(reverse('astakos.im.views.index'))
try:
if not backend:
backend = get_backend(request)
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))
+ context_instance=get_context(request, extra_context))
except (Invitation.DoesNotExist, ValueError), e:
messages.add_message(request, messages.ERROR, e)
for provider in IM_MODULES:
response['Location'] = next
response.status_code = 302
return response
+ elif LOGOUT_NEXT:
+ response['Location'] = LOGOUT_NEXT
+ response.status_code = 301
+ return response
+ messages.add_message(request, messages.SUCCESS, _('You have successfully logged out.'))
context = get_context(request, extra_context)
response.write(render_to_string(template, context_instance=context))
return response
#ASTAKOS_COOKIE_NAME = '_pithos2_a'
#ASTAKOS_COOKIE_DOMAIN = None
+#ASTAKOS_COOKIE_SECURE = True
#ASTAKOS_IM_STATIC_URL = '/im/static/im/'
#ASTAKOS_SITENAME = 'GRNET Cloud'
# Set cloud services appear in the horizontal bar
-#ASTAKOS_CLOUD_SERVICES = getattr(settings, 'ASTAKOS_CLOUD_SERVICES', (
+#ASTAKOS_CLOUD_SERVICES = (
# { 'url':'/', 'name':'grnet cloud', 'id':'cloud', 'icon':'home-icon.png' },
# { 'url':'/okeanos.html', 'name':'~okeanos', 'id':'okeanos' },
-# { 'url':'/ui/', 'name':'pithos+', 'id':'pithos' }))
+# { 'url':'/ui/', 'name':'pithos+', 'id':'pithos' })
#
# Set recaptcha keys