Commit 8df59879 authored by Giorgos Korfiatis's avatar Giorgos Korfiatis

astakos: Adapt to new project/app scheme

parent 29fb7398
...@@ -47,8 +47,10 @@ from .util import user_from_token, invert_dict, read_json_body ...@@ -47,8 +47,10 @@ from .util import user_from_token, invert_dict, read_json_body
from astakos.im import functions from astakos.im import functions
from astakos.im.models import ( from astakos.im.models import (
AstakosUser, Project, ProjectApplication, ProjectMembership, AstakosUser, Project, ProjectApplication, ProjectMembership,
ProjectResourceGrant, ProjectLog, ProjectMembershipLog) ProjectResourceQuota, ProjectResourceGrant, ProjectLog,
ProjectMembershipLog)
import synnefo.util.date as date_util import synnefo.util.date as date_util
from synnefo.util import units
MEMBERSHIP_POLICY_SHOW = { MEMBERSHIP_POLICY_SHOW = {
...@@ -69,13 +71,11 @@ APPLICATION_STATE_SHOW = { ...@@ -69,13 +71,11 @@ APPLICATION_STATE_SHOW = {
} }
PROJECT_STATE_SHOW = { PROJECT_STATE_SHOW = {
Project.O_PENDING: "pending", Project.UNINITIALIZED: "uninitialized",
Project.O_ACTIVE: "active", Project.NORMAL: "active",
Project.O_DENIED: "denied", Project.SUSPENDED: "suspended",
Project.O_DISMISSED: "dismissed", Project.TERMINATED: "terminated",
Project.O_CANCELLED: "cancelled", Project.DELETED: "deleted",
Project.O_SUSPENDED: "suspended",
Project.O_TERMINATED: "terminated",
} }
PROJECT_STATE = invert_dict(PROJECT_STATE_SHOW) PROJECT_STATE = invert_dict(PROJECT_STATE_SHOW)
...@@ -91,8 +91,7 @@ MEMBERSHIP_STATE_SHOW = { ...@@ -91,8 +91,7 @@ MEMBERSHIP_STATE_SHOW = {
} }
def _application_details(application, all_grants): def _grant_details(grants):
grants = all_grants.get(application.id, [])
resources = {} resources = {}
for grant in grants: for grant in grants:
if not grant.resource.api_visible: if not grant.resource.api_visible:
...@@ -101,71 +100,75 @@ def _application_details(application, all_grants): ...@@ -101,71 +100,75 @@ def _application_details(application, all_grants):
"member_capacity": grant.member_capacity, "member_capacity": grant.member_capacity,
"project_capacity": grant.project_capacity, "project_capacity": grant.project_capacity,
} }
return resources
join_policy = MEMBERSHIP_POLICY_SHOW[application.member_join_policy]
leave_policy = MEMBERSHIP_POLICY_SHOW[application.member_leave_policy] def _application_details(application, all_grants):
grants = all_grants.get(application.id, [])
resources = _grant_details(grants)
join_policy = MEMBERSHIP_POLICY_SHOW.get(application.member_join_policy)
leave_policy = MEMBERSHIP_POLICY_SHOW.get(application.member_leave_policy)
d = { d = {
"id": application.id,
"state": APPLICATION_STATE_SHOW[application.state],
"name": application.name, "name": application.name,
"owner": application.owner.uuid, "owner": application.owner.uuid if application.owner else None,
"applicant": application.applicant.uuid, "applicant": application.applicant.uuid,
"homepage": application.homepage, "homepage": application.homepage,
"description": application.description, "description": application.description,
"start_date": application.start_date, "start_date": application.start_date,
"end_date": application.end_date, "end_date": application.end_date,
"comments": application.comments,
"join_policy": join_policy, "join_policy": join_policy,
"leave_policy": leave_policy, "leave_policy": leave_policy,
"max_members": application.limit_on_members_number, "max_members": application.limit_on_members_number,
"private": application.private,
"resources": resources, "resources": resources,
} }
return d return d
def get_applications_details(applications):
grants = ProjectResourceGrant.objects.grants_per_app(applications)
l = []
for application in applications:
d = {
"id": application.id,
"project": application.chain.uuid,
"state": APPLICATION_STATE_SHOW[application.state],
"comments": application.comments,
}
d.update(_application_details(application, grants))
l.append(d)
return l
def get_application_details(application):
return get_applications_details([application])[0]
def get_projects_details(projects, request_user=None): def get_projects_details(projects, request_user=None):
pendings = ProjectApplication.objects.pending_per_project(projects) applications = [p.last_application for p in projects if p.last_application]
applications = [p.application for p in projects] proj_quotas = ProjectResourceQuota.objects.quotas_per_project(projects)
grants = ProjectResourceGrant.objects.grants_per_app(applications) app_grants = ProjectResourceGrant.objects.grants_per_app(applications)
deactivations = ProjectLog.objects.last_deactivations(projects) deactivations = ProjectLog.objects.last_deactivations(projects)
l = [] l = []
for project in projects: for project in projects:
application = project.application join_policy = MEMBERSHIP_POLICY_SHOW[project.member_join_policy]
leave_policy = MEMBERSHIP_POLICY_SHOW[project.member_leave_policy]
quotas = proj_quotas.get(project.id, [])
resources = _grant_details(quotas)
d = { d = {
"id": project.uuid, "id": project.uuid,
"application": application.id, "state": PROJECT_STATE_SHOW[project.state],
"state": PROJECT_STATE_SHOW[project.overall_state()],
"creation_date": project.creation_date, "creation_date": project.creation_date,
} "name": project.realname,
"owner": project.owner.uuid if project.owner else None,
"homepage": project.homepage,
"description": project.description,
"end_date": project.end_date,
"join_policy": join_policy,
"leave_policy": leave_policy,
"max_members": project.limit_on_members_number,
"private": project.private,
"base_project": project.is_base,
"resources": resources,
}
check = functions.project_check_allowed check = functions.project_check_allowed
if check(project, request_user, if check(project, request_user,
level=functions.APPLICANT_LEVEL, silent=True): level=functions.APPLICANT_LEVEL, silent=True):
d["comments"] = application.comments application = project.last_application
pending = pendings.get(project.id) if application:
d["pending_application"] = pending.id if pending else None d["last_application"] = _application_details(
application, app_grants)
deact = deactivations.get(project.id) deact = deactivations.get(project.id)
if deact is not None: if deact is not None:
d["deactivation_date"] = deact.date d["deactivation_date"] = deact.date
d.update(_application_details(application, grants))
l.append(d) l.append(d)
return l return l
...@@ -219,13 +222,13 @@ def _get_project_state(val): ...@@ -219,13 +222,13 @@ def _get_project_state(val):
def _project_state_query(val): def _project_state_query(val):
if isinstance(val, list): if isinstance(val, list):
states = [_get_project_state(v) for v in val] states = [_get_project_state(v) for v in val]
return Project.o_states_q(states) return Q(state__in=states)
return Project.o_state_q(_get_project_state(val)) return Q(state=_get_project_state(val))
PROJECT_QUERY = { PROJECT_QUERY = {
"name": _query("application__name"), "name": _query("realname"),
"owner": _query("application__owner__uuid"), "owner": _query("owner__uuid"),
"state": _project_state_query, "state": _project_state_query,
} }
...@@ -291,13 +294,11 @@ def _get_projects(query, request_user=None): ...@@ -291,13 +294,11 @@ def _get_projects(query, request_user=None):
membs = request_user.projectmembership_set.any_accepted() membs = request_user.projectmembership_set.any_accepted()
memb_projects = membs.values_list("project", flat=True) memb_projects = membs.values_list("project", flat=True)
is_memb = Q(id__in=memb_projects) is_memb = Q(id__in=memb_projects)
owned = (Q(application__owner=request_user) | owned = Q(owner=request_user)
Q(application__applicant=request_user)) active = (Q(state=Project.NORMAL) &
active = (Project.o_state_q(Project.O_ACTIVE) & Q(private=False))
Q(application__private=False))
projects = projects.filter(is_memb | owned | active) projects = projects.filter(is_memb | owned | active)
return projects.select_related( return projects.select_related("last_application")
"application", "application__owner", "application__applicant")
@api.api_method(http_method="POST", token_required=True, user_required=False) @api.api_method(http_method="POST", token_required=True, user_required=False)
...@@ -307,7 +308,7 @@ def create_project(request): ...@@ -307,7 +308,7 @@ def create_project(request):
user = request.user user = request.user
data = request.body data = request.body
app_data = json.loads(data) app_data = json.loads(data)
return submit_application(app_data, user, project_id=None) return submit_new_project(app_data, user)
@csrf_exempt @csrf_exempt
...@@ -345,7 +346,7 @@ def modify_project(request, project_id): ...@@ -345,7 +346,7 @@ def modify_project(request, project_id):
user = request.user user = request.user
data = request.body data = request.body
app_data = json.loads(data) app_data = json.loads(data)
return submit_application(app_data, user, project_id=project_id) return submit_modification(app_data, user, project_id=project_id)
def _get_date(d, key): def _get_date(d, key):
...@@ -359,17 +360,21 @@ def _get_date(d, key): ...@@ -359,17 +360,21 @@ def _get_date(d, key):
return None return None
def _get_maybe_string(d, key): def _get_maybe_string(d, key, default=None):
value = d.get(key) value = d.get(key)
if value is not None and not isinstance(value, basestring): if value is not None and not isinstance(value, basestring):
raise faults.BadRequest("%s must be string" % key) raise faults.BadRequest("%s must be string" % key)
if value is None:
return default
return value return value
def _get_maybe_boolean(d, key): def _get_maybe_boolean(d, key, default=None):
value = d.get(key) value = d.get(key)
if value is not None and not isinstance(value, bool): if value is not None and not isinstance(value, bool):
raise faults.BadRequest("%s must be boolean" % key) raise faults.BadRequest("%s must be boolean" % key)
if value is None:
return default
return value return value
...@@ -382,7 +387,17 @@ def valid_project_name(name): ...@@ -382,7 +387,17 @@ def valid_project_name(name):
return DOMAIN_VALUE_REGEX.match(name) is not None return DOMAIN_VALUE_REGEX.match(name) is not None
def submit_application(app_data, user, project_id=None): def _parse_max_members(s):
try:
max_members = units.parse(s)
if max_members < 0:
raise faults.BadRequest("Invalid max_members")
return max_members
except units.ParseError:
raise faults.BadRequest("Invalid max_members")
def submit_new_project(app_data, user):
uuid = app_data.get("owner") uuid = app_data.get("owner")
if uuid is None: if uuid is None:
owner = user owner = user
...@@ -418,11 +433,76 @@ def submit_application(app_data, user, project_id=None): ...@@ -418,11 +433,76 @@ def submit_application(app_data, user, project_id=None):
if end_date is None: if end_date is None:
raise faults.BadRequest("Missing end date") raise faults.BadRequest("Missing end date")
max_members = app_data.get("max_members") try:
if not isinstance(max_members, (int, long)) or max_members < 0: max_members = _parse_max_members(app_data["max_members"])
raise faults.BadRequest("Invalid max_members") except KeyError:
max_members = units.PRACTICALLY_INFINITE
private = bool(_get_maybe_boolean(app_data, "private")) private = bool(_get_maybe_boolean(app_data, "private"))
homepage = _get_maybe_string(app_data, "homepage", "")
description = _get_maybe_string(app_data, "description", "")
comments = _get_maybe_string(app_data, "comments", "")
resources = app_data.get("resources", {})
submit = functions.submit_application
with ExceptionHandler():
application = submit(
owner=owner,
name=name,
project_id=None,
homepage=homepage,
description=description,
start_date=start_date,
end_date=end_date,
member_join_policy=join_policy,
member_leave_policy=leave_policy,
limit_on_members_number=max_members,
private=private,
comments=comments,
resources=resources,
request_user=user)
result = {"application": application.id,
"id": application.chain.uuid,
}
return json_response(result, status_code=201)
def submit_modification(app_data, user, project_id):
owner = app_data.get("owner")
if owner is not None:
try:
owner = AstakosUser.objects.accepted().get(uuid=owner)
except AstakosUser.DoesNotExist:
raise faults.BadRequest("User does not exist.")
name = app_data.get("name")
if name is not None and not valid_project_name(name):
raise faults.BadRequest("Project name should be in domain format")
join_policy = app_data.get("join_policy")
if join_policy is not None:
try:
join_policy = MEMBERSHIP_POLICY[join_policy]
except KeyError:
raise faults.BadRequest("Invalid join policy")
leave_policy = app_data.get("leave_policy")
if leave_policy is not None:
try:
leave_policy = MEMBERSHIP_POLICY[leave_policy]
except KeyError:
raise faults.BadRequest("Invalid leave policy")
start_date = _get_date(app_data, "start_date")
end_date = _get_date(app_data, "end_date")
max_members = app_data.get("max_members")
if max_members is not None:
max_members = _parse_max_members(max_members)
private = _get_maybe_boolean(app_data, "private")
homepage = _get_maybe_string(app_data, "homepage") homepage = _get_maybe_string(app_data, "homepage")
description = _get_maybe_string(app_data, "description") description = _get_maybe_string(app_data, "description")
comments = _get_maybe_string(app_data, "comments") comments = _get_maybe_string(app_data, "comments")
...@@ -475,98 +555,35 @@ PROJECT_ACTION = { ...@@ -475,98 +555,35 @@ PROJECT_ACTION = {
} }
@csrf_exempt
@api.api_method(http_method="POST", token_required=True, user_required=False)
@user_from_token
@transaction.commit_on_success
def project_action(request, project_id):
user = request.user
data = request.body
input_data = json.loads(data)
func, action_data = get_action(PROJECT_ACTION, input_data)
with ExceptionHandler():
func(project_id, request_user=user, reason=action_data)
return HttpResponse()
@csrf_exempt
def applications(request):
method = request.method
if method == "GET":
return get_applications(request)
return api.api_method_not_allowed(request, allowed_methods=['GET'])
def make_application_query(input_data):
project_id = input_data.get("project")
if project_id is not None:
return Q(chain__uuid=project_id)
return Q()
@api.api_method(http_method="GET", token_required=True, user_required=False)
@user_from_token
@transaction.commit_on_success
def get_applications(request):
user = request.user
input_data = read_json_body(request, default={})
query = make_application_query(input_data)
apps = _get_applications(query, request_user=user)
data = get_applications_details(apps)
return json_response(data)
def _get_applications(query, request_user=None):
apps = ProjectApplication.objects.filter(query)
if not request_user.is_project_admin():
owned = (Q(owner=request_user) |
Q(applicant=request_user))
apps = apps.filter(owned)
return apps.select_related()
@csrf_exempt
@api.api_method(http_method="GET", token_required=True, user_required=False)
@user_from_token
@transaction.commit_on_success
def application(request, app_id):
user = request.user
with ExceptionHandler():
application = _get_application(app_id, user)
data = get_application_details(application)
return json_response(data)
def _get_application(app_id, request_user=None):
application = functions.get_application(app_id)
functions.app_check_allowed(
application, request_user, level=functions.APPLICANT_LEVEL)
return application
APPLICATION_ACTION = { APPLICATION_ACTION = {
"approve": functions.approve_application, "approve": functions.approve_application,
"deny": functions.deny_application, "deny": functions.deny_application,
"dismiss": functions.dismiss_application, "dismiss": functions.dismiss_application,
"cancel": functions.cancel_application, "cancel": functions.cancel_application,
} }
PROJECT_ACTION.update(APPLICATION_ACTION)
APP_ACTION_FUNCS = APPLICATION_ACTION.values()
@csrf_exempt @csrf_exempt
@api.api_method(http_method="POST", token_required=True, user_required=False) @api.api_method(http_method="POST", token_required=True, user_required=False)
@user_from_token @user_from_token
@transaction.commit_on_success @transaction.commit_on_success
def application_action(request, app_id): def project_action(request, project_id):
user = request.user user = request.user
data = request.body data = request.body
input_data = json.loads(data) input_data = json.loads(data)
func, action_data = get_action(APPLICATION_ACTION, input_data) func, action_data = get_action(PROJECT_ACTION, input_data)
with ExceptionHandler(): with ExceptionHandler():
func(app_id, request_user=user, reason=action_data) kwargs = {"request_user": user,
"reason": action_data.get("reason", ""),
}
if func in APP_ACTION_FUNCS:
kwargs["application_id"] = action_data["app_id"]
func(project_id=project_id, **kwargs)
return HttpResponse() return HttpResponse()
...@@ -602,14 +619,12 @@ def get_memberships(request): ...@@ -602,14 +619,12 @@ def get_memberships(request):
def _get_memberships(query, request_user=None): def _get_memberships(query, request_user=None):
memberships = ProjectMembership.objects memberships = ProjectMembership.objects
if not request_user.is_project_admin(): if not request_user.is_project_admin():
owned = Q(project__application__owner=request_user) owned = Q(project__owner=request_user)
memb = Q(person=request_user) memb = Q(person=request_user)
memberships = memberships.filter(owned | memb) memberships = memberships.filter(owned | memb)
return memberships.select_related( return memberships.select_related(
"project", "project__application", "project", "project__owner", "person").filter(query)
"project__application__owner", "project__application__applicant",
"person").filter(query)
def join_project(data, request_user): def join_project(data, request_user):
......
...@@ -60,11 +60,6 @@ astakos_account_v1_0 += patterns( ...@@ -60,11 +60,6 @@ astakos_account_v1_0 += patterns(
astakos_account_v1_0 += patterns( astakos_account_v1_0 += patterns(
'astakos.api.projects', 'astakos.api.projects',
url(r'^projects/?$', 'projects', name='api_projects'), url(r'^projects/?$', 'projects', name='api_projects'),
url(r'^projects/apps/?$', 'applications', name='api_applications'),
url(r'^projects/apps/(?P<app_id>\d+)/?$', 'application',
name='api_application'),
url(r'^projects/apps/(?P<app_id>\d+)/action/?$', 'application_action',
name='api_application_action'),
url(r'^projects/memberships/?$', 'memberships', name='api_memberships'), url(r'^projects/memberships/?$', 'memberships', name='api_memberships'),
url(r'^projects/memberships/(?P<memb_id>\d+)/?$', 'membership', url(r'^projects/memberships/(?P<memb_id>\d+)/?$', 'membership',
name='api_membership'), name='api_membership'),
......
...@@ -824,7 +824,8 @@ class ProjectApplicationForm(forms.ModelForm): ...@@ -824,7 +824,8 @@ class ProjectApplicationForm(forms.ModelForm):
policies = {} policies = {}
for d in self.resource_policies: for d in self.resource_policies:
policies[d["name"]] = { policies[d["name"]] = {
"project_capacity": None, ### TEMPORARY HACK !!!
"project_capacity": d["uplimit"],
"member_capacity": d["uplimit"] "member_capacity": d["uplimit"]
} }
......
...@@ -44,7 +44,7 @@ from synnefo_branding.utils import render_to_string ...@@ -44,7 +44,7 @@ from synnefo_branding.utils import render_to_string