Fixes an issue where the organization home page would throw a 505 when no projects...
[Melange.git] / app / soc / views / models / student_project.py
blob3d42dc16e5dfd29a21301c20b716d448648b3dfd
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 # pylint: disable-msg=E1103
248 if additional_mentors and mentor.key() in additional_mentors:
249 # remove the mentor from the additional mentors list
250 additional_mentors.remove(mentor.key())
251 fields = {'additional_mentors': additional_mentors}
252 project_logic.updateEntityProperties(entity, fields)
254 # redirect to the same page without GET arguments
255 redirect = request.path
256 return http.HttpResponseRedirect(redirect)
258 template = params['manage_template']
260 # get the context for this webpage
261 context = responses.getUniversalContext(request)
262 responses.useJavaScript(context, params['js_uses_all'])
263 context['page_name'] = "%s '%s' from %s" % (page_name, entity.title,
264 entity.student.name())
265 context['entity'] = entity
267 # get all mentors for this organization
268 fields = {'scope': entity.scope,
269 'status': 'active'}
270 mentors = mentor_logic.logic.getForFields(fields)
272 choices = [(mentor.link_id,'%s (%s)' %(mentor.name(), mentor.link_id))
273 for mentor in mentors]
275 # create the form that org admins will use to reassign a mentor
276 dynafields = [
277 {'name': 'mentor_id',
278 'base': forms.ChoiceField,
279 'label': 'Primary Mentor',
280 'required': True,
281 'passthrough': ['required', 'choices', 'label'],
282 'choices': choices,
285 dynaproperties = params_helper.getDynaFields(dynafields)
287 mentor_edit_form = dynaform.newDynaForm(
288 dynabase = params['dynabase'],
289 dynaproperties = dynaproperties,
292 params['mentor_edit_form'] = mentor_edit_form
294 additional_mentors = entity.additional_mentors
296 # we want to show the names of the additional mentors in the context
297 # therefore they need to be resolved to entities first
298 additional_mentors_context = []
300 for mentor_key in additional_mentors:
301 mentor_entity = mentor_logic.logic.getFromKeyName(
302 mentor_key.id_or_name())
303 additional_mentors_context.append(mentor_entity)
305 context['additional_mentors'] = additional_mentors_context
307 # all mentors who are not already an additional mentor or
308 # the primary mentor are allowed to become an additional mentor
309 possible_additional_mentors = [m for m in mentors if
310 (m.key() not in additional_mentors)
311 and (m.key() != entity.mentor.key())]
313 # create the information to be shown on the additional mentor form
314 additional_mentor_choices = [
315 (mentor.link_id,'%s (%s)' %(mentor.name(), mentor.link_id))
316 for mentor in possible_additional_mentors]
318 dynafields = [
319 {'name': 'mentor_id',
320 'base': forms.ChoiceField,
321 'label': 'Co-Mentor',
322 'required': True,
323 'passthrough': ['required', 'choices', 'label'],
324 'choices': additional_mentor_choices,
327 dynaproperties = params_helper.getDynaFields(dynafields)
329 additional_mentor_form = dynaform.newDynaForm(
330 dynabase = params['dynabase'],
331 dynaproperties = dynaproperties,
334 params['additional_mentor_form'] = additional_mentor_form
336 if request.POST:
337 return self.managePost(request, template, context, params, entity,
338 **kwargs)
339 else: #request.GET
340 return self.manageGet(request, template, context, params, entity,
341 **kwargs)
343 def manageGet(self, request, template, context, params, entity, **kwargs):
344 """Handles the GET request for the project's manage page.
346 Args:
347 template: the template used for this view
348 entity: the student project entity
349 rest: see base.View.public()
352 # populate form with the current mentor
353 initial = {'mentor_id': entity.mentor.link_id}
354 context['mentor_edit_form'] = params['mentor_edit_form'](initial=initial)
356 context['additional_mentor_form'] = params['additional_mentor_form']()
358 return responses.respond(request, template, context)
360 def managePost(self, request, template, context, params, entity, **kwargs):
361 """Handles the POST request for the project's manage page.
363 Args:
364 template: the template used for this view
365 entity: the student project entity
366 rest: see base.View.public()
369 post_dict = request.POST
371 if 'set_mentor' in post_dict:
372 form = params['mentor_edit_form'](post_dict)
373 return self._manageSetMentor(request, template, context, params, entity,
374 form)
375 elif 'add_additional_mentor' in post_dict:
376 form = params['additional_mentor_form'](post_dict)
377 return self._manageAddAdditionalMentor(request, template, context,
378 params, entity, form)
379 else:
380 # unexpected error return the normal page
381 logging.warning('Unexpected POST data found')
382 return self.manageGet(request, template, context, params, entity)
384 def _manageSetMentor(self, request, template, context, params, entity, form):
385 """Handles the POST request for changing a Projects's mentor.
387 Args:
388 template: the template used for this view
389 entity: the student project entity
390 form: instance of the form used to set the mentor
391 rest: see base.View.public()
394 if not form.is_valid():
395 context['mentor_edit_form'] = form
397 # add an a fresh additional mentors form
398 context['additional_mentor_form'] = params['additional_mentor_form']()
400 return responses.respond(request, template, context)
402 _, fields = forms_helper.collectCleanedFields(form)
404 # get the mentor from the form
405 fields = {'link_id': fields['mentor_id'],
406 'scope': entity.scope,
407 'status': 'active'}
408 mentor = mentor_logic.logic.getForFields(fields, unique=True)
410 # update the project with the assigned mentor
411 fields = {'mentor': mentor}
413 additional_mentors = entity.additional_mentors
415 # pylint: disable-msg=E1103
416 if additional_mentors and mentor.key() in additional_mentors:
417 # remove the mentor that is now becoming the primary mentor
418 additional_mentors.remove(mentor.key())
419 fields['additional_mentors'] = additional_mentors
421 # update the project with the new mentor and possible
422 # new set of additional mentors
423 project_logic.updateEntityProperties(entity, fields)
425 # redirect to the same page
426 redirect = request.path
427 return http.HttpResponseRedirect(redirect)
429 def _manageAddAdditionalMentor(self, request, template,
430 context, params, entity, form):
431 """Handles the POST request for changing a Projects's additional mentors.
433 Args:
434 template: the template used for this view
435 entity: the student project entity
436 form: instance of the form used to add an additional mentor
437 rest: see base.View.public()
440 if not form.is_valid():
441 context['additional_mentor_form'] = form
443 # add a fresh edit mentor form
444 initial = {'mentor_id': entity.mentor.link_id}
445 context['mentor_edit_form'] = params['mentor_edit_form'](initial=initial)
447 return responses.respond(request, template, context)
449 _, fields = forms_helper.collectCleanedFields(form)
451 # get the mentor from the form
452 fields = {'link_id': fields['mentor_id'],
453 'scope': entity.scope,
454 'status': 'active'}
455 mentor = mentor_logic.logic.getForFields(fields, unique=True)
457 # add this mentor to the additional mentors
458 # pylint: disable-msg=E1103
459 if not entity.additional_mentors:
460 additional_mentors = [mentor.key()]
461 else:
462 additional_mentors = additional_mentors.append(mentor.key())
464 fields = {'additional_mentors': additional_mentors}
465 project_logic.updateEntityProperties(entity, fields)
467 # redirect to the same page
468 redirect = request.path
469 return http.HttpResponseRedirect(redirect)
471 @decorators.merge_params
472 @decorators.check_access
473 def manageOverview(self, request, access_type,
474 page_name=None, params=None, **kwargs):
475 """View that allows Organization Admins to see an overview of
476 their Organization's Student Projects.
478 For params see base.View().public()
481 # make sure the organization exists
482 org_entity = org_logic.getFromKeyNameOr404(kwargs['scope_path'])
483 fields = {'scope': org_entity}
485 # get the context for this webpage
486 context = responses.getUniversalContext(request)
487 responses.useJavaScript(context, params['js_uses_all'])
488 context['page_name'] = '%s %s' % (page_name, org_entity.name)
490 list_params = params.copy()
492 #list all active projects
493 fields['status'] = ['accepted', 'mid_term_passed']
494 active_params = list_params.copy()
495 active_params['list_description'] = \
496 'List of all active %(name_plural)s' % list_params
497 active_params['list_action'] = (redirects.getManageRedirect, list_params)
499 active_list = lists.getListContent(
500 request, active_params, fields, idx=0)
502 # list all failed projects
503 fields['status'] = ['mid_term_failed', 'final_failed']
504 failed_params = list_params.copy()
505 failed_params['list_description'] = ('List of all failed %(name_plural)s, '
506 'these cannot be managed.') % list_params
507 failed_params['list_action'] = (redirects.getPublicRedirect, list_params)
509 failed_list = lists.getListContent(
510 request, failed_params, fields, idx=1, need_content=True)
512 #list all completed projects
513 fields['status'] = ['passed']
514 completed_params = list_params.copy()
515 completed_params['list_description'] = ('List of %(name_plural)s that have '
516 'successfully completed the program, '
517 'these cannot be managed.' % list_params)
518 completed_params['list_action'] = (redirects.getPublicRedirect, list_params)
520 completed_list = lists.getListContent(
521 request, completed_params, fields, idx=2, need_content=True)
523 # always show the list with active projects
524 content = [active_list]
526 if failed_list != None:
527 # do not show empty failed list
528 content.append(failed_list)
530 if completed_list != None:
531 # do not show empty completed list
532 content.append(completed_list)
534 # call the _list method from base to display the list
535 return self._list(request, list_params, content,
536 context['page_name'], context)
538 @decorators.merge_params
539 @decorators.check_access
540 def stEdit(self, request, access_type,
541 page_name=None, params=None, **kwargs):
542 """View that allows students to edit information about their project.
544 For params see base.View().public()
547 try:
548 entity = self._logic.getFromKeyFieldsOr404(kwargs)
549 except out_of_band.Error, error:
550 return responses.errorResponse(
551 error, request, template=params['error_public'])
553 # get the context for this webpage
554 context = responses.getUniversalContext(request)
555 responses.useJavaScript(context, params['js_uses_all'])
556 context['page_name'] = page_name
557 # cancel should go to the public view
558 params['cancel_redirect'] = redirects.getPublicRedirect(entity, params)
560 if request.POST:
561 return self.stEditPost(request, context, params, entity, **kwargs)
562 else: #request.GET
563 return self.stEditGet(request, context, params, entity, **kwargs)
565 def stEditGet(self, request, context, params, entity, **kwargs):
566 """Handles the GET request for the student's edit page.
568 Args:
569 entity: the student project entity
570 rest: see base.View.public()
573 # populate form with the existing entity
574 form = params['student_edit_form'](instance=entity)
576 return self._constructResponse(request, entity, context, form, params)
578 def stEditPost(self, request, context, params, entity, **kwargs):
579 """Handles the POST request for the student's edit page.
581 Args:
582 entity: the student project entity
583 rest: see base.View.public()
586 form = params['student_edit_form'](request.POST)
588 if not form.is_valid():
589 return self._constructResponse(request, entity, context, form, params)
591 _, fields = forms_helper.collectCleanedFields(form)
593 project_logic.updateEntityProperties(entity, fields)
595 return self.stEditGet(request, context, params, entity, **kwargs)
598 view = View()
600 admin = decorators.view(view.admin)
601 create = decorators.view(view.create)
602 delete = decorators.view(view.delete)
603 edit = decorators.view(view.edit)
604 list = decorators.view(view.list)
605 manage = decorators.view(view.manage)
606 manage_overview = decorators.view(view.manageOverview)
607 public = decorators.view(view.public)
608 st_edit = decorators.view(view.stEdit)
609 export = decorators.view(view.export)
610 pick = decorators.view(view.pick)