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