Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views / projects.py @ 362dadaa

History | View | Annotate | Download (29.9 kB)

1
# Copyright 2011, 2012, 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import logging
35
import inflect
36

    
37
engine = inflect.engine()
38

    
39
from django_tables2 import RequestConfig
40

    
41
from django.shortcuts import get_object_or_404
42
from django.contrib import messages
43
from django.core.urlresolvers import reverse
44
from django.http import Http404, HttpResponse
45
from django.shortcuts import redirect
46
from django.utils.html import escape
47
from django.utils.translation import ugettext as _
48
from django.views.generic.list_detail import object_list, object_detail
49
from django.core.exceptions import PermissionDenied
50
from django.views.decorators.http import require_http_methods
51
from django.db import transaction
52

    
53
import astakos.im.messages as astakos_messages
54

    
55
from astakos.im import tables
56
from astakos.im.models import ProjectApplication, ProjectMembership, Project
57
from astakos.im.util import get_context, restrict_next
58
from astakos.im.forms import ProjectApplicationForm, AddProjectMembersForm, \
59
    ProjectSearchForm
60
from astakos.im.functions import check_pending_app_quota, accept_membership, \
61
    reject_membership, remove_membership, cancel_membership, leave_project, \
62
    join_project, enroll_member, can_join_request, can_leave_request, \
63
    get_related_project_id, approve_application, \
64
    deny_application, cancel_application, dismiss_application, ProjectError
65
from astakos.im import settings
66
from astakos.im.util import redirect_back
67
from astakos.im.views.util import render_response, _create_object, \
68
    _update_object, _resources_catalog, ExceptionHandler
69
from astakos.im.views.decorators import cookie_fix, signed_terms_required,\
70
    valid_astakos_user_required, login_required
71

    
72
logger = logging.getLogger(__name__)
73

    
74

    
75
@cookie_fix
76
def how_it_works(request):
77
    return render_response(
78
        'im/how_it_works.html',
79
        context_instance=get_context(request))
80

    
81

    
82
@require_http_methods(["GET", "POST"])
83
@cookie_fix
84
@valid_astakos_user_required
85
def project_add(request):
86
    user = request.user
87
    if not user.is_project_admin():
88
        ok, limit = check_pending_app_quota(user)
89
        if not ok:
90
            m = _(astakos_messages.PENDING_APPLICATION_LIMIT_ADD) % limit
91
            messages.error(request, m)
92
            next = reverse('astakos.im.views.project_list')
93
            next = restrict_next(next, domain=settings.COOKIE_DOMAIN)
94
            return redirect(next)
95

    
96
    details_fields = ["name", "homepage", "description", "start_date",
97
                      "end_date", "comments"]
98
    membership_fields = ["member_join_policy", "member_leave_policy",
99
                         "limit_on_members_number"]
100
    resource_catalog, resource_groups = _resources_catalog()
101
    if resource_catalog is False:
102
        # on fail resource_groups contains the result object
103
        result = resource_groups
104
        messages.error(request, 'Unable to retrieve system resources: %s' %
105
                       result.reason)
106
    extra_context = {
107
        'resource_catalog': resource_catalog,
108
        'resource_groups': resource_groups,
109
        'show_form': True,
110
        'details_fields': details_fields,
111
        'membership_fields': membership_fields}
112

    
113
    response = None
114
    with ExceptionHandler(request):
115
        response = create_app_object(request, extra_context=extra_context)
116

    
117
    if response is not None:
118
        return response
119

    
120
    next = reverse('astakos.im.views.project_list')
121
    next = restrict_next(next, domain=settings.COOKIE_DOMAIN)
122
    return redirect(next)
123

    
124

    
125
@transaction.commit_on_success
126
def create_app_object(request, extra_context=None):
127
    try:
128
        summary = 'im/projects/projectapplication_form_summary.html'
129
        return _create_object(
130
            request,
131
            template_name='im/projects/projectapplication_form.html',
132
            summary_template_name=summary,
133
            extra_context=extra_context,
134
            post_save_redirect=reverse('project_list'),
135
            form_class=ProjectApplicationForm,
136
            msg=_("The %(verbose_name)s has been received and "
137
                  "is under consideration."))
138
    except ProjectError as e:
139
        messages.error(request, e)
140

    
141

    
142
def get_user_projects_table(projects, user, prefix):
143
    apps = ProjectApplication.objects.pending_per_project(projects)
