Commit e5caa3e9 authored by Sofia Papagiannaki's avatar Sofia Papagiannaki

Progress I

parent 995d6106
This diff is collapsed.
...@@ -33,19 +33,28 @@ ...@@ -33,19 +33,28 @@
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings
from functools import wraps from functools import wraps
from smtplib import SMTPException from smtplib import SMTPException
from astakos.im.models import ( from astakos.im.models import (
AstakosUser, AstakosGroup, GroupKind, Resource, Service, RESOURCE_SEPARATOR AstakosUser, AstakosGroup, GroupKind, Resource, Service, RESOURCE_SEPARATOR,
Project, ProjectApplication, ProjectMembership, filter_queryset_by_property
) )
from astakos.im.api.backends.base import BaseBackend, SuccessResult, FailureResult from astakos.im.api.backends.base import BaseBackend, SuccessResult, FailureResult
from astakos.im.api.backends.errors import ( from astakos.im.api.backends.errors import (
ItemNotExists, ItemExists, MissingIdentifier, MultipleItemsExist ItemNotExists, ItemExists, MissingIdentifier, MultipleItemsExist
) )
# from astakos.im.api.backends.lib.notifications import EmailNotification
from astakos.im.util import reserved_email, model_to_dict from astakos.im.util import reserved_email, model_to_dict
from astakos.im.endpoints.qh import get_quota from astakos.im.endpoints.qh import get_quota, send_quota
from astakos.im.settings import SITENAME
try:
from astakos.im.messages import astakos_messages
except:
pass
import logging import logging
...@@ -56,7 +65,6 @@ DEFAULT_CONTENT_TYPE = None ...@@ -56,7 +65,6 @@ DEFAULT_CONTENT_TYPE = None
def safe(func): def safe(func):
"""Decorator function for views that implement an API method.""" """Decorator function for views that implement an API method."""
@transaction.commit_manually
@wraps(func) @wraps(func)
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
logger.debug('%s %s %s' % (func, args, kwargs)) logger.debug('%s %s %s' % (func, args, kwargs))
...@@ -64,10 +72,8 @@ def safe(func): ...@@ -64,10 +72,8 @@ def safe(func):
data = func(self, *args, **kwargs) or () data = func(self, *args, **kwargs) or ()
except Exception, e: except Exception, e:
logger.exception(e) logger.exception(e)
transaction.rollback()
return FailureResult(e) return FailureResult(e)
else: else:
transaction.commit()
return SuccessResult(data) return SuccessResult(data)
return wrapper return wrapper
...@@ -107,7 +113,10 @@ class DjangoBackend(BaseBackend): ...@@ -107,7 +113,10 @@ class DjangoBackend(BaseBackend):
q = model.objects.all() q = model.objects.all()
if filter: if filter:
q = q.filter(id__in=filter) q = q.filter(id__in=filter)
return map(lambda o: model_to_dict(o, exclude=[]), q) return map(lambda o: self._details(o), q)
def _details(self, obj):
return model_to_dict(obj, exclude=[])
def _create_object(self, model, **kwargs): def _create_object(self, model, **kwargs):
o = model.objects.create(**kwargs) o = model.objects.create(**kwargs)
...@@ -136,6 +145,7 @@ class DjangoBackend(BaseBackend): ...@@ -136,6 +145,7 @@ class DjangoBackend(BaseBackend):
permissions = kwargs.pop('permissions', ()) permissions = kwargs.pop('permissions', ())
groups = kwargs.pop('groups', ()) groups = kwargs.pop('groups', ())
password = kwargs.pop('password', None) password = kwargs.pop('password', None)
provider = kwargs.pop('provider', 'local')
u = self._create_object(AstakosUser, **kwargs) u = self._create_object(AstakosUser, **kwargs)
...@@ -145,10 +155,10 @@ class DjangoBackend(BaseBackend): ...@@ -145,10 +155,10 @@ class DjangoBackend(BaseBackend):
u.policies = policies u.policies = policies
u.extended_groups = groups u.extended_groups = groups
if not u.has_auth_provider('local'): if not u.has_auth_provider(provider):
u.add_auth_provider('local') u.add_auth_provider(provider)
return self._list(AstakosUser, filter=(u.id,)) return self._details(u)
@safe @safe
def add_policies(self, user_id, update=False, policies=()): def add_policies(self, user_id, update=False, policies=()):
...@@ -180,6 +190,7 @@ class DjangoBackend(BaseBackend): ...@@ -180,6 +190,7 @@ class DjangoBackend(BaseBackend):
except ObjectDoesNotExist, e: except ObjectDoesNotExist, e:
append((service, resource, e)) append((service, resource, e))
return rejected return rejected
@safe @safe
def add_permissions(self, user_id, permissions=()): def add_permissions(self, user_id, permissions=()):
user = self._lookup_user(user_id) user = self._lookup_user(user_id)
...@@ -252,7 +263,7 @@ class DjangoBackend(BaseBackend): ...@@ -252,7 +263,7 @@ class DjangoBackend(BaseBackend):
resources = kwargs.pop('resources', ()) resources = kwargs.pop('resources', ())
s = self._create_object(Service, **kwargs) s = self._create_object(Service, **kwargs)
s.resources = resources s.resources = resources
return self._list(Service, filter=(s.id,)) return self._details(s)
@safe @safe
def remove_services(self, ids=()): def remove_services(self, ids=()):
...@@ -308,4 +319,152 @@ class DjangoBackend(BaseBackend): ...@@ -308,4 +319,152 @@ class DjangoBackend(BaseBackend):
g.policies = policies g.policies = policies
# g.members = members # g.members = members
g.owners = owners g.owners = owners
return self._list(AstakosGroup, filter=(g.id,)) return self._details(g)
@safe
def submit_application(self, **kwargs):
app = self._create_object(ProjectApplication, **kwargs)
notification = build_notification(
settings.SERVER_EMAIL,
[settings.ADMINS],
_(GROUP_CREATION_SUBJECT) % {'group':app.definition.name},
_('An new project application identified by %(serial)s has been submitted.') % app.serial
)
notification.send()
@safe
def list_applications(self):
return self._list(ProjectAppication)
@safe
def approve_application(self, serial):
app = self._lookup_object(ProjectAppication, serial=serial)
notify = False
if not app.precursor_application:
kwargs = {
'application':app,
'creation_date':datetime.now(),
'last_approval_date':datetime.now(),
}
project = self._create_object(Project, **kwargs)
else:
project = app.precursor_application.project
last_approval_date = project.last_approval_date
if project.is_valid:
project.application = app
project.last_approval_date = datetime.now()
project.save()
else:
raise Exception(_(astakos_messages.INVALID_PROJECT) % project.__dict__)
r = _synchonize_project(project.serial)
if not r.is_success:
# revert to precursor
project.appication = app.precursor_application
if project.application:
project.last_approval_date = last_approval_date
project.save()
r = synchonize_project(project.serial)
if not r.is_success:
raise Exception(_(astakos_messages.QH_SYNC_ERROR))
else:
project.last_application_synced = app
project.save()
sender, recipients, subject, message
notification = build_notification(
settings.SERVER_EMAIL,
[project.owner.email],
_('Project application has been approved on %s alpha2 testing' % SITENAME),
_('Your application request %(serial)s has been apporved.')
)
notification.send()
@safe
def list_projects(self, filter_property=None):
if filter_property:
q = filter_queryset_by_property(
Project.objects.all(),
filter_property
)
return map(lambda o: self._details(o), q)
return self._list(Project)
@safe
def add_project_member(self, serial, user_id, request_user):
project = self._lookup_object(Project, serial=serial)
user = self.lookup_user(user_id)
if not project.owner == request_user:
raise Exception(_(astakos_messages.NOT_PROJECT_OWNER))
if not project.is_alive:
raise Exception(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
if len(project.members) + 1 > project.limit_on_members_number:
raise Exception(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
m = self._lookup_object(ProjectMembership, person=user, project=project)
if m.is_accepted:
return
m.is_accepted = True
m.decision_date = datetime.now()
m.save()
notification = build_notification(
settings.SERVER_EMAIL,
[user.email],
_('Your membership on project %(name)s has been accepted.') % project.definition.__dict__,
_('Your membership on project %(name)s has been accepted.') % project.definition.__dict__,
)
notification.send()
@safe
def remove_project_member(self, serial, user_id, request_user):
project = self._lookup_object(Project, serial=serial)
if not project.is_alive:
raise Exception(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
if not project.owner == request_user:
raise Exception(_(astakos_messages.NOT_PROJECT_OWNER))
user = self.lookup_user(user_id)
m = self._lookup_object(ProjectMembership, person=user, project=project)
if not m.is_accepted:
return
m.is_accepted = False
m.decision_date = datetime.now()
m.save()
notification = build_notification(
settings.SERVER_EMAIL,
[user.email],
_('Your membership on project %(name)s has been removed.') % project.definition.__dict__,
_('Your membership on project %(name)s has been removed.') % project.definition.__dict__,
)
notification.send()
@safe
def suspend_project(self, serial):
project = self._lookup_object(Project, serial=serial)
project.suspend()
notification = build_notification(
settings.SERVER_EMAIL,
[project.owner.email],
_('Project %(name)s has been suspended on %s alpha2 testing' % SITENAME),
_('Project %(name)s has been suspended on %s alpha2 testing' % SITENAME)
)
notification.send()
@safe
def terminate_project(self, serial):
project = self._lookup_object(Project, serial=serial)
project.termination()
notification = build_notification(
settings.SERVER_EMAIL,
[project.owner.email],
_('Project %(name)s has been terminated on %s alpha2 testing' % SITENAME),
_('Project %(name)s has been terminated on %s alpha2 testing' % SITENAME)
)
notification.send()
@safe
def synchonize_project(self, serial):
project = self._lookup_object(Project, serial=serial)
project.sync()
\ No newline at end of file
[
{
"model": "im.memberacceptpolicy",
"pk": 1,
"fields": {
"policy": "auto_accept",
"description": "new join requests are automatically accepted by the system"
}
},
{
"model": "im.memberacceptpolicy",
"pk": 2,
"fields": {
"policy": "owner_accepts",
"description": "new join requests must be accepted by the owner of the project"
}
},
{
"model": "im.memberacceptpolicy",
"pk": 3,
"fields": {
"policy": "closed",
"description": "no new members can join the project, even if old ones leave"
}
}
]
...@@ -35,9 +35,11 @@ from random import random ...@@ -35,9 +35,11 @@ from random import random
from django import forms from django import forms
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm, from django.contrib.auth.forms import (
PasswordResetForm, PasswordChangeForm, UserCreationForm, AuthenticationForm,
SetPasswordForm) PasswordResetForm, PasswordChangeForm,
SetPasswordForm
)
from django.core.mail import send_mail from django.core.mail import send_mail
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
from django.template import Context, loader from django.template import Context, loader
...@@ -50,10 +52,12 @@ from django.forms.models import fields_for_model ...@@ -50,10 +52,12 @@ from django.forms.models import fields_for_model
from django.db import transaction from django.db import transaction
from django.utils.encoding import smart_unicode from django.utils.encoding import smart_unicode
from django.core import validators from django.core import validators
from django.contrib.auth.models import AnonymousUser
from astakos.im.models import ( from astakos.im.models import (
AstakosUser, EmailChange, AstakosGroup, Invitation, GroupKind, AstakosUser, EmailChange, AstakosGroup, Invitation, GroupKind,
Resource, PendingThirdPartyUser, get_latest_terms, RESOURCE_SEPARATOR Resource, PendingThirdPartyUser, get_latest_terms, RESOURCE_SEPARATOR,
ProjectDefinition, ProjectApplication, create_application
) )
from astakos.im.settings import ( from astakos.im.settings import (
INVITATIONS_PER_LEVEL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, INVITATIONS_PER_LEVEL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY,
...@@ -902,3 +906,104 @@ class ExtendedSetPasswordForm(SetPasswordForm): ...@@ -902,3 +906,104 @@ class ExtendedSetPasswordForm(SetPasswordForm):
except BaseException, e: except BaseException, e:
logger.exception(e) logger.exception(e)
return super(ExtendedSetPasswordForm, self).save(commit=commit) return super(ExtendedSetPasswordForm, self).save(commit=commit)
class ProjectApplicationForm(forms.ModelForm):
name = forms.CharField(
validators=[validators.RegexValidator(
DOMAIN_VALUE_REGEX,
_(astakos_messages.DOMAIN_VALUE_ERR),
'invalid'
)],
widget=forms.TextInput(attrs={'placeholder': 'eg. foo.ece.ntua.gr'}),
help_text="Name should be in the form of dns"
)
comments = forms.CharField(widget=forms.Textarea, required=False)
class Meta:
model = ProjectDefinition
exclude = ('resource_grants')
def __init__(self, *args, **kwargs):
#update QueryDict
args = list(args)
qd = args.pop(0).copy()
members_unlimited = qd.pop('members_unlimited', False)
members_uplimit = qd.pop('members_uplimit', None)
#substitue QueryDict
args.insert(0, qd)
super(AstakosGroupCreationForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['kind', 'name', 'homepage', 'desc',
'issue_date', 'expiration_date',
'moderation_enabled', 'max_participants']
def add_fields((k, v)):
k = k.partition('_proxy')[0]
self.fields[k] = forms.IntegerField(
required=False,
widget=forms.HiddenInput(),
min_value=1
)
map(add_fields,
((k, v) for k,v in qd.iteritems() if k.endswith('_uplimit'))
)
def add_fields((k, v)):
self.fields[k] = forms.BooleanField(
required=False,
#widget=forms.HiddenInput()
)
map(add_fields,
((k, v) for k,v in qd.iteritems() if k.startswith('is_selected_'))
)
def clean(self):
userid = self.data.get('user', None)[0]
self.user = None
if userid:
try:
self.user = AstakosUser.objects.get(id=userid)
except AstakosUser.DoesNotExist:
pass
if not self.user:
raise forms.ValidationError(_(astakos_messages.NO_APPLICANT))
super(ProjectApplicationForm, self).clean()
return self.cleaned_data
def resource_policies(self, clean=True):
if clean:
self.clean()
policies = []
append = policies.append
for name, uplimit in self.cleaned_data.iteritems():
subs = name.split('_uplimit')
if len(subs) == 2:
prefix, suffix = subs
s, sep, r = prefix.partition(RESOURCE_SEPARATOR)
resource = Resource.objects.get(service__name=s, name=r)
# keep only resource limits for selected resource groups
if self.cleaned_data.get(
'is_selected_%s' % resource.group, False
):
append(dict(service=s, resource=r, uplimit=uplimit))
return policies
def save(self, commit=True):
definition = super(ProjectApplicationForm, self).save(commit=commit)
definition.resource_policies=self.resource_policies(clean=False)
applicant = self.user
comments = self.cleaned_data.pop('comments', None)
try:
precursor_application = self.instance.projectapplication
except:
precursor_application = None
return create_application(
definition,
applicant,
comments,
precursor_application,
commit
)
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
# interpreted as representing official policies, either expressed # interpreted as representing official policies, either expressed
# or implied, of GRNET S.A. # or implied, of GRNET S.A.
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import NoArgsCommand, CommandError
from astakos.im.models import AstakosUser, Resource from astakos.im.models import AstakosUser, Resource
from astakos.im.endpoints.qh import register_users, register_resources from astakos.im.endpoints.qh import register_users, register_resources
...@@ -40,10 +40,10 @@ import logging ...@@ -40,10 +40,10 @@ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Command(BaseCommand): class Command(NoArgsCommand):
help = "Send user information and resource quota in the Quotaholder" help = "Send user information and resource quota in the Quotaholder"
def handle(self, *args, **options): def handle_noargs(self, **options):
try: try:
register_resources(Resource.objects.all()) register_resources(Resource.objects.all())
register_users(AstakosUser.objects.all()) register_users(AstakosUser.objects.all())
......
...@@ -39,12 +39,12 @@ from django.db import models ...@@ -39,12 +39,12 @@ from django.db import models
from astakos.im.models import AstakosGroup from astakos.im.models import AstakosGroup
class Command(BaseCommand): class Command(BaseCommand):
args = "<group name>" args = "<group id or name>"
help = "Show group info" help = "Show group info"
def handle(self, *args, **options): def handle(self, *args, **options):
if len(args) != 1: if len(args) != 1:
raise CommandError("Please provide a group name") raise CommandError("Please provide a group id or name")
group = AstakosGroup.objects group = AstakosGroup.objects
name_or_id = args[0].decode('utf8') name_or_id = args[0].decode('utf8')
......
...@@ -33,17 +33,17 @@ ...@@ -33,17 +33,17 @@
from optparse import make_option from optparse import make_option
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import NoArgsCommand, CommandError
from astakos.im.models import Invitation from astakos.im.models import Invitation
from ._common import format_bool from ._common import format_bool
class Command(BaseCommand): class Command(NoArgsCommand):
help = "List invitations" help = "List invitations"
option_list = BaseCommand.option_list + ( option_list = NoArgsCommand.option_list + (
make_option('-c', make_option('-c',
action='store_true', action='store_true',
dest='csv', dest='csv',
...@@ -51,10 +51,7 @@ class Command(BaseCommand): ...@@ -51,10 +51,7 @@ class Command(BaseCommand):
help="Use pipes to separate values"), help="Use pipes to separate values"),
) )
def handle(self, *args, **options): def handle_noargs(self, **options):
if args:
raise CommandError("Command doesn't accept any arguments")
invitations = Invitation.objects.all().order_by('id') invitations = Invitation.objects.all().order_by('id')
labels = ('id', 'inviter', 'email', 'real name', 'code', 'consumed') labels = ('id', 'inviter', 'email', 'real name', 'code', 'consumed')
......
...@@ -33,15 +33,15 @@ ...@@ -33,15 +33,15 @@
from optparse import make_option from optparse import make_option
from django.core.management.base import BaseCommand from django.core.management.base import NoArgsCommand
from astakos.im.models import Resource from astakos.im.models import Resource
class Command(BaseCommand): class Command(NoArgsCommand):
help = "List resources" help = "List resources"
option_list = BaseCommand.option_list + ( option_list = NoArgsCommand.option_list + (