Revision 3e3743f2

b/snf-astakos-app/astakos/im/functions.py
67 67
from astakos.im.notifications import build_notification, NotificationError
68 68
from astakos.im.models import (
69 69
    AstakosUser, ProjectMembership, ProjectApplication, Project,
70
    sync_projects, PendingMembershipError, get_resource_names)
71
from astakos.im.models import submit_application as models_submit_application
70
    sync_projects, PendingMembershipError, get_resource_names, new_chain)
72 71
from astakos.im.project_notif import (
73 72
    membership_change_notify,
74 73
    application_submit_notify, application_approve_notify,
......
624 623
def submit_application(kw, request_user=None):
625 624

  
626 625
    kw['applicant'] = request_user
626
    resource_policies = kw.pop('resource_policies', None)
627 627

  
628
    precursor = None
628 629
    precursor_id = kw.get('precursor_application', None)
629 630
    if precursor_id is not None:
630 631
        sfu = ProjectApplication.objects.select_for_update()
631 632
        precursor = sfu.get(id=precursor_id)
632 633
        kw['precursor_application'] = precursor
633 634

  
634
        if request_user and \
635
            (not precursor.owner == request_user and \
636
                not request_user.is_superuser):
637
            raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
635
        if (request_user and
636
            (not precursor.owner == request_user and
637
             not request_user.is_superuser)):
638
            m = _(astakos_messages.NOT_ALLOWED)
639
            raise PermissionDenied(m)
638 640

  
639
    application = models_submit_application(**kw)
641
    application = ProjectApplication(**kw)
640 642

  
643
    if precursor is None:
644
        application.chain = new_chain()
645
    else:
646
        chain = precursor.chain
647
        application.chain = chain
648
        sfu = ProjectApplication.objects.select_for_update()
649
        pending = sfu.filter(chain=chain, state=ProjectApplication.PENDING)
650
        for app in pending:
651
            app.state = ProjectApplication.REPLACED
652
            app.save()
653

  
654
    application.save()
655
    application.resource_policies = resource_policies
641 656
    application_submit_notify(application)
642 657
    return application
643 658

  
b/snf-astakos-app/astakos/im/models.py
1231 1231

  
1232 1232
    def user_visible_by_chain(self, flt):
1233 1233
        model = self.model
1234
        pending = self.filter(model.Q_PENDING).values_list('chain')
1234
        pending = self.filter(model.Q_PENDING | model.Q_DENIED).values_list('chain')
1235 1235
        approved = self.filter(model.Q_APPROVED).values_list('chain')
1236 1236
        by_chain = dict(pending.annotate(models.Max('id')))
1237 1237
        by_chain.update(approved.annotate(models.Max('id')))
......
1326 1326
    # Compiled queries
1327 1327
    Q_PENDING  = Q(state=PENDING)
1328 1328
    Q_APPROVED = Q(state=APPROVED)
1329
    Q_DENIED   = Q(state=DENIED)
1329 1330

  
1330 1331
    class Meta:
1331 1332
        unique_together = ("chain", "id")
......
1396 1397
            uplimit = p.get('uplimit', 0)
1397 1398
            self.add_resource_policy(service, resource, uplimit)
1398 1399

  
1399
    def followers(self):
1400
        followers = self.chained_applications()
1401
        followers = followers.exclude(id=self.pk).filter(state=self.PENDING)
1402
        followers = followers.order_by('id')
1403
        return followers
1400
    def pending_modifications(self):
1401
        q = self.chained_applications()
1402
        q = q.filter(~Q(id=self.id) & Q(state=self.PENDING))
1403
        q = q.order_by('id')
1404
        return q
1404 1405

  
1405
    def last_follower(self):
1406
    def last_pending(self):
1406 1407
        try:
1407
            return self.followers().order_by('-id')[0]
1408
            return self.pending_modifications().order_by('-id')[0]
1408 1409
        except IndexError:
1409 1410
            return None
1410 1411

  
1411 1412
    def is_modification(self):
1413
        if self.state != self.PENDING:
1414
            return False
1412 1415
        parents = self.chained_applications().filter(id__lt=self.id)
1413 1416
        parents = parents.filter(state__in=[self.APPROVED])
1414 1417
        return parents.count() > 0
......
1417 1420
        return ProjectApplication.objects.filter(chain=self.chain)
1418 1421

  
1419 1422
    def has_pending_modifications(self):
1420
        return bool(self.last_follower())
1423
        return bool(self.last_pending())
1421 1424

  
1422 1425
    def get_project(self):
1423 1426
        try:
......
1530 1533
    def member_leave_policy_display(self):
1531 1534
        return PROJECT_MEMBER_LEAVE_POLICIES.get(str(self.member_leave_policy))
1532 1535

  
1533
def submit_application(**kw):
1534

  
1535
    resource_policies = kw.pop('resource_policies', None)
1536
    application = ProjectApplication(**kw)
1537

  
1538
    precursor = kw['precursor_application']
1539

  
1540
    if precursor is None:
1541
        application.chain = new_chain()
1542
    else:
1543
        application.chain = precursor.chain
1544
        if precursor.state == ProjectApplication.PENDING:
1545
            precursor.state = ProjectApplication.REPLACED
1546
            precursor.save()
1547

  
1548
    application.save()
1549
    application.resource_policies = resource_policies
1550
    return application
1551

  
1552 1536
class ProjectResourceGrant(models.Model):
1553 1537

  
1554 1538
    resource                =   models.ForeignKey(Resource)
b/snf-astakos-app/astakos/im/tables.py
237 237

  
238 238
        super(UserTable, self).__init__(*args, **kwargs)
239 239

  
240

  
241 240
def project_name_append(application, column):
242 241
    if application.has_pending_modifications():
243 242
        return mark_safe("<br /><i class='tiny'>%s</i>" % \
244
                                                _('modifications pending'))
243
                             _('modifications pending'))