144
    memberships = user.projectmembership_set.one_per_project()
145
    objs = ProjectMembership.objects
146
    accepted_ms = objs.any_accepted_per_project(projects)
147
    requested_ms = objs.requested_per_project(projects)
148
    return tables.UserProjectsTable(projects, user=user,
149
                                    prefix=prefix,
150
                                    pending_apps=apps,
151
                                    memberships=memberships,
152
                                    accepted=accepted_ms,
153
                                    requested=requested_ms)
154

    
155

    
156
@require_http_methods(["GET"])
157
@cookie_fix
158
@valid_astakos_user_required
159
def project_list(request):
160
    projects = Project.objects.user_accessible_projects(request.user)
161
    table = (get_user_projects_table(projects, user=request.user,
162
                                     prefix="my_projects_")
163
             if list(projects) else None)
164

    
165
    return object_list(
166
        request,
167
        projects,
168
        template_name='im/projects/project_list.html',
169
        extra_context={
170
            'is_search': False,
171
            'table': table,
172
        })
173

    
174

    
175
@require_http_methods(["POST"])
176
@cookie_fix
177
@valid_astakos_user_required
178
def project_app_cancel(request, application_id):
179
    next = request.GET.get('next')
180
    chain_id = None
181

    
182
    with ExceptionHandler(request):
183
        chain_id = _project_app_cancel(request, application_id)
184

    
185
    if not next:
186
        if chain_id:
187
            next = reverse('astakos.im.views.project_detail', args=(chain_id,))
188
        else:
189
            next = reverse('astakos.im.views.project_list')
190

    
191
    next = restrict_next(next, domain=settings.COOKIE_DOMAIN)
192
    return redirect(next)
193

    
194

    
195
@transaction.commit_on_success
196
def _project_app_cancel(request, application_id):
197
    chain_id = None
198
    try:
199
        application_id = int(application_id)
200
        chain_id = get_related_project_id(application_id)
201
        cancel_application(chain_id, application_id, request.user)
202
    except ProjectError as e:
203
        messages.error(request, e)
204

    
205
    else:
206
        msg = _(astakos_messages.APPLICATION_CANCELLED)
207
        messages.success(request, msg)
208
        return chain_id
209

    
210

    
211
@require_http_methods(["GET", "POST"])
212
@cookie_fix
213
@valid_astakos_user_required
214
def project_modify(request, application_id):
215

    
216
    try:
217
        app = ProjectApplication.objects.get(id=application_id)
218
    except ProjectApplication.DoesNotExist:
219
        raise Http404
220

    
221
    user = request.user
222
    if not (user.owns_application(app) or user.is_project_admin(app.id)):
223
        m = _(astakos_messages.NOT_ALLOWED)
224
        raise PermissionDenied(m)
225

    
226
    if not user.is_project_admin():
227
        owner = app.owner
228
        ok, limit = check_pending_app_quota(owner, project=app.chain)
229
        if not ok:
230
            m = _(astakos_messages.PENDING_APPLICATION_LIMIT_MODIFY) % limit
231
            messages.error(request, m)
232
            next = reverse('astakos.im.views.project_list')
233
            next = restrict_next(next, domain=settings.COOKIE_DOMAIN)
234
            return redirect(next)
235

    
236
    details_fields = ["name", "homepage", "description", "start_date",
237
                      "end_date", "comments"]
238
    membership_fields = ["member_join_policy", "member_leave_policy",
239
                         "limit_on_members_number"]
240
    resource_catalog, resource_groups = _resources_catalog()
241
    if resource_catalog is False:
242
        # on fail resource_groups contains the result object
243
        result = resource_groups
244
        messages.error(request, 'Unable to retrieve system resources: %s' %
245
                       result.reason)
246
    extra_context = {
247
        'resource_catalog': resource_catalog,
248
        'resource_groups': resource_groups,
249
        'show_form': True,
250
        'details_fields': details_fields,
251
        'update_form': True,
252
        'membership_fields': membership_fields
253
    }
254

    
255
    response = None
256
    with ExceptionHandler(request):
257
        response = update_app_object(request, application_id,
258
                                     extra_context=extra_context)
259

    
260
    if response is not None:
261
        return response
262

    
263
    next = reverse('astakos.im.views.project_list')
