Commit 1df36221 authored by Olga Brani's avatar Olga Brani
Browse files

Merge branch '0.6.4' of https://code.grnet.gr/git/astakos into 0.6.4

Conflicts:
	snf-astakos-app/astakos/im/static/im/css/modules.css
	snf-astakos-app/astakos/im/static/im/js/common.js
	snf-astakos-app/astakos/im/templates/im/astakosuserquota_list.html
	snf-astakos-app/astakos/im/views.py
parents d5a8054b d7d59ff9
......@@ -40,54 +40,64 @@ Settings
Configure in ``settings.py`` or a ``.conf`` file in ``/etc/synnefo`` if using snf-webproject.
=================================== ============================================================================= ===========================================================================================
Name Default value Description
=================================== ============================================================================= ===========================================================================================
ASTAKOS_AUTH_TOKEN_DURATION one month Expiration time of newly created auth tokens
ASTAKOS_DEFAULT_USER_LEVEL 4 Default (not-invited) user level
ASTAKOS_INVITATIONS_PER_LEVEL {0:100, 1:2, 2:0, 3:0, 4:0} Number of user invitations per user level
ASTAKOS_DEFAULT_CONTACT_EMAIL support\@cloud.grnet.gr Contact email
ASTAKOS_IM_MODULES ['local', 'shibboleth'] Signup modules
ASTAKOS_FORCE_PROFILE_UPDATE True Force user profile verification
ASTAKOS_INVITATIONS_ENABLED True Enable invitations
ASTAKOS_COOKIE_NAME _pithos2_a ``Key`` parameter passed in ``django.http.HttpResponse.set_cookie``
ASTAKOS_COOKIE_DOMAIN None ``Domain`` parameter passed in ``django.http.HttpResponse.set_cookie``
ASTAKOS_COOKIE_SECURE True ``Secure`` parameter passed in ``django.http.HttpResponse.set_cookie``
ASTAKOS_IM_STATIC_URL /static/im/ URL to use when referring to static files
ASTAKOS_MODERATION_ENABLED True If False and invitations are not enabled newly created user will be automatically accepted
ASTAKOS_BASEURL \http://pithos.dev.grnet.gr Astakos baseurl
ASTAKOS_SITENAME GRNET Cloud Service name that appears in emails
ASTAKOS_RECAPTCHA_ENABLED True Enable recaptcha
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'
(if it is not set, it does not send messages)
ASTAKOS_RE_USER_EMAIL_PATTERNS [] Email patterns that are automatically activated ex. ['^[a-zA-Z0-9\._-]+@grnet\.gr$']
ASTAKOS_LOGIN_MESSAGES {} Notification messages to display on login page header
e.g. {'warning': 'Warning message (can contain html)'}
ASTAKOS_PROFILE_EXTRA_LINKS {} Messages to display as extra actions in account forms
e.g. {'https://cms.okeanos.grnet.gr/': 'Back to ~okeanos'}
ASTAKOS_RATELIMIT_RETRIES_ALLOWED 3 Number of unsuccessful login requests per minute allowed for a specific account.
When this number exceeds and ASTAKOS_RECAPTCHA_ENABLED is set the user has to solve a
captcha challenge.
ASTAKOS_EMAILCHANGE_ENABLED False Enable email change mechanism
ASTAKOS_EMAILCHANGE_ACTIVATION_DAYS 10 Number of days that email change requests remain active
ASTAKOS_LOGGING_LEVEL INFO Message logging severity
ASTAKOS_QUOTA_HOLDER_URL '' The quota holder URI
=========================================== ============================================================================= ===========================================================================================
Name Default value Description
=========================================== ============================================================================= ===========================================================================================
ASTAKOS_AUTH_TOKEN_DURATION one month Expiration time of newly created auth tokens
ASTAKOS_DEFAULT_USER_LEVEL 4 Default (not-invited) user level
ASTAKOS_INVITATIONS_PER_LEVEL {0:100, 1:2, 2:0, 3:0, 4:0} Number of user invitations per user level
ASTAKOS_DEFAULT_FROM_EMAIL GRNET Cloud <no-reply\@grnet.gr> ``from`` parameter passed in ``django.core.mail.send_mail``
ASTAKOS_DEFAULT_CONTACT_EMAIL support\@cloud.grnet.gr Contact email
ASTAKOS_DEFAULT_ADMIN_EMAIL support\@cloud.grnet.gr Administrator email to receive user creation notifications (if None disables notifications)
ASTAKOS_IM_MODULES ['local', 'shibboleth'] Signup modules
ASTAKOS_FORCE_PROFILE_UPDATE True Force user profile verification
ASTAKOS_INVITATIONS_ENABLED True Enable invitations
ASTAKOS_COOKIE_NAME _pithos2_a ``Key`` parameter passed in ``django.http.HttpResponse.set_cookie``
ASTAKOS_COOKIE_DOMAIN None ``Domain`` parameter passed in ``django.http.HttpResponse.set_cookie``
ASTAKOS_COOKIE_SECURE True ``Secure`` parameter passed in ``django.http.HttpResponse.set_cookie``
ASTAKOS_IM_STATIC_URL /static/im/ URL to use when referring to static files
ASTAKOS_MODERATION_ENABLED True If False and invitations are not enabled newly created user will be automatically accepted
ASTAKOS_BASEURL \http://pithos.dev.grnet.gr Astakos baseurl
ASTAKOS_SITENAME GRNET Cloud Service name that appears in emails
ASTAKOS_RECAPTCHA_ENABLED True Enable recaptcha
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'
(if it is not set, it does not send messages)
ASTAKOS_RE_USER_EMAIL_PATTERNS [] Email patterns that are automatically activated ex. ['^[a-zA-Z0-9\._-]+@grnet\.gr$']
ASTAKOS_LOGIN_MESSAGES {} Notification messages to display on login page header
e.g. {'warning': 'Warning message (can contain html)'}
ASTAKOS_PROFILE_EXTRA_LINKS {} Messages to display as extra actions in account forms
e.g. {'https://cms.okeanos.grnet.gr/': 'Back to ~okeanos'}
ASTAKOS_RATELIMIT_RETRIES_ALLOWED 3 Number of unsuccessful login requests per minute allowed for a specific account.
When this number exceeds and ASTAKOS_RECAPTCHA_ENABLED is set the user has to solve a
captcha challenge.
ASTAKOS_EMAILCHANGE_ENABLED False Enable email change mechanism
ASTAKOS_EMAILCHANGE_ACTIVATION_DAYS 10 Number of days that email change requests remain active
ASTAKOS_LOGGING_LEVEL INFO Message logging severity
ASTAKOS_INVITATION_EMAIL_SUBJECT 'Invitation to %s alpha2 testing' % SITENAME Invitation email subject
ASTAKOS_GREETING_EMAIL_SUBJECT 'Welcome to %s alpha2 testing' % SITENAME Welcome email subject
ASTAKOS_FEEDBACK_EMAIL_SUBJECT 'Feedback from %s alpha2 testing' % SITENAME Feedback email subject
ASTAKOS_VERIFICATION_EMAIL_SUBJECT '%s alpha2 testing account activation is needed' % SITENAME Account activation email subject
ASTAKOS_ADMIN_NOTIFICATION_EMAIL_SUBJECT '%s alpha2 testing account created (%%(user)s)' % SITENAME Account creation admin notification email subject
ASTAKOS_HELPDESK_NOTIFICATION_EMAIL_SUBJECT '%s alpha2 testing account activated (%%(user)s)' % SITENAME Account activation helpdesk notification email subject
ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT 'Email change on %s alpha2 testing' % SITENAME Email change subject
ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT 'Password reset on %s alpha2 testing' % SITENAME Password change email subject
ASTAKOS_QUOTA_HOLDER_URL '' The quota holder URI
e.g. ``http://localhost:8080/api/quotaholder/v``
ASTAKOS_SERVICES {'cyclades': {'url':'https://node1.example.com/ui/', 'quota': {'vm': 2}}, Cloud service default url and quota
'pithos+': {'url':'https://node2.example.com/ui/', 'quota': {
'diskspace': 50 * 1024 * 1024 * 1024}}})
ASTAKOS_AQUARIUM_URL '' The billing (aquarium) URI
ASTAKOS_SERVICES {'cyclades': {'url':'https://node1.example.com/ui/', 'quota': {'vm': 2}}, Cloud service default url and quota
'pithos+': {'url':'https://node2.example.com/ui/', 'quota': {
'diskspace': 50 * 1024 * 1024 * 1024}}})
ASTAKOS_AQUARIUM_URL '' The billing (aquarium) URI
e.g. ``http://localhost:8888/user``
ASTAKOS_PAGINATE_BY 10 Number of object to be displayed per page
=================================== ============================================================================= ===========================================================================================
ASTAKOS_PAGINATE_BY 10 Number of object to be displayed per page
=========================================== ============================================================================= ===========================================================================================
Administrator functions
-----------------------
......
......@@ -172,87 +172,55 @@ def get_menu(request, with_extra_links=False, with_signout=True):
append = l.append
item = MenuItem
item.current_path = absolute(request, request.path)
append(
item(
append(item(
url=absolute(request, reverse('index')),
name=user.email
)
)
append(
item(
url=absolute(request, reverse('edit_profile')),
name="My account"
)
)
name=user.email))
append(item(url=absolute(request, reverse('edit_profile')),
name="My account"))
if with_extra_links:
if user.has_usable_password() and user.provider in ('local', ''):
append(
item(
append(item(
url=absolute(request, reverse('password_change')),
name="Change password"
)
)
name="Change password"))
if EMAILCHANGE_ENABLED:
append(
item(
append(item(
url=absolute(request, reverse('email_change')),
name="Change email"
)
)
name="Change email"))
if INVITATIONS_ENABLED:
append(
item(
append(item(
url=absolute(request, reverse('invite')),
name="Invitations"
)
)
append(
item(
name="Invitations"))
append(item(
url=absolute(request, reverse('feedback')),
name="Feedback"
)
)
append(
item(
name="Feedback"))
append(item(
url=absolute(request, reverse('group_list')),
name="Groups",
submenu=(
item(
url=absolute(request, reverse('group_list')),
name="Overview"
),
item(
url=absolute(request,
reverse('group_create_list')
),
name="Create"
),
item(
url=absolute(request, reverse('group_search')),
name="Join"
),
)
)
)
append(
item(
submenu=(item(
url=absolute(request,
reverse('group_list')),
name="Overview"),
item(
url=absolute(request,
reverse('group_create_list')),
name="Create"),
item(
url=absolute(request,
reverse('group_search')),
name="Join"),)))
append(item(
url=absolute(request, reverse('resource_list')),
name="Resources"
)
)
append(
item(
name="Resources"))
append(item(
url=absolute(request, reverse('billing')),
name="Billing"
)
)
name="Billing"))
append(item(
url=absolute(request, reverse('timeline')),
name="Timeline"))
if with_signout:
append(
item(
append(item(
url=absolute(request, reverse('logout')),
name="Sign out"
)
)
name="Sign out"))
callback = request.GET.get('callback', None)
data = json.dumps(tuple(l))
......
......@@ -99,6 +99,4 @@ def menu(request):
def group_kinds(request):
return {'group_kinds': GroupKind.objects.exclude(
name='default'
).values_list('name', flat=True)
}
name='default').values_list('name', flat=True)}
......@@ -162,4 +162,126 @@ def register_resources(resources, client=None):
client=client,
field='service')
created = (e for e in copy if unicode(e) not in rejected)
return send_resource_quantities(created, client)
\ No newline at end of file
return send_resource_quantities(created, client)
from datetime import datetime
strptime = datetime.strptime
timefmt = '%Y-%m-%dT%H:%M:%S.%f'
SECOND_RESOLUTION = 1
def total_seconds(timedelta_object):
return timedelta_object.seconds + timedelta_object.days * 86400
def iter_timeline(timeline, before):
if not timeline:
return
for t in timeline:
yield t
t = dict(t)
t['issue_time'] = before
yield t
def _usage_units(timeline, after, before, details=0):
t_total = 0
uu_total = 0
t_after = strptime(after, timefmt)
t_before = strptime(before, timefmt)
t0 = t_after
u0 = 0
for point in iter_timeline(timeline, before):
issue_time = point['issue_time']
if issue_time <= after:
u0 = point['target_allocated_through']
continue
t = strptime(issue_time, timefmt) if issue_time <= before else t_before
t_diff = int(total_seconds(t - t0) * SECOND_RESOLUTION)
t_total += t_diff
uu_cost = u0 * t_diff
uu_total += uu_cost
t0 = t
u0 = point['target_allocated_through']
target = point['target']
if details:
yield (target,
point['resource'],
point['name'],
issue_time,
uu_cost,
uu_total)
if not t_total:
return
yield (target,
'total',
point['resource'],
issue_time,
uu_total/t_total,
uu_total)
def usage_units(timeline, after, before, details=0):
return list(_usage_units(timeline, after, before, details=details))
def traffic_units(timeline, after, before, details=0):
tu_total = 0
target = None
issue_time = None
for point in timeline:
issue_time = point['issue_time']
if issue_time <= after:
continue
if issue_time > before:
break
target = point['target']
tu = point['target_allocated_through']
tu_total += tu
if details:
yield (target,
point['resource'],
point['name'],
issue_time,
tu,
tu_total)
if not tu_total:
return
yield (target,
'total',
point['resource'],
issue_time,
tu_total//len(timeline),
tu_total)
def timeline_charge(entity, resource, after, before, details, charge_type):
key = '1'
if charge_type == 'charge_usage':
charge_units = usage_units
elif charge_type == 'charge_traffic':
charge_units = traffic_units
else:
m = 'charge type %s not supported' % charge_type
raise ValueError(m)
quotaholder = QuotaholderHTTP(QUOTA_HOLDER_URL)
timeline = quotaholder.get_timeline(
context = {},
after = after,
before = before,
get_timeline = [[entity, resource, key]])
cu = charge_units(timeline, after, before, details=details)
return cu
......@@ -47,14 +47,13 @@ from django.utils.encoding import smart_str
from django.forms.extras.widgets import SelectDateWidget
from django.conf import settings
from astakos.im.models import (
AstakosUser, EmailChange, AstakosGroup, Invitation,
Membership, GroupKind, get_latest_terms
)
from astakos.im.models import (AstakosUser, EmailChange, AstakosGroup,
Invitation, Membership, GroupKind, Resource,
get_latest_terms)
from astakos.im.settings import (INVITATIONS_PER_LEVEL, BASEURL, SITENAME,
RECAPTCHA_PRIVATE_KEY, RECAPTCHA_ENABLED, DEFAULT_CONTACT_EMAIL,
LOGGING_LEVEL
)
RECAPTCHA_PRIVATE_KEY, RECAPTCHA_ENABLED,
DEFAULT_CONTACT_EMAIL, LOGGING_LEVEL,
PASSWORD_RESET_EMAIL_SUBJECT)
from astakos.im.widgets import DummyWidget, RecaptchaWidget
from astakos.im.functions import send_change_email
......@@ -443,7 +442,7 @@ class ExtendedPasswordResetForm(PasswordResetForm):
'support': DEFAULT_CONTACT_EMAIL
}
from_email = settings.SERVER_EMAIL
send_mail(_("Password reset on %s alpha2 testing") % SITENAME,
send_mail(_(PASSWORD_RESET_EMAIL_SUBJECT),
t.render(Context(c)), from_email, [user.email])
......@@ -525,8 +524,6 @@ class ExtendedPasswordChangeForm(PasswordChangeForm):
class AstakosGroupCreationForm(forms.ModelForm):
# issue_date = forms.DateField(widget=SelectDateWidget())
# expiration_date = forms.DateField(widget=SelectDateWidget())
kind = forms.ModelChoiceField(
queryset=GroupKind.objects.all(),
label="",
......@@ -599,11 +596,42 @@ class AddGroupMembersForm(forms.Form):
class AstakosGroupSearchForm(forms.Form):
q = forms.CharField(max_length=200, label='Search group')
class TimelineForm(forms.Form):
# entity = forms.CharField(
# widget=forms.HiddenInput(), label='')
entity = forms.ModelChoiceField(
queryset=AstakosUser.objects.filter(is_active = True)
)
resource = forms.ModelChoiceField(
queryset=Resource.objects.all()
)
start_date = forms.DateTimeField()
end_date = forms.DateTimeField()
details = forms.BooleanField(required=False, label="Detailed Listing")
operation = forms.ChoiceField(
label = 'Charge Method',
choices = ( ('', '-------------'),
('charge_usage', 'Charge Usage'),
('charge_traffic', 'Charge Traffic'), )
)
def clean(self):
super(TimelineForm, self).clean()
d = self.cleaned_data
if 'resource' in d:
d['resource'] = str(d['resource'])
if 'start_date' in d:
d['start_date'] = d['start_date'].strftime("%Y-%m-%dT%H:%M:%S.%f")[:24]
if 'end_date' in d:
d['end_date'] = d['end_date'].strftime("%Y-%m-%dT%H:%M:%S.%f")[:24]
if 'entity' in d:
d['entity'] = d['entity'].email
return d
class AstakosGroupSortForm(forms.Form):
sort_by = forms.ChoiceField(label='Sort by',
choices=(('groupname', 'Name'),
('kindname', 'Type'),
('issue_date', 'Issue Date'),
choices=(('groupname', 'Name'),
('kindname', 'Type'),
('issue_date', 'Issue Date'),
('expiration_date', 'Expiration Date'),
('approved_members_num', 'Participants'),
('is_enabled', 'Status'),
......@@ -618,4 +646,10 @@ class MembersSortForm(forms.Form):
('person__first_name', 'Name'),
('date_joined', 'Status')
),
required=False)
\ No newline at end of file
required=False)
class PickResourceForm(forms.Form):
resource = forms.ModelChoiceField(
queryset=Resource.objects.select_related().all()
)
resource.widget.attrs["onchange"]="this.form.submit()"
\ No newline at end of file
......@@ -51,8 +51,13 @@ from datetime import datetime
from functools import wraps
from astakos.im.settings import (DEFAULT_CONTACT_EMAIL, SITENAME, BASEURL,
LOGGING_LEVEL
)
LOGGING_LEVEL, VERIFICATION_EMAIL_SUBJECT,
ADMIN_NOTIFICATION_EMAIL_SUBJECT,
HELPDESK_NOTIFICATION_EMAIL_SUBJECT,
INVITATION_EMAIL_SUBJECT,
GREETING_EMAIL_SUBJECT,
FEEDBACK_EMAIL_SUBJECT,
EMAIL_CHANGE_EMAIL_SUBJECT)
from astakos.im.models import AstakosUser
logger = logging.getLogger(__name__)
......@@ -96,8 +101,7 @@ def send_verification(user, template_name='im/activation_email.txt'):
'support': DEFAULT_CONTACT_EMAIL})
sender = settings.SERVER_EMAIL
try:
send_mail('%s alpha2 testing account activation is needed' %
SITENAME, message, sender, [user.email])
send_mail(_(VERIFICATION_EMAIL_SUBJECT), message, sender, [user.email])
except (SMTPException, socket.error) as e:
logger.exception(e)
raise SendVerificationError()
......@@ -127,7 +131,8 @@ def send_admin_notification(template_name,
message = render_to_string(template_name, dictionary)
sender = settings.SERVER_EMAIL
try:
send_mail(subject, message, sender, [i[1] for i in settings.ADMINS])
send_mail(_(ADMIN_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email},
message, sender, [i[1] for i in settings.ADMINS])
except (SMTPException, socket.error) as e:
logger.exception(e)
raise SendNotificationError()
......@@ -150,12 +155,8 @@ def send_helpdesk_notification(user, template_name='im/account_notification.txt'
)
sender = settings.SERVER_EMAIL
try:
send_mail(
'%s alpha2 testing account activated' % SITENAME,
message,
sender,
[DEFAULT_CONTACT_EMAIL]
)
send_mail(_(HELPDESK_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email},
message, sender, [DEFAULT_CONTACT_EMAIL])
except (SMTPException, socket.error) as e:
logger.exception(e)
raise SendNotificationError()
......@@ -170,7 +171,7 @@ def send_invitation(invitation, template_name='im/invitation.txt'):
Raises SendInvitationError
"""
subject = _('Invitation to %s alpha2 testing' % SITENAME)
subject = _(INVITATION_EMAIL_SUBJECT)
url = '%s?code=%d' % (urljoin(BASEURL, reverse('index')), invitation.code)
message = render_to_string(template_name, {
'invitation': invitation,
......@@ -239,8 +240,8 @@ def send_change_email(ec, request, email_template_name='registration/email_chang
t = loader.get_template(email_template_name)
c = {'url': url, 'site_name': SITENAME}
from_email = settings.SERVER_EMAIL
send_mail(_("Email change on %s alpha2 testing") % SITENAME,
t.render(Context(c)), from_email, [ec.new_email_address])
send_mail(_(EMAIL_CHANGE_EMAIL_SUBJECT),
t.render(Context(c)), from_email, [ec.new_email_address])
except (SMTPException, socket.error) as e:
logger.exception(e)
raise ChangeEmailError()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment