Commit c77db977 authored by Sofia Papagiannaki's avatar Sofia Papagiannaki

Progress VII

parent a3ac522a
...@@ -319,152 +319,4 @@ class DjangoBackend(BaseBackend): ...@@ -319,152 +319,4 @@ class DjangoBackend(BaseBackend):
g.policies = policies g.policies = policies
# g.members = members # g.members = members
g.owners = owners g.owners = owners
return self._details(g) return self._details(g)
\ No newline at end of file
@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.memberleavepolicy", "model": "im.memberjoinpolicy",
"pk": 1, "pk": 1,
"fields": { "fields": {
"policy": "auto_accept", "policy": "auto_accept",
"description": "remove requests are automatically accepted by the system" "description": "leave requests are automatically accepted by the system"
} }
}, },
{ {
"model": "im.memberleavepolicy", "model": "im.memberjoinpolicy",
"pk": 2, "pk": 2,
"fields": { "fields": {
"policy": "owner_accepts", "policy": "owner_accepts",
"description": "remove requests must be accepted by the owner of the project" "description": "leave requests must be accepted by the owner of the project"
} }
}, },
{ {
"model": "im.memberleavepolicy", "model": "im.memberjoinpolicy",
"pk": 3, "pk": 3,
"fields": { "fields": {
"policy": "closed", "policy": "closed",
"description": "members can not leave the project" "description": "no member can leave the project"
} }
} }
] ]
\ No newline at end of file
[ [
{ {
"model": "im.memberjoinpolicy", "model": "im.memberleavepolicy",
"pk": 1, "pk": 1,
"fields": { "fields": {
"policy": "auto_accept", "policy": "auto_accept",
"description": "leave requests are automatically accepted by the system" "description": "remove requests are automatically accepted by the system"
} }
}, },
{ {
"model": "im.memberjoinpolicy", "model": "im.memberleavepolicy",
"pk": 2, "pk": 2,
"fields": { "fields": {
"policy": "owner_accepts", "policy": "owner_accepts",
"description": "leave requests must be accepted by the owner of the project" "description": "remove requests must be accepted by the owner of the project"
} }
}, },
{ {
"model": "im.memberjoinpolicy", "model": "im.memberleavepolicy",
"pk": 3, "pk": 3,
"fields": { "fields": {
"policy": "closed", "policy": "closed",
"description": "no member can leave the project" "description": "members can not leave the project"
} }
} }
] ]
...@@ -1019,4 +1019,17 @@ class AddProjectMembersForm(forms.Form): ...@@ -1019,4 +1019,17 @@ class AddProjectMembersForm(forms.Form):
try: try:
return self.valid_users return self.valid_users
except: except:
return () return ()
\ No newline at end of file
class ProjectMembersSortForm(forms.Form):
sorting = forms.ChoiceField(
label='Sort by',
choices=(('person__email', 'User Id'),
('person__first_name', 'Name'),
('acceptance_date', 'Acceptance date')
),
required=True
)
class ProjectGroupSearchForm(forms.Form):
q = forms.CharField(max_length=200, label='Search project')
\ No newline at end of file
# Copyright 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
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# 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
# or implied, of GRNET S.A.
from optparse import make_option
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.views.generic.create_update import lookup_object
from django.http import Http404
from astakos.im.models import ProjectApplication
@transaction.commit_on_success
class Command(BaseCommand):
args = "<project application id>"
help = "Update project state"
def handle(self, *args, **options):
if len(args) < 1:
raise CommandError("Please provide a group identifier")
try:
id = int(args[0])
except ValueError:
raise CommandError('Invalid id')
else:
try:
# Is it a project application id?
app = lookup_object(ProjectApplication, id, None, None)
except Http404:
raise CommandError('Invalid id')
try:
app.approve()
except BaseException, e:
raise CommandError(e)
\ No newline at end of file
...@@ -52,7 +52,7 @@ class Command(NoArgsCommand): ...@@ -52,7 +52,7 @@ class Command(NoArgsCommand):
def handle_noargs(self, **options): def handle_noargs(self, **options):
apps = ProjectApplication.objects.select_related().all() apps = ProjectApplication.objects.select_related().all()
labels = ('id', 'name', 'status') labels = ('id', 'name', 'state')
columns = (3, 40, 10) columns = (3, 40, 10)
if not options['csv']: if not options['csv']:
...@@ -65,7 +65,7 @@ class Command(NoArgsCommand): ...@@ -65,7 +65,7 @@ class Command(NoArgsCommand):
fields = ( fields = (
str(app.id), str(app.id),
app.definition.name, app.definition.name,
app.status app.state
) )
if options['csv']: if options['csv']:
......
...@@ -35,20 +35,19 @@ from optparse import make_option ...@@ -35,20 +35,19 @@ from optparse import make_option
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.db import transaction from django.db import transaction
from django.views.generic.create_update import lookup_object
from django.http import Http404
from astakos.im.models import _lookup_object, ProjectApplication, Project from astakos.im.models import (
ProjectApplication, Project, PENDING
)
@transaction.commit_on_success @transaction.commit_on_success
class Command(BaseCommand): class Command(BaseCommand):
args = "<project application id>" args = "<project id>"
help = "Update project state" help = "Update project state"
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('--approve',
action='store_true',
dest='approve',
default=False,
help="Approve group"),
make_option('--terminate', make_option('--terminate',
action='store_true', action='store_true',
dest='terminate', dest='terminate',
...@@ -65,36 +64,20 @@ class Command(BaseCommand): ...@@ -65,36 +64,20 @@ class Command(BaseCommand):
if len(args) < 1: if len(args) < 1:
raise CommandError("Please provide a group identifier") raise CommandError("Please provide a group identifier")
app = None
p = None
try: try:
id = int(args[0]) id = int(args[0])
except ValueError: except ValueError:
raise CommandError('Invalid id') raise CommandError('Invalid id')
else: else:
try: try:
# Is it a project application id? # Is it a project id?
app = _lookup_object(ProjectApplication, id=id) p = lookup_object(Project, id, None, None)
except ProjectApplication.DoesNotExist: except Http404:
try: raise CommandError('Invalid id')
# Is it a project id? else:
p = _lookup_object(Project, id=id)
except Project.DoesNotExist:
raise CommandError('Invalid id')
try:
if options['approve']:
if not app:
raise CommandError('Project application id is required.')
app.approve()
if app and app.status != 'Pending':
p = app.project
if options['terminate']: if options['terminate']:
p.terminate() p.terminate()
if options['suspend']: elif options['suspend']:
p.suspend() p.suspend()
except BaseException, e: except BaseException, e:
import traceback
traceback.print_exc()
raise CommandError(e) raise CommandError(e)
...@@ -47,7 +47,9 @@ EMAIL_CHANGE_REGISTERED = 'Change email request has been regis ...@@ -47,7 +47,9 @@ EMAIL_CHANGE_REGISTERED = 'Change email request has been regis
You are going to receive a verification email in the new address.' You are going to receive a verification email in the new address.'
OBJECT_CREATED = 'The %(verbose_name)s was created successfully.' OBJECT_CREATED = 'The %(verbose_name)s was created successfully.'
MEMBER_JOINED_GROUP = '%(realname)s has been successfully joined the group.' USER_JOINED_GROUP = '%(realname)s has been successfully joined the group.'
USER_LEFT_GROUP = '%(realname)s has been successfully left the group.'
USER_MEMBERSHIP_REJECTED = '%(realname)s\'s request to join the group has been rejected.'
MEMBER_REMOVED = '%(realname)s has been successfully removed from the group.' MEMBER_REMOVED = '%(realname)s has been successfully removed from the group.'
BILLING_ERROR = 'Service response status: %(status)d' BILLING_ERROR = 'Service response status: %(status)d'
LOGOUT_SUCCESS = 'You have successfully logged out.' LOGOUT_SUCCESS = 'You have successfully logged out.'
...@@ -57,10 +59,12 @@ GENERIC_ERROR = 'Something wrong has happened. \ ...@@ -57,10 +59,12 @@ GENERIC_ERROR = 'Something wrong has happened. \
MAX_INVITATION_NUMBER_REACHED = 'There are no invitations left.' MAX_INVITATION_NUMBER_REACHED = 'There are no invitations left.'
GROUP_MAX_PARTICIPANT_NUMBER_REACHED = 'Group maximum participant number has been reached.' GROUP_MAX_PARTICIPANT_NUMBER_REACHED = 'Group maximum participant number has been reached.'
PROJECT_MAX_PARTICIPANT_NUMBER_REACHED = 'Project maximum participant number has been reached.'
NO_APPROVAL_TERMS = 'There are no approval terms.' NO_APPROVAL_TERMS = 'There are no approval terms.'
PENDING_EMAIL_CHANGE_REQUEST = 'There is already a pending change email request.' PENDING_EMAIL_CHANGE_REQUEST = 'There is already a pending change email request.'
OBJECT_CREATED_FAILED = 'The %(verbose_name)s creation failed: %(reason)s.' OBJECT_CREATED_FAILED = 'The %(verbose_name)s creation failed: %(reason)s.'
GROUP_JOIN_FAILURE = 'Failed to join group.' GROUP_JOIN_FAILURE = 'Failed to join group.'
PROJECT_JOIN_FAILURE = 'Failed to join project.'
GROUPKIND_UNKNOWN = 'There is no such a group kind' GROUPKIND_UNKNOWN = 'There is no such a group kind'
NOT_MEMBER = 'User is not member of the group.' NOT_MEMBER = 'User is not member of the group.'
NOT_OWNER = 'User is not a group owner.' NOT_OWNER = 'User is not a group owner.'
...@@ -136,5 +140,9 @@ MEMBER_NUMBER_LIMIT_REACHED = 'Maximum participant number has been ...@@ -136,5 +140,9 @@ MEMBER_NUMBER_LIMIT_REACHED = 'Maximum participant number has been
MEMBER_JOIN_POLICY_CLOSED = 'The project member join policy is cloesd.' MEMBER_JOIN_POLICY_CLOSED = 'The project member join policy is cloesd.'
MEMBER_LEAVE_POLICY_CLOSED = 'The project member leave policy is cloesd.' MEMBER_LEAVE_POLICY_CLOSED = 'The project member leave policy is cloesd.'
NOT_MEMBERSHIP_REQUEST = 'There is no such a membership request.' NOT_MEMBERSHIP_REQUEST = 'There is no such a membership request.'
MEMBERSHIP_REQUEST_EXISTS = 'There is alreary such a membership request.'
NO_APPLICANT = 'Project application requires an applicant. None found.' NO_APPLICANT = 'Project application requires an applicant. None found.'
ADD_PROJECT_MEMBERS_Q_HELP = 'Add comma separated user emails, eg. user1@user.com, user2@user.com' ADD_PROJECT_MEMBERS_Q_HELP = 'Add comma separated user emails, eg. user1@user.com, user2@user.com'
\ No newline at end of file MISSING_IDENTIFIER = 'Missing identifier.'
UNKNOWN_IDENTIFIER = 'Unknown identidier.'
PENDING_MEMBERSHIP_LEAVE = 'Your request is pending acceptio.'
\ No newline at end of file
...@@ -21,15 +21,12 @@ class Migration(SchemaMigration): ...@@ -21,15 +21,12 @@ class Migration(SchemaMigration):
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('person', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.AstakosUser'])), ('person', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.AstakosUser'])),
('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.Project'])), ('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.Project'])),
('request_date', self.gf('django.db.models.fields.DateField')(default=datetime.datetime(2012, 12, 9, 11, 4, 41, 210148))), ('request_date', self.gf('django.db.models.fields.DateField')(default=datetime.datetime(2012, 12, 10, 13, 48, 24, 819868))),
('removal_date', self.gf('django.db.models.fields.DateField')(null=True)), ('removal_date', self.gf('django.db.models.fields.DateField')(null=True)),
('rejection_date', self.gf('django.db.models.fields.DateField')(null=True)), ('rejection_date', self.gf('django.db.models.fields.DateField')(null=True)),
)) ))
db.send_create_signal('im', ['ProjectMembershipHistory']) db.send_create_signal('im', ['ProjectMembershipHistory'])
# Adding unique constraint on 'ProjectMembershipHistory', fields ['person', 'project']
db.create_unique('im_projectmembershiphistory', ['person_id', 'project_id'])
# Adding model 'ProjectApplication' # Adding model 'ProjectApplication'
db.create_table('im_projectapplication', ( db.create_table('im_projectapplication', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
...@@ -39,6 +36,7 @@ class Migration(SchemaMigration): ...@@ -39,6 +36,7 @@ class Migration(SchemaMigration):
('definition', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['im.ProjectDefinition'], unique=True)), ('definition', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['im.ProjectDefinition'], unique=True)),
('issue_date', self.gf('django.db.models.fields.DateTimeField')()), ('issue_date', self.gf('django.db.models.fields.DateTimeField')()),
('precursor_application', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['im.ProjectApplication'], unique=True, null=True, blank=True)), ('precursor_application', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['im.ProjectApplication'], unique=True, null=True, blank=True)),
('state', self.gf('django.db.models.fields.CharField')(default='Unknown', max_length=80)),
)) ))
db.send_create_signal('im', ['ProjectApplication']) db.send_create_signal('im', ['ProjectApplication'])
...@@ -60,8 +58,9 @@ class Migration(SchemaMigration): ...@@ -60,8 +58,9 @@ class Migration(SchemaMigration):
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('person', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.AstakosUser'])), ('person', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.AstakosUser'])),
('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.Project'])), ('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.Project'])),
('request_date', self.gf('django.db.models.fields.DateField')(default=datetime.datetime(2012, 12, 9, 11, 4, 41, 209391))), ('request_date', self.gf('django.db.models.fields.DateField')(default=datetime.datetime(2012, 12, 10, 13, 48, 24, 819045))),
('acceptance_date', self.gf('django.db.models.fields.DateField')(null=True, db_index=True)), ('acceptance_date', self.gf('django.db.models.fields.DateField')(null=True, db_index=True)),
('leave_request_date', self.gf('django.db.models.fields.DateField')(null=True)),
)) ))
db.send_create_signal('im', ['ProjectMembership']) db.send_create_signal('im', ['ProjectMembership'])
...@@ -112,9 +111,6 @@ class Migration(SchemaMigration): ...@@ -112,9 +111,6 @@ class Migration(SchemaMigration):
# Removing unique constraint on 'ProjectMembership', fields ['person', 'project'] # Removing unique constraint on 'ProjectMembership', fields ['person', 'project']
db.delete_unique('im_projectmembership', ['person_id', 'project_id']) db.delete_unique('im_projectmembership', ['person_id', 'project_id'])
# Removing unique constraint on 'ProjectMembershipHistory', fields ['person', 'project']
db.delete_unique('im_projectmembershiphistory', ['person_id', 'project_id'])
# Deleting model 'MemberJoinPolicy' # Deleting model 'MemberJoinPolicy'
db.delete_table('im_memberjoinpolicy') db.delete_table('im_memberjoinpolicy')
...@@ -185,14 +181,14 @@ class Migration(SchemaMigration): ...@@ -185,14 +181,14 @@ class Migration(SchemaMigration):
}, },
'im.approvalterms': { 'im.approvalterms': {
'Meta': {'object_name': 'ApprovalTerms'}, 'Meta': {'object_name': 'ApprovalTerms'},
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 12, 9, 11, 4, 41, 198398)', 'db_index': 'True'}), 'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 12, 10, 13, 48, 24, 808772)', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'location': ('django.db.models.fields.CharField', [], {'max_length': '255'}) 'location': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}, },
'im.astakosgroup': { 'im.astakosgroup': {
'Meta': {'object_name': 'AstakosGroup', '_ormbases': ['auth.Group']}, 'Meta': {'object_name': 'AstakosGroup', '_ormbases': ['auth.Group']},
'approval_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 'approval_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),