264
    next = restrict_next(next, domain=settings.COOKIE_DOMAIN)
265
    return redirect(next)
266

    
267

    
268
@transaction.commit_on_success
269
def update_app_object(request, object_id, extra_context=None):
270
    try:
271
        summary = 'im/projects/projectapplication_form_summary.html'
272
        return _update_object(
273
            request,
274
            object_id=object_id,
275
            template_name='im/projects/projectapplication_form.html',
276
            summary_template_name=summary,
277
            extra_context=extra_context,
278
            post_save_redirect=reverse('project_list'),
279
            form_class=ProjectApplicationForm,
280
            msg=_("The %(verbose_name)s has been received and is under "
281
                  "consideration."))
282
    except ProjectError as e:
283
        messages.error(request, e)
284

    
285

    
286
@require_http_methods(["GET", "POST"])
287
@cookie_fix
288
@valid_astakos_user_required
289
def project_app(request, application_id):
290
    return common_detail(request, application_id, project_view=False)
291

    
292

    
293
@require_http_methods(["GET", "POST"])
294
@cookie_fix
295
@valid_astakos_user_required
296
def project_detail(request, chain_id):
297
    if request.method == 'POST':
298
        addmembers_form = AddProjectMembersForm(
299
            request.POST,
300
            chain_id=int(chain_id),
301
            request_user=request.user)
302
        with ExceptionHandler(request):
303
            addmembers(request, chain_id, addmembers_form)
304

    
305
        if addmembers_form.is_valid():
306
            addmembers_form = AddProjectMembersForm()  # clear form data
307
    else:
308
        addmembers_form = AddProjectMembersForm()  # initialize form
309

    
310
    project = get_object_or_404(Project, id=chain_id)
311
    members = project.projectmembership_set
312
    approved_members_count = project.members_count()
313
    pending_members_count = project.count_pending_memberships()
314
    _limit = project.limit_on_members_number
315
    remaining_memberships_count = (max(0, _limit - approved_members_count)
316
                                   if _limit is not None else None)
317
    members = members.associated()
318
    members = members.select_related()
319
    members_table = tables.ProjectMembersTable(project,
320
                                               members,
321
                                               user=request.user,
322
                                               prefix="members_")
323
    RequestConfig(request, paginate={"per_page": settings.PAGINATE_BY}
324
                  ).configure(members_table)
325

    
326
    user = request.user
327
    is_project_admin = user.is_project_admin()
328
    is_owner = user.owns_project(project)
329

    
330
    if not (is_owner or is_project_admin) and \
331
            not user.non_owner_can_view(project):
332
        m = _(astakos_messages.NOT_ALLOWED)
333
        raise PermissionDenied(m)
334

    
335
    membership = user.get_membership(project) if project else None
336
    membership_id = membership.id if membership else None
337
    mem_display = user.membership_display(project) if project else None
338
    can_join_req = can_join_request(project, user) if project else False
339
    can_leave_req = can_leave_request(project, user) if project else False
340

    
341
    return object_detail(
342
        request,
343
        queryset=Project.objects.select_related(),
344
        object_id=project.pk,
345
        template_name="im/projects/project_detail.html",
346
        extra_context={
347
            'addmembers_form': addmembers_form,
348
            'approved_members_count': approved_members_count,
349
            'pending_members_count': pending_members_count,
350
            'members_table': members_table,
351
            'owner_mode': is_owner,
352
            'admin_mode': is_project_admin,
353
            'mem_display': mem_display,
354
            'membership_id': membership_id,
355
            'can_join_request': can_join_req,
356
            'can_leave_request': can_leave_req,
357
            'remaining_memberships_count': remaining_memberships_count,
358
        })
359

    
360

    
361
@transaction.commit_on_success
362
def addmembers(request, chain_id, addmembers_form):
363
    if addmembers_form.is_valid():
364
        try:
365
            chain_id = int(chain_id)
366
            map(lambda u: enroll_member(chain_id,
367
                                        u,
368
                                        request_user=request.user),
369
                addmembers_form.valid_users)
370
        except ProjectError as e:
371
            messages.error(request, e)
372

    
373

    
374
MEMBERSHIP_STATUS_FILTER = {
375
    0: lambda x: x.requested(),
376
    1: lambda x: x.any_accepted(),
377
}
378

    
379

    
380
def common_detail(request, chain_or_app_id, project_view=True,
381
                  template_name='im/projects/project_detail.html',
382
                  members_status_filter=None):
