Commit e5caa3e9 authored by Sofia Papagiannaki's avatar Sofia Papagiannaki

Progress I

parent 995d6106
This diff is collapsed.
......@@ -33,19 +33,28 @@
from django.db import IntegrityError, transaction
from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings
from functools import wraps
from smtplib import SMTPException
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.errors import (
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.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
......@@ -56,7 +65,6 @@ DEFAULT_CONTENT_TYPE = None
def safe(func):
"""Decorator function for views that implement an API method."""
@transaction.commit_manually
@wraps(func)
def wrapper(self, *args, **kwargs):
logger.debug('%s %s %s' % (func, args, kwargs))
......@@ -64,10 +72,8 @@ def safe(func):
data = func(self, *args, **kwargs) or ()
except Exception, e:
logger.exception(e)
transaction.rollback()
return FailureResult(e)
else:
transaction.commit()
return SuccessResult(data)
return wrapper
......@@ -107,7 +113,10 @@ class DjangoBackend(BaseBackend):
q = model.objects.all()
if 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):
o = model.objects.create(**kwargs)
......@@ -136,6 +145,7 @@ class DjangoBackend(BaseBackend):
permissions = kwargs.pop('permissions', ())
groups = kwargs.pop('groups', ())
password = kwargs.pop('password', None)
provider = kwargs.pop('provider', 'local')
u = self._create_object(AstakosUser, **kwargs)
......@@ -145,10 +155,10 @@ class DjangoBackend(BaseBackend):
u.policies = policies
u.extended_groups = groups
if not u.has_auth_provider('local'):
u.add_auth_provider('local')
if not u.has_auth_provider(provider):
u.add_auth_provider(provider)
return self._list(AstakosUser, filter=(u.id,))
return self._details(u)
@safe
def add_policies(self, user_id, update=False, policies=()):
......@@ -180,6 +190,7 @@ class DjangoBackend(BaseBackend):
except ObjectDoesNotExist, e:
append((service, resource, e))
return rejected
@safe
def add_permissions(self, user_id, permissions=()):
user = self._lookup_user(user_id)
......@@ -252,7 +263,7 @@ class DjangoBackend(BaseBackend):
resources = kwargs.pop('resources', ())
s = self._create_object(Service, **kwargs)
s.resources = resources
return self._list(Service, filter=(s.id,))
return self._details(s)
@safe
def remove_services(self, ids=()):
......@@ -308,4 +319,152 @@ class DjangoBackend(BaseBackend):
g.policies = policies
# g.members = members
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
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,
SetPasswordForm
)
from django.core.mail import send_mail
from django.contrib.auth.tokens import default_token_generator
from django.template import Context, loader
......@@ -50,10 +52,12 @@ from django.forms.models import fields_for_model
from django.db import transaction
from django.utils.encoding import smart_unicode
from django.core import validators
from django.contrib.auth.models import AnonymousUser
from astakos.im.models import (
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 (
INVITATIONS_PER_LEVEL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY,
......@@ -902,3 +906,104 @@ class ExtendedSetPasswordForm(SetPasswordForm):
except BaseException, e:
logger.exception(e)
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 @@
# interpreted as representing official policies, either expressed
# 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.endpoints.qh import register_users, register_resources
......@@ -40,10 +40,10 @@ import logging
logger = logging.getLogger(__name__)
class Command(BaseCommand):
class Command(NoArgsCommand):
help = "Send user information and resource quota in the Quotaholder"
def handle(self, *args, **options):
def handle_noargs(self, **options):
try:
register_resources(Resource.objects.all())
register_users(AstakosUser.objects.all())
......
......@@ -39,12 +39,12 @@ from django.db import models
from astakos.im.models import AstakosGroup
class Command(BaseCommand):
args = "<group name>"
args = "<group id or name>"
help = "Show group info"
def handle(self, *args, **options):
if len(args) != 1:
raise CommandError("Please provide a group name")
raise CommandError("Please provide a group id or name")
group = AstakosGroup.objects
name_or_id = args[0].decode('utf8')
......
......@@ -33,17 +33,17 @@
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 ._common import format_bool
class Command(BaseCommand):
class Command(NoArgsCommand):
help = "List invitations"
option_list = BaseCommand.option_list + (
option_list = NoArgsCommand.option_list + (
make_option('-c',
action='store_true',
dest='csv',
......@@ -51,10 +51,7 @@ class Command(BaseCommand):
help="Use pipes to separate values"),
)
def handle(self, *args, **options):
if args:
raise CommandError("Command doesn't accept any arguments")
def handle_noargs(self, **options):
invitations = Invitation.objects.all().order_by('id')
labels = ('id', 'inviter', 'email', 'real name', 'code', 'consumed')
......
......@@ -33,15 +33,15 @@
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
class Command(BaseCommand):
class Command(NoArgsCommand):
help = "List resources"
option_list = BaseCommand.option_list + (
option_list = NoArgsCommand.option_list + (
make_option('-c',
action='store_true',
dest='csv',
......@@ -49,7 +49,7 @@ class Command(BaseCommand):
help="Use pipes to separate values"),
)
def handle(self, *args, **options):
def handle_noargs(self, **options):
resources = Resource.objects.select_related().all()
labels = ('id', 'service', 'name')
......
......@@ -58,10 +58,6 @@ class Command(BaseCommand):
dest='password',
metavar='PASSWORD',
help="Set user's password"),
make_option('--provider',
dest='provider',
metavar='PROVIDER',
help="Set user's provider"),
make_option('--renew-token',
action='store_true',
dest='renew_token',
......@@ -189,10 +185,6 @@ class Command(BaseCommand):
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()
......
......@@ -113,11 +113,11 @@ MISSING_NEXT_PARAMETER = 'No next parameter'
INVITATION_SENT = 'Invitation sent to %(email)s.'
VERIFICATION_SENT = 'Verification sent.'
SWITCH_ACCOUNT_LINK_SENT = 'This email is already associated with another local account. \
To change this account to a shibboleth one follow the link in the verification email sent to %(email)s. \
Otherwise just ignore it.'
To change this account to a shibboleth one follow the link in the verification email sent to %(email)s. \
Otherwise just ignore it.'
NOTIFICATION_SENT = 'Your request for an account was successfully received and is now pending approval. \
You will be notified by email in the next few days. \
Thanks for your interest in ~okeanos! The GRNET team.'
You will be notified by email in the next few days. \
Thanks for your interest in ~okeanos! The GRNET team.'
ACTIVATION_SENT = 'Activation sent.'
REGISTRATION_COMPLETED = 'Registration completed. You can now login.'
......@@ -126,4 +126,11 @@ NO_RESPONSE = 'There is no response.'
NOT_ALLOWED_NEXT_PARAM = 'Not allowed next parameter.'
MISSING_KEY_PARAMETER = 'Missing key parameter.'
INVALID_KEY_PARAMETER = 'Invalid key.'
DOMAIN_VALUE_ERR = 'Enter a valid domain.'
\ No newline at end of file
DOMAIN_VALUE_ERR = 'Enter a valid domain.'
QH_SYNC_ERROR = 'Failed to get synchronized with quotaholder.'
UNIQUE_PROJECT_NAME_CONSTRAIN_ERR = 'The project name (as specified in its application\'s definition) must be unique among all active projects.'
INVALID_PROJECT = 'Project %(serial)s is invalid.'
NOT_ALIVE_PROJECT = 'Project %(serial)s is not alive.'
NOT_PROJECT_OWNER = 'Only project owner can perform this action.'
MEMBER_NUMBER_LIMIT_REACHED = 'Maximum participant number has been reached.'
NO_APPLICANT = 'Project application requires an applicant. None found.'
\ No newline at end of file
......@@ -9,70 +9,14 @@ import logging
logger = logging.getLogger(__name__)
class Migration(DataMigration):
"Obsolete migration."
def forwards(self, orm):
"Write your forwards methods here."
try:
default = orm.AstakosGroup.objects.get(name='default')
except orm.AstakosGroup.DoesNotExist:
return
def create_policies(args):
sn, dict = args
url = dict.get('url')
resources = dict.get('resources') or ()
s, created = orm.Service.objects.get_or_create(
name=sn,
defaults={'url': url}
)
for r in resources:
try:
rn = r.pop('name', '')
uplimit = r.pop('uplimit', None)
r, created = orm.Resource.objects.get_or_create(
service=s,
name=rn,
defaults=r)
except Exception, e:
print "Cannot create resource ", rn
continue
else:
q, created = orm.AstakosGroupQuota.objects.get_or_create(
group=default,
resource=r,
defaults={
'uplimit':uplimit,
}
)
map(create_policies, SERVICES.iteritems())
pass
def backwards(self, orm):
try:
default = orm.AstakosGroup.objects.get(name='default')
except orm.AstakosGroup.DoesNotExist:
return
def destroy_policies(args):
sn, dict = args
url = dict.get('url')
resources = dict.get('resources') or ()
for r in resources:
rn = r.get('name', '')
try:
q = orm.AstakosGroupQuota.objects.get(
group=default,
resource__name=rn)
q.delete()
q = orm.Resource.objects.get(service__name=sn, name=rn)
q.delete()
except Exception, e:
print "Cannot create resource ", rn
continue
map(destroy_policies, SERVICES.iteritems())
pass
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
......