Revision 9efd0075 snf-astakos-app/astakos/im/views/projects.py

b/snf-astakos-app/astakos/im/views/projects.py
36 36

  
37 37
engine = inflect.engine()
38 38

  
39
from functools import wraps
39 40
from django_tables2 import RequestConfig
40 41

  
41
from django.shortcuts import get_object_or_404
42
from django.shortcuts import get_object_or_404, render_to_response
42 43
from django.contrib import messages
43 44
from django.core.urlresolvers import reverse
44 45
from django.http import Http404, HttpResponse
......
49 50
from django.core.exceptions import PermissionDenied
50 51
from django.views.decorators.http import require_http_methods
51 52
from django.db import transaction
53
from django.template import RequestContext
52 54

  
53 55
import astakos.im.messages as astakos_messages
54 56

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

  
76
from astakos.api import projects as api
77
from astakos.im import functions as project_actions
78

  
72 79
logger = logging.getLogger(__name__)
73 80

  
74 81

  
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))
82
def no_transaction(func):
83
    return func
80 84

  
81 85

  
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)
86
def project_view(get=True, post=False, transaction=False):
87
    methods = []
88
    if get:
89
        methods.append("GET")
90
    if post:
91
        methods.append("POST")
95 92

  
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
93
    if transaction:
94
        transaction_method = transaction.commit_on_success
95
    else:
96
        transaction_method = no_transaction
181 97

  
182
    with ExceptionHandler(request):
183
        chain_id = _project_app_cancel(request, application_id)
98
    def wrapper(func):
99
        return \
100
            wraps(func)(
101
                require_http_methods(methods)(
102
                    cookie_fix(
103
                        valid_astakos_user_required(
104
                            transaction_method(
105
                                func)))))
106
    return wrapper
184 107

  
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 108

  
109
@project_view()
110
def how_it_works(request):
111
    return render_response('im/how_it_works.html',
112
                           context_instance=get_context(request))
194 113

  
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 114

  
205
    else:
206
        msg = _(astakos_messages.APPLICATION_CANCELLED)
207
        messages.success(request, msg)
208
        return chain_id
115
@project_view()
116
def project_list(request, template_name="im/projects/project_list.html"):
117
    query = api.make_project_query({})
118
    projects = api._get_projects(query, request_user=request.user)
209 119

  
120
    table = None
121
    if projects.count():
122
        table = get_user_projects_table(projects, user=request.user,
123
                                        prefix="my_projects_")
210 124

  
211
@require_http_methods(["GET", "POST"])
212
@cookie_fix
213
@valid_astakos_user_required
214
def project_modify(request, application_id):
125
    context = {'is_search': False, 'table': table}
126
    return object_list(request, projects, template_name=template_name,
127
                       extra_context=context)
215 128

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

  
130
@project_view(post=True)
131
def project_add_or_modify(request, project_uuid=None):
221 132
    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 133

  
134
    # only check quota for non project admin users
226 135
    if not user.is_project_admin():
227
        owner = app.owner
228
        ok, limit = check_pending_app_quota(owner, project=app.chain)
136
        ok, limit = check_pending_app_quota(user)
229 137
        if not ok:
230
            m = _(astakos_messages.PENDING_APPLICATION_LIMIT_MODIFY) % limit
138
            m = _(astakos_messages.PENDING_APPLICATION_LIMIT_ADD) % limit
231 139
            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)
140
            return redirect(restrict_reverse(
141
                'astakos.im.views.project_list'))
142

  
143
    project = None
144
    if project_uuid:
145
        project = get_object_or_404(Project, uuid=project_uuid)
146

  
147
        if not user.owns_project(project) and not user.is_project_admin():
148
            m = _(astakos_messages.NOT_ALLOWED)
149
            raise PermissionDenied(m)
235 150

  
236 151
    details_fields = ["name", "homepage", "description", "start_date",
237 152
                      "end_date", "comments"]
238 153
    membership_fields = ["member_join_policy", "member_leave_policy",
239 154
                         "limit_on_members_number"]
155

  
240 156
    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)
157
    resource_catalog_dict, resource_groups_dict = \
158
            _resources_catalog(as_dict=True)
159

  
246 160
    extra_context = {
247 161
        'resource_catalog': resource_catalog,
248 162
        'resource_groups': resource_groups,
163
        'resource_catalog_dict': resource_catalog_dict,
164
        'resource_groups_dict': resource_groups_dict,
249 165
        'show_form': True,
250 166
        'details_fields': details_fields,
251
        'update_form': True,
252
        'membership_fields': membership_fields
167
        'membership_fields': membership_fields,
168
        'object': project
253 169
    }