383
    project = None
384
    approved_members_count = 0
385
    pending_members_count = 0
386
    remaining_memberships_count = None
387
    if project_view:
388
        chain_id = chain_or_app_id
389
        if request.method == 'POST':
390
            addmembers_form = AddProjectMembersForm(
391
                request.POST,
392
                chain_id=int(chain_id),
393
                request_user=request.user)
394
            with ExceptionHandler(request):
395
                addmembers(request, chain_id, addmembers_form)
396

    
397
            if addmembers_form.is_valid():
398
                addmembers_form = AddProjectMembersForm()  # clear form data
399
        else:
400
            addmembers_form = AddProjectMembersForm()  # initialize form
401

    
402
        project = get_object_or_404(Project, pk=chain_id)
403
        application = project.application
404
        if project:
405
            members = project.projectmembership_set
406
            approved_members_count = project.members_count()
407
            pending_members_count = project.count_pending_memberships()
408
            _limit = application.limit_on_members_number
409
            if _limit is not None:
410
                remaining_memberships_count = \
411
                    max(0, _limit - approved_members_count)
412
            flt = MEMBERSHIP_STATUS_FILTER.get(members_status_filter)
413
            if flt is not None:
414
                members = flt(members)
415
            else:
416
                members = members.associated()
417
            members = members.select_related()
418
            members_table = tables.ProjectMembersTable(project,
419
                                                       members,
420
                                                       user=request.user,
421
                                                       prefix="members_")
422
        else:
423
            members_table = None
424

    
425
    else:
426
        # is application
427
        application_id = chain_or_app_id
428
        application = get_object_or_404(ProjectApplication, pk=application_id)
429
        members_table = None
430
        addmembers_form = None
431

    
432
    user = request.user
433
    is_project_admin = user.is_project_admin()
434
    is_owner = user.owns_application(application)
435
    if not (is_owner or is_project_admin) and not project_view:
436
        m = _(astakos_messages.NOT_ALLOWED)
437
        raise PermissionDenied(m)
438

    
439
    if (
440
        not (is_owner or is_project_admin) and project_view and
441
        not user.non_owner_can_view(project)
442
    ):
443
        m = _(astakos_messages.NOT_ALLOWED)
444
        raise PermissionDenied(m)
445

    
446
    membership = user.get_membership(project) if project else None
447
    membership_id = membership.id if membership else None
448
    mem_display = user.membership_display(project) if project else None
449
    can_join_req = can_join_request(project, user) if project else False
450
    can_leave_req = can_leave_request(project, user) if project else False
451

    
452
    return object_detail(
453
        request,
454
        queryset=ProjectApplication.objects.select_related(),
455
        object_id=application.id,
456
        template_name=template_name,
457
        extra_context={
458
            'project_view': project_view,
459
            'chain_id': chain_or_app_id,
460
            'application': application,
461
            'addmembers_form': addmembers_form,
462
            'approved_members_count': approved_members_count,
463
            'pending_members_count': pending_members_count,
464
            'members_table': members_table,
465
            'owner_mode': is_owner,
466
            'admin_mode': is_project_admin,
467
            'mem_display': mem_display,
468
            'membership_id': membership_id,
469
            'can_join_request': can_join_req,
470
            'can_leave_request': can_leave_req,
471
            'members_status_filter': members_status_filter,
472
            'remaining_memberships_count': remaining_memberships_count,
473
        })
474

    
475

    
476
@require_http_methods(["GET", "POST"])
477
@cookie_fix
478
@valid_astakos_user_required
479
def project_search(request):
480
    q = request.GET.get('q', '')
481
    form = ProjectSearchForm()
482
    q = q.strip()
483

    
484
    if request.method == "POST":
485
        form = ProjectSearchForm(request.POST)
486
        if form.is_valid():
487
            q = form.cleaned_data['q'].strip()
488
        else:
489
            q = None
490

    
491
    if q is None:
492
        projects = Project.objects.none()
493
    else:
494
        accepted = request.user.projectmembership_set.filter(
495
            state__in=ProjectMembership.ACCEPTED_STATES).values_list(
496
            'project', flat=True)
497

    
498
        projects = Project.objects.search_by_name(q)
499
        projects = projects.filter(state=Project.NORMAL)
