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
+ e.g. ``http://localhost:8888/user``
+ASTAKOS_PAGINATE_BY 10 Number of object to be displayed per page
++
+ ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN True Enforce token renewal on password change/reset. If set to False, user can optionally decide
+ whether to renew the token or not.
=========================================== ============================================================================= ===========================================================================================
Administrator functions
To update user credibility from the billing system (Aquarium), enable the queue, install snf-pithos-tools and use ``pithos-dispatcher``::
- pithos-dispatcher --exchange=aquarium --callback=astakos.im.queue.listener.on_creditevent
-
-Load groups:
-------------
-
-To set the initial user groups load the followind fixture:
-
- snf-manage loaddata groups
+ pithos-dispatcher --exchange=aquarium --callback=astakos.im.endpoints.aquarium.consumer.on_creditevent
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from urlparse import urljoin
from django import forms
from django.utils.translation import ugettext as _
-from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, \
- PasswordResetForm, PasswordChangeForm, SetPasswordForm
+from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
- PasswordResetForm, PasswordChangeForm
- )
++ PasswordResetForm, PasswordChangeForm,
++ SetPasswordForm)
from django.core.mail import send_mail
from django.contrib.auth.tokens import default_token_generator
from django.template import Context, loader
from django.utils.http import int_to_base36
from django.core.urlresolvers import reverse
-from django.utils.functional import lazy
from django.utils.safestring import mark_safe
-from django.contrib import messages
from django.utils.encoding import smart_str
-
-from astakos.im.models import AstakosUser, Invitation, get_latest_terms, EmailChange
-from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, \
- BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL, \
- RECAPTCHA_ENABLED, LOGGING_LEVEL, PASSWORD_RESET_EMAIL_SUBJECT, \
- NEWPASSWD_INVALIDATE_TOKEN
+from django.forms.extras.widgets import SelectDateWidget
+from django.conf import settings
+
+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,
- PASSWORD_RESET_EMAIL_SUBJECT)
-
++ PASSWORD_RESET_EMAIL_SUBJECT,
++ NEWPASSWD_INVALIDATE_TOKEN)
from astakos.im.widgets import DummyWidget, RecaptchaWidget
from astakos.im.functions import send_change_email
-# since Django 1.4 use django.core.urlresolvers.reverse_lazy instead
-from astakos.im.util import reverse_lazy, reserved_email, get_query
+from astakos.im.util import reserved_email, get_query
import logging
import hashlib
logger = logging.getLogger(__name__)
+
class LocalUserCreationForm(UserCreationForm):
"""
Extends the built in UserCreationForm in several ways:
* User created is not active.
"""
recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
- recaptcha_response_field = forms.CharField(widget=RecaptchaWidget, label='')
+ recaptcha_response_field = forms.CharField(
+ widget=RecaptchaWidget, label='')
class Meta:
model = AstakosUser
- fields = ("email", "first_name", "last_name", "has_signed_terms", "has_signed_terms")
+ fields = ("email", "first_name", "last_name",
+ "has_signed_terms", "has_signed_terms")
def __init__(self, *args, **kwargs):
"""
if RECAPTCHA_ENABLED:
self.fields.keyOrder.extend(['recaptcha_challenge_field',
- 'recaptcha_response_field',])
+ 'recaptcha_response_field', ])
if get_latest_terms():
self.fields.keyOrder.append('has_signed_terms')
# Overriding field label since we need to apply a link
# to the terms within the label
terms_link_html = '<a href="%s" target="_blank">%s</a>' \
- % (reverse('latest_terms'), _("the terms"))
+ % (reverse('latest_terms'), _("the terms"))
self.fields['has_signed_terms'].label = \
- mark_safe("I agree with %s" % terms_link_html)
+ mark_safe("I agree with %s" % terms_link_html)
def clean_email(self):
email = self.cleaned_data['email']
rrf = self.cleaned_data['recaptcha_response_field']
check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
if not check.is_valid:
- raise forms.ValidationError(_('You have not entered the correct words'))
+ raise forms.ValidationError(
+ _('You have not entered the correct words'))
def save(self, commit=True):
"""
user.renew_token()
if commit:
user.save()
- logger._log(LOGGING_LEVEL, 'Created user %s' % user.email, [])
+ logger.log(LOGGING_LEVEL, 'Created user %s' % user.email)
return user
+
class InvitedLocalUserCreationForm(LocalUserCreationForm):
"""
Extends the LocalUserCreationForm: email is readonly.
ro = ('email', 'username',)
for f in ro:
self.fields[f].widget.attrs['readonly'] = True
-
-
+
def save(self, commit=True):
user = super(InvitedLocalUserCreationForm, self).save(commit=False)
level = user.invitation.inviter.level + 1
user.save()
return user
+
class ThirdPartyUserCreationForm(forms.ModelForm):
class Meta:
model = AstakosUser
- fields = ("email", "first_name", "last_name", "third_party_identifier", "has_signed_terms")
-
+ fields = ("email", "first_name", "last_name",
+ "third_party_identifier", "has_signed_terms")
+
def __init__(self, *args, **kwargs):
"""
Changes the order of fields, and removes the username field.
if self.request:
kwargs.pop('request')
super(ThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
- self.fields.keyOrder = ['email', 'first_name', 'last_name', 'third_party_identifier']
+ self.fields.keyOrder = ['email', 'first_name', 'last_name',
+ 'third_party_identifier']
if get_latest_terms():
self.fields.keyOrder.append('has_signed_terms')
#set readonly form fields
# Overriding field label since we need to apply a link
# to the terms within the label
terms_link_html = '<a href="%s" target="_blank">%s</a>' \
- % (reverse('latest_terms'), _("the terms"))
+ % (reverse('latest_terms'), _("the terms"))
self.fields['has_signed_terms'].label = \
- mark_safe("I agree with %s" % terms_link_html)
-
+ mark_safe("I agree with %s" % terms_link_html)
+
def clean_email(self):
email = self.cleaned_data['email']
if not email:
user.provider = get_query(self.request).get('provider')
if commit:
user.save()
- logger._log(LOGGING_LEVEL, 'Created user %s' % user.email, [])
+ logger.log(LOGGING_LEVEL, 'Created user %s' % user.email)
return user
+
class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm):
"""
Extends the ThirdPartyUserCreationForm: email is readonly.
"""
Changes the order of fields, and removes the username field.
"""
- super(InvitedThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
+ super(
+ InvitedThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
#set readonly form fields
ro = ('email',)
self.fields[f].widget.attrs['readonly'] = True
def save(self, commit=True):
- user = super(InvitedThirdPartyUserCreationForm, self).save(commit=False)
+ user = super(
+ InvitedThirdPartyUserCreationForm, self).save(commit=False)
level = user.invitation.inviter.level + 1
user.level = level
user.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
user.save()
return user
-class ShibbolethUserCreationForm(ThirdPartyUserCreationForm):
- additional_email = forms.CharField(widget=forms.HiddenInput(), label='', required = False)
+class ShibbolethUserCreationForm(ThirdPartyUserCreationForm):
+ additional_email = forms.CharField(
+ widget=forms.HiddenInput(), label='', required=False)
+
def __init__(self, *args, **kwargs):
super(ShibbolethUserCreationForm, self).__init__(*args, **kwargs)
self.fields.keyOrder.append('additional_email')
# copy email value to additional_mail in case user will change it
name = 'email'
field = self.fields[name]
- self.initial['additional_email'] = self.initial.get(name, field.initial)
-
+ self.initial['additional_email'] = self.initial.get(name,
+ field.initial)
+
def clean_email(self):
email = self.cleaned_data['email']
- for user in AstakosUser.objects.filter(email = email):
+ for user in AstakosUser.objects.filter(email=email):
if user.provider == 'shibboleth':
raise forms.ValidationError(_("This email is already associated with another shibboleth account."))
elif not user.is_active:
super(ShibbolethUserCreationForm, self).clean_email()
return email
+
class InvitedShibbolethUserCreationForm(ShibbolethUserCreationForm, InvitedThirdPartyUserCreationForm):
pass
+
class LoginForm(AuthenticationForm):
username = forms.EmailField(label=_("Email"))
recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
- recaptcha_response_field = forms.CharField(widget=RecaptchaWidget, label='')
-
+ recaptcha_response_field = forms.CharField(
+ widget=RecaptchaWidget, label='')
+
def __init__(self, *args, **kwargs):
was_limited = kwargs.get('was_limited', False)
request = kwargs.get('request', None)
self.fields.keyOrder = ['username', 'password']
if was_limited and RECAPTCHA_ENABLED:
self.fields.keyOrder.extend(['recaptcha_challenge_field',
- 'recaptcha_response_field',])
-
+ 'recaptcha_response_field', ])
+
+ def clean_username(self):
+ if 'username' in self.cleaned_data:
+ return self.cleaned_data['username'].lower()
+
def clean_recaptcha_response_field(self):
if 'recaptcha_challenge_field' in self.cleaned_data:
self.validate_captcha()
rrf = self.cleaned_data['recaptcha_response_field']
check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
if not check.is_valid:
- raise forms.ValidationError(_('You have not entered the correct words'))
-
+ raise forms.ValidationError(
+ _('You have not entered the correct words'))
+
def clean(self):
super(LoginForm, self).clean()
if self.user_cache and self.user_cache.provider not in ('local', ''):
raise forms.ValidationError(_('Local login is not the current authentication method for this account.'))
return self.cleaned_data
+
class ProfileForm(forms.ModelForm):
"""
Subclass of ``ModelForm`` for permiting user to edit his/her profile.
class Meta:
model = AstakosUser
- fields = ('email', 'first_name', 'last_name', 'auth_token', 'auth_token_expires')
+ fields = ('email', 'first_name', 'last_name', 'auth_token',
+ 'auth_token_expires')
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
user.save()
return user
+
class FeedbackForm(forms.Form):
"""
Form for writing feedback.
feedback_data = forms.CharField(widget=forms.HiddenInput(), label='',
required=False)
+
class SendInvitationForm(forms.Form):
"""
Form for sending an invitations
"""
- email = forms.EmailField(required = True, label = 'Email address')
- first_name = forms.EmailField(label = 'First name')
- last_name = forms.EmailField(label = 'Last name')
+ email = forms.EmailField(required=True, label='Email address')
+ first_name = forms.EmailField(label='First name')
+ last_name = forms.EmailField(label='Last name')
+
class ExtendedPasswordResetForm(PasswordResetForm):
"""
try:
user = AstakosUser.objects.get(email=email, is_active=True)
if not user.has_usable_password():
- raise forms.ValidationError(_("This account has not a usable password."))
- except AstakosUser.DoesNotExist, e:
+ raise forms.ValidationError(
+ _("This account has not a usable password."))
+ except AstakosUser.DoesNotExist:
raise forms.ValidationError(_('That e-mail address doesn\'t have an associated user account. Are you sure you\'ve registered?'))
return email
- def save(self, domain_override=None, email_template_name='registration/password_reset_email.html',
- use_https=False, token_generator=default_token_generator, request=None):
+ def save(
+ self, domain_override=None, email_template_name='registration/password_reset_email.html',
+ use_https=False, token_generator=default_token_generator, request=None):
"""
Generates a one-use only link for resetting password and sends to the user.
"""
for user in self.users_cache:
url = reverse('django.contrib.auth.views.password_reset_confirm',
- kwargs={'uidb36':int_to_base36(user.id),
- 'token':token_generator.make_token(user)})
+ kwargs={'uidb36': int_to_base36(user.id),
+ 'token': token_generator.make_token(user)
+ }
+ )
url = urljoin(BASEURL, url)
t = loader.get_template(email_template_name)
c = {
'baseurl': BASEURL,
'support': DEFAULT_CONTACT_EMAIL
}
- from_email = DEFAULT_FROM_EMAIL
+ from_email = settings.SERVER_EMAIL
send_mail(_(PASSWORD_RESET_EMAIL_SUBJECT),
- t.render(Context(c)), from_email, [user.email])
+ t.render(Context(c)), from_email, [user.email])
+
class EmailChangeForm(forms.ModelForm):
class Meta:
def save(self, email_template_name, request, commit=True):
ec = super(EmailChangeForm, self).save(commit=False)
ec.user = request.user
- activation_key = hashlib.sha1(str(random()) + smart_str(ec.new_email_address))
- ec.activation_key=activation_key.hexdigest()
+ activation_key = hashlib.sha1(
+ str(random()) + smart_str(ec.new_email_address))
+ ec.activation_key = activation_key.hexdigest()
if commit:
ec.save()
send_change_email(ec, request, email_template_name=email_template_name)
+
class SignApprovalTermsForm(forms.ModelForm):
class Meta:
model = AstakosUser
raise forms.ValidationError(_('You have to agree with the terms'))
return has_signed_terms
+
class InvitationForm(forms.ModelForm):
username = forms.EmailField(label=_("Email"))
def clean_username(self):
username = self.cleaned_data['username']
try:
- Invitation.objects.get(username = username)
- raise forms.ValidationError(_('There is already invitation for this email.'))
+ Invitation.objects.get(username=username)
+ raise forms.ValidationError(
+ _('There is already invitation for this email.'))
except Invitation.DoesNotExist:
pass
return username
+
class ExtendedPasswordChangeForm(PasswordChangeForm):
"""
Extends PasswordChangeForm by enabling user
to optionally renew also the token.
"""
- renew = forms.BooleanField(label='Renew token', required=False)
+ if not NEWPASSWD_INVALIDATE_TOKEN:
+ renew = forms.BooleanField(label='Renew token', required=False,
+ initial=True,
+ help_text='Unsetting this may result in security risk.')
def __init__(self, user, *args, **kwargs):
super(ExtendedPasswordChangeForm, self).__init__(user, *args, **kwargs)
def save(self, commit=True):
user = super(ExtendedPasswordChangeForm, self).save(commit=False)
- if self.cleaned_data.get('renew'):
+ if NEWPASSWD_INVALIDATE_TOKEN or self.cleaned_data.get('renew'):
user.renew_token()
if commit:
user.save()
return user
+
+class AstakosGroupCreationForm(forms.ModelForm):
+ kind = forms.ModelChoiceField(
+ queryset=GroupKind.objects.all(),
+ label="",
+ widget=forms.HiddenInput()
+ )
+ name = forms.URLField()
+ moderation_enabled = forms.BooleanField(
+ help_text="Check if you want to approve members participation manually",
+ required=False
+ )
+
+ class Meta:
+ model = AstakosGroup
+
+ def __init__(self, *args, **kwargs):
+ try:
+ resources = kwargs.pop('resources')
+ except KeyError:
+ resources = {}
+ super(AstakosGroupCreationForm, self).__init__(*args, **kwargs)
+ self.fields.keyOrder = ['kind', 'name', 'homepage', 'desc', 'issue_date',
+ 'expiration_date', 'estimated_participants',
+ 'moderation_enabled']
+ for id, r in resources.iteritems():
+ self.fields['resource_%s' % id] = forms.IntegerField(
+ label=r,
+ required=False,
+ help_text=_('Leave it blank for no additional quota.')
+ )
+
+ def resources(self):
+ for name, value in self.cleaned_data.items():
+ prefix, delimiter, suffix = name.partition('resource_')
+ if suffix:
+ # yield only those having a value
+ if not value:
+ continue
+ yield (suffix, value)
+
+class AstakosGroupUpdateForm(forms.ModelForm):
+ class Meta:
+ model = AstakosGroup
+ fields = ('homepage', 'desc')
+
+class AddGroupMembersForm(forms.Form):
+ q = forms.CharField(max_length=800, widget=forms.Textarea, label=_('Search users'),
+ help_text=_('Add comma separated user emails'),
+ required=True)
+
+ def clean(self):
+ q = self.cleaned_data.get('q') or ''
+ users = q.split(',')
+ users = list(u.strip() for u in users if u)
+ db_entries = AstakosUser.objects.filter(email__in=users)
+ unknown = list(set(users) - set(u.email for u in db_entries))
+ if unknown:
+ raise forms.ValidationError(
+ _('Unknown users: %s' % ','.join(unknown)))
+ self.valid_users = db_entries
+ return self.cleaned_data
+
+ def get_valid_users(self):
+ """Should be called after form cleaning"""
+ try:
+ return self.valid_users
+ except:
+ return ()
+
+
+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'),
+ ('expiration_date', 'Expiration Date'),
+ ('approved_members_num', 'Participants'),
+ ('is_enabled', 'Status'),
+ ('moderation_enabled', 'Moderation'),
+ ('membership_status','Enrollment Status')
+ ),
+ required=False)
+
+class MembersSortForm(forms.Form):
+ sort_by = forms.ChoiceField(label='Sort by',
+ choices=(('person__email', 'User Id'),
+ ('person__first_name', 'Name'),
+ ('date_joined', 'Status')
+ ),
+ required=False)
+
+class PickResourceForm(forms.Form):
+ resource = forms.ModelChoiceField(
+ queryset=Resource.objects.select_related().all()
+ )
- resource.widget.attrs["onchange"]="this.form.submit()"
++ resource.widget.attrs["onchange"]="this.form.submit()"
++
+ class ExtendedSetPasswordForm(SetPasswordForm):
+ """
+ Extends SetPasswordForm by enabling user
+ to optionally renew also the token.
+ """
+ if not NEWPASSWD_INVALIDATE_TOKEN:
+ renew = forms.BooleanField(label='Renew token', required=False,
+ initial=True,
+ help_text='Unsetting this may result in security risk.')
+
+ def __init__(self, user, *args, **kwargs):
+ super(ExtendedSetPasswordForm, self).__init__(user, *args, **kwargs)
+
+ def save(self, commit=True):
+ user = super(ExtendedSetPasswordForm, self).save(commit=False)
+ if NEWPASSWD_INVALIDATE_TOKEN or self.cleaned_data.get('renew'):
+ try:
+ user = AstakosUser.objects.get(id=user.id)
+ except AstakosUser.DoesNotExist:
+ pass
+ else:
+ user.renew_token()
+ if commit:
+ user.save()
+ return user
# or implied, of GRNET S.A.
from optparse import make_option
+from datetime import datetime
from django.core.management.base import BaseCommand, CommandError
-from django.contrib.auth.models import Group, Permission
-from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
+from django.db.utils import IntegrityError
-from astakos.im.models import AstakosUser
+from astakos.im.models import (AstakosUser, AstakosGroup, Membership, Resource,
+ AstakosUserQuota)
+from astakos.im.endpoints.aquarium.producer import report_user_credits_event
from ._common import remove_user_permission, add_user_permission
+
class Command(BaseCommand):
args = "<user ID>"
help = "Modify a user's attributes"
-
- option_list = BaseCommand.option_list + (
+
+ option_list = list(BaseCommand.option_list) + [
make_option('--invitations',
- dest='invitations',
- metavar='NUM',
- help="Update user's invitations"),
+ dest='invitations',
+ metavar='NUM',
+ help="Update user's invitations"),
make_option('--level',
- dest='level',
- metavar='NUM',
- help="Update user's level"),
+ dest='level',
+ metavar='NUM',
+ help="Update user's level"),
make_option('--password',
- dest='password',
- metavar='PASSWORD',
- help="Set user's password"),
+ dest='password',
+ metavar='PASSWORD',
+ help="Set user's password"),
make_option('--provider',
- dest='provider',
- metavar='PROVIDER',
- help="Set user's provider"),
+ dest='provider',
+ metavar='PROVIDER',
+ help="Set user's provider"),
make_option('--renew-token',
- action='store_true',
- dest='renew_token',
- default=False,
- help="Renew the user's token"),
+ action='store_true',
+ dest='renew_token',
+ default=False,
+ help="Renew the user's token"),
make_option('--renew-password',
- action='store_true',
- dest='renew_password',
- default=False,
- help="Renew the user's password"),
+ action='store_true',
+ dest='renew_password',
+ default=False,
+ help="Renew the user's password"),
make_option('--set-admin',
- action='store_true',
- dest='admin',
- default=False,
- help="Give user admin rights"),
+ action='store_true',
+ dest='admin',
+ default=False,
+ help="Give user admin rights"),
make_option('--set-noadmin',
- action='store_true',
- dest='noadmin',
- default=False,
- help="Revoke user's admin rights"),
+ action='store_true',
+ dest='noadmin',
+ default=False,
+ help="Revoke user's admin rights"),
make_option('--set-active',
- action='store_true',
- dest='active',
- default=False,
- help="Change user's state to inactive"),
+ action='store_true',
+ dest='active',
+ default=False,
+ help="Change user's state to inactive"),
make_option('--set-inactive',
- action='store_true',
- dest='inactive',
- default=False,
- help="Change user's state to inactive"),
+ action='store_true',
+ dest='inactive',
+ default=False,
+ help="Change user's state to inactive"),
make_option('--add-group',
- dest='add-group',
- help="Add user group"),
+ dest='add-group',
+ help="Add user group"),
make_option('--delete-group',
- dest='delete-group',
- help="Delete user group"),
+ dest='delete-group',
+ help="Delete user group"),
make_option('--add-permission',
- dest='add-permission',
- help="Add user permission"),
+ dest='add-permission',
+ help="Add user permission"),
make_option('--delete-permission',
- dest='delete-permission',
- help="Delete user permission"),
- )
+ dest='delete-permission',
+ help="Delete user permission"),
+ make_option('--refill-credits',
+ action='store_true',
+ dest='refill',
+ default=False,
+ help="Refill user credits"),
+ ]
+ resources = Resource.objects.select_related().all()
+ append = option_list.append
+ for r in resources:
+ append(make_option('--%s-set-quota' % r,
+ dest='%s-set-quota' % r,
+ metavar='QUANTITY',
+ help="Set resource quota"))
def handle(self, *args, **options):
if len(args) != 1:
raise CommandError("Please provide a user ID")
-
+
if args[0].isdigit():
- user = AstakosUser.objects.get(id=int( args[0]))
+ user = AstakosUser.objects.get(id=int(args[0]))
else:
raise CommandError("Invalid ID")
-
+
if not user:
raise CommandError("Unknown user")
-
+
if options.get('admin'):
user.is_superuser = True
elif options.get('noadmin'):
user.is_superuser = False
-
+
if options.get('active'):
user.is_active = True
elif options.get('inactive'):
user.is_active = False
-
+
invitations = options.get('invitations')
if invitations is not None:
user.invitations = int(invitations)
-
+
groupname = options.get('add-group')
if groupname is not None:
try:
- group = Group.objects.get(name=groupname)
- user.groups.add(group)
- except Group.DoesNotExist, e:
- self.stdout.write("Group named %s does not exist\n" % groupname)
-
+ group = AstakosGroup.objects.get(name=groupname)
+ m = Membership(
+ person=user, group=group, date_joined=datetime.now())
+ m.save()
+ except AstakosGroup.DoesNotExist, e:
+ self.stdout.write(
+ "Group named %s does not exist\n" % groupname)
+ except IntegrityError, e:
+ self.stdout.write("User is already member of %s\n" % groupname)
+
groupname = options.get('delete-group')
if groupname is not None:
try:
- group = Group.objects.get(name=groupname)
- user.groups.remove(group)
- except Group.DoesNotExist, e:
- self.stdout.write("Group named %s does not exist\n" % groupname)
-
+ group = AstakosGroup.objects.get(name=groupname)
+ m = Membership.objects.get(person=user, group=group)
+ m.delete()
+ except AstakosGroup.DoesNotExist, e:
+ self.stdout.write(
+ "Group named %s does not exist\n" % groupname)
+ except Membership.DoesNotExist, e:
+ self.stdout.write("User is not a member of %s\n" % groupname)
+
pname = options.get('add-permission')
if pname is not None:
try:
r, created = add_user_permission(user, pname)
if created:
- self.stdout.write('Permission: %s created successfully\n' % pname)
+ self.stdout.write(
+ 'Permission: %s created successfully\n' % pname)
if r > 0:
- self.stdout.write('Permission: %s added successfully\n' % pname)
- elif r==0:
- self.stdout.write('User has already permission: %s\n' % pname)
+ self.stdout.write(
+ 'Permission: %s added successfully\n' % pname)
+ elif r == 0:
+ self.stdout.write(
+ 'User has already permission: %s\n' % pname)
except Exception, e:
raise CommandError(e)
-
- pname = options.get('delete-permission')
+
+ pname = options.get('delete-permission')
if pname is not None and not user.has_perm(pname):
try:
r = remove_user_permission(user, pname)
if r < 0:
- self.stdout.write('Invalid permission codename: %s\n' % pname)
+ self.stdout.write(
+ 'Invalid permission codename: %s\n' % pname)
elif r == 0:
self.stdout.write('User has not permission: %s\n' % pname)
elif r > 0:
- self.stdout.write('Permission: %s removed successfully\n' % pname)
+ self.stdout.write(
+ 'Permission: %s removed successfully\n' % pname)
except Exception, e:
raise CommandError(e)
-
+
level = options.get('level')
if level is not None:
user.level = int(level)
-
+
password = options.get('password')
if password is not None:
user.set_password(password)
-
+
provider = options.get('provider')
if provider is not None:
user.provider = provider
-
-
+
password = None
if options['renew_password']:
password = AstakosUser.objects.make_random_password()
user.set_password(password)
-
+
if options['renew_token']:
user.renew_token()
-
+
+ if options['refill']:
+ report_user_credits_event(user)
+
try:
user.save()
except ValidationError, e:
raise CommandError(e)
-
+
if password:
self.stdout.write('User\'s new password: %s\n' % password)
+
+ for r in self.resources:
+ limit = options.get('%s-set-quota' % r)
+ if not limit:
+ continue
+ if not limit.isdigit():
+ raise CommandError('Invalid limit')
+
+ q = AstakosUserQuota.objects
+ q, created = q.get_or_create(resource=r, user=user,
+ defaults={'uplimit': limit})
+ verb = 'set' if created else 'updated'
+ self.stdout.write('User\'s quota %s successfully\n' % verb)
DEFAULT_USER_LEVEL = getattr(settings, 'ASTAKOS_DEFAULT_USER_LEVEL', 4)
INVITATIONS_PER_LEVEL = getattr(settings, 'ASTAKOS_INVITATIONS_PER_LEVEL', {
- 0 : 100,
- 1 : 2,
- 2 : 0,
- 3 : 0,
- 4 : 0
+ 0: 100,
+ 1: 2,
+ 2: 0,
+ 3: 0,
+ 4: 0
})
# Address to use for outgoing emails
-DEFAULT_FROM_EMAIL = getattr(settings, 'ASTAKOS_DEFAULT_FROM_EMAIL', 'GRNET Cloud <no-reply@grnet.gr>')
-DEFAULT_CONTACT_EMAIL = getattr(settings, 'ASTAKOS_DEFAULT_CONTACT_EMAIL', 'support@cloud.grnet.gr')
-DEFAULT_ADMIN_EMAIL = getattr(settings, 'ASTAKOS_DEFAULT_ADMIN_EMAIL', 'support@cloud.grnet.gr')
+DEFAULT_CONTACT_EMAIL = getattr(
+ settings, 'ASTAKOS_DEFAULT_CONTACT_EMAIL', 'support@cloud.grnet.gr')
# Identity Management enabled modules
IM_MODULES = getattr(settings, 'ASTAKOS_IM_MODULES', ['local', 'shibboleth'])
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' : 'custom', 'custom_theme_widget': 'okeanos_recaptcha'})
+ {'theme': 'custom', 'custom_theme_widget': 'okeanos_recaptcha'})
RECAPTCHA_USE_SSL = getattr(settings, 'ASTAKOS_RECAPTCHA_USE_SSL', True)
RECAPTCHA_ENABLED = getattr(settings, 'ASTAKOS_RECAPTCHA_ENABLED', True)
BILLING_FIELDS = getattr(settings, 'ASTAKOS_BILLING_FIELDS', ['is_active'])
# Queue for billing.
-QUEUE_CONNECTION = getattr(settings, 'ASTAKOS_QUEUE_CONNECTION', None) # Example: 'rabbitmq://guest:guest@localhost:5672/astakos'
+QUEUE_CONNECTION = getattr(settings, 'ASTAKOS_QUEUE_CONNECTION', None) # Example: 'rabbitmq://guest:guest@localhost:5672/astakos'
# Set where the user should be redirected after logout
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', [])
+RE_USER_EMAIL_PATTERNS = getattr(
+ settings, 'ASTAKOS_RE_USER_EMAIL_PATTERNS', [])
# Messages to display on login page header
# e.g. {'warning': 'This warning message will be displayed on the top of login page'}
# e.g. {'https://cms.okeanos.grnet.gr/': 'Back to ~okeanos'}
PROFILE_EXTRA_LINKS = getattr(settings, 'ASTAKOS_PROFILE_EXTRA_LINKS', {})
-# The number of unsuccessful login requests per minute allowed for a specific email
-RATELIMIT_RETRIES_ALLOWED = getattr(settings, 'ASTAKOS_RATELIMIT_RETRIES_ALLOWED', 3)
+# The number of unsuccessful login requests per minute allowed for a specific user
+RATELIMIT_RETRIES_ALLOWED = getattr(
+ settings, 'ASTAKOS_RATELIMIT_RETRIES_ALLOWED', 3)
# If False the email change mechanism is disabled
EMAILCHANGE_ENABLED = getattr(settings, 'ASTAKOS_EMAILCHANGE_ENABLED', False)
# Set the expiration time (in days) of email change requests
-EMAILCHANGE_ACTIVATION_DAYS = getattr(settings, 'ASTAKOS_EMAILCHANGE_ACTIVATION_DAYS', 10)
+EMAILCHANGE_ACTIVATION_DAYS = getattr(
+ settings, 'ASTAKOS_EMAILCHANGE_ACTIVATION_DAYS', 10)
# Set the astakos main functions logging severity (None to disable)
from logging import INFO
PASSWORD_RESET_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT',
'Password reset on %s alpha2 testing' % SITENAME)
- # Configurable email subjects
- INVITATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_INVITATION_EMAIL_SUBJECT',
- 'Invitation to %s alpha2 testing' % SITENAME)
- GREETING_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_GREETING_EMAIL_SUBJECT',
- 'Welcome to %s alpha2 testing' % SITENAME)
- FEEDBACK_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_FEEDBACK_EMAIL_SUBJECT',
- 'Feedback from %s alpha2 testing' % SITENAME)
- VERIFICATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_VERIFICATION_EMAIL_SUBJECT',
- '%s alpha2 testing account activation is needed' % SITENAME)
- ADMIN_NOTIFICATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_ADMIN_NOTIFICATION_EMAIL_SUBJECT',
- '%s alpha2 testing account created (%%(user)s)' % SITENAME)
- HELPDESK_NOTIFICATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_HELPDESK_NOTIFICATION_EMAIL_SUBJECT',
- '%s alpha2 testing account activated (%%(user)s)' % SITENAME)
- EMAIL_CHANGE_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT',
- 'Email change on %s alpha2 testing' % SITENAME)
- PASSWORD_RESET_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT',
- 'Password reset on %s alpha2 testing' % SITENAME)
-
+# Set the quota holder component URI
+QUOTA_HOLDER_URL = getattr(settings, 'ASTAKOS_QUOTA_HOLDER_URL', '')
+
+# Set the cloud service properties
+SERVICES = getattr(settings, 'ASTAKOS_SERVICES',
+ {'cyclades': {'url':'https://node1.example.com/ui/',
+ 'quota': {'vm': 2}},
+ 'pithos+': {'url':'https://node2.example.com/ui/',
+ 'quota': {'diskspace': 50 * 1024 * 1024 * 1024}}})
+
+# Set the billing URI
+AQUARIUM_URL = getattr(settings, 'ASTAKOS_AQUARIUM_URL', '')
+
+# Set how many objects should be displayed per page
- PAGINATE_BY = getattr(settings, 'ASTAKOS_PAGINATE_BY', 8)
++PAGINATE_BY = getattr(settings, 'ASTAKOS_PAGINATE_BY', 8)
++
+ # Enforce token renewal on password change/reset
+ NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
-from django.http import HttpResponseBadRequest
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.contrib import messages
from django.utils.translation import ugettext as _
from django.views.decorators.csrf import csrf_exempt
+ from django.views.decorators.http import require_http_methods
from astakos.im.util import prepare_response, get_query
from astakos.im.views import requires_anonymous
-from astakos.im.models import AstakosUser
from astakos.im.forms import LoginForm
from astakos.im.settings import RATELIMIT_RETRIES_ALLOWED
from ratelimit.decorators import ratelimit
-retries = RATELIMIT_RETRIES_ALLOWED-1
-rate = str(retries)+'/m'
+retries = RATELIMIT_RETRIES_ALLOWED - 1
+rate = str(retries) + '/m'
+
+ @require_http_methods(["GET", "POST"])
@csrf_exempt
@requires_anonymous
@ratelimit(field='username', method='POST', rate=rate)
on_failure: the template name to render on login failure
"""
was_limited = getattr(request, 'limited', False)
- form = LoginForm(data=request.POST, was_limited=was_limited, request=request)
+ form = LoginForm(data=request.POST,
+ was_limited=was_limited,
+ request=request)
next = get_query(request).get('next', '')
if not form.is_valid():
return render_to_response(on_failure,
- {'login_form':form,
- 'next':next},
+ {'login_form': form,
+ 'next': next},
context_instance=RequestContext(request))
# get the user from the cash
user = form.user_cache
-
+
message = None
if not user:
message = _('Cannot authenticate account')
elif not user.is_active:
message = _('Inactive account')
if message:
- messages.add_message(request, messages.ERROR, message)
+ messages.error(request, message)
return render_to_response(on_failure,
- {'form':form},
+ {'form': form},
context_instance=RequestContext(request))
-
+
return prepare_response(request, user, next)
# or implied, of GRNET S.A.
from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
from django.utils.translation import ugettext as _
-from django.contrib import messages
from django.utils.http import urlencode
from django.contrib.auth import authenticate
from django.http import HttpResponse, HttpResponseBadRequest
from django.core.exceptions import ValidationError
+ from django.views.decorators.http import require_http_methods
-from urllib import quote
-from urlparse import urlunsplit, urlsplit, urlparse, parse_qsl
+from urlparse import urlunsplit, urlsplit, parse_qsl
from astakos.im.settings import COOKIE_NAME, COOKIE_DOMAIN
from astakos.im.util import set_cookie
logger = logging.getLogger(__name__)
+
+ @require_http_methods(["GET", "POST"])
def login(request):
"""
If there is no ``next`` request parameter redirects to astakos index page
if request.user.is_authenticated():
# if user has not signed the approval terms
# redirect to approval terms with next the request path
- if not request.user.signed_terms():
+ if not request.user.signed_terms:
# first build next parameter
parts = list(urlsplit(request.build_absolute_uri()))
params = dict(parse_qsl(parts[3], keep_blank_values=True))
# delete force parameter
parts[3] = urlencode(params)
next = urlunsplit(parts)
-
+
# build url location
parts[2] = reverse('latest_terms')
- params = {'next':next}
+ params = {'next': next}
parts[3] = urlencode(params)
url = urlunsplit(parts)
response['Location'] = url
except ValidationError, e:
return HttpResponseBadRequest(e)
# authenticate before login
- user = authenticate(email=request.user.email, auth_token=request.user.auth_token)
+ user = authenticate(email=request.user.email,
+ auth_token=request.user.auth_token
+ )
auth_login(request, user)
set_cookie(response, user)
logger.info('Token reset for %s' % request.user.email)
parts = list(urlsplit(next))
- parts[3] = urlencode({'user': request.user.email, 'token': request.user.auth_token})
+ parts[3] = urlencode({'user': request.user.email,
+ 'token': request.user.auth_token
+ }
+ )
url = urlunsplit(parts)
response['Location'] = url
response.status_code = 302
return response
else:
# redirect to login with next the request path
-
+
# first build next parameter
parts = list(urlsplit(request.build_absolute_uri()))
params = dict(parse_qsl(parts[3], keep_blank_values=True))
del params['force']
parts[3] = urlencode(params)
next = urlunsplit(parts)
-
+
# build url location
- parts[2] = reverse('astakos.im.views.index')
- params = {'next':next}
+ parts[2] = reverse('index')
+ params = {'next': next}
parts[3] = urlencode(params)
url = urlunsplit(parts)
response['Location'] = url
response.status_code = 302
- return response
+ return response
# Copyright 2011-2012 GRNET S.A. All rights reserved.
-#
+#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
-#
+#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
-#
+#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
-#
+#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
-#
+#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
from django.utils.translation import ugettext as _
from django.contrib import messages
from django.template import RequestContext
+ from django.views.decorators.http import require_http_methods
-from astakos.im.util import prepare_response, get_context, get_invitation
+from astakos.im.util import prepare_response, get_context
from astakos.im.views import requires_anonymous, render_response
-from astakos.im.settings import DEFAULT_USER_LEVEL
-from astakos.im.models import AstakosUser, Invitation, AdditionalMail
+from astakos.im.models import AstakosUser
from astakos.im.forms import LoginForm
from astakos.im.activation_backends import get_backend, SimpleBackend
+
class Tokens:
# these are mapped by the Shibboleth SP software
- SHIB_EPPN = "HTTP_EPPN" # eduPersonPrincipalName
+ SHIB_EPPN = "HTTP_EPPN" # eduPersonPrincipalName
SHIB_NAME = "HTTP_SHIB_INETORGPERSON_GIVENNAME"
SHIB_SURNAME = "HTTP_SHIB_PERSON_SURNAME"
SHIB_CN = "HTTP_SHIB_PERSON_COMMONNAME"
SHIB_SESSION_ID = "HTTP_SHIB_SESSION_ID"
SHIB_MAIL = "HTTP_SHIB_MAIL"
+
+ @require_http_methods(["GET", "POST"])
@requires_anonymous
-def login(request, backend=None, on_login_template='im/login.html', on_creation_template='im/third_party_registration.html', extra_context={}):
+def login(request, backend=None, on_login_template='im/login.html',
+ on_creation_template='im/third_party_registration.html',
+ extra_context=None
+ ):
tokens = request.META
-
+
try:
eppn = tokens[Tokens.SHIB_EPPN]
except KeyError:
return HttpResponseBadRequest("Missing unique token in request")
-
+
if Tokens.SHIB_DISPLAYNAME in tokens:
realname = tokens[Tokens.SHIB_DISPLAYNAME]
elif Tokens.SHIB_CN in tokens:
realname = tokens[Tokens.SHIB_NAME] + ' ' + tokens[Tokens.SHIB_SURNAME]
else:
return HttpResponseBadRequest("Missing user name in request")
-
+
affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
email = tokens.get(Tokens.SHIB_MAIL, None)
-
+
try:
- user = AstakosUser.objects.get(provider='shibboleth', third_party_identifier=eppn)
+ user = AstakosUser.objects.get(provider='shibboleth',
- third_party_identifier=eppn
- )
++ third_party_identifier=eppn)
if user.is_active:
return prepare_response(request,
user,
'renew' in request.GET)
else:
message = _('Inactive account')
- messages.add_message(request, messages.ERROR, message)
+ messages.error(request, message)
return render_response(on_login_template,
- login_form = LoginForm(request=request),
+ login_form=LoginForm(request=request),
context_instance=RequestContext(request))
except AstakosUser.DoesNotExist, e:
user = AstakosUser(third_party_identifier=eppn, realname=realname,
try:
if not backend:
backend = get_backend(request)
- form = backend.get_signup_form(provider='shibboleth', instance=user)
+ form = backend.get_signup_form(
+ provider='shibboleth', instance=user)
except Exception, e:
- form = SimpleBackend(request).get_signup_form(provider='shibboleth', instance=user)
- messages.add_message(request, messages.ERROR, e)
+ form = SimpleBackend(request).get_signup_form(
+ provider='shibboleth',
- instance=user
- )
++ instance=user)
+ messages.error(request, e)
return render_response(on_creation_template,
- signup_form = form,
- provider = 'shibboleth',
- context_instance=get_context(request, extra_context))
+ signup_form=form,
+ provider='shibboleth',
- context_instance=get_context(
- request,
- extra_context
- )
- )
++ context_instance=get_context(request,
++ extra_context))
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
-from django.conf.urls.defaults import patterns, include, url
-from django.contrib.auth.views import password_change
+from django.conf.urls.defaults import patterns, url
- from astakos.im.forms import ExtendedPasswordResetForm, ExtendedPasswordChangeForm, LoginForm
+ from astakos.im.forms import (ExtendedPasswordResetForm,
+ ExtendedPasswordChangeForm,
+ ExtendedSetPasswordForm, LoginForm)
from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED, EMAILCHANGE_ENABLED
urlpatterns = patterns('astakos.im.views',
- url(r'^$', 'index', {}, name='index'),
- url(r'^login/?$', 'index', {}, name='login'),
- url(r'^profile/?$', 'edit_profile'),
- url(r'^feedback/?$', 'feedback'),
- url(r'^signup/?$', 'signup', {'on_success':'im/login.html', 'extra_context':{'login_form':LoginForm()}}),
- url(r'^logout/?$', 'logout', {'template':'im/login.html', 'extra_context':{'login_form':LoginForm()}}),
- url(r'^activate/?$', 'activate'),
- url(r'^approval_terms/?$', 'approval_terms', {}, name='latest_terms'),
- url(r'^approval_terms/(?P<term_id>\d+)/?$', 'approval_terms'),
- url(r'^password/?$', 'change_password', {}, name='password_change'),
-)
+ url(r'^$', 'index', {}, name='index'),
+ url(r'^login/?$', 'index', {}, name='login'),
+ url(r'^profile/?$',
+ 'edit_profile', {}, name='edit_profile'),
+ url(r'^feedback/?$', 'feedback', {}, name='feedback'),
+ url(r'^signup/?$', 'signup',
+ {'on_success': 'im/login.html',
+ 'extra_context': {'login_form': LoginForm()}},
+ name='signup'),
+ url(r'^logout/?$', 'logout',
+ {'template': 'im/login.html',
+ 'extra_context': {'login_form': LoginForm()}},
+ name='logout'),
+ url(r'^activate/?$', 'activate', {}, name='activate'),
+ url(r'^approval_terms/?$',
+ 'approval_terms', {}, name='latest_terms'),
+ url(r'^approval_terms/(?P<term_id>\d+)/?$',
+ 'approval_terms'),
+ url(r'^password/?$',
+ 'change_password', {}, name='password_change'),
+ url(r'^resources/?$',
+ 'resource_list', {}, name='resource_list'),
+ url(r'^billing/?$', 'billing', {}, name='billing'),
+ url(r'^timeline/?$', 'timeline', {}, name='timeline'),
+ url(r'^group/add/(?P<kind_name>\w+)?$',
+ 'group_add', {}, name='group_add'),
+ url(r'^group/list/?$',
+ 'group_list', {}, name='group_list'),
+ url(r'^group/(?P<group_id>\d+)/?$', 'group_detail',
+ {}, name='group_detail'),
+ url(r'^group/search/?$',
+ 'group_search', {}, name='group_search'),
+ url(r'^group/all/?$',
+ 'group_all', {}, name='group_all'),
+ url(r'^group/(?P<group_id>\d+)/join/?$', 'group_join',
+ {}, name='group_join'),
+ url(r'^group/(?P<group_id>\d+)/leave/?$', 'group_leave',
+ {}, name='group_leave'),
+ url(r'^group/(?P<group_id>\d+)/(?P<user_id>\d+)/approve/?$',
+ 'approve_member', {}, name='approve_member'),
+ url(r'^group/(?P<group_id>\d+)/(?P<user_id>\d+)/disapprove/?$',
+ 'disapprove_member', {}, name='disapprove_member'),
+ url(r'^group/create/?$', 'group_create_list', {},
+ name='group_create_list'),
+ )
if EMAILCHANGE_ENABLED:
urlpatterns += patterns('astakos.im.views',
- url(r'^email_change/?$', 'change_email', {}, name='email_change'),
- url(r'^email_change/confirm/(?P<activation_key>\w+)/', 'change_email', {},
- name='email_change_confirm')
-)
-
+ url(r'^email_change/?$',
+ 'change_email', {}, name='email_change'),
+ url(
+ r'^email_change/confirm/(?P<activation_key>\w+)/', 'change_email', {},
+ name='email_change_confirm')
+ )
+
urlpatterns += patterns('astakos.im.target',
- url(r'^login/redirect/?$', 'redirect.login')
-)
+ url(r'^login/redirect/?$', 'redirect.login')
+ )
if 'local' in IM_MODULES:
urlpatterns += patterns('astakos.im.target',
- url(r'^local/?$', 'local.login')
- )
+ url(r'^local/?$', 'local.login')
+ )
urlpatterns += patterns('django.contrib.auth.views',
- url(r'^local/password_reset/?$', 'password_reset',
- {'email_template_name': 'registration/password_email.txt',
- 'password_reset_form': ExtendedPasswordResetForm}),
- url(r'^local/password_reset_done/?$',
- 'password_reset_done'),
- url(
- r'^local/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/?$',
- 'password_reset_confirm'),
- url(r'^local/password/reset/complete/?$',
- 'password_reset_complete'),
- url(
- r'^password_change/?$', 'password_change', {'post_change_redirect': 'profile',
- 'password_change_form': ExtendedPasswordChangeForm})
- )
+ url(r'^local/password_reset/?$', 'password_reset',
+ {'email_template_name':'registration/password_email.txt',
+ 'password_reset_form':ExtendedPasswordResetForm}),
+ url(r'^local/password_reset_done/?$', 'password_reset_done'),
+ url(r'^local/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/?$',
+ 'password_reset_confirm', {'set_password_form':ExtendedSetPasswordForm}),
+ url(r'^local/password/reset/complete/?$', 'password_reset_complete'),
+ url(r'^password_change/?$', 'password_change', {'post_change_redirect':'profile',
+ 'password_change_form':ExtendedPasswordChangeForm})
+ )
if INVITATIONS_ENABLED:
urlpatterns += patterns('astakos.im.views',
- url(r'^invite/?$', 'invite')
- )
+ url(r'^invite/?$', 'invite', {}, name='invite')
+ )
if 'shibboleth' in IM_MODULES:
urlpatterns += patterns('astakos.im.target',
- url(r'^login/shibboleth/?$', 'shibboleth.login')
- )
+ url(r'^login/shibboleth/?$', 'shibboleth.login')
+ )
if 'twitter' in IM_MODULES:
urlpatterns += patterns('astakos.im.target',
- url(r'^login/twitter/?$', 'twitter.login'),
- url(r'^login/twitter/authenticated/?$', 'twitter.authenticated')
- )
+ url(r'^login/twitter/?$', 'twitter.login'),
+ url(r'^login/twitter/authenticated/?$',
+ 'twitter.authenticated')
+ )
+
+urlpatterns += patterns('astakos.im.api',
+ url(r'^get_services/?$', 'get_services'),
+ url(r'^get_menu/?$', 'get_menu'),
+ )
urlpatterns += patterns('astakos.im.api.admin',
- url(r'^authenticate/?$', 'authenticate_old'),
- #url(r'^authenticate/v2/?$', 'authenticate'),
- url(r'^get_services/?$', 'get_services'),
- url(r'^get_menu/?$', 'get_menu'),
- url(r'^admin/api/v2.0/users/?$', 'get_user_by_email'),
- url(r'^admin/api/v2.0/users/(?P<user_id>.+?)/?$', 'get_user_by_username'),
-)
+ url(r'^authenticate/?$', 'authenticate_old'),
+ #url(r'^authenticate/v2/?$', 'authenticate'),
+ url(r'^admin/api/v2.0/users/?$', 'get_user_by_email'),
+ url(r'^admin/api/v2.0/users/(?P<user_id>.+?)/?$',
+ 'get_user_by_username'),
+ )
urlpatterns += patterns('astakos.im.api.service',
- #url(r'^service/api/v2.0/tokens/(?P<token_id>.+?)/?$', 'validate_token'),
- url(r'^service/api/v2.0/feedback/?$', 'send_feedback'),
- url(r'^service/api/v2.0/users/?$', 'get_user_by_email'),
- url(r'^service/api/v2.0/users/(?P<user_id>.+?)/?$', 'get_user_by_username'),
-)
+ #url(r'^service/api/v2.0/tokens/(?P<token_id>.+?)/?$', 'validate_token'),
+ url(r'^service/api/v2.0/feedback/?$', 'send_feedback'),
+ url(r'^service/api/v2.0/users/?$',
+ 'get_user_by_email'),
+ url(r'^service/api/v2.0/users/(?P<user_id>.+?)/?$',
+ 'get_user_by_username'),
+ )
# Copyright 2011-2012 GRNET S.A. All rights reserved.
-#
+#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
-#
+#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
-#
+#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
-#
+#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
-#
+#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
import time
from urllib import quote
-from urlparse import urlsplit, urlunsplit
from datetime import tzinfo, timedelta
from django.http import HttpResponse, HttpResponseBadRequest, urlencode
from django.core.urlresolvers import reverse
from django.core.exceptions import ValidationError
-from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
-from astakos.im.settings import INVITATIONS_PER_LEVEL, COOKIE_NAME, \
+from astakos.im.models import AstakosUser, Invitation
+from astakos.im.settings import COOKIE_NAME, \
COOKIE_DOMAIN, COOKIE_SECURE, FORCE_PROFILE_UPDATE, LOGGING_LEVEL
from astakos.im.functions import login
logger = logging.getLogger(__name__)
+
class UTC(tzinfo):
- def utcoffset(self, dt):
- return timedelta(0)
+ def utcoffset(self, dt):
+ return timedelta(0)
+
+ def tzname(self, dt):
+ return 'UTC'
- def tzname(self, dt):
- return 'UTC'
+ def dst(self, dt):
+ return timedelta(0)
- def dst(self, dt):
- return timedelta(0)
def isoformat(d):
- """Return an ISO8601 date string that includes a timezone."""
+ """Return an ISO8601 date string that includes a timezone."""
+
+ return d.replace(tzinfo=UTC()).isoformat()
- return d.replace(tzinfo=UTC()).isoformat()
def epoch(datetime):
- return int(time.mktime(datetime.timetuple())*1000)
+ return int(time.mktime(datetime.timetuple()) * 1000)
-def get_context(request, extra_context={}, **kwargs):
- if not extra_context:
- extra_context = {}
+
+def get_context(request, extra_context=None, **kwargs):
+ extra_context = extra_context or {}
extra_context.update(kwargs)
return RequestContext(request, extra_context)
+
def get_invitation(request):
"""
Returns the invitation identified by the ``code``.
-
+
Raises ValueError if the invitation is consumed or there is another account
associated with this email.
"""
code = request.POST.get('code')
if not code:
return
- invitation = Invitation.objects.get(code = code)
+ invitation = Invitation.objects.get(code=code)
if invitation.is_consumed:
raise ValueError(_('Invitation is used'))
if reserved_email(invitation.username):
raise ValueError(_('Email: %s is reserved' % invitation.username))
return invitation
+
def prepare_response(request, user, next='', renew=False):
"""Return the unique username and the token
as 'X-Auth-User' and 'X-Auth-Token' headers,
or redirect to the URL provided in 'next'
with the 'user' and 'token' as parameters.
-
+
Reissue the token even if it has not yet
expired, if the 'renew' parameter is present
or user has not a valid token.
"""
renew = renew or (not user.auth_token)
- renew = renew or (user.auth_token_expires and user.auth_token_expires < datetime.datetime.now())
+ renew = renew or (user.auth_token_expires < datetime.datetime.now())
if renew:
user.renew_token()
try:
user.save()
except ValidationError, e:
- return HttpResponseBadRequest(e)
-
+ return HttpResponseBadRequest(e)
+
if FORCE_PROFILE_UPDATE and not user.is_verified and not user.is_superuser:
params = ''
if next:
params = '?' + urlencode({'next': next})
- next = reverse('astakos.im.views.edit_profile') + params
-
+ next = reverse('edit_profile') + params
+
response = HttpResponse()
-
+
# authenticate before login
user = authenticate(email=user.email, auth_token=user.auth_token)
login(request, user)
set_cookie(response, user)
request.session.set_expiry(user.auth_token_expires)
-
+
if not next:
- next = reverse('astakos.im.views.index')
-
+ next = reverse('index')
+
response['Location'] = next
response.status_code = 302
return response
+
def set_cookie(response, user):
expire_fmt = user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
cookie_value = quote(user.email + '|' + user.auth_token)
response.set_cookie(COOKIE_NAME, value=cookie_value,
expires=expire_fmt, path='/',
- domain=COOKIE_DOMAIN, secure=COOKIE_SECURE)
- msg = 'Cookie [expiring %s] set for %s' % (user.auth_token_expires, user.email)
- logger._log(LOGGING_LEVEL, msg, [])
+ domain=COOKIE_DOMAIN, secure=COOKIE_SECURE
+ )
+ msg = 'Cookie [expiring %s] set for %s' % (
+ user.auth_token_expires,
+ user.email
+ )
+ logger.log(LOGGING_LEVEL, msg)
+
class lazy_string(object):
def __init__(self, function, *args, **kwargs):
- self.function=function
- self.args=args
- self.kwargs=kwargs
-
+ self.function = function
+ self.args = args
+ self.kwargs = kwargs
+
def __str__(self):
if not hasattr(self, 'str'):
- self.str=self.function(*self.args, **self.kwargs)
+ self.str = self.function(*self.args, **self.kwargs)
return self.str
+
def reverse_lazy(*args, **kwargs):
return lazy_string(reverse, *args, **kwargs)
+
def reserved_email(email):
- return AstakosUser.objects.filter(email = email).count() != 0
+ return AstakosUser.objects.filter(email__iexact=email).count() != 0
+
def get_query(request):
try:
return request.__getattribute__(request.method)
except AttributeError:
- return request.GET
+ return {}
# or implied, of GRNET S.A.
import logging
-import socket
+import calendar
-from smtplib import SMTPException
from urllib import quote
from functools import wraps
+from datetime import datetime, timedelta
+from collections import defaultdict
-from django.core.mail import send_mail
-from django.http import HttpResponse, HttpResponseBadRequest
-from django.shortcuts import redirect
-from django.template.loader import render_to_string
-from django.utils.translation import ugettext as _
-from django.core.urlresolvers import reverse
-from django.contrib.auth.decorators import login_required
from django.contrib import messages
-from django.db import transaction
-from django.utils.http import urlencode
-from django.http import HttpResponseRedirect, HttpResponseBadRequest
-from django.db.utils import IntegrityError
+from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import password_change
-from django.core.exceptions import ValidationError
+from django.core.urlresolvers import reverse
+from django.db import transaction
from django.db.models import Q
++<<<<<<< HEAD
+from django.db.utils import IntegrityError
+from django.forms.fields import URLField
+from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \
+ HttpResponseRedirect, HttpResponseBadRequest, Http404
+from django.shortcuts import redirect
+from django.template import RequestContext, loader as template_loader
+from django.utils.http import urlencode
+from django.utils.translation import ugettext as _
+from django.views.generic.create_update import (create_object, delete_object,
+ get_model_and_form_class)
+from django.views.generic.list_detail import object_list, object_detail
+from django.http import HttpResponseBadRequest
+from django.core.xheaders import populate_xheaders
+
+from astakos.im.models import (
+ AstakosUser, ApprovalTerms, AstakosGroup, Resource,
+ EmailChange, GroupKind, Membership, AstakosGroupQuota)
+ from django.views.decorators.http import require_http_methods
+
-from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
from astakos.im.activation_backends import get_backend, SimpleBackend
from astakos.im.util import get_context, prepare_response, set_cookie, get_query
-from astakos.im.forms import *
-from astakos.im.functions import send_greeting, send_feedback, SendMailError, \
- invite as invite_func, logout as auth_logout, activate as activate_func, switch_account_to_shibboleth
-from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, LOGOUT_NEXT, LOGGING_LEVEL
+from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
+ FeedbackForm, SignApprovalTermsForm,
+ ExtendedPasswordChangeForm, EmailChangeForm,
+ AstakosGroupCreationForm, AstakosGroupSearchForm,
+ AstakosGroupUpdateForm, AddGroupMembersForm,
+ AstakosGroupSortForm, MembersSortForm,
+ TimelineForm, PickResourceForm)
+from astakos.im.functions import (send_feedback, SendMailError,
+ invite as invite_func, logout as auth_logout,
+ activate as activate_func,
+ switch_account_to_shibboleth,
+ send_admin_notification,
+ SendNotificationError)
+from astakos.im.endpoints.quotaholder import timeline_charge
+from astakos.im.settings import (
+ COOKIE_NAME, COOKIE_DOMAIN, SITENAME, LOGOUT_NEXT,
+ LOGGING_LEVEL, PAGINATE_BY)
+from astakos.im.tasks import request_billing
logger = logging.getLogger(__name__)
-def render_response(template, tab=None, status=200, reset_cookie=False, context_instance=None, **kwargs):
+
+DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
+ 'https://', '')"""
+
+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
if tab is None:
tab = template.partition('_')[0].partition('.html')[0]
kwargs.setdefault('tab', tab)
- html = render_to_string(template, kwargs, context_instance=context_instance)
+ html = template_loader.render_to_string(
+ template, kwargs, context_instance=context_instance)
response = HttpResponse(html, status=status)
if reset_cookie:
set_cookie(response, context_instance['request'].user)
return func(request, *args)
return wrapper
+
def signed_terms_required(func):
"""
Decorator checkes whether the request.user is Anonymous and in that case
"""
@wraps(func)
def wrapper(request, *args, **kwargs):
- if request.user.is_authenticated() and not request.user.signed_terms():
+ if request.user.is_authenticated() and not request.user.signed_terms:
params = urlencode({'next': request.build_absolute_uri(),
- 'show_form':''})
+ 'show_form': ''})
terms_uri = reverse('latest_terms') + '?' + params
return HttpResponseRedirect(terms_uri)
return func(request, *args, **kwargs)
return wrapper
+
+ @require_http_methods(["GET", "POST"])
@signed_terms_required
-def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context={}):
+def index(request, login_template_name='im/login.html', extra_context=None):
"""
If there is logged on user renders the profile page otherwise renders login page.
"""
template_name = login_template_name
if request.user.is_authenticated():
- return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
+ return HttpResponseRedirect(reverse('edit_profile'))
return render_response(template_name,
- login_form = LoginForm(request=request),
- context_instance = get_context(request, extra_context))
+ login_form=LoginForm(request=request),
+ context_instance=get_context(request, extra_context))
+
+ @require_http_methods(["GET", "POST"])
@login_required
@signed_terms_required
@transaction.commit_manually
-def invite(request, template_name='im/invitations.html', extra_context={}):
+def invite(request, template_name='im/invitations.html', extra_context=None):
"""
Allows a user to invite somebody else.
* LOGIN_URL: login uri
* ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
- * ASTAKOS_DEFAULT_FROM_EMAIL: from email
"""
status = None
message = None
form = InvitationForm()
-
+
inviter = request.user
if request.method == 'POST':
form = InvitationForm(request.POST)
try:
invitation = form.save()
invite_func(invitation, inviter)
- status = messages.SUCCESS
message = _('Invitation sent to %s' % invitation.username)
+ messages.success(request, message)
except SendMailError, e:
- status = messages.ERROR
message = e.message
+ messages.error(request, message)
transaction.rollback()
except BaseException, e:
- status = messages.ERROR
message = _('Something went wrong.')
+ messages.error(request, message)
logger.exception(e)
transaction.rollback()
else:
transaction.commit()
else:
- status = messages.ERROR
message = _('No invitations left')
- messages.add_message(request, status, message)
+ messages.error(request, message)
sent = [{'email': inv.username,
'realname': inv.realname,
'is_consumed': inv.is_consumed}
- for inv in request.user.invitations_sent.all()]
+ for inv in request.user.invitations_sent.all()]
kwargs = {'inviter': inviter,
- 'sent':sent}
+ 'sent': sent}
context = get_context(request, extra_context, **kwargs)
return render_response(template_name,
- invitation_form = form,
- context_instance = context)
+ invitation_form=form,
+ context_instance=context)
+
+ @require_http_methods(["GET", "POST"])
@login_required
@signed_terms_required
-def edit_profile(request, template_name='im/profile.html', extra_context={}):
+def edit_profile(request, template_name='im/profile.html', extra_context=None):
"""
Allows a user to edit his/her profile.
* LOGIN_URL: login uri
"""
+ extra_context = extra_context or {}
form = ProfileForm(instance=request.user)
extra_context['next'] = request.GET.get('next')
reset_cookie = False
next = request.POST.get('next')
if next:
return redirect(next)
- msg = _('<p>Profile has been updated successfully</p>')
- messages.add_message(request, messages.SUCCESS, msg)
+ msg = _('Profile has been updated successfully')
+ messages.success(request, msg)
except ValueError, ve:
- messages.add_message(request, messages.ERROR, ve)
+ messages.success(request, ve)
elif request.method == "GET":
- request.user.is_verified = True
- request.user.save()
+ if not request.user.is_verified:
+ request.user.is_verified = True
+ request.user.save()
return render_response(template_name,
- reset_cookie = reset_cookie,
- profile_form = form,
- context_instance = get_context(request,
- extra_context))
+ reset_cookie=reset_cookie,
+ profile_form=form,
+ context_instance=get_context(request,
+ extra_context))
+
+@transaction.commit_manually
+ @require_http_methods(["GET", "POST"])
-def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context={}, backend=None):
+def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
"""
Allows a user to create a local account.
if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
(see activation_backends);
-
+
Upon successful user creation, if ``next`` url parameter is present the user is redirected there
otherwise renders the same page with a success message.
-
+
On unsuccessful creation, renders ``template_name`` with an error message.
-
+
**Arguments**
-
+
``template_name``
A custom template to render. This is optional;
if not specified, this will default to ``im/signup.html``.
An dictionary of variables to add to the template context.
**Template:**
-
+
im/signup.html or ``template_name`` keyword argument.
- im/signup_complete.html or ``on_success`` keyword argument.
+ im/signup_complete.html or ``on_success`` keyword argument.
"""
if request.user.is_authenticated():
- return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
-
+ return HttpResponseRedirect(reverse('edit_profile'))
+
provider = get_query(request).get('provider', 'local')
try:
if not backend:
form = backend.get_signup_form(provider)
except Exception, e:
form = SimpleBackend(request).get_signup_form(provider)
- messages.add_message(request, messages.ERROR, e)
+ messages.error(request, e)
if request.method == 'POST':
if form.is_valid():
user = form.save(commit=False)
additional_email = form.cleaned_data['additional_email']
if additional_email != user.email:
user.additionalmail_set.create(email=additional_email)
- msg = 'Additional email: %s saved for user %s.' % (additional_email, user.email)
- logger._log(LOGGING_LEVEL, msg, [])
+ msg = 'Additional email: %s saved for user %s.' % (
+ additional_email, user.email)
+ logger.log(LOGGING_LEVEL, msg)
if user and user.is_active:
next = request.POST.get('next', '')
+ transaction.commit()
return prepare_response(request, user, next=next)
messages.add_message(request, status, message)
+ transaction.commit()
return render_response(on_success,
context_instance=get_context(request, extra_context))
except SendMailError, e:
- status = messages.ERROR
message = e.message
- messages.add_message(request, status, message)
+ messages.error(request, message)
+ transaction.rollback()
except BaseException, e:
- status = messages.ERROR
message = _('Something went wrong.')
- messages.add_message(request, status, message)
+ messages.error(request, message)
logger.exception(e)
+ transaction.rollback()
return render_response(template_name,
- signup_form = form,
- provider = provider,
+ signup_form=form,
+ provider=provider,
context_instance=get_context(request, extra_context))
+
+ @require_http_methods(["GET", "POST"])
@login_required
@signed_terms_required
-def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context={}):
+def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
"""
Allows a user to send feedback.
try:
send_feedback(msg, data, request.user, email_template_name)
except SendMailError, e:
- message = e.message
- status = messages.ERROR
+ messages.error(request, message)
else:
message = _('Feedback successfully sent')
- status = messages.SUCCESS
- messages.add_message(request, status, message)
+ messages.success(request, message)
return render_response(template_name,
- feedback_form = form,
- context_instance = get_context(request, extra_context))
+ feedback_form=form,
+ context_instance=get_context(request, extra_context))
+
+ @require_http_methods(["GET", "POST"])
-def logout(request, template='registration/logged_out.html', extra_context={}):
+@signed_terms_required
+def logout(request, template='registration/logged_out.html', extra_context=None):
"""
Wraps `django.contrib.auth.logout` and delete the cookie.
"""
auth_logout(request)
response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
msg = 'Cookie deleted for %s' % email
- logger._log(LOGGING_LEVEL, msg, [])
+ logger.log(LOGGING_LEVEL, msg)
next = request.GET.get('next')
if next:
response['Location'] = next
response['Location'] = LOGOUT_NEXT
response.status_code = 301
return response
- messages.add_message(request, messages.SUCCESS, _('<p>You have successfully logged out.</p>'))
+ messages.success(request, _('You have successfully logged out.'))
context = get_context(request, extra_context)
- response.write(render_to_string(template, context_instance=context))
+ response.write(template_loader.render_to_string(template, context_instance=context))
return response
+
+ @require_http_methods(["GET", "POST"])
@transaction.commit_manually
-def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
+def activate(request, greeting_email_template_name='im/welcome_email.txt',
+ helpdesk_email_template_name='im/helpdesk_notification.txt'):
"""
Activates the user identified by the ``auth`` request parameter, sends a welcome email
and renews the user token.
user = AstakosUser.objects.get(auth_token=token)
except AstakosUser.DoesNotExist:
return HttpResponseBadRequest(_('No such user'))
-
+
if user.is_active:
message = _('Account already active.')
- messages.add_message(request, messages.ERROR, message)
+ messages.error(request, message)
return index(request)
-
+
try:
- local_user = AstakosUser.objects.get(~Q(id = user.id), email=user.email, is_active=True)
+ local_user = AstakosUser.objects.get(
+ ~Q(id=user.id),
+ email=user.email,
+ is_active=True
+ )
except AstakosUser.DoesNotExist:
try:
- activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
+ activate_func(
+ user,
+ greeting_email_template_name,
+ helpdesk_email_template_name,
+ verify_email=True
+ )
response = prepare_response(request, user, next, renew=True)
transaction.commit()
return response
except SendMailError, e:
message = e.message
- messages.add_message(request, messages.ERROR, message)
+ messages.error(request, message)
transaction.rollback()
return index(request)
except BaseException, e:
- status = messages.ERROR
message = _('Something went wrong.')
- messages.add_message(request, messages.ERROR, message)
+ messages.error(request, message)
logger.exception(e)
transaction.rollback()
return index(request)
else:
try:
- user = switch_account_to_shibboleth(user, local_user, greeting_email_template_name)
+ user = switch_account_to_shibboleth(
+ user,
+ local_user,
+ greeting_email_template_name
+ )
response = prepare_response(request, user, next, renew=True)
transaction.commit()
return response
except SendMailError, e:
message = e.message
- messages.add_message(request, messages.ERROR, message)
+ messages.error(request, message)
transaction.rollback()
return index(request)
except BaseException, e:
- status = messages.ERROR
message = _('Something went wrong.')
- messages.add_message(request, messages.ERROR, message)
+ messages.error(request, message)
logger.exception(e)
transaction.rollback()
return index(request)
+
+ @require_http_methods(["GET", "POST"])
-def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context={}):
+def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
term = None
terms = None
if not term_id:
pass
else:
try:
- term = ApprovalTerms.objects.get(id=term_id)
- except ApprovalTermDoesNotExist, e:
+ term = ApprovalTerms.objects.get(id=term_id)
+ except ApprovalTerms.DoesNotExist, e:
pass
if not term:
- return HttpResponseRedirect(reverse('astakos.im.views.index'))
+ return HttpResponseRedirect(reverse('index'))
f = open(term.location, 'r')
terms = f.read()
if request.method == 'POST':
next = request.POST.get('next')
if not next:
- next = reverse('astakos.im.views.index')
+ next = reverse('index')
form = SignApprovalTermsForm(request.POST, instance=request.user)
if not form.is_valid():
return render_response(template_name,
- terms = terms,
- approval_terms_form = form,
- context_instance = get_context(request, extra_context))
+ terms=terms,
+ approval_terms_form=form,
+ context_instance=get_context(request, extra_context))
user = form.save()
return HttpResponseRedirect(next)
else:
form = None
- if request.user.is_authenticated() and not request.user.signed_terms():
+ if request.user.is_authenticated() and not request.user.signed_terms:
form = SignApprovalTermsForm(instance=request.user)
return render_response(template_name,
- terms = terms,
- approval_terms_form = form,
- context_instance = get_context(request, extra_context))
+ terms=terms,
+ approval_terms_form=form,
+ context_instance=get_context(request, extra_context))
+
+ @require_http_methods(["GET", "POST"])
@signed_terms_required
def change_password(request):
return password_change(request,
- post_change_redirect=reverse('astakos.im.views.edit_profile'),
- password_change_form=ExtendedPasswordChangeForm)
+ post_change_redirect=reverse('edit_profile'),
+ password_change_form=ExtendedPasswordChangeForm)
-
- @signed_terms_required
+ @require_http_methods(["GET", "POST"])
@login_required
+ @signed_terms_required
@transaction.commit_manually
def change_email(request, activation_key=None,
email_template_name='registration/email_change_email.txt',
form_template_name='registration/email_change_form.html',
confirm_template_name='registration/email_change_done.html',
- extra_context={}):
+ extra_context=None):
if activation_key:
try:
user = EmailChange.objects.change_email(activation_key)
if request.user.is_authenticated() and request.user == user:
msg = _('Email changed successfully.')
- messages.add_message(request, messages.SUCCESS, msg)
+ messages.success(request, msg)
auth_logout(request)
response = prepare_response(request, user)
transaction.commit()
return response
except ValueError, e:
- messages.add_message(request, messages.ERROR, e)
+ messages.error(request, e)
return render_response(confirm_template_name,
- modified_user = user if 'user' in locals() else None,
- context_instance = get_context(request,
- extra_context))
-
+ modified_user=user if 'user' in locals(
+ ) else None,
+ context_instance=get_context(request,
+ extra_context))
+
if not request.user.is_authenticated():
path = quote(request.get_full_path())
- url = request.build_absolute_uri(reverse('astakos.im.views.index'))
+ url = request.build_absolute_uri(reverse('index'))
return HttpResponseRedirect(url + '?next=' + path)
form = EmailChangeForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
try:
ec = form.save(email_template_name, request)
except SendMailError, e:
- status = messages.ERROR
msg = e
+ messages.error(request, msg)
transaction.rollback()
except IntegrityError, e:
- status = messages.ERROR
msg = _('There is already a pending change email request.')
+ messages.error(request, msg)
else:
- status = messages.SUCCESS
msg = _('Change email request has been registered succefully.\
You are going to receive a verification email in the new address.')
+ messages.success(request, msg)
transaction.commit()
- messages.add_message(request, status, msg)
return render_response(form_template_name,
- form = form,
- context_instance = get_context(request,
- extra_context))
+ form=form,
+ context_instance=get_context(request,
+ extra_context))
+
+
+@signed_terms_required
+@login_required
+def group_add(request, kind_name='default'):
+ try:
+ kind = GroupKind.objects.get(name=kind_name)
+ except:
+ return HttpResponseBadRequest(_('No such group kind'))
+
+ post_save_redirect = '/im/group/%(id)s/'
+ context_processors = None
+ model, form_class = get_model_and_form_class(
+ model=None,
+ form_class=AstakosGroupCreationForm
+ )
+ resources = dict(
+ (str(r.id), r) for r in Resource.objects.select_related().all())
+ policies = []
+ if request.method == 'POST':
+ form = form_class(request.POST, request.FILES, resources=resources)
+ if form.is_valid():
+ new_object = form.save()
+
+ # save owner
+ new_object.owners = [request.user]
+
+ # save quota policies
+ for (rid, uplimit) in form.resources():
+ try:
+ r = resources[rid]
+ except KeyError, e:
+ logger.exception(e)
+ # TODO Should I stay or should I go???
+ continue
+ else:
+ new_object.astakosgroupquota_set.create(
+ resource=r,
+ uplimit=uplimit
+ )
+ policies.append('%s %d' % (r, uplimit))
+ msg = _("The %(verbose_name)s was created successfully.") %\
+ {"verbose_name": model._meta.verbose_name}
+ messages.success(request, msg, fail_silently=True)
+
+ # send notification
+ try:
+ send_admin_notification(
+ template_name='im/group_creation_notification.txt',
+ dictionary={
+ 'group': new_object,
+ 'owner': request.user,
+ 'policies': policies,
+ },
+ subject='%s alpha2 testing group creation notification' % SITENAME
+ )
+ except SendNotificationError, e:
+ messages.error(request, e, fail_silently=True)
+ return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
+ else:
+ now = datetime.now()
+ data = {
+ 'kind': kind
+ }
+ form = form_class(data, resources=resources)
+
+ # Create the template, context, response
+ template_name = "%s/%s_form.html" % (
+ model._meta.app_label,
+ model._meta.object_name.lower()
+ )
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ 'form': form,
+ 'kind': kind,
+ }, context_processors)
+ return HttpResponse(t.render(c))
+
+
+@signed_terms_required
+@login_required
+def group_list(request):
+ none = request.user.astakos_groups.none()
+ q = AstakosGroup.objects.raw("""
+ SELECT auth_group.id,
+ %s AS groupname,
+ im_groupkind.name AS kindname,
+ im_astakosgroup.*,
+ owner.email AS groupowner,
+ (SELECT COUNT(*) FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND date_joined IS NOT NULL) AS approved_members_num,
+ (SELECT CASE WHEN(
+ SELECT date_joined FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND person_id = %s) IS NULL
+ THEN 0 ELSE 1 END) AS membership_status
+ FROM im_astakosgroup
+ INNER JOIN im_membership ON (
+ im_astakosgroup.group_ptr_id = im_membership.group_id)
+ INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
+ INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
+ LEFT JOIN im_astakosuser_owner ON (
+ im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
+ LEFT JOIN auth_user as owner ON (
+ im_astakosuser_owner.astakosuser_id = owner.id)
+ WHERE im_membership.person_id = %s
+ """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id))
+ d = defaultdict(list)
+ for g in q:
+ if request.user.email == g.groupowner:
+ d['own'].append(g)
+ else:
+ d['other'].append(g)
+
+ # validate sorting
+ fields = ('own', 'other')
+ for f in fields:
+ v = globals()['%s_sorting' % f] = request.GET.get('%s_sorting' % f)
+ if v:
+ form = AstakosGroupSortForm({'sort_by': v})
+ if not form.is_valid():
+ globals()['%s_sorting' % f] = form.cleaned_data.get('sort_by')
+ return object_list(request, queryset=none,
+ extra_context={'is_search':False,
+ 'mine': d['own'],
+ 'other': d['other'],
+ 'own_sorting': own_sorting,
+ 'other_sorting': other_sorting,
+ 'own_page': request.GET.get('own_page', 1),
+ 'other_page': request.GET.get('other_page', 1)
+ })
+
+
+@signed_terms_required
+@login_required
+def group_detail(request, group_id):
+ q = AstakosGroup.objects.select_related().filter(pk=group_id)
+ q = q.extra(select={
+ 'is_member': """SELECT CASE WHEN EXISTS(
+ SELECT id FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND person_id = %s)
+ THEN 1 ELSE 0 END""" % request.user.id,
+ 'is_owner': """SELECT CASE WHEN EXISTS(
+ SELECT id FROM im_astakosuser_owner
+ WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
+ AND astakosuser_id = %s)
+ THEN 1 ELSE 0 END""" % request.user.id,
+ 'kindname': """SELECT name FROM im_groupkind
+ WHERE id = im_astakosgroup.kind_id"""})
+
+ model = q.model
+ context_processors = None
+ mimetype = None
+ try:
+ obj = q.get()
+ except AstakosGroup.DoesNotExist:
+ raise Http404("No %s found matching the query" % (
+ model._meta.verbose_name))
+
+ update_form = AstakosGroupUpdateForm(instance=obj)
+ addmembers_form = AddGroupMembersForm()
+ if request.method == 'POST':
+ update_data = {}
+ addmembers_data = {}
+ for k,v in request.POST.iteritems():
+ if k in update_form.fields:
+ update_data[k] = v
+ if k in addmembers_form.fields:
+ addmembers_data[k] = v
+ update_data = update_data or None
+ addmembers_data = addmembers_data or None
+ update_form = AstakosGroupUpdateForm(update_data, instance=obj)
+ addmembers_form = AddGroupMembersForm(addmembers_data)
+ if update_form.is_valid():
+ update_form.save()
+ if addmembers_form.is_valid():
+ map(obj.approve_member, addmembers_form.valid_users)
+ addmembers_form = AddGroupMembersForm()
+
+ template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
+ t = template_loader.get_template(template_name)
+ c = RequestContext(request, {
+ 'object': obj,
+ }, context_processors)
+
+ # validate sorting
+ sorting= request.GET.get('sorting')
+ if sorting:
+ form = MembersSortForm({'sort_by': sorting})
+ if form.is_valid():
+ sorting = form.cleaned_data.get('sort_by')
+
+ extra_context = {'update_form': update_form,
+ 'addmembers_form': addmembers_form,
+ 'page': request.GET.get('page', 1),
+ 'sorting': sorting}
+ for key, value in extra_context.items():
+ if callable(value):
+ c[key] = value()
+ else:
+ c[key] = value
+ response = HttpResponse(t.render(c), mimetype=mimetype)
+ populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
+ return response
+
+
+@signed_terms_required
+@login_required
+def group_search(request, extra_context=None, **kwargs):
+ q = request.GET.get('q')
+ sorting = request.GET.get('sorting')
+ if request.method == 'GET':
+ form = AstakosGroupSearchForm({'q': q} if q else None)
+ else:
+ form = AstakosGroupSearchForm(get_query(request))
+ if form.is_valid():
+ q = form.cleaned_data['q'].strip()
+ if q:
+ queryset = AstakosGroup.objects.select_related()
+ queryset = queryset.filter(name__contains=q)
+ queryset = queryset.filter(approval_date__isnull=False)
+ queryset = queryset.extra(select={
+ 'groupname': DB_REPLACE_GROUP_SCHEME,
+ 'kindname': "im_groupkind.name",
+ 'approved_members_num': """
+ SELECT COUNT(*) FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND date_joined IS NOT NULL""",
+ 'membership_approval_date': """
+ SELECT date_joined FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND person_id = %s""" % request.user.id,
+ 'is_member': """
+ SELECT CASE WHEN EXISTS(
+ SELECT date_joined FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND person_id = %s)
+ THEN 1 ELSE 0 END""" % request.user.id,
+ 'is_owner': """
+ SELECT CASE WHEN EXISTS(
+ SELECT id FROM im_astakosuser_owner
+ WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
+ AND astakosuser_id = %s)
+ THEN 1 ELSE 0 END""" % request.user.id})
+ if sorting:
+ # TODO check sorting value
+ queryset = queryset.order_by(sorting)
+ else:
+ queryset = AstakosGroup.objects.none()
+ return object_list(
+ request,
+ queryset,
+ paginate_by=PAGINATE_BY,
+ page=request.GET.get('page') or 1,
+ template_name='im/astakosgroup_list.html',
+ extra_context=dict(form=form,
+ is_search=True,
+ q=q,
+ sorting=sorting))
+
+@signed_terms_required
+@login_required
+def group_all(request, extra_context=None, **kwargs):
+ q = AstakosGroup.objects.select_related()
+ q = q.filter(approval_date__isnull=False)
+ q = q.extra(select={
+ 'groupname': DB_REPLACE_GROUP_SCHEME,
+ 'kindname': "im_groupkind.name",
+ 'approved_members_num': """
+ SELECT COUNT(*) FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND date_joined IS NOT NULL""",
+ 'membership_approval_date': """
+ SELECT date_joined FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND person_id = %s""" % request.user.id,
+ 'is_member': """
+ SELECT CASE WHEN EXISTS(
+ SELECT date_joined FROM im_membership
+ WHERE group_id = im_astakosgroup.group_ptr_id
+ AND person_id = %s)
+ THEN 1 ELSE 0 END""" % request.user.id})
+ sorting = request.GET.get('sorting')
+ if sorting:
+ # TODO check sorting value
+ q = q.order_by(sorting)
+ return object_list(
+ request,
+ q,
+ paginate_by=PAGINATE_BY,
+ page=request.GET.get('page') or 1,
+ template_name='im/astakosgroup_list.html',
+ extra_context=dict(form=AstakosGroupSearchForm(),
+ is_search=True,
+ sorting=sorting))
+
+
+@signed_terms_required
+@login_required
+def group_join(request, group_id):
+ m = Membership(group_id=group_id,
+ person=request.user,
+ date_requested=datetime.now())
+ try:
+ m.save()
+ post_save_redirect = reverse(
+ 'group_detail',
+ kwargs=dict(group_id=group_id))
+ return HttpResponseRedirect(post_save_redirect)
+ except IntegrityError, e:
+ logger.exception(e)
+ msg = _('Failed to join group.')
+ messages.error(request, msg)
+ return group_search(request)
+
+
+@signed_terms_required
+@login_required
+def group_leave(request, group_id):
+ try:
+ m = Membership.objects.select_related().get(
+ group__id=group_id,
+ person=request.user)
+ except Membership.DoesNotExist:
+ return HttpResponseBadRequest(_('Invalid membership.'))
+ if request.user in m.group.owner.all():
+ return HttpResponseForbidden(_('Owner can not leave the group.'))
+ return delete_object(
+ request,
+ model=Membership,
+ object_id=m.id,
+ template_name='im/astakosgroup_list.html',
+ post_delete_redirect=reverse(
+ 'group_detail',
+ kwargs=dict(group_id=group_id)))
+
+
+def handle_membership(func):
+ @wraps(func)
+ def wrapper(request, group_id, user_id):
+ try:
+ m = Membership.objects.select_related().get(
+ group__id=group_id,
+ person__id=user_id)
+ except Membership.DoesNotExist:
+ return HttpResponseBadRequest(_('Invalid membership.'))
+ else:
+ if request.user not in m.group.owner.all():
+ return HttpResponseForbidden(_('User is not a group owner.'))
+ func(request, m)
+ return group_detail(request, group_id)
+ return wrapper
+
+
+@signed_terms_required
+@login_required
+@handle_membership
+def approve_member(request, membership):
+ try:
+ membership.approve()
+ realname = membership.person.realname
+ msg = _('%s has been successfully joined the group.' % realname)
+ messages.success(request, msg)
+ except BaseException, e:
+ logger.exception(e)
+ realname = membership.person.realname
+ msg = _('Something went wrong during %s\'s approval.' % realname)
+ messages.error(request, msg)
+
+
+@signed_terms_required
+@login_required
+@handle_membership
+def disapprove_member(request, membership):
+ try:
+ membership.disapprove()
+ realname = membership.person.realname
+ msg = _('%s has been successfully removed from the group.' % realname)
+ messages.success(request, msg)
+ except BaseException, e:
+ logger.exception(e)
+ msg = _('Something went wrong during %s\'s disapproval.' % realname)
+ messages.error(request, msg)
+
+
+@signed_terms_required
+@login_required
+def resource_list(request):
+ if request.method == 'POST':
+ form = PickResourceForm(request.POST)
+ if form.is_valid():
+ r = form.cleaned_data.get('resource')
+ if r:
+ groups = request.user.membership_set.only('group').filter(
+ date_joined__isnull=False)
+ groups = [g.group_id for g in groups]
+ q = AstakosGroupQuota.objects.select_related().filter(
+ resource=r, group__in=groups)
+ else:
+ form = PickResourceForm()
+ q = AstakosGroupQuota.objects.none()
+ return object_list(request, q,
+ template_name='im/astakosuserquota_list.html',
+ extra_context={'form': form})
+
+
+def group_create_list(request):
+ form = PickResourceForm()
+ return render_response(
+ template='im/astakosgroup_create_list.html',
+ context_instance=get_context(request),)
+
+
+@signed_terms_required
+@login_required
+def billing(request):
+
+ today = datetime.today()
+ month_last_day= calendar.monthrange(today.year, today.month)[1]
+
+ start = request.POST.get('datefrom', None)
+ if start:
+ today = datetime.fromtimestamp(int(start))
+ month_last_day= calendar.monthrange(today.year, today.month)[1]
+
+ start = datetime(today.year, today.month, 1).strftime("%s")
+ end = datetime(today.year, today.month, month_last_day).strftime("%s")
+ r = request_billing.apply(args=('pgerakios@grnet.gr',
+ int(start) * 1000,
+ int(end) * 1000))
+ data = {}
+
+ try:
+ status, data = r.result
+ data=_clear_billing_data(data)
+ if status != 200:
+ messages.error(request, _('Service response status: %d' % status))
+ except:
+ messages.error(request, r.result)
+
+ print type(start)
+
+ return render_response(
+ template='im/billing.html',
+ context_instance=get_context(request),
+ data=data,
+ zerodate=datetime(month=1,year=1970, day=1),
+ today=today,
+ start=int(start),
+ month_last_day=month_last_day)
+
+def _clear_billing_data(data):
+
+ # remove addcredits entries
+ def isnotcredit(e):
+ return e['serviceName'] != "addcredits"
+
+
+
+ # separate services
+ def servicefilter(service_name):
+ service = service_name
+ def fltr(e):
+ return e['serviceName'] == service
+ return fltr
+
+
+ data['bill_nocredits'] = filter(isnotcredit, data['bill'])
+ data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
+ data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
+ data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
+
+ return data
+
+@signed_terms_required
+@login_required
+def timeline(request):
+# data = {'entity':request.user.email}
+ timeline_body = ()
+ timeline_header = ()
+# form = TimelineForm(data)
+ form = TimelineForm()
+ if request.method == 'POST':
+ data = request.POST
+ form = TimelineForm(data)
+ if form.is_valid():
+ data = form.cleaned_data
+ timeline_header = ('entity', 'resource',
+ 'event name', 'event date',
+ 'incremental cost', 'total cost')
+ timeline_body = timeline_charge(
+ data['entity'], data['resource'],
+ data['start_date'], data['end_date'],
+ data['details'], data['operation'])
+
+ return render_response(template='im/timeline.html',
+ context_instance=get_context(request),
+ form=form,
+ timeline_header=timeline_header,
+ timeline_body=timeline_body)
+ return data
#from logging import INFO
#ASTAKOS_LOGGING_LEVEL = INFO
-# Email subjects configuration. For admin/helper notification emails %(user)s
+# Email subjects configuration. For admin/helper notification emails %(user)s
# maps to registered/activated user email.
#ASTAKOS_INVITATION_EMAIL_SUBJECT = 'Invitation to %s alpha2 testing' % SITENAME
#ASTAKOS_GREETING_EMAIL_SUBJECT = 'Welcome to %s alpha2 testing' % SITENAME
#ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT = 'Email change on %s alpha2 testing' % SITENAME
#ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT = 'Password reset on %s alpha2 testing' % SITENAME
+# Set the quota holder component URI
+#ASTAKOS_QUOTA_HOLDER_URL = ''
+
+# Set the cloud service properties
+# SERVICES = getattr(settings, 'ASTAKOS_SERVICES',
+# {'cyclades': {'url':'https://node1.example.com/ui/',
+# 'quota': {'vm': 2}},
+# 'pithos+': {'url':'https://node2.example.com/ui/',
+# 'quota': {'diskspace': 50 * 1024 * 1024 * 1024}}})
+
+# Set the billing URI
+#ASTAKOS_AQUARIUM_URL = ''
+
+# Set how many objects should be displayed per page
- #PAGINATE_BY = getattr(settings, 'ASTAKOS_PAGINATE_BY', 10)
++#PAGINATE_BY = getattr(settings, 'ASTAKOS_PAGINATE_BY', 10)
++
+ # Enforce token renewal on password change/reset
+ NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)