Changed Additional Mentors to Co-Mentors in the manage view.
[Melange.git] / app / soc / views / models / student_project.py
blob51076a976cb162cfd5026a9b0342f2333f9c62e3
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 @decorators.merge_params
204 @decorators.check_access
205 def manage(self, request, access_type,
206 page_name=None, params=None, **kwargs):
207 """View that allows Organization Admins to manage their Student Projects.
209 For params see base.View().public()
212 try:
213 entity = self._logic.getFromKeyFieldsOr404(kwargs)
214 except out_of_band.Error, error:
215 return responses.errorResponse(
216 error, request, template=params['error_public'])
218 get_dict = request.GET
220 if 'remove' in get_dict:
221 # get the mentor to remove
222 fields = {'link_id': get_dict['remove'],
223 'scope': entity.scope}
224 mentor = mentor_logic.logic.getForFields(fields, unique=True)
226 additional_mentors = entity.additional_mentors
227 if additional_mentors and mentor.key() in additional_mentors:
228 # remove the mentor from the additional mentors list
229 additional_mentors.remove(mentor.key())
230 fields= {'additional_mentors': additional_mentors}
231 project_logic.updateEntityProperties(entity, fields)
233 # redirect to the same page without GET arguments
234 redirect = request.path
235 return http.HttpResponseRedirect(redirect)
237 template = params['manage_template']
239 # get the context for this webpage
240 context = responses.getUniversalContext(request)
241 responses.useJavaScript(context, params['js_uses_all'])
242 context['page_name'] = "%s '%s' from %s" % (page_name, entity.title,
243 entity.student.name())
244 context['entity'] = entity
246 # get all mentors for this organization
247 fields = {'scope': entity.scope,
248 'status': 'active'}
249 mentors = mentor_logic.logic.getForFields(fields)
251 choices = [(mentor.link_id,'%s (%s)' %(mentor.name(), mentor.link_id))
252 for mentor in mentors]
254 # create the form that org admins will use to reassign a mentor
255 dynafields = [
256 {'name': 'mentor_id',
257 'base': forms.ChoiceField,
258 'label': 'Primary Mentor',
259 'required': True,
260 'passthrough': ['required', 'choices', 'label'],
261 'choices': choices,
264 dynaproperties = params_helper.getDynaFields(dynafields)
266 mentor_edit_form = dynaform.newDynaForm(
267 dynabase = params['dynabase'],
268 dynaproperties = dynaproperties,
271 params['mentor_edit_form'] = mentor_edit_form
273 additional_mentors = entity.additional_mentors
275 # we want to show the names of the additional mentors in the context
276 # therefore they need to be resolved to entities first
277 additional_mentors_context = []
279 for mentor_key in additional_mentors:
280 mentor_entity = mentor_logic.logic.getFromKeyName(
281 mentor_key.id_or_name())
282 additional_mentors_context.append(mentor_entity)
284 context['additional_mentors'] = additional_mentors_context
286 # all mentors who are not already an additional mentor or
287 # the primary mentor are allowed to become an additional mentor
288 possible_additional_mentors = [m for m in mentors if
289 (m.key() not in additional_mentors) and (m.key() != entity.mentor.key())]
291 # create the information to be shown on the additional mentor form
292 additional_mentor_choices = [
293 (mentor.link_id,'%s (%s)' %(mentor.name(), mentor.link_id))
294 for mentor in possible_additional_mentors]
296 dynafields = [
297 {'name': 'mentor_id',
298 'base': forms.ChoiceField,
299 'label': 'Co-Mentor',
300 'required': True,
301 'passthrough': ['required', 'choices', 'label'],
302 'choices': additional_mentor_choices,
305 dynaproperties = params_helper.getDynaFields(dynafields)
307 additional_mentor_form = dynaform.newDynaForm(
308 dynabase = params['dynabase'],
309 dynaproperties = dynaproperties,
312 params['additional_mentor_form'] = additional_mentor_form
314 if request.POST:
315 return self.managePost(request, template, context, params, entity,
316 **kwargs)
317 else: #request.GET
318 return self.manageGet(request, template, context, params, entity,
319 **kwargs)
321 def manageGet(self, request, template, context, params, entity, **kwargs):
322 """Handles the GET request for the project's manage page.
324 Args:
325 template: the template used for this view
326 entity: the student project entity
327 rest: see base.View.public()
330 # populate form with the current mentor
331 initial = {'mentor_id': entity.mentor.link_id}
332 context['mentor_edit_form'] = params['mentor_edit_form'](initial=initial)
334 context['additional_mentor_form'] = params['additional_mentor_form']()
336 return responses.respond(request, template, context)
338 def managePost(self, request, template, context, params, entity, **kwargs):
339 """Handles the POST request for the project's manage page.
341 Args:
342 template: the template used for this view
343 entity: the student project entity
344 rest: see base.View.public()
347 post_dict = request.POST
349 if 'set_mentor' in post_dict:
350 form = params['mentor_edit_form'](post_dict)
351 return self._manageSetMentor(request, template, context, params, entity,
352 form)
353 elif 'add_additional_mentor' in post_dict:
354 form = params['additional_mentor_form'](post_dict)
355 return self._manageAddAdditionalMentor(request, template, context,
356 params, entity, form)
357 else:
358 # unexpected error return the normal page
359 logging.warning('Unexpected POST data found')
360 return self.manageGet(request, template, context, params, entity)
362 def _manageSetMentor(self, request, template, context, params, entity, form):
363 """Handles the POST request for changing a Projects's mentor.
365 Args:
366 template: the template used for this view
367 entity: the student project entity
368 form: instance of the form used to set the mentor
369 rest: see base.View.public()
372 if not form.is_valid():
373 context['mentor_edit_form'] = form
375 # add an a fresh additional mentors form
376 context['additional_mentor_form'] = params['additional_mentor_form']()
378 return responses.respond(request, template, context)
380 _, fields = forms_helper.collectCleanedFields(form)
382 # get the mentor from the form
383 fields = {'link_id': fields['mentor_id'],
384 'scope': entity.scope,
385 'status': 'active'}
386 mentor = mentor_logic.logic.getForFields(fields, unique=True)
388 # update the project with the assigned mentor
389 fields = {'mentor': mentor}
391 additional_mentors = entity.additional_mentors
393 if additional_mentors and mentor.key() in additional_mentors:
394 # remove the mentor that is now becoming the primary mentor
395 additional_mentors.remove(mentor.key())
396 fields['additional_mentors'] = additional_mentors
398 # update the project with the new mentor and possible
399 # new set of additional mentors
400 project_logic.updateEntityProperties(entity, fields)
402 # redirect to the same page
403 redirect = request.path
404 return http.HttpResponseRedirect(redirect)
406 def _manageAddAdditionalMentor(self, request, template, context, params, entity, form):
407 """Handles the POST request for changing a Projects's additional mentors.
409 Args:
410 template: the template used for this view
411 entity: the student project entity
412 form: instance of the form used to add an additional mentor
413 rest: see base.View.public()
416 if not form.is_valid():
417 context['additional_mentor_form'] = form
419 # add a fresh edit mentor form
420 initial = {'mentor_id': entity.mentor.link_id}
421 context['mentor_edit_form'] = params['mentor_edit_form'](initial=initial)
423 return responses.respond(request, template, context)
425 _, fields = forms_helper.collectCleanedFields(form)
427 # get the mentor from the form
428 fields = {'link_id': fields['mentor_id'],
429 'scope': entity.scope,
430 'status': 'active'}
431 mentor = mentor_logic.logic.getForFields(fields, unique=True)
433 # add this mentor to the additional mentors
434 if not entity.additional_mentors:
435 additional_mentors = [mentor.key()]
436 else:
437 additional_mentors = additional_mentors.append(mentor.key())
439 fields = {'additional_mentors': additional_mentors}
440 project_logic.updateEntityProperties(entity, fields)
442 # redirect to the same page
443 redirect = request.path
444 return http.HttpResponseRedirect(redirect)
446 @decorators.merge_params
447 @decorators.check_access
448 def manageOverview(self, request, access_type,
449 page_name=None, params=None, **kwargs):
450 """View that allows Organization Admins to see an overview of
451 their Organization's Student Projects.
453 For params see base.View().public()
456 # make sure the organization exists
457 org_entity = org_logic.getFromKeyNameOr404(kwargs['scope_path'])
458 fields = {'scope': org_entity}
460 # get the context for this webpage
461 context = responses.getUniversalContext(request)
462 responses.useJavaScript(context, params['js_uses_all'])
463 context['page_name'] = '%s %s' % (page_name, org_entity.name)
465 list_params = params.copy()
467 #list all active projects
468 fields['status'] = ['accepted', 'mid_term_passed']
469 active_params = list_params.copy()
470 active_params['list_description'] = \
471 'List of all active %(name_plural)s' % list_params
472 active_params['list_action'] = (redirects.getManageRedirect, list_params)
474 active_list = lists.getListContent(
475 request, active_params, fields, idx=0)
477 # list all failed projects
478 fields['status'] = ['mid_term_failed', 'final_failed']
479 failed_params = list_params.copy()
480 failed_params['list_description'] = ('List of all failed %(name_plural)s, '
481 'these cannot be managed.') % list_params
482 failed_params['list_action'] = (redirects.getPublicRedirect, list_params)
484 failed_list = lists.getListContent(
485 request, failed_params, fields, idx=1, need_content=True)
487 #list all completed projects
488 fields['status'] = ['passed']
489 completed_params = list_params.copy()
490 completed_params['list_description'] = ('List of %(name_plural)s that have '
491 'successfully completed the program, '
492 'these cannot be managed.' % list_params)
493 completed_params['list_action'] = (redirects.getPublicRedirect, list_params)
495 completed_list = lists.getListContent(
496 request, completed_params, fields, idx=2, need_content=True)
498 # always show the list with active projects
499 content = [active_list]
501 if failed_list != None:
502 # do not show empty failed list
503 content.append(failed_list)
505 if completed_list != None:
506 # do not show empty completed list
507 content.append(completed_list)
509 # call the _list method from base to display the list
510 return self._list(request, list_params, content,
511 context['page_name'], context)
513 @decorators.merge_params
514 @decorators.check_access
515 def stEdit(self, request, access_type,
516 page_name=None, params=None, **kwargs):
517 """View that allows students to edit information about their project.
519 For params see base.View().public()
522 try:
523 entity = self._logic.getFromKeyFieldsOr404(kwargs)
524 except out_of_band.Error, error:
525 return responses.errorResponse(
526 error, request, template=params['error_public'])
528 # get the context for this webpage
529 context = responses.getUniversalContext(request)
530 responses.useJavaScript(context, params['js_uses_all'])
531 context['page_name'] = page_name
532 # cancel should go to the public view
533 params['edit_cancel_redirect'] = redirects.getPublicRedirect(entity, params)
535 if request.POST:
536 return self.stEditPost(request, context, params, entity, **kwargs)
537 else: #request.GET
538 return self.stEditGet(request, context, params, entity, **kwargs)
540 def stEditGet(self, request, context, params, entity, **kwargs):
541 """Handles the GET request for the student's edit page.
543 Args:
544 entity: the student project entity
545 rest: see base.View.public()
548 # populate form with the existing entity
549 form = params['student_edit_form'](instance=entity)
551 return self._constructResponse(request, entity, context, form, params)
553 def stEditPost(self, request, context, params, entity, **kwargs):
554 """Handles the POST request for the student's edit page.
556 Args:
557 entity: the student project entity
558 rest: see base.View.public()
561 form = params['student_edit_form'](request.POST)
563 if not form.is_valid():
564 return self._constructResponse(request, entity, context, form, params)
566 _, fields = forms_helper.collectCleanedFields(form)
568 project_logic.updateEntityProperties(entity, fields)
570 return self.stEditGet(request, context, params, entity, **kwargs)
573 view = View()
575 admin = decorators.view(view.admin)
576 create = decorators.view(view.create)
577 delete = decorators.view(view.delete)
578 edit = decorators.view(view.edit)
579 list = decorators.view(view.list)
580 manage = decorators.view(view.manage)
581 manage_overview = decorators.view(view.manageOverview)
582 public = decorators.view(view.public)
583 st_edit = decorators.view(view.stEdit)
584 export = decorators.view(view.export)
585 pick = decorators.view(view.pick)