500
        projects = projects.exclude(id__in=accepted).select_related(
501
            'application', 'application__owner', 'application__applicant')
502

    
503
    table = get_user_projects_table(projects, user=request.user,
504
                                    prefix="my_projects_")
505
    if request.method == "POST":
506
        table.caption = _('SEARCH RESULTS')
507
    else:
508
        table.caption = _('ALL PROJECTS')
509

    
510
    return object_list(
511
        request,
512
        projects,
513
        template_name='im/projects/project_list.html',
514
        extra_context={
515
            'form': form,
516
            'is_search': True,
517
            'q': q,
518
            'table': table
519
        })
520

    
521

    
522
@require_http_methods(["POST"])
523
@cookie_fix
524
@valid_astakos_user_required
525
def project_join(request, chain_id):
526
    next = request.GET.get('next')
527
    if not next:
528
        next = reverse('astakos.im.views.project_detail',
529
                       args=(chain_id,))
530

    
531
    with ExceptionHandler(request):
532
        _project_join(request, chain_id)
533

    
534
    next = restrict_next(next, domain=settings.COOKIE_DOMAIN)
535
    return redirect(next)
536

    
537

    
538
@transaction.commit_on_success
539
def _project_join(request, chain_id):
540
    try:
541
        chain_id = int(chain_id)
542
        membership = join_project(chain_id, request.user)
543
        if membership.state != membership.REQUESTED:
544
            m = _(astakos_messages.USER_JOINED_PROJECT)
545
        else:
546
            m = _(astakos_messages.USER_JOIN_REQUEST_SUBMITTED)
547
        messages.success(request, m)
548
    except ProjectError as e:
549
        messages.error(request, e)
550

    
551

    
552
@require_http_methods(["POST"])
553
@cookie_fix
554
@valid_astakos_user_required
555
def project_leave(request, memb_id):
556
    next = request.GET.get('next')
557
    if not next:
558
        next = reverse('astakos.im.views.project_list')
559

    
560
    with ExceptionHandler(request):
561
        _project_leave(request, memb_id)
562

    
563
    next = restrict_next(next, domain=settings.COOKIE_DOMAIN)
564
    return redirect(next)
565

    
566

    
567
@transaction.commit_on_success
568
def _project_leave(request, memb_id):
569
    try:
570
        memb_id = int(memb_id)
571
        auto_accepted = leave_project(memb_id, request.user)
572
        if auto_accepted:
573
            m = _(astakos_messages.USER_LEFT_PROJECT)
574
        else:
575
            m = _(astakos_messages.USER_LEAVE_REQUEST_SUBMITTED)
576
        messages.success(request, m)
577
    except ProjectError as e:
578
        messages.error(request, e)
579

    
580

    
581
@require_http_methods(["POST"])
582
@cookie_fix
583
@valid_astakos_user_required
584
def project_cancel_member(request, memb_id):
585
    next = request.GET.get('next')
586
    if not next:
587
        next = reverse('astakos.im.views.project_list')
588

    
589
    with ExceptionHandler(request):
590
        _project_cancel_member(request, memb_id)
591

    
592
    next = restrict_next(next, domain=settings.COOKIE_DOMAIN)
593
    return redirect(next)
594

    
595

    
596
@transaction.commit_on_success
597
def _project_cancel_member(request, memb_id):
598
    try:
599
        cancel_membership(memb_id, request.user)
600
        m = _(astakos_messages.USER_REQUEST_CANCELLED)
601
        messages.success(request, m)
602
    except ProjectError as e:
603
        messages.error(request, e)
604

    
605

    
606
@require_http_methods(["POST"])
607
@cookie_fix
608
@valid_astakos_user_required
609
def project_accept_member(request, memb_id):
610

    
611
    with ExceptionHandler(request):
612
        _project_accept_member(request, memb_id)
613

    
614
    return redirect_back(request, 'project_list')
615

    
616

    
617
@transaction.commit_on_success
618
def _project_accept_member(request, memb_id):
619
    try:
620
        memb_id = int(memb_id)
621
        m = accept_membership(memb_id, request.user)
622
    except ProjectError as e:
623
        messages.error(request, e)
624

    
625
    else:
626
        email = escape(m.person.email)
627
        msg = _(astakos_messages.USER_MEMBERSHIP_ACCEPTED) % email
628
        messages.success(request, msg)