254 170

  
255
    response = None
171
    with transaction.commit_on_success():
172
        template_name = 'im/projects/projectapplication_form.html'
173
        summary_template_name = \
174
                'im/projects/projectapplication_form_summary.html'
175
        success_msg = _("The project application has been received and "
176
                        "is under consideration.")
177
        form_class = ProjectApplicationForm
178

  
179
        if project:
180
            template_name = 'im/projects/projectmodification_form.html'
181
            summary_template_name = \
182
                    'im/projects/projectmodification_form_summary.html'
183
            success_msg = _("The project modification has been received and "
184
                            "is under consideration.")
185
            form_class = ProjectModificationForm
186
            details_fields.remove('start_date')
187

  
188
        extra_context['edit'] = 0
189
        if request.method == 'POST':
190
            form = form_class(request.POST, request.FILES, instance=project)
191
            if form.is_valid():
192
                verify = request.GET.get('verify')
193
                edit = request.GET.get('edit')
194
                if verify == '1':
195
                    extra_context['show_form'] = False
196
                    extra_context['form_data'] = form.cleaned_data
197
                    template_name = summary_template_name
198
                elif edit == '1':
199
                    extra_context['show_form'] = True
200
                else:
201
                    new_object = form.save()
202
                    messages.success(request, success_msg,
203
                                     fail_silently=True)
204
                    return redirect(restrict_reverse('project_list'))
205
        else:
206
            form = form_class(instance=project)
207

  
208
        extra_context['form'] = form
209
        return render_to_response(template_name, extra_context,
210
                                  context_instance=RequestContext(request))
211

  
212

  
213
@project_view(get=False, post=True)
214
def project_app_cancel(request, project_uuid, application_id):
256 215
    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)
216
        with transaction.commit_on_success():
217
            cancel_application(application_id, project_uuid,
218
                               request_user=request.user)
219
            messages.success(request, _(astakos_messages.APPLICATION_CANCELLED))
220
    return redirect(reverse('project_list'))
284 221

  
285 222

  
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 223

  
224
@project_view(post=True)
225
def project_or_app_detail(request, project_uuid, app_id=None):
292 226

  
293
@require_http_methods(["GET", "POST"])
294
@cookie_fix
295
@valid_astakos_user_required
296
def project_detail(request, chain_id):
227
    project = get_object_or_404(Project, uuid=project_uuid)
228
    application = None
229
    if app_id:
230
        application = get_object_or_404(ProjectApplication, id=app_id)
231
        if request.method == "POST":
232
            raise PermissionDenied
233

  
234

  
235
    if project.state in [Project.O_PENDING] and not application:
236
        return redirect(reverse('project_app',
237
                                args=(project.uuid,
238
                                      project.last_application.id,)))
239

  
240
    members = project.projectmembership_set
241

  
242
    # handle members
297 243
    if request.method == 'POST':
298 244
        addmembers_form = AddProjectMembersForm(
299 245
            request.POST,
300
            chain_id=int(chain_id),
246
            project_id=project.pk,
301 247
            request_user=request.user)
302 248
        with ExceptionHandler(request):
303
            addmembers(request, chain_id, addmembers_form)
249
            handle_valid_members_form(request, project.pk, addmembers_form)
304 250

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

  
310
    project = get_object_or_404(Project, id=chain_id)
311
    members = project.projectmembership_set
312 256
    approved_members_count = project.members_count()
313 257
    pending_members_count = project.count_pending_memberships()
314 258
    _limit = project.limit_on_members_number
......
320 264
                                               members,
321 265
                                               user=request.user,
322 266
                                               prefix="members_")
323
    RequestConfig(request, paginate={"per_page": settings.PAGINATE_BY}
324
                  ).configure(members_table)
267
    paginate = {"per_page": settings.PAGINATE_BY}
268
    RequestConfig(request, paginate=paginate).configure(members_table)
325 269

  
326 270
    user = request.user
327 271
    is_project_admin = user.is_project_admin()
......
337 281
    mem_display = user.membership_display(project) if project else None
338 282
    can_join_req = can_join_request(project, user) if project else False
339 283
    can_leave_req = can_leave_request(project, user) if project else False
284
    can_cancel_req = can_cancel_join_request(project, user) if project else False
340 285

  
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)
286
    is_modification = application.is_modification() if application else False
445 287

  
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
288
    queryset = Project.objects.select_related()
289
    object_id = project.pk
290
    resources_set = project.resource_set
291
    template_name = "im/projects/project_detail.html"
292
    if application:
293
        queryset = ProjectApplication.objects.select_related()
