Revision 362dadaa snf-astakos-app/astakos/api/projects.py

b/snf-astakos-app/astakos/api/projects.py
47 47
from astakos.im import functions
48 48
from astakos.im.models import (
49 49
    AstakosUser, Project, ProjectApplication, ProjectMembership,
50
    ProjectResourceGrant, ProjectLog, ProjectMembershipLog)
50
    ProjectResourceQuota, ProjectResourceGrant, ProjectLog,
51
    ProjectMembershipLog)
51 52
import synnefo.util.date as date_util
53
from synnefo.util import units
52 54

  
53 55

  
54 56
MEMBERSHIP_POLICY_SHOW = {
......
69 71
}
70 72

  
71 73
PROJECT_STATE_SHOW = {
72
    Project.O_PENDING:    "pending",
73
    Project.O_ACTIVE:     "active",
74
    Project.O_DENIED:     "denied",
75
    Project.O_DISMISSED:  "dismissed",
76
    Project.O_CANCELLED:  "cancelled",
77
    Project.O_SUSPENDED:  "suspended",
78
    Project.O_TERMINATED: "terminated",
74
    Project.UNINITIALIZED: "uninitialized",
75
    Project.NORMAL:        "active",
76
    Project.SUSPENDED:     "suspended",
77
    Project.TERMINATED:    "terminated",
78
    Project.DELETED:       "deleted",
79 79
}
80 80

  
81 81
PROJECT_STATE = invert_dict(PROJECT_STATE_SHOW)
......
91 91
}
92 92

  
93 93

  
94
def _application_details(application, all_grants):
95
    grants = all_grants.get(application.id, [])
94
def _grant_details(grants):
96 95
    resources = {}
97 96
    for grant in grants:
98 97
        if not grant.resource.api_visible:
......
101 100
            "member_capacity": grant.member_capacity,
102 101
            "project_capacity": grant.project_capacity,
103 102
        }
103
    return resources
104 104

  
105
    join_policy = MEMBERSHIP_POLICY_SHOW[application.member_join_policy]
106
    leave_policy = MEMBERSHIP_POLICY_SHOW[application.member_leave_policy]
105

  
106
def _application_details(application, all_grants):
107
    grants = all_grants.get(application.id, [])
108
    resources = _grant_details(grants)
109
    join_policy = MEMBERSHIP_POLICY_SHOW.get(application.member_join_policy)
110
    leave_policy = MEMBERSHIP_POLICY_SHOW.get(application.member_leave_policy)
107 111

  
108 112
    d = {
113
        "id": application.id,
114
        "state": APPLICATION_STATE_SHOW[application.state],
109 115
        "name": application.name,
110
        "owner": application.owner.uuid,
116
        "owner": application.owner.uuid if application.owner else None,
111 117
        "applicant": application.applicant.uuid,
112 118
        "homepage": application.homepage,
113 119
        "description": application.description,
114 120
        "start_date": application.start_date,
115 121
        "end_date": application.end_date,
122
        "comments": application.comments,
116 123
        "join_policy": join_policy,
117 124
        "leave_policy": leave_policy,
118 125
        "max_members": application.limit_on_members_number,
126
        "private": application.private,
119 127
        "resources": resources,
120 128
    }
121 129
    return d
122 130

  
123 131

  
124
def get_applications_details(applications):
125
    grants = ProjectResourceGrant.objects.grants_per_app(applications)
126

  
127
    l = []
128
    for application in applications:
129
        d = {
130
            "id": application.id,
131
            "project": application.chain.uuid,
132
            "state": APPLICATION_STATE_SHOW[application.state],
133
            "comments": application.comments,
134
        }
135
        d.update(_application_details(application, grants))
136
        l.append(d)
137
    return l
138

  
139

  
140
def get_application_details(application):
141
    return get_applications_details([application])[0]
142

  
143

  
144 132
def get_projects_details(projects, request_user=None):
145
    pendings = ProjectApplication.objects.pending_per_project(projects)
146
    applications = [p.application for p in projects]
147
    grants = ProjectResourceGrant.objects.grants_per_app(applications)
133
    applications = [p.last_application for p in projects if p.last_application]
134
    proj_quotas = ProjectResourceQuota.objects.quotas_per_project(projects)