629

    
630

    
631
@require_http_methods(["POST"])
632
@cookie_fix
633
@valid_astakos_user_required
634
def project_remove_member(request, memb_id):
635

    
636
    with ExceptionHandler(request):
637
        _project_remove_member(request, memb_id)
638

    
639
    return redirect_back(request, 'project_list')
640

    
641

    
642
@transaction.commit_on_success
643
def _project_remove_member(request, memb_id):
644
    try:
645
        memb_id = int(memb_id)
646
        m = remove_membership(memb_id, request.user)
647
    except ProjectError as e:
648
        messages.error(request, e)
649
    else:
650
        email = escape(m.person.email)
651
        msg = _(astakos_messages.USER_MEMBERSHIP_REMOVED) % email
652
        messages.success(request, msg)
653

    
654

    
655
@require_http_methods(["POST"])
656
@cookie_fix
657
@valid_astakos_user_required
658
def project_reject_member(request, memb_id):
659

    
660
    with ExceptionHandler(request):
661
        _project_reject_member(request, memb_id)
662

    
663
    return redirect_back(request, 'project_list')
664

    
665

    
666
@transaction.commit_on_success
667
def _project_reject_member(request, memb_id):
668
    try:
669
        memb_id = int(memb_id)
670
        m = reject_membership(memb_id, request.user)
671
    except ProjectError as e:
672
        messages.error(request, e)
673
    else:
674
        email = escape(m.person.email)
675
        msg = _(astakos_messages.USER_MEMBERSHIP_REJECTED) % email
676
        messages.success(request, msg)
677

    
678

    
679
@require_http_methods(["POST"])
680
@signed_terms_required
681
@login_required
682
@cookie_fix
683
def project_app_approve(request, application_id):
684

    
685
    if not request.user.is_project_admin():
686
        m = _(astakos_messages.NOT_ALLOWED)
687
        raise PermissionDenied(m)
688

    
689
    try:
690
        ProjectApplication.objects.get(id=application_id)
691
    except ProjectApplication.DoesNotExist:
692
        raise Http404
693

    
694
    chain_id = get_related_project_id(application_id)
695
    with ExceptionHandler(request):
696
        _project_app_approve(request, chain_id, application_id)
697

    
698
    return redirect(reverse('project_detail', args=(chain_id,)))
699

    
700

    
701
@transaction.commit_on_success
702
def _project_app_approve(request, project_id, application_id):
703
    approve_application(project_id, application_id)
704

    
705

    
706
@require_http_methods(["POST"])
707
@signed_terms_required
708
@login_required
709
@cookie_fix
710
def project_app_deny(request, application_id):
711

    
712
    reason = request.POST.get('reason', None)
713
    if not reason:
714
        reason = None
715

    
716
    if not request.user.is_project_admin():
717
        m = _(astakos_messages.NOT_ALLOWED)
718
        raise PermissionDenied(m)
719

    
720
    try:
721
        ProjectApplication.objects.get(id=application_id)
722
    except ProjectApplication.DoesNotExist:
723
        raise Http404
724

    
725
    chain_id = get_related_project_id(application_id)
726
    with ExceptionHandler(request):
727
        _project_app_deny(request, chain_id, application_id, reason)
728

    
729
    return redirect(reverse('project_list'))
730

    
731

    
732
@transaction.commit_on_success
733
def _project_app_deny(request, project_id, application_id, reason):
734
    deny_application(project_id, application_id, reason=reason)
735

    
736

    
737
@require_http_methods(["POST"])
738
@signed_terms_required
739
@login_required
740
@cookie_fix
741
def project_app_dismiss(request, application_id):
742
    try:
743
        app = ProjectApplication.objects.get(id=application_id)
744
    except ProjectApplication.DoesNotExist:
745
        raise Http404
746

    
747
    if not request.user.owns_application(app):
748
        m = _(astakos_messages.NOT_ALLOWED)
749
        raise PermissionDenied(m)
750

    
751
    chain_id = get_related_project_id(application_id)
752
    with ExceptionHandler(request):
753
        _project_app_dismiss(request, application_id)
754

    
755
    if chain_id:
756
        next = reverse('project_detail', args=(chain_id,))
757
    else:
758
        next = reverse('project_list')
759
    return redirect(next)
760

    
761

    
762
def _project_app_dismiss(request, project_id, application_id):
763
    # XXX: dismiss application also does authorization