294
        object_id = application.pk
295
        resources_set = application.resource_set
296
        template_name = "im/projects/project_application_detail.html"
451 297

  
452 298
    return object_detail(
453 299
        request,
454
        queryset=ProjectApplication.objects.select_related(),
455
        object_id=application.id,
300
        queryset=queryset,
301
        object_id=object_id,
456 302
        template_name=template_name,
457 303
        extra_context={
458
            'project_view': project_view,
459
            'chain_id': chain_or_app_id,
304
            'project': project,
460 305
            'application': application,
306
            'is_application': bool(application),
307
            'is_modification': is_modification,
461 308
            'addmembers_form': addmembers_form,
462 309
            'approved_members_count': approved_members_count,
463 310
            'pending_members_count': pending_members_count,
......
468 315
            'membership_id': membership_id,
469 316
            'can_join_request': can_join_req,
470 317
            'can_leave_request': can_leave_req,
471
            'members_status_filter': members_status_filter,
472
            'remaining_memberships_count': remaining_memberships_count,
318
            'can_cancel_join_request': can_cancel_req,
319
            'resources_set': resources_set,
320
            'last_app': None if application else project.last_application,
321
            'remaining_memberships_count': remaining_memberships_count
473 322
        })
474 323

  
475 324

  
325
MEMBERSHIP_STATUS_FILTER = {
326
    0: {'state': ProjectMembership.REQUESTED},
327
    1: {'state__in': ProjectMembership.ACCEPTED_STATES}
328
}
329

  
330

  
476 331
@require_http_methods(["GET", "POST"])
477 332
@cookie_fix
478 333
@valid_astakos_user_required
......
519 374
        })
520 375

  
521 376

  
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

  
377
@project_view(get=False, post=True)
378
def project_join(request, project_uuid):
379
    project = get_object_or_404(Project, uuid=project_uuid)
531 380
    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)
381
        with transaction.commit_on_success():
382
            membership = join_project(project_uuid, request.user)
383
            if membership.state != membership.REQUESTED:
384
                m = _(astakos_messages.USER_JOINED_PROJECT)
385
            else:
386
                m = _(astakos_messages.USER_JOIN_REQUEST_SUBMITTED)
387
            messages.success(request, m)
388
    return redirect_to_next(request, 'project_detail', args=(project.uuid,))
629 389

  
630 390

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

  
391
@project_view(get=False, post=True)
392
def project_leave(request, project_uuid):
393
    project = get_object_or_404(Project, uuid=project_uuid)
636 394
    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

  
395
        with transaction.commit_on_success():
396
            memb_id = request.user.get_membership(project).pk
397
            auto_accepted = leave_project(memb_id, request.user)
398
            if auto_accepted:
399
                m = _(astakos_messages.USER_LEFT_PROJECT)
400
            else:
401
                m = _(astakos_messages.USER_LEAVE_REQUEST_SUBMITTED)
402
            messages.success(request, m)
403
    return redirect_to_next(request, 'project_detail', args=(project.uuid,))
654 404

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

  
406
@project_view(get=False, post=True)
407
def project_cancel_join(request, project_uuid):
408
    project = get_object_or_404(Project, uuid=project_uuid)
660 409
    with ExceptionHandler(request):
661
        _project_reject_member(request, memb_id)
410
        with transaction.commit_on_success():
411
            project = get_object_or_404(Project, uuid=project_uuid)
412
            memb_id = request.user.get_membership(project).pk
413
            cancel_membership(memb_id, request.user)
414
            m = _(astakos_messages.USER_REQUEST_CANCELLED)
415
            messages.success(request, m)
416
    return redirect_to_next(request, 'project_detail', args=(project.uuid,))
662 417

  
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 418

  
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)
419
@project_view(get=False, post=True)
420
def project_app_approve(request, project_uuid, application_id):
695 421
    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)
422
        with transaction.commit_on_success():
423
            approve_application(application_id, project_uuid,
424
                                request_user=request.user)
425
            messages.success(request, _(astakos_messages.APPLICATION_APPROVED))
426
    return redirect(reverse('project_detail', args=(project_uuid,)))
704 427

  
705 428

  
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)
429
@project_view(get=False, post=True)
430
def project_app_deny(request, project_uuid, application_id):
726 431
    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
432
        reason = request.POST.get("reason", "")
433
        with transaction.commit_on_success():
434
            deny_application(application_id, project_uuid,
435
                                request_user=request.user, reason=reason)
436
            messages.success(request, _(astakos_messages.APPLICATION_DENIED))
437
    return redirect(reverse("project_list"))
746 438

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

  
751
    chain_id = get_related_project_id(application_id)
