Removed TODO that has been completed in r2876.
[Melange.git] / app / soc / views / models / student_proposal.py
blob29dad91d665d204d87618f592621e8397ecf02b8
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'], ['new', 'pending', 'invalid']])]
91 new_params = {}
92 new_params['logic'] = soc.logic.models.student_proposal.logic
93 new_params['rights'] = rights
94 new_params['name'] = "Student Proposal"
95 new_params['url_name'] = "student_proposal"
96 new_params['sidebar_grouping'] = 'Students'
98 new_params['scope_view'] = student_view
99 new_params['scope_redirect'] = redirects.getCreateRedirect
101 new_params['no_create_with_key_fields'] = True
103 patterns = [
104 (r'^%(url_name)s/(?P<access_type>apply)/%(scope)s$',
105 'soc.views.models.%(module_name)s.apply',
106 'Create a new %(name)s'),
107 (r'^%(url_name)s/(?P<access_type>list_self)/%(scope)s$',
108 'soc.views.models.%(module_name)s.list_self',
109 'List my %(name_plural)s'),
110 (r'^%(url_name)s/(?P<access_type>list_orgs)/%(scope)s$',
111 'soc.views.models.%(module_name)s.list_orgs',
112 'List my %(name_plural)s'),
113 (r'^%(url_name)s/(?P<access_type>review)/%(key_fields)s$',
114 'soc.views.models.%(module_name)s.review',
115 'Review %(name)s'),
118 new_params['extra_django_patterns'] = patterns
120 new_params['extra_dynaexclude'] = ['org', 'program', 'score',
121 'status', 'mentor', 'link_id',
122 'possible_mentors']
124 new_params['create_extra_dynaproperties'] = {
125 'content': forms.fields.CharField(required=True,
126 widget=widgets.FullTinyMCE(attrs={'rows': 25, 'cols': 100})),
127 'scope_path': forms.CharField(widget=forms.HiddenInput,
128 required=True),
129 'organization': forms.CharField(label='Organization Link ID',
130 required=True),
131 'clean_abstract': cleaning.clean_content_length('abstract'),
132 'clean_content': cleaning.clean_html_content('content'),
133 'clean_organization': cleaning.clean_link_id('organization'),
134 'clean_additional_info': cleaning.clean_url('additional_info'),
135 'clean': cleaning.validate_student_proposal('organization',
136 'scope_path', student_logic, org_logic),
139 new_params['edit_extra_dynaproperties'] = {
140 'organization': forms.CharField(label='Organization Link ID',
141 widget=widgets.ReadOnlyInput),
142 'link_id': forms.CharField(widget=forms.HiddenInput)
145 new_params['edit_template'] = 'soc/student_proposal/edit.html'
146 new_params['review_template'] = 'soc/student_proposal/review.html'
148 params = dicts.merge(params, new_params)
150 super(View, self).__init__(params=params)
152 # create the special form for students
153 dynafields = [
154 {'name': 'organization',
155 'base': forms.CharField,
156 'label': 'Organization Link ID',
157 'widget': widgets.ReadOnlyInput(),
158 'required': False,
162 dynaproperties = params_helper.getDynaFields(dynafields)
164 student_create_form = dynaform.extendDynaForm(
165 dynaform=self._params['create_form'],
166 dynaproperties=dynaproperties)
168 params['student_create_form'] = student_create_form
170 # create the special form for public review
171 dynafields = [
172 {'name': 'comment',
173 'base': forms.CharField,
174 'widget': widgets.FullTinyMCE(attrs={'rows': 10, 'cols': 40}),
175 'label': 'Comment',
176 'required': False,
180 dynaproperties = params_helper.getDynaFields(dynafields)
181 dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')
183 public_review_form = dynaform.newDynaForm(dynamodel=None,
184 dynabase=helper.forms.BaseForm, dynainclude=None,
185 dynaexclude=None, dynaproperties=dynaproperties)
186 params['public_review_form'] = public_review_form
188 # create the special form for mentors
189 dynafields = [
190 {'name': 'score',
191 'base': forms.ChoiceField,
192 'label': 'Score',
193 'initial': 0,
194 'required': False,
195 'passthrough': ['initial', 'required', 'choices'],
196 'example_text':
197 'A score will only be assigned if the review is private!',
198 'choices': [(-4,'-4: Wow. This. Sucks.'),
199 (-3,'-3: Needs a lot of work'),
200 (-2,'-2: This is bad'),
201 (-1,'-1: I dont like this'),
202 (0,'0: No score'),
203 (1,'1: Might have potential'),
204 (2,'2: Good'),
205 (3,'3: Almost there'),
206 (4,'4: Made. Of. Awesome.')]
208 {'name': 'comment',
209 'base': forms.CharField,
210 'widget': widgets.FullTinyMCE(attrs={'rows': 10, 'cols': 40}),
211 'label': 'Comment',
212 'required': False,
214 {'name': 'public',
215 'base': forms.BooleanField,
216 'label': 'Review visible to Student',
217 'initial': False,
218 'required': False,
219 'help_text': 'By ticking this box the score will not be assigned, '
220 'and the review will be visible to the student.',
224 dynaproperties = params_helper.getDynaFields(dynafields)
225 dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')
227 mentor_review_form = dynaform.newDynaForm(dynamodel=None,
228 dynabase=helper.forms.BaseForm, dynainclude=None,
229 dynaexclude=None, dynaproperties=dynaproperties)
230 params['mentor_review_form'] = mentor_review_form
232 dynafields = [
233 {'name': 'rank',
234 'base': forms.IntegerField,
235 'label': 'Set to rank',
236 'help_text':
237 'Set this proposal to the given rank (ignores the given score)',
238 'example_text': 'A rank will only be assigned if the '
239 'review is private!',
240 'min_value': 1,
241 'required': False,
242 'passthrough': ['min_value', 'required', 'help_text'],
244 {'name': 'mentor',
245 'base': widgets.ReferenceField,
246 'passthrough': ['reference_url', 'required', 'label', 'filter'],
247 'reference_url': 'mentor',
248 'filter': ['__org__'],
249 'label': 'Assign Mentor (Link ID)',
250 'required': False,
251 'help_text': 'Fill in the Link ID of the Mentor '
252 'you would like to assign to this Proposal. '
253 'Leave this box empty if you don\'t want any mentor assigned.',
257 dynaproperties = params_helper.getDynaFields(dynafields)
258 dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')
260 admin_review_form = dynaform.extendDynaForm(dynaform=mentor_review_form,
261 dynaproperties=dynaproperties)
263 params['admin_review_form'] = admin_review_form
265 def _editGet(self, request, entity, form):
266 """See base.View._editGet().
269 form.fields['link_id'].initial = entity.link_id
270 form.fields['organization'].initial = entity.org.link_id
272 return super(View, self)._editGet(request, entity, form)
274 def _editPost(self, request, entity, fields):
275 """See base.View._editPost().
278 if not entity:
279 fields['link_id'] = 't%i' % (int(time.time()*100))
280 else:
281 fields['link_id'] = entity.link_id
283 # fill in the scope via call to super
284 super(View, self)._editPost(request, entity, fields)
286 if not entity:
287 # creating a new application so set the program and org field
288 fields['program'] = fields['scope'].scope
290 filter = {'scope': fields['program'],
291 'link_id': fields['organization']}
292 fields['org'] = org_logic.logic.getForFields(filter, unique=True)
294 # explicitly change the last_modified_on since the content has been edited
295 fields['last_modified_on'] = datetime.datetime.now()
297 @decorators.merge_params
298 @decorators.check_access
299 def public(self, request, access_type,
300 page_name=None, params=None, **kwargs):
301 """View in which the student can see and reply to the comments on the
302 Student Proposal.
304 For params see base.view.Public().
307 context = helper.responses.getUniversalContext(request)
308 helper.responses.useJavaScript(context, params['js_uses_all'])
309 context['page_name'] = page_name
311 try:
312 entity = self._logic.getFromKeyFieldsOr404(kwargs)
313 except out_of_band.Error, error:
314 return helper.responses.errorResponse(
315 error, request, template=params['error_public'], context=context)
317 context['entity'] = entity
318 context['entity_type'] = params['name']
319 context['entity_type_url'] = params['url_name']
321 if request.method == 'POST':
322 return self.publicPost(request, context, params, entity, **kwargs)
323 else: # request.method == 'GET'
324 return self.publicGet(request, context, params, entity, **kwargs)
326 def publicPost(self, request, context, params, entity, **kwargs):
327 """Handles the POST request for the entity's public page.
329 Args:
330 entity: the student proposal entity
331 rest: see base.View.public()
334 # populate the form using the POST data
335 form = params['public_review_form'](request.POST)
337 if not form.is_valid():
338 # get some entity specific context
339 self.updatePublicContext(context, entity, params)
341 # return the invalid form response
342 return self._constructResponse(request, entity=entity, context=context,
343 form=form, params=params, template=params['public_template'])
345 # get the commentary
346 fields = form.cleaned_data
347 comment = fields['comment']
349 if comment:
350 # create a new public review containing the comment
351 user_entity = user_logic.logic.getForCurrentAccount()
353 if user_entity.key() == entity.scope.user.key():
354 # student is posting
355 reviewer = entity.scope
356 else:
357 # check if the person commenting is an org_admin
358 # or a mentor for the given proposal
359 fields = {'user': user_entity,
360 'scope': entity.org,
361 'status': 'active',
364 reviewer = org_admin_logic.logic.getForFields(fields, unique=True)
366 if not reviewer:
367 # no org_admin found, maybe it's a mentor?
368 reviewer = mentor_logic.logic.getForFields(fields, unique=True)
370 # create the review (reviewer might be None
371 # if a Host or Developer is posting)
372 self._createReviewFor(entity, reviewer, comment, is_public=True)
374 # redirect to the same page
375 return http.HttpResponseRedirect('')
377 def publicGet(self, request, context, params, entity, **kwargs):
378 """Handles the GET request for the entity's public page.
380 Args:
381 entity: the student proposal entity
382 rest see base.View.public()
385 from soc.logic.models.review_follower import logic as review_follower_logic
387 get_dict = request.GET
389 if get_dict.get('subscription') and (
390 get_dict['subscription'] in ['on', 'off']):
392 subscription = get_dict['subscription']
394 # get the current user
395 user_entity = user_logic.logic.getForCurrentAccount()
397 # create the fields that should be in the ReviewFollower entity
398 fields = {'link_id': user_entity.link_id,
399 'scope': entity,
400 'scope_path': entity.key().name(),
401 'user': user_entity
403 # get the keyname for the ReviewFollower entity
404 key_name = review_follower_logic.getKeyNameFromFields(fields)
406 # determine if we should set subscribed_public to True or False
407 if subscription == 'on':
408 fields['subscribed_public'] = True
409 elif subscription == 'off':
410 fields['subscribed_public'] = False
412 # update the ReviewFollower
413 review_follower_logic.updateOrCreateFromKeyName(fields, key_name)
415 # get some entity specific context
416 self.updatePublicContext(context, entity, params)
418 context['form'] = params['public_review_form']()
419 template = params['public_template']
421 return responses.respond(request, template, context=context)
423 def updatePublicContext(self, context, entity, params):
424 """Updates the context for the public page with information from the entity.
426 Args:
427 context: the context that should be updated
428 entity: a student proposal_entity used to set context
429 params: dict with params for the view using this context
432 from soc.logic.models.review import logic as review_logic
433 from soc.logic.models.review_follower import logic as review_follower_logic
435 student_entity = entity.scope
437 context['student'] = student_entity
438 context['student_name'] = student_entity.name()
440 user_entity = user_logic.logic.getForCurrentAccount()
442 # check if the current user is the student
443 if user_entity.key() == student_entity.user.key():
444 # show the proposal edit link
445 context['edit_link'] = redirects.getEditRedirect(entity, params)
447 # check if the current user is subscribed to this proposal's public reviews
448 fields = {'user': user_entity,
449 'scope': entity,
450 'subscribed_public': True}
452 context['is_subscribed'] = review_follower_logic.getForFields(fields,
453 unique=True)
455 context['public_reviews'] = review_logic.getReviewsForEntity(entity,
456 is_public=True, order=['created'])
458 @decorators.merge_params
459 @decorators.check_access
460 def apply(self, request, access_type,
461 page_name=None, params=None, **kwargs):
462 """Special view used to prepopulate the form with the organization
463 contributors template.
465 For params see base.View.public()
467 get_dict = request.GET
469 if get_dict.get('organization'):
470 # organization chosen, prepopulate with template
472 # get the organization
473 student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])
474 program_entity = student_entity.scope
476 filter = {'link_id': get_dict['organization'],
477 'scope': program_entity}
479 org_entity = org_logic.logic.getForFields(filter, unique=True)
481 if org_entity:
482 # organization found use special form
483 params['create_form'] = params['student_create_form']
484 kwargs['content'] = org_entity.contrib_template
486 # Create page is an edit page with no key fields
487 empty_kwargs = {}
488 fields = self._logic.getKeyFieldNames()
489 for field in fields:
490 empty_kwargs[field] = None
492 return super(View, self).edit(request, access_type, page_name=page_name,
493 params=params, seed=kwargs, **empty_kwargs)
495 @decorators.merge_params
496 @decorators.check_access
497 def edit(self, request, access_type,
498 page_name=None, params=None, seed=None, **kwargs):
499 """If the POST contains (action, Withdraw) the proposal in kwargs
500 will be marked as invalid.
502 For params see base.View.edit()
505 # check if request.POST contains action
506 post_dict = request.POST
507 if 'action' in post_dict and post_dict['action'] == 'Withdraw':
508 # withdraw this proposal
509 filter = {'scope_path': kwargs['scope_path'],
510 'link_id': kwargs['link_id']}
512 proposal_logic = params['logic']
513 student_proposal_entity = proposal_logic.getForFields(filter, unique=True)
514 reviewer = student_proposal_entity.scope
516 # update the entity mark it as invalid
517 proposal_logic.updateEntityProperties(student_proposal_entity,
518 {'status': 'invalid'})
520 # redirect to the program's homepage
521 redirect_url = redirects.getHomeRedirect(student_proposal_entity.program,
522 {'url_name': 'program'})
524 comment = "Student withdrew proposal."
525 self._createReviewFor(student_proposal_entity, reviewer, comment)
526 return http.HttpResponseRedirect(redirect_url)
528 return super(View, self).edit(request=request, access_type=access_type,
529 page_name=page_name, params=params, seed=seed, **kwargs)
531 @decorators.merge_params
532 @decorators.check_access
533 def listOrgs(self, request, access_type,
534 page_name=None, params=None, **kwargs):
535 """Lists all organization which the given student can propose to.
537 For params see base.View.public().
540 from soc.views.models import organization as org_view
542 student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])
544 filter = {'scope': student_entity.scope,
545 'status': 'active'}
547 list_params = org_view.view.getParams().copy()
548 list_params['list_description'] = ('List of %(name_plural)s you can send '
549 'your proposal to.') % list_params
550 list_params['list_action'] = (redirects.getStudentProposalRedirect,
551 {'student_key': student_entity.key().name(),
552 'url_name': params['url_name']})
554 return self.list(request, access_type=access_type, page_name=page_name,
555 params=list_params, filter=filter, **kwargs)
557 @decorators.merge_params
558 @decorators.check_access
559 def listSelf(self, request, access_type,
560 page_name=None, params=None, **kwargs):
561 """Lists all proposals from the current logged-in user
562 for the given student.
564 For params see base.View.public().
567 context = {}
568 student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])
570 filter = {'scope' : student_entity,
571 'status': ['new', 'pending', 'accepted', 'rejected']}
573 list_params = params.copy()
574 list_params['list_description'] = \
575 'List of my %(name_plural)s.' % list_params
576 list_params['list_action'] = (redirects.getPublicRedirect, list_params)
578 valid_list = lists.getListContent(
579 request, list_params, filter, idx=0)
581 ip_params = list_params.copy() # ineligible proposals
583 description = ugettext('List of my ineligible/withdrawn %s.') % (
584 ip_params['name_plural'])
586 ip_params['list_description'] = description
587 ip_params['list_action'] = (redirects.getPublicRedirect, ip_params)
589 filter = {'scope' : student_entity,
590 'status': 'invalid'}
592 ip_list = lists.getListContent(
593 request, ip_params, filter, idx=1, need_content=True)
595 contents = []
596 # fill contents with all the needed lists
597 contents.append(valid_list)
599 if ip_list != None:
600 contents.append(ip_list)
602 # call the _list method from base to display the list
603 return self._list(request, list_params, contents, page_name, context)
605 @decorators.merge_params
606 @decorators.check_access
607 def review(self, request, access_type,
608 page_name=None, params=None, **kwargs):
609 """View that allows Organization Admins and Mentors to review the proposal.
611 For Args see base.View.public().
614 try:
615 entity = self._logic.getFromKeyFieldsOr404(kwargs)
616 except out_of_band.Error, error:
617 return helper.responses.errorResponse(
618 error, request, template=params['error_public'])
620 # get the context for this webpage
621 context = responses.getUniversalContext(request)
622 responses.useJavaScript(context, params['js_uses_all'])
623 context['page_name'] = '%s "%s" from %s' % (page_name, entity.title,
624 entity.scope.name())
625 context['entity'] = entity
626 context['entity_type'] = params['name']
627 context['entity_type_url'] = params['url_name']
629 # get the roles important for reviewing an application
630 filter = {'user': user_logic.logic.getForCurrentAccount(),
631 'scope': entity.org,
632 'status': 'active'}
634 org_admin_entity = org_admin_logic.logic.getForFields(filter, unique=True)
635 mentor_entity = mentor_logic.logic.getForFields(filter, unique=True)
637 # decide which form to use
638 if org_admin_entity:
639 form = params['admin_review_form']
640 else:
641 form = params['mentor_review_form']
643 if request.method == 'POST':
644 return self.reviewPost(request, context, params, entity,
645 form, org_admin_entity, mentor_entity, **kwargs)
646 else:
647 # request.method == 'GET'
648 return self.reviewGet(request, context, params, entity,
649 form, org_admin_entity, mentor_entity, **kwargs)
651 def reviewPost(self, request, context, params, entity, form,
652 org_admin, mentor, **kwargs):
653 """Handles the POST request for the proposal review view.
655 Args:
656 entity: the student proposal entity
657 form: the form to use in this view
658 org_admin: org admin entity for the current user/proposal (iff available)
659 mentor: mentor entity for the current user/proposal (iff available)
660 rest: see base.View.public()
662 # populate the form using the POST data
663 form = form(request.POST)
665 if not form.is_valid():
666 # return the invalid form response
667 # get all the extra information that should be in the context
668 review_context = self._getDefaultReviewContext(entity, org_admin, mentor)
669 context = dicts.merge(context, review_context)
671 return self._constructResponse(request, entity=entity, context=context,
672 form=form, params=params, template=params['review_template'])
674 fields = form.cleaned_data
675 is_public = fields['public']
676 comment = fields['comment']
677 given_score = int(fields['score'])
679 if org_admin:
680 # org admin found, try to adjust the assigned mentor
681 self._adjustMentor(entity, fields['mentor'])
682 reviewer = org_admin
684 # try to see if the rank is given and adjust the given_score if needed
685 rank = fields['rank']
686 if rank:
687 ranker = self._logic.getRankerFor(entity)
688 # if a very high rank is filled in use the highest
689 # one that returns a score
690 rank = min(ranker.TotalRankedScores(), rank)
691 # ranker uses zero-based ranking
692 score_and_rank = ranker.FindScore(rank-1)
693 # get the score at the requested rank
694 score_at_rank = score_and_rank[0][0]
695 # calculate the score that should be given to end up at the given rank
696 given_score = score_at_rank - entity.score
697 else:
698 # might be None (if Host or Developer is commenting)
699 reviewer = mentor
701 # store the properties to update the proposal with
702 properties = {}
704 if reviewer and (not is_public) and (given_score is not 0):
705 # if it is not a public comment and it's made by a member of the
706 # organization we update the score of the proposal
707 new_score = given_score + entity.score
708 properties = {'score': new_score}
710 if comment or (given_score is not 0):
711 # if the proposal is new we change it status to pending
712 if entity.status == 'new':
713 properties['status'] = 'pending'
715 # create the review entity
716 self._createReviewFor(entity, reviewer, comment, given_score, is_public)
718 if properties.values():
719 # there is something to update
720 self._logic.updateEntityProperties(entity, properties)
722 # redirect to the same page
723 return http.HttpResponseRedirect('')
725 def reviewGet(self, request, context, params, entity, form,
726 org_admin, mentor, **kwargs):
727 """Handles the GET request for the proposal review view.
729 Args:
730 entity: the student proposal entity
731 form: the form to use in this view
732 org_admin: org admin entity for the current user/proposal (iff available)
733 mentor: mentor entity for the current user/proposal (iff available)
734 rest: see base.View.public()
737 from soc.logic.models.review_follower import logic as review_follower_logic
739 get_dict = request.GET
741 # check if the current user is a mentor and wants
742 # to change his role for this app
743 choice = get_dict.get('mentor')
744 if mentor and choice:
745 self._adjustPossibleMentors(entity, mentor, choice)
747 ineligible = get_dict.get('ineligible')
749 if org_admin:
750 reviewer = org_admin
751 elif mentor:
752 reviewer = mentor
754 if (org_admin or mentor) and ineligible != None:
755 ineligible = int(ineligible)
756 if ineligible == 1:
757 # mark the proposal invalid and return to the list
758 properties = {'status': 'invalid'}
759 self._logic.updateEntityProperties(entity, properties)
761 redirect = redirects.getListProposalsRedirect(entity.org,
762 {'url_name': 'org'})
763 comment = "Marked Student Proposal as Ineligible."
764 self._createReviewFor(entity, reviewer, comment, is_public=False)
765 return http.HttpResponseRedirect(redirect)
766 elif ineligible == 0:
767 # mark the proposal as new and return to the list
768 properties = {'status': 'new'}
769 self._logic.updateEntityProperties(entity, properties)
771 redirect = redirects.getListProposalsRedirect(entity.org,
772 {'url_name': 'org'})
773 comment = "Marked Student Proposal as Eligible."
774 self._createReviewFor(entity, reviewer, comment, is_public=False)
775 return http.HttpResponseRedirect(redirect)
777 # check if we should change the subscription state for the current user
778 public_subscription = None
779 private_subscription = None
781 if get_dict.get('public_subscription') and (
782 get_dict['public_subscription'] in ['on', 'off']):
784 public_subscription = get_dict['public_subscription'] == 'on'
786 if get_dict.get('private_subscription') and (
787 get_dict['private_subscription'] in ['on', 'off']):
788 private_subscription = get_dict['private_subscription'] == 'on'
790 if public_subscription != None or private_subscription != None:
791 # get the current user
792 user_entity = user_logic.logic.getForCurrentAccount()
794 # create the fields that should be in the ReviewFollower entity
795 fields = {'link_id': user_entity.link_id,
796 'scope': entity,
797 'scope_path': entity.key().name(),
798 'user': user_entity
800 # get the keyname for the ReviewFollower entity
801 key_name = review_follower_logic.getKeyNameFromFields(fields)
803 # determine which subscription properties we should change
804 if public_subscription != None:
805 fields['subscribed_public'] = public_subscription
807 if private_subscription != None:
808 fields['subscribed_private'] = private_subscription
810 # update the ReviewFollower
811 review_follower_logic.updateOrCreateFromKeyName(fields, key_name)
813 # set the initial score since the default is ignored
814 initial = {'score': 0}
816 if org_admin and entity.mentor:
817 # set the mentor field to the current mentor
818 initial['mentor'] = entity.mentor.link_id
820 context['form'] = form(initial)
822 # create the special form for mentors
823 comment_public = ['public', 'comment']
824 comment_private = ['score']
825 comment_admin = ['rank', 'mentor']
826 class FilterForm(object):
827 """Helper class used for form filtering.
829 def __init__(self, form, fields):
830 self.__form = form
831 self.__fields = fields
833 @property
834 def fields(self):
835 """Property that returns all fields as dictionary."""
836 fields = self.__form.fields.iteritems()
837 return dict([(k, i) for k, i in fields if k in self.__fields])
839 def __iter__(self):
840 for field in self.__form:
841 if field.name not in self.__fields:
842 continue
843 yield field
845 _marker = []
846 def __getattr__(self, key, default=_marker):
847 if default is self._marker:
848 return getattr(self.__form, key)
849 else:
850 return getattr(self.__form, key, default)
852 context['form'] = form(initial)
853 context['comment_public'] = FilterForm(context['form'], comment_public)
854 context['comment_private'] = FilterForm(context['form'], comment_private)
855 context['comment_admin'] = FilterForm(context['form'], comment_admin)
857 # get all the extra information that should be in the context
858 review_context = self._getDefaultReviewContext(entity, org_admin, mentor)
859 context = dicts.merge(context, review_context)
861 template = params['review_template']
863 return responses.respond(request, template, context=context)
865 def _getDefaultReviewContext(self, entity, org_admin,
866 mentor):
867 """Returns the default context for the review page.
869 Args:
870 entity: Student Proposal entity
871 org_admin: org admin entity for the current user/proposal (iff available)
872 mentor: mentor entity for the current user/proposal (iff available)
875 from soc.logic.models.review import logic as review_logic
876 from soc.logic.models.review_follower import logic as review_follower_logic
878 context = {}
880 context['student'] = entity.scope
881 context['student_name'] = entity.scope.name()
883 if entity.mentor:
884 context['mentor_name'] = entity.mentor.name()
885 else:
886 context['mentor_name'] = "No mentor assigned"
888 # set the possible mentors in the context
889 possible_mentors = entity.possible_mentors
891 if not possible_mentors:
892 context['possible_mentors'] = "None"
893 else:
894 mentor_names = []
896 for mentor_key in possible_mentors:
897 possible_mentor = mentor_logic.logic.getFromKeyName(mentor_key.name())
898 mentor_names.append(possible_mentor.name())
900 context['possible_mentors'] = ', '.join(mentor_names)
902 # TODO(ljvderijk) listing of total given scores per mentor
903 # a dict with key as role.user ?
905 # order the reviews by ascending creation date
906 order = ['created']
908 # get the public reviews
909 context['public_reviews'] = review_logic.getReviewsForEntity(entity,
910 is_public=True, order=order)
912 # get the private reviews
913 context['private_reviews'] = review_logic.getReviewsForEntity(entity,
914 is_public=False, order=order)
916 # which button should we show to the mentor?
917 if mentor:
918 context['is_mentor'] = True
919 if mentor.key() in possible_mentors:
920 # show "No longer willing to mentor"
921 context['remove_me_as_mentor'] = True
922 else:
923 # show "I am willing to mentor"
924 context['add_me_as_mentor'] = True
926 if org_admin:
927 context['is_org_admin'] = True
929 user_entity = user_logic.logic.getForCurrentAccount()
931 # check if the current user is subscribed to public or private reviews
932 fields = {'scope': entity,
933 'user': user_entity,}
934 follower_entity = review_follower_logic.getForFields(fields, unique=True)
936 if follower_entity:
937 context['is_subscribed_public'] = follower_entity.subscribed_public
938 context['is_subscribed_private'] = follower_entity.subscribed_private
940 return context
942 def _adjustPossibleMentors(self, entity, mentor, choice):
943 """Adjusts the possible mentors list for a proposal.
945 Args:
946 entity: Student Proposal entity
947 mentor: Mentor entity
948 choice: 1 means want to mentor, 0 do not want to mentor
950 possible_mentors = entity.possible_mentors
952 if choice == '1':
953 # add the mentor to possible mentors list if not already in
954 if mentor.key() not in possible_mentors:
955 possible_mentors.append(mentor.key())
956 fields = {'possible_mentors': possible_mentors}
957 self._logic.updateEntityProperties(entity, fields)
958 elif choice == '0':
959 # remove the mentor from the possible mentors list
960 if mentor.key() in possible_mentors:
961 possible_mentors.remove(mentor.key())
962 fields = {'possible_mentors': possible_mentors}
963 self._logic.updateEntityProperties(entity, fields)
965 def _adjustMentor(self, entity, mentor_id):
966 """Changes the mentor to the given link_id.
968 Args:
969 entity: Student Proposal entity
970 mentor_id: Link ID of the mentor that needs to be assigned
971 Iff not given then removes the assigned mentor
974 if entity.mentor and entity.mentor.link_id == mentor_id:
975 # no need to change
976 return
978 if mentor_id:
979 # try to locate the mentor
980 fields = {'link_id': mentor_id,
981 'scope': entity.org,
982 'status': 'active'}
984 mentor_entity = mentor_logic.logic.getForFields(fields, unique=True)
986 if not mentor_entity:
987 # no mentor found, do not update
988 return
989 else:
990 # reset to None
991 mentor_entity = None
993 # update the proposal
994 properties = {'mentor': mentor_entity}
995 self._logic.updateEntityProperties(entity, properties)
997 def _createReviewFor(self, entity, reviewer, comment,
998 score=0, is_public=True):
999 """Creates a review for the given proposal and sends
1000 out a message to all followers.
1002 Args:
1003 entity: Student Proposal entity for which the review should be created
1004 reviewer: A role entity of the reviewer (if possible, else None)
1005 comment: The textual contents of the review
1006 score: The score of the review (only used if the review is not public)
1007 is_public: Determines if the review is a public review
1010 from soc.logic.helper import notifications as notifications_helper
1011 from soc.logic.models.review import logic as review_logic
1012 from soc.logic.models.review_follower import logic as review_follower_logic
1014 # create the fields for the review entity
1015 fields = {'link_id': 't%i' % (int(time.time()*100)),
1016 'scope': entity,
1017 'scope_path': entity.key().name(),
1018 'author': user_logic.logic.getForCurrentAccount(),
1019 'content': comment,
1020 'is_public': is_public,
1021 'reviewer': reviewer
1024 # add the given score if the review is not public
1025 if not is_public:
1026 fields['score'] = score
1028 # create a new Review
1029 key_name = review_logic.getKeyNameFromFields(fields)
1030 review_entity = review_logic.updateOrCreateFromKeyName(fields, key_name)
1032 # get all followers
1033 fields = {'scope': entity}
1035 if is_public:
1036 fields['subscribed_public'] = True
1037 else:
1038 fields['subscribed_private'] = True
1040 followers = review_follower_logic.getForFields(fields)
1042 if is_public:
1043 # redirect to public page
1044 redirect_url = redirects.getPublicRedirect(entity, self._params)
1045 else:
1046 # redirect to review page
1047 redirect_url = redirects.getReviewRedirect(entity, self._params)
1049 for follower in followers:
1050 # sent to every follower except the reviewer
1051 if follower.user.key() != review_entity.author.key():
1052 notifications_helper.sendNewReviewNotification(follower.user,
1053 review_entity, entity.title, redirect_url)
1056 view = View()
1058 admin = decorators.view(view.admin)
1059 apply = decorators.view(view.apply)
1060 create = decorators.view(view.create)
1061 delete = decorators.view(view.delete)
1062 edit = decorators.view(view.edit)
1063 list = decorators.view(view.list)
1064 list_orgs = decorators.view(view.listOrgs)
1065 list_self = decorators.view(view.listSelf)
1066 public = decorators.view(view.public)
1067 review = decorators.view(view.review)
1068 export = decorators.view(view.export)
1069 pick = decorators.view(view.pick)