Now clicking cancel button returns user to previous page. Fixes issue 569.
[Melange.git] / app / soc / views / models / student_project.py
blob9fd2386f661f8e0220b774c4ffae035ec46dd8ca
1 #!/usr/bin/python2.5
3 # Copyright 2009 the Melange authors.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Views for Student Project.
18 """
20 __authors__ = [
21 '"Lennard de Rijk" <ljvderijk@gmail.com>',
25 import logging
26 import time
28 from django import forms
29 from django import http
31 from soc.logic import cleaning
32 from soc.logic import dicts
33 from soc.logic.models import mentor as mentor_logic
34 from soc.logic.models.organization import logic as org_logic
35 from soc.logic.models.org_admin import logic as org_admin_logic
36 from soc.logic.models import student as student_logic
37 from soc.logic.models.student_project import logic as project_logic
38 from soc.views import out_of_band
39 from soc.views.helper import access
40 from soc.views.helper import decorators
41 from soc.views.helper import dynaform
42 from soc.views.helper import forms as forms_helper
43 from soc.views.helper import lists
44 from soc.views.helper import params as params_helper
45 from soc.views.helper import redirects
46 from soc.views.helper import responses
47 from soc.views.helper import widgets
48 from soc.views.models import base
49 from soc.views.models import organization as org_view
51 import soc.logic.models.student_project
54 class View(base.View):
55 """View methods for the Student Project model.
56 """
58 def __init__(self, params=None):
59 """Defines the fields and methods required for the base View class
60 to provide the user with list, public, create, edit and delete views.
62 Params:
63 params: a dict with params for this View
64 """
66 rights = access.Checker(params)
67 rights['any_access'] = ['allow']
68 rights['create'] = ['checkIsDeveloper']
69 rights['edit'] = ['checkIsDeveloper']
70 rights['delete'] = ['checkIsDeveloper']
71 rights['show'] = ['allow']
72 rights['list'] = ['checkIsDeveloper']
73 rights['manage'] = [('checkHasActiveRoleForScope',
74 org_admin_logic),
75 ('checkStudentProjectHasStatus', [['accepted', 'mid_term_passed']])]
76 rights['manage_overview'] = [('checkHasActiveRoleForScope',
77 org_admin_logic)]
78 # TODO lack of better name here!
79 rights['st_edit'] = ['checkIsMyStudentProject',
80 ('checkStudentProjectHasStatus',
81 [['accepted', 'mid_term_passed', 'passed']])
84 new_params = {}
85 new_params['logic'] = soc.logic.models.student_project.logic
86 new_params['rights'] = rights
87 new_params['name'] = "Student Project"
88 new_params['url_name'] = "student_project"
89 new_params['sidebar_grouping'] = 'Students'
91 new_params['scope_view'] = org_view
92 new_params['scope_redirect'] = redirects.getCreateRedirect
94 new_params['no_create_with_key_fields'] = True
96 new_params['extra_dynaexclude'] = ['program', 'status', 'link_id',
97 'mentor', 'additional_mentors',
98 'student']
100 new_params['create_extra_dynaproperties'] = {
101 'scope_path': forms.CharField(widget=forms.HiddenInput,
102 required=True),
103 'public_info': forms.fields.CharField(required=True,
104 widget=widgets.FullTinyMCE(attrs={'rows': 25, 'cols': 100})),
105 'student_id': forms.CharField(label='Student Link ID',
106 required=True),
107 'mentor_id': forms.CharField(label='Mentor Link ID',
108 required=True),
109 'clean_abstract': cleaning.clean_content_length('abstract'),
110 'clean_public_info': cleaning.clean_html_content('public_info'),
111 'clean_student': cleaning.clean_link_id('student'),
112 'clean_mentor': cleaning.clean_link_id('mentor'),
113 'clean_additional_info': cleaning.clean_url('additional_info'),
114 'clean_feed_url': cleaning.clean_feed_url,
115 'clean': cleaning.validate_student_project('scope_path',
116 'mentor_id', 'student_id')
119 new_params['edit_extra_dynaproperties'] = {
120 'link_id': forms.CharField(widget=forms.HiddenInput),
123 patterns = [
124 (r'^%(url_name)s/(?P<access_type>manage_overview)/%(scope)s$',
125 'soc.views.models.%(module_name)s.manage_overview',
126 'Overview of %(name_plural)s to Manage for'),
127 (r'^%(url_name)s/(?P<access_type>manage)/%(key_fields)s$',
128 'soc.views.models.%(module_name)s.manage',
129 'Manage %(name)s'),
130 (r'^%(url_name)s/(?P<access_type>st_edit)/%(key_fields)s$',
131 'soc.views.models.%(module_name)s.st_edit',
132 'Edit my %(name)s'),
135 new_params['extra_django_patterns'] = patterns
137 new_params['edit_template'] = 'soc/student_project/edit.html'
138 new_params['manage_template'] = 'soc/student_project/manage.html'
140 params = dicts.merge(params, new_params)
142 super(View, self).__init__(params=params)
144 # create the form that students will use to edit their projects
145 dynaproperties = {
146 'public_info': forms.fields.CharField(required=True,
147 widget=widgets.FullTinyMCE(attrs={'rows': 25, 'cols': 100})),
148 'clean_abstract': cleaning.clean_content_length('abstract'),
149 'clean_public_info': cleaning.clean_html_content('public_info'),
150 'clean_additional_info': cleaning.clean_url('additional_info'),
151 'clean_feed_url': cleaning.clean_feed_url,
154 student_edit_form = dynaform.newDynaForm(
155 dynabase = self._params['dynabase'],
156 dynamodel = self._params['logic'].getModel(),
157 dynaexclude = self._params['create_dynaexclude'],
158 dynaproperties = dynaproperties,
161 self._params['student_edit_form'] = student_edit_form
164 def _editGet(self, request, entity, form):
165 """See base.View._editGet().
168 form.fields['link_id'].initial = entity.link_id
169 form.fields['student_id'].initial = entity.student.link_id
170 form.fields['mentor_id'].initial = entity.mentor.link_id
172 return super(View, self)._editGet(request, entity, form)
174 def _editPost(self, request, entity, fields):
175 """See base.View._editPost().
178 if not entity:
179 fields['link_id'] = 't%i' % (int(time.time()*100))
180 else:
181 fields['link_id'] = entity.link_id
183 # fill in the scope via call to super
184 super(View, self)._editPost(request, entity, fields)
186 # editing a project so set the program, student and mentor field
187 if entity:
188 organization = entity.scope
189 else:
190 organization = fields['scope']
192 fields['program'] = organization.scope
194 filter = {'scope': fields['program'],
195 'link_id': fields['student_id']}
196 fields['student'] = student_logic.logic.getForFields(filter, unique=True)
198 filter = {'scope': organization,
199 'link_id': fields['mentor_id'],
200 'status': 'active'}
201 fields['mentor'] = mentor_logic.logic.getForFields(filter, unique=True)
203 def _public(self, request, entity, context):
204 """Adds the names of all additional mentors to the context.
206 For params see base.View._public()
209 additional_mentors = entity.additional_mentors
211 if not additional_mentors:
212 context['additional_mentors'] = []
213 else:
214 mentor_names = []
216 for mentor_key in additional_mentors:
217 additional_mentor = mentor_logic.logic.getFromKeyName(
218 mentor_key.id_or_name())
219 mentor_names.append(additional_mentor.name())
221 context['additional_mentors'] = ', '.join(mentor_names)
223 @decorators.merge_params
224 @decorators.check_access
225 def manage(self, request, access_type,
226 page_name=None, params=None, **kwargs):
227 """View that allows Organization Admins to manage their Student Projects.
229 For params see base.View().public()
232 try:
233 entity = self._logic.getFromKeyFieldsOr404(kwargs)
234 except out_of_band.Error, error:
235 return responses.errorResponse(
236 error, request, template=params['error_public'])
238 get_dict = request.GET
240 if 'remove' in get_dict:
241 # get the mentor to remove
242 fields = {'link_id': get_dict['remove'],
243 'scope': entity.scope}
244 mentor = mentor_logic.logic.getForFields(fields, unique=True)
246 additional_mentors = entity.additional_mentors
247 if additional_mentors and mentor.key() in additional_mentors:
248 # remove the mentor from the additional mentors list
249 additional_mentors.remove(mentor.key())
250 fields = {'additional_mentors': additional_mentors}
251 project_logic.updateEntityProperties(entity, fields)
253 # redirect to the same page without GET arguments
254 redirect = request.path
255 return http.HttpResponseRedirect(redirect)
257 template = params['manage_template']
259 # get the context for this webpage
260 context = responses.getUniversalContext(request)
261 responses.useJavaScript(context, params['js_uses_all'])
262 context['page_name'] = "%s '%s' from %s" % (page_name, entity.title,
263 entity.student.name())
264 context['entity'] = entity
266 # get all mentors for this organization
267 fields = {'scope': entity.scope,
268 'status': 'active'}
269 mentors = mentor_logic.logic.getForFields(fields)
271 choices = [(mentor.link_id,'%s (%s)' %(mentor.name(), mentor.link_id))
272 for mentor in mentors]
274 # create the form that org admins will use to reassign a mentor
275 dynafields = [
276 {'name': 'mentor_id',
277 'base': forms.ChoiceField,
278 'label': 'Primary Mentor',
279 'required': True,
280 'passthrough': ['required', 'choices', 'label'],
281 'choices': choices,
284 dynaproperties = params_helper.getDynaFields(dynafields)
286 mentor_edit_form = dynaform.newDynaForm(
287 dynabase = params['dynabase'],
288 dynaproperties = dynaproperties,
291 params['mentor_edit_form'] = mentor_edit_form
293 additional_mentors = entity.additional_mentors
295 # we want to show the names of the additional mentors in the context
296 # therefore they need to be resolved to entities first
297 additional_mentors_context = []
299 for mentor_key in additional_mentors:
300 mentor_entity = mentor_logic.logic.getFromKeyName(
301 mentor_key.id_or_name())
302 additional_mentors_context.append(mentor_entity)
304 context['additional_mentors'] = additional_mentors_context
306 # all mentors who are not already an additional mentor or
307 # the primary mentor are allowed to become an additional mentor
308 possible_additional_mentors = [m for m in mentors if
309 (m.key() not in additional_mentors)
310 and (m.key() != entity.mentor.key())]
312 # create the information to be shown on the additional mentor form
313 additional_mentor_choices = [
314 (mentor.link_id,'%s (%s)' %(mentor.name(), mentor.link_id))
315 for mentor in possible_additional_mentors]
317 dynafields = [
318 {'name': 'mentor_id',
319 'base': forms.ChoiceField,
320 'label': 'Co-Mentor',
321 'required': True,
322 'passthrough': ['required', 'choices', 'label'],
323 'choices': additional_mentor_choices,
326 dynaproperties = params_helper.getDynaFields(dynafields)
328 additional_mentor_form = dynaform.newDynaForm(
329 dynabase = params['dynabase'],
330 dynaproperties = dynaproperties,
333 params['additional_mentor_form'] = additional_mentor_form
335 if request.POST:
336 return self.managePost(request, template, context, params, entity,
337 **kwargs)
338 else: #request.GET
339 return self.manageGet(request, template, context, params, entity,
340 **kwargs)
342 def manageGet(self, request, template, context, params, entity, **kwargs):
343 """Handles the GET request for the project's manage page.
345 Args:
346 template: the template used for this view
347 entity: the student project entity
348 rest: see base.View.public()
351 # populate form with the current mentor
352 initial = {'mentor_id': entity.mentor.link_id}
353 context['mentor_edit_form'] = params['mentor_edit_form'](initial=initial)
355 context['additional_mentor_form'] = params['additional_mentor_form']()
357 return responses.respond(request, template, context)
359 def managePost(self, request, template, context, params, entity, **kwargs):
360 """Handles the POST request for the project's manage page.
362 Args:
363 template: the template used for this view
364 entity: the student project entity
365 rest: see base.View.public()
368 post_dict = request.POST
370 if 'set_mentor' in post_dict:
371 form = params['mentor_edit_form'](post_dict)
372 return self._manageSetMentor(request, template, context, params, entity,
373 form)
374 elif 'add_additional_mentor' in post_dict:
375 form = params['additional_mentor_form'](post_dict)
376 return self._manageAddAdditionalMentor(request, template, context,
377 params, entity, form)
378 else:
379 # unexpected error return the normal page
380 logging.warning('Unexpected POST data found')
381 return self.manageGet(request, template, context, params, entity)
383 def _manageSetMentor(self, request, template, context, params, entity, form):
384 """Handles the POST request for changing a Projects's mentor.
386 Args:
387 template: the template used for this view
388 entity: the student project entity
389 form: instance of the form used to set the mentor
390 rest: see base.View.public()
393 if not form.is_valid():
394 context['mentor_edit_form'] = form
396 # add an a fresh additional mentors form
397 context['additional_mentor_form'] = params['additional_mentor_form']()
399 return responses.respond(request, template, context)
401 _, fields = forms_helper.collectCleanedFields(form)
403 # get the mentor from the form
404 fields = {'link_id': fields['mentor_id'],
405 'scope': entity.scope,
406 'status': 'active'}
407 mentor = mentor_logic.logic.getForFields(fields, unique=True)
409 # update the project with the assigned mentor
410 fields = {'mentor': mentor}
412 additional_mentors = entity.additional_mentors
414 if additional_mentors and mentor.key() in additional_mentors:
415 # remove the mentor that is now becoming the primary mentor
416 additional_mentors.remove(mentor.key())
417 fields['additional_mentors'] = additional_mentors
419 # update the project with the new mentor and possible
420 # new set of additional mentors
421 project_logic.updateEntityProperties(entity, fields)
423 # redirect to the same page
424 redirect = request.path
425 return http.HttpResponseRedirect(redirect)
427 def _manageAddAdditionalMentor(self, request, template,
428 context, params, entity, form):
429 """Handles the POST request for changing a Projects's additional mentors.
431 Args:
432 template: the template used for this view
433 entity: the student project entity
434 form: instance of the form used to add an additional mentor
435 rest: see base.View.public()
438 if not form.is_valid():
439 context['additional_mentor_form'] = form
441 # add a fresh edit mentor form
442 initial = {'mentor_id': entity.mentor.link_id}
443 context['mentor_edit_form'] = params['mentor_edit_form'](initial=initial)
445 return responses.respond(request, template, context)
447 _, fields = forms_helper.collectCleanedFields(form)
449 # get the mentor from the form
450 fields = {'link_id': fields['mentor_id'],
451 'scope': entity.scope,
452 'status': 'active'}
453 mentor = mentor_logic.logic.getForFields(fields, unique=True)
455 # add this mentor to the additional mentors
456 if not entity.additional_mentors:
457 additional_mentors = [mentor.key()]
458 else:
459 additional_mentors = additional_mentors.append(mentor.key())
461 fields = {'additional_mentors': additional_mentors}
462 project_logic.updateEntityProperties(entity, fields)
464 # redirect to the same page
465 redirect = request.path
466 return http.HttpResponseRedirect(redirect)
468 @decorators.merge_params
469 @decorators.check_access
470 def manageOverview(self, request, access_type,
471 page_name=None, params=None, **kwargs):
472 """View that allows Organization Admins to see an overview of
473 their Organization's Student Projects.
475 For params see base.View().public()
478 # make sure the organization exists
479 org_entity = org_logic.getFromKeyNameOr404(kwargs['scope_path'])
480 fields = {'scope': org_entity}
482 # get the context for this webpage
483 context = responses.getUniversalContext(request)
484 responses.useJavaScript(context, params['js_uses_all'])
485 context['page_name'] = '%s %s' % (page_name, org_entity.name)
487 list_params = params.copy()
489 #list all active projects
490 fields['status'] = ['accepted', 'mid_term_passed']
491 active_params = list_params.copy()
492 active_params['list_description'] = \
493 'List of all active %(name_plural)s' % list_params
494 active_params['list_action'] = (redirects.getManageRedirect, list_params)
496 active_list = lists.getListContent(
497 request, active_params, fields, idx=0)
499 # list all failed projects
500 fields['status'] = ['mid_term_failed', 'final_failed']
501 failed_params = list_params.copy()
502 failed_params['list_description'] = ('List of all failed %(name_plural)s, '
503 'these cannot be managed.') % list_params
504 failed_params['list_action'] = (redirects.getPublicRedirect, list_params)
506 failed_list = lists.getListContent(
507 request, failed_params, fields, idx=1, need_content=True)
509 #list all completed projects
510 fields['status'] = ['passed']
511 completed_params = list_params.copy()
512 completed_params['list_description'] = ('List of %(name_plural)s that have '
513 'successfully completed the program, '
514 'these cannot be managed.' % list_params)
515 completed_params['list_action'] = (redirects.getPublicRedirect, list_params)
517 completed_list = lists.getListContent(
518 request, completed_params, fields, idx=2, need_content=True)
520 # always show the list with active projects
521 content = [active_list]
523 if failed_list != None:
524 # do not show empty failed list
525 content.append(failed_list)
527 if completed_list != None:
528 # do not show empty completed list
529 content.append(completed_list)
531 # call the _list method from base to display the list
532 return self._list(request, list_params, content,
533 context['page_name'], context)
535 @decorators.merge_params
536 @decorators.check_access
537 def stEdit(self, request, access_type,
538 page_name=None, params=None, **kwargs):
539 """View that allows students to edit information about their project.
541 For params see base.View().public()
544 try:
545 entity = self._logic.getFromKeyFieldsOr404(kwargs)
546 except out_of_band.Error, error:
547 return responses.errorResponse(
548 error, request, template=params['error_public'])
550 # get the context for this webpage
551 context = responses.getUniversalContext(request)
552 responses.useJavaScript(context, params['js_uses_all'])
553 context['page_name'] = page_name
554 # cancel should go to the public view
555 params['cancel_redirect'] = redirects.getPublicRedirect(entity, params)
557 if request.POST:
558 return self.stEditPost(request, context, params, entity, **kwargs)
559 else: #request.GET
560 return self.stEditGet(request, context, params, entity, **kwargs)
562 def stEditGet(self, request, context, params, entity, **kwargs):
563 """Handles the GET request for the student's edit page.
565 Args:
566 entity: the student project entity
567 rest: see base.View.public()
570 # populate form with the existing entity
571 form = params['student_edit_form'](instance=entity)
573 return self._constructResponse(request, entity, context, form, params)
575 def stEditPost(self, request, context, params, entity, **kwargs):
576 """Handles the POST request for the student's edit page.
578 Args:
579 entity: the student project entity
580 rest: see base.View.public()
583 form = params['student_edit_form'](request.POST)
585 if not form.is_valid():
586 return self._constructResponse(request, entity, context, form, params)
588 _, fields = forms_helper.collectCleanedFields(form)
590 project_logic.updateEntityProperties(entity, fields)
592 return self.stEditGet(request, context, params, entity, **kwargs)
595 view = View()
597 admin = decorators.view(view.admin)
598 create = decorators.view(view.create)
599 delete = decorators.view(view.delete)
600 edit = decorators.view(view.edit)
601 list = decorators.view(view.list)
602 manage = decorators.view(view.manage)
603 manage_overview = decorators.view(view.manageOverview)
604 public = decorators.view(view.public)
605 st_edit = decorators.view(view.stEdit)
606 export = decorators.view(view.export)
607 pick = decorators.view(view.pick)