Fixes an issue where the organization home page would throw a 505 when no projects...
[Melange.git] / app / soc / views / models / student_proposal.py
blobd5271ae4841e063ac8a40d9d21069347f7b977b7
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 Proposal.
18 """
20 __authors__ = [
21 '"Lennard de Rijk" <ljvderijk@gmail.com>',
25 import datetime
26 import time
28 from django import forms
29 from django import http
30 from django.utils.translation import ugettext
32 from soc.logic import cleaning
33 from soc.logic import dicts
34 from soc.logic.models import mentor as mentor_logic
35 from soc.logic.models import organization as org_logic
36 from soc.logic.models import org_admin as org_admin_logic
37 from soc.logic.models import student as student_logic
38 from soc.logic.models import user as user_logic
39 from soc.views import helper
40 from soc.views import out_of_band
41 from soc.views.helper import access
42 from soc.views.helper import decorators
43 from soc.views.helper import dynaform
44 from soc.views.helper import lists
45 from soc.views.helper import params as params_helper
46 from soc.views.helper import redirects
47 from soc.views.helper import responses
48 from soc.views.helper import widgets
49 from soc.views.models import base
50 from soc.views.models import student as student_view
52 import soc.logic.models.student_proposal
55 class View(base.View):
56 """View methods for the Student Proposal model.
57 """
59 def __init__(self, params=None):
60 """Defines the fields and methods required for the base View class
61 to provide the user with list, public, create, edit and delete views.
63 Params:
64 params: a dict with params for this View
65 """
67 rights = access.Checker(params)
68 rights['create'] = ['checkIsDeveloper']
69 rights['edit'] = [('checkCanStudentPropose', ['scope_path', False]),
70 ('checkRoleAndStatusForStudentProposal',
71 [['proposer'], ['active'], ['new', 'pending', 'invalid']])]
72 rights['delete'] = ['checkIsDeveloper']
73 rights['show'] = [
74 ('checkRoleAndStatusForStudentProposal',
75 [['proposer', 'org_admin', 'mentor', 'host'],
76 ['active', 'inactive'],
77 ['new', 'pending', 'accepted', 'rejected', 'invalid']])]
78 rights['list'] = ['checkIsDeveloper']
79 rights['list_orgs'] = [
80 ('checkIsStudent', ['scope_path', ['active']]),
81 ('checkCanStudentPropose', ['scope_path', False])]
82 rights['list_self'] = [
83 ('checkIsStudent', ['scope_path', ['active', 'inactive']])]
84 rights['apply'] = [
85 ('checkIsStudent', ['scope_path', ['active']]),
86 ('checkCanStudentPropose', ['scope_path', True])]
87 rights['review'] = [('checkRoleAndStatusForStudentProposal',
88 [['org_admin', 'mentor', 'host'],
89 ['active'],
90 ['new', 'pending', 'accepted', 'rejected', 'invalid']])]
92 new_params = {}
93 new_params['logic'] = soc.logic.models.student_proposal.logic
94 new_params['rights'] = rights
95 new_params['name'] = "Student Proposal"
96 new_params['url_name'] = "student_proposal"
97 new_params['sidebar_grouping'] = 'Students'
99 new_params['scope_view'] = student_view
100 new_params['scope_redirect'] = redirects.getCreateRedirect
102 new_params['no_create_with_key_fields'] = True
103 new_params['list_key_order'] = ['title', 'abstract', 'content',
104 'additional_info', 'created_on', 'last_modified_on']
106 patterns = [
107 (r'^%(url_name)s/(?P<access_type>apply)/%(scope)s$',
108 'soc.views.models.%(module_name)s.apply',
109 'Create a new %(name)s'),
110 (r'^%(url_name)s/(?P<access_type>list_self)/%(scope)s$',
111 'soc.views.models.%(module_name)s.list_self',
112 'List my %(name_plural)s'),
113 (r'^%(url_name)s/(?P<access_type>list_orgs)/%(scope)s$',
114 'soc.views.models.%(module_name)s.list_orgs',
115 'List my %(name_plural)s'),
116 (r'^%(url_name)s/(?P<access_type>review)/%(key_fields)s$',
117 'soc.views.models.%(module_name)s.review',
118 'Review %(name)s'),
121 new_params['extra_django_patterns'] = patterns
123 new_params['extra_dynaexclude'] = ['org', 'program', 'score',
124 'status', 'mentor', 'link_id',
125 'possible_mentors']
127 new_params['create_extra_dynaproperties'] = {
128 'content': forms.fields.CharField(required=True,
129 widget=widgets.FullTinyMCE(attrs={'rows': 25, 'cols': 100})),
130 'scope_path': forms.CharField(widget=forms.HiddenInput,
131 required=True),
132 'organization': forms.CharField(label='Organization Link ID',
133 required=True),
134 'clean_abstract': cleaning.clean_content_length('abstract'),
135 'clean_content': cleaning.clean_html_content('content'),
136 'clean_organization': cleaning.clean_link_id('organization'),
137 'clean_additional_info': cleaning.clean_url('additional_info'),
138 'clean': cleaning.validate_student_proposal('organization',
139 'scope_path', student_logic, org_logic),
142 new_params['edit_extra_dynaproperties'] = {
143 'organization': forms.CharField(label='Organization Link ID',
144 widget=widgets.ReadOnlyInput),
145 'link_id': forms.CharField(widget=forms.HiddenInput)
148 new_params['edit_template'] = 'soc/student_proposal/edit.html'
149 new_params['review_template'] = 'soc/student_proposal/review.html'
150 new_params['review_after_deadline_template'] = \
151 'soc/student_proposal/review_after_deadline.html'
153 params = dicts.merge(params, new_params)
155 super(View, self).__init__(params=params)
157 # create the special form for students
158 dynafields = [
159 {'name': 'organization',
160 'base': forms.CharField,
161 'label': 'Organization Link ID',
162 'widget': widgets.ReadOnlyInput(),
163 'required': False,
167 dynaproperties = params_helper.getDynaFields(dynafields)
169 student_create_form = dynaform.extendDynaForm(
170 dynaform=self._params['create_form'],
171 dynaproperties=dynaproperties)
173 self._params['student_create_form'] = student_create_form
175 # create the special form for public review
176 dynafields = [
177 {'name': 'comment',
178 'base': forms.CharField,
179 'widget': widgets.FullTinyMCE(attrs={'rows': 10, 'cols': 40}),
180 'label': 'Comment',
181 'required': False,
182 'example_text': 'Caution, you will not be able to edit your comment!',
186 dynaproperties = params_helper.getDynaFields(dynafields)
187 dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')
189 public_review_form = dynaform.newDynaForm(dynamodel=None,
190 dynabase=helper.forms.BaseForm, dynainclude=None,
191 dynaexclude=None, dynaproperties=dynaproperties)
192 self._params['public_review_form'] = public_review_form
194 # create the special form for mentors
195 dynafields = [
196 {'name': 'score',
197 'base': forms.ChoiceField,
198 'label': 'Score',
199 'initial': 0,
200 'required': False,
201 'passthrough': ['initial', 'required', 'choices'],
202 'example_text':
203 'A score will only be assigned if the review is private!',
204 'choices': [(-4,'-4: Wow. This. Sucks.'),
205 (-3,'-3: Needs a lot of work'),
206 (-2,'-2: This is bad'),
207 (-1,'-1: I dont like this'),
208 (0,'0: No score'),
209 (1,'1: Might have potential'),
210 (2,'2: Good'),
211 (3,'3: Almost there'),
212 (4,'4: Made. Of. Awesome.')]
214 {'name': 'comment',
215 'base': forms.CharField,
216 'widget': widgets.FullTinyMCE(attrs={'rows': 10, 'cols': 40}),
217 'label': 'Comment',
218 'required': False,
219 'example_text': 'Caution, you will not be able to edit your review!',
221 {'name': 'public',
222 'base': forms.BooleanField,
223 'label': 'Review visible to Student',
224 'initial': False,
225 'required': False,
226 'help_text': 'By ticking this box the score will not be assigned, '
227 'and the review will be visible to the student.',
231 dynaproperties = params_helper.getDynaFields(dynafields)
232 dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')
234 mentor_review_form = dynaform.newDynaForm(dynamodel=None,
235 dynabase=helper.forms.BaseForm, dynainclude=None,
236 dynaexclude=None, dynaproperties=dynaproperties)
237 self._params['mentor_review_form'] = mentor_review_form
239 dynafields = [
240 {'name': 'rank',
241 'base': forms.IntegerField,
242 'label': 'Set to rank',
243 'help_text':
244 'Set this proposal to the given rank (ignores the given score)',
245 'example_text': 'A rank will only be assigned if the '
246 'review is private!',
247 'min_value': 1,
248 'required': False,
249 'passthrough': ['min_value', 'required', 'help_text'],
251 {'name': 'mentor',
252 'base': widgets.ReferenceField,
253 'passthrough': ['reference_url', 'required', 'label', 'filter'],
254 'reference_url': 'mentor',
255 'filter': ['__org__'],
256 'label': 'Assign Mentor (Link ID)',
257 'required': False,
258 'help_text': 'Fill in the Link ID of the Mentor '
259 'you would like to assign to this Proposal. '
260 'Leave this box empty if you don\'t want any mentor assigned.',
264 dynaproperties = params_helper.getDynaFields(dynafields)
265 dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')
267 admin_review_form = dynaform.extendDynaForm(dynaform=mentor_review_form,
268 dynaproperties=dynaproperties)
270 self._params['admin_review_form'] = admin_review_form
272 def _editGet(self, request, entity, form):
273 """See base.View._editGet().
276 form.fields['link_id'].initial = entity.link_id
277 form.fields['organization'].initial = entity.org.link_id
279 return super(View, self)._editGet(request, entity, form)
281 def _editPost(self, request, entity, fields):
282 """See base.View._editPost().
285 if not entity:
286 fields['link_id'] = 't%i' % (int(time.time()*100))
287 else:
288 fields['link_id'] = entity.link_id
290 # fill in the scope via call to super
291 super(View, self)._editPost(request, entity, fields)
293 if not entity:
294 # creating a new application so set the program and org field
295 fields['program'] = fields['scope'].scope
297 filter = {'scope': fields['program'],
298 'link_id': fields['organization']}
299 fields['org'] = org_logic.logic.getForFields(filter, unique=True)
301 # explicitly change the last_modified_on since the content has been edited
302 fields['last_modified_on'] = datetime.datetime.now()
304 @decorators.merge_params
305 @decorators.check_access
306 def public(self, request, access_type,
307 page_name=None, params=None, **kwargs):
308 """View in which the student can see and reply to the comments on the
309 Student Proposal.
311 For params see base.view.Public().
314 context = helper.responses.getUniversalContext(request)
315 helper.responses.useJavaScript(context, params['js_uses_all'])
316 context['page_name'] = page_name
318 try:
319 entity = self._logic.getFromKeyFieldsOr404(kwargs)
320 except out_of_band.Error, error:
321 return helper.responses.errorResponse(
322 error, request, template=params['error_public'], context=context)
324 context['entity'] = entity
325 context['entity_type'] = params['name']
326 context['entity_type_url'] = params['url_name']
328 if request.method == 'POST':
329 return self.publicPost(request, context, params, entity, **kwargs)
330 else: # request.method == 'GET'
331 return self.publicGet(request, context, params, entity, **kwargs)
333 def publicPost(self, request, context, params, entity, **kwargs):
334 """Handles the POST request for the entity's public page.
336 Args:
337 entity: the student proposal entity
338 rest: see base.View.public()
341 # populate the form using the POST data
342 form = params['public_review_form'](request.POST)
344 if not form.is_valid():
345 # get some entity specific context
346 self.updatePublicContext(context, entity, params)
348 # return the invalid form response
349 return self._constructResponse(request, entity=entity, context=context,
350 form=form, params=params, template=params['public_template'])
352 # get the commentary
353 fields = form.cleaned_data
354 comment = fields['comment']
356 if comment:
357 # create a new public review containing the comment
358 user_entity = user_logic.logic.getForCurrentAccount()
359 # pylint: disable-msg=E1103
360 if user_entity.key() == entity.scope.user.key():
361 # student is posting
362 reviewer = entity.scope
363 else:
364 # check if the person commenting is an org_admin
365 # or a mentor for the given proposal
366 fields = {'user': user_entity,
367 'scope': entity.org,
368 'status': 'active',
371 reviewer = org_admin_logic.logic.getForFields(fields, unique=True)
373 if not reviewer:
374 # no org_admin found, maybe it's a mentor?
375 reviewer = mentor_logic.logic.getForFields(fields, unique=True)
377 # create the review (reviewer might be None
378 # if a Host or Developer is posting)
379 self._createReviewFor(entity, reviewer, comment, is_public=True)
381 # redirect to the same page
382 return http.HttpResponseRedirect('')
384 def publicGet(self, request, context, params, entity, **kwargs):
385 """Handles the GET request for the entity's public page.
387 Args:
388 entity: the student proposal entity
389 rest see base.View.public()
392 from soc.logic.models.review_follower import logic as review_follower_logic
394 get_dict = request.GET
396 if get_dict.get('subscription') and (
397 get_dict['subscription'] in ['on', 'off']):
399 subscription = get_dict['subscription']
401 # get the current user
402 user_entity = user_logic.logic.getForCurrentAccount()
404 # create the fields that should be in the ReviewFollower entity
405 # pylint: disable-msg=E1103
406 fields = {'link_id': user_entity.link_id,
407 'scope': entity,
408 'scope_path': entity.key().id_or_name(),
409 'user': user_entity
411 # get the keyname for the ReviewFollower entity
412 key_name = review_follower_logic.getKeyNameFromFields(fields)
414 # determine if we should set subscribed_public to True or False
415 if subscription == 'on':
416 fields['subscribed_public'] = True
417 elif subscription == 'off':
418 fields['subscribed_public'] = False
420 # update the ReviewFollower
421 review_follower_logic.updateOrCreateFromKeyName(fields, key_name)
423 # get some entity specific context
424 self.updatePublicContext(context, entity, params)
426 context['form'] = params['public_review_form']()
427 template = params['public_template']
429 return responses.respond(request, template, context=context)
431 def updatePublicContext(self, context, entity, params):
432 """Updates the context for the public page with information from the entity.
434 Args:
435 context: the context that should be updated
436 entity: a student proposal_entity used to set context
437 params: dict with params for the view using this context
440 from soc.logic.models.review import logic as review_logic
441 from soc.logic.models.review_follower import logic as review_follower_logic
443 student_entity = entity.scope
445 context['student'] = student_entity
446 context['student_name'] = student_entity.name()
448 user_entity = user_logic.logic.getForCurrentAccount()
450 # check if the current user is the student
451 # pylint: disable-msg=E1103
452 if user_entity.key() == student_entity.user.key():
453 # show the proposal edit link
454 context['edit_link'] = redirects.getEditRedirect(entity, params)
456 # check if the current user is subscribed to this proposal's public reviews
457 fields = {'user': user_entity,
458 'scope': entity,
459 'subscribed_public': True}
461 context['is_subscribed'] = review_follower_logic.getForFields(fields,
462 unique=True)
464 context['public_reviews'] = review_logic.getReviewsForEntity(entity,
465 is_public=True, order=['created'])
467 @decorators.merge_params
468 @decorators.check_access
469 def apply(self, request, access_type,
470 page_name=None, params=None, **kwargs):
471 """Special view used to prepopulate the form with the organization
472 contributors template.
474 For params see base.View.public()
476 get_dict = request.GET
478 if get_dict.get('organization'):
479 # organization chosen, prepopulate with template
481 # get the organization
482 student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])
483 program_entity = student_entity.scope
485 filter = {'link_id': get_dict['organization'],
486 'scope': program_entity}
488 org_entity = org_logic.logic.getForFields(filter, unique=True)
490 if org_entity:
491 # organization found use special form and also seed this form
492 params['create_form'] = params['student_create_form']
493 # pylint: disable-msg=E1103
494 kwargs['organization'] = org_entity.link_id
495 kwargs['content'] = org_entity.contrib_template
497 return super(View, self).create(request, access_type, page_name=page_name,
498 params=params, **kwargs)
500 @decorators.merge_params
501 @decorators.check_access
502 def edit(self, request, access_type,
503 page_name=None, params=None, seed=None, **kwargs):
504 """If the POST contains (action, Withdraw) the proposal in kwargs
505 will be marked as invalid.
507 For params see base.View.edit()
510 # check if request.POST contains action
511 post_dict = request.POST
512 if 'action' in post_dict and post_dict['action'] == 'Withdraw':
513 # withdraw this proposal
514 filter = {'scope_path': kwargs['scope_path'],
515 'link_id': kwargs['link_id']}
517 proposal_logic = params['logic']
518 student_proposal_entity = proposal_logic.getForFields(filter, unique=True)
519 reviewer = student_proposal_entity.scope
521 # update the entity mark it as invalid
522 proposal_logic.updateEntityProperties(student_proposal_entity,
523 {'status': 'invalid'})
525 # redirect to the program's homepage
526 redirect_url = redirects.getHomeRedirect(student_proposal_entity.program,
527 {'url_name': 'program'})
529 comment = "Student withdrew proposal."
530 self._createReviewFor(student_proposal_entity, reviewer, comment)
531 return http.HttpResponseRedirect(redirect_url)
533 return super(View, self).edit(request=request, access_type=access_type,
534 page_name=page_name, params=params, seed=seed, **kwargs)
536 @decorators.merge_params
537 @decorators.check_access
538 def listOrgs(self, request, access_type,
539 page_name=None, params=None, **kwargs):
540 """Lists all organization which the given student can propose to.
542 For params see base.View.public().
545 from soc.views.models import organization as org_view
547 student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])
549 filter = {'scope': student_entity.scope,
550 'status': 'active'}
552 list_params = org_view.view.getParams().copy()
553 list_params['list_description'] = ('List of %(name_plural)s you can send '
554 'your proposal to.') % list_params
555 list_params['list_action'] = (redirects.getStudentProposalRedirect,
556 {'student_key': student_entity.key().id_or_name(),
557 'url_name': params['url_name']})
559 return self.list(request, access_type=access_type, page_name=page_name,
560 params=list_params, filter=filter, **kwargs)
562 @decorators.merge_params
563 @decorators.check_access
564 def listSelf(self, request, access_type,
565 page_name=None, params=None, **kwargs):
566 """Lists all proposals from the current logged-in user
567 for the given student.
569 For params see base.View.public().
572 context = {}
573 student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])
575 filter = {'scope' : student_entity,
576 'status': ['new', 'pending', 'accepted', 'rejected']}
578 list_params = params.copy()
579 list_params['list_description'] = \
580 'List of my %(name_plural)s.' % list_params
581 list_params['list_action'] = (redirects.getPublicRedirect, list_params)
583 valid_list = lists.getListContent(
584 request, list_params, filter, idx=0)
586 ip_params = list_params.copy() # ineligible proposals
588 description = ugettext('List of my ineligible/withdrawn %s.') % (
589 ip_params['name_plural'])
591 ip_params['list_description'] = description
592 ip_params['list_action'] = (redirects.getPublicRedirect, ip_params)
594 filter = {'scope' : student_entity,
595 'status': 'invalid'}
597 ip_list = lists.getListContent(
598 request, ip_params, filter, idx=1, need_content=True)
600 contents = []
601 # fill contents with all the needed lists
602 contents.append(valid_list)
604 if ip_list != None:
605 contents.append(ip_list)
607 # call the _list method from base to display the list
608 return self._list(request, list_params, contents, page_name, context)
610 @decorators.merge_params
611 @decorators.check_access
612 def review(self, request, access_type,
613 page_name=None, params=None, **kwargs):
614 """View that allows Organization Admins and Mentors to review the proposal.
616 For Args see base.View.public().
619 from soc.logic.helper import timeline as timeline_helper
622 try:
623 entity = self._logic.getFromKeyFieldsOr404(kwargs)
624 except out_of_band.Error, error:
625 return helper.responses.errorResponse(
626 error, request, template=params['error_public'])
628 # get the context for this webpage
629 context = responses.getUniversalContext(request)
630 responses.useJavaScript(context, params['js_uses_all'])
631 context['page_name'] = '%s "%s" from %s' % (page_name, entity.title,
632 entity.scope.name())
633 context['entity'] = entity
634 context['entity_type'] = params['name']
635 context['entity_type_url'] = params['url_name']
637 program_entity = entity.program
639 if timeline_helper.isAfterEvent(program_entity.timeline,
640 'accepted_students_announced_deadline'):
641 return self.reviewAfterDeadline(request, context, params, entity,
642 **kwargs)
644 # get the roles important for reviewing an application
645 filter = {'user': user_logic.logic.getForCurrentAccount(),
646 'scope': entity.org,
647 'status': 'active'}
649 org_admin_entity = org_admin_logic.logic.getForFields(filter, unique=True)
650 mentor_entity = mentor_logic.logic.getForFields(filter, unique=True)
652 # decide which form to use
653 if org_admin_entity:
654 form = params['admin_review_form']
655 else:
656 form = params['mentor_review_form']
658 if request.method == 'POST':
659 return self.reviewPost(request, context, params, entity,
660 form, org_admin_entity, mentor_entity, **kwargs)
661 else:
662 # request.method == 'GET'
663 return self.reviewGet(request, context, params, entity,
664 form, org_admin_entity, mentor_entity, **kwargs)
666 def reviewPost(self, request, context, params, entity, form,
667 org_admin, mentor, **kwargs):
668 """Handles the POST request for the proposal review view.
670 Args:
671 entity: the student proposal entity
672 form: the form to use in this view
673 org_admin: org admin entity for the current user/proposal (iff available)
674 mentor: mentor entity for the current user/proposal (iff available)
675 rest: see base.View.public()
677 # populate the form using the POST data
678 form = form(request.POST)
680 if not form.is_valid():
681 # return the invalid form response
682 # get all the extra information that should be in the context
683 review_context = self._getDefaultReviewContext(entity, org_admin, mentor)
684 context = dicts.merge(context, review_context)
686 return self._constructResponse(request, entity=entity, context=context,
687 form=form, params=params, template=params['review_template'])
689 fields = form.cleaned_data
690 is_public = fields['public']
691 comment = fields['comment']
692 given_score = int(fields['score'])
694 if org_admin:
695 # org admin found, try to adjust the assigned mentor
696 self._adjustMentor(entity, fields['mentor'])
697 reviewer = org_admin
699 # try to see if the rank is given and adjust the given_score if needed
700 rank = fields['rank']
701 if rank:
702 ranker = self._logic.getRankerFor(entity)
703 # if a very high rank is filled in use the highest
704 # one that returns a score
705 rank = min(ranker.TotalRankedScores(), rank)
706 # ranker uses zero-based ranking
707 score_and_rank = ranker.FindScore(rank-1)
708 # get the score at the requested rank
709 score_at_rank = score_and_rank[0][0]
710 # calculate the score that should be given to end up at the given rank
711 # give +1 to make sure that in the case of a tie they end up top
712 given_score = score_at_rank - entity.score + 1
713 else:
714 # might be None (if Host or Developer is commenting)
715 reviewer = mentor
717 # store the properties to update the proposal with
718 properties = {}
720 if reviewer and (not is_public) and (given_score is not 0):
721 # if it is not a public comment and it's made by a member of the
722 # organization we update the score of the proposal
723 new_score = given_score + entity.score
724 properties = {'score': new_score}
726 if comment or (given_score is not 0):
727 # if the proposal is new we change it status to pending
728 if entity.status == 'new':
729 properties['status'] = 'pending'
731 # create the review entity
732 self._createReviewFor(entity, reviewer, comment, given_score, is_public)
734 if properties.values():
735 # there is something to update
736 self._logic.updateEntityProperties(entity, properties)
738 # redirect to the same page
739 return http.HttpResponseRedirect('')
741 def reviewGet(self, request, context, params, entity, form,
742 org_admin, mentor, **kwargs):
743 """Handles the GET request for the proposal review view.
745 Args:
746 entity: the student proposal entity
747 form: the form to use in this view
748 org_admin: org admin entity for the current user/proposal (iff available)
749 mentor: mentor entity for the current user/proposal (iff available)
750 rest: see base.View.public()
753 from soc.logic.models.review_follower import logic as review_follower_logic
755 get_dict = request.GET
757 # check if the current user is a mentor and wants
758 # to change his role for this app
759 choice = get_dict.get('mentor')
760 if mentor and choice:
761 self._adjustPossibleMentors(entity, mentor, choice)
763 ineligible = get_dict.get('ineligible')
765 if org_admin:
766 reviewer = org_admin
767 elif mentor:
768 reviewer = mentor
770 if (org_admin or mentor) and (ineligible != None) and (
771 entity.status not in ['accepted', 'rejected']):
772 ineligible = int(ineligible)
773 if ineligible == 1:
774 # mark the proposal invalid and return to the list
775 properties = {'status': 'invalid'}
776 self._logic.updateEntityProperties(entity, properties)
778 redirect = redirects.getListProposalsRedirect(entity.org,
779 {'url_name': 'org'})
780 comment = "Marked Student Proposal as Ineligible."
781 self._createReviewFor(entity, reviewer, comment, is_public=False)
782 return http.HttpResponseRedirect(redirect)
783 elif ineligible == 0:
784 # mark the proposal as new and return to the list
785 properties = {'status': 'new'}
786 self._logic.updateEntityProperties(entity, properties)
788 redirect = redirects.getListProposalsRedirect(entity.org,
789 {'url_name': 'org'})
790 comment = "Marked Student Proposal as Eligible."
791 self._createReviewFor(entity, reviewer, comment, is_public=False)
792 return http.HttpResponseRedirect(redirect)
794 # check if we should change the subscription state for the current user
795 public_subscription = None
796 private_subscription = None
798 if get_dict.get('public_subscription') and (
799 get_dict['public_subscription'] in ['on', 'off']):
801 public_subscription = get_dict['public_subscription'] == 'on'
803 if get_dict.get('private_subscription') and (
804 get_dict['private_subscription'] in ['on', 'off']):
805 private_subscription = get_dict['private_subscription'] == 'on'
807 if public_subscription != None or private_subscription != None:
808 # get the current user
809 user_entity = user_logic.logic.getForCurrentAccount()
811 # create the fields that should be in the ReviewFollower entity
812 # pylint: disable-msg=E1103
813 fields = {'link_id': user_entity.link_id,
814 'scope': entity,
815 'scope_path': entity.key().id_or_name(),
816 'user': user_entity
818 # get the keyname for the ReviewFollower entity
819 key_name = review_follower_logic.getKeyNameFromFields(fields)
821 # determine which subscription properties we should change
822 if public_subscription != None:
823 fields['subscribed_public'] = public_subscription
825 if private_subscription != None:
826 fields['subscribed_private'] = private_subscription
828 # update the ReviewFollower
829 review_follower_logic.updateOrCreateFromKeyName(fields, key_name)
831 # set the initial score since the default is ignored
832 initial = {'score': 0}
834 if org_admin and entity.mentor:
835 # set the mentor field to the current mentor
836 initial['mentor'] = entity.mentor.link_id
838 context['form'] = form(initial)
840 # create the special form for mentors
841 comment_public = ['public', 'comment']
842 comment_private = ['score']
843 comment_admin = ['rank', 'mentor']
844 class FilterForm(object):
845 """Helper class used for form filtering.
847 def __init__(self, form, fields):
848 self.__form = form
849 self.__fields = fields
851 @property
852 def fields(self):
853 """Property that returns all fields as dictionary."""
854 fields = self.__form.fields.iteritems()
855 return dict([(k, i) for k, i in fields if k in self.__fields])
857 def __iter__(self):
858 for field in self.__form:
859 if field.name not in self.__fields:
860 continue
861 yield field
863 _marker = []
864 def __getattr__(self, key, default=_marker):
865 if default is self._marker:
866 return getattr(self.__form, key)
867 else:
868 return getattr(self.__form, key, default)
870 context['form'] = form(initial)
871 context['comment_public'] = FilterForm(context['form'], comment_public)
872 context['comment_private'] = FilterForm(context['form'], comment_private)
873 context['comment_admin'] = FilterForm(context['form'], comment_admin)
875 # get all the extra information that should be in the context
876 review_context = self._getDefaultReviewContext(entity, org_admin, mentor)
877 context = dicts.merge(context, review_context)
879 template = params['review_template']
881 return responses.respond(request, template, context=context)
883 def reviewAfterDeadline(self, request, context, params, entity, **kwargs):
884 """View that shows the review view after the accepted students
885 announced deadline.
887 For Args see base.View.public().
890 review_context = self._getDefaultReviewContext(entity, None, None)
891 context = dicts.merge(context, review_context)
893 template = params['review_after_deadline_template']
895 return responses.respond(request, template, context=context)
897 def _getDefaultReviewContext(self, entity, org_admin,
898 mentor):
899 """Returns the default context for the review page.
901 Args:
902 entity: Student Proposal entity
903 org_admin: org admin entity for the current user/proposal (iff available)
904 mentor: mentor entity for the current user/proposal (iff available)
907 from soc.logic.models.review import logic as review_logic
908 from soc.logic.models.review_follower import logic as review_follower_logic
910 context = {}
912 context['student'] = entity.scope
913 context['student_name'] = entity.scope.name()
915 if entity.mentor:
916 context['mentor_name'] = entity.mentor.name()
917 else:
918 context['mentor_name'] = "No mentor assigned"
920 # set the possible mentors in the context
921 possible_mentors = entity.possible_mentors
923 if not possible_mentors:
924 context['possible_mentors'] = "None"
925 else:
926 mentor_names = []
928 for mentor_key in possible_mentors:
929 possible_mentor = mentor_logic.logic.getFromKeyName(
930 mentor_key.id_or_name())
931 mentor_names.append(possible_mentor.name())
933 context['possible_mentors'] = ', '.join(mentor_names)
935 # order the reviews by ascending creation date
936 order = ['created']
938 # get the public reviews
939 public_reviews = review_logic.getReviewsForEntity(entity,
940 is_public=True, order=order)
942 # get the private reviews
943 private_reviews = review_logic.getReviewsForEntity(entity,
944 is_public=False, order=order)
946 # store the reviews in the context
947 context['public_reviews'] = public_reviews
948 context['private_reviews'] = private_reviews
950 # create a summary of all the private reviews
951 review_summary = {}
953 for private_review in private_reviews:
954 # make sure there is a reviewer
955 reviewer = private_review.reviewer
956 if not reviewer:
957 continue
959 reviewer_key = reviewer.key()
960 reviewer_summary = review_summary.get(reviewer_key)
962 if reviewer_summary:
963 # we already have something on file for this reviewer
964 old_total_score = reviewer_summary['total_score']
965 reviewer_summary['total_score'] = old_total_score + private_review.score
967 old_total_comments = reviewer_summary['total_comments']
968 reviewer_summary['total_comments'] = old_total_comments + 1
969 else:
970 review_summary[reviewer_key] = {
971 'name': reviewer.name(),
972 'total_comments': 1,
973 'total_score': private_review.score}
975 context['review_summary'] = review_summary
977 # which button should we show to the mentor?
978 if mentor:
979 context['is_mentor'] = True
980 if mentor.key() in possible_mentors:
981 # show "No longer willing to mentor"
982 context['remove_me_as_mentor'] = True
983 else:
984 # show "I am willing to mentor"
985 context['add_me_as_mentor'] = True
987 if org_admin:
988 context['is_org_admin'] = True
990 user_entity = user_logic.logic.getForCurrentAccount()
992 # check if the current user is subscribed to public or private reviews
993 fields = {'scope': entity,
994 'user': user_entity,}
995 follower_entity = review_follower_logic.getForFields(fields, unique=True)
997 if follower_entity:
998 # pylint: disable-msg=E1103
999 context['is_subscribed_public'] = follower_entity.subscribed_public
1000 context['is_subscribed_private'] = follower_entity.subscribed_private
1002 return context
1004 def _adjustPossibleMentors(self, entity, mentor, choice):
1005 """Adjusts the possible mentors list for a proposal.
1007 Args:
1008 entity: Student Proposal entity
1009 mentor: Mentor entity
1010 choice: 1 means want to mentor, 0 do not want to mentor
1012 possible_mentors = entity.possible_mentors
1014 if choice == '1':
1015 # add the mentor to possible mentors list if not already in
1016 if mentor.key() not in possible_mentors:
1017 possible_mentors.append(mentor.key())
1018 fields = {'possible_mentors': possible_mentors}
1019 self._logic.updateEntityProperties(entity, fields)
1020 elif choice == '0':
1021 # remove the mentor from the possible mentors list
1022 if mentor.key() in possible_mentors:
1023 possible_mentors.remove(mentor.key())
1024 fields = {'possible_mentors': possible_mentors}
1025 self._logic.updateEntityProperties(entity, fields)
1027 def _adjustMentor(self, entity, mentor_id):
1028 """Changes the mentor to the given link_id.
1030 Args:
1031 entity: Student Proposal entity
1032 mentor_id: Link ID of the mentor that needs to be assigned
1033 Iff not given then removes the assigned mentor
1036 if entity.mentor and entity.mentor.link_id == mentor_id:
1037 # no need to change
1038 return
1040 if mentor_id:
1041 # try to locate the mentor
1042 fields = {'link_id': mentor_id,
1043 'scope': entity.org,
1044 'status': 'active'}
1046 mentor_entity = mentor_logic.logic.getForFields(fields, unique=True)
1048 if not mentor_entity:
1049 # no mentor found, do not update
1050 return
1051 else:
1052 # reset to None
1053 mentor_entity = None
1055 # update the proposal
1056 properties = {'mentor': mentor_entity}
1057 self._logic.updateEntityProperties(entity, properties)
1059 def _createReviewFor(self, entity, reviewer, comment,
1060 score=0, is_public=True):
1061 """Creates a review for the given proposal and sends
1062 out a message to all followers.
1064 Args:
1065 entity: Student Proposal entity for which the review should be created
1066 reviewer: A role entity of the reviewer (if possible, else None)
1067 comment: The textual contents of the review
1068 score: The score of the review (only used if the review is not public)
1069 is_public: Determines if the review is a public review
1072 from soc.logic.helper import notifications as notifications_helper
1073 from soc.logic.models.review import logic as review_logic
1074 from soc.logic.models.review_follower import logic as review_follower_logic
1076 # create the fields for the review entity
1077 fields = {'link_id': 't%i' % (int(time.time()*100)),
1078 'scope': entity,
1079 'scope_path': entity.key().id_or_name(),
1080 'author': user_logic.logic.getForCurrentAccount(),
1081 'content': comment,
1082 'is_public': is_public,
1083 'reviewer': reviewer
1086 # add the given score if the review is not public
1087 if not is_public:
1088 fields['score'] = score
1090 # create a new Review
1091 key_name = review_logic.getKeyNameFromFields(fields)
1092 review_entity = review_logic.updateOrCreateFromKeyName(fields, key_name)
1094 # get all followers
1095 fields = {'scope': entity}
1097 if is_public:
1098 fields['subscribed_public'] = True
1099 else:
1100 fields['subscribed_private'] = True
1102 followers = review_follower_logic.getForFields(fields)
1104 if is_public:
1105 # redirect to public page
1106 redirect_url = redirects.getPublicRedirect(entity, self._params)
1107 else:
1108 # redirect to review page
1109 redirect_url = redirects.getReviewRedirect(entity, self._params)
1111 for follower in followers:
1112 # sent to every follower except the reviewer
1113 if follower.user.key() != review_entity.author.key():
1114 notifications_helper.sendNewReviewNotification(follower.user,
1115 review_entity, entity.title, redirect_url)
1118 view = View()
1120 admin = decorators.view(view.admin)
1121 apply = decorators.view(view.apply)
1122 create = decorators.view(view.create)
1123 delete = decorators.view(view.delete)
1124 edit = decorators.view(view.edit)
1125 list = decorators.view(view.list)
1126 list_orgs = decorators.view(view.listOrgs)
1127 list_self = decorators.view(view.listSelf)
1128 public = decorators.view(view.public)
1129 review = decorators.view(view.review)
1130 export = decorators.view(view.export)
1131 pick = decorators.view(view.pick)