135
    app_grants = ProjectResourceGrant.objects.grants_per_app(applications)
148 136
    deactivations = ProjectLog.objects.last_deactivations(projects)
149 137

  
150 138
    l = []
151 139
    for project in projects:
152
        application = project.application
140
        join_policy = MEMBERSHIP_POLICY_SHOW[project.member_join_policy]
141
        leave_policy = MEMBERSHIP_POLICY_SHOW[project.member_leave_policy]
142
        quotas = proj_quotas.get(project.id, [])
143
        resources = _grant_details(quotas)
144

  
153 145
        d = {
154 146
            "id": project.uuid,
155
            "application": application.id,
156
            "state": PROJECT_STATE_SHOW[project.overall_state()],
147
            "state": PROJECT_STATE_SHOW[project.state],
157 148
            "creation_date": project.creation_date,
158
        }
149
            "name": project.realname,
150
            "owner": project.owner.uuid if project.owner else None,
151
            "homepage": project.homepage,
152
            "description": project.description,
153
            "end_date": project.end_date,
154
            "join_policy": join_policy,
155
            "leave_policy": leave_policy,
156
            "max_members": project.limit_on_members_number,
157
            "private": project.private,
158
            "base_project": project.is_base,
159
            "resources": resources,
160
            }
161

  
159 162
        check = functions.project_check_allowed
160 163
        if check(project, request_user,
161 164
                 level=functions.APPLICANT_LEVEL, silent=True):
162
            d["comments"] = application.comments
163
            pending = pendings.get(project.id)
164
            d["pending_application"] = pending.id if pending else None
165
            application = project.last_application
166
            if application:
167
                d["last_application"] = _application_details(
168
                    application, app_grants)
165 169
            deact = deactivations.get(project.id)
166 170
            if deact is not None:
167 171
                d["deactivation_date"] = deact.date
168
        d.update(_application_details(application, grants))
169 172
        l.append(d)
170 173
    return l
171 174

  
......
219 222
def _project_state_query(val):
220 223
    if isinstance(val, list):
221 224
        states = [_get_project_state(v) for v in val]
222
        return Project.o_states_q(states)
223
    return Project.o_state_q(_get_project_state(val))
225
        return Q(state__in=states)
226
    return Q(state=_get_project_state(val))
224 227

  
225 228

  
226 229
PROJECT_QUERY = {
227
    "name": _query("application__name"),
228
    "owner": _query("application__owner__uuid"),
230
    "name": _query("realname"),
231
    "owner": _query("owner__uuid"),
229 232
    "state": _project_state_query,
230 233
}
231 234

  
......
291 294
        membs = request_user.projectmembership_set.any_accepted()
292 295
        memb_projects = membs.values_list("project", flat=True)
293 296
        is_memb = Q(id__in=memb_projects)
294
        owned = (Q(application__owner=request_user) |
295
                 Q(application__applicant=request_user))
296
        active = (Project.o_state_q(Project.O_ACTIVE) &
297
                  Q(application__private=False))
297
        owned = Q(owner=request_user)
298
        active = (Q(state=Project.NORMAL) &
299
                  Q(private=False))
298 300
        projects = projects.filter(is_memb | owned | active)
299
    return projects.select_related(
300
        "application", "application__owner", "application__applicant")
301
    return projects.select_related("last_application")
301 302

  
302 303

  
303 304
@api.api_method(http_method="POST", token_required=True, user_required=False)
......
307 308
    user = request.user
308 309
    data = request.body
309 310
    app_data = json.loads(data)
310
    return submit_application(app_data, user, project_id=None)
311
    return submit_new_project(app_data, user)
311 312

  
312 313

  
313 314
@csrf_exempt
......
345 346
    user = request.user
346 347
    data = request.body
347 348
    app_data = json.loads(data)
348
    return submit_application(app_data, user, project_id=project_id)
349
    return submit_modification(app_data, user, project_id=project_id)
349 350

  
350 351

  
351 352
def _get_date(d, key):
......
359 360
        return None
360 361

  
361 362

  
362
def _get_maybe_string(d, key):
363
def _get_maybe_string(d, key, default=None):
363 364
    value = d.get(key)
364 365
    if value is not None and not isinstance(value, basestring):
365 366
        raise faults.BadRequest("%s must be string" % key)
367
    if value is None:
368
        return default
366 369
    return value
367 370

  
368 371

  
369
def _get_maybe_boolean(d, key):
372
def _get_maybe_boolean(d, key, default=None):
370 373
    value = d.get(key)
371 374
    if value is not None and not isinstance(value, bool):
372 375
        raise faults.BadRequest("%s must be boolean" % key)
376
    if value is None:
377
        return default
373 378
    return value
374 379

  
375 380

  
......
382 387
    return DOMAIN_VALUE_REGEX.match(name) is not None
383 388

  
384 389

  
385
def submit_application(app_data, user, project_id=None):
390
def _parse_max_members(s):
391
    try:
392
        max_members = units.parse(s)
393
        if max_members < 0:
394
            raise faults.BadRequest("Invalid max_members")
395
        return max_members
396
    except units.ParseError:
397
        raise faults.BadRequest("Invalid max_members")
398

  
399

  
400
def submit_new_project(app_data, user):
386 401
    uuid = app_data.get("owner")
387 402
    if uuid is None:
388 403
        owner = user
......
418 433
    if end_date is None:
419 434
        raise faults.BadRequest("Missing end date")
420 435

  
421
    max_members = app_data.get("max_members")
422
    if not isinstance(max_members, (int, long)) or max_members < 0:
423
        raise faults.BadRequest("Invalid max_members")
436
    try:
437
        max_members = _parse_max_members(app_data["max_members"])
438
    except KeyError:
439
        max_members = units.PRACTICALLY_INFINITE
424 440

  
425 441
    private = bool(_get_maybe_boolean(app_data, "private"))
442
    homepage = _get_maybe_string(app_data, "homepage", "")
443
    description = _get_maybe_string(app_data, "description", "")
444
    comments = _get_maybe_string(app_data, "comments", "")
445
    resources = app_data.get("resources", {})
446

  
447
    submit = functions.submit_application
448
    with ExceptionHandler():
449
        application = submit(
450
            owner=owner,
451
            name=name,
452
            project_id=None,
453
            homepage=homepage,
454
            description=description,
455
            start_date=start_date,
456
            end_date=end_date,
457
            member_join_policy=join_policy,
458
            member_leave_policy=leave_policy,
459
            limit_on_members_number=max_members,
460
            private=private,
461
            comments=comments,
462
            resources=resources,
463
            request_user=user)
464

  
465
    result = {"application": application.id,
466
              "id": application.chain.uuid,
467
              }
468
    return json_response(result, status_code=201)
469

  
470

  
471
def submit_modification(app_data, user, project_id):
472
    owner = app_data.get("owner")
473
    if owner is not None:
474
        try:
475
            owner = AstakosUser.objects.accepted().get(uuid=owner)
476
        except AstakosUser.DoesNotExist:
477
            raise faults.BadRequest("User does not exist.")
478

  
479
    name = app_data.get("name")
480

  
481
    if name is not None and not valid_project_name(name):
482
        raise faults.BadRequest("Project name should be in domain format")
483

  
484
    join_policy = app_data.get("join_policy")
485
    if join_policy is not None:
486
        try:
487
            join_policy = MEMBERSHIP_POLICY[join_policy]
488
        except KeyError:
489
            raise faults.BadRequest("Invalid join policy")
490

  
491
    leave_policy = app_data.get("leave_policy")
492
    if leave_policy is not None:
493
        try:
494
            leave_policy = MEMBERSHIP_POLICY[leave_policy]
495
        except KeyError:
496
            raise faults.BadRequest("Invalid leave policy")
497

  
498
    start_date = _get_date(app_data, "start_date")
499
    end_date = _get_date(app_data, "end_date")
500

  
501
    max_members = app_data.get("max_members")
502
    if max_members is not None:
503
        max_members = _parse_max_members(max_members)
504

  
505
    private = _get_maybe_boolean(app_data, "private")
426 506
    homepage = _get_maybe_string(app_data, "homepage")
427 507
    description = _get_maybe_string(app_data, "description")
428 508
    comments = _get_maybe_string(app_data, "comments")
......
475 555
}
476 556

  
477 557

  
478
@csrf_exempt
479
@api.api_method(http_method="POST", token_required=True, user_required=False)
480
@user_from_token
481
@transaction.commit_on_success
482
def project_action(request, project_id):
483
    user = request.user