245 244
    return u''
246 245

  
247 246
# Table classes
......
332 331
                            confirm=confirms[i]))
333 332
    return context
334 333

  
335
class ProjectApplicationMembersTable(UserTable):
334
class ProjectMembersTable(UserTable):
336 335
    name = tables.Column(accessor="person.last_name", verbose_name=_('Name'))
337 336
    status = tables.Column(accessor="state", verbose_name=_('Status'))
338 337
    project_action = RichLinkColumn(verbose_name=_('Action'),
......
342 341

  
343 342
    def __init__(self, project, *args, **kwargs):
344 343
        self.project = project
345
        super(ProjectApplicationMembersTable, self).__init__(*args, **kwargs)
344
        super(ProjectMembersTable, self).__init__(*args, **kwargs)
346 345
        if not self.user.owns_project(self.project):
347 346
            self.exclude = ('project_action', )
348 347

  
b/snf-astakos-app/astakos/im/templates/im/projects/project_detail.html
11 11
        <em>
12 12
            {% if user_owns_project %}
13 13
            [ PROJECT {% if object.is_modification %} MODIFICATION {% endif %}{{ object.state_display|upper }} 
14
            {% if object.last_follower %} - 
14
            {% if object.has_pending_modifications %} -
15 15
            <a
16
                href="{% url astakos.im.views.project_detail object.last_follower.pk %}">MODIFICATION PENDING</a>
16
                href="{% url astakos.im.views.project_app object.last_pending.pk %}">MODIFICATION PENDING</a>
17 17
            {% endif %}]
18 18
            {%  else %}
19 19
                {% if user in approved_members %}
......
33 33
        <span>{% if object.is_modification %}
34 34
            <span class="extratitle">MODIFICATION OF </span>{% endif %}{{ object.name|upper }}</span>
35 35
        {% if user_owns_project %}
36
        {% if object.last_follower %}
37
        <br /><a style="font-size:0.7em" href="{% url astakos.im.views.project_update object.pk %}">REQUEST MODIFICATION</a>
38
        {% else %}
39
        <br /><a style="font-size:0.7em" href="{% url astakos.im.views.project_update object.pk %}">MODIFY</a>
40
        {% endif %}
36
        <br /><a style="font-size:0.7em" href="{% url astakos.im.views.project_modify object.pk %}">MODIFY</a>
41 37
        {% else %}
42 38
        {% if member_status == -1 %}
43 39
         - 
......
93 89
		 	<dt>Precursor Application</dt>
94 90
		 	<dd>
95 91
		 	    {% if object.precursor_application %}
96
                    <a href="{% url project_detail object.precursor_application.id %}">{{object.precursor_application.id}}</a>
92
                    <a href="{% url project_app object.precursor_application.id %}">{{object.precursor_application.id}}</a>
97 93
                {% endif %}
98 94
                &nbsp;
99 95
		 	</dd>
100 96
		 	<dt>Follower Application</dt>
101 97
		 	<dd>
102 98
		 	    {% if object.follower %}
103
                    <a href="{% url project_detail object.follower.id %}">{{object.follower.id}}</a>
99
                    <a href="{% url project_app object.follower.id %}">{{object.follower.id}}</a>
104 100
                {% endif %}
105 101
                &nbsp;
106 102
            </dd>
b/snf-astakos-app/astakos/im/urls.py
59 59
#    url(r'^timeline/?$', 'timeline', {}, name='timeline'),
60 60

  
61 61
    url(r'^projects/add/?$', 'project_add', {}, name='project_add'),
62
    url(r'^projects/update/(?P<application_id>\d+)/?$', 'project_update', {}, name='project_update'),
63 62
    url(r'^projects/?$', 'project_list', {}, name='project_list'),
64 63
    url(r'^projects/search/?$', 'project_search', {}, name='project_search'),
65 64
    url(r'^projects/(?P<chain_id>\d+)/?$', 'project_detail', {}, name='project_detail'),
......
68 67
    url(r'^projects/(?P<chain_id>\d+)/(?P<user_id>\d+)/accept/?$', 'project_accept_member', {}, name='project_accept_member'),
69 68
    url(r'^projects/(?P<chain_id>\d+)/(?P<user_id>\d+)/reject/?$', 'project_reject_member', {}, name='project_reject_member'),
70 69
    url(r'^projects/(?P<chain_id>\d+)/(?P<user_id>\d+)/remove/?$', 'project_remove_member', {}, name='project_remove_member'),
70
    url(r'^projects/app/(?P<application_id>\d+)/?$', 'project_app', {}, name='project_app'),
71
    url(r'^projects/app/(?P<application_id>\d+)/modify$', 'project_modify', {}, name='project_modify'),
71 72

  
72 73
    url(r'^projects/how_it_works/?$', 'how_it_works', {}, name='how_it_works'),
73 74
    url(r'^remove_auth_provider/(?P<pk>\d+)?$', 'remove_auth_provider', {}, name='remove_auth_provider'),
b/snf-astakos-app/astakos/im/views.py
1095 1095
@require_http_methods(["GET", "POST"])
1096 1096
@signed_terms_required
1097 1097
@login_required
1098
def project_update(request, application_id):
1098
def project_modify(request, application_id):
1099 1099
    resource_groups = RESOURCES_PRESENTATION_DATA.get('groups', {})
1100 1100
    resource_catalog = ()
1101 1101
    result = callpoint.list_resources()
......
1133 1133
@signed_terms_required
1134 1134
@login_required
1135 1135
@transaction.commit_on_success
1136
def project_app(request, application_id):
1137
    return common_detail(request, application_id, is_chain=False)
1138

  
1139
@require_http_methods(["GET", "POST"])
1140
@signed_terms_required
1141
@login_required
1142
@transaction.commit_on_success
1136 1143
def project_detail(request, chain_id):
1137
    addmembers_form = AddProjectMembersForm()
1138
    if request.method == 'POST':
1139
        addmembers_form = AddProjectMembersForm(
1140
            request.POST,
1141
            chain_id=int(chain_id),
1142
            request_user=request.user)
1143
        if addmembers_form.is_valid():
1144
            try:
1145
                rollback = False
1146
                chain_id = int(chain_id)
1147
                map(lambda u: enroll_member(
1148
                        chain_id,
1149
                        u,
1150
                        request_user=request.user),
1151
                    addmembers_form.valid_users)
1152
            except (IOError, PermissionDenied), e:
1153
                messages.error(request, e)
1154
            except BaseException, e:
1155
                rollback = True
1156
                messages.error(request, e)
1157
            finally:
1158
                if rollback == True:
1159
                    transaction.rollback()
1160
                else:
1161
                    transaction.commit()
1162
            addmembers_form = AddProjectMembersForm()
1144
    return common_detail(request, chain_id)
1145

  
1146
def addmembers(request, chain_id):
1147
    addmembers_form = AddProjectMembersForm(
1148
        request.POST,
1149
        chain_id=int(chain_id),
1150
        request_user=request.user)
1151
    if addmembers_form.is_valid():
1152
        try:
1153
            rollback = False
1154
            chain_id = int(chain_id)
1155
            map(lambda u: enroll_member(
1156
                    chain_id,
1157
                    u,
1158
                    request_user=request.user),
1159
                addmembers_form.valid_users)
1160
        except (IOError, PermissionDenied), e:
1161
            messages.error(request, e)
1162
        except BaseException, e:
1163
            rollback = True
1164
            messages.error(request, e)
1165
        finally:
1166
            if rollback == True:
1167
                transaction.rollback()
1168
            else:
1169
                transaction.commit()
1163 1170

  
1164
    rollback = False
1171
def common_detail(request, common_id, is_chain=True):
1172
    if is_chain:
1173
        if request.method == 'POST':
1174
            addmembers(request, common_id)
1165 1175

  
1166
    project, application = get_by_chain_or_404(chain_id)
1167
    if project:
1168
        members = project.projectmembership_set.select_related()
1169
    else:
1170
        members = ProjectMembership.objects.none()
1176
        addmembers_form = AddProjectMembersForm()
1177

  
1178
        project, application = get_by_chain_or_404(common_id)
1179
        if project:
1180
            members = project.projectmembership_set.select_related()
1181
            members_table = tables.ProjectMembersTable(project,
1182
                                                       members,
1183
                                                       user=request.user,
1184
                                                       prefix="members_")
1185
            RequestConfig(request, paginate={"per_page": PAGINATE_BY}
1186
                          ).configure(members_table)
1187

  
1188
        else:
1189
            members_table = None
1171 1190

  
1172
    members_table = tables.ProjectApplicationMembersTable(project,
1173
                                                          members,
1174
                                                          user=request.user,
1175
                                                          prefix="members_")
1176
    RequestConfig(request, paginate={"per_page": PAGINATE_BY}).configure(members_table)
1191
    else: # is application
1192
        application = get_object_or_404(ProjectApplication, pk=common_id)
1193
        members_table = None
1194
        addmembers_form = None
1177 1195

  
1178 1196
    modifications_table = None
1179 1197

  
1180
    following_applications = list(application.followers())
1198
    following_applications = list(application.pending_modifications())
1181 1199
    following_applications.reverse()
1182
    modifications_table = \
1200
    modifications_table = (
1183 1201
        tables.ProjectModificationApplicationsTable(following_applications,
1184 1202
                                                    user=request.user,
1185
                                                    prefix="modifications_")
1203
                                                    prefix="modifications_"))
1186 1204

  
1187 1205
    return object_detail(
1188 1206
        request,
......
1192 1210
        extra_context={
1193 1211
            'addmembers_form':addmembers_form,
1194 1212
            'members_table': members_table,
1195
            'user_owns_project': request.user.owns_project(project),
1213
            'user_owns_project': request.user.owns_application(application),
1196 1214
            'modifications_table': modifications_table,
1197 1215
            'member_status': application.user_status(request.user)
1198 1216
            })

Also available in: Unified diff