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