440
@project_view(get=False, post=True)
441
def project_app_dismiss(request, project_uuid, application_id):
752 442
    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)
443
        with transaction.commit_on_success():
444
            dismiss_application(application_id, project_uuid,
445
                                request_user=request.user)
446
            messages.success(request,
447
                             _(astakos_messages.APPLICATION_DISMISSED))
448
    return redirect(reverse("project_list"))
760 449

  
761 450

  
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,
451
@project_view(post=True)
452
def project_members(request, project_uuid, members_status_filter=None,
770 453
                    template_name='im/projects/project_members.html'):
771
    project = get_object_or_404(Project, pk=chain_id)
454
    project = get_object_or_404(Project, uuid=project_uuid)
772 455

  
773 456
    user = request.user
774 457
    if not user.owns_project(project) and not user.is_project_admin():
......
780 463
            chain_id=int(chain_id),
781 464
            request_user=request.user)
782 465
        with ExceptionHandler(request):
783
            addmembers(request, chain_id, addmembers_form)
466
            handle_valid_members_form(request, chain_id, addmembers_form)
784 467

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

  
790
    members = project.projectmembership_set
473
    query = api.make_membership_query({'project': project_uuid})
474
    members = api._get_memberships(query, request_user=user)
791 475
    approved_members_count = project.members_count()
792 476
    pending_members_count = project.count_pending_memberships()
793 477
    _limit = project.limit_on_members_number
......
796 480
            max(0, _limit - approved_members_count)
797 481
    flt = MEMBERSHIP_STATUS_FILTER.get(members_status_filter)
798 482
    if flt is not None:
799
        members = flt(members)
483
        members = members.filter(**flt)
800 484
    else:
801
        members = members.associated()
485
        members = members.filter(state__in=ProjectMembership.ASSOCIATED_STATES)
486

  
802 487
    members = members.select_related()
803 488
    members_table = tables.ProjectMembersTable(project,
804 489
                                               members,
......
841 526
            'can_join_request': can_join_req,
842 527
            'can_leave_request': can_leave_req,
843 528
            'members_status_filter': members_status_filter,
529
            'project': project,
844 530
            'remaining_memberships_count': remaining_memberships_count,
845 531
        })
846 532

  
847 533

  
848
@require_http_methods(["POST"])
849
@valid_astakos_user_required
850
def project_members_action(request, chain_id, action=None, redirect_to=''):
534
@project_view(get=False, post=True)
535
def project_members_action(request, project_uuid, action=None, redirect_to='',
536
                           memb_id=None):
851 537

  
852 538
    actions_map = {
853
        'remove': _project_remove_member,
854
        'accept': _project_accept_member,
855
        'reject': _project_reject_member
539
        'remove': {
540
            'method': 'remove_membership',
541
            'msg': _(astakos_messages.USER_MEMBERSHIP_REMOVED)
542
        },
543
        'accept': {
544
            'method': 'accept_membership',
545
            'msg': _(astakos_messages.USER_MEMBERSHIP_ACCEPTED)
546
        },
547
        'reject': {
548
            'method': 'reject_membership',
549
            'msg': _(astakos_messages.USER_MEMBERSHIP_REJECTED)
550
        }
856 551
    }
857 552

  
553

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

  
861
    member_ids = request.POST.getlist('members')
862
    project = get_object_or_404(Project, pk=chain_id)
557
    if memb_id:
558
        member_ids = [memb_id]
559
    else:
560
        member_ids = request.POST.getlist('members')
561

  
562
    project = get_object_or_404(Project, uuid=project_uuid)
863 563

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

  
868
    logger.info("Batch members action from %s (chain: %r, action: %s, "
869
                "members: %r)", user.log_display, chain_id, action, member_ids)
568
    logger.info("Member(s) action from %s (project: %r, action: %s, "
569
                "members: %r)", user.log_display, project.uuid, action, member_ids)
870 570

  
871
    action_func = actions_map.get(action)
571
    action = actions_map.get(action)
572
    action_func = getattr(project_actions, action.get('method'))
872 573
    for member_id in member_ids:
873 574
        member_id = int(member_id)
874 575
        with ExceptionHandler(request):
875
            action_func(request, member_id)
576
            with transaction.commit_on_success():
577
                try:
578
                    m = action_func(member_id, request.user)
579
                except ProjectError as e:
580
                    messages.error(request, e)
581
                else:
582
                    email = escape(m.person.email)
583
                    msg =  action.get('msg') % email
584
                    messages.success(request, msg)
876 585

  
877 586
    return redirect_back(request, 'project_list')

Also available in: Unified diff