484
    data = request.body
485
    input_data = json.loads(data)
486

  
487
    func, action_data = get_action(PROJECT_ACTION, input_data)
488
    with ExceptionHandler():
489
        func(project_id, request_user=user, reason=action_data)
490
    return HttpResponse()
491

  
492

  
493
@csrf_exempt
494
def applications(request):
495
    method = request.method
496
    if method == "GET":
497
        return get_applications(request)
498
    return api.api_method_not_allowed(request, allowed_methods=['GET'])
499

  
500

  
501
def make_application_query(input_data):
502
    project_id = input_data.get("project")
503
    if project_id is not None:
504
        return Q(chain__uuid=project_id)
505
    return Q()
506

  
507

  
508
@api.api_method(http_method="GET", token_required=True, user_required=False)
509
@user_from_token
510
@transaction.commit_on_success
511
def get_applications(request):
512
    user = request.user
513
    input_data = read_json_body(request, default={})
514
    query = make_application_query(input_data)
515
    apps = _get_applications(query, request_user=user)
516
    data = get_applications_details(apps)
517
    return json_response(data)
518

  
519

  
520
def _get_applications(query, request_user=None):
521
    apps = ProjectApplication.objects.filter(query)
522

  
523
    if not request_user.is_project_admin():
524
        owned = (Q(owner=request_user) |
525
                 Q(applicant=request_user))
526
        apps = apps.filter(owned)
527
    return apps.select_related()
528

  
529

  
530
@csrf_exempt
531
@api.api_method(http_method="GET", token_required=True, user_required=False)
532
@user_from_token
533
@transaction.commit_on_success
534
def application(request, app_id):
535
    user = request.user
536
    with ExceptionHandler():
537
        application = _get_application(app_id, user)
538
    data = get_application_details(application)
539
    return json_response(data)
540

  
541

  
542
def _get_application(app_id, request_user=None):
543
    application = functions.get_application(app_id)
544
    functions.app_check_allowed(
545
        application, request_user, level=functions.APPLICANT_LEVEL)
546
    return application
547

  
548

  
549 558
APPLICATION_ACTION = {
550 559
    "approve": functions.approve_application,
551
    "deny": functions.deny_application,
560
    "deny":    functions.deny_application,
552 561
    "dismiss": functions.dismiss_application,
553
    "cancel": functions.cancel_application,
562
    "cancel":  functions.cancel_application,
554 563
}
555 564

  
556 565

  
566
PROJECT_ACTION.update(APPLICATION_ACTION)
567
APP_ACTION_FUNCS = APPLICATION_ACTION.values()
568

  
569

  
557 570
@csrf_exempt
558 571
@api.api_method(http_method="POST", token_required=True, user_required=False)
559 572
@user_from_token
560 573
@transaction.commit_on_success
561
def application_action(request, app_id):
574
def project_action(request, project_id):
562 575
    user = request.user
563 576
    data = request.body
564 577
    input_data = json.loads(data)
565 578

  
566
    func, action_data = get_action(APPLICATION_ACTION, input_data)
579
    func, action_data = get_action(PROJECT_ACTION, input_data)
567 580
    with ExceptionHandler():
568
        func(app_id, request_user=user, reason=action_data)
569

  
581
        kwargs = {"request_user": user,
582
                  "reason": action_data.get("reason", ""),
583
                  }
584
        if func in APP_ACTION_FUNCS:
585
            kwargs["application_id"] = action_data["app_id"]
586
        func(project_id=project_id, **kwargs)
570 587
    return HttpResponse()
571 588

  
572 589

  
......
602 619
def _get_memberships(query, request_user=None):
603 620
    memberships = ProjectMembership.objects
604 621
    if not request_user.is_project_admin():
605
        owned = Q(project__application__owner=request_user)
622
        owned = Q(project__owner=request_user)
606 623
        memb = Q(person=request_user)
607 624
        memberships = memberships.filter(owned | memb)
608 625

  
609 626
    return memberships.select_related(
610
        "project", "project__application",
611
        "project__application__owner", "project__application__applicant",
612
        "person").filter(query)
627
        "project", "project__owner", "person").filter(query)
613 628

  
614 629

  
615 630
def join_project(data, request_user):

Also available in: Unified diff