764
    dismiss_application(project_id, application_id, request_user=request.user)
765

    
766

    
767
@require_http_methods(["GET", "POST"])
768
@valid_astakos_user_required
769
def project_members(request, chain_id, members_status_filter=None,
770
                    template_name='im/projects/project_members.html'):
771
    project = get_object_or_404(Project, pk=chain_id)
772

    
773
    user = request.user
774
    if not user.owns_project(project) and not user.is_project_admin():
775
        return redirect(reverse('index'))
776

    
777
    if request.method == 'POST':
778
        addmembers_form = AddProjectMembersForm(
779
            request.POST,
780
            chain_id=int(chain_id),
781
            request_user=request.user)
782
        with ExceptionHandler(request):
783
            addmembers(request, chain_id, addmembers_form)
784

    
785
        if addmembers_form.is_valid():
786
            addmembers_form = AddProjectMembersForm()  # clear form data
787
    else:
788
        addmembers_form = AddProjectMembersForm()  # initialize form
789

    
790
    members = project.projectmembership_set
791
    approved_members_count = project.members_count()
792
    pending_members_count = project.count_pending_memberships()
793
    _limit = project.limit_on_members_number
794
    if _limit is not None:
795
        remaining_memberships_count = \
796
            max(0, _limit - approved_members_count)
797
    flt = MEMBERSHIP_STATUS_FILTER.get(members_status_filter)
798
    if flt is not None:
799
        members = flt(members)
800
    else:
801
        members = members.associated()
802
    members = members.select_related()
803
    members_table = tables.ProjectMembersTable(project,
804
                                               members,
805
                                               user=request.user,
806
                                               prefix="members_")
807
    RequestConfig(request, paginate={"per_page": settings.PAGINATE_BY}
808
                  ).configure(members_table)
809

    
810

    
811
    user = request.user
812
    is_project_admin = user.is_project_admin()
813
    is_owner = user.owns_application(project)
814
    if (
815
        not (is_owner or is_project_admin) and
816
        not user.non_owner_can_view(project)
817
    ):
818
        m = _(astakos_messages.NOT_ALLOWED)
819
        raise PermissionDenied(m)
820

    
821
    membership = user.get_membership(project) if project else None
822
    membership_id = membership.id if membership else None
823
    mem_display = user.membership_display(project) if project else None
824
    can_join_req = can_join_request(project, user) if project else False
825
    can_leave_req = can_leave_request(project, user) if project else False
826

    
827
    return object_detail(
828
        request,
829
        queryset=Project.objects.select_related(),
830
        object_id=project.id,
831
        template_name='im/projects/project_members.html',
832
        extra_context={
833
            'addmembers_form': addmembers_form,
834
            'approved_members_count': approved_members_count,
835
            'pending_members_count': pending_members_count,
836
            'members_table': members_table,
837
            'owner_mode': is_owner,
838
            'admin_mode': is_project_admin,
839
            'mem_display': mem_display,
840
            'membership_id': membership_id,
841
            'can_join_request': can_join_req,
842
            'can_leave_request': can_leave_req,
843
            'members_status_filter': members_status_filter,
844
            'remaining_memberships_count': remaining_memberships_count,
845
        })
846

    
847

    
848
@require_http_methods(["POST"])
849
@valid_astakos_user_required
850
def project_members_action(request, chain_id, action=None, redirect_to=''):
851

    
852
    actions_map = {
853
        'remove': _project_remove_member,
854
        'accept': _project_accept_member,
855
        'reject': _project_reject_member
856
    }
857

    
858
    if not action in actions_map.keys():
859
        raise PermissionDenied
860

    
861
    member_ids = request.POST.getlist('members')
862
    project = get_object_or_404(Project, pk=chain_id)
863

    
864
    user = request.user
865
    if not user.owns_project(project) and not user.is_project_admin():
866
        return redirect(reverse('index'))
867

    
868
    logger.info("Batch members action from %s (chain: %r, action: %s, "
869
                "members: %r)", user.log_display, chain_id, action, member_ids)
870

    
871
    action_func = actions_map.get(action)
872
    for member_id in member_ids:
873
        member_id = int(member_id)
874
        with ExceptionHandler(request):
875
            action_func(request, member_id)
876

    
877
    return redirect_back(request, 'project_list')