Commit 56327f5a authored by Kostas Papadimitriou's avatar Kostas Papadimitriou
Browse files

Merge branch 'latest-quota' into feature-astakos-tables

Conflicts:
	snf-astakos-app/astakos/im/models.py
	snf-astakos-app/astakos/im/views.py
parents cd9b4efc 8c89ca91
......@@ -234,7 +234,9 @@ class DjangoBackend(BaseBackend):
@safe
def get_resource_usage(self, user_id):
user = self._lookup_user(user_id)
data = get_quota((user,))
data = get_quota(user)
if not data:
return ()
resources = []
append = resources.append
for t in data:
......@@ -244,11 +246,18 @@ class DjangoBackend(BaseBackend):
service, sep, resource = name.partition(RESOURCE_SEPARATOR)
resource = Resource.objects.select_related().get(
service__name=service, name=resource)
currValue=quantity + imported - released - exported + returned
d = dict(name=name,
description=resource.desc,
unit=resource.unit or '',
help_text=resource.help_text,
help_text_input_each=resource.help_text_input_each,
is_abbreviation=resource.is_abbreviation,
report_desc=resource.report_desc,
placeholder=resource.placeholder,
verbose_name=resource.verbose_name,
maxValue=quantity + capacity,
currValue=quantity + imported - released - exported + returned)
currValue=currValue)
append(d)
return resources
......@@ -302,4 +311,4 @@ class DjangoBackend(BaseBackend):
# TODO return information for unknown ids
q = Resource.objects.filter(service__id=service_id,
id__in=ids)
q.delete()
\ No newline at end of file
q.delete()
......@@ -100,6 +100,11 @@ class AuthProvider(object):
if override:
setattr(self, tpl_name, override)
for key in ['one_per_user']:
override = self.get_setting(key)
if override != None:
setattr(self, key, override)
def __getattr__(self, key):
if not key.startswith('get_'):
return super(AuthProvider, self).__getattribute__(key)
......@@ -118,6 +123,7 @@ class AuthProvider(object):
attr_sec = 'ASTAKOS_%s_%s' % (self.module.upper(), name.upper())
if not hasattr(settings, attr):
return getattr(settings, attr_sec, default)
return getattr(settings, attr, default)
def is_available_for_login(self):
......
......@@ -65,7 +65,7 @@ from astakos.im.settings import (
from astakos.im.widgets import DummyWidget, RecaptchaWidget
from astakos.im.functions import send_change_email, submit_application
from astakos.im.util import reserved_email, get_query
from astakos.im.util import reserved_email, get_query, model_to_dict
from astakos.im import auth_providers
import astakos.im.messages as astakos_messages
......@@ -687,10 +687,12 @@ class ProjectApplicationForm(forms.ModelForm):
if self.data.get(
'is_selected_%s' % resource.group, "0"
) == "1":
d = model_to_dict(resource)
if uplimit:
append(dict(service=s, resource=r, uplimit=uplimit))
d.update(dict(service=s, resource=r, uplimit=uplimit))
else:
append(dict(service=s, resource=r, uplimit=None))
d.update(dict(service=s, resource=r, uplimit=None))
append(d)
return policies
......
......@@ -418,6 +418,13 @@ def get_project_by_application_id(project_application_id):
raise IOError(
_(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
def get_project_id_of_application_id(project_application_id):
try:
return Project.objects.get(application__id=project_application_id).id
except Project.DoesNotExist:
raise IOError(
_(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
def get_project_by_id(project_id):
try:
return Project.objects.get(id=project_id)
......@@ -425,6 +432,13 @@ def get_project_by_id(project_id):
raise IOError(
_(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
def get_project_for_update(project_id):
try:
return Project.objects.select_for_update().get(id=project_id)
except Project.DoesNotExist:
raise IOError(
_(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
def get_user_by_id(user_id):
try:
return AstakosUser.objects.get(id=user_id)
......@@ -433,7 +447,7 @@ def get_user_by_id(user_id):
def create_membership(project, user):
if isinstance(project, int):
project = get_project_by_application_id(project)
project = get_project_by_id(project)
if isinstance(user, int):
user = get_user_by_id(user)
m = ProjectMembership(
......@@ -447,36 +461,41 @@ def create_membership(project, user):
else:
return m
def get_membership(project, user):
def get_membership_for_update(project, user):
if isinstance(project, int):
project = get_project_by_application_id(project)
project = get_project_by_id(project)
if isinstance(user, int):
user = get_user_by_id(user)
try:
return ProjectMembership.objects.select_related().get(
return ProjectMembership.objects.select_for_update().get(
project=project,
person=user)
except ProjectMembership.DoesNotExist:
raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
def accept_membership(project, user, request_user=None):
def accept_membership(project_application_id, user, request_user=None):
"""
Raises:
django.core.exceptions.PermissionDenied
IOError
"""
membership = get_membership(project, user)
project_id = get_project_id_of_application_id(project_application_id)
return do_accept_membership(project_id, user, request_user)
def do_accept_membership(project_id, user, request_user=None):
project = get_project_for_update(project_id)
if request_user and \
(not membership.project.application.owner == request_user and \
(not project.application.owner == request_user and \
not request_user.is_superuser):
raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
if not membership.project.is_alive:
if not project.is_alive:
raise PermissionDenied(
_(astakos_messages.NOT_ALIVE_PROJECT) % membership.project.__dict__)
if len(membership.project.approved_members) + 1 > \
membership.project.application.limit_on_members_number:
_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
if project.violates_members_limit(adding=1):
raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
membership = get_membership_for_update(project, user)
membership.accept()
trigger_sync()
......@@ -484,56 +503,68 @@ def accept_membership(project, user, request_user=None):
notification = build_notification(
settings.SERVER_EMAIL,
[membership.person.email],
_(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % membership.project.__dict__,
_(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % project.__dict__,
template='im/projects/project_membership_change_notification.txt',
dictionary={'object':membership.project.application, 'action':'accepted'})
dictionary={'object':project.application, 'action':'accepted'})
notification.send()
except NotificationError, e:
logger.error(e.message)
return membership
def reject_membership(project, user, request_user=None):
def reject_membership(project_application_id, user, request_user=None):
"""
Raises:
django.core.exceptions.PermissionDenied
IOError
"""
membership = get_membership(project, user)
project_id = get_project_id_of_application_id(project_application_id)
return do_reject_membership(project_id, user, request_user)
def do_reject_membership(project_id, user, request_user=None):
project = get_project_for_update(project_id)
if request_user and \
(not membership.project.application.owner == request_user and \
(not project.application.owner == request_user and \
not request_user.is_superuser):
raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
if not membership.project.is_alive:
raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % membership.project.__dict__)
if not project.is_alive:
raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
membership = get_membership_for_update(project, user)
membership.reject()
try:
notification = build_notification(
settings.SERVER_EMAIL,
[membership.person.email],
_(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % membership.project.__dict__,
_(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % project.__dict__,
template='im/projects/project_membership_change_notification.txt',
dictionary={'object':membership.project.application, 'action':'rejected'})
dictionary={'object':project.application, 'action':'rejected'})
notification.send()
except NotificationError, e:
logger.error(e.message)
return membership
def remove_membership(project, user, request_user=None):
def remove_membership(project_application_id, user, request_user=None):
"""
Raises:
django.core.exceptions.PermissionDenied
IOError
"""
membership = get_membership(project, user)
project_id = get_project_id_of_application_id(project_application_id)
return do_remove_membership(project_id, user, request_user)
def do_remove_membership(project_id, user, request_user=None):
project = get_project_for_update(project_id)
if request_user and \
(not membership.project.application.owner == request_user and \
(not project.application.owner == request_user and \
not request_user.is_superuser):
raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
if not membership.project.is_alive:
raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % membership.project.__dict__)
if not project.is_alive:
raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
membership = get_membership_for_update(project, user)
membership.remove()
trigger_sync()
......@@ -541,31 +572,43 @@ def remove_membership(project, user, request_user=None):
notification = build_notification(
settings.SERVER_EMAIL,
[membership.person.email],
_(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % membership.project.__dict__,
_(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % project.__dict__,
template='im/projects/project_membership_change_notification.txt',
dictionary={'object':membership.project.application, 'action':'removed'})
dictionary={'object':project.application, 'action':'removed'})
notification.send()
except NotificationError, e:
logger.error(e.message)
return membership
def enroll_member(project, user, request_user=None):
membership = create_membership(project, user)
accept_membership(project, user, request_user)
def enroll_member(project_application_id, user, request_user=None):
project_id = get_project_id_of_application_id(project_application_id)
return do_enroll_member(project_id, user, request_user)
def do_enroll_member(project_id, user, request_user=None):
membership = create_membership(project_id, user)
return do_accept_membership(project_id, user, request_user)
def leave_project(project_application_id, user_id):
"""
Raises:
django.core.exceptions.PermissionDenied
IOError
"""
project = get_project_by_application_id(project_application_id)
project_id = get_project_id_of_application_id(project_application_id)
return do_leave_project(project_id, user_id)
def do_leave_project(project_id, user_id):
project = get_project_for_update(project_id)
if not project.is_alive:
m = _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__
raise PermissionDenied(m)
leave_policy = project.application.member_leave_policy
print '>>>', leave_policy, get_closed_leave_policy()
if leave_policy == get_closed_leave_policy():
raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
membership = get_membership(project_application_id, user_id)
membership = get_membership_for_update(project, user_id)
if leave_policy == get_auto_accept_leave_policy():
membership.remove()
trigger_sync()
......@@ -580,18 +623,28 @@ def join_project(project_application_id, user_id):
django.core.exceptions.PermissionDenied
IOError
"""
project = get_project_by_application_id(project_application_id)
project_id = get_project_id_of_application_id(project_application_id)
return do_join_project(project_id, user_id)
def do_join_project(project_id, user_id):
project = get_project_for_update(project_id)
if not project.is_alive:
m = _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__
raise PermissionDenied(m)
join_policy = project.application.member_join_policy
if join_policy == get_closed_join_policy():
raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
membership = create_membership(project_application_id, user_id)
membership = create_membership(project, user_id)
if join_policy == get_auto_accept_join_policy():
if (join_policy == get_auto_accept_join_policy() and
not project.violates_members_limit(adding=1)):
membership.accept()
trigger_sync()
return membership
def submit_application(
application, resource_policies, applicant, comments, precursor_application=None):
......@@ -610,10 +663,33 @@ def submit_application(
logger.error(e)
return application
def approve_application(application):
def update_application(app_id, **kw):
app = ProjectApplication.objects.get(id=app_id)
app.id = None
app.state = app.PENDING
app.precursor_application_id = app_id
app.issue_date = datetime.now()
resource_policies = kw.pop('resource_policies', None)
for k, v in kw:
setattr(app, k, v)
app.save()
app.resource_policies = resource_policies
return app.id
def approve_application(app):
app_id = app if isinstance(app, int) else app.id
try:
objects = ProjectApplication.objects.select_for_update()
application = objects.get(id=app_id)
except ProjectApplication.DoesNotExist:
raise PermissionDenied()
application.approve()
trigger_sync()
try:
notification = build_notification(
settings.SERVER_EMAIL,
......
......@@ -66,16 +66,13 @@ from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
from astakos.im.settings import (
DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
AUTH_TOKEN_DURATION, BILLING_FIELDS,
EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL,
SITENAME, SERVICES, MODERATION_ENABLED)
AUTH_TOKEN_DURATION, EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL,
SITENAME, SERVICES, MODERATION_ENABLED, RESOURCES_PRESENTATION_DATA)
from astakos.im import settings as astakos_settings
from astakos.im.endpoints.qh import (
register_users, register_resources, qh_add_quota, QuotaLimits,
qh_query_serials, qh_ack_serials)
from astakos.im import auth_providers
#from astakos.im.endpoints.aquarium.producer import report_user_event
#from astakos.im.tasks import propagate_groupmembers_quota
import astakos.im.messages as astakos_messages
from .managers import ForUpdateManager
......@@ -152,6 +149,15 @@ class ResourceMetadata(models.Model):
key = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
value = models.CharField(_('Value'), max_length=255)
_presentation_data = {}
def get_presentation(resource):
global _presentation_data
presentation = _presentation_data.get(resource, {})
if not presentation:
resource_presentation = RESOURCES_PRESENTATION_DATA.get('resources', {})
presentation = resource_presentation.get(resource, {})
_presentation_data[resource] = presentation
return presentation
class Resource(models.Model):
name = models.CharField(_('Name'), max_length=255)
......@@ -167,6 +173,31 @@ class Resource(models.Model):
def __str__(self):
return '%s%s%s' % (self.service, RESOURCE_SEPARATOR, self.name)
@property
def help_text(self):
return get_presentation(str(self)).get('help_text', '')
@property
def help_text_input_each(self):
return get_presentation(str(self)).get('help_text_input_each', '')
@property
def is_abbreviation(self):
return get_presentation(str(self)).get('is_abbreviation', False)
@property
def report_desc(self):
return get_presentation(str(self)).get('report_desc', '')
@property
def placeholder(self):
return get_presentation(str(self)).get('placeholder', '')
@property
def verbose_name(self):
return get_presentation(str(self)).get('verbose_name', '')
_default_quota = {}
def get_default_quota():
global _default_quota
......@@ -320,7 +351,6 @@ class AstakosUser(User):
grants = p.application.projectresourcegrant_set.all()
for g in grants:
d[str(g.resource)] += g.member_capacity or inf
# TODO set default for remaining
return d
@property
......@@ -1097,7 +1127,7 @@ def make_synced(prefix='sync', name='SyncedState'):
SyncedState = make_synced(prefix='sync', name='SyncedState')
class ProjectApplicationManager(models.Manager):
class ProjectApplicationManager(ForUpdateManager):
def user_projects(self, user):
"""
......@@ -1151,6 +1181,7 @@ class ProjectApplication(models.Model):
comments = models.TextField(null=True, blank=True)
issue_date = models.DateTimeField()
objects = ProjectApplicationManager()
def add_resource_policy(self, service, resource, uplimit):
......@@ -1229,7 +1260,8 @@ class ProjectApplication(models.Model):
precursor = self
while precursor:
try:
project = precursor.project
objects = Project.objects.select_for_update()
project = objects.get(application=precursor)
return project
except Project.DoesNotExist:
pass
......@@ -1336,14 +1368,16 @@ class Project(models.Model):
db_index=True,
unique=True)
objects = ForUpdateManager()
@property
def violated_resource_grants(self):
return False
@property
def violated_members_number_limit(self):
def violates_members_limit(self, adding=0):
application = self.application
return len(self.approved_members) > application.limit_on_members_number
return (len(self.approved_members) + adding >
application.limit_on_members_number)
@property
def is_terminated(self):
......@@ -1495,8 +1529,8 @@ class ProjectMembership(models.Model):
history_item = ProjectMembershipHistory(
serial=self.id,
person=self.person,
project=self.project,
person=self.person.uuid,
project=self.project_id,
date=date or datetime.now(),
reason=reason)
history_item.save()
......@@ -1694,6 +1728,8 @@ def sync_projects():
def trigger_sync(retries=3, retry_wait=1.0):
transaction.commit()
cursor = connection.cursor()
locked = True
try:
......@@ -1712,7 +1748,6 @@ def trigger_sync(retries=3, retry_wait=1.0):
return False
sleep(retry_wait)
transaction.commit()
sync_projects()
return True
......@@ -1726,8 +1761,8 @@ class ProjectMembershipHistory(models.Model):
reasons_list = ['ACCEPT', 'REJECT', 'REMOVE']
reasons = dict((k, v) for v, k in enumerate(reasons_list))
person = models.ForeignKey(AstakosUser)
project = models.ForeignKey(Project)
person = models.CharField(max_length=255)
project = models.BigIntegerField()
date = models.DateField(default=datetime.now)
reason = models.IntegerField()
serial = models.BigIntegerField()
......@@ -1762,39 +1797,19 @@ def user_post_save(sender, instance, created, **kwargs):
create_astakos_user(instance)
post_save.connect(user_post_save, sender=User)
def astakosuser_pre_save(sender, instance, **kwargs):
instance.aquarium_report = False
instance.new = False
try:
db_instance = AstakosUser.objects.get(id=instance.id)
except AstakosUser.DoesNotExist:
# create event
instance.aquarium_report = True
instance.new = True
else:
get = AstakosUser.__getattribute__
l = filter(lambda f: get(db_instance, f) != get(instance, f),
BILLING_FIELDS)
instance.aquarium_report = True if l else False
pre_save.connect(astakosuser_pre_save, sender=AstakosUser)
def astakosuser_post_save(sender, instance, created, **kwargs):
if instance.aquarium_report:
report_user_event(instance, create=instance.new)
if not created:
return
# TODO handle socket.error & IOError
register_users((instance,))
post_save.connect(astakosuser_post_save, sender=AstakosUser)
def resource_post_save(sender, instance, created, **kwargs):
if not created:
return
register_resources((instance,))
post_save.connect(resource_post_save, sender=Resource)
def renew_token(sender, instance, **kwargs):
if not instance.auth_token:
instance.renew_token()
......
......@@ -519,6 +519,4 @@ form.withlabels.hidden-submit { margin-bottom:4em; }
/* login section */
.login-section {}
.main-login-method { margin-bottom: 20px;}
.1quotas-form span.info { z-index:2; }
\ No newline at end of file
.main-login-method { margin-bottom: 20px;}
\ No newline at end of file
......@@ -97,6 +97,7 @@ $(document).ready(function() {
});
// if you fill _proxy fields do stuff
$('.quotas-form .quota input[type="text"]').change(function () {
......@@ -107,12 +108,13 @@ $(document).ready(function() {
// get value from input
var value = $(this).val();
//get input name without _proxy
hidden_name = $(this).attr('name').replace("_proxy","");
var hidden_input = $("input[name='"+hidden